FidoMetadataDownloader.java

1
// Copyright (c) 2015-2021, Yubico AB
2
// All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
6
//
7
// 1. Redistributions of source code must retain the above copyright notice, this
8
//    list of conditions and the following disclaimer.
9
//
10
// 2. Redistributions in binary form must reproduce the above copyright notice,
11
//    this list of conditions and the following disclaimer in the documentation
12
//    and/or other materials provided with the distribution.
13
//
14
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25
package com.yubico.fido.metadata;
26
27
import com.fasterxml.jackson.core.Base64Variants;
28
import com.fasterxml.jackson.databind.ObjectMapper;
29
import com.yubico.fido.metadata.FidoMetadataDownloaderException.Reason;
30
import com.yubico.internal.util.BinaryUtil;
31
import com.yubico.internal.util.CertificateParser;
32
import com.yubico.internal.util.OptionalUtil;
33
import com.yubico.webauthn.data.ByteArray;
34
import com.yubico.webauthn.data.exception.Base64UrlException;
35
import com.yubico.webauthn.data.exception.HexException;
36
import java.io.ByteArrayInputStream;
37
import java.io.File;
38
import java.io.FileInputStream;
39
import java.io.FileNotFoundException;
40
import java.io.FileOutputStream;
41
import java.io.IOException;
42
import java.io.InputStream;
43
import java.net.MalformedURLException;
44
import java.net.URL;
45
import java.net.URLConnection;
46
import java.nio.charset.StandardCharsets;
47
import java.security.DigestException;
48
import java.security.InvalidAlgorithmParameterException;
49
import java.security.InvalidKeyException;
50
import java.security.KeyManagementException;
51
import java.security.KeyStore;
52
import java.security.KeyStoreException;
53
import java.security.MessageDigest;
54
import java.security.NoSuchAlgorithmException;
55
import java.security.Signature;
56
import java.security.SignatureException;
57
import java.security.cert.CRL;
58
import java.security.cert.CRLException;
59
import java.security.cert.CertPath;
60
import java.security.cert.CertPathValidator;
61
import java.security.cert.CertPathValidatorException;
62
import java.security.cert.CertStore;
63
import java.security.cert.CertStoreParameters;
64
import java.security.cert.CertificateException;
65
import java.security.cert.CertificateFactory;
66
import java.security.cert.CollectionCertStoreParameters;
67
import java.security.cert.PKIXParameters;
68
import java.security.cert.TrustAnchor;
69
import java.security.cert.X509Certificate;
70
import java.time.Clock;
71
import java.util.ArrayList;
72
import java.util.Collection;
73
import java.util.Collections;
74
import java.util.Date;
75
import java.util.List;
76
import java.util.Optional;
77
import java.util.Scanner;
78
import java.util.Set;
79
import java.util.UUID;
80
import java.util.function.Consumer;
81
import java.util.function.Supplier;
82
import java.util.stream.Collectors;
83
import java.util.stream.Stream;
84
import javax.net.ssl.HttpsURLConnection;
85
import javax.net.ssl.SSLContext;
86
import javax.net.ssl.TrustManagerFactory;
87
import lombok.AccessLevel;
88
import lombok.AllArgsConstructor;
89
import lombok.NonNull;
90
import lombok.RequiredArgsConstructor;
91
import lombok.Value;
92
import lombok.extern.slf4j.Slf4j;
93
94
/**
95
 * Utility for downloading, caching and verifying Fido Metadata Service BLOBs and associated
96
 * certificates.
97
 *
98
 * <p>This class is NOT THREAD SAFE since it reads and writes caches. However, it has no internal
99
 * mutable state, so instances MAY be reused in single-threaded or externally synchronized contexts.
100
 * See also the {@link #loadCachedBlob()} and {@link #refreshBlob()} methods.
101
 *
102
 * <p>Use the {@link #builder() builder} to configure settings, then use the {@link
103
 * #loadCachedBlob()} and {@link #refreshBlob()} methods to load the metadata BLOB.
104
 */
