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 }