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 5package acme 6 7import ( 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 identity provided by a CA during registration. 24type keyID string 25 26// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID. 27// See jwsEncodeJSON for details. 28const 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. 34const noPayload = "" 35 36// jsonWebSignature can be easily serialized into a JWS following 37// https://tools.ietf.org/html/rfc7515#section-3.2. 38type jsonWebSignature struct { 39 Protected string `json:"protected"` 40 Payload string `json:"payload"` 41 Sig string `json:"signature"` 42} 43 44// jwsEncodeJSON signs claimset using provided key and a nonce. 45// The result is serialized in JSON format containing either kid or jwk 46// fields based on the provided keyID value. 47// 48// If kid is non-empty, its quoted value is inserted in the protected head 49// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted 50// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive. 51// 52// See https://tools.ietf.org/html/rfc7515#section-7. 53func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) { 54 alg, sha := jwsHasher(key.Public()) 55 if alg == "" || !sha.Available() { 56 return nil, ErrUnsupportedKey 57 } 58 var phead string 59 switch kid { 60 case noKeyID: 61 jwk, err := jwkEncode(key.Public()) 62 if err != nil { 63 return nil, err 64 } 65 phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url) 66 default: 67 phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url) 68 } 69 phead = base64.RawURLEncoding.EncodeToString([]byte(phead)) 70 var payload string 71 if claimset != noPayload { 72 cs, err := json.Marshal(claimset) 73 if err != nil { 74 return nil, err 75 } 76 payload = base64.RawURLEncoding.EncodeToString(cs) 77 } 78 hash := sha.New() 79 hash.Write([]byte(phead + "." + payload)) 80 sig, err := jwsSign(key, sha, hash.Sum(nil)) 81 if err != nil { 82 return nil, err 83 } 84 enc := jsonWebSignature{ 85 Protected: phead, 86 Payload: payload, 87 Sig: base64.RawURLEncoding.EncodeToString(sig), 88 } 89 return json.Marshal(&enc) 90} 91 92// jwsWithMAC creates and signs a JWS using the given key and the HS256 93// algorithm. kid and url are included in the protected header. rawPayload 94// should not be base64-URL-encoded. 95func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) { 96 if len(key) == 0 { 97 return nil, errors.New("acme: cannot sign JWS with an empty MAC key") 98 } 99 header := struct { 100 Algorithm string `json:"alg"` 101 KID string `json:"kid"` 102 URL string `json:"url,omitempty"` 103 }{ 104 // Only HMAC-SHA256 is supported. 105 Algorithm: "HS256", 106 KID: kid, 107 URL: url, 108 } 109 rawProtected, err := json.Marshal(header) 110 if err != nil { 111 return nil, err 112 } 113 protected := base64.RawURLEncoding.EncodeToString(rawProtected) 114 payload := base64.RawURLEncoding.EncodeToString(rawPayload) 115 116 h := hmac.New(sha256.New, key) 117 if _, err := h.Write([]byte(protected + "." + payload)); err != nil { 118 return nil, err 119 } 120 mac := h.Sum(nil) 121 122 return &jsonWebSignature{ 123 Protected: protected, 124 Payload: payload, 125 Sig: base64.RawURLEncoding.EncodeToString(mac), 126 }, nil 127} 128 129// jwkEncode encodes public part of an RSA or ECDSA key into a JWK. 130// The result is also suitable for creating a JWK thumbprint. 131// https://tools.ietf.org/html/rfc7517 132func jwkEncode(pub crypto.PublicKey) (string, error) { 133 switch pub := pub.(type) { 134 case *rsa.PublicKey: 135 // https://tools.ietf.org/html/rfc7518#section-6.3.1 136 n := pub.N 137 e := big.NewInt(int64(pub.E)) 138 // Field order is important. 139 // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. 140 return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, 141 base64.RawURLEncoding.EncodeToString(e.Bytes()), 142 base64.RawURLEncoding.EncodeToString(n.Bytes()), 143 ), nil 144 case *ecdsa.PublicKey: 145 // https://tools.ietf.org/html/rfc7518#section-6.2.1 146 p := pub.Curve.Params() 147 n := p.BitSize / 8 148 if p.BitSize%8 != 0 { 149 n++ 150 } 151 x := pub.X.Bytes() 152 if n > len(x) { 153 x = append(make([]byte, n-len(x)), x...) 154 } 155 y := pub.Y.Bytes() 156 if n > len(y) { 157 y = append(make([]byte, n-len(y)), y...) 158 } 159 // Field order is important. 160 // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. 161 return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, 162 p.Name, 163 base64.RawURLEncoding.EncodeToString(x), 164 base64.RawURLEncoding.EncodeToString(y), 165 ), nil 166 } 167 return "", ErrUnsupportedKey 168} 169 170// jwsSign signs the digest using the given key. 171// The hash is unused for ECDSA keys. 172func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) { 173 switch pub := key.Public().(type) { 174 case *rsa.PublicKey: 175 return key.Sign(rand.Reader, digest, hash) 176 case *ecdsa.PublicKey: 177 sigASN1, err := key.Sign(rand.Reader, digest, hash) 178 if err != nil { 179 return nil, err 180 } 181 182 var rs struct{ R, S *big.Int } 183 if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil { 184 return nil, err 185 } 186 187 rb, sb := rs.R.Bytes(), rs.S.Bytes() 188 size := pub.Params().BitSize / 8 189 if size%8 > 0 { 190 size++ 191 } 192 sig := make([]byte, size*2) 193 copy(sig[size-len(rb):], rb) 194 copy(sig[size*2-len(sb):], sb) 195 return sig, nil 196 } 197 return nil, ErrUnsupportedKey 198} 199 200// jwsHasher indicates suitable JWS algorithm name and a hash function 201// to use for signing a digest with the provided key. 202// It returns ("", 0) if the key is not supported. 203func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) { 204 switch pub := pub.(type) { 205 case *rsa.PublicKey: 206 return "RS256", crypto.SHA256 207 case *ecdsa.PublicKey: 208 switch pub.Params().Name { 209 case "P-256": 210 return "ES256", crypto.SHA256 211 case "P-384": 212 return "ES384", crypto.SHA384 213 case "P-521": 214 return "ES512", crypto.SHA512 215 } 216 } 217 return "", 0 218} 219 220// JWKThumbprint creates a JWK thumbprint out of pub 221// as specified in https://tools.ietf.org/html/rfc7638. 222func JWKThumbprint(pub crypto.PublicKey) (string, error) { 223 jwk, err := jwkEncode(pub) 224 if err != nil { 225 return "", err 226 } 227 b := sha256.Sum256([]byte(jwk)) 228 return base64.RawURLEncoding.EncodeToString(b[:]), nil 229} 230