105
@Slf4j
106
@AllArgsConstructor(access = AccessLevel.PRIVATE)
107
public final class FidoMetadataDownloader {
108
109
  @NonNull private final Set<String> expectedLegalHeaders;
110
  private final X509Certificate trustRootCertificate;
111
  private final URL trustRootUrl;
112
  private final Set<ByteArray> trustRootSha256;
113
  private final File trustRootCacheFile;
114
  private final Supplier<Optional<ByteArray>> trustRootCacheSupplier;
115
  private final Consumer<ByteArray> trustRootCacheConsumer;
116
  private final String blobJwt;
117
  private final URL blobUrl;
118
  private final File blobCacheFile;
119
  private final Supplier<Optional<ByteArray>> blobCacheSupplier;
120
  private final Consumer<ByteArray> blobCacheConsumer;
121
  private final CertStore certStore;
122
  @NonNull private final Clock clock;
123
  private final KeyStore httpsTrustStore;
124
  private final boolean verifyDownloadsOnly;
125
126
  /** For overriding JSON mapper settings in tests. */
127
  private final Supplier<ObjectMapper> makeHeaderJsonMapper;
128
129
  /** For overriding JSON mapper settings in tests. */
130
  private final Supplier<ObjectMapper> makePayloadJsonMapper;
131
132
  /**
133
   * Begin configuring a {@link FidoMetadataDownloader} instance. See the {@link
134
   * FidoMetadataDownloaderBuilder.Step1 Step1} type.
135
   *
136
   * @see FidoMetadataDownloaderBuilder.Step1
137
   */
138
  public static FidoMetadataDownloaderBuilder.Step1 builder() {
139 1 1. builder : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::builder → KILLED
    return new FidoMetadataDownloaderBuilder.Step1();
140
  }
141
142
  @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
143
  public static class FidoMetadataDownloaderBuilder {
144
    @NonNull private final Set<String> expectedLegalHeaders;
145
    private final X509Certificate trustRootCertificate;
146
    private final URL trustRootUrl;
147
    private final Set<ByteArray> trustRootSha256;
148
    private final File trustRootCacheFile;
149
    private final Supplier<Optional<ByteArray>> trustRootCacheSupplier;
150
    private final Consumer<ByteArray> trustRootCacheConsumer;
151
    private final String blobJwt;
152
    private final URL blobUrl;
153
    private final File blobCacheFile;
154
    private final Supplier<Optional<ByteArray>> blobCacheSupplier;
155
    private final Consumer<ByteArray> blobCacheConsumer;
156
157
    private CertStore certStore = null;
158
    @NonNull private Clock clock = Clock.systemUTC();
159
    private KeyStore httpsTrustStore = null;
160
    private boolean verifyDownloadsOnly = false;
161
162
    private Supplier<ObjectMapper> makeHeaderJsonMapper =
163
        FidoMetadataDownloader::defaultHeaderJsonMapper;
164
    private Supplier<ObjectMapper> makePayloadJsonMapper =
165
        FidoMetadataDownloader::defaultPayloadJsonMapper;
166
167
    public FidoMetadataDownloader build() {
168 1 1. build : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::build → KILLED
      return new FidoMetadataDownloader(
169
          expectedLegalHeaders,
170
          trustRootCertificate,
171
          trustRootUrl,
172
          trustRootSha256,
173
          trustRootCacheFile,
174
          trustRootCacheSupplier,
175
          trustRootCacheConsumer,
176
          blobJwt,
177
          blobUrl,
178
          blobCacheFile,
179
          blobCacheSupplier,
180
          blobCacheConsumer,
181
          certStore,
182
          clock,
183
          httpsTrustStore,
184
          verifyDownloadsOnly,
185
          makeHeaderJsonMapper,
186
          makePayloadJsonMapper);
187
    }
188
189
    /**
190
     * Step 1: Set the legal header to expect from the FIDO Metadata Service.
191
     *
192
     * <p>By using the FIDO Metadata Service, you will be subject to its terms of service. This step
193
     * serves two purposes:
194
     *
195
     * <ol>
196
     *   <li>To remind you and any code reviewers that you need to read those terms of service
197
     *       before using this feature.
198
     *   <li>To help you detect if the legal header changes, so you can take appropriate action.
199
     * </ol>
200
     *
201
     * <p>See {@link Step1#expectLegalHeader(String...)}.
202
     *
203
     * @see Step1#expectLegalHeader(String...)
204
     */
205
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
206
    public static class Step1 {
207
208
      /**
209
       * Set legal headers expected in the metadata BLOB.
210
       *
211
       * <p>By using the FIDO Metadata Service, you will be subject to its terms of service. This
212
       * builder step serves two purposes:
213
       *
214
       * <ol>
215
       *   <li>To remind you and any code reviewers that you need to read those terms of service
216
       *       before using this feature.
217
       *   <li>To help you detect if the legal header changes, so you can take appropriate action.
218
       * </ol>
219
       *
220
       * <p>If the legal header in the downloaded BLOB does not equal any of the <code>
221
       * expectedLegalHeaders</code>, an {@link UnexpectedLegalHeader} exception will be thrown in
222
       * the finalizing builder step.
223
       *
224
       * <p>Note that this library makes no guarantee that a change to the FIDO Metadata Service
225
       * terms of service will also cause a change to the legal header in the BLOB.
226
       *
227
       * <p>At the time of this library release, the current legal header is <code>
228
       * "Retrieval and use of this BLOB indicates acceptance of the appropriate agreement located at https://fidoalliance.org/metadata/metadata-legal-terms/"
229
       * </code>.
230
       *
231
       * @param expectedLegalHeaders the set of BLOB legal headers you expect in the metadata BLOB
232
       *     payload.
233
       */
234 1 1. expectLegalHeader : negated conditional → KILLED
      public Step2 expectLegalHeader(@NonNull String... expectedLegalHeaders) {
235 1 1. expectLegalHeader : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step1::expectLegalHeader → KILLED
        return new Step2(Stream.of(expectedLegalHeaders).collect(Collectors.toSet()));
236
      }
237
    }
238
239
    /**
240
     * Step 2: Configure how to retrieve the FIDO Metadata Service trust root certificate when
241
     * necessary.
242
     *
243
     * <p>This step offers three mutually exclusive options:
244
     *
245
     * <ol>
246
     *   <li>Use the default download URL and certificate hash. This is the main intended use case.
247
     *       See {@link #useDefaultTrustRoot()}.
248
     *   <li>Use a custom download URL and certificate hash. This is for future-proofing in case the
249
     *       trust root certificate changes and there is no new release of this library. See {@link
250
     *       #downloadTrustRoot(URL, Set)}.
251
     *   <li>Use a pre-retrieved trust root certificate. It is up to you to perform any integrity
252
     *       checks and cache it as desired. See {@link #useTrustRoot(X509Certificate)}.
253
     * </ol>
254
     */
255
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
256
    public static class Step2 {
257
258
      @NonNull private final Set<String> expectedLegalHeaders;
259
260
      /**
261
       * Download the trust root certificate from a hard-coded URL and verify it against a
262
       * hard-coded SHA-256 hash.
263
       *
264
       * <p>This is an alias of:
265
       *
266
       * <pre>
267
       * downloadTrustRoot(
268
       *   new URL("https://secure.globalsign.com/cacert/root-r3.crt"),
269
       *   Collections.singleton(ByteArray.fromHex("cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b"))
270
       * )
271
       * </pre>
272
       *
273
       * This is the current FIDO Metadata Service trust root certificate at the time of this
274
       * library release.
275
       *
276
       * @see #downloadTrustRoot(URL, Set)
277
       */
278
      public Step3 useDefaultTrustRoot() {
279
        try {
280 1 1. useDefaultTrustRoot : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step2::useDefaultTrustRoot → NO_COVERAGE
          return downloadTrustRoot(
281
              new URL("https://secure.globalsign.com/cacert/root-r3.crt"),
282
              Collections.singleton(
283
                  ByteArray.fromHex(
284
                      "cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b")));
285
        } catch (MalformedURLException e) {
286
          throw new RuntimeException(
287
              "Bad hard-coded trust root certificate URL. Please file a bug report.", e);
288
        } catch (HexException e) {
289
          throw new RuntimeException(
290
              "Bad hard-coded trust root certificate hash. Please file a bug report.", e);
291
        }
292
      }
293
294
      /**
295
       * Download the trust root certificate from the given HTTPS <code>url</code> and verify its
296
       * SHA-256 hash against <code>acceptedCertSha256</code>.
297
       *
298
       * <p>The certificate will be downloaded if it does not exist in the cache, or if the cached
299
       * certificate is not currently valid.
300
       *
301
       * <p>If the cert is downloaded, it is also written to the cache {@link File} or {@link
302
       * Consumer} configured in the {@link Step3 next step}.
303
       *
304
       * @param url the HTTP URL to download. It MUST use the <code>https:</code> scheme.
305
       * @param acceptedCertSha256 a set of SHA-256 hashes to verify the downloaded certificate
306
       *     against. The downloaded certificate MUST match at least one of these hashes.
307
       * @throws IllegalArgumentException if <code>url</code> is not a HTTPS URL.
308
       */
309 2 1. downloadTrustRoot : negated conditional → KILLED
2. downloadTrustRoot : negated conditional → KILLED
      public Step3 downloadTrustRoot(@NonNull URL url, @NonNull Set<ByteArray> acceptedCertSha256) {
310 1 1. downloadTrustRoot : negated conditional → KILLED
        if (!"https".equals(url.getProtocol())) {
311
          throw new IllegalArgumentException("Trust certificate download URL must be a HTTPS URL.");
312
        }
313 1 1. downloadTrustRoot : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step2::downloadTrustRoot → KILLED
        return new Step3(this, null, url, acceptedCertSha256);
314
      }
315
316
      /**
317
       * Use the given trust root certificate. It is the caller's responsibility to perform any
318
       * integrity checks and/or caching logic.
319
       *
320
       * @param trustRootCertificate the certificate to use as the FIDO Metadata Service trust root.
321
       */
322 1 1. useTrustRoot : negated conditional → KILLED
      public Step4 useTrustRoot(@NonNull X509Certificate trustRootCertificate) {
323 1 1. useTrustRoot : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step2::useTrustRoot → KILLED
        return new Step4(new Step3(this, trustRootCertificate, null, null), null, null, null);
324
      }
325
    }
326
327
    /**
328
     * Step 3: Configure how to cache the trust root certificate.
329
     *
330
     * <p>This step offers two mutually exclusive options:
331
     *
332
     * <ol>
333
     *   <li>Cache the trust root certificate in a {@link File}. See {@link
334
     *       Step3#useTrustRootCacheFile(File)}.
335
     *   <li>Cache the trust root certificate using a {@link Supplier} to read the cache and a
336
     *       {@link Consumer} to write the cache. See {@link Step3#useTrustRootCache(Supplier,
337
     *       Consumer)}.
338
     * </ol>
339
     */
340
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
341
    public static class Step3 {
342
      @NonNull private final Step2 step2;
343
      private final X509Certificate trustRootCertificate;
344
      private final URL trustRootUrl;
345
      private final Set<ByteArray> trustRootSha256;
346
347
      /**
348
       * Cache the trust root certificate in the file <code>cacheFile</code>.
349
       *
350
       * <p>If <code>cacheFile</code> exists, is a normal file, is readable, matches one of the
351
       * SHA-256 hashes configured in the previous step, and contains a currently valid X.509
352
       * certificate, then it will be used as the trust root for the FIDO Metadata Service blob.
353
       *
354
       * <p>Otherwise, the trust root certificate will be downloaded and written to this file.
355
       */
356 1 1. useTrustRootCacheFile : negated conditional → KILLED
      public Step4 useTrustRootCacheFile(@NonNull File cacheFile) {
357 1 1. useTrustRootCacheFile : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step3::useTrustRootCacheFile → KILLED
        return new Step4(this, cacheFile, null, null);
358
      }
359
360
      /**
361
       * Cache the trust root certificate using a {@link Supplier} to read the cache, and using a
362
       * {@link Consumer} to write the cache.
363
       *
364
       * <p>If <code>getCachedTrustRootCert</code> returns non-empty, the value matches one of the
365
       * SHA-256 hashes configured in the previous step, and is a currently valid X.509 certificate,
366
       * then it will be used as the trust root for the FIDO Metadata Service blob.
367
       *
368
       * <p>Otherwise, the trust root certificate will be downloaded and written to <code>
369
       * writeCachedTrustRootCert</code>.
370
       *
371
       * @param getCachedTrustRootCert a {@link Supplier} that fetches the cached trust root
372
       *     certificate if it exists. MUST NOT return <code>null</code>. The returned value, if
373
       *     present, MUST be the trust root certificate in X.509 DER format.
374
       * @param writeCachedTrustRootCert a {@link Consumer} that accepts the trust root certificate
375
       *     in X.509 DER format and writes it to the cache. Its argument will never be <code>null
376
       *     </code>.
377
       */
378
      public Step4 useTrustRootCache(
379 1 1. useTrustRootCache : negated conditional → KILLED
          @NonNull Supplier<Optional<ByteArray>> getCachedTrustRootCert,
380 1 1. useTrustRootCache : negated conditional → KILLED
          @NonNull Consumer<ByteArray> writeCachedTrustRootCert) {
381 1 1. useTrustRootCache : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step3::useTrustRootCache → KILLED
        return new Step4(this, null, getCachedTrustRootCert, writeCachedTrustRootCert);
382
      }
383
    }
384
385
    /**
386
     * Step 4: Configure how to fetch the FIDO Metadata Service metadata BLOB.
387
     *
388
     * <p>This step offers three mutually exclusive options:
389
     *
390
     * <ol>
391
     *   <li>Use the default download URL. This is the main intended use case. See {@link
392
     *       #useDefaultBlob()}.
393
     *   <li>Use a custom download URL. This is for future-proofing in case the BLOB download URL
394
     *       changes and there is no new release of this library. See {@link #downloadBlob(URL)}.
395
     *   <li>Use a pre-retrieved BLOB. The signature will still be verified, but it is up to you to
396
     *       renew it when appropriate and perform any caching as desired. See {@link
397
     *       #useBlob(String)}.
398
     * </ol>
399
     */
400
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
401
    public static class Step4 {
402
      @NonNull private final Step3 step3;
403
      private final File trustRootCacheFile;
404
      private final Supplier<Optional<ByteArray>> trustRootCacheSupplier;
405
      private final Consumer<ByteArray> trustRootCacheConsumer;
406
407
      /**
408
       * Download the metadata BLOB from a hard-coded URL.
409
       *
410
       * <p>This is an alias of <code>downloadBlob(new URL("https://mds.fidoalliance.org/"))</code>.
411
       *
412
       * <p>This is the current FIDO Metadata Service BLOB download URL at the time of this library
413
       * release.
414
       *
415
       * @see #downloadBlob(URL)
416
       */
417
      public Step5 useDefaultBlob() {
418
        try {
419 1 1. useDefaultBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step4::useDefaultBlob → NO_COVERAGE
          return downloadBlob(new URL("https://mds.fidoalliance.org/"));
420
        } catch (MalformedURLException e) {
421
          throw new RuntimeException(
422
              "Bad hard-coded trust root certificate URL. Please file a bug report.", e);
423
        }
424
      }
425
426
      /**
427
       * Download the metadata BLOB from the given HTTPS <code>url</code>.
428
       *
429
       * <p>The BLOB will be downloaded if it does not exist in the cache, or if the <code>
430
       * nextUpdate</code> property of the cached BLOB is the current date or earlier.
431
       *
432
       * <p>If the BLOB is downloaded, it is also written to the cache {@link File} or {@link
433
       * Consumer} configured in the next step.
434
       *
435
       * @param url the HTTP URL to download. It MUST use the <code>https:</code> scheme.
436
       */
437 1 1. downloadBlob : negated conditional → KILLED
      public Step5 downloadBlob(@NonNull URL url) {
438 1 1. downloadBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step4::downloadBlob → KILLED
        return new Step5(this, null, url);
439
      }
440
441
      /**
442
       * Use the given metadata BLOB; never download it.
443
       *
444
       * <p>The blob signature and trust chain will still be verified, but it is the caller's
445
       * responsibility to renew the metadata BLOB according to the <a
446
       * href="https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#metadata-blob-object-processing-rules">FIDO
447
       * Metadata Service specification</a>.
448
       *
449
       * @param blobJwt the Metadata BLOB in JWT format as defined in <a
450
       *     href="https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#metadata-blob">FIDO
451
       *     Metadata Service §3.1.7. Metadata BLOB</a>. The byte array MUST NOT be Base64-decoded.
452
       * @see <a
453
       *     href="https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#metadata-blob">FIDO
454
       *     Metadata Service §3.1.7. Metadata BLOB</a>
455
       * @see <a
456
       *     href="https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#metadata-blob-object-processing-rules">FIDO
457
       *     Metadata Service §3.2. Metadata BLOB object processing rules</a>
458
       */
459 1 1. useBlob : negated conditional → KILLED
      public FidoMetadataDownloaderBuilder useBlob(@NonNull String blobJwt) {
460 1 1. useBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step4::useBlob → KILLED
        return finishRequiredSteps(new Step5(this, blobJwt, null), null, null, null);
461
      }
462
    }
463
464
    /**
465
     * Step 5: Configure how to cache the metadata BLOB.
466
     *
467
     * <p>This step offers two mutually exclusive options:
468
     *
469
     * <ol>
470
     *   <li>Cache the metadata BLOB in a {@link File}. See {@link Step5#useBlobCacheFile(File)}.
471
     *   <li>Cache the metadata BLOB using a {@link Supplier} to read the cache and a {@link
472
     *       Consumer} to write the cache. See {@link Step5#useBlobCache(Supplier, Consumer)}.
473
     * </ol>
474
     */
475
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
476
    public static class Step5 {
477
      @NonNull private final Step4 step4;
478
      private final String blobJwt;
479
      private final URL blobUrl;
480
481
      /**
482
       * Cache metadata BLOB in the file <code>cacheFile</code>.
483
       *
484
       * <p>If <code>cacheFile</code> exists, is a normal file, is readable, and is not out of date,
485
       * then it will be used as the FIDO Metadata Service BLOB.
486
       *
487
       * <p>Otherwise, the metadata BLOB will be downloaded and written to this file.
488
       *
489
       * @param cacheFile a {@link File} which may or may not exist. If it exists, it MUST contain
490
       *     the metadata BLOB in JWS compact serialization format <a
491
       *     href="https://datatracker.ietf.org/doc/html/rfc7515#section-3.1">[RFC7515]</a>.
492
       */
493 1 1. useBlobCacheFile : negated conditional → KILLED
      public FidoMetadataDownloaderBuilder useBlobCacheFile(@NonNull File cacheFile) {
494 1 1. useBlobCacheFile : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step5::useBlobCacheFile → KILLED
        return finishRequiredSteps(this, cacheFile, null, null);
495
      }
496
497
      /**
498
       * Cache the metadata BLOB using a {@link Supplier} to read the cache, and using a {@link
499
       * Consumer} to write the cache.
500
       *
501
       * <p>If <code>getCachedBlob</code> returns non-empty and the content is not out of date, then
502
       * it will be used as the FIDO Metadata Service BLOB.
503
       *
504
       * <p>Otherwise, the metadata BLOB will be downloaded and written to <code>writeCachedBlob
505
       * </code>.
506
       *
507
       * @param getCachedBlob a {@link Supplier} that fetches the cached metadata BLOB if it exists.
508
       *     MUST NOT return <code>null</code>. The returned value, if present, MUST be in JWS
509
       *     compact serialization format <a
510
       *     href="https://datatracker.ietf.org/doc/html/rfc7515#section-3.1">[RFC7515]</a>.
511
       * @param writeCachedBlob a {@link Consumer} that accepts the metadata BLOB in JWS compact
512
       *     serialization format <a
513
       *     href="https://datatracker.ietf.org/doc/html/rfc7515#section-3.1">[RFC7515]</a> and
514
       *     writes it to the cache. Its argument will never be <code>null</code>.
515
       */
516
      public FidoMetadataDownloaderBuilder useBlobCache(
517 1 1. useBlobCache : negated conditional → KILLED
          @NonNull Supplier<Optional<ByteArray>> getCachedBlob,
518 1 1. useBlobCache : negated conditional → KILLED
          @NonNull Consumer<ByteArray> writeCachedBlob) {
519 1 1. useBlobCache : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step5::useBlobCache → KILLED
        return finishRequiredSteps(this, null, getCachedBlob, writeCachedBlob);
520
      }
521
    }
522
523
    private static FidoMetadataDownloaderBuilder finishRequiredSteps(
524
        FidoMetadataDownloaderBuilder.Step5 step5,
525
        File blobCacheFile,
526
        Supplier<Optional<ByteArray>> blobCacheSupplier,
527
        Consumer<ByteArray> blobCacheConsumer) {
528 1 1. finishRequiredSteps : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::finishRequiredSteps → KILLED
      return new FidoMetadataDownloaderBuilder(
529
          step5.step4.step3.step2.expectedLegalHeaders,
530
          step5.step4.step3.trustRootCertificate,
531
          step5.step4.step3.trustRootUrl,
532
          step5.step4.step3.trustRootSha256,
533
          step5.step4.trustRootCacheFile,
534
          step5.step4.trustRootCacheSupplier,
535
          step5.step4.trustRootCacheConsumer,
536
          step5.blobJwt,
537
          step5.blobUrl,
538
          blobCacheFile,
539
          blobCacheSupplier,
540
          blobCacheConsumer);
541
    }
542
543
    /**
544
     * Use <code>clock</code> as the source of the current time for some application-level logic.
545
     *
546
     * <p>This is primarily intended for testing.
547
     *
548
     * <p>The default is {@link Clock#systemUTC()}.
549
     *
550
     * @param clock a {@link Clock} which the finished {@link FidoMetadataDownloader} will use to
551
     *     tell the time.
552
     */
553 1 1. clock : negated conditional → KILLED
    public FidoMetadataDownloaderBuilder clock(@NonNull Clock clock) {
554
      this.clock = clock;
555 1 1. clock : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::clock → KILLED
      return this;
556
    }
557
558
    /**
559
     * Use the provided CRLs.
560
     *
561
     * <p>CRLs will also be downloaded from distribution points for any certificates with a
562
     * CRLDistributionPoints extension, if the extension can be successfully interpreted. A warning
563
     * message will be logged CRLDistributionPoints parsing fails.
564
     *
565
     * @throws InvalidAlgorithmParameterException if {@link CertStore#getInstance(String,
566
     *     CertStoreParameters)} does.
567
     * @throws NoSuchAlgorithmException if a <code>"Collection"</code> type {@link CertStore}
568
     *     provider is not available.
569
     * @see #useCrls(CertStore)
570
     */
571 1 1. useCrls : negated conditional → KILLED
    public FidoMetadataDownloaderBuilder useCrls(@NonNull Collection<CRL> crls)
572
        throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
573 1 1. useCrls : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::useCrls → KILLED
      return useCrls(CertStore.getInstance("Collection", new CollectionCertStoreParameters(crls)));
574
    }
575
576
    /**
577
     * Use CRLs in the provided {@link CertStore}.
578
     *
579
     * <p>CRLs will also be downloaded from distribution points for any certificates with a
580
     * CRLDistributionPoints extension, if the extension can be successfully interpreted. A warning
581
     * message will be logged CRLDistributionPoints parsing fails.
582
     *
583
     * @see #useCrls(Collection)
584
     */
585
    public FidoMetadataDownloaderBuilder useCrls(CertStore certStore) {
586
      this.certStore = certStore;
587 1 1. useCrls : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::useCrls → KILLED
      return this;
588
    }
589
590
    /**
591
     * Use the provided {@link X509Certificate}s as trust roots for HTTPS downloads.
592
     *
593
     * <p>This is primarily useful when setting {@link Step2#downloadTrustRoot(URL, Set)
594
     * downloadTrustRoot} and/or {@link Step4#downloadBlob(URL) downloadBlob} to download from
595
     * custom servers instead of the defaults.
596
     *
597
     * <p>If provided, these will be used for downloading
598
     *
599
     * <ul>
600
     *   <li>the trust root certificate for the BLOB signature chain, and
601
     *   <li>the metadata BLOB.
602
     * </ul>
603
     *
604
     * If not set, the system default certificate store will be used.
605
     */
606 1 1. trustHttpsCerts : negated conditional → KILLED
    public FidoMetadataDownloaderBuilder trustHttpsCerts(@NonNull X509Certificate... certificates) {
607
      final KeyStore trustStore;
608
      try {
609
        trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
610 1 1. trustHttpsCerts : removed call to java/security/KeyStore::load → KILLED
        trustStore.load(null);
611
      } catch (KeyStoreException
612
          | IOException
613
          | NoSuchAlgorithmException
614
          | CertificateException e) {
615
        throw new RuntimeException(
616
            "Failed to instantiate or initialize KeyStore. This should not be possible, please file a bug report.",
617
            e);
618
      }
619
      for (X509Certificate cert : certificates) {
620
        try {
621 1 1. trustHttpsCerts : removed call to java/security/KeyStore::setCertificateEntry → KILLED
          trustStore.setCertificateEntry(UUID.randomUUID().toString(), cert);
622
        } catch (KeyStoreException e) {
623
          throw new RuntimeException(
624
              "Failed to import HTTPS cert into KeyStore. This should not be possible, please file a bug report.",
625
              e);
626
        }
627
      }
628
      this.httpsTrustStore = trustStore;
629
630 1 1. trustHttpsCerts : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::trustHttpsCerts → KILLED
      return this;
631
    }
632
633
    /**
634
     * If set to <code>true</code>, the BLOB signature will not be verified when loading the BLOB
635
     * from cache or when explicitly set via {@link Step4#useBlob(String)}. This means that if a
636
     * BLOB was successfully verified once and written to cache, that cached value will be
637
     * implicitly trusted when loaded in the future.
638
     *
639
     * <p>If set to <code>false</code>, the BLOB signature will always be verified no matter where
640
     * the BLOB came from. This means that a cached BLOB may become invalid if the BLOB certificate
641
     * expires, even if the BLOB was successfully verified at the time it was downloaded.
642
     *
643
     * <p>The default setting is <code>false</code>.
644
     *
645
     * @param verifyDownloadsOnly <code>true</code> if the BLOB signature should be ignored when
646
     *     loading the BLOB from cache or when explicitly set via {@link Step4#useBlob(String)}.
647
     */
648
    public FidoMetadataDownloaderBuilder verifyDownloadsOnly(final boolean verifyDownloadsOnly) {
649
      this.verifyDownloadsOnly = verifyDownloadsOnly;
650 1 1. verifyDownloadsOnly : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::verifyDownloadsOnly → KILLED
      return this;
651
    }
652
653
    /** For internal testing use only. */
654
    FidoMetadataDownloaderBuilder headerJsonMapper(
655
        final Supplier<ObjectMapper> makeHeaderJsonMapper) {
656
      this.makeHeaderJsonMapper = makeHeaderJsonMapper;
657 1 1. headerJsonMapper : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::headerJsonMapper → NO_COVERAGE
      return this;
658
    }
659
660
    /** For internal testing use only. */
661
    FidoMetadataDownloaderBuilder payloadJsonMapper(
662
        final Supplier<ObjectMapper> makePayloadJsonMapper) {
663
      this.makePayloadJsonMapper = makePayloadJsonMapper;
664 1 1. payloadJsonMapper : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::payloadJsonMapper → NO_COVERAGE
      return this;
665
    }
666
  }
