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