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

Mutations

132

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

156

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

220

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

221

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

266

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

295

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

296

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

299

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

308

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

309

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

342

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

343

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

365

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

366

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

367

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

405

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

423

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

424

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

445

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

446

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

479

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

480

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

503

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

504

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

505

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

514

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

539

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

541

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

557

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

559

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

573

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

592

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

596

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

607

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

616

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

636

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

718

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

720

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

724

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

732

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

734

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

743

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

821

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

823

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

827

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

833

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

837

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

855

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

857

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

859

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

865

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

870

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

872

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

876

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

877

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

880

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

882

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

884

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

889

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

891

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

910

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

911

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

915

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

922

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

924

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

928

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

936

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

938

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

944

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

946

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

948

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

952

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

953

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

957

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

985

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

986

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

998

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

1011

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

1014

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

1023

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

1038

1.1
Location : download
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

1044

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

1046

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

1048

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

1055

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

1058

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

1071

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

1084

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

1085

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

1087

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

1103

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

1105

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

1106

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

1107

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

1108

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

1123

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

1146

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

1147

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

1150

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

1159

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

1160

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

1162

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

1165

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

1179

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

1190

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

1201

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

1202

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

Active mutators

Tests examined


Report generated by PIT 1.15.0