667
668
  /**
669
   * Load the metadata BLOB from cache, or download a fresh one if necessary.
670
   *
671
   * <p>This method is NOT THREAD SAFE since it reads and writes caches.
672
   *
673
   * <p>On each execution this will, in order:
674
   *
675
   * <ol>
676
   *   <li>Download the trust root certificate, if necessary: if the cache is empty, the cache fails
677
   *       to load, or the cached cert is not valid at the current time (as determined by the {@link
678
   *       FidoMetadataDownloaderBuilder#clock(Clock) clock} setting).
679
   *   <li>If downloaded, cache the trust root certificate using the configured {@link File} or
680
   *       {@link Consumer} (see {@link FidoMetadataDownloaderBuilder.Step3})
681
   *   <li>Download the metadata BLOB, if necessary: if the cache is empty, the cache fails to load,
682
   *       or the <code>"nextUpdate"</code> property in the cached BLOB is the current date (as
683
   *       determined by the {@link FidoMetadataDownloaderBuilder#clock(Clock) clock} setting) or
684
   *       earlier.
685
   *   <li>Check the <code>"no"</code> property of the downloaded BLOB, if any, and compare it with
686
   *       the <code>"no"</code> of the cached BLOB, if any. The one with a greater <code>"no"
687
   *       </code> overrides the other, even if its <code>"nextUpdate"</code> is in the past.
688
   *   <li>If a BLOB with a newer <code>"no"</code> was downloaded, verify that the value of its
689
   *       <code>"legalHeader"</code> appears in the configured {@link
690
   *       FidoMetadataDownloaderBuilder.Step1#expectLegalHeader(String...) expectLegalHeader}
691
   *       setting. If not, throw an {@link UnexpectedLegalHeader} exception containing the cached
692
   *       BLOB, if any, and the downloaded BLOB.
693
   *   <li>If a BLOB with a newer <code>"no"</code> was downloaded and had an expected <code>
694
   *       "legalHeader"</code>, cache the new BLOB using the configured {@link File} or {@link
695
   *       Consumer} (see {@link FidoMetadataDownloaderBuilder.Step5}).
696
   * </ol>
697
   *
698
   * No internal mutable state is maintained between invocations of this method; each invocation
699
   * will reload/rewrite caches, perform downloads and check the <code>"legalHeader"
700
   * </code> as necessary. You may therefore reuse a {@link FidoMetadataDownloader} instance and,
701
   * for example, call this method periodically to refresh the BLOB when appropriate. Each call will
702
   * return a new {@link MetadataBLOB} instance; ones already returned will not be updated by
703
   * subsequent calls.
704
   *
705
   * @return the successfully retrieved and validated metadata BLOB.
706
   * @throws Base64UrlException if the explicitly configured or newly downloaded BLOB is not a
707
   *     well-formed JWT in compact serialization.
708
   * @throws CertPathValidatorException if the explicitly configured or newly downloaded BLOB fails
709
   *     certificate path validation.
710
   * @throws CertificateException if the trust root certificate was downloaded and passed the
711
   *     SHA-256 integrity check, but does not contain a currently valid X.509 DER certificate; or
712
   *     if the BLOB signing certificate chain fails to parse.
713
   * @throws DigestException if the trust root certificate was downloaded but failed the SHA-256
714
   *     integrity check.
715
   * @throws FidoMetadataDownloaderException if the explicitly configured or newly downloaded BLOB
716
   *     (if any) has a bad signature and there is no cached BLOB to fall back to.
717
   * @throws IOException if any of the following fails: downloading the trust root certificate,
718
   *     downloading the BLOB, reading or writing any cache file (if any), or parsing the BLOB
719
   *     contents.
720
   * @throws InvalidAlgorithmParameterException if certificate path validation fails.
721
   * @throws InvalidKeyException if signature verification fails.
722
   * @throws NoSuchAlgorithmException if signature verification fails, or if the SHA-256 algorithm
723
   *     or the <code>"Collection"</code> type {@link CertStore} is not available.
724
   * @throws SignatureException if signature verification fails.
725
   * @throws UnexpectedLegalHeader if the downloaded BLOB (if any) contains a <code>"legalHeader"
726
   *     </code> value not configured in {@link
727
   *     FidoMetadataDownloaderBuilder.Step1#expectLegalHeader(String...)
728
   *     expectLegalHeader(String...)} but is otherwise valid. The downloaded BLOB will not be
729
   *     written to cache in this case.
730
   */
