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