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/sha256"
13	"crypto/x509"
14	"encoding/base64"
15	"encoding/json"
16	"encoding/pem"
17	"fmt"
18	"io"
19	"math/big"
20	"testing"
21)
22
23// The following shell command alias is used in the comments
24// throughout this file:
25// alias b64raw="base64 -w0 | tr -d '=' | tr '/+' '_-'"
26
27const (
28	// Modulus in raw base64:
29	// 4xgZ3eRPkwoRvy7qeRUbmMDe0V-xH9eWLdu0iheeLlrmD2mqWXfP9IeSKApbn34
30	// g8TuAS9g5zhq8ELQ3kmjr-KV86GAMgI6VAcGlq3QrzpTCf_30Ab7-zawrfRaFON
31	// a1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosqEXeaIkVYBEhbh
32	// Nu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZfoyFyek380mHg
33	// JAumQ_I2fjj98_97mk3ihOY4AgVdCDj1z_GCoZkG5Rq7nbCGyosyKWyDX00Zs-n
34	// NqVhoLeIvXC4nnWdJMZ6rogxyQQ
35	testKeyPEM = `
36-----BEGIN RSA PRIVATE KEY-----
37MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
38WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30
39Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq
40EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf
41oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy
42KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV
439IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H
44r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm
45ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP
46G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS
47zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6
489gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s
498Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc
507FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL
51qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ
52Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU
53RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o
54JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd
554gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt
56jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q
57YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73
58c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G
59N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7
60EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO
619XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx
62-----END RSA PRIVATE KEY-----
63`
64
65	// This thumbprint is for the testKey defined above.
66	testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
67
68	// openssl ecparam -name secp256k1 -genkey -noout
69	testKeyECPEM = `
70-----BEGIN EC PRIVATE KEY-----
71MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49
72AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5
73QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ==
74-----END EC PRIVATE KEY-----
75`
76	// openssl ecparam -name secp384r1 -genkey -noout
77	testKeyEC384PEM = `
78-----BEGIN EC PRIVATE KEY-----
79MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD
80Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj
81JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke
82WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg=
83-----END EC PRIVATE KEY-----
84`
85	// openssl ecparam -name secp521r1 -genkey -noout
86	testKeyEC512PEM = `
87-----BEGIN EC PRIVATE KEY-----
88MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z
89KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx
907JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD
91FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd
92GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
93-----END EC PRIVATE KEY-----
94`
95	// 1. openssl ec -in key.pem -noout -text
96	// 2. remove first byte, 04 (the header); the rest is X and Y
97	// 3. convert each with: echo <val> | xxd -r -p | b64raw
98	testKeyECPubX    = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
99	testKeyECPubY    = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
100	testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt"
101	testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo"
102	testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY"
103	testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax"
104
105	// echo -n '{"crv":"P-256","kty":"EC","x":"<testKeyECPubX>","y":"<testKeyECPubY>"}' | \
106	// openssl dgst -binary -sha256 | b64raw
107	testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
108)
109
110var (
111	testKey      *rsa.PrivateKey
112	testKeyEC    *ecdsa.PrivateKey
113	testKeyEC384 *ecdsa.PrivateKey
114	testKeyEC512 *ecdsa.PrivateKey
115)
116
117func init() {
118	testKey = parseRSA(testKeyPEM, "testKeyPEM")
119	testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM")
120	testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM")
121	testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM")
122}
123
124func decodePEM(s, name string) []byte {
125	d, _ := pem.Decode([]byte(s))
126	if d == nil {
127		panic("no block found in " + name)
128	}
129	return d.Bytes
130}
131
132func parseRSA(s, name string) *rsa.PrivateKey {
133	b := decodePEM(s, name)
134	k, err := x509.ParsePKCS1PrivateKey(b)
135	if err != nil {
136		panic(fmt.Sprintf("%s: %v", name, err))
137	}
138	return k
139}
140
141func parseEC(s, name string) *ecdsa.PrivateKey {
142	b := decodePEM(s, name)
143	k, err := x509.ParseECPrivateKey(b)
144	if err != nil {
145		panic(fmt.Sprintf("%s: %v", name, err))
146	}
147	return k
148}
149
150func TestJWSEncodeJSON(t *testing.T) {
151	claims := struct{ Msg string }{"Hello JWS"}
152	// JWS signed with testKey and "nonce" as the nonce value
153	// JSON-serialized JWS fields are split for easier testing
154	const (
155		// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
156		protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
157			"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
158			"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
159			"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
160			"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
161			"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
162			"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
163			"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
164			"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
165			"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
166			"UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
167		// {"Msg":"Hello JWS"}
168		payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
169		// printf '<protected>.<payload>' | openssl dgst -binary -sha256 -sign testKey | b64raw
170		signature = "YFyl_xz1E7TR-3E1bIuASTr424EgCvBHjt25WUFC2VaDjXYV0Rj_" +
171			"Hd3dJ_2IRqBrXDZZ2n4ZeA_4mm3QFwmwyeDwe2sWElhb82lCZ8iX" +
172			"uFnjeOmSOjx-nWwPa5ibCXzLq13zZ-OBV1Z4oN_TuailQeRoSfA3" +
173			"nO8gG52mv1x2OMQ5MAFtt8jcngBLzts4AyhI6mBJ2w7Yaj3ZCriq" +
174			"DWA3GLFvvHdW1Ba9Z01wtGT2CuZI7DUk_6Qj1b3BkBGcoKur5C9i" +
175			"bUJtCkABwBMvBQNyD3MmXsrRFRTgvVlyU_yMaucYm7nmzEr_2PaQ" +
176			"50rFt_9qOfJ4sfbLtG1Wwae57BQx1g"
177	)
178
179	b, err := jwsEncodeJSON(claims, testKey, noKeyID, "nonce", "url")
180	if err != nil {
181		t.Fatal(err)
182	}
183	var jws struct{ Protected, Payload, Signature string }
184	if err := json.Unmarshal(b, &jws); err != nil {
185		t.Fatal(err)
186	}
187	if jws.Protected != protected {
188		t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
189	}
190	if jws.Payload != payload {
191		t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
192	}
193	if jws.Signature != signature {
194		t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature)
195	}
196}
197
198func TestJWSEncodeKID(t *testing.T) {
199	kid := keyID("https://example.org/account/1")
200	claims := struct{ Msg string }{"Hello JWS"}
201	// JWS signed with testKeyEC
202	const (
203		// {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"}
204		protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5" +
205			"vcmcvYWNjb3VudC8xIiwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
206		// {"Msg":"Hello JWS"}
207		payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
208	)
209
210	b, err := jwsEncodeJSON(claims, testKeyEC, kid, "nonce", "url")
211	if err != nil {
212		t.Fatal(err)
213	}
214	var jws struct{ Protected, Payload, Signature string }
215	if err := json.Unmarshal(b, &jws); err != nil {
216		t.Fatal(err)
217	}
218	if jws.Protected != protected {
219		t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
220	}
221	if jws.Payload != payload {
222		t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
223	}
224
225	sig, err := base64.RawURLEncoding.DecodeString(jws.Signature)
226	if err != nil {
227		t.Fatalf("jws.Signature: %v", err)
228	}
229	r, s := big.NewInt(0), big.NewInt(0)
230	r.SetBytes(sig[:len(sig)/2])
231	s.SetBytes(sig[len(sig)/2:])
232	h := sha256.Sum256([]byte(protected + "." + payload))
233	if !ecdsa.Verify(testKeyEC.Public().(*ecdsa.PublicKey), h[:], r, s) {
234		t.Error("invalid signature")
235	}
236}
237
238func TestJWSEncodeJSONEC(t *testing.T) {
239	tt := []struct {
240		key      *ecdsa.PrivateKey
241		x, y     string
242		alg, crv string
243	}{
244		{testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"},
245		{testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"},
246		{testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"},
247	}
248	for i, test := range tt {
249		claims := struct{ Msg string }{"Hello JWS"}
250		b, err := jwsEncodeJSON(claims, test.key, noKeyID, "nonce", "url")
251		if err != nil {
252			t.Errorf("%d: %v", i, err)
253			continue
254		}
255		var jws struct{ Protected, Payload, Signature string }
256		if err := json.Unmarshal(b, &jws); err != nil {
257			t.Errorf("%d: %v", i, err)
258			continue
259		}
260
261		b, err = base64.RawURLEncoding.DecodeString(jws.Protected)
262		if err != nil {
263			t.Errorf("%d: jws.Protected: %v", i, err)
264		}
265		var head struct {
266			Alg   string
267			Nonce string
268			URL   string `json:"url"`
269			KID   string `json:"kid"`
270			JWK   struct {
271				Crv string
272				Kty string
273				X   string
274				Y   string
275			} `json:"jwk"`
276		}
277		if err := json.Unmarshal(b, &head); err != nil {
278			t.Errorf("%d: jws.Protected: %v", i, err)
279		}
280		if head.Alg != test.alg {
281			t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg)
282		}
283		if head.Nonce != "nonce" {
284			t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce)
285		}
286		if head.URL != "url" {
287			t.Errorf("%d: head.URL = %q; want 'url'", i, head.URL)
288		}
289		if head.KID != "" {
290			// We used noKeyID in jwsEncodeJSON: expect no kid value.
291			t.Errorf("%d: head.KID = %q; want empty", i, head.KID)
292		}
293		if head.JWK.Crv != test.crv {
294			t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv)
295		}
296		if head.JWK.Kty != "EC" {
297			t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty)
298		}
299		if head.JWK.X != test.x {
300			t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x)
301		}
302		if head.JWK.Y != test.y {
303			t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y)
304		}
305	}
306}
307
308type customTestSigner struct {
309	sig []byte
310	pub crypto.PublicKey
311}
312
313func (s *customTestSigner) Public() crypto.PublicKey { return s.pub }
314func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) {
315	return s.sig, nil
316}
317
318func TestJWSEncodeJSONCustom(t *testing.T) {
319	claims := struct{ Msg string }{"hello"}
320	const (
321		// printf '{"Msg":"hello"}' | b64raw
322		payload = "eyJNc2ciOiJoZWxsbyJ9"
323		// printf 'testsig' | b64raw
324		testsig = "dGVzdHNpZw"
325
326		// the example P256 curve point from https://tools.ietf.org/html/rfc7515#appendix-A.3.1
327		// encoded as ASN.1…
328		es256stdsig = "MEUCIA7RIVN5Y2xIPC9/FVgH1AKjsigDOvl8fheBmsMWnqZlAiEA" +
329			"xQoH04w8cOXY8S2vCEpUgKZlkMXyk1Cajz9/ioOjVNU"
330		// …and RFC7518 (https://tools.ietf.org/html/rfc7518#section-3.4)
331		es256jwsig = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw" +
332			"5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
333
334		// printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":<testKeyECPubY>,"y":<testKeyECPubY>},"nonce":"nonce","url":"url"}' | b64raw
335		es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0" +
336			"eSI6IkVDIiwieCI6IjVsaEV1ZzV4SzR4QkRaMm5BYmF4THRhTGl2" +
337			"ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFHa3Yw" +
338			"VGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6" +
339			"Im5vbmNlIiwidXJsIjoidXJsIn0"
340
341		// {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
342		rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
343			"IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
344			"SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
345			"QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
346			"VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
347			"NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
348			"QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
349			"bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
350			"ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
351			"b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
352			"UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
353	)
354
355	tt := []struct {
356		alg, phead    string
357		pub           crypto.PublicKey
358		stdsig, jwsig string
359	}{
360		{"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig},
361		{"RS256", rs256phead, testKey.Public(), testsig, testsig},
362	}
363	for _, tc := range tt {
364		tc := tc
365		t.Run(tc.alg, func(t *testing.T) {
366			stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig)
367			if err != nil {
368				t.Errorf("couldn't decode test vector: %v", err)
369			}
370			signer := &customTestSigner{
371				sig: stdsig,
372				pub: tc.pub,
373			}
374
375			b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
376			if err != nil {
377				t.Fatal(err)
378			}
379			var j struct{ Protected, Payload, Signature string }
380			if err := json.Unmarshal(b, &j); err != nil {
381				t.Fatal(err)
382			}
383			if j.Protected != tc.phead {
384				t.Errorf("j.Protected = %q\nwant %q", j.Protected, tc.phead)
385			}
386			if j.Payload != payload {
387				t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload)
388			}
389			if j.Signature != tc.jwsig {
390				t.Errorf("j.Signature = %q\nwant %q", j.Signature, tc.jwsig)
391			}
392		})
393	}
394}
395
396func TestJWKThumbprintRSA(t *testing.T) {
397	// Key example from RFC 7638
398	const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
399		"VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" +
400		"4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" +
401		"W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" +
402		"1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" +
403		"aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
404	const base64E = "AQAB"
405	const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
406
407	b, err := base64.RawURLEncoding.DecodeString(base64N)
408	if err != nil {
409		t.Fatalf("Error parsing example key N: %v", err)
410	}
411	n := new(big.Int).SetBytes(b)
412
413	b, err = base64.RawURLEncoding.DecodeString(base64E)
414	if err != nil {
415		t.Fatalf("Error parsing example key E: %v", err)
416	}
417	e := new(big.Int).SetBytes(b)
418
419	pub := &rsa.PublicKey{N: n, E: int(e.Uint64())}
420	th, err := JWKThumbprint(pub)
421	if err != nil {
422		t.Error(err)
423	}
424	if th != expected {
425		t.Errorf("thumbprint = %q; want %q", th, expected)
426	}
427}
428
429func TestJWKThumbprintEC(t *testing.T) {
430	// Key example from RFC 7520
431	// expected was computed with
432	// printf '{"crv":"P-521","kty":"EC","x":"<base64X>","y":"<base64Y>"}' | \
433	// openssl dgst -binary -sha256 | b64raw
434	const (
435		base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" +
436			"KqjqvjyekWF-7ytDyRXYgCF5cj0Kt"
437		base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" +
438			"QkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
439		expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M"
440	)
441
442	b, err := base64.RawURLEncoding.DecodeString(base64X)
443	if err != nil {
444		t.Fatalf("Error parsing example key X: %v", err)
445	}
446	x := new(big.Int).SetBytes(b)
447
448	b, err = base64.RawURLEncoding.DecodeString(base64Y)
449	if err != nil {
450		t.Fatalf("Error parsing example key Y: %v", err)
451	}
452	y := new(big.Int).SetBytes(b)
453
454	pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}
455	th, err := JWKThumbprint(pub)
456	if err != nil {
457		t.Error(err)
458	}
459	if th != expected {
460		t.Errorf("thumbprint = %q; want %q", th, expected)
461	}
462}
463
464func TestJWKThumbprintErrUnsupportedKey(t *testing.T) {
465	_, err := JWKThumbprint(struct{}{})
466	if err != ErrUnsupportedKey {
467		t.Errorf("err = %q; want %q", err, ErrUnsupportedKey)
468	}
469}
470