731
  public MetadataBLOB loadCachedBlob()
732
      throws CertPathValidatorException,
733
          InvalidAlgorithmParameterException,
734
          Base64UrlException,
735
          CertificateException,
736
          IOException,
737
          NoSuchAlgorithmException,
738
          SignatureException,
739
          InvalidKeyException,
740
          UnexpectedLegalHeader,
741
          DigestException,
742
          FidoMetadataDownloaderException {
743
    final X509Certificate trustRoot = retrieveTrustRootCert();
744
745
    final Optional<MetadataBLOB> explicit = loadExplicitBlobOnly(trustRoot);
746 1 1. loadCachedBlob : negated conditional → KILLED
    if (explicit.isPresent()) {
747
      log.debug("Explicit BLOB is set - disregarding cache and download.");
748 1 1. loadCachedBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlob → KILLED
      return explicit.get();
749
    }
750
751
    final Optional<MetadataBLOB> cached = loadCachedBlobOnly(trustRoot);
752 1 1. loadCachedBlob : negated conditional → KILLED
    if (cached.isPresent()) {
753
      log.debug("Cached BLOB exists, checking expiry date...");
754
      if (cached
755
          .get()
756
          .getPayload()
757
          .getNextUpdate()
758
          .atStartOfDay()
759
          .atZone(clock.getZone())
760 1 1. loadCachedBlob : negated conditional → KILLED
          .isAfter(clock.instant().atZone(clock.getZone()))) {
761
        log.debug("Cached BLOB has not yet expired - using cached BLOB.");
762 1 1. loadCachedBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlob → KILLED
        return cached.get();
763
      } else {
764
        log.debug("Cached BLOB has expired.");
765
      }
766
767
    } else {
768
      log.debug("Cached BLOB does not exist or is invalid.");
769
    }
770
771 1 1. loadCachedBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlob → KILLED
    return refreshBlobInternal(trustRoot, cached).get();
772
  }
