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

Mutations

131

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

155

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

219

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

220

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

265

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

294

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

295

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

298

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

307

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

308

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

341

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

342

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

364

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

365

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

366

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

404

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

422

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

423

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

444

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

445

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

478

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

479

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

502

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

503

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

504

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

513

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

538

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

540

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

556

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

558

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

572

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

591

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

595

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

606

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

615

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

635

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

717

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

719

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

723

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

731

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

733

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

742

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

820

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

822

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

826

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

832

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

836

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

854

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

856

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

2.2
Location : refreshBlobInternal
Killed by : none
changed conditional boundary → SURVIVED

858

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

864

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

869

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

871

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

875

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

876

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

879

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

881

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
replaced return value with Optional.empty for com/yubico/fido/metadata/FidoMetadataDownloader::refreshBlobInternal → KILLED

888

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

890

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

909

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

910

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

914

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

921

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

923

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

927

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

935

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

937

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

943

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

945

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

947

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

951

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
removed call to java/util/function/Consumer::accept → KILLED

956

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

984

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

985

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

997

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

1010

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

1013

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

1022

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

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

1037

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

1039

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

1043

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

1045

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

1047

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

1054

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

1057

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

1070

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

1083

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

1084

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

1086

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

1102

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

1104

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

1105

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

1106

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

1107

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

1122

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

1145

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

1146

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

1149

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

1158

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

1159

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

1161

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

1164

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

1176

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

1187

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

1198

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

1199

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

Active mutators

Tests examined


Report generated by PIT 1.15.0