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/elliptic"
11	"crypto/rsa"
12	"crypto/x509"
13	"encoding/base64"
14	"encoding/json"
15	"encoding/pem"
16	"fmt"
17	"io"
18	"math/big"
19	"testing"
20)
21
22const (
23	testKeyPEM = `
24-----BEGIN RSA PRIVATE KEY-----
25MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
26WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30
27Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq
28EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf
29oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy
30KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV
319IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H
32r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm
33ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP
34G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS
35zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6
369gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s
378Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc
387FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL
39qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ
40Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU
41RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o
42JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd
434gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt
44jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q
45YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73
46c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G
47N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7
48EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO
499XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx
50-----END RSA PRIVATE KEY-----
51`
52
53	// This thumbprint is for the testKey defined above.
54	testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
55
56	// openssl ecparam -name secp256k1 -genkey -noout
57	testKeyECPEM = `
58-----BEGIN EC PRIVATE KEY-----
59MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49
60AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5
61QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ==
62-----END EC PRIVATE KEY-----
63`
64	// openssl ecparam -name secp384r1 -genkey -noout
65	testKeyEC384PEM = `
66-----BEGIN EC PRIVATE KEY-----
67MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD
68Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj
69JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke
70WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg=
71-----END EC PRIVATE KEY-----
72`
73	// openssl ecparam -name secp521r1 -genkey -noout
74	testKeyEC512PEM = `
75-----BEGIN EC PRIVATE KEY-----
76MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z
77KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx
787JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD
79FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd
80GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
81-----END EC PRIVATE KEY-----
82`
83	// 1. openssl ec -in key.pem -noout -text
84	// 2. remove first byte, 04 (the header); the rest is X and Y
85	// 3. convert each with: echo <val> | xxd -r -p | base64 -w 100 | tr -d '=' | tr '/+' '_-'
86	testKeyECPubX    = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
87	testKeyECPubY    = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
88	testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt"
89	testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo"
90	testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY"
91	testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax"
92
93	// echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
94	// openssl dgst -binary -sha256 | base64 | tr -d '=' | tr '/+' '_-'
95	testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
96)
97
98var (
99	testKey      *rsa.PrivateKey
100	testKeyEC    *ecdsa.PrivateKey
101	testKeyEC384 *ecdsa.PrivateKey
102	testKeyEC512 *ecdsa.PrivateKey
103)
104
105func init() {
106	testKey = parseRSA(testKeyPEM, "testKeyPEM")
107	testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM")
108	testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM")
109	testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM")
110}
111
112func decodePEM(s, name string) []byte {
113	d, _ := pem.Decode([]byte(s))
114	if d == nil {
115		panic("no block found in " + name)
116	}
117	return d.Bytes
118}
119
120func parseRSA(s, name string) *rsa.PrivateKey {
121	b := decodePEM(s, name)
122	k, err := x509.ParsePKCS1PrivateKey(b)
123	if err != nil {
124		panic(fmt.Sprintf("%s: %v", name, err))
125	}
126	return k
127}
128
129func parseEC(s, name string) *ecdsa.PrivateKey {
130	b := decodePEM(s, name)
131	k, err := x509.ParseECPrivateKey(b)
132	if err != nil {
133		panic(fmt.Sprintf("%s: %v", name, err))
134	}
135	return k
136}
137
138func TestJWSEncodeJSON(t *testing.T) {
139	claims := struct{ Msg string }{"Hello JWS"}
140	// JWS signed with testKey and "nonce" as the nonce value
141	// JSON-serialized JWS fields are split for easier testing
142	const (
143		// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"}
144		protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
145			"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
146			"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
147			"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
148			"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
149			"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
150			"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
151			"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
152			"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
153			"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
154			"UVEifSwibm9uY2UiOiJub25jZSJ9"
155		// {"Msg":"Hello JWS"}
156		payload   = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
157		signature = "eAGUikStX_UxyiFhxSLMyuyBcIB80GeBkFROCpap2sW3EmkU_ggF" +
158			"knaQzxrTfItICSAXsCLIquZ5BbrSWA_4vdEYrwWtdUj7NqFKjHRa" +
159			"zpLHcoR7r1rEHvkoP1xj49lS5fc3Wjjq8JUhffkhGbWZ8ZVkgPdC" +
160			"4tMBWiQDoth-x8jELP_3LYOB_ScUXi2mETBawLgOT2K8rA0Vbbmx" +
161			"hWNlOWuUf-8hL5YX4IOEwsS8JK_TrTq5Zc9My0zHJmaieqDV0UlP" +
162			"k0onFjPFkGm7MrPSgd0MqRG-4vSAg2O4hDo7rKv4n8POjjXlNQvM" +
163			"9IPLr8qZ7usYBKhEGwX3yq_eicAwBw"
164	)
165
166	b, err := jwsEncodeJSON(claims, testKey, "nonce")
167	if err != nil {
168		t.Fatal(err)
169	}
170	var jws struct{ Protected, Payload, Signature string }
171	if err := json.Unmarshal(b, &jws); err != nil {
172		t.Fatal(err)
173	}
174	if jws.Protected != protected {
175		t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
176	}
177	if jws.Payload != payload {
178		t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
179	}
180	if jws.Signature != signature {
181		t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature)
182	}
183}
184
185func TestJWSEncodeJSONEC(t *testing.T) {
186	tt := []struct {
187		key      *ecdsa.PrivateKey
188		x, y     string
189		alg, crv string
190	}{
191		{testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"},
192		{testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"},
193		{testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"},
194	}
195	for i, test := range tt {
196		claims := struct{ Msg string }{"Hello JWS"}
197		b, err := jwsEncodeJSON(claims, test.key, "nonce")
198		if err != nil {
199			t.Errorf("%d: %v", i, err)
200			continue
201		}
202		var jws struct{ Protected, Payload, Signature string }
203		if err := json.Unmarshal(b, &jws); err != nil {
204			t.Errorf("%d: %v", i, err)
205			continue
206		}
207
208		b, err = base64.RawURLEncoding.DecodeString(jws.Protected)
209		if err != nil {
210			t.Errorf("%d: jws.Protected: %v", i, err)
211		}
212		var head struct {
213			Alg   string
214			Nonce string
215			JWK   struct {
216				Crv string
217				Kty string
218				X   string
219				Y   string
220			} `json:"jwk"`
221		}
222		if err := json.Unmarshal(b, &head); err != nil {
223			t.Errorf("%d: jws.Protected: %v", i, err)
224		}
225		if head.Alg != test.alg {
226			t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg)
227		}
228		if head.Nonce != "nonce" {
229			t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce)
230		}
231		if head.JWK.Crv != test.crv {
232			t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv)
233		}
234		if head.JWK.Kty != "EC" {
235			t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty)
236		}
237		if head.JWK.X != test.x {
238			t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x)
239		}
240		if head.JWK.Y != test.y {
241			t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y)
242		}
243	}
244}
245
246type customTestSigner struct {
247	sig []byte
248	pub crypto.PublicKey
249}
250
251func (s *customTestSigner) Public() crypto.PublicKey { return s.pub }
252func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) {
253	return s.sig, nil
254}
255
256func TestJWSEncodeJSONCustom(t *testing.T) {
257	claims := struct{ Msg string }{"hello"}
258	const (
259		// printf '{"Msg":"hello"}' | base64 | tr -d '=' | tr '/+' '_-'
260		payload = "eyJNc2ciOiJoZWxsbyJ9"
261		// printf 'testsig' | base64 | tr -d '='
262		testsig = "dGVzdHNpZw"
263
264		// printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":<testKeyECPubY>,"y":<testKeyECPubY>,"nonce":"nonce"}' | \
265		// base64 | tr -d '=' | tr '/+' '_-'
266		es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjVsaEV1" +
267			"ZzV4SzR4QkRaMm5BYmF4THRhTGl2ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFH" +
268			"a3YwVGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6Im5vbmNlIn0"
269
270		// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce"}
271		rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
272			"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
273			"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
274			"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
275			"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
276			"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
277			"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
278			"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
279			"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
280			"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
281			"UVEifSwibm9uY2UiOiJub25jZSJ9"
282	)
283
284	tt := []struct {
285		alg, phead string
286		pub        crypto.PublicKey
287	}{
288		{"RS256", rs256phead, testKey.Public()},
289		{"ES256", es256phead, testKeyEC.Public()},
290	}
291	for _, tc := range tt {
292		tc := tc
293		t.Run(tc.alg, func(t *testing.T) {
294			signer := &customTestSigner{
295				sig: []byte("testsig"),
296				pub: tc.pub,
297			}
298			b, err := jwsEncodeJSON(claims, signer, "nonce")
299			if err != nil {
300				t.Fatal(err)
301			}
302			var j struct{ Protected, Payload, Signature string }
303			if err := json.Unmarshal(b, &j); err != nil {
304				t.Fatal(err)
305			}
306			if j.Protected != tc.phead {
307				t.Errorf("j.Protected = %q\nwant %q", j.Protected, tc.phead)
308			}
309			if j.Payload != payload {
310				t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload)
311			}
312			if j.Signature != testsig {
313				t.Errorf("j.Signature = %q\nwant %q", j.Signature, testsig)
314			}
315		})
316	}
317}
318
319func TestJWKThumbprintRSA(t *testing.T) {
320	// Key example from RFC 7638
321	const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
322		"VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" +
323		"4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" +
324		"W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" +
325		"1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" +
326		"aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
327	const base64E = "AQAB"
328	const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
329
330	b, err := base64.RawURLEncoding.DecodeString(base64N)
331	if err != nil {
332		t.Fatalf("Error parsing example key N: %v", err)
333	}
334	n := new(big.Int).SetBytes(b)
335
336	b, err = base64.RawURLEncoding.DecodeString(base64E)
337	if err != nil {
338		t.Fatalf("Error parsing example key E: %v", err)
339	}
340	e := new(big.Int).SetBytes(b)
341
342	pub := &rsa.PublicKey{N: n, E: int(e.Uint64())}
343	th, err := JWKThumbprint(pub)
344	if err != nil {
345		t.Error(err)
346	}
347	if th != expected {
348		t.Errorf("thumbprint = %q; want %q", th, expected)
349	}
350}
351
352func TestJWKThumbprintEC(t *testing.T) {
353	// Key example from RFC 7520
354	// expected was computed with
355	// echo -n '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \
356	// openssl dgst -binary -sha256 | \
357	// base64 | \
358	// tr -d '=' | tr '/+' '_-'
359	const (
360		base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" +
361			"KqjqvjyekWF-7ytDyRXYgCF5cj0Kt"
362		base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" +
363			"QkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
364		expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M"
365	)
366
367	b, err := base64.RawURLEncoding.DecodeString(base64X)
368	if err != nil {
369		t.Fatalf("Error parsing example key X: %v", err)
370	}
371	x := new(big.Int).SetBytes(b)
372
373	b, err = base64.RawURLEncoding.DecodeString(base64Y)
374	if err != nil {
375		t.Fatalf("Error parsing example key Y: %v", err)
376	}
377	y := new(big.Int).SetBytes(b)
378
379	pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}
380	th, err := JWKThumbprint(pub)
381	if err != nil {
382		t.Error(err)
383	}
384	if th != expected {
385		t.Errorf("thumbprint = %q; want %q", th, expected)
386	}
387}
388
389func TestJWKThumbprintErrUnsupportedKey(t *testing.T) {
390	_, err := JWKThumbprint(struct{}{})
391	if err != ErrUnsupportedKey {
392		t.Errorf("err = %q; want %q", err, ErrUnsupportedKey)
393	}
394}
395