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 |
|
156 |
1.1 |
|
220 |
1.1 |
|
221 |
1.1 |
|
266 |
1.1 |
|
295 |
1.1 2.2 |
|
296 |
1.1 |
|
299 |
1.1 |
|
308 |
1.1 |
|
309 |
1.1 |
|
342 |
1.1 |
|
343 |
1.1 |
|
365 |
1.1 |
|
366 |
1.1 |
|
367 |
1.1 |
|
405 |
1.1 |
|
423 |
1.1 |
|
424 |
1.1 |
|
445 |
1.1 |
|
446 |
1.1 |
|
479 |
1.1 |
|
480 |
1.1 |
|
503 |
1.1 |
|
504 |
1.1 |
|
505 |
1.1 |
|
514 |
1.1 |
|
539 |
1.1 |
|
541 |
1.1 |
|
557 |
1.1 |
|
559 |
1.1 |
|
573 |
1.1 |
|
592 |
1.1 |
|
596 |
1.1 |
|
607 |
1.1 |
|
616 |
1.1 |
|
636 |
1.1 |
|
718 |
1.1 |
|
720 |
1.1 |
|
724 |
1.1 |
|
732 |
1.1 |
|
734 |
1.1 |
|
743 |
1.1 |
|
821 |
1.1 |
|
823 |
1.1 |
|
827 |
1.1 |
|
833 |
1.1 |
|
837 |
1.1 2.2 |
|
855 |
1.1 |
|
857 |
1.1 2.2 |
|
859 |
1.1 |
|
865 |
1.1 |
|
870 |
1.1 |
|
872 |
1.1 |
|
876 |
1.1 |
|
877 |
1.1 |
|
880 |
1.1 |
|
882 |
1.1 2.2 |
|
884 |
1.1 |
|
889 |
1.1 |
|
891 |
1.1 |
|
910 |
1.1 |
|
911 |
1.1 |
|
915 |
1.1 |
|
922 |
1.1 |
|
924 |
1.1 |
|
928 |
1.1 |
|
936 |
1.1 |
|
938 |
1.1 |
|
944 |
1.1 |
|
946 |
1.1 |
|
948 |
1.1 |
|
952 |
1.1 |
|
953 |
1.1 |
|
957 |
1.1 |
|
985 |
1.1 |
|
986 |
1.1 |
|
998 |
1.1 |
|
1011 |
1.1 |
|
1014 |
1.1 |
|
1023 |
1.1 2.2 3.3 |
|
1038 |
1.1 |
|
1040 |
1.1 |
|
1044 |
1.1 |
|
1046 |
1.1 |
|
1048 |
1.1 |
|
1055 |
1.1 |
|
1058 |
1.1 |
|
1071 |
1.1 |
|
1084 |
1.1 |
|
1085 |
1.1 |
|
1087 |
1.1 |
|
1103 |
1.1 |
|
1105 |
1.1 |
|
1106 |
1.1 |
|
1107 |
1.1 |
|
1108 |
1.1 |
|
1123 |
1.1 |
|
1146 |
1.1 |
|
1147 |
1.1 |
|
1150 |
1.1 |
|
1159 |
1.1 |
|
1160 |
1.1 |
|
1162 |
1.1 |
|
1165 |
1.1 |
|
1179 |
1.1 |
|
1190 |
1.1 |
|
1201 |
1.1 |
|
1202 |
1.1 |