773
774
  /**
775
   * Download and cache a fresh metadata BLOB, or read it from cache if the downloaded BLOB is not
776
   * up to date.
777
   *
778
   * <p>This method is NOT THREAD SAFE since it reads and writes caches.
779
   *
780
   * <p>On each execution this will, in order:
781
   *
782
   * <ol>
783
   *   <li>Download the trust root certificate, if necessary: if the cache is empty, the cache fails
784
   *       to load, or the cached cert is not valid at the current time (as determined by the {@link
785
   *       FidoMetadataDownloaderBuilder#clock(Clock) clock} setting).
786
   *   <li>If downloaded, cache the trust root certificate using the configured {@link File} or
787
   *       {@link Consumer} (see {@link FidoMetadataDownloaderBuilder.Step3})
788
   *   <li>Download the metadata BLOB.
789
   *   <li>Check the <code>"no"</code> property of the downloaded BLOB and compare it with the
790
   *       <code>"no"</code> of the cached BLOB, if any. The one with a greater <code>"no"
791
   *       </code> overrides the other, even if its <code>"nextUpdate"</code> is in the past.
792
   *   <li>If the downloaded BLOB has a newer <code>"no"</code>, or if no BLOB was cached, verify
793
   *       that the value of the downloaded BLOB's <code>"legalHeader"</code> appears in the
794
   *       configured {@link FidoMetadataDownloaderBuilder.Step1#expectLegalHeader(String...)
795
   *       expectLegalHeader} setting. If not, throw an {@link UnexpectedLegalHeader} exception
796
   *       containing the cached BLOB, if any, and the downloaded BLOB.
797
   *   <li>If the downloaded BLOB has an expected <code>
798
   *       "legalHeader"</code>, cache it using the configured {@link File} or {@link Consumer} (see
799
   *       {@link FidoMetadataDownloaderBuilder.Step5}).
800
   * </ol>
801
   *
802
   * No internal mutable state is maintained between invocations of this method; each invocation
803
   * will reload/rewrite caches, perform downloads and check the <code>"legalHeader"
804
   * </code> as necessary. You may therefore reuse a {@link FidoMetadataDownloader} instance and,
805
   * for example, call this method periodically to refresh the BLOB. Each call will return a new
806
   * {@link MetadataBLOB} instance; ones already returned will not be updated by subsequent calls.
807
   *
808
   * @return the successfully retrieved and validated metadata BLOB.
809
   * @throws Base64UrlException if the explicitly configured or newly downloaded BLOB is not a
810
   *     well-formed JWT in compact serialization.
811
   * @throws CertPathValidatorException if the explicitly configured or newly downloaded BLOB fails
812
   *     certificate path validation.
813
   * @throws CertificateException if the trust root certificate was downloaded and passed the
814
   *     SHA-256 integrity check, but does not contain a currently valid X.509 DER certificate; or
815
   *     if the BLOB signing certificate chain fails to parse.
816
   * @throws DigestException if the trust root certificate was downloaded but failed the SHA-256
817
   *     integrity check.
818
   * @throws FidoMetadataDownloaderException if the explicitly configured or newly downloaded BLOB
819
   *     (if any) has a bad signature and there is no cached BLOB to fall back to.
820
   * @throws IOException if any of the following fails: downloading the trust root certificate,
821
   *     downloading the BLOB, reading or writing any cache file (if any), or parsing the BLOB
822
   *     contents.
823
   * @throws InvalidAlgorithmParameterException if certificate path validation fails.
824
   * @throws InvalidKeyException if signature verification fails.
825
   * @throws NoSuchAlgorithmException if signature verification fails, or if the SHA-256 algorithm
826
   *     or the <code>"Collection"</code> type {@link CertStore} is not available.
827
   * @throws SignatureException if signature verification fails.
828
   * @throws UnexpectedLegalHeader if the downloaded BLOB (if any) contains a <code>"legalHeader"
829
   *     </code> value not configured in {@link
830
   *     FidoMetadataDownloaderBuilder.Step1#expectLegalHeader(String...)
831
   *     expectLegalHeader(String...)} but is otherwise valid. The downloaded BLOB will not be
832
   *     written to cache in this case.
833
   */
834
  public MetadataBLOB refreshBlob()
835
      throws CertPathValidatorException,
836
          InvalidAlgorithmParameterException,
837
          Base64UrlException,
838
          CertificateException,
839
          IOException,
840
          NoSuchAlgorithmException,
841
          SignatureException,
842
          InvalidKeyException,
843
          UnexpectedLegalHeader,
844
          DigestException,
845
          FidoMetadataDownloaderException {
846
    final X509Certificate trustRoot = retrieveTrustRootCert();
847
848
    final Optional<MetadataBLOB> explicit = loadExplicitBlobOnly(trustRoot);
849 1 1. refreshBlob : negated conditional → KILLED
    if (explicit.isPresent()) {
850
      log.debug("Explicit BLOB is set - disregarding cache and download.");
851 1 1. refreshBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlob → MEMORY_ERROR
      return explicit.get();
852
    }
853
854
    final Optional<MetadataBLOB> cached = loadCachedBlobOnly(trustRoot);
855 1 1. refreshBlob : negated conditional → SURVIVED
    if (cached.isPresent()) {
856
      log.debug("Cached BLOB exists, proceeding to compare against fresh BLOB...");
857
    } else {
858
      log.debug("Cached BLOB does not exist or is invalid.");
859
    }
860
861 1 1. refreshBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlob → KILLED
    return refreshBlobInternal(trustRoot, cached).get();
862
  }
863
864
  private Optional<MetadataBLOB> refreshBlobInternal(
865 2 1. refreshBlobInternal : negated conditional → KILLED
2. refreshBlobInternal : negated conditional → KILLED
      @NonNull X509Certificate trustRoot, @NonNull Optional<MetadataBLOB> cached)
866
      throws CertPathValidatorException,
867
          InvalidAlgorithmParameterException,
868
          Base64UrlException,
869
          CertificateException,
870
          IOException,
871
          NoSuchAlgorithmException,
872
          SignatureException,
873
          InvalidKeyException,
874
          UnexpectedLegalHeader,
875
          FidoMetadataDownloaderException {
876
877
    try {
878
      log.debug("Attempting to download new BLOB...");
879
      final ByteArray downloadedBytes = download(blobUrl);
880
      final MetadataBLOB downloadedBlob = parseAndVerifyBlob(downloadedBytes, trustRoot);
881
      log.debug("New BLOB downloaded.");
882
883 1 1. refreshBlobInternal : negated conditional → KILLED
      if (cached.isPresent()) {
884
        log.debug("Cached BLOB exists - checking if new BLOB has a higher \"no\"...");
885 2 1. refreshBlobInternal : negated conditional → MEMORY_ERROR
2. refreshBlobInternal : changed conditional boundary → KILLED
        if (downloadedBlob.getPayload().getNo() <= cached.get().getPayload().getNo()) {
886
          log.debug("New BLOB does not have a higher \"no\" - using cached BLOB instead.");
887 1 1. refreshBlobInternal : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → KILLED
          return cached;
888
        }
889
        log.debug("New BLOB has a higher \"no\" - proceeding with new BLOB.");
890
      }
891
892
      log.debug("Checking legalHeader in new BLOB...");
893 1 1. refreshBlobInternal : negated conditional → KILLED
      if (!expectedLegalHeaders.contains(downloadedBlob.getPayload().getLegalHeader())) {
894
        throw new UnexpectedLegalHeader(cached.orElse(null), downloadedBlob);
895
      }
896
897
      log.debug("Writing new BLOB to cache...");
898 1 1. refreshBlobInternal : negated conditional → KILLED
      if (blobCacheFile != null) {
899
        try (FileOutputStream f = new FileOutputStream(blobCacheFile)) {
900 1 1. refreshBlobInternal : removed call to java/io/FileOutputStream::write → KILLED
          f.write(downloadedBytes.getBytes());
901
        }
902
      }
903
904 1 1. refreshBlobInternal : negated conditional → KILLED
      if (blobCacheConsumer != null) {
905 1 1. refreshBlobInternal : removed call to java/util/function/Consumer::accept → KILLED
        blobCacheConsumer.accept(downloadedBytes);
906
      }
907
908 1 1. refreshBlobInternal : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → MEMORY_ERROR
      return Optional.of(downloadedBlob);
909
    } catch (FidoMetadataDownloaderException e) {
910 2 1. refreshBlobInternal : negated conditional → KILLED
2. refreshBlobInternal : negated conditional → KILLED
      if (e.getReason() == Reason.BAD_SIGNATURE && cached.isPresent()) {
911
        log.warn("New BLOB has bad signature - falling back to cached BLOB.");
912 1 1. refreshBlobInternal : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → KILLED
        return cached;
913
      } else {
914
        throw e;
915
      }
916
    } catch (Exception e) {
917 1 1. refreshBlobInternal : negated conditional → KILLED
      if (cached.isPresent()) {
918
        log.warn("Failed to download new BLOB - falling back to cached BLOB.", e);
919 1 1. refreshBlobInternal : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → KILLED
        return cached;
920
      } else {
921
        throw e;
922
      }
923
    }
924
  }
925
926
  /**
927
   * @throws CertificateException if the trust root certificate was downloaded and passed the
928
   *     SHA-256 integrity check, but does not contain a currently valid X.509 DER certificate.
929
   * @throws DigestException if the trust root certificate was downloaded but failed the SHA-256
930
   *     integrity check.
931
   * @throws IOException if the trust root certificate download failed, or if reading or writing the
932
   *     cache file (if any) failed.
933
   * @throws NoSuchAlgorithmException if the SHA-256 algorithm is not available.
934
   */
935
  private X509Certificate retrieveTrustRootCert()
