└── but /but: -------------------------------------------------------------------------------- 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 | } 258 | --------------------------------------------------------------------------------