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/rand" 11 "crypto/rsa" 12 "crypto/sha256" 13 _ "crypto/sha512" // need for EC keys 14 "encoding/asn1" 15 "encoding/base64" 16 "encoding/json" 17 "fmt" 18 "math/big" 19) 20 21// keyID is the account identity provided by a CA during registration. 22type keyID string 23 24// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID. 25// See jwsEncodeJSON for details. 26const noKeyID = keyID("") 27 28// noPayload indicates jwsEncodeJSON will encode zero-length octet string 29// in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make 30// authenticated GET requests via POSTing with an empty payload. 31// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details. 32const noPayload = "" 33 34// jwsEncodeJSON signs claimset using provided key and a nonce. 35// The result is serialized in JSON format containing either kid or jwk 36// fields based on the provided keyID value. 37// 38// If kid is non-empty, its quoted value is inserted in the protected head 39// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted 40// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive. 41// 42// See https://tools.ietf.org/html/rfc7515#section-7. 43func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) { 44 alg, sha := jwsHasher(key.Public()) 45 if alg == "" || !sha.Available() { 46 return nil, ErrUnsupportedKey 47 } 48 var phead string 49 switch kid { 50 case noKeyID: 51 jwk, err := jwkEncode(key.Public()) 52 if err != nil { 53 return nil, err 54 } 55 phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url) 56 default: 57 phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url) 58 } 59 phead = base64.RawURLEncoding.EncodeToString([]byte(phead)) 60 var payload string 61 if claimset != noPayload { 62 cs, err := json.Marshal(claimset) 63 if err != nil { 64 return nil, err 65 } 66 payload = base64.RawURLEncoding.EncodeToString(cs) 67 } 68 hash := sha.New() 69 hash.Write([]byte(phead + "." + payload)) 70 sig, err := jwsSign(key, sha, hash.Sum(nil)) 71 if err != nil { 72 return nil, err 73 } 74 75 enc := struct { 76 Protected string `json:"protected"` 77 Payload string `json:"payload"` 78 Sig string `json:"signature"` 79 }{ 80 Protected: phead, 81 Payload: payload, 82 Sig: base64.RawURLEncoding.EncodeToString(sig), 83 } 84 return json.Marshal(&enc) 85} 86 87// jwkEncode encodes public part of an RSA or ECDSA key into a JWK. 88// The result is also suitable for creating a JWK thumbprint. 89// https://tools.ietf.org/html/rfc7517 90func jwkEncode(pub crypto.PublicKey) (string, error) { 91 switch pub := pub.(type) { 92 case *rsa.PublicKey: 93 // https://tools.ietf.org/html/rfc7518#section-6.3.1 94 n := pub.N 95 e := big.NewInt(int64(pub.E)) 96 // Field order is important. 97 // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. 98 return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, 99 base64.RawURLEncoding.EncodeToString(e.Bytes()), 100 base64.RawURLEncoding.EncodeToString(n.Bytes()), 101 ), nil 102 case *ecdsa.PublicKey: 103 // https://tools.ietf.org/html/rfc7518#section-6.2.1 104 p := pub.Curve.Params() 105 n := p.BitSize / 8 106 if p.BitSize%8 != 0 { 107 n++ 108 } 109 x := pub.X.Bytes() 110 if n > len(x) { 111 x = append(make([]byte, n-len(x)), x...) 112 } 113 y := pub.Y.Bytes() 114 if n > len(y) { 115 y = append(make([]byte, n-len(y)), y...) 116 } 117 // Field order is important. 118 // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. 119 return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, 120 p.Name, 121 base64.RawURLEncoding.EncodeToString(x), 122 base64.RawURLEncoding.EncodeToString(y), 123 ), nil 124 } 125 return "", ErrUnsupportedKey 126} 127 128// jwsSign signs the digest using the given key. 129// The hash is unused for ECDSA keys. 130func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) { 131 switch pub := key.Public().(type) { 132 case *rsa.PublicKey: 133 return key.Sign(rand.Reader, digest, hash) 134 case *ecdsa.PublicKey: 135 sigASN1, err := key.Sign(rand.Reader, digest, hash) 136 if err != nil { 137 return nil, err 138 } 139 140 var rs struct{ R, S *big.Int } 141 if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil { 142 return nil, err 143 } 144 145 rb, sb := rs.R.Bytes(), rs.S.Bytes() 146 size := pub.Params().BitSize / 8 147 if size%8 > 0 { 148 size++ 149 } 150 sig := make([]byte, size*2) 151 copy(sig[size-len(rb):], rb) 152 copy(sig[size*2-len(sb):], sb) 153 return sig, nil 154 } 155 return nil, ErrUnsupportedKey 156} 157 158// jwsHasher indicates suitable JWS algorithm name and a hash function 159// to use for signing a digest with the provided key. 160// It returns ("", 0) if the key is not supported. 161func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) { 162 switch pub := pub.(type) { 163 case *rsa.PublicKey: 164 return "RS256", crypto.SHA256 165 case *ecdsa.PublicKey: 166 switch pub.Params().Name { 167 case "P-256": 168 return "ES256", crypto.SHA256 169 case "P-384": 170 return "ES384", crypto.SHA384 171 case "P-521": 172 return "ES512", crypto.SHA512 173 } 174 } 175 return "", 0 176} 177 178// JWKThumbprint creates a JWK thumbprint out of pub 179// as specified in https://tools.ietf.org/html/rfc7638. 180func JWKThumbprint(pub crypto.PublicKey) (string, error) { 181 jwk, err := jwkEncode(pub) 182 if err != nil { 183 return "", err 184 } 185 b := sha256.Sum256([]byte(jwk)) 186 return base64.RawURLEncoding.EncodeToString(b[:]), nil 187} 188