936
      throws CertificateException, DigestException, IOException, NoSuchAlgorithmException {
937
938 1 1. retrieveTrustRootCert : negated conditional → KILLED
    if (trustRootCertificate != null) {
939 1 1. retrieveTrustRootCert : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::retrieveTrustRootCert → KILLED
      return trustRootCertificate;
940
941
    } else {
942
      final Optional<ByteArray> cachedContents;
943 1 1. retrieveTrustRootCert : negated conditional → KILLED
      if (trustRootCacheFile != null) {
944
        cachedContents = readCacheFile(trustRootCacheFile);
945
      } else {
946
        cachedContents = trustRootCacheSupplier.get();
947
      }
948
949
      X509Certificate cert = null;
950 1 1. retrieveTrustRootCert : negated conditional → KILLED
      if (cachedContents.isPresent()) {
951
        final ByteArray verifiedCachedContents = verifyHash(cachedContents.get(), trustRootSha256);
952 1 1. retrieveTrustRootCert : negated conditional → KILLED
        if (verifiedCachedContents != null) {
953
          try {
954
            final X509Certificate cachedCert =
955
                CertificateParser.parseDer(verifiedCachedContents.getBytes());
956 1 1. retrieveTrustRootCert : removed call to java/security/cert/X509Certificate::checkValidity → SURVIVED
            cachedCert.checkValidity(Date.from(clock.instant()));
957
            cert = cachedCert;
958
          } catch (CertificateException e) {
959
            // Fall through
960
          }
961
        }
962
      }
963
964 1 1. retrieveTrustRootCert : negated conditional → KILLED
      if (cert == null) {
965
        final ByteArray downloaded = verifyHash(download(trustRootUrl), trustRootSha256);
966 1 1. retrieveTrustRootCert : negated conditional → KILLED
        if (downloaded == null) {
967
          throw new DigestException(
968
              "Downloaded trust root certificate matches none of the acceptable hashes.");
969
        }
970
971
        cert = CertificateParser.parseDer(downloaded.getBytes());
972 1 1. retrieveTrustRootCert : removed call to java/security/cert/X509Certificate::checkValidity → SURVIVED
        cert.checkValidity(Date.from(clock.instant()));
973
974 1 1. retrieveTrustRootCert : negated conditional → KILLED
        if (trustRootCacheFile != null) {
975
          try (FileOutputStream f = new FileOutputStream(trustRootCacheFile)) {
976 1 1. retrieveTrustRootCert : removed call to java/io/FileOutputStream::write → KILLED
            f.write(downloaded.getBytes());
977
          }
978
        }
979
980 1 1. retrieveTrustRootCert : negated conditional → KILLED
        if (trustRootCacheConsumer != null) {
981 1 1. retrieveTrustRootCert : removed call to java/util/function/Consumer::accept → KILLED
          trustRootCacheConsumer.accept(downloaded);
982
        }
983
      }
984
985 1 1. retrieveTrustRootCert : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::retrieveTrustRootCert → KILLED
      return cert;
986
    }
987
  }
988
989
  /**
990
   * @throws Base64UrlException if the metadata BLOB is not a well-formed JWT in compact
991
   *     serialization.
992
   * @throws CertPathValidatorException if the explicitly configured BLOB fails certificate path
993
   *     validation.
994
   * @throws CertificateException if the BLOB signing certificate chain fails to parse.
995
   * @throws IOException on failure to parse the BLOB contents.
996
   * @throws InvalidAlgorithmParameterException if certificate path validation fails.
997
   * @throws InvalidKeyException if signature verification fails.
998
   * @throws NoSuchAlgorithmException if signature verification fails, or if the SHA-256 algorithm
999
   *     or the <code>"Collection"</code> type {@link CertStore} is not available.
1000
   * @throws SignatureException if signature verification fails.
1001
   * @throws FidoMetadataDownloaderException if the explicitly configured BLOB (if any) has a bad
1002
   *     signature.
1003
   */
1004
  private Optional<MetadataBLOB> loadExplicitBlobOnly(X509Certificate trustRootCertificate)
1005
      throws Base64UrlException,
1006
          CertPathValidatorException,
1007
          CertificateException,
1008
          IOException,
1009
          InvalidAlgorithmParameterException,
1010
          InvalidKeyException,
1011
          NoSuchAlgorithmException,
1012
          SignatureException,
1013
          FidoMetadataDownloaderException {
1014 1 1. loadExplicitBlobOnly : negated conditional → KILLED
    if (blobJwt != null) {
1015 1 1. loadExplicitBlobOnly : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::loadExplicitBlobOnly → KILLED
      return Optional.of(
1016
          parseAndMaybeVerifyBlob(
1017
              new ByteArray(blobJwt.getBytes(StandardCharsets.UTF_8)), trustRootCertificate));
1018
1019
    } else {
1020
      return Optional.empty();
1021
    }
1022
  }
1023
1024
  private Optional<MetadataBLOB> loadCachedBlobOnly(X509Certificate trustRootCertificate) {
1025
1026
    final Optional<ByteArray> cachedContents;
1027 1 1. loadCachedBlobOnly : negated conditional → KILLED
    if (blobCacheFile != null) {
1028
      log.debug("Attempting to read BLOB from cache file...");
1029
1030
      try {
1031
        cachedContents = readCacheFile(blobCacheFile);
1032
      } catch (IOException e) {
1033
        return Optional.empty();
1034
      }
1035
    } else {
1036
      log.debug("Attempting to read BLOB from cache Supplier...");
1037
      cachedContents = blobCacheSupplier.get();
1038
    }
1039
1040 1 1. loadCachedBlobOnly : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlobOnly → KILLED
    return cachedContents.map(
1041
        cached -> {
1042
          try {
1043 1 1. lambda$loadCachedBlobOnly$0 : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::lambda$loadCachedBlobOnly$0 → KILLED
            return parseAndMaybeVerifyBlob(cached, trustRootCertificate);
1044
          } catch (Exception e) {
1045
            log.warn("Failed to read or parse cached BLOB.", e);
1046
            return null;
1047
          }
1048
        });
1049
  }
1050
1051
  private Optional<ByteArray> readCacheFile(File cacheFile) throws IOException {
1052 3 1. readCacheFile : negated conditional → MEMORY_ERROR
2. readCacheFile : negated conditional → KILLED
3. readCacheFile : negated conditional → KILLED
    if (cacheFile.exists() && cacheFile.canRead() && cacheFile.isFile()) {
1053
      try (FileInputStream f = new FileInputStream(cacheFile)) {
1054
        return Optional.of(readAll(f));
1055
      } catch (FileNotFoundException e) {
1056
        throw new RuntimeException(
1057
            "This exception should be impossible, please file a bug report.", e);
1058
      }
1059
    } else {
1060
      return Optional.empty();
1061
    }
1062
  }
1063
1064
  private ByteArray download(URL url) throws IOException {
1065
    URLConnection conn = url.openConnection();
1066
1067 1 1. download : negated conditional → KILLED
    if (conn instanceof HttpsURLConnection) {
1068
      HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
1069 1 1. download : negated conditional → KILLED
      if (httpsTrustStore != null) {
1070
        try {
1071
          TrustManagerFactory trustMan =
1072
              TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
1073 1 1. download : removed call to javax/net/ssl/TrustManagerFactory::init → KILLED
          trustMan.init(httpsTrustStore);
1074
          SSLContext sslContext = SSLContext.getInstance("TLS");
1075 1 1. download : removed call to javax/net/ssl/SSLContext::init → KILLED
          sslContext.init(null, trustMan.getTrustManagers(), null);
1076
1077 1 1. download : removed call to javax/net/ssl/HttpsURLConnection::setSSLSocketFactory → KILLED
          httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
1078
        } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
1079
          throw new RuntimeException(
1080
              "Failed to initialize HTTPS trust store. This should be impossible, please file a bug report.",
1081
              e);
1082
        }
1083
      }
1084 1 1. download : removed call to javax/net/ssl/HttpsURLConnection::setRequestMethod → SURVIVED
      httpsConn.setRequestMethod("GET");
1085
    }
1086
1087 1 1. download : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::download → KILLED
    return readAll(conn.getInputStream());
1088
  }
1089
1090
  private MetadataBLOB parseAndVerifyBlob(ByteArray jwt, X509Certificate trustRootCertificate)
1091
      throws CertPathValidatorException,
1092
          InvalidAlgorithmParameterException,
1093
          CertificateException,
1094
          IOException,
1095
          NoSuchAlgorithmException,
1096
          SignatureException,
1097
          InvalidKeyException,
1098
          Base64UrlException,
1099
          FidoMetadataDownloaderException {
1100 1 1. parseAndVerifyBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseAndVerifyBlob → MEMORY_ERROR
    return verifyBlob(parseBlob(jwt), trustRootCertificate);
1101
  }
1102
1103
  private MetadataBLOB parseAndMaybeVerifyBlob(ByteArray jwt, X509Certificate trustRootCertificate)
1104
      throws CertPathValidatorException,
1105
          InvalidAlgorithmParameterException,
1106
          CertificateException,
1107
          IOException,
1108
          NoSuchAlgorithmException,
1109
          SignatureException,
1110
          InvalidKeyException,
1111
          Base64UrlException,
1112
          FidoMetadataDownloaderException {
1113 1 1. parseAndMaybeVerifyBlob : negated conditional → KILLED
    if (verifyDownloadsOnly) {
1114 1 1. parseAndMaybeVerifyBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseAndMaybeVerifyBlob → KILLED
      return parseBlob(jwt).blob;
1115
    } else {
1116 1 1. parseAndMaybeVerifyBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseAndMaybeVerifyBlob → KILLED
      return verifyBlob(parseBlob(jwt), trustRootCertificate);
1117
    }
1118
  }
1119
1120
  private MetadataBLOB verifyBlob(ParseResult parseResult, X509Certificate trustRootCertificate)
1121
      throws IOException,
1122
          CertificateException,
1123
          NoSuchAlgorithmException,
1124
          InvalidKeyException,
1125
          SignatureException,
1126
          CertPathValidatorException,
1127
          InvalidAlgorithmParameterException,
