• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 package com.google.crypto.tink.jwt;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.crypto.tink.internal.TinkBugException.exceptionIsBug;
21 import static java.nio.charset.StandardCharsets.UTF_8;
22 import static org.junit.Assert.assertThrows;
23 
24 import com.google.crypto.tink.InsecureSecretKeyAccess;
25 import com.google.crypto.tink.Key;
26 import com.google.crypto.tink.KeyTemplate;
27 import com.google.crypto.tink.KeyTemplates;
28 import com.google.crypto.tink.KeysetHandle;
29 import com.google.crypto.tink.Parameters;
30 import com.google.crypto.tink.RegistryConfiguration;
31 import com.google.crypto.tink.TinkProtoKeysetFormat;
32 import com.google.crypto.tink.internal.KeyManagerRegistry;
33 import com.google.crypto.tink.proto.JwtHmacKey;
34 import com.google.crypto.tink.proto.JwtHmacKey.CustomKid;
35 import com.google.crypto.tink.proto.KeyData;
36 import com.google.crypto.tink.proto.Keyset;
37 import com.google.crypto.tink.subtle.Base64;
38 import com.google.crypto.tink.subtle.Hex;
39 import com.google.crypto.tink.subtle.PrfHmacJce;
40 import com.google.crypto.tink.subtle.PrfMac;
41 import com.google.crypto.tink.testing.TestUtil;
42 import com.google.crypto.tink.util.SecretBytes;
43 import com.google.gson.JsonObject;
44 import com.google.protobuf.ExtensionRegistryLite;
45 import java.security.GeneralSecurityException;
46 import java.time.Clock;
47 import java.time.Duration;
48 import java.time.Instant;
49 import java.time.ZoneOffset;
50 import java.util.Set;
51 import java.util.TreeSet;
52 import javax.crypto.spec.SecretKeySpec;
53 import org.junit.BeforeClass;
54 import org.junit.Test;
55 import org.junit.experimental.theories.DataPoint;
56 import org.junit.experimental.theories.DataPoints;
57 import org.junit.experimental.theories.FromDataPoints;
58 import org.junit.experimental.theories.Theories;
59 import org.junit.experimental.theories.Theory;
60 import org.junit.runner.RunWith;
61 
62 /** Unit tests for {@link JwtHmacKeyManager}. */
63 @RunWith(Theories.class)
64 public class JwtHmacKeyManagerTest {
65 
66   @BeforeClass
setUp()67   public static void setUp() throws Exception {
68     JwtMacConfig.register();
69   }
70 
71   @Test
testKeyManagerRegistered()72   public void testKeyManagerRegistered() throws Exception {
73     assertThat(
74             KeyManagerRegistry.globalInstance()
75                 .getUntypedKeyManager("type.googleapis.com/google.crypto.tink.JwtHmacKey"))
76         .isNotNull();
77   }
78 
79   @DataPoint public static final String JWT_HS256 = "JWT_HS256";
80   @DataPoint public static final String JWT_HS384 = "JWT_HS384";
81   @DataPoint public static final String JWT_HS512 = "JWT_HS512";
82   @DataPoint public static final String JWT_HS256_RAW = "JWT_HS256_RAW";
83 
84 
85   @DataPoints("templateNames")
86   public static final String[] KEY_TEMPLATES =
87       new String[] {
88         "JWT_HS256_RAW", "JWT_HS256", "JWT_HS384_RAW", "JWT_HS384", "JWT_HS512_RAW", "JWT_HS512",
89       };
90 
91   @Theory
testTemplates(@romDataPoints"templateNames") String templateName)92   public void testTemplates(@FromDataPoints("templateNames") String templateName) throws Exception {
93     KeysetHandle h = KeysetHandle.generateNew(KeyTemplates.get(templateName));
94     assertThat(h.size()).isEqualTo(1);
95     assertThat(h.getAt(0).getKey().getParameters())
96         .isEqualTo(KeyTemplates.get(templateName).toParameters());
97   }
98 
99   @Test
createKey_multipleTimes()100   public void createKey_multipleTimes() throws Exception {
101     JwtHmacParameters parameters =
102         JwtHmacParameters.builder()
103             .setKeySizeBytes(32)
104             .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
105             .setAlgorithm(JwtHmacParameters.Algorithm.HS256)
106             .build();
107     int numKeys = 100;
108     Set<String> keys = new TreeSet<>();
109     for (int i = 0; i < numKeys; ++i) {
110       KeysetHandle handle = KeysetHandle.generateNew(parameters);
111       com.google.crypto.tink.jwt.JwtHmacKey jwtHmacKey =
112           (com.google.crypto.tink.jwt.JwtHmacKey) handle.getAt(0).getKey();
113       keys.add(Hex.encode(jwtHmacKey.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get())));
114     }
115     assertThat(keys).hasSize(numKeys);
116   }
117 
118   @Test
testHs256Template()119   public void testHs256Template() throws Exception {
120     KeyTemplate template = KeyTemplates.get("JWT_HS256");
121     assertThat(template.toParameters())
122         .isEqualTo(
123             JwtHmacParameters.builder()
124                 .setKeySizeBytes(32)
125                 .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
126                 .setAlgorithm(JwtHmacParameters.Algorithm.HS256)
127                 .build());
128   }
129 
130   @Test
testHs256Template_function()131   public void testHs256Template_function() throws Exception {
132     assertThat(JwtHmacKeyManager.hs256Template().toParameters())
133         .isEqualTo(
134             JwtHmacParameters.builder()
135                 .setKeySizeBytes(32)
136                 .setKidStrategy(JwtHmacParameters.KidStrategy.IGNORED)
137                 .setAlgorithm(JwtHmacParameters.Algorithm.HS256)
138                 .build());
139   }
140 
141   @Test
testHs384Template()142   public void testHs384Template() throws Exception {
143     KeyTemplate template = KeyTemplates.get("JWT_HS384");
144     assertThat(template.toParameters())
145         .isEqualTo(
146             JwtHmacParameters.builder()
147                 .setKeySizeBytes(48)
148                 .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
149                 .setAlgorithm(JwtHmacParameters.Algorithm.HS384)
150                 .build());
151   }
152 
153   @Test
testHs384Template_function()154   public void testHs384Template_function() throws Exception {
155     assertThat(JwtHmacKeyManager.hs384Template().toParameters())
156         .isEqualTo(
157             JwtHmacParameters.builder()
158                 .setKeySizeBytes(48)
159                 .setKidStrategy(JwtHmacParameters.KidStrategy.IGNORED)
160                 .setAlgorithm(JwtHmacParameters.Algorithm.HS384)
161                 .build());
162   }
163 
164   @Test
testHs512Template()165   public void testHs512Template() throws Exception {
166     KeyTemplate template = KeyTemplates.get("JWT_HS512");
167     assertThat(template.toParameters())
168         .isEqualTo(
169             JwtHmacParameters.builder()
170                 .setKeySizeBytes(64)
171                 .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
172                 .setAlgorithm(JwtHmacParameters.Algorithm.HS512)
173                 .build());
174   }
175 
176   @Test
testHs512Template_function()177   public void testHs512Template_function() throws Exception {
178     assertThat(JwtHmacKeyManager.hs512Template().toParameters())
179         .isEqualTo(
180             JwtHmacParameters.builder()
181                 .setKeySizeBytes(64)
182                 .setKidStrategy(JwtHmacParameters.KidStrategy.IGNORED)
183                 .setAlgorithm(JwtHmacParameters.Algorithm.HS512)
184                 .build());
185   }
186 
187   @Test
testHs256RawTemplate()188   public void testHs256RawTemplate() throws Exception {
189     KeyTemplate template = KeyTemplates.get("JWT_HS256_RAW");
190     assertThat(template.toParameters())
191         .isEqualTo(
192             JwtHmacParameters.builder()
193                 .setKeySizeBytes(32)
194                 .setKidStrategy(JwtHmacParameters.KidStrategy.IGNORED)
195                 .setAlgorithm(JwtHmacParameters.Algorithm.HS256)
196                 .build());
197   }
198 
199   @Test
testHs384RawTemplate()200   public void testHs384RawTemplate() throws Exception {
201     KeyTemplate template = KeyTemplates.get("JWT_HS384_RAW");
202     assertThat(template.toParameters())
203         .isEqualTo(
204             JwtHmacParameters.builder()
205                 .setKeySizeBytes(48)
206                 .setKidStrategy(JwtHmacParameters.KidStrategy.IGNORED)
207                 .setAlgorithm(JwtHmacParameters.Algorithm.HS384)
208                 .build());
209   }
210 
211   @Test
testHs512RawTemplate()212   public void testHs512RawTemplate() throws Exception {
213     KeyTemplate template = KeyTemplates.get("JWT_HS512_RAW");
214     assertThat(template.toParameters())
215         .isEqualTo(
216             JwtHmacParameters.builder()
217                 .setKeySizeBytes(64)
218                 .setKidStrategy(JwtHmacParameters.KidStrategy.IGNORED)
219                 .setAlgorithm(JwtHmacParameters.Algorithm.HS512)
220                 .build());
221   }
222 
223   @Test
testKeyTemplatesWork()224   public void testKeyTemplatesWork() throws Exception {
225     Parameters p = KeyTemplates.get("JWT_HS256").toParameters();
226     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
227 
228     p = KeyTemplates.get("JWT_HS384").toParameters();
229     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
230 
231     p = KeyTemplates.get("JWT_HS512").toParameters();
232     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
233 
234     p = KeyTemplates.get("JWT_HS512_RAW").toParameters();
235     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
236   }
237 
238   @Test
createKeysetHandle_works()239   public void createKeysetHandle_works() throws Exception {
240     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get("JWT_HS256"));
241     Key key = handle.getAt(0).getKey();
242     assertThat(key).isInstanceOf(com.google.crypto.tink.jwt.JwtHmacKey.class);
243     com.google.crypto.tink.jwt.JwtHmacKey jwtHmacKey = (com.google.crypto.tink.jwt.JwtHmacKey) key;
244     assertThat(jwtHmacKey.getParameters())
245         .isEqualTo(
246             JwtHmacParameters.builder()
247                 .setKeySizeBytes(32)
248                 .setAlgorithm(JwtHmacParameters.Algorithm.HS256)
249                 .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
250                 .build());
251   }
252 
253   // Note: we use Theory as a parametrized test -- different from what the Theory framework intends.
254   @Theory
createSignVerify_success(String templateNames)255   public void createSignVerify_success(String templateNames) throws Exception {
256     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateNames));
257     JwtMac primitive = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
258     RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build();
259     String signedCompact = primitive.computeMacAndEncode(rawToken);
260     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
261     VerifiedJwt verifiedToken = primitive.verifyMacAndDecode(signedCompact, validator);
262     assertThat(verifiedToken.getJwtId()).isEqualTo("jwtId");
263   }
264 
265   // Note: we use Theory as a parametrized test -- different from what the Theory framework intends.
266   @Theory
createSignVerifyDifferentKey_throw(String templateNames)267   public void createSignVerifyDifferentKey_throw(String templateNames) throws Exception {
268     KeyTemplate template = KeyTemplates.get(templateNames);
269     KeysetHandle handle = KeysetHandle.generateNew(template);
270     JwtMac primitive = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
271     RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build();
272     String compact = primitive.computeMacAndEncode(rawToken);
273 
274     KeysetHandle otherHandle = KeysetHandle.generateNew(template);
275     JwtMac otherPrimitive = otherHandle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
276     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
277     assertThrows(
278         GeneralSecurityException.class,
279         () -> otherPrimitive.verifyMacAndDecode(compact, validator));
280   }
281 
282   // Note: we use Theory as a parametrized test -- different from what the Theory framework intends.
283   @Theory
createSignVerify_modifiedHeader_throw(String templateNames)284   public void createSignVerify_modifiedHeader_throw(String templateNames) throws Exception {
285     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateNames));
286     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
287 
288     String jwtId = "user123";
289     RawJwt unverified = RawJwt.newBuilder().setJwtId(jwtId).withoutExpiration().build();
290     String compact = mac.computeMacAndEncode(unverified);
291     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
292 
293     String[] parts = compact.split("\\.", -1);
294     byte[] header = Base64.urlSafeDecode(parts[0]);
295 
296     for (TestUtil.BytesMutation mutation : TestUtil.generateMutations(header)) {
297       String modifiedHeader = Base64.urlSafeEncode(mutation.value);
298       String modifiedToken = modifiedHeader + "." + parts[1] + "." + parts[2];
299 
300       assertThrows(
301           GeneralSecurityException.class, () -> mac.verifyMacAndDecode(modifiedToken, validator));
302     }
303   }
304 
305   // Note: we use Theory as a parametrized test -- different from what the Theory framework intends.
306   @Theory
createSignVerify_modifiedPayload_throw(String templateNames)307   public void createSignVerify_modifiedPayload_throw(String templateNames) throws Exception {
308     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateNames));
309     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
310 
311     String jwtId = "user123";
312     RawJwt unverified = RawJwt.newBuilder().setJwtId(jwtId).withoutExpiration().build();
313     String compact = mac.computeMacAndEncode(unverified);
314     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
315 
316     String[] parts = compact.split("\\.", -1);
317     byte[] payload = Base64.urlSafeDecode(parts[1]);
318 
319     for (TestUtil.BytesMutation mutation : TestUtil.generateMutations(payload)) {
320       String modifiedPayload = Base64.urlSafeEncode(mutation.value);
321       String modifiedToken = parts[0] + "." + modifiedPayload + "." + parts[2];
322 
323       assertThrows(
324           GeneralSecurityException.class, () -> mac.verifyMacAndDecode(modifiedToken, validator));
325     }
326   }
327 
328   // Note: we use Theory as a parametrized test -- different from what the Theory framework intends.
329   @Theory
verify_modifiedSignature_shouldThrow(String templateNames)330   public void verify_modifiedSignature_shouldThrow(String templateNames) throws Exception {
331     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateNames));
332     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
333 
334     String jwtId = "user123";
335     RawJwt unverified = RawJwt.newBuilder().setJwtId(jwtId).withoutExpiration().build();
336     String compact = mac.computeMacAndEncode(unverified);
337     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
338 
339     String[] parts = compact.split("\\.", -1);
340     byte[] signature = Base64.urlSafeDecode(parts[1]);
341 
342     for (TestUtil.BytesMutation mutation : TestUtil.generateMutations(signature)) {
343       String modifiedSignature = Base64.urlSafeEncode(mutation.value);
344       String modifiedToken = parts[0] + "." + parts[1] + "." + modifiedSignature;
345 
346       assertThrows(
347           GeneralSecurityException.class, () -> mac.verifyMacAndDecode(modifiedToken, validator));
348     }
349   }
350 
351   @Test
computeVerify_canGetData()352   public void computeVerify_canGetData() throws Exception {
353     KeyTemplate template = KeyTemplates.get("JWT_HS256");
354     KeysetHandle handle = KeysetHandle.generateNew(template);
355     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
356 
357     String issuer = "google";
358     String audience = "mybank";
359     String jwtId = "user123";
360     double amount = 0.1;
361     RawJwt unverified =
362         RawJwt.newBuilder()
363             .setTypeHeader("myType")
364             .setIssuer(issuer)
365             .addAudience(audience)
366             .setJwtId(jwtId)
367             .addNumberClaim("amount", amount)
368             .withoutExpiration()
369             .build();
370     String compact = mac.computeMacAndEncode(unverified);
371     JwtValidator validator =
372         JwtValidator.newBuilder()
373             .expectTypeHeader("myType")
374             .expectIssuer(issuer)
375             .expectAudience(audience)
376             .allowMissingExpiration()
377             .build();
378     VerifiedJwt token = mac.verifyMacAndDecode(compact, validator);
379 
380     assertThat(token.getTypeHeader()).isEqualTo("myType");
381     assertThat(token.getNumberClaim("amount")).isEqualTo(amount);
382     assertThat(token.getIssuer()).isEqualTo(issuer);
383     assertThat(token.getAudiences()).containsExactly(audience);
384     assertThat(token.getJwtId()).isEqualTo(jwtId);
385   }
386 
387   @Test
verify_expired_shouldThrow()388   public void verify_expired_shouldThrow() throws Exception {
389     KeyTemplate template = KeyTemplates.get("JWT_HS256");
390     KeysetHandle handle = KeysetHandle.generateNew(template);
391     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
392 
393     Clock clock1 = Clock.systemUTC();
394     // This token expires in 1 minute in the future.
395     RawJwt token =
396         RawJwt.newBuilder()
397             .setExpiration(clock1.instant().plus(Duration.ofMinutes(1)))
398             .build();
399     String compact = mac.computeMacAndEncode(token);
400 
401     // Move the clock to 2 minutes in the future.
402     Clock clock2 = Clock.offset(clock1, Duration.ofMinutes(2));
403     JwtValidator validator = JwtValidator.newBuilder().setClock(clock2).build();
404 
405     assertThrows(JwtInvalidException.class, () -> mac.verifyMacAndDecode(compact, validator));
406   }
407 
408   @Test
verify_notExpired_success()409   public void verify_notExpired_success() throws Exception {
410     KeyTemplate template = KeyTemplates.get("JWT_HS256");
411     KeysetHandle handle = KeysetHandle.generateNew(template);
412     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
413 
414     Clock clock = Clock.systemUTC();
415     // This token expires in 1 minute in the future.
416     Instant expiration = clock.instant().plus(Duration.ofMinutes(1));
417     RawJwt unverified =
418         RawJwt.newBuilder().setExpiration(expiration).build();
419     String compact = mac.computeMacAndEncode(unverified);
420     JwtValidator validator = JwtValidator.newBuilder().build();
421     VerifiedJwt token = mac.verifyMacAndDecode(compact, validator);
422 
423     assertThat(token.getExpiration()).isEqualTo(unverified.getExpiration());
424   }
425 
426   @Test
verify_notExpired_clockSkew_success()427   public void verify_notExpired_clockSkew_success() throws Exception {
428     KeyTemplate template = KeyTemplates.get("JWT_HS256");
429     KeysetHandle handle = KeysetHandle.generateNew(template);
430     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
431 
432     Clock clock1 = Clock.systemUTC();
433     // This token expires in 1 minutes in the future.
434     Instant expiration = clock1.instant().plus(Duration.ofMinutes(1));
435     RawJwt unverified =
436         RawJwt.newBuilder().setExpiration(expiration).build();
437     String compact = mac.computeMacAndEncode(unverified);
438 
439     // A clock skew of 1 minute is allowed.
440     JwtValidator validator = JwtValidator.newBuilder().setClockSkew(Duration.ofMinutes(1)).build();
441     VerifiedJwt token = mac.verifyMacAndDecode(compact, validator);
442 
443     assertThat(token.getExpiration()).isEqualTo(unverified.getExpiration());
444   }
445 
446   @Test
verify_before_shouldThrow()447   public void verify_before_shouldThrow() throws Exception {
448     KeyTemplate template = KeyTemplates.get("JWT_HS256");
449     KeysetHandle handle = KeysetHandle.generateNew(template);
450     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
451 
452     Clock clock = Clock.systemUTC();
453     // This token cannot be used until 1 minute in the future.
454     Instant notBefore = clock.instant().plus(Duration.ofMinutes(1));
455     RawJwt unverified =
456         RawJwt.newBuilder().setNotBefore(notBefore).withoutExpiration().build();
457     String compact = mac.computeMacAndEncode(unverified);
458 
459     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
460 
461     assertThrows(JwtInvalidException.class, () -> mac.verifyMacAndDecode(compact, validator));
462   }
463 
464   @Test
validate_notBefore_success()465   public void validate_notBefore_success() throws Exception {
466     KeyTemplate template = KeyTemplates.get("JWT_HS256");
467     KeysetHandle handle = KeysetHandle.generateNew(template);
468     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
469 
470     Clock clock1 = Clock.systemUTC();
471     // This token cannot be used until 1 minute in the future.
472     Instant notBefore = clock1.instant().plus(Duration.ofMinutes(1));
473     RawJwt unverified =
474         RawJwt.newBuilder().setNotBefore(notBefore).withoutExpiration().build();
475     String compact = mac.computeMacAndEncode(unverified);
476 
477     // Move the clock to 2 minutes in the future.
478     Clock clock2 = Clock.offset(clock1, Duration.ofMinutes(2));
479     JwtValidator validator =
480         JwtValidator.newBuilder().allowMissingExpiration().setClock(clock2).build();
481     VerifiedJwt token = mac.verifyMacAndDecode(compact, validator);
482 
483     assertThat(token.getNotBefore()).isEqualTo(unverified.getNotBefore());
484   }
485 
486   @Test
validate_notBefore_clockSkew_success()487   public void validate_notBefore_clockSkew_success() throws Exception {
488     KeyTemplate template = KeyTemplates.get("JWT_HS256");
489     KeysetHandle handle = KeysetHandle.generateNew(template);
490     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
491 
492     Clock clock1 = Clock.systemUTC();
493     // This token cannot be used until 1 minute in the future.
494     Instant notBefore = clock1.instant().plus(Duration.ofMinutes(1));
495     RawJwt unverified =
496         RawJwt.newBuilder().setNotBefore(notBefore).withoutExpiration().build();
497     String compact = mac.computeMacAndEncode(unverified);
498 
499     // A clock skew of 1 minute is allowed.
500     JwtValidator validator =
501         JwtValidator.newBuilder()
502             .allowMissingExpiration()
503             .setClockSkew(Duration.ofMinutes(1))
504             .build();
505     VerifiedJwt token = mac.verifyMacAndDecode(compact, validator);
506 
507     assertThat(token.getNotBefore()).isEqualTo(unverified.getNotBefore());
508   }
509 
510   @Test
verify_noAudienceInJwt_shouldThrow()511   public void verify_noAudienceInJwt_shouldThrow() throws Exception {
512     KeyTemplate template = KeyTemplates.get("JWT_HS256");
513     KeysetHandle handle = KeysetHandle.generateNew(template);
514     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
515 
516     RawJwt unverified = RawJwt.newBuilder().withoutExpiration().build();
517     String compact = mac.computeMacAndEncode(unverified);
518     JwtValidator validator =
519         JwtValidator.newBuilder().allowMissingExpiration().expectAudience("foo").build();
520 
521     assertThrows(JwtInvalidException.class, () -> mac.verifyMacAndDecode(compact, validator));
522   }
523 
524   @Test
verify_noAudienceInValidator_shouldThrow()525   public void verify_noAudienceInValidator_shouldThrow() throws Exception {
526     KeyTemplate template = KeyTemplates.get("JWT_HS256");
527     KeysetHandle handle = KeysetHandle.generateNew(template);
528     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
529 
530     RawJwt unverified =
531         RawJwt.newBuilder().addAudience("foo").withoutExpiration().build();
532     String compact = mac.computeMacAndEncode(unverified);
533     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
534 
535     assertThrows(JwtInvalidException.class, () -> mac.verifyMacAndDecode(compact, validator));
536   }
537 
538   @Test
verify_wrongAudience_shouldThrow()539   public void verify_wrongAudience_shouldThrow() throws Exception {
540     KeyTemplate template = KeyTemplates.get("JWT_HS256");
541     KeysetHandle handle = KeysetHandle.generateNew(template);
542     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
543 
544     RawJwt unverified =
545         RawJwt.newBuilder().addAudience("foo").withoutExpiration().build();
546     String compact = mac.computeMacAndEncode(unverified);
547     JwtValidator validator =
548         JwtValidator.newBuilder().allowMissingExpiration().expectAudience("bar").build();
549 
550     assertThrows(JwtInvalidException.class, () -> mac.verifyMacAndDecode(compact, validator));
551   }
552 
553   @Test
verify_audience_success()554   public void verify_audience_success() throws Exception {
555     KeyTemplate template = KeyTemplates.get("JWT_HS256");
556     KeysetHandle handle = KeysetHandle.generateNew(template);
557     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
558 
559     RawJwt unverified =
560         RawJwt.newBuilder().addAudience("foo").withoutExpiration().build();
561     String compact = mac.computeMacAndEncode(unverified);
562     JwtValidator validator =
563         JwtValidator.newBuilder().allowMissingExpiration().expectAudience("foo").build();
564     VerifiedJwt token = mac.verifyMacAndDecode(compact, validator);
565 
566     assertThat(token.getAudiences()).containsExactly("foo");
567   }
568 
569   @Test
verify_multipleAudiences_success()570   public void verify_multipleAudiences_success() throws Exception {
571     KeyTemplate template = KeyTemplates.get("JWT_HS256");
572     KeysetHandle handle = KeysetHandle.generateNew(template);
573     JwtMac mac = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
574 
575     RawJwt unverified =
576         RawJwt.newBuilder()
577             .addAudience("foo")
578             .addAudience("bar").withoutExpiration()
579             .build();
580     String compact = mac.computeMacAndEncode(unverified);
581     JwtValidator validator =
582         JwtValidator.newBuilder().allowMissingExpiration().expectAudience("bar").build();
583     VerifiedJwt token = mac.verifyMacAndDecode(compact, validator);
584 
585     assertThat(token.getAudiences()).containsExactly("foo", "bar");
586   }
587 
generateSignedCompact(PrfMac mac, JsonObject header, JsonObject payload)588   private static String generateSignedCompact(PrfMac mac, JsonObject header, JsonObject payload)
589       throws GeneralSecurityException {
590     String payloadBase64 = Base64.urlSafeEncode(payload.toString().getBytes(UTF_8));
591     String headerBase64 = Base64.urlSafeEncode(header.toString().getBytes(UTF_8));
592     String unsignedCompact = headerBase64 + "." + payloadBase64;
593     String signature = Base64.urlSafeEncode(mac.computeMac(unsignedCompact.getBytes(UTF_8)));
594     return unsignedCompact + "." + signature;
595   }
596 
597   @Test
createSignVerifyRaw_withDifferentHeaders()598   public void createSignVerifyRaw_withDifferentHeaders() throws Exception {
599     KeyTemplate template = KeyTemplates.get("JWT_HS256_RAW");
600     KeysetHandle handle = KeysetHandle.generateNew(template);
601     com.google.crypto.tink.jwt.JwtHmacKey key =
602         (com.google.crypto.tink.jwt.JwtHmacKey) handle.getAt(0).getKey();
603 
604     byte[] keyValue = key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get());
605     SecretKeySpec keySpec = new SecretKeySpec(keyValue, "HMAC");
606     PrfHmacJce prf = new PrfHmacJce("HMACSHA256", keySpec);
607     PrfMac rawPrimitive = new PrfMac(prf, prf.getMaxOutputLength());
608     JwtMac primitive = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
609 
610     JsonObject payload = new JsonObject();
611     payload.addProperty("jti", "jwtId");
612     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
613 
614     // Normal, valid signed compact.
615     JsonObject normalHeader = new JsonObject();
616     normalHeader.addProperty("alg", "HS256");
617     String normalSignedCompact = generateSignedCompact(rawPrimitive, normalHeader, payload);
618     Object unused = primitive.verifyMacAndDecode(normalSignedCompact, validator);
619 
620     // valid token, with "typ" set in the header
621     JsonObject goodHeader = new JsonObject();
622     goodHeader.addProperty("alg", "HS256");
623     goodHeader.addProperty("typ", "typeHeader");
624     String goodSignedCompact = generateSignedCompact(rawPrimitive, goodHeader, payload);
625     unused =
626         primitive.verifyMacAndDecode(
627             goodSignedCompact,
628             JwtValidator.newBuilder()
629                 .expectTypeHeader("typeHeader")
630                 .allowMissingExpiration()
631                 .build());
632 
633     // invalid token with an empty header
634     JsonObject emptyHeader = new JsonObject();
635     String emptyHeaderSignedCompact = generateSignedCompact(rawPrimitive, emptyHeader, payload);
636     assertThrows(
637         GeneralSecurityException.class,
638         () -> primitive.verifyMacAndDecode(emptyHeaderSignedCompact, validator));
639 
640     // invalid token with a valid but incorrect algorithm in the header
641     JsonObject badAlgoHeader = new JsonObject();
642     badAlgoHeader.addProperty("alg", "RS256");
643     String badAlgoSignedCompact = generateSignedCompact(rawPrimitive, badAlgoHeader, payload);
644     assertThrows(
645         GeneralSecurityException.class,
646         () -> primitive.verifyMacAndDecode(badAlgoSignedCompact, validator));
647 
648     // for raw keys without customKid, the validation should work even if a "kid" header is present.
649     JsonObject headerWithUnknownKid = new JsonObject();
650     headerWithUnknownKid.addProperty("alg", "HS256");
651     headerWithUnknownKid.addProperty("kid", "unknown");
652     String tokenWithUnknownKid = generateSignedCompact(
653         rawPrimitive, headerWithUnknownKid, payload);
654     unused = primitive.verifyMacAndDecode(tokenWithUnknownKid, validator);
655   }
656 
657   @Test
createSignVerifyTink_withDifferentHeaders()658   public void createSignVerifyTink_withDifferentHeaders() throws Exception {
659     KeyTemplate template = KeyTemplates.get("JWT_HS256");
660     KeysetHandle handle = KeysetHandle.generateNew(template);
661     com.google.crypto.tink.jwt.JwtHmacKey key =
662         (com.google.crypto.tink.jwt.JwtHmacKey) handle.getAt(0).getKey();
663 
664     byte[] keyValue = key.getKeyBytes().toByteArray(InsecureSecretKeyAccess.get());
665     SecretKeySpec keySpec = new SecretKeySpec(keyValue, "HMAC");
666     PrfHmacJce prf = new PrfHmacJce("HMACSHA256", keySpec);
667     PrfMac rawPrimitive = new PrfMac(prf, prf.getMaxOutputLength());
668     JwtMac primitive = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
669     String kid = key.getKid().get();
670 
671     JsonObject payload = new JsonObject();
672     payload.addProperty("jti", "jwtId");
673     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
674 
675     // Normal, valid signed compact.
676     JsonObject normalHeader = new JsonObject();
677     normalHeader.addProperty("alg", "HS256");
678     normalHeader.addProperty("kid", kid);
679     String normalToken = generateSignedCompact(rawPrimitive, normalHeader, payload);
680     Object unused = primitive.verifyMacAndDecode(normalToken, validator);
681 
682     // valid token, with "typ" set in the header
683     JsonObject headerWithTyp = new JsonObject();
684     headerWithTyp.addProperty("alg", "HS256");
685     headerWithTyp.addProperty("typ", "typeHeader");
686     headerWithTyp.addProperty("kid", kid);
687     String tokenWithTyp = generateSignedCompact(rawPrimitive, headerWithTyp, payload);
688     unused =
689         primitive.verifyMacAndDecode(
690             tokenWithTyp,
691             JwtValidator.newBuilder()
692                 .expectTypeHeader("typeHeader")
693                 .allowMissingExpiration()
694                 .build());
695 
696     // invalid token without algorithm
697     JsonObject headerWithoutAlg = new JsonObject();
698     headerWithoutAlg.addProperty("kid", kid);
699     String tokenWithoutAlg = generateSignedCompact(rawPrimitive, headerWithoutAlg, payload);
700     assertThrows(
701         GeneralSecurityException.class,
702         () -> primitive.verifyMacAndDecode(tokenWithoutAlg, validator));
703 
704     // invalid token with a valid but incorrect algorithm in the header
705     JsonObject headerWithBadAlg = new JsonObject();
706     headerWithBadAlg.addProperty("alg", "RS256");
707     headerWithBadAlg.addProperty("kid", kid);
708     String tokenWithBadAlg = generateSignedCompact(rawPrimitive, headerWithBadAlg, payload);
709     assertThrows(
710         GeneralSecurityException.class,
711         () -> primitive.verifyMacAndDecode(tokenWithBadAlg, validator));
712 
713     // token with an unknown "kid" in the header is valid
714     JsonObject headerWithUnknownKid = new JsonObject();
715     headerWithUnknownKid.addProperty("alg", "HS256");
716     headerWithUnknownKid.addProperty("kid", "unknown");
717     String tokenWithUnknownKid = generateSignedCompact(
718         rawPrimitive, headerWithUnknownKid, payload);
719     assertThrows(
720         GeneralSecurityException.class,
721         () -> primitive.verifyMacAndDecode(tokenWithUnknownKid, validator));
722   }
723 
getRfc7515ExampleKeysetHandle()724   private static KeysetHandle getRfc7515ExampleKeysetHandle() throws Exception {
725     String keyValue =
726         "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
727     JwtHmacParameters parameters =
728         JwtHmacParameters.builder()
729             .setKeySizeBytes(64)
730             .setKidStrategy(JwtHmacParameters.KidStrategy.IGNORED)
731             .setAlgorithm(JwtHmacParameters.Algorithm.HS256)
732             .build();
733     com.google.crypto.tink.jwt.JwtHmacKey newKey =
734         com.google.crypto.tink.jwt.JwtHmacKey.builder()
735             .setParameters(parameters)
736             .setKeyBytes(
737                 SecretBytes.copyFrom(Base64.urlSafeDecode(keyValue), InsecureSecretKeyAccess.get()))
738             .build();
739     return KeysetHandle.newBuilder()
740         .addEntry(KeysetHandle.importKey(newKey).withFixedId(123).makePrimary())
741         .build();
742   }
743 
744   // Test vectors copied from https://tools.ietf.org/html/rfc7515#appendix-A.1.
745   @Test
verify_rfc7515TestVector_shouldThrow()746   public void verify_rfc7515TestVector_shouldThrow() throws Exception {
747     KeysetHandle handle = getRfc7515ExampleKeysetHandle();
748     JwtMac primitive = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
749 
750     // The sample token has expired since 2011-03-22.
751     String compact =
752         "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9."
753             + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQo"
754             + "gImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ."
755             + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
756 
757     JwtValidator validator = JwtValidator.newBuilder().build();
758     assertThrows(JwtInvalidException.class, () -> primitive.verifyMacAndDecode(compact, validator));
759   }
760 
761   // Test vectors copied from https://tools.ietf.org/html/rfc7515#appendix-A.1.
762   @Test
verify_rfc7515TestVector_fixedClock_success()763   public void verify_rfc7515TestVector_fixedClock_success() throws Exception {
764     KeysetHandle handle = getRfc7515ExampleKeysetHandle();
765     JwtMac primitive = handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
766 
767     // The sample token has expired since 2011-03-22T18:43:00Z.
768     String compact =
769         "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9."
770             + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQo"
771             + "gImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ."
772             + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
773 
774     // One minute earlier than the expiration time of the sample token.
775     String instant = "2011-03-22T18:42:00Z";
776     Clock clock = Clock.fixed(Instant.parse(instant), ZoneOffset.UTC);
777     JwtValidator validator =
778         JwtValidator.newBuilder()
779             .expectTypeHeader("JWT")
780             .expectIssuer("joe")
781             .setClock(clock)
782             .build();
783 
784     VerifiedJwt token = primitive.verifyMacAndDecode(compact, validator);
785 
786     assertThat(token.getIssuer()).isEqualTo("joe");
787     assertThat(token.getBooleanClaim("http://example.com/is_root")).isTrue();
788   }
789 
790   /* Create a new keyset handle with the "custom_kid" value set. */
withCustomKid(KeysetHandle keysetHandle, String customKid)791   private KeysetHandle withCustomKid(KeysetHandle keysetHandle, String customKid)
792       throws Exception {
793     com.google.crypto.tink.jwt.JwtHmacKey key =
794         (com.google.crypto.tink.jwt.JwtHmacKey) keysetHandle.getAt(0).getKey();
795 
796     JwtHmacParameters newParameters =
797         JwtHmacParameters.builder()
798             .setKeySizeBytes(key.getParameters().getKeySizeBytes())
799             .setKidStrategy(JwtHmacParameters.KidStrategy.CUSTOM)
800             .setAlgorithm(key.getParameters().getAlgorithm())
801             .build();
802     com.google.crypto.tink.jwt.JwtHmacKey newKey =
803         com.google.crypto.tink.jwt.JwtHmacKey.builder()
804             .setParameters(newParameters)
805             .setCustomKid(customKid)
806             .setKeyBytes(key.getKeyBytes())
807             .build();
808 
809     return KeysetHandle.newBuilder()
810         .addEntry(KeysetHandle.importKey(newKey).withFixedId(123).makePrimary())
811         .build();
812   }
813 
814   @Test
macWithCustomKid()815   public void macWithCustomKid() throws Exception {
816     KeyTemplate template = KeyTemplates.get("JWT_HS256_RAW");
817     KeysetHandle handleWithoutKid = KeysetHandle.generateNew(template);
818     KeysetHandle handleWithKid =
819         withCustomKid(handleWithoutKid, "Lorem ipsum dolor sit amet, consectetur adipiscing elit");
820     JwtMac jwtMacWithoutKid =
821         handleWithoutKid.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
822     JwtMac jwtMacWithKid = handleWithKid.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
823     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
824 
825     RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build();
826     String compactWithKid = jwtMacWithKid.computeMacAndEncode(rawToken);
827     String compactWithoutKid = jwtMacWithoutKid.computeMacAndEncode(rawToken);
828 
829     // Verify the kid in the header
830     String jsonHeaderWithKid = JwtFormat.splitSignedCompact(compactWithKid).header;
831     String kid = JsonUtil.parseJson(jsonHeaderWithKid).get("kid").getAsString();
832     assertThat(kid).isEqualTo("Lorem ipsum dolor sit amet, consectetur adipiscing elit");
833     String jsonHeaderWithoutKid = JwtFormat.splitSignedCompact(compactWithoutKid).header;
834     assertThat(JsonUtil.parseJson(jsonHeaderWithoutKid).has("kid")).isFalse();
835 
836     // Even if custom_kid is set, we don't require a "kid" in the header.
837     assertThat(jwtMacWithKid.verifyMacAndDecode(compactWithKid, validator).getJwtId())
838         .isEqualTo("jwtId");
839     assertThat(jwtMacWithoutKid.verifyMacAndDecode(compactWithKid, validator).getJwtId())
840         .isEqualTo("jwtId");
841 
842     assertThat(jwtMacWithKid.verifyMacAndDecode(compactWithoutKid, validator).getJwtId())
843         .isEqualTo("jwtId");
844     assertThat(jwtMacWithoutKid.verifyMacAndDecode(compactWithoutKid, validator).getJwtId())
845         .isEqualTo("jwtId");
846   }
847 
848   @Test
macWithWrongCustomKid()849   public void macWithWrongCustomKid() throws Exception {
850     KeyTemplate template = KeyTemplates.get("JWT_HS256_RAW");
851     KeysetHandle handleWithoutKid = KeysetHandle.generateNew(template);
852     KeysetHandle handleWithKid = withCustomKid(handleWithoutKid, "kid");
853     KeysetHandle handleWithWrongKid = withCustomKid(handleWithoutKid, "wrong kid");
854     JwtMac jwtMacWithKid = handleWithKid.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
855     JwtMac jwtMacWithWrongKid =
856         handleWithWrongKid.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
857     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
858 
859     RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build();
860     String compactWithKid = jwtMacWithKid.computeMacAndEncode(rawToken);
861 
862     assertThrows(
863         JwtInvalidException.class,
864         () -> jwtMacWithWrongKid.verifyMacAndDecode(compactWithKid, validator));
865   }
866 
867   @Test
keyWithKid_tokenWithoutKid()868   public void keyWithKid_tokenWithoutKid() throws Exception {
869     KeyTemplate template = KeyTemplates.get("JWT_HS256_RAW");
870     KeysetHandle handleWithoutKid = KeysetHandle.generateNew(template);
871 
872     com.google.crypto.tink.jwt.JwtHmacKey key =
873         (com.google.crypto.tink.jwt.JwtHmacKey) handleWithoutKid.getAt(0).getKey();
874 
875     JwtHmacParameters newParameters =
876         JwtHmacParameters.builder()
877             .setKeySizeBytes(key.getParameters().getKeySizeBytes())
878             .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
879             .setAlgorithm(key.getParameters().getAlgorithm())
880             .build();
881     com.google.crypto.tink.jwt.JwtHmacKey newKey =
882         com.google.crypto.tink.jwt.JwtHmacKey.builder()
883             .setParameters(newParameters)
884             .setIdRequirement(0x22446688)
885             .setKeyBytes(key.getKeyBytes())
886             .build();
887 
888     KeysetHandle handleWithTinkKid =
889         KeysetHandle.newBuilder()
890             .addEntry(KeysetHandle.importKey(newKey).withFixedId(0x22446688).makePrimary())
891             .build();
892 
893     JwtMac jwtMacWithKid =
894         handleWithTinkKid.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
895     JwtMac jwtMacWithoutKid =
896         handleWithoutKid.getPrimitive(RegistryConfiguration.get(), JwtMac.class);
897     JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build();
898 
899     RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build();
900     String compactWithKid = jwtMacWithoutKid.computeMacAndEncode(rawToken);
901 
902     assertThrows(
903         GeneralSecurityException.class,
904         () -> jwtMacWithKid.verifyMacAndDecode(compactWithKid, validator));
905   }
906 
907   @Test
macWithTinkKeyAndCustomKid_fails()908   public void macWithTinkKeyAndCustomKid_fails() throws Exception {
909     KeyTemplate template = KeyTemplates.get("JWT_HS256");
910     KeysetHandle handle = KeysetHandle.generateNew(template);
911 
912     // Create a new handle with the "kid" value set.
913     Keyset keyset =
914         Keyset.parseFrom(
915             TinkProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get()),
916             ExtensionRegistryLite.getEmptyRegistry());
917 
918     JwtHmacKey hmacKey =
919         JwtHmacKey.parseFrom(
920             keyset.getKey(0).getKeyData().getValue(), ExtensionRegistryLite.getEmptyRegistry());
921     JwtHmacKey hmacKeyWithKid =
922         hmacKey.toBuilder()
923             .setCustomKid(
924                 CustomKid.newBuilder()
925                     .setValue("Lorem ipsum dolor sit amet, consectetur adipiscing elit")
926                     .build())
927             .build();
928     KeyData keyDataWithKid =
929         keyset.getKey(0).getKeyData().toBuilder().setValue(hmacKeyWithKid.toByteString()).build();
930     Keyset.Key keyWithKid = keyset.getKey(0).toBuilder().setKeyData(keyDataWithKid).build();
931     byte[] serializeKeysetWithKid = keyset.toBuilder().setKey(0, keyWithKid).build().toByteArray();
932     // A key with OutputPrefixType TINK and a KID value set needs to be rejected either when parsing
933     // or when we call getPrimitive.
934     assertThrows(
935         GeneralSecurityException.class,
936         () ->
937             TinkProtoKeysetFormat.parseKeyset(serializeKeysetWithKid, InsecureSecretKeyAccess.get())
938                 .getPrimitive(RegistryConfiguration.get(), JwtMac.class));
939   }
940 
941   @Test
serializeAndDeserialize_works()942   public void serializeAndDeserialize_works() throws Exception {
943     KeyTemplate template = KeyTemplates.get("JWT_HS256_RAW");
944     KeysetHandle handle = KeysetHandle.generateNew(template);
945     byte[] serialized =
946         TinkProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get());
947     KeysetHandle parsed =
948         TinkProtoKeysetFormat.parseKeyset(serialized, InsecureSecretKeyAccess.get());
949     assertThat(parsed.equalsKeyset(handle)).isTrue();
950   }
951 
952   @Theory
createKeyWithRejectedParameters_throws( @romDataPoints"KeyManager rejected") JwtHmacParameters params)953   public void createKeyWithRejectedParameters_throws(
954       @FromDataPoints("KeyManager rejected") JwtHmacParameters params) throws Exception {
955     assertThrows(GeneralSecurityException.class, () -> KeysetHandle.generateNew(params));
956   }
957 
958   @Theory
createPrimitiveWithRejectedParameters_throws( @romDataPoints"KeyManager rejected") JwtHmacParameters params)959   public void createPrimitiveWithRejectedParameters_throws(
960       @FromDataPoints("KeyManager rejected") JwtHmacParameters params) throws Exception {
961     com.google.crypto.tink.jwt.JwtHmacKey key =
962         com.google.crypto.tink.jwt.JwtHmacKey.builder()
963             .setParameters(params)
964             .setKeyBytes(SecretBytes.randomBytes(params.getKeySizeBytes()))
965             .setIdRequirement(123)
966             .build();
967     KeysetHandle handle =
968         KeysetHandle.newBuilder().addEntry(KeysetHandle.importKey(key).makePrimary()).build();
969     assertThrows(
970         GeneralSecurityException.class,
971         () -> handle.getPrimitive(RegistryConfiguration.get(), JwtMac.class));
972   }
973 
createRejectedParameters()974   private static JwtHmacParameters[] createRejectedParameters() {
975     return exceptionIsBug(
976         () ->
977             new JwtHmacParameters[] {
978               // Key Sizes below the minimum are rejected.
979               JwtHmacParameters.builder()
980                   .setKeySizeBytes(31)
981                   .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
982                   .setAlgorithm(JwtHmacParameters.Algorithm.HS256)
983                   .build(),
984               JwtHmacParameters.builder()
985                   .setKeySizeBytes(47)
986                   .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
987                   .setAlgorithm(JwtHmacParameters.Algorithm.HS384)
988                   .build(),
989               JwtHmacParameters.builder()
990                   .setKeySizeBytes(63)
991                   .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
992                   .setAlgorithm(JwtHmacParameters.Algorithm.HS512)
993                   .build(),
994               JwtHmacParameters.builder()
995                   .setKeySizeBytes(32)
996                   .setKidStrategy(JwtHmacParameters.KidStrategy.BASE64_ENCODED_KEY_ID)
997                   .setAlgorithm(JwtHmacParameters.Algorithm.HS512)
998                   .build(),
999             });
1000   }
1001 
1002   @DataPoints("KeyManager rejected")
1003   public static final JwtHmacParameters[] RECJECTED_PARAMETERS = createRejectedParameters();
1004 }
1005