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/base64" 15 "encoding/json" 16 "fmt" 17 "math/big" 18) 19 20// jwsEncodeJSON signs claimset using provided key and a nonce. 21// The result is serialized in JSON format. 22// See https://tools.ietf.org/html/rfc7515#section-7. 23func jwsEncodeJSON(claimset interface{}, key crypto.Signer, nonce string) ([]byte, error) { 24 jwk, err := jwkEncode(key.Public()) 25 if err != nil { 26 return nil, err 27 } 28 alg, sha := jwsHasher(key.Public()) 29 if alg == "" || !sha.Available() { 30 return nil, ErrUnsupportedKey 31 } 32 phead := fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q}`, alg, jwk, nonce) 33 phead = base64.RawURLEncoding.EncodeToString([]byte(phead)) 34 cs, err := json.Marshal(claimset) 35 if err != nil { 36 return nil, err 37 } 38 payload := base64.RawURLEncoding.EncodeToString(cs) 39 hash := sha.New() 40 hash.Write([]byte(phead + "." + payload)) 41 sig, err := jwsSign(key, sha, hash.Sum(nil)) 42 if err != nil { 43 return nil, err 44 } 45 46 enc := struct { 47 Protected string `json:"protected"` 48 Payload string `json:"payload"` 49 Sig string `json:"signature"` 50 }{ 51 Protected: phead, 52 Payload: payload, 53 Sig: base64.RawURLEncoding.EncodeToString(sig), 54 } 55 return json.Marshal(&enc) 56} 57 58// jwkEncode encodes public part of an RSA or ECDSA key into a JWK. 59// The result is also suitable for creating a JWK thumbprint. 60// https://tools.ietf.org/html/rfc7517 61func jwkEncode(pub crypto.PublicKey) (string, error) { 62 switch pub := pub.(type) { 63 case *rsa.PublicKey: 64 // https://tools.ietf.org/html/rfc7518#section-6.3.1 65 n := pub.N 66 e := big.NewInt(int64(pub.E)) 67 // Field order is important. 68 // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. 69 return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, 70 base64.RawURLEncoding.EncodeToString(e.Bytes()), 71 base64.RawURLEncoding.EncodeToString(n.Bytes()), 72 ), nil 73 case *ecdsa.PublicKey: 74 // https://tools.ietf.org/html/rfc7518#section-6.2.1 75 p := pub.Curve.Params() 76 n := p.BitSize / 8 77 if p.BitSize%8 != 0 { 78 n++ 79 } 80 x := pub.X.Bytes() 81 if n > len(x) { 82 x = append(make([]byte, n-len(x)), x...) 83 } 84 y := pub.Y.Bytes() 85 if n > len(y) { 86 y = append(make([]byte, n-len(y)), y...) 87 } 88 // Field order is important. 89 // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. 90 return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, 91 p.Name, 92 base64.RawURLEncoding.EncodeToString(x), 93 base64.RawURLEncoding.EncodeToString(y), 94 ), nil 95 } 96 return "", ErrUnsupportedKey 97} 98 99// jwsSign signs the digest using the given key. 100// The hash is unused for ECDSA keys. 101// 102// Note: non-stdlib crypto.Signer implementations are expected to return 103// the signature in the format as specified in RFC7518. 104// See https://tools.ietf.org/html/rfc7518 for more details. 105func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) { 106 if key, ok := key.(*ecdsa.PrivateKey); ok { 107 // The key.Sign method of ecdsa returns ASN1-encoded signature. 108 // So, we use the package Sign function instead 109 // to get R and S values directly and format the result accordingly. 110 r, s, err := ecdsa.Sign(rand.Reader, key, digest) 111 if err != nil { 112 return nil, err 113 } 114 rb, sb := r.Bytes(), s.Bytes() 115 size := key.Params().BitSize / 8 116 if size%8 > 0 { 117 size++ 118 } 119 sig := make([]byte, size*2) 120 copy(sig[size-len(rb):], rb) 121 copy(sig[size*2-len(sb):], sb) 122 return sig, nil 123 } 124 return key.Sign(rand.Reader, digest, hash) 125} 126 127// jwsHasher indicates suitable JWS algorithm name and a hash function 128// to use for signing a digest with the provided key. 129// It returns ("", 0) if the key is not supported. 130func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) { 131 switch pub := pub.(type) { 132 case *rsa.PublicKey: 133 return "RS256", crypto.SHA256 134 case *ecdsa.PublicKey: 135 switch pub.Params().Name { 136 case "P-256": 137 return "ES256", crypto.SHA256 138 case "P-384": 139 return "ES384", crypto.SHA384 140 case "P-521": 141 return "ES512", crypto.SHA512 142 } 143 } 144 return "", 0 145} 146 147// JWKThumbprint creates a JWK thumbprint out of pub 148// as specified in https://tools.ietf.org/html/rfc7638. 149func JWKThumbprint(pub crypto.PublicKey) (string, error) { 150 jwk, err := jwkEncode(pub) 151 if err != nil { 152 return "", err 153 } 154 b := sha256.Sum256([]byte(jwk)) 155 return base64.RawURLEncoding.EncodeToString(b[:]), nil 156} 157