gtsocial-umbx

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

jws.go (7455B)


      1 // Copyright 2015 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package acme
      6 
      7 import (
      8 	"crypto"
      9 	"crypto/ecdsa"
     10 	"crypto/hmac"
     11 	"crypto/rand"
     12 	"crypto/rsa"
     13 	"crypto/sha256"
     14 	_ "crypto/sha512" // need for EC keys
     15 	"encoding/asn1"
     16 	"encoding/base64"
     17 	"encoding/json"
     18 	"errors"
     19 	"fmt"
     20 	"math/big"
     21 )
     22 
     23 // KeyID is the account key identity provided by a CA during registration.
     24 type KeyID string
     25 
     26 // noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
     27 // See jwsEncodeJSON for details.
     28 const noKeyID = KeyID("")
     29 
     30 // noPayload indicates jwsEncodeJSON will encode zero-length octet string
     31 // in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
     32 // authenticated GET requests via POSTing with an empty payload.
     33 // See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
     34 const noPayload = ""
     35 
     36 // noNonce indicates that the nonce should be omitted from the protected header.
     37 // See jwsEncodeJSON for details.
     38 const noNonce = ""
     39 
     40 // jsonWebSignature can be easily serialized into a JWS following
     41 // https://tools.ietf.org/html/rfc7515#section-3.2.
     42 type jsonWebSignature struct {
     43 	Protected string `json:"protected"`
     44 	Payload   string `json:"payload"`
     45 	Sig       string `json:"signature"`
     46 }
     47 
     48 // jwsEncodeJSON signs claimset using provided key and a nonce.
     49 // The result is serialized in JSON format containing either kid or jwk
     50 // fields based on the provided KeyID value.
     51 //
     52 // The claimset is marshalled using json.Marshal unless it is a string.
     53 // In which case it is inserted directly into the message.
     54 //
     55 // If kid is non-empty, its quoted value is inserted in the protected header
     56 // as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
     57 // as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
     58 //
     59 // If nonce is non-empty, its quoted value is inserted in the protected header.
     60 //
     61 // See https://tools.ietf.org/html/rfc7515#section-7.
     62 func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
     63 	if key == nil {
     64 		return nil, errors.New("nil key")
     65 	}
     66 	alg, sha := jwsHasher(key.Public())
     67 	if alg == "" || !sha.Available() {
     68 		return nil, ErrUnsupportedKey
     69 	}
     70 	headers := struct {
     71 		Alg   string          `json:"alg"`
     72 		KID   string          `json:"kid,omitempty"`
     73 		JWK   json.RawMessage `json:"jwk,omitempty"`
     74 		Nonce string          `json:"nonce,omitempty"`
     75 		URL   string          `json:"url"`
     76 	}{
     77 		Alg:   alg,
     78 		Nonce: nonce,
     79 		URL:   url,
     80 	}
     81 	switch kid {
     82 	case noKeyID:
     83 		jwk, err := jwkEncode(key.Public())
     84 		if err != nil {
     85 			return nil, err
     86 		}
     87 		headers.JWK = json.RawMessage(jwk)
     88 	default:
     89 		headers.KID = string(kid)
     90 	}
     91 	phJSON, err := json.Marshal(headers)
     92 	if err != nil {
     93 		return nil, err
     94 	}
     95 	phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
     96 	var payload string
     97 	if val, ok := claimset.(string); ok {
     98 		payload = val
     99 	} else {
    100 		cs, err := json.Marshal(claimset)
    101 		if err != nil {
    102 			return nil, err
    103 		}
    104 		payload = base64.RawURLEncoding.EncodeToString(cs)
    105 	}
    106 	hash := sha.New()
    107 	hash.Write([]byte(phead + "." + payload))
    108 	sig, err := jwsSign(key, sha, hash.Sum(nil))
    109 	if err != nil {
    110 		return nil, err
    111 	}
    112 	enc := jsonWebSignature{
    113 		Protected: phead,
    114 		Payload:   payload,
    115 		Sig:       base64.RawURLEncoding.EncodeToString(sig),
    116 	}
    117 	return json.Marshal(&enc)
    118 }
    119 
    120 // jwsWithMAC creates and signs a JWS using the given key and the HS256
    121 // algorithm. kid and url are included in the protected header. rawPayload
    122 // should not be base64-URL-encoded.
    123 func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
    124 	if len(key) == 0 {
    125 		return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
    126 	}
    127 	header := struct {
    128 		Algorithm string `json:"alg"`
    129 		KID       string `json:"kid"`
    130 		URL       string `json:"url,omitempty"`
    131 	}{
    132 		// Only HMAC-SHA256 is supported.
    133 		Algorithm: "HS256",
    134 		KID:       kid,
    135 		URL:       url,
    136 	}
    137 	rawProtected, err := json.Marshal(header)
    138 	if err != nil {
    139 		return nil, err
    140 	}
    141 	protected := base64.RawURLEncoding.EncodeToString(rawProtected)
    142 	payload := base64.RawURLEncoding.EncodeToString(rawPayload)
    143 
    144 	h := hmac.New(sha256.New, key)
    145 	if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
    146 		return nil, err
    147 	}
    148 	mac := h.Sum(nil)
    149 
    150 	return &jsonWebSignature{
    151 		Protected: protected,
    152 		Payload:   payload,
    153 		Sig:       base64.RawURLEncoding.EncodeToString(mac),
    154 	}, nil
    155 }
    156 
    157 // jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
    158 // The result is also suitable for creating a JWK thumbprint.
    159 // https://tools.ietf.org/html/rfc7517
    160 func jwkEncode(pub crypto.PublicKey) (string, error) {
    161 	switch pub := pub.(type) {
    162 	case *rsa.PublicKey:
    163 		// https://tools.ietf.org/html/rfc7518#section-6.3.1
    164 		n := pub.N
    165 		e := big.NewInt(int64(pub.E))
    166 		// Field order is important.
    167 		// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
    168 		return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
    169 			base64.RawURLEncoding.EncodeToString(e.Bytes()),
    170 			base64.RawURLEncoding.EncodeToString(n.Bytes()),
    171 		), nil
    172 	case *ecdsa.PublicKey:
    173 		// https://tools.ietf.org/html/rfc7518#section-6.2.1
    174 		p := pub.Curve.Params()
    175 		n := p.BitSize / 8
    176 		if p.BitSize%8 != 0 {
    177 			n++
    178 		}
    179 		x := pub.X.Bytes()
    180 		if n > len(x) {
    181 			x = append(make([]byte, n-len(x)), x...)
    182 		}
    183 		y := pub.Y.Bytes()
    184 		if n > len(y) {
    185 			y = append(make([]byte, n-len(y)), y...)
    186 		}
    187 		// Field order is important.
    188 		// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
    189 		return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
    190 			p.Name,
    191 			base64.RawURLEncoding.EncodeToString(x),
    192 			base64.RawURLEncoding.EncodeToString(y),
    193 		), nil
    194 	}
    195 	return "", ErrUnsupportedKey
    196 }
    197 
    198 // jwsSign signs the digest using the given key.
    199 // The hash is unused for ECDSA keys.
    200 func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
    201 	switch pub := key.Public().(type) {
    202 	case *rsa.PublicKey:
    203 		return key.Sign(rand.Reader, digest, hash)
    204 	case *ecdsa.PublicKey:
    205 		sigASN1, err := key.Sign(rand.Reader, digest, hash)
    206 		if err != nil {
    207 			return nil, err
    208 		}
    209 
    210 		var rs struct{ R, S *big.Int }
    211 		if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
    212 			return nil, err
    213 		}
    214 
    215 		rb, sb := rs.R.Bytes(), rs.S.Bytes()
    216 		size := pub.Params().BitSize / 8
    217 		if size%8 > 0 {
    218 			size++
    219 		}
    220 		sig := make([]byte, size*2)
    221 		copy(sig[size-len(rb):], rb)
    222 		copy(sig[size*2-len(sb):], sb)
    223 		return sig, nil
    224 	}
    225 	return nil, ErrUnsupportedKey
    226 }
    227 
    228 // jwsHasher indicates suitable JWS algorithm name and a hash function
    229 // to use for signing a digest with the provided key.
    230 // It returns ("", 0) if the key is not supported.
    231 func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
    232 	switch pub := pub.(type) {
    233 	case *rsa.PublicKey:
    234 		return "RS256", crypto.SHA256
    235 	case *ecdsa.PublicKey:
    236 		switch pub.Params().Name {
    237 		case "P-256":
    238 			return "ES256", crypto.SHA256
    239 		case "P-384":
    240 			return "ES384", crypto.SHA384
    241 		case "P-521":
    242 			return "ES512", crypto.SHA512
    243 		}
    244 	}
    245 	return "", 0
    246 }
    247 
    248 // JWKThumbprint creates a JWK thumbprint out of pub
    249 // as specified in https://tools.ietf.org/html/rfc7638.
    250 func JWKThumbprint(pub crypto.PublicKey) (string, error) {
    251 	jwk, err := jwkEncode(pub)
    252 	if err != nil {
    253 		return "", err
    254 	}
    255 	b := sha256.Sum256([]byte(jwk))
    256 	return base64.RawURLEncoding.EncodeToString(b[:]), nil
    257 }