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

Mutations

133

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

157

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

221

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

222

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

267

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

296

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

297

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

300

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

309

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

310

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

343

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

344

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

366

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

367

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

368

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

406

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

424

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

425

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

446

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

447

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

480

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

481

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

504

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

505

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

506

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

515

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

540

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

542

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

558

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

560

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

574

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

593

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

597

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

608

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

617

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

637

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

719

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

721

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

725

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
negated conditional → KILLED

735

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

744

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

822

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

824

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

828

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

834

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

838

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

856

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

858

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

860

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

866

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

871

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

873

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

877

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

878

1.1
Location : refreshBlobInternal
Killed by : none
removed call to java/util/function/Consumer::accept → MEMORY_ERROR

881

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

883

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

885

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

890

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

892

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

911

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

912

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

916

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

925

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

929

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

937

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

939

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

945

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

947

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

949

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

953

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

954

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

958

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

987

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

988

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

1000

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

1013

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

1016

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

1025

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

1040

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

1042

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

1046

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

1048

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

1050

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

1057

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

1060

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

1073

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

1086

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

1087

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

1089

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

1121

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

1122

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

1125

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

1134

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

1135

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

1140

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

1142

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

1145

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

1157

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

1168

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

1179

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

1180

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

1198

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

1200

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

1201

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

1202

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

1203

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

1217

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

1218

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

1219

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

1221

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

1247

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

1260

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

1270

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

1284

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