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