-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
DefaultJwtHeaderBuilderTest.groovy
593 lines (516 loc) · 20 KB
/
DefaultJwtHeaderBuilderTest.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
/*
* Copyright (C) 2021 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl
import io.jsonwebtoken.JweHeader
import io.jsonwebtoken.JwsHeader
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.ProtectedHeader
import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.impl.lang.Bytes
import io.jsonwebtoken.impl.security.DefaultHashAlgorithm
import io.jsonwebtoken.impl.security.DefaultRequest
import io.jsonwebtoken.impl.security.TestKeys
import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.lang.Collections
import io.jsonwebtoken.lang.Strings
import io.jsonwebtoken.security.Jwks
import org.junit.Before
import org.junit.Test
import java.security.interfaces.RSAPublicKey
import static org.junit.Assert.*
class DefaultJwtHeaderBuilderTest {
static DefaultJwtHeaderBuilder builder
static def header
static DefaultJwtHeaderBuilder jws() {
// assignment and return must be on different lines when testing on JDK 7:
builder = new DefaultJwtHeaderBuilder().add('alg', 'foo') as DefaultJwtHeaderBuilder
return builder
}
static DefaultJwtHeaderBuilder jwe() {
// assignment and return must be on different lines when testing on JDK 7 otherwise we get
// (class: io/jsonwebtoken/impl/DefaultJwtHeaderBuilderTest, method: jwe signature: ()Lio/jsonwebtoken/impl/DefaultJwtHeaderBuilder;) Illegal target of jump or branch
builder = jws().add('enc', 'bar') as DefaultJwtHeaderBuilder
return builder
}
@Before
void setUp() {
header = null
builder = new DefaultJwtHeaderBuilder()
}
@SuppressWarnings('GroovyAssignabilityCheck')
private static void assertSymmetry(String propName, def val) {
def name = Strings.capitalize(propName)
switch (propName) {
case 'algorithm': builder.add('alg', val); break // no setter
case 'compressionAlgorithm': builder.add('zip', val); break // no setter
default: builder."$propName"(val)
}
header = builder.build()
if (val instanceof byte[]) {
assertArrayEquals val, header."get$name"()
} else {
assertEquals val, header."get$name"()
}
}
@Test
void testStaticFactoryMethod() {
assertTrue Jwts.header() instanceof DefaultJwtHeaderBuilder
}
@Test
void testDefault() { // no properties are set, so assert an unprotected header:
header = builder.build()
assertFalse header instanceof JwsHeader
assertFalse header instanceof JweHeader
assertTrue header instanceof DefaultHeader
}
// ====================== Map Methods =======================
@Test
void testSize() {
assertEquals 0, builder.size()
builder.put('foo', 'bar')
assertEquals 1, builder.build().size()
}
@Test
void testIsEmpty() {
assertTrue builder.build().isEmpty()
builder.put('foo', 'bar')
assertFalse builder.build().isEmpty()
}
@Test
void testContainsKey() {
def key = 'foo'
assertFalse builder.build().containsKey(key)
builder.put(key, 'bar')
assertTrue builder.build().containsKey(key)
}
@Test
void testContainsValue() {
def value = 'bar'
assertFalse builder.build().containsValue(value)
builder.put('foo', value)
assertTrue builder.build().containsValue(value)
}
@Test
void testGet() {
def key = 'foo'
def value = 'bar'
assertNull builder.build().get(key)
builder.put(key, value)
assertEquals value, builder.build().get(key)
}
@Test
void testKeySet() {
def key = 'foo'
def value = 'bar'
assertTrue builder.build().keySet().isEmpty()
builder.put(key, value)
def built = builder.build()
assertFalse built.keySet().isEmpty()
assertEquals 1, built.keySet().size()
assertEquals key, built.keySet().iterator().next()
def i = builder.build().keySet().iterator()
i.next()
//built headers are immutable:
try {
i.remove() // assert keyset modification modifies builder state:
fail()
} catch (UnsupportedOperationException expected) {
}
}
@Test
void testValues() {
def key = 'foo'
def value = 'bar'
assertTrue builder.build().values().isEmpty()
builder.put(key, value)
assertFalse builder.build().values().isEmpty()
assertEquals 1, builder.build().values().size()
assertEquals value, builder.build().values().iterator().next()
def i = builder.build().values().iterator()
i.next()
//built headers are immutable:
try {
i.remove()
fail()
} catch (UnsupportedOperationException expected) {
}
}
@Test
void testEntrySet() {
def key = 'foo'
def value = 'bar'
assertTrue builder.build().entrySet().isEmpty()
builder.put(key, value)
assertFalse builder.build().entrySet().isEmpty()
assertEquals 1, builder.build().entrySet().size()
def entry = builder.build().entrySet().iterator().next()
assertEquals key, entry.getKey()
assertEquals value, entry.getValue()
def i = builder.build().entrySet().iterator()
i.next()
//built headers are immutable:
try {
i.remove()
fail()
} catch (UnsupportedOperationException expected) {
}
}
@Test
void testPut() {
builder.put('foo', 'bar')
assertEquals 'bar', builder.build().get('foo')
}
@Test
void testPutAll() {
def m = ['foo': 'bar', 'baz': 'bat']
def header = builder.add(m).build()
assertEquals m, header
}
@Test
void testRemove() {
builder.put('foo', 'bar')
assertEquals 'bar', builder.build().foo
builder.remove('foo')
assertTrue builder.build().isEmpty()
}
@Test
void testClear() {
def m = ['foo': 'bar', 'baz': 'bat']
builder.add(m)
builder.clear()
def header = builder.build()
assertTrue header.isEmpty()
}
@Test
void testEmpty() {
def m = ['foo': 'bar', 'baz': 'bat']
def header = builder.add(m).empty().build()
assertTrue header.isEmpty()
}
@Test
void testToMap() {
def m = ['foo': 'bar', 'baz': 'bat']
builder.putAll(m)
assertEquals m, builder
assertEquals m, builder.build()
}
// ====================== Generic Header Methods =======================
@Test
void testType() {
assertSymmetry('type', 'foo')
}
@Test
void testContentType() {
assertSymmetry('contentType', 'text/plain')
}
/**
* Asserts that if the 'alg' member is set to any other value other than 'none', but no JWE-only members
* are set, a JwsHeader is created. Although a JweHeader also has an 'alg' value, there must be at least
* one JWE-only member set as well to trigger JweHeader creation.
*/
@Test
void testAlgNone() { // alg of 'none', so build an unprotected header:
assertSymmetry('algorithm', 'none')
assertFalse header instanceof JwsHeader
assertFalse header instanceof JweHeader
assertTrue header instanceof DefaultHeader
}
@Test
void testCompressionAlgorithm() {
assertSymmetry('compressionAlgorithm', 'DEF')
}
@Test
void testDeprecatedSetters() { // TODO: remove before 1.0
assertEquals 'foo', builder.setType('foo').build().getType()
assertEquals 'foo', builder.setContentType('foo').build().get('cty') // compact form
assertEquals 'application/foo', builder.build().getContentType() // normalized form
assertEquals 'foo', builder.setCompressionAlgorithm('foo').build().getCompressionAlgorithm()
assertEquals 'foo', jws().setKeyId('foo').build().getKeyId()
assertEquals 'foo', jws().setAlgorithm('foo').build().getAlgorithm()
}
// ====================== Protected Header Methods =======================
/**
* Asserts that if the protected-header-only 'jku' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testJwkSetUrl() {
URI uri = URI.create('https://github.com/jwtk/jjwt')
header = jws().jwkSetUrl(uri).build() as JwsHeader
assertEquals uri, header.getJwkSetUrl()
}
/**
* Asserts that if the protected-header-only 'jwk' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testJwk() {
def jwk = Jwks.builder().key(TestKeys.RS256.pair.public as RSAPublicKey).build()
header = jws().jwk(jwk).build() as JwsHeader
assertEquals jwk, header.getJwk()
}
/**
* Asserts that if the protected-header-only 'kid' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testKeyId() {
def kid = 'whatever'
header = jws().keyId(kid).build() as JwsHeader
assertEquals kid, header.getKeyId()
}
/**
* Asserts that if the protected-header-only 'crit' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testCritical() {
def crit = ['foo'] as Set<String>
header = jws().add('foo', 'bar').critical().add(crit).and().build() as JwsHeader
assertTrue header instanceof JwsHeader
assertFalse header instanceof JweHeader
assertEquals crit, header.getCritical()
}
// ====================== X.509 Methods =======================
/**
* Asserts that if the protected-header-only 'x5u' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testX09Url() {
def uri = URI.create('https://github.com/jwtk/jjwt')
header = jws().x509Url(uri).build() as JwsHeader
assertEquals uri, header.getX509Url()
}
/**
* Asserts that if the protected-header-only 'x5c' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testX509CertificateChain() {
def chain = TestKeys.RS256.chain
header = jws().x509Chain(chain).build() as JwsHeader
assertEquals chain, header.getX509Chain()
}
/**
* Asserts that if the protected-header-only 'x5t' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testX509CertificateSha1Thumbprint() {
def payload = Streams.of(TestKeys.RS256.cert.getEncoded())
def request = new DefaultRequest(payload, null, null)
def x5t = DefaultHashAlgorithm.SHA1.digest(request)
String encoded = Encoders.BASE64URL.encode(x5t)
header = jws().x509Sha1Thumbprint(x5t).build() as JwsHeader
assertArrayEquals x5t, header.getX509Sha1Thumbprint()
assertEquals encoded, header.get('x5t')
}
@Test
void testX509CertificateSha1ThumbprintEnabled() {
def chain = TestKeys.RS256.chain
def payload = Streams.of(chain[0].getEncoded())
def request = new DefaultRequest(payload, null, null)
def x5t = DefaultHashAlgorithm.SHA1.digest(request)
String encoded = Encoders.BASE64URL.encode(x5t)
header = jws().x509Chain(chain).x509Sha1Thumbprint(true).build() as JwsHeader
assertArrayEquals x5t, header.getX509Sha1Thumbprint()
assertEquals encoded, header.get('x5t')
}
/**
* Asserts that if the protected-header-only 'x5t#S256' member is set, but no JWE-only members are set, a
* JwsHeader is created.
*/
@Test
void testX509CertificateSha256Thumbprint() {
def payload = Streams.of(TestKeys.RS256.cert.getEncoded())
def request = new DefaultRequest(payload, null, null)
def x5tS256 = Jwks.HASH.@SHA256.digest(request)
String encoded = Encoders.BASE64URL.encode(x5tS256)
header = jws().x509Sha256Thumbprint(x5tS256).build() as JwsHeader
assertArrayEquals x5tS256, header.getX509Sha256Thumbprint()
assertEquals encoded, header.get('x5t#S256')
}
@Test
void testX509CertificateSha256ThumbprintEnabled() {
def chain = TestKeys.RS256.chain
def payload = Streams.of(chain[0].getEncoded())
def request = new DefaultRequest(payload, null, null)
def x5tS256 = Jwks.HASH.SHA256.digest(request)
String encoded = Encoders.BASE64URL.encode(x5tS256)
header = jws().x509Chain(chain).x509Sha256Thumbprint(true).build() as JwsHeader
assertArrayEquals x5tS256, header.getX509Sha256Thumbprint()
assertEquals encoded, header.get('x5t#S256')
}
// ====================== JWE Header Methods =======================
@Test
void testEncryptionAlgorithm() {
def enc = Jwts.ENC.A256GCM.getId()
header = builder.add('alg', Jwts.KEY.A192KW.getId()).add('enc', enc).build() as JweHeader
assertEquals enc, header.getEncryptionAlgorithm()
}
@Test
void testEphemeralPublicKey() {
def key = TestKeys.ES256.pair.public
def jwk = Jwks.builder().key(key).build()
header = jwe().add('epk', jwk).build() as JweHeader
assertEquals jwk, header.getEphemeralPublicKey()
}
@Test
void testAgreementPartyUInfo() {
def info = Strings.utf8("UInfo")
def header = jwe().agreementPartyUInfo(info).build() as JweHeader
assertArrayEquals info, header.getAgreementPartyUInfo()
}
@Test
void testAgreementPartyUInfoString() {
def s = "UInfo"
def info = Strings.utf8(s)
def header = jwe().agreementPartyUInfo(s).build() as JweHeader
assertArrayEquals info, header.getAgreementPartyUInfo()
}
@Test
void testAgreementPartyVInfo() {
def info = Strings.utf8("VInfo")
def header = jwe().agreementPartyVInfo(info).build() as JweHeader
assertArrayEquals info, header.getAgreementPartyVInfo()
}
@Test
void testAgreementPartyVInfoString() {
def s = "VInfo"
def info = Strings.utf8(s)
def header = jwe().agreementPartyVInfo(s).build() as JweHeader
assertArrayEquals info, header.getAgreementPartyVInfo()
}
@Test
void testPbes2Salt() {
byte[] salt = Bytes.randomBits(256)
def header = jwe().add('p2s', salt).build() as JweHeader
assertArrayEquals salt, header.getPbes2Salt()
}
@Test
void testPbes2Count() {
int count = 4096
def header = jwe().pbes2Count(count).build() as JweHeader
assertEquals count, header.getPbes2Count()
}
@Test
void testInitializationVector() {
byte[] iv = Bytes.randomBits(96)
def header = jwe().add('iv', iv).build() as JweHeader
assertArrayEquals iv, header.getInitializationVector()
}
@Test
void testAuthenticationTag() {
byte[] val = Bytes.randomBits(128)
def header = jwe().add('tag', val).build() as JweHeader
assertArrayEquals val, header.getAuthenticationTag()
}
@Test
void testUnprotectedHeaderChangedToProtectedHeaderChangedToJweHeader() {
builder.put('foo', 'bar')
assertEquals new DefaultHeader([foo: 'bar']), builder.build()
// add JWS-required property:
builder.put(DefaultHeader.ALGORITHM.getId(), 'HS256')
assertEquals new DefaultJwsHeader([foo: 'bar', alg: 'HS256']), builder.build()
// add JWE required property:
builder.put(DefaultJweHeader.ENCRYPTION_ALGORITHM.getId(), Jwts.ENC.A256GCM.getId())
assertEquals new DefaultJweHeader([foo: 'bar', alg: 'HS256', enc: 'A256GCM']), builder.build()
}
@Test
void testCritSingle() {
def crit = 'test'
def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader
def expected = [crit] as Set<String>
assertEquals expected, header.getCritical()
}
/**
* Asserts that if a .critical() builder is used, and its .and() method is not called, the change to the
* crit collection is still applied when building the header.
* @see <a href="https://github.com/jwtk/jjwt/issues/916">JJWT Issue 916</a>
* @since 0.12.5
*/
@Test
void testCritWithoutConjunction() {
def crit = 'test'
def builder = jws()
builder.add(crit, 'foo').critical().add(crit) // no .and() method
def header = builder.build() as ProtectedHeader
def expected = [crit] as Set<String>
assertEquals expected, header.getCritical()
}
@Test
void testCritSingleNullIgnored() {
def crit = 'test'
def expected = [crit] as Set<String>
def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals expected, header.getCritical()
header = builder.critical().add((String) null).and().build() as ProtectedHeader // ignored
assertEquals expected, header.getCritical() // nothing changed
}
@Test
void testCritNullCollectionIgnored() {
def crit = ['test'] as Set<String>
def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals crit, header.getCritical()
header = builder.critical().add((Collection) null).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() // nothing changed
}
@Test
void testCritCollectionWithNullElement() {
def crit = [null] as Set<String>
def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertNull header.getCritical()
}
@Test
void testCritEmptyIgnored() {
def crit = ['test'] as Set<String>
ProtectedHeader header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals crit, header.getCritical()
header = builder.critical().add([] as Set<String>).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() // ignored
}
/**
* Asserts that per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, a {@code crit} header is not
* allowed in non-protected headers.
*/
@Test
void testCritRemovedForUnprotectedHeader() {
def crit = Collections.setOf('foo', 'bar')
// no JWS or JWE params specified:
def header = builder.add('test', 'value').critical().add(crit).and().build()
assertFalse header.containsKey(DefaultProtectedHeader.CRIT.getId())
}
/**
* Asserts that per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, a value in the {@code crit} set
* is removed if the corresponding header parameter is missing.
*/
@Test
void testCritNamesSanitizedWhenHeaderMissingCorrespondingParameter() {
def critGiven = ['foo', 'bar'] as Set<String>
def critExpected = ['foo'] as Set<String>
def header = jws().add('foo', 'fooVal').critical().add(critGiven).and().build() as ProtectedHeader
// header didn't set the 'bar' parameter, so 'bar' should not be in the crit values:
assertEquals critExpected, header.getCritical()
}
@Test
void testCritNamesRemovedWhenHeaderMissingCorrespondingParameter() {
def critGiven = ['foo'] as Set<String>
ProtectedHeader header = jws().critical().add(critGiven).and().build() as ProtectedHeader
// header didn't set the 'foo' parameter, so crit would have been empty, and then removed from the header:
assertNull header.getCritical()
}
}