blob: b38828d85935c300c796ddb5c4bfb23a709f6178 [file] [log] [blame]
Alex Vaghin1777f3b2016-03-18 12:12:46 +00001// 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"
Anmol Sethie0d166c2016-08-04 00:47:19 -04009 "crypto/ecdsa"
James Kasten9d135272020-11-11 15:47:14 -080010 "crypto/hmac"
Alex Vaghin1777f3b2016-03-18 12:12:46 +000011 "crypto/rand"
12 "crypto/rsa"
13 "crypto/sha256"
Alex Vaghin3461a682016-08-21 13:39:17 +020014 _ "crypto/sha512" // need for EC keys
edefe7c43682019-11-25 19:39:25 +000015 "encoding/asn1"
Alex Vaghin1777f3b2016-03-18 12:12:46 +000016 "encoding/base64"
17 "encoding/json"
James Kasten9d135272020-11-11 15:47:14 -080018 "errors"
Alex Vaghin1777f3b2016-03-18 12:12:46 +000019 "fmt"
20 "math/big"
21)
22
Roland Shoemaker30dcbda2021-10-08 11:41:44 -070023// KeyID is the account key identity provided by a CA during registration.
24type KeyID string
Alex Vaghinfa1a2912019-08-23 16:55:35 +020025
26// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
27// See jwsEncodeJSON for details.
Roland Shoemaker30dcbda2021-10-08 11:41:44 -070028const noKeyID = KeyID("")
Alex Vaghinfa1a2912019-08-23 16:55:35 +020029
Alex Vaghin88343682019-08-31 21:44:59 +020030// 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
Jason Baker403b0172022-05-13 17:19:05 +000036// noNonce indicates that the nonce should be omitted from the protected header.
37// See jwsEncodeJSON for details.
38const noNonce = ""
39
James Kasten9d135272020-11-11 15:47:14 -080040// jsonWebSignature can be easily serialized into a JWS following
41// https://tools.ietf.org/html/rfc7515#section-3.2.
42type jsonWebSignature struct {
43 Protected string `json:"protected"`
44 Payload string `json:"payload"`
45 Sig string `json:"signature"`
46}
47
Alex Vaghin1777f3b2016-03-18 12:12:46 +000048// jwsEncodeJSON signs claimset using provided key and a nonce.
Alex Vaghinfa1a2912019-08-23 16:55:35 +020049// The result is serialized in JSON format containing either kid or jwk
Roland Shoemaker30dcbda2021-10-08 11:41:44 -070050// fields based on the provided KeyID value.
Alex Vaghinfa1a2912019-08-23 16:55:35 +020051//
Jason Baker403b0172022-05-13 17:19:05 +000052// 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
Alex Vaghinfa1a2912019-08-23 16:55:35 +020056// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
Alex Vaghina8328652019-08-28 23:16:55 +020057// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
Alex Vaghinfa1a2912019-08-23 16:55:35 +020058//
Jason Baker403b0172022-05-13 17:19:05 +000059// If nonce is non-empty, its quoted value is inserted in the protected header.
60//
Alex Vaghin1777f3b2016-03-18 12:12:46 +000061// See https://tools.ietf.org/html/rfc7515#section-7.
Roland Shoemaker30dcbda2021-10-08 11:41:44 -070062func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
Ben Burkert65fa2f72022-01-22 13:59:02 -050063 if key == nil {
64 return nil, errors.New("nil key")
65 }
Alex Vaghinbfa7d422018-10-26 11:44:37 -070066 alg, sha := jwsHasher(key.Public())
Alex Vaghin3461a682016-08-21 13:39:17 +020067 if alg == "" || !sha.Available() {
68 return nil, ErrUnsupportedKey
69 }
Jason Baker403b0172022-05-13 17:19:05 +000070 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 }
Alex Vaghinfa1a2912019-08-23 16:55:35 +020081 switch kid {
82 case noKeyID:
83 jwk, err := jwkEncode(key.Public())
84 if err != nil {
85 return nil, err
86 }
Jason Baker403b0172022-05-13 17:19:05 +000087 headers.JWK = json.RawMessage(jwk)
Alex Vaghinfa1a2912019-08-23 16:55:35 +020088 default:
Jason Baker403b0172022-05-13 17:19:05 +000089 headers.KID = string(kid)
Alex Vaghinfa1a2912019-08-23 16:55:35 +020090 }
Jason Baker403b0172022-05-13 17:19:05 +000091 phJSON, err := json.Marshal(headers)
92 if err != nil {
93 return nil, err
94 }
95 phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
Alex Vaghin88343682019-08-31 21:44:59 +020096 var payload string
Jason Baker403b0172022-05-13 17:19:05 +000097 if val, ok := claimset.(string); ok {
98 payload = val
99 } else {
Alex Vaghin88343682019-08-31 21:44:59 +0200100 cs, err := json.Marshal(claimset)
101 if err != nil {
102 return nil, err
103 }
104 payload = base64.RawURLEncoding.EncodeToString(cs)
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000105 }
Alex Vaghin3461a682016-08-21 13:39:17 +0200106 hash := sha.New()
107 hash.Write([]byte(phead + "." + payload))
108 sig, err := jwsSign(key, sha, hash.Sum(nil))
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000109 if err != nil {
110 return nil, err
111 }
James Kasten9d135272020-11-11 15:47:14 -0800112 enc := jsonWebSignature{
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000113 Protected: phead,
114 Payload: payload,
115 Sig: base64.RawURLEncoding.EncodeToString(sig),
116 }
117 return json.Marshal(&enc)
118}
119
Filippo Valsordaeec23a32020-12-21 17:57:36 +0100120// 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.
123func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
James Kasten9d135272020-11-11 15:47:14 -0800124 if len(key) == 0 {
125 return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
126 }
Filippo Valsordaeec23a32020-12-21 17:57:36 +0100127 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 }
James Kasten9d135272020-11-11 15:47:14 -0800141 protected := base64.RawURLEncoding.EncodeToString(rawProtected)
142 payload := base64.RawURLEncoding.EncodeToString(rawPayload)
143
Filippo Valsordaeec23a32020-12-21 17:57:36 +0100144 h := hmac.New(sha256.New, key)
145 if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
James Kasten9d135272020-11-11 15:47:14 -0800146 return nil, err
147 }
Filippo Valsordaeec23a32020-12-21 17:57:36 +0100148 mac := h.Sum(nil)
James Kasten9d135272020-11-11 15:47:14 -0800149
150 return &jsonWebSignature{
151 Protected: protected,
152 Payload: payload,
153 Sig: base64.RawURLEncoding.EncodeToString(mac),
154 }, nil
155}
156
Anmol Sethie0d166c2016-08-04 00:47:19 -0400157// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000158// The result is also suitable for creating a JWK thumbprint.
Anmol Sethie0d166c2016-08-04 00:47:19 -0400159// https://tools.ietf.org/html/rfc7517
160func 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
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000196}
197
Alex Vaghin3461a682016-08-21 13:39:17 +0200198// jwsSign signs the digest using the given key.
Alex Vaghinbfa7d422018-10-26 11:44:37 -0700199// The hash is unused for ECDSA keys.
Alex Vaghin3461a682016-08-21 13:39:17 +0200200func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
edefe7c43682019-11-25 19:39:25 +0000201 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)
Alex Vaghin3461a682016-08-21 13:39:17 +0200206 if err != nil {
207 return nil, err
208 }
edefe7c43682019-11-25 19:39:25 +0000209
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
Alex Vaghin3461a682016-08-21 13:39:17 +0200217 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 }
edefe7c43682019-11-25 19:39:25 +0000225 return nil, ErrUnsupportedKey
Alex Vaghin3461a682016-08-21 13:39:17 +0200226}
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.
Alex Vaghinbfa7d422018-10-26 11:44:37 -0700231func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
232 switch pub := pub.(type) {
233 case *rsa.PublicKey:
Alex Vaghin3461a682016-08-21 13:39:17 +0200234 return "RS256", crypto.SHA256
Alex Vaghinbfa7d422018-10-26 11:44:37 -0700235 case *ecdsa.PublicKey:
236 switch pub.Params().Name {
Alex Vaghin3461a682016-08-21 13:39:17 +0200237 case "P-256":
238 return "ES256", crypto.SHA256
239 case "P-384":
240 return "ES384", crypto.SHA384
Alex Vaghin2b0eeec2016-12-16 22:11:23 +0000241 case "P-521":
Alex Vaghin3461a682016-08-21 13:39:17 +0200242 return "ES512", crypto.SHA512
243 }
244 }
245 return "", 0
246}
247
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000248// JWKThumbprint creates a JWK thumbprint out of pub
249// as specified in https://tools.ietf.org/html/rfc7638.
Anmol Sethie0d166c2016-08-04 00:47:19 -0400250func JWKThumbprint(pub crypto.PublicKey) (string, error) {
251 jwk, err := jwkEncode(pub)
252 if err != nil {
253 return "", err
254 }
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000255 b := sha256.Sum256([]byte(jwk))
Anmol Sethie0d166c2016-08-04 00:47:19 -0400256 return base64.RawURLEncoding.EncodeToString(b[:]), nil
Alex Vaghin1777f3b2016-03-18 12:12:46 +0000257}