1 // Copyright 2021 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 org.junit.Assert.assertThrows; 21 22 import com.google.crypto.tink.InsecureSecretKeyAccess; 23 import com.google.crypto.tink.KeyTemplate; 24 import com.google.crypto.tink.KeyTemplates; 25 import com.google.crypto.tink.KeysetHandle; 26 import com.google.crypto.tink.KeysetManager; 27 import com.google.crypto.tink.Parameters; 28 import com.google.crypto.tink.RegistryConfiguration; 29 import com.google.crypto.tink.TinkProtoKeysetFormat; 30 import com.google.crypto.tink.internal.MonitoringAnnotations; 31 import com.google.crypto.tink.internal.MutableMonitoringRegistry; 32 import com.google.crypto.tink.internal.testing.FakeMonitoringClient; 33 import com.google.crypto.tink.proto.Keyset; 34 import com.google.crypto.tink.proto.OutputPrefixType; 35 import com.google.crypto.tink.testing.TestUtil; 36 import com.google.protobuf.ExtensionRegistryLite; 37 import java.security.GeneralSecurityException; 38 import java.time.Clock; 39 import java.time.Instant; 40 import java.time.temporal.ChronoUnit; 41 import java.util.List; 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.experimental.theories.DataPoints; 45 import org.junit.experimental.theories.FromDataPoints; 46 import org.junit.experimental.theories.Theories; 47 import org.junit.experimental.theories.Theory; 48 import org.junit.runner.RunWith; 49 50 /** Tests for JwtSignKeyverifyWrapper. */ 51 @RunWith(Theories.class) 52 public class JwtPublicKeySignVerifyWrappersTest { 53 54 @DataPoints("templateNames") 55 public static final String[] TEMPLATE_NAMES = 56 new String[] { 57 "JWT_ES256", 58 "JWT_ES384", 59 "JWT_ES512", 60 "JWT_ES256_RAW", 61 "JWT_RS256_2048_F4", 62 "JWT_RS256_3072_F4", 63 "JWT_RS384_3072_F4", 64 "JWT_RS512_4096_F4", 65 "JWT_RS256_2048_F4_RAW", 66 "JWT_PS256_2048_F4", 67 "JWT_PS256_3072_F4", 68 "JWT_PS384_3072_F4", 69 "JWT_PS512_4096_F4", 70 "JWT_PS256_2048_F4_RAW", 71 }; 72 73 @Before setUp()74 public void setUp() throws GeneralSecurityException { 75 JwtSignatureConfig.register(); 76 } 77 78 @Test test_noPrimary_getSignPrimitive_fails()79 public void test_noPrimary_getSignPrimitive_fails() throws Exception { 80 // The old KeysetManager API allows keysets without primary key. 81 // The KeysetHandle.Builder does not allow this and can't be used in this test. 82 KeyTemplate template = KeyTemplates.get("JWT_ES256"); 83 KeysetManager manager = KeysetManager.withEmptyKeyset().add(template); 84 KeysetHandle handle = manager.getKeysetHandle(); 85 assertThrows( 86 GeneralSecurityException.class, 87 () -> handle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class)); 88 } 89 90 @Test test_noPrimary_getVerifyPrimitive_success()91 public void test_noPrimary_getVerifyPrimitive_success() throws Exception { 92 KeysetHandle privateKeysetHandle = 93 KeysetHandle.newBuilder() 94 .addEntry( 95 KeysetHandle.generateEntryFromParametersName("JWT_ES256") 96 .withRandomId() 97 .makePrimary()) 98 .build(); 99 KeysetHandle publicHandle = privateKeysetHandle.getPublicKeysetHandle(); 100 Object unused = 101 publicHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 102 } 103 104 @Test test_wrapLegacy_throws()105 public void test_wrapLegacy_throws() throws Exception { 106 KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get("JWT_ES256_RAW")); 107 Keyset keyset = 108 Keyset.parseFrom( 109 TinkProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get()), 110 ExtensionRegistryLite.getEmptyRegistry()); 111 Keyset.Builder legacyKeysetBuilder = keyset.toBuilder(); 112 legacyKeysetBuilder.setKey( 113 0, legacyKeysetBuilder.getKey(0).toBuilder().setOutputPrefixType(OutputPrefixType.LEGACY)); 114 KeysetHandle legacyHandle = 115 TinkProtoKeysetFormat.parseKeyset( 116 legacyKeysetBuilder.build().toByteArray(), InsecureSecretKeyAccess.get()); 117 assertThrows( 118 GeneralSecurityException.class, 119 () -> legacyHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class)); 120 121 KeysetHandle publicHandle = legacyHandle.getPublicKeysetHandle(); 122 assertThrows( 123 GeneralSecurityException.class, 124 () -> publicHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class)); 125 } 126 127 @Test test_wrapSingleTinkKey_works()128 public void test_wrapSingleTinkKey_works() throws Exception { 129 KeyTemplate tinkTemplate = KeyTemplates.get("JWT_ES256"); 130 131 KeysetHandle handle = KeysetHandle.generateNew(tinkTemplate); 132 133 JwtPublicKeySign signer = 134 handle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 135 JwtPublicKeyVerify verifier = 136 handle 137 .getPublicKeysetHandle() 138 .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 139 RawJwt rawToken = RawJwt.newBuilder().setJwtId("blah").withoutExpiration().build(); 140 String signedCompact = signer.signAndEncode(rawToken); 141 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 142 VerifiedJwt verifiedToken = verifier.verifyAndDecode(signedCompact, validator); 143 assertThat(verifiedToken.getJwtId()).isEqualTo("blah"); 144 } 145 146 @Test test_wrapSingleRawKey_works()147 public void test_wrapSingleRawKey_works() throws Exception { 148 KeyTemplate template = KeyTemplates.get("JWT_ES256_RAW"); 149 KeysetHandle handle = KeysetHandle.generateNew(template); 150 151 JwtPublicKeySign signer = 152 handle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 153 JwtPublicKeyVerify verifier = 154 handle 155 .getPublicKeysetHandle() 156 .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 157 RawJwt rawToken = RawJwt.newBuilder().setJwtId("blah").withoutExpiration().build(); 158 String signedCompact = signer.signAndEncode(rawToken); 159 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 160 VerifiedJwt verifiedToken = verifier.verifyAndDecode(signedCompact, validator); 161 assertThat(verifiedToken.getJwtId()).isEqualTo("blah"); 162 } 163 164 @Test test_wrapMultipleRawKeys()165 public void test_wrapMultipleRawKeys() throws Exception { 166 KeysetHandle oldHandle = 167 KeysetHandle.newBuilder() 168 .addEntry( 169 KeysetHandle.generateEntryFromParametersName("JWT_ES256_RAW") 170 .withRandomId() 171 .makePrimary()) 172 .build(); 173 KeysetHandle newHandle = 174 KeysetHandle.newBuilder(oldHandle) 175 .addEntry( 176 KeysetHandle.generateEntryFromParametersName("JWT_ES256_RAW") 177 .withRandomId() 178 .makePrimary()) 179 .build(); 180 181 JwtPublicKeySign oldSigner = 182 oldHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 183 JwtPublicKeySign newSigner = 184 newHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 185 186 JwtPublicKeyVerify oldVerifier = 187 oldHandle 188 .getPublicKeysetHandle() 189 .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 190 JwtPublicKeyVerify newVerifier = 191 newHandle 192 .getPublicKeysetHandle() 193 .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 194 195 RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build(); 196 String oldSignedCompact = oldSigner.signAndEncode(rawToken); 197 String newSignedCompact = newSigner.signAndEncode(rawToken); 198 199 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 200 assertThat(oldVerifier.verifyAndDecode(oldSignedCompact, validator).getJwtId()) 201 .isEqualTo("jwtId"); 202 assertThat(newVerifier.verifyAndDecode(oldSignedCompact, validator).getJwtId()) 203 .isEqualTo("jwtId"); 204 assertThat(newVerifier.verifyAndDecode(newSignedCompact, validator).getJwtId()) 205 .isEqualTo("jwtId"); 206 assertThrows( 207 GeneralSecurityException.class, 208 () -> oldVerifier.verifyAndDecode(newSignedCompact, validator)); 209 } 210 211 @Test test_wrapMultipleTinkKeys()212 public void test_wrapMultipleTinkKeys() throws Exception { 213 KeysetHandle oldHandle = 214 KeysetHandle.newBuilder() 215 .addEntry( 216 KeysetHandle.generateEntryFromParametersName("JWT_ES256") 217 .withRandomId() 218 .makePrimary()) 219 .build(); 220 KeysetHandle newHandle = 221 KeysetHandle.newBuilder(oldHandle) 222 .addEntry( 223 KeysetHandle.generateEntryFromParametersName("JWT_ES256") 224 .withRandomId() 225 .makePrimary()) 226 .build(); 227 228 JwtPublicKeySign oldSigner = 229 oldHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 230 JwtPublicKeySign newSigner = 231 newHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 232 233 JwtPublicKeyVerify oldVerifier = 234 oldHandle 235 .getPublicKeysetHandle() 236 .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 237 JwtPublicKeyVerify newVerifier = 238 newHandle 239 .getPublicKeysetHandle() 240 .getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 241 242 RawJwt rawToken = RawJwt.newBuilder().setJwtId("jwtId").withoutExpiration().build(); 243 String oldSignedCompact = oldSigner.signAndEncode(rawToken); 244 String newSignedCompact = newSigner.signAndEncode(rawToken); 245 246 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 247 assertThat(oldVerifier.verifyAndDecode(oldSignedCompact, validator).getJwtId()) 248 .isEqualTo("jwtId"); 249 assertThat(newVerifier.verifyAndDecode(oldSignedCompact, validator).getJwtId()) 250 .isEqualTo("jwtId"); 251 assertThat(newVerifier.verifyAndDecode(newSignedCompact, validator).getJwtId()) 252 .isEqualTo("jwtId"); 253 assertThrows( 254 GeneralSecurityException.class, 255 () -> oldVerifier.verifyAndDecode(newSignedCompact, validator)); 256 } 257 258 // Note: we use Theory as a parametrized test -- different from what the Theory framework intends. 259 @Theory wrongKey_throwsInvalidSignatureException( @romDataPoints"templateNames") String templateName)260 public void wrongKey_throwsInvalidSignatureException( 261 @FromDataPoints("templateNames") String templateName) throws Exception { 262 if (TestUtil.isTsan()) { 263 // KeysetHandle.generateNew is too slow in Tsan. 264 // We do not use assume because Theories expects to find something which is not skipped. 265 return; 266 } 267 KeyTemplate template = KeyTemplates.get(templateName); 268 KeysetHandle keysetHandle = KeysetHandle.generateNew(template); 269 JwtPublicKeySign jwtSign = 270 keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 271 RawJwt rawJwt = RawJwt.newBuilder().withoutExpiration().build(); 272 String compact = jwtSign.signAndEncode(rawJwt); 273 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 274 275 KeysetHandle wrongKeysetHandle = KeysetHandle.generateNew(template); 276 KeysetHandle wrongPublicKeysetHandle = wrongKeysetHandle.getPublicKeysetHandle(); 277 278 JwtPublicKeyVerify wrongJwtVerify = 279 wrongPublicKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 280 assertThrows( 281 GeneralSecurityException.class, () -> wrongJwtVerify.verifyAndDecode(compact, validator)); 282 } 283 284 @Test wrongIssuer_throwsInvalidException()285 public void wrongIssuer_throwsInvalidException() throws Exception { 286 KeyTemplate template = KeyTemplates.get("JWT_ES256"); 287 KeysetHandle keysetHandle = KeysetHandle.generateNew(template); 288 JwtPublicKeySign jwtSigner = 289 keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 290 KeysetHandle publicHandle = keysetHandle.getPublicKeysetHandle(); 291 JwtPublicKeyVerify jwtVerifier = 292 publicHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 293 RawJwt rawJwt = RawJwt.newBuilder().setIssuer("Justus").withoutExpiration().build(); 294 String compact = jwtSigner.signAndEncode(rawJwt); 295 JwtValidator validator = 296 JwtValidator.newBuilder().expectIssuer("Peter").allowMissingExpiration().build(); 297 assertThrows(JwtInvalidException.class, () -> jwtVerifier.verifyAndDecode(compact, validator)); 298 } 299 300 @Test expiredCompact_throwsInvalidException()301 public void expiredCompact_throwsInvalidException() throws Exception { 302 KeyTemplate template = KeyTemplates.get("JWT_ES256"); 303 KeysetHandle keysetHandle = KeysetHandle.generateNew(template); 304 JwtPublicKeySign jwtSigner = 305 keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 306 KeysetHandle publicHandle = keysetHandle.getPublicKeysetHandle(); 307 JwtPublicKeyVerify jwtVerifier = 308 publicHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 309 310 Instant now = Clock.systemUTC().instant().truncatedTo(ChronoUnit.SECONDS); 311 RawJwt rawJwt = 312 RawJwt.newBuilder() 313 .setExpiration(now.minusSeconds(100)) // exipired 100 seconds ago 314 .setIssuedAt(now.minusSeconds(200)) 315 .build(); 316 String compact = jwtSigner.signAndEncode(rawJwt); 317 JwtValidator validator = JwtValidator.newBuilder().build(); 318 assertThrows(JwtInvalidException.class, () -> jwtVerifier.verifyAndDecode(compact, validator)); 319 } 320 321 @Test notYetValidCompact_throwsInvalidException()322 public void notYetValidCompact_throwsInvalidException() throws Exception { 323 KeyTemplate template = KeyTemplates.get("JWT_ES256"); 324 KeysetHandle keysetHandle = KeysetHandle.generateNew(template); 325 JwtPublicKeySign jwtSigner = 326 keysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 327 KeysetHandle publicHandle = keysetHandle.getPublicKeysetHandle(); 328 JwtPublicKeyVerify jwtVerifier = 329 publicHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 330 331 Instant now = Clock.systemUTC().instant().truncatedTo(ChronoUnit.SECONDS); 332 RawJwt rawJwt = 333 RawJwt.newBuilder() 334 .setNotBefore(now.plusSeconds(3600)) // is valid in 1 hour, but not before 335 .setIssuedAt(now) 336 .withoutExpiration() 337 .build(); 338 String compact = jwtSigner.signAndEncode(rawJwt); 339 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 340 assertThrows(JwtInvalidException.class, () -> jwtVerifier.verifyAndDecode(compact, validator)); 341 } 342 343 /* TODO: b/252792776. All keysets without primary should be rejected in every case. */ 344 @Test test_verifyWithoutPrimary_works()345 public void test_verifyWithoutPrimary_works() throws Exception { 346 Parameters parameters = KeyTemplates.get("JWT_ES256").toParameters(); 347 KeysetHandle handle = 348 KeysetHandle.newBuilder() 349 .addEntry( 350 KeysetHandle.generateEntryFromParameters(parameters).withRandomId().makePrimary()) 351 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId()) 352 .build(); 353 KeysetHandle publicHandle = handle.getPublicKeysetHandle(); 354 Keyset publicKeyset = 355 Keyset.parseFrom( 356 TinkProtoKeysetFormat.serializeKeysetWithoutSecret(publicHandle), 357 ExtensionRegistryLite.getEmptyRegistry()); 358 Keyset publicKeysetWithoutPrimary = publicKeyset.toBuilder().setPrimaryKeyId(0).build(); 359 // TODO(b/252792776): Optimally, this would throw. 360 KeysetHandle publicHandleWithoutPrimary = 361 TinkProtoKeysetFormat.parseKeysetWithoutSecret(publicKeysetWithoutPrimary.toByteArray()); 362 363 JwtPublicKeySign signer = 364 handle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 365 // TODO(b/252792776): At least this should throw. 366 JwtPublicKeyVerify verifier = 367 publicHandleWithoutPrimary.getPrimitive( 368 RegistryConfiguration.get(), JwtPublicKeyVerify.class); 369 RawJwt rawToken = RawJwt.newBuilder().setJwtId("blah").withoutExpiration().build(); 370 String signedCompact = signer.signAndEncode(rawToken); 371 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 372 VerifiedJwt verifiedToken = verifier.verifyAndDecode(signedCompact, validator); 373 assertThat(verifiedToken.getJwtId()).isEqualTo("blah"); 374 } 375 376 @Test testWithoutAnnotations_hasNoMonitoring()377 public void testWithoutAnnotations_hasNoMonitoring() throws Exception { 378 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 379 MutableMonitoringRegistry.globalInstance().clear(); 380 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 381 382 KeysetHandle privateKeysetHandle = 383 KeysetHandle.newBuilder() 384 .addEntry( 385 KeysetHandle.generateEntryFromParametersName("JWT_ES256") 386 .makePrimary() 387 .withFixedId(42)) 388 .build(); 389 KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle(); 390 JwtPublicKeySign signer = 391 privateKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 392 RawJwt rawJwt = RawJwt.newBuilder().setJwtId("id123").withoutExpiration().build(); 393 String signedCompact = signer.signAndEncode(rawJwt); 394 395 JwtPublicKeyVerify verifier = 396 publicKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 397 398 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 399 VerifiedJwt verifiedToken = verifier.verifyAndDecode(signedCompact, validator); 400 assertThat(verifiedToken.getJwtId()).isEqualTo("id123"); 401 assertThrows( 402 GeneralSecurityException.class, () -> verifier.verifyAndDecode("invalid", validator)); 403 404 assertThat(fakeMonitoringClient.getLogEntries()).isEmpty(); 405 assertThat(fakeMonitoringClient.getLogFailureEntries()).isEmpty(); 406 } 407 408 @Test testWithAnnotations_hasMonitoring()409 public void testWithAnnotations_hasMonitoring() throws Exception { 410 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 411 MutableMonitoringRegistry.globalInstance().clear(); 412 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 413 414 MonitoringAnnotations annotations = 415 MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build(); 416 KeysetHandle privateKeysetHandle = 417 KeysetHandle.newBuilder() 418 .addEntry( 419 KeysetHandle.generateEntryFromParametersName("JWT_ES256") 420 .makePrimary() 421 .withFixedId(42)) 422 .setMonitoringAnnotations(annotations) 423 .build(); 424 KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle(); 425 JwtPublicKeySign signer = 426 privateKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class); 427 RawJwt rawJwt = RawJwt.newBuilder().setJwtId("id123").withoutExpiration().build(); 428 String signedCompact = signer.signAndEncode(rawJwt); 429 430 JwtPublicKeyVerify verifier = 431 publicKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class); 432 433 JwtValidator validator = JwtValidator.newBuilder().allowMissingExpiration().build(); 434 VerifiedJwt verifiedToken = verifier.verifyAndDecode(signedCompact, validator); 435 assertThat(verifiedToken.getJwtId()).isEqualTo("id123"); 436 assertThrows( 437 GeneralSecurityException.class, () -> verifier.verifyAndDecode("invalid", validator)); 438 439 List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries(); 440 assertThat(logEntries).hasSize(2); 441 FakeMonitoringClient.LogEntry signEntry = logEntries.get(0); 442 assertThat(signEntry.getKeyId()).isEqualTo(42); 443 assertThat(signEntry.getPrimitive()).isEqualTo("jwtsign"); 444 assertThat(signEntry.getApi()).isEqualTo("sign"); 445 assertThat(signEntry.getNumBytesAsInput()).isEqualTo(1); 446 assertThat(signEntry.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 447 448 FakeMonitoringClient.LogEntry verifyEntry = logEntries.get(1); 449 assertThat(verifyEntry.getKeyId()).isEqualTo(42); 450 assertThat(verifyEntry.getPrimitive()).isEqualTo("jwtverify"); 451 assertThat(verifyEntry.getApi()).isEqualTo("verify"); 452 assertThat(verifyEntry.getNumBytesAsInput()).isEqualTo(1); 453 assertThat(verifyEntry.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 454 455 List<FakeMonitoringClient.LogFailureEntry> failures = 456 fakeMonitoringClient.getLogFailureEntries(); 457 assertThat(failures).hasSize(1); 458 FakeMonitoringClient.LogFailureEntry verifyFailure = failures.get(0); 459 assertThat(verifyFailure.getPrimitive()).isEqualTo("jwtverify"); 460 assertThat(verifyFailure.getApi()).isEqualTo("verify"); 461 assertThat(verifyFailure.getKeysetInfo().getPrimaryKeyId()).isEqualTo(42); 462 assertThat(verifyFailure.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 463 } 464 } 465