1 package jwx_test
2
3 import (
4 "context"
5 "crypto/ecdsa"
6 "crypto/rsa"
7 "fmt"
8 "strings"
9 "testing"
10
11 "github.com/lestrrat-go/jwx"
12 "github.com/lestrrat-go/jwx/internal/ecutil"
13 "github.com/lestrrat-go/jwx/internal/jose"
14 "github.com/lestrrat-go/jwx/internal/json"
15 "github.com/lestrrat-go/jwx/internal/jwxtest"
16 "github.com/lestrrat-go/jwx/jwa"
17 "github.com/lestrrat-go/jwx/jwe"
18 "github.com/lestrrat-go/jwx/jwk"
19 "github.com/stretchr/testify/assert"
20 )
21
22 func TestShowBuildInfo(t *testing.T) {
23 t.Logf("Running tests using JSON backend => %s\n", json.Engine())
24 t.Logf("Available elliptic curves:")
25 for _, alg := range ecutil.AvailableAlgorithms() {
26 t.Logf(" %s", alg)
27 }
28 }
29
30 type jsonUnmarshalWrapper struct {
31 buf []byte
32 }
33
34 func (w jsonUnmarshalWrapper) Decode(v interface{}) error {
35 return json.Unmarshal(w.buf, v)
36 }
37
38 func TestDecoderSetting(t *testing.T) {
39
40 const src = `{"foo": 1}`
41 for _, useNumber := range []bool{true, false} {
42 useNumber := useNumber
43 t.Run(fmt.Sprintf("jwx.WithUseNumber(%t)", useNumber), func(t *testing.T) {
44 if useNumber {
45 jwx.DecoderSettings(jwx.WithUseNumber(useNumber))
46 t.Cleanup(func() {
47 jwx.DecoderSettings(jwx.WithUseNumber(false))
48 })
49 }
50
51
52 decoders := []struct {
53 Name string
54 Decoder interface{ Decode(interface{}) error }
55 }{
56 {Name: "Decoder", Decoder: json.NewDecoder(strings.NewReader(src))},
57 {Name: "Unmarshal", Decoder: jsonUnmarshalWrapper{buf: []byte(src)}},
58 }
59
60 for _, tc := range decoders {
61 tc := tc
62 t.Run(tc.Name, func(t *testing.T) {
63 var m map[string]interface{}
64 if !assert.NoError(t, tc.Decoder.Decode(&m), `Decode should succeed`) {
65 return
66 }
67
68 v, ok := m["foo"]
69 if !assert.True(t, ok, `m["foo"] should exist`) {
70 return
71 }
72
73 if useNumber {
74 if !assert.Equal(t, json.Number("1"), v, `v should be a json.Number object`) {
75 return
76 }
77 } else {
78 if !assert.Equal(t, float64(1), v, `v should be a float64`) {
79 return
80 }
81 }
82 })
83 }
84 })
85 }
86 }
87
88
89 func TestJoseCompatibility(t *testing.T) {
90 t.Parallel()
91
92 if testing.Short() {
93 t.Logf("Skipped during short tests")
94 return
95 }
96
97 if !jose.Available() {
98 t.Logf("`jose` binary not available, skipping tests")
99 return
100 }
101
102 t.Run("jwk", func(t *testing.T) {
103 t.Parallel()
104 testcases := []struct {
105 Name string
106 Raw interface{}
107 Template string
108 VerifyKey func(context.Context, *testing.T, jwk.Key) bool
109 }{
110 {
111 Name: "RSA Private Key (256)",
112 Raw: rsa.PrivateKey{},
113 Template: `{"alg": "RS256"}`,
114 },
115 {
116 Name: "RSA Private Key (384)",
117 Raw: rsa.PrivateKey{},
118 Template: `{"alg": "RS384"}`,
119 },
120 {
121 Name: "RSA Private Key (512)",
122 Raw: rsa.PrivateKey{},
123 Template: `{"alg": "RS512"}`,
124 },
125 {
126 Name: "RSA Private Key with Private Parameters",
127 Raw: rsa.PrivateKey{},
128 Template: `{"alg": "RS256", "x-jwx": 1234}`,
129 VerifyKey: func(ctx context.Context, t *testing.T, key jwk.Key) bool {
130 m, err := key.AsMap(ctx)
131 if !assert.NoError(t, err, `key.AsMap() should succeed`) {
132 return false
133 }
134
135 if !assert.Equal(t, float64(1234), m["x-jwx"], `private parameters should match`) {
136 return false
137 }
138
139 return true
140 },
141 },
142 }
143
144 for _, tc := range testcases {
145 tc := tc
146 t.Run(tc.Name, func(t *testing.T) {
147 t.Parallel()
148
149 ctx, cancel := context.WithCancel(context.Background())
150 defer cancel()
151
152 keyfile, cleanup, err := jose.GenerateJwk(ctx, t, tc.Template)
153 if !assert.NoError(t, err, `jose.GenerateJwk should succeed`) {
154 return
155 }
156 defer cleanup()
157
158 webkey, err := jwxtest.ParseJwkFile(ctx, keyfile)
159 if !assert.NoError(t, err, `ParseJwkFile should succeed`) {
160 return
161 }
162
163 if vk := tc.VerifyKey; vk != nil {
164 if !vk(ctx, t, webkey) {
165 return
166 }
167 }
168
169 if !assert.NoError(t, webkey.Raw(&tc.Raw), `jwk.Raw should succeed`) {
170 return
171 }
172 })
173 }
174 })
175 t.Run("jwe", func(t *testing.T) {
176 t.Parallel()
177 tests := []interopTest{
178 {jwa.RSA1_5, jwa.A128GCM},
179 {jwa.RSA1_5, jwa.A128CBC_HS256},
180 {jwa.RSA1_5, jwa.A256CBC_HS512},
181 {jwa.RSA_OAEP, jwa.A128GCM},
182 {jwa.RSA_OAEP, jwa.A128CBC_HS256},
183 {jwa.RSA_OAEP, jwa.A256CBC_HS512},
184 {jwa.RSA_OAEP_256, jwa.A128GCM},
185 {jwa.RSA_OAEP_256, jwa.A128CBC_HS256},
186 {jwa.RSA_OAEP_256, jwa.A256CBC_HS512},
187 {jwa.ECDH_ES, jwa.A128GCM},
188 {jwa.ECDH_ES, jwa.A256GCM},
189 {jwa.ECDH_ES, jwa.A128CBC_HS256},
190 {jwa.ECDH_ES, jwa.A256CBC_HS512},
191 {jwa.ECDH_ES_A128KW, jwa.A128GCM},
192 {jwa.ECDH_ES_A128KW, jwa.A128CBC_HS256},
193 {jwa.ECDH_ES_A256KW, jwa.A256GCM},
194 {jwa.ECDH_ES_A256KW, jwa.A256CBC_HS512},
195 {jwa.A128KW, jwa.A128GCM},
196 {jwa.A128KW, jwa.A128CBC_HS256},
197 {jwa.A256KW, jwa.A256GCM},
198 {jwa.A256KW, jwa.A256CBC_HS512},
199 {jwa.A128GCMKW, jwa.A128GCM},
200 {jwa.A128GCMKW, jwa.A128CBC_HS256},
201 {jwa.A256GCMKW, jwa.A256GCM},
202 {jwa.A256GCMKW, jwa.A256CBC_HS512},
203 {jwa.PBES2_HS256_A128KW, jwa.A128GCM},
204 {jwa.PBES2_HS256_A128KW, jwa.A128CBC_HS256},
205 {jwa.PBES2_HS384_A192KW, jwa.A192GCM},
206 {jwa.PBES2_HS384_A192KW, jwa.A192CBC_HS384},
207 {jwa.PBES2_HS512_A256KW, jwa.A256GCM},
208 {jwa.PBES2_HS512_A256KW, jwa.A256CBC_HS512},
209 {jwa.DIRECT, jwa.A128GCM},
210 {jwa.DIRECT, jwa.A128CBC_HS256},
211 {jwa.DIRECT, jwa.A256GCM},
212 {jwa.DIRECT, jwa.A256CBC_HS512},
213 }
214
215 for _, test := range tests {
216 test := test
217 t.Run(fmt.Sprintf("%s-%s", test.alg, test.enc), func(t *testing.T) {
218 t.Parallel()
219 ctx, cancel := context.WithCancel(context.Background())
220 defer cancel()
221 joseInteropTest(ctx, test, t)
222 })
223 }
224 })
225 t.Run("jws", func(t *testing.T) {
226 t.Parallel()
227 tests := []jwa.SignatureAlgorithm{
228 jwa.ES256,
229
230 jwa.ES384,
231 jwa.ES512,
232
233 jwa.HS256,
234 jwa.HS384,
235 jwa.HS512,
236 jwa.PS256,
237 jwa.PS384,
238 jwa.PS512,
239 jwa.RS256,
240 jwa.RS384,
241 jwa.RS512,
242 }
243 for _, test := range tests {
244 test := test
245 t.Run(test.String(), func(t *testing.T) {
246 t.Parallel()
247 ctx, cancel := context.WithCancel(context.Background())
248 defer cancel()
249 joseJwsInteropTest(ctx, test, t)
250 })
251 }
252 })
253 }
254
255 type interopTest struct {
256 alg jwa.KeyEncryptionAlgorithm
257 enc jwa.ContentEncryptionAlgorithm
258 }
259
260 func joseInteropTest(ctx context.Context, spec interopTest, t *testing.T) {
261 t.Helper()
262
263 expected := []byte("Lorem ipsum")
264
265
266 alg := spec.alg.String()
267 if spec.alg == jwa.DIRECT {
268 alg = spec.enc.String()
269 }
270 joseJwkFile, joseJwkCleanup, err := jose.GenerateJwk(ctx, t, fmt.Sprintf(`{"alg": "%s"}`, alg))
271 if !assert.NoError(t, err, `jose.GenerateJwk should succeed`) {
272 return
273 }
274 defer joseJwkCleanup()
275
276
277 jwxJwk, err := jwxtest.ParseJwkFile(ctx, joseJwkFile)
278 if !assert.NoError(t, err, `jwxtest.ParseJwkFile should succeed`) {
279 return
280 }
281
282 t.Run("Parse JWK via jwx", func(t *testing.T) {
283 switch spec.alg {
284 case jwa.RSA1_5, jwa.RSA_OAEP, jwa.RSA_OAEP_256:
285 var rawkey rsa.PrivateKey
286 if !assert.NoError(t, jwxJwk.Raw(&rawkey), `jwk.Raw should succeed`) {
287 return
288 }
289 case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW:
290 var rawkey ecdsa.PrivateKey
291 if !assert.NoError(t, jwxJwk.Raw(&rawkey), `jwk.Raw should succeed`) {
292 return
293 }
294 default:
295 var rawkey []byte
296 if !assert.NoError(t, jwxJwk.Raw(&rawkey), `jwk.Raw should succeed`) {
297 return
298 }
299 }
300 })
301 t.Run("Encrypt with jose, Decrypt with jwx", func(t *testing.T) {
302
303 joseCryptFile, joseCryptCleanup, err := jose.EncryptJwe(ctx, t, expected, spec.alg.String(), joseJwkFile, spec.enc.String(), true)
304 if !assert.NoError(t, err, `jose.EncryptJwe should succeed`) {
305 return
306 }
307 defer joseCryptCleanup()
308
309 jwxtest.DumpFile(t, joseCryptFile)
310
311
312 payload, err := jwxtest.DecryptJweFile(ctx, joseCryptFile, spec.alg, joseJwkFile)
313 if !assert.NoError(t, err, `decryptFile.DecryptJwe should succeed`) {
314 return
315 }
316
317 if !assert.Equal(t, expected, payload, `decrypted payloads should match`) {
318 return
319 }
320 })
321 t.Run("Encrypt with jwx, Decrypt with jose", func(t *testing.T) {
322 jwxCryptFile, jwxCryptCleanup, err := jwxtest.EncryptJweFile(ctx, expected, spec.alg, joseJwkFile, spec.enc, jwa.NoCompress)
323 if !assert.NoError(t, err, `jwxtest.EncryptJweFile should succeed`) {
324 return
325 }
326 defer jwxCryptCleanup()
327
328 payload, err := jose.DecryptJwe(ctx, t, jwxCryptFile, joseJwkFile)
329 if !assert.NoError(t, err, `jose.DecryptJwe should succeed`) {
330 return
331 }
332
333 if !assert.Equal(t, expected, payload, `decrypted payloads should match`) {
334 return
335 }
336 })
337 }
338
339 func joseJwsInteropTest(ctx context.Context, alg jwa.SignatureAlgorithm, t *testing.T) {
340 t.Helper()
341
342 expected := []byte(`{"foo":"bar"}`)
343
344 joseJwkFile, joseJwkCleanup, err := jose.GenerateJwk(ctx, t, fmt.Sprintf(`{"alg": "%s"}`, alg))
345 if !assert.NoError(t, err, `jose.GenerateJwk should succeed`) {
346 return
347 }
348 defer joseJwkCleanup()
349
350
351 _, err = jwxtest.ParseJwkFile(ctx, joseJwkFile)
352 if !assert.NoError(t, err, `jwxtest.ParseJwkFile should succeed`) {
353 return
354 }
355 t.Run("Sign with jose, Verify with jwx", func(t *testing.T) {
356
357 joseCryptFile, joseCryptCleanup, err := jose.SignJws(ctx, t, expected, joseJwkFile, true)
358 if !assert.NoError(t, err, `jose.SignJws should succeed`) {
359 return
360 }
361 defer joseCryptCleanup()
362
363 jwxtest.DumpFile(t, joseCryptFile)
364
365
366 payload, err := jwxtest.VerifyJwsFile(ctx, joseCryptFile, alg, joseJwkFile)
367 if !assert.NoError(t, err, `jwxtest.VerifyJwsFile should succeed`) {
368 return
369 }
370
371 if !assert.Equal(t, expected, payload, `decrypted payloads should match`) {
372 return
373 }
374 })
375 t.Run("Sign with jwx, Verify with jose", func(t *testing.T) {
376 jwxCryptFile, jwxCryptCleanup, err := jwxtest.SignJwsFile(ctx, expected, alg, joseJwkFile)
377 if !assert.NoError(t, err, `jwxtest.SignJwsFile should succeed`) {
378 return
379 }
380 defer jwxCryptCleanup()
381
382 payload, err := jose.VerifyJws(ctx, t, jwxCryptFile, joseJwkFile)
383 if !assert.NoError(t, err, `jose.VerifyJws should succeed`) {
384 return
385 }
386
387 if !assert.Equal(t, expected, payload, `decrypted payloads should match`) {
388 return
389 }
390 })
391 }
392
393 func TestGHIssue230(t *testing.T) {
394 t.Parallel()
395 if !jose.Available() {
396 t.SkipNow()
397 }
398
399 data := "eyJhbGciOiJFQ0RILUVTIiwiY2xldmlzIjp7InBpbiI6InRhbmciLCJ0YW5nIjp7ImFkdiI6eyJrZXlzIjpbeyJhbGciOiJFQ01SIiwiY3J2IjoiUC01MjEiLCJrZXlfb3BzIjpbImRlcml2ZUtleSJdLCJrdHkiOiJFQyIsIngiOiJBZm5tR2xHRTFHRUZ5NEpUT2tGWmo5ZEhEUmdpVE5IeFBST3hpZDZLdm0xVGRFQkZ3bElsSVB6TG5lTjlnb3h6OUVGYmJLM3BoN0tWZS05aVF4MmxhOVNFIiwieSI6IkFmZGFaTVYzVzk1NE14elQxeXF3MWVaRU9xTFFZZnBXSGczMlJvekhyQjBEYmoxWWV3OVFvTDg1M2Y2aUw2REIyRC1nbEcxSFFsb3czdGRNdFhjN1pSY0IifSx7ImFsZyI6IkVTNTEyIiwiY3J2IjoiUC01MjEiLCJrZXlfb3BzIjpbInZlcmlmeSJdLCJrdHkiOiJFQyIsIngiOiJBR0drcXRPZzZqel9pZnhmVnVWQ01CalVySFhCTGtfS2hIb3lKRkU5NmJucTZKZVVHNFNMZnRrZ2FIYk5WT0U4Q3Mwd0JqR0ZkSWxDbnBmak94RGJfbFBoIiwieSI6IkFLU0laT0JYY1Jfa3RkWjZ6T3F3TGI5SEJzai0yYmRMUmw5dFZVbnVlV2N3aXg5X3NiekliSWx0SE9YUGhBTW9yaUlYMWVyNzc4Unh6Vkg5d0FtaUhGa1kifV19LCJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjM5NDIxIn19LCJlbmMiOiJBMjU2R0NNIiwiZXBrIjp7ImNydiI6IlAtNTIxIiwia3R5IjoiRUMiLCJ4IjoiQUJMUm9sQWotZFdVdzZLSjg2T3J6d1F6RjlGT09URFZBZnNWNkh0OU0zREhyQ045Q0N6dVJ1b3cwbWp6M3BjZnVCaFpYREpfN0dkdzE0LXdneV9fTFNrYyIsInkiOiJBT3NRMzlKZmFQVGhjc2FZTjhSMVBHXzIwYXZxRU1NRl9fM2RHQmI3c1BqNmktNEJORDVMdkZ3cVpJT1l4SS1kVWlvNzkyOWY1YnE0eEdJY0lGWWtlbllxIn0sImtpZCI6ImhlZmVpNzVqMkp4Sko3REZnSDAxUWlOVmlGayJ9..GH3-8v7wfxEsRnki.wns--EIYTRjM3Tb0HyA.EGn2Gq7PnSVvPaMN0oRi5A"
400
401 compactMsg, err := jwe.ParseString(data)
402 if !assert.NoError(t, err, `jwe.ParseString should succeed`) {
403 return
404 }
405
406 formatted, err := jose.FmtJwe(context.TODO(), t, []byte(data))
407 if !assert.NoError(t, err, `jose.FmtJwe should succeed`) {
408 return
409 }
410
411 jsonMsg, err := jwe.Parse(formatted)
412 if !assert.NoError(t, err, `jwe.Parse should succeed`) {
413 return
414 }
415
416 if !assert.Equal(t, compactMsg, jsonMsg, `messages should match`) {
417 return
418 }
419 }
420
421 func TestGuessFormat(t *testing.T) {
422 testcases := []struct {
423 Name string
424 Expected jwx.FormatKind
425 Source []byte
426 }{
427 {
428 Name: "Raw String",
429 Expected: jwx.UnknownFormat,
430 Source: []byte(`Hello, World`),
431 },
432 {
433 Name: "Random JSON Object",
434 Expected: jwx.UnknownFormat,
435 Source: []byte(`{"random": "JSON"}`),
436 },
437 {
438 Name: "Random JSON Array",
439 Expected: jwx.UnknownFormat,
440 Source: []byte(`["random", "JSON"]`),
441 },
442 {
443 Name: "Random Broken JSON",
444 Expected: jwx.UnknownFormat,
445 Source: []byte(`{"aud": "foo", "x-customg": "extra semicolon after this string", }`),
446 },
447 {
448 Name: "JWS",
449 Expected: jwx.JWS,
450
451 Source: []byte(`eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk`),
452 },
453 {
454 Name: "JWE",
455 Expected: jwx.JWE,
456 Source: []byte(`eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`),
457 },
458 {
459 Name: "JWK",
460 Expected: jwx.JWK,
461 Source: []byte(`{"kty":"OKP","crv":"X25519","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"}`),
462 },
463 {
464 Name: "JWKS",
465 Expected: jwx.JWKS,
466 Source: []byte(`{"keys":[{"kty":"OKP","crv":"X25519","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"}]}`),
467 },
468 {
469 Name: "JWS (JSON)",
470 Expected: jwx.JWS,
471 Source: []byte(`{"signatures": [], "payload": ""}`),
472 },
473 {
474 Name: "JWT",
475 Expected: jwx.JWT,
476 Source: []byte(`{"aud":"github.com/lestrrat-go/jwx"}`),
477 },
478 }
479
480 for _, tc := range testcases {
481 tc := tc
482 t.Run(tc.Name, func(t *testing.T) {
483 got := jwx.GuessFormat(tc.Source)
484 if !assert.Equal(t, got, tc.Expected, `value of jwx.GuessFormat should match (%s != %s)`, got, tc.Expected) {
485 return
486 }
487 })
488 }
489 }
490
491 func TestFormat(t *testing.T) {
492 testcases := []struct {
493 Value jwx.FormatKind
494 Expected string
495 Error bool
496 }{
497 {
498 Value: jwx.UnknownFormat,
499 Expected: "UnknownFormat",
500 },
501 {
502 Value: jwx.JWE,
503 Expected: "JWE",
504 },
505 {
506 Value: jwx.JWS,
507 Expected: "JWS",
508 },
509 {
510 Value: jwx.JWK,
511 Expected: "JWK",
512 },
513 {
514 Value: jwx.JWKS,
515 Expected: "JWKS",
516 },
517 {
518 Value: jwx.JWT,
519 Expected: "JWT",
520 },
521 {
522 Value: jwx.FormatKind(9999999),
523 Expected: "FormatKind(9999999)",
524 },
525 }
526 for _, tc := range testcases {
527 tc := tc
528 t.Run(tc.Expected, func(t *testing.T) {
529 if !assert.Equal(t, tc.Expected, tc.Value.String(), `stringification should match`) {
530 return
531 }
532 })
533 }
534 }
535
View as plain text