1128
          FidoMetadataDownloaderException {
1129
    final MetadataBLOBHeader header = parseResult.blob.getHeader();
1130
    final List<X509Certificate> certChain = fetchHeaderCertChain(trustRootCertificate, header);
1131
    final X509Certificate leafCert = certChain.get(0);
1132
1133
    final Signature signature;
1134
    switch (header.getAlg()) {
1135
      case "RS256":
1136
        signature = Signature.getInstance("SHA256withRSA");
1137
        break;
1138
1139
      case "ES256":
1140
        signature = Signature.getInstance("SHA256withECDSA");
1141
        break;
1142
1143
      default:
1144
        throw new UnsupportedOperationException(
1145
            "Unimplemented JWT verification algorithm: " + header.getAlg());
1146
    }
1147
1148 1 1. verifyBlob : removed call to java/security/Signature::initVerify → KILLED
    signature.initVerify(leafCert.getPublicKey());
1149 1 1. verifyBlob : removed call to java/security/Signature::update → KILLED
    signature.update(
1150
        (parseResult.jwtHeader.getBase64Url() + "." + parseResult.jwtPayload.getBase64Url())
1151
            .getBytes(StandardCharsets.UTF_8));
1152 1 1. verifyBlob : negated conditional → KILLED
    if (!signature.verify(parseResult.jwtSignature.getBytes())) {
1153
      throw new FidoMetadataDownloaderException(Reason.BAD_SIGNATURE);
1154
    }
1155
1156
    final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
1157
    final CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
1158
    final CertPath blobCertPath = certFactory.generateCertPath(certChain);
1159
    final PKIXParameters pathParams =
1160
        new PKIXParameters(Collections.singleton(new TrustAnchor(trustRootCertificate, null)));
1161 1 1. verifyBlob : negated conditional → KILLED
    if (certStore != null) {
1162 1 1. verifyBlob : removed call to java/security/cert/PKIXParameters::addCertStore → KILLED
      pathParams.addCertStore(certStore);
1163
    }
1164
1165
    // Parse CRLDistributionPoints ourselves so users don't have to set the
1166
    // `com.sun.security.enableCRLDP=true` system property
1167 1 1. verifyBlob : removed call to java/util/Optional::ifPresent → SURVIVED
    fetchCrlDistributionPoints(certChain, certFactory).ifPresent(pathParams::addCertStore);
1168
1169 1 1. verifyBlob : removed call to java/security/cert/PKIXParameters::setDate → KILLED
    pathParams.setDate(Date.from(clock.instant()));
1170
    cpv.validate(blobCertPath, pathParams);
1171
1172 1 1. verifyBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::verifyBlob → KILLED
    return parseResult.blob;
1173
  }
1174
1175
  ParseResult parseBlob(ByteArray jwt) throws IOException, Base64UrlException {
1176
    Scanner s = new Scanner(new ByteArrayInputStream(jwt.getBytes())).useDelimiter("\\.");
1177
    final ByteArray jwtHeader = ByteArray.fromBase64Url(s.next());
1178
    final ByteArray jwtPayload = ByteArray.fromBase64Url(s.next());
1179
    final ByteArray jwtSignature = ByteArray.fromBase64Url(s.next());
1180
1181
    final ObjectMapper headerJsonMapper =
1182
        makeHeaderJsonMapper.get().setBase64Variant(Base64Variants.MIME_NO_LINEFEEDS);
1183
1184 1 1. parseBlob : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseBlob → KILLED
    return new ParseResult(
1185
        new MetadataBLOB(
1186
            headerJsonMapper.readValue(jwtHeader.getBytes(), MetadataBLOBHeader.class),
1187
            makePayloadJsonMapper
1188
                .get()
1189
                .readValue(jwtPayload.getBytes(), MetadataBLOBPayload.class)),
1190
        jwtHeader,
1191
        jwtPayload,
1192
        jwtSignature);
1193
  }
1194
1195
  static ObjectMapper defaultHeaderJsonMapper() {
1196 1 1. defaultHeaderJsonMapper : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::defaultHeaderJsonMapper → KILLED
    return JacksonCodecs.json();
1197
  }
1198
1199
  static ObjectMapper defaultPayloadJsonMapper() {
1200 1 1. defaultPayloadJsonMapper : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::defaultPayloadJsonMapper → KILLED
    return JacksonCodecs.jsonWithDefaultEnums();
1201
  }
1202
1203
  private static ByteArray readAll(InputStream is) throws IOException {
1204 1 1. readAll : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::readAll → KILLED
    return new ByteArray(BinaryUtil.readAll(is));
1205
  }
1206
1207
  /**
1208
   * @return <code>contents</code> if its SHA-256 hash matches any element of <code>
1209
   *     acceptedCertSha256</code>, otherwise <code>null</code>.
1210
   */
1211
  private static ByteArray verifyHash(ByteArray contents, Set<ByteArray> acceptedCertSha256)
1212
      throws NoSuchAlgorithmException {
1213
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
1214
    final ByteArray hash = new ByteArray(digest.digest(contents.getBytes()));
1215 1 1. verifyHash : negated conditional → KILLED
    if (acceptedCertSha256.stream().anyMatch(hash::equals)) {
1216 1 1. verifyHash : replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::verifyHash → KILLED
      return contents;
1217
    } else {
1218
      return null;
1219
    }
1220
  }
1221
1222
  @Value
1223
  static class ParseResult {
1224
    private MetadataBLOB blob;
1225
    private ByteArray jwtHeader;
1226
    private ByteArray jwtPayload;
1227
    private ByteArray jwtSignature;
1228
  }
1229
1230
  /** Parse the header cert chain and download any certificates as necessary. */
1231
  List<X509Certificate> fetchHeaderCertChain(
1232
      X509Certificate trustRootCertificate, MetadataBLOBHeader header)
1233
      throws IOException, CertificateException {
1234 1 1. fetchHeaderCertChain : negated conditional → KILLED
    if (header.getX5u().isPresent()) {
1235
      final URL x5u = header.getX5u().get();
1236 1 1. fetchHeaderCertChain : negated conditional → KILLED
      if (blobUrl != null
1237 1 1. fetchHeaderCertChain : negated conditional → MEMORY_ERROR
          && (!(x5u.getHost().equals(blobUrl.getHost())
1238 1 1. fetchHeaderCertChain : negated conditional → SURVIVED
              && x5u.getProtocol().equals(blobUrl.getProtocol())
1239 1 1. fetchHeaderCertChain : negated conditional → KILLED
              && x5u.getPort() == blobUrl.getPort()))) {
1240
        throw new IllegalArgumentException(
1241
            String.format(
1242
                "x5u in BLOB header must have same origin as the URL the BLOB was downloaded from. Expected origin of: %s ; found: %s",
1243
                blobUrl, x5u));
1244
      }
1245
      List<X509Certificate> certs = new ArrayList<>();
1246
      for (String pem :
1247
          new String(download(x5u).getBytes(), StandardCharsets.UTF_8)
1248
              .trim()
1249
              .split("\\n+-----END CERTIFICATE-----\\n+-----BEGIN CERTIFICATE-----\\n+")) {
1250
        X509Certificate x509Certificate = CertificateParser.parsePem(pem);
1251
        certs.add(x509Certificate);
1252
      }
1253 1 1. fetchHeaderCertChain : replaced return value with Collections.emptyList for com/yubico/fido/metadata/FidoMetadataDownloader::fetchHeaderCertChain → KILLED
      return certs;
1254 1 1. fetchHeaderCertChain : negated conditional → KILLED
    } else if (header.getX5c().isPresent()) {
1255 1 1. fetchHeaderCertChain : replaced return value with Collections.emptyList for com/yubico/fido/metadata/FidoMetadataDownloader::fetchHeaderCertChain → KILLED
      return header.getX5c().get();
1256
    } else {
1257 1 1. fetchHeaderCertChain : replaced return value with Collections.emptyList for com/yubico/fido/metadata/FidoMetadataDownloader::fetchHeaderCertChain → KILLED
      return Collections.singletonList(trustRootCertificate);
1258
    }
1259
  }
1260
1261
  /**
1262
   * Parse the CRLDistributionPoints extension of each certificate, fetch each distribution point
1263
   * and assemble them into a {@link CertStore} ready to be injected into {@link
1264
   * PKIXParameters#addCertStore(CertStore)} to provide CRLs for the verification procedure.
1265
   *
1266
   * <p>We do this ourselves so that users don't have to set the <code>
1267
   * com.sun.security.enableCRLDP=true</code> system property. This is required by the default SUN
1268
   * provider in order to enable CRLDistributionPoints resolution.
1269
   *
1270
   * <p>Any CRLDistributionPoints entries in unknown format are ignored and log a warning.
1271
   */
1272
  private Optional<CertStore> fetchCrlDistributionPoints(
1273
      List<X509Certificate> certChain, CertificateFactory certFactory)
1274
      throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
1275
    final List<URL> crlDistributionPointUrls =
1276
        certChain.stream()
1277
            .flatMap(
1278
                cert -> {
1279
                  log.debug(
1280
                      "Attempting to parse CRLDistributionPoints extension of cert: {}",
1281
                      cert.getSubjectX500Principal());
1282
                  try {
1283 1 1. lambda$fetchCrlDistributionPoints$1 : replaced return value with Stream.empty for com/yubico/fido/metadata/FidoMetadataDownloader::lambda$fetchCrlDistributionPoints$1 → SURVIVED
                    return CertificateParser.parseCrlDistributionPointsExtension(cert)
1284
                        .getDistributionPoints()
1285
                        .stream();
1286
                  } catch (Exception e) {
1287
                    log.warn(
1288
                        "Failed to parse CRLDistributionPoints extension of cert: {}",
1289
                        cert.getSubjectX500Principal(),
1290
                        e);
1291
                    return Stream.empty();
1292
                  }
1293
                })
1294
            .collect(Collectors.toList());
1295
1296 1 1. fetchCrlDistributionPoints : negated conditional → SURVIVED
    if (crlDistributionPointUrls.isEmpty()) {
1297
      return Optional.empty();
1298
1299
    } else {
1300
      final List<CRL> crldpCrls =
1301
          crlDistributionPointUrls.stream()
1302
              .map(
1303
                  crldpUrl -> {
1304
                    log.debug("Attempting to download CRL distribution point: {}", crldpUrl);
1305
                    try {
1306 1 1. lambda$fetchCrlDistributionPoints$2 : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::lambda$fetchCrlDistributionPoints$2 → NO_COVERAGE
                      return Optional.of(
1307
                          certFactory.generateCRL(
1308
                              new ByteArrayInputStream(download(crldpUrl).getBytes())));
1309
                    } catch (CRLException e) {
1310
                      log.warn("Failed to import CRL from distribution point: {}", crldpUrl, e);
1311
                      return Optional.<CRL>empty();
1312
                    } catch (Exception e) {
1313
                      log.warn("Failed to download CRL distribution point: {}", crldpUrl, e);
1314
                      return Optional.<CRL>empty();
1315
                    }
1316
                  })
1317
              .flatMap(OptionalUtil::stream)
1318
              .collect(Collectors.toList());
1319
1320 1 1. fetchCrlDistributionPoints : replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::fetchCrlDistributionPoints → NO_COVERAGE
      return Optional.of(
1321
          CertStore.getInstance("Collection", new CollectionCertStoreParameters(crldpCrls)));
1322
    }
