• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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