gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

verifying.go (4744B)


      1 package httpsig
      2 
      3 import (
      4 	"crypto"
      5 	"encoding/base64"
      6 	"errors"
      7 	"fmt"
      8 	"net/http"
      9 	"strconv"
     10 	"strings"
     11 	"time"
     12 )
     13 
     14 var _ Verifier = &verifier{}
     15 
     16 type verifier struct {
     17 	header      http.Header
     18 	kId         string
     19 	signature   string
     20 	created     int64
     21 	expires     int64
     22 	headers     []string
     23 	sigStringFn func(http.Header, []string, int64, int64) (string, error)
     24 }
     25 
     26 func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64) (string, error)) (*verifier, error) {
     27 	scheme, s, err := getSignatureScheme(h)
     28 	if err != nil {
     29 		return nil, err
     30 	}
     31 	kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s)
     32 	if created != 0 {
     33 		//check if created is not in the future, we assume a maximum clock offset of 10 seconds
     34 		now := time.Now().Unix()
     35 		if created-now > 10 {
     36 			return nil, errors.New("created is in the future")
     37 		}
     38 	}
     39 	if expires != 0 {
     40 		//check if expires is in the past, we assume a maximum clock offset of 10 seconds
     41 		now := time.Now().Unix()
     42 		if now-expires > 10 {
     43 			return nil, errors.New("signature expired")
     44 		}
     45 	}
     46 	if err != nil {
     47 		return nil, err
     48 	}
     49 	return &verifier{
     50 		header:      h,
     51 		kId:         kId,
     52 		signature:   sig,
     53 		created:     created,
     54 		expires:     expires,
     55 		headers:     headers,
     56 		sigStringFn: sigStringFn,
     57 	}, nil
     58 }
     59 
     60 func (v *verifier) KeyId() string {
     61 	return v.kId
     62 }
     63 
     64 func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error {
     65 	s, err := signerFromString(string(algo))
     66 	if err == nil {
     67 		return v.asymmVerify(s, pKey)
     68 	}
     69 	m, err := macerFromString(string(algo))
     70 	if err == nil {
     71 		return v.macVerify(m, pKey)
     72 	}
     73 	return fmt.Errorf("no crypto implementation available for %q", algo)
     74 }
     75 
     76 func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error {
     77 	key, ok := pKey.([]byte)
     78 	if !ok {
     79 		return fmt.Errorf("public key for MAC verifying must be of type []byte")
     80 	}
     81 	signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires)
     82 	if err != nil {
     83 		return err
     84 	}
     85 	actualMAC, err := base64.StdEncoding.DecodeString(v.signature)
     86 	if err != nil {
     87 		return err
     88 	}
     89 	ok, err = m.Equal([]byte(signature), actualMAC, key)
     90 	if err != nil {
     91 		return err
     92 	} else if !ok {
     93 		return fmt.Errorf("invalid http signature")
     94 	}
     95 	return nil
     96 }
     97 
     98 func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error {
     99 	toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires)
    100 	if err != nil {
    101 		return err
    102 	}
    103 	signature, err := base64.StdEncoding.DecodeString(v.signature)
    104 	if err != nil {
    105 		return err
    106 	}
    107 	err = s.Verify(pKey, []byte(toHash), signature)
    108 	if err != nil {
    109 		return err
    110 	}
    111 	return nil
    112 }
    113 
    114 func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) {
    115 	s := h.Get(string(Signature))
    116 	sigHasAll := strings.Contains(s, keyIdParameter) ||
    117 		strings.Contains(s, headersParameter) ||
    118 		strings.Contains(s, signatureParameter)
    119 	a := h.Get(string(Authorization))
    120 	authHasAll := strings.Contains(a, keyIdParameter) ||
    121 		strings.Contains(a, headersParameter) ||
    122 		strings.Contains(a, signatureParameter)
    123 	if sigHasAll && authHasAll {
    124 		err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization)
    125 		return
    126 	} else if !sigHasAll && !authHasAll {
    127 		err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization)
    128 		return
    129 	} else if sigHasAll {
    130 		val = s
    131 		scheme = Signature
    132 		return
    133 	} else { // authHasAll
    134 		val = a
    135 		scheme = Authorization
    136 		return
    137 	}
    138 }
    139 
    140 func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) {
    141 	if as := scheme.authScheme(); len(as) > 0 {
    142 		s = strings.TrimPrefix(s, as+prefixSeparater)
    143 	}
    144 	params := strings.Split(s, parameterSeparater)
    145 	for _, p := range params {
    146 		kv := strings.SplitN(p, parameterKVSeparater, 2)
    147 		if len(kv) != 2 {
    148 			err = fmt.Errorf("malformed http signature parameter: %v", kv)
    149 			return
    150 		}
    151 		k := kv[0]
    152 		v := strings.Trim(kv[1], parameterValueDelimiter)
    153 		switch k {
    154 		case keyIdParameter:
    155 			kId = v
    156 		case createdKey:
    157 			created, err = strconv.ParseInt(v, 10, 64)
    158 			if err != nil {
    159 				return
    160 			}
    161 		case expiresKey:
    162 			expires, err = strconv.ParseInt(v, 10, 64)
    163 			if err != nil {
    164 				return
    165 			}
    166 		case algorithmParameter:
    167 			// Deprecated, ignore
    168 		case headersParameter:
    169 			headers = strings.Split(v, headerParameterValueDelim)
    170 		case signatureParameter:
    171 			sig = v
    172 		default:
    173 			// Ignore unrecognized parameters
    174 		}
    175 	}
    176 	if len(kId) == 0 {
    177 		err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter)
    178 	} else if len(sig) == 0 {
    179 		err = fmt.Errorf("missing %q parameter in http signature", signatureParameter)
    180 	} else if len(headers) == 0 { // Optional
    181 		headers = defaultHeaders
    182 	}
    183 	return
    184 }