1323
  }
1324
}

Mutations

139

1.1
Location : builder
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::builder → KILLED

168

1.1
Location : build
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::build → KILLED

234

1.1
Location : expectLegalHeader
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

235

1.1
Location : expectLegalHeader
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step1::expectLegalHeader → KILLED

280

1.1
Location : useDefaultTrustRoot
Killed by : none
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step2::useDefaultTrustRoot → NO_COVERAGE

309

1.1
Location : downloadTrustRoot
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

2.2
Location : downloadTrustRoot
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

310

1.1
Location : downloadTrustRoot
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

313

1.1
Location : downloadTrustRoot
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step2::downloadTrustRoot → KILLED

322

1.1
Location : useTrustRoot
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

323

1.1
Location : useTrustRoot
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step2::useTrustRoot → KILLED

356

1.1
Location : useTrustRootCacheFile
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

357

1.1
Location : useTrustRootCacheFile
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step3::useTrustRootCacheFile → KILLED

379

1.1
Location : useTrustRootCache
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

380

1.1
Location : useTrustRootCache
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

381

1.1
Location : useTrustRootCache
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step3::useTrustRootCache → KILLED

419

1.1
Location : useDefaultBlob
Killed by : none
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step4::useDefaultBlob → NO_COVERAGE

437

1.1
Location : downloadBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

438

1.1
Location : downloadBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step4::downloadBlob → KILLED

459

1.1
Location : useBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

460

1.1
Location : useBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step4::useBlob → KILLED

493

1.1
Location : useBlobCacheFile
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

494

1.1
Location : useBlobCacheFile
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step5::useBlobCacheFile → KILLED

517

1.1
Location : useBlobCache
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

518

1.1
Location : useBlobCache
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

519

1.1
Location : useBlobCache
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder$Step5::useBlobCache → KILLED

528

1.1
Location : finishRequiredSteps
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::finishRequiredSteps → KILLED

553

1.1
Location : clock
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

555

1.1
Location : clock
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::clock → KILLED

571

1.1
Location : useCrls
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

573

1.1
Location : useCrls
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::useCrls → KILLED

587

1.1
Location : useCrls
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::useCrls → KILLED

606

1.1
Location : trustHttpsCerts
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

610

1.1
Location : trustHttpsCerts
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to java/security/KeyStore::load → KILLED

621

1.1
Location : trustHttpsCerts
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to java/security/KeyStore::setCertificateEntry → KILLED

630

1.1
Location : trustHttpsCerts
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::trustHttpsCerts → KILLED

650

1.1
Location : verifyDownloadsOnly
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::verifyDownloadsOnly → KILLED

657

1.1
Location : headerJsonMapper
Killed by : none
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::headerJsonMapper → NO_COVERAGE

664

1.1
Location : payloadJsonMapper
Killed by : none
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader$FidoMetadataDownloaderBuilder::payloadJsonMapper → NO_COVERAGE

746

1.1
Location : loadCachedBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

748

1.1
Location : loadCachedBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlob → KILLED

752

1.1
Location : loadCachedBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

760

1.1
Location : loadCachedBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

762

1.1
Location : loadCachedBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlob → KILLED

771

1.1
Location : loadCachedBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlob → KILLED

849

1.1
Location : refreshBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

851

1.1
Location : refreshBlob
Killed by : none
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlob → MEMORY_ERROR

855

1.1
Location : refreshBlob
Killed by : none
negated conditional → SURVIVED

861

1.1
Location : refreshBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlob → KILLED

865

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

2.2
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

883

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

885

1.1
Location : refreshBlobInternal
Killed by : none
negated conditional → MEMORY_ERROR

2.2
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
changed conditional boundary → KILLED

887

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → KILLED

893

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

898

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

900

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to java/io/FileOutputStream::write → KILLED

904

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

905

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to java/util/function/Consumer::accept → KILLED

908

1.1
Location : refreshBlobInternal
Killed by : none
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → MEMORY_ERROR

910

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

2.2
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

912

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → KILLED

917

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

919

1.1
Location : refreshBlobInternal
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → KILLED

938

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

939

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::retrieveTrustRootCert → KILLED

943

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

950

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

952

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

956

1.1
Location : retrieveTrustRootCert
Killed by : none
removed call to java/security/cert/X509Certificate::checkValidity → SURVIVED

964

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

966

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

972

1.1
Location : retrieveTrustRootCert
Killed by : none
removed call to java/security/cert/X509Certificate::checkValidity → SURVIVED

974

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

976

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to java/io/FileOutputStream::write → KILLED

980

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

981

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to java/util/function/Consumer::accept → KILLED

985

1.1
Location : retrieveTrustRootCert
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::retrieveTrustRootCert → KILLED

1014

1.1
Location : loadExplicitBlobOnly
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

1015

1.1
Location : loadExplicitBlobOnly
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::loadExplicitBlobOnly → KILLED

1027

1.1
Location : loadCachedBlobOnly
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1040

1.1
Location : loadCachedBlobOnly
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::loadCachedBlobOnly → KILLED

1043

1.1
Location : lambda$loadCachedBlobOnly$0
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::lambda$loadCachedBlobOnly$0 → KILLED

1052

1.1
Location : readCacheFile
Killed by : none
negated conditional → MEMORY_ERROR

2.2
Location : readCacheFile
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

3.3
Location : readCacheFile
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1067

1.1
Location : download
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1069

1.1
Location : download
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1073

1.1
Location : download
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to javax/net/ssl/TrustManagerFactory::init → KILLED

1075

1.1
Location : download
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to javax/net/ssl/SSLContext::init → KILLED

1077

1.1
Location : download
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
removed call to javax/net/ssl/HttpsURLConnection::setSSLSocketFactory → KILLED

1084

1.1
Location : download
Killed by : none
removed call to javax/net/ssl/HttpsURLConnection::setRequestMethod → SURVIVED

1087

1.1
Location : download
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::download → KILLED

1100

1.1
Location : parseAndVerifyBlob
Killed by : none
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseAndVerifyBlob → MEMORY_ERROR

1113

1.1
Location : parseAndMaybeVerifyBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1114

1.1
Location : parseAndMaybeVerifyBlob
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseAndMaybeVerifyBlob → KILLED

1116

1.1
Location : parseAndMaybeVerifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseAndMaybeVerifyBlob → KILLED

1148

1.1
Location : verifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
removed call to java/security/Signature::initVerify → KILLED

1149

1.1
Location : verifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
removed call to java/security/Signature::update → KILLED

1152

1.1
Location : verifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

1161

1.1
Location : verifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

1162

1.1
Location : verifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
removed call to java/security/cert/PKIXParameters::addCertStore → KILLED

1167

1.1
Location : verifyBlob
Killed by : none
removed call to java/util/Optional::ifPresent → SURVIVED

1169

1.1
Location : verifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
removed call to java/security/cert/PKIXParameters::setDate → KILLED

1172

1.1
Location : verifyBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::verifyBlob → KILLED

1184

1.1
Location : parseBlob
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::parseBlob → KILLED

1196

1.1
Location : defaultHeaderJsonMapper
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::defaultHeaderJsonMapper → KILLED

1200

1.1
Location : defaultPayloadJsonMapper
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::defaultPayloadJsonMapper → KILLED

1204

1.1
Location : readAll
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::readAll → KILLED

1215

1.1
Location : verifyHash
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1216

1.1
Location : verifyHash
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with null for com/yubico/fido/metadata/FidoMetadataDownloader::verifyHash → KILLED

1234

1.1
Location : fetchHeaderCertChain
Killed by : com.yubico.fido.metadata.FidoMds3Spec
negated conditional → KILLED

1236

1.1
Location : fetchHeaderCertChain
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1237

1.1
Location : fetchHeaderCertChain
Killed by : none
negated conditional → MEMORY_ERROR

1238

1.1
Location : fetchHeaderCertChain
Killed by : none
negated conditional → SURVIVED

1239

1.1
Location : fetchHeaderCertChain
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1253

1.1
Location : fetchHeaderCertChain
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with Collections.emptyList for com/yubico/fido/metadata/FidoMetadataDownloader::fetchHeaderCertChain → KILLED

1254

1.1
Location : fetchHeaderCertChain
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
negated conditional → KILLED

1255

1.1
Location : fetchHeaderCertChain
Killed by : com.yubico.fido.metadata.FidoMds3Spec
replaced return value with Collections.emptyList for com/yubico/fido/metadata/FidoMetadataDownloader::fetchHeaderCertChain → KILLED

1257

1.1
Location : fetchHeaderCertChain
Killed by : com.yubico.fido.metadata.FidoMetadataDownloaderSpec
replaced return value with Collections.emptyList for com/yubico/fido/metadata/FidoMetadataDownloader::fetchHeaderCertChain → KILLED

1283

1.1
Location : lambda$fetchCrlDistributionPoints$1
Killed by : none
replaced return value with Stream.empty for com/yubico/fido/metadata/FidoMetadataDownloader::lambda$fetchCrlDistributionPoints$1 → SURVIVED

1296

1.1
Location : fetchCrlDistributionPoints
Killed by : none
negated conditional → SURVIVED

1306

1.1
Location : lambda$fetchCrlDistributionPoints$2
Killed by : none
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::lambda$fetchCrlDistributionPoints$2 → NO_COVERAGE

1320

1.1
Location : fetchCrlDistributionPoints
Killed by : none
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::fetchCrlDistributionPoints → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.15.0