|
29 | 29 | import com.yubico.fido.metadata.FidoMetadataDownloaderException.Reason;
|
30 | 30 | import com.yubico.internal.util.BinaryUtil;
|
31 | 31 | import com.yubico.internal.util.CertificateParser;
|
| 32 | +import com.yubico.internal.util.OptionalUtil; |
32 | 33 | import com.yubico.webauthn.data.ByteArray;
|
33 | 34 | import com.yubico.webauthn.data.exception.Base64UrlException;
|
34 | 35 | import com.yubico.webauthn.data.exception.HexException;
|
|
54 | 55 | import java.security.Signature;
|
55 | 56 | import java.security.SignatureException;
|
56 | 57 | import java.security.cert.CRL;
|
| 58 | +import java.security.cert.CRLException; |
57 | 59 | import java.security.cert.CertPath;
|
58 | 60 | import java.security.cert.CertPathValidator;
|
59 | 61 | import java.security.cert.CertPathValidatorException;
|
@@ -1131,13 +1133,18 @@ private MetadataBLOB verifyBlob(ParseResult parseResult, X509Certificate trustRo
|
1131 | 1133 | if (certStore != null) {
|
1132 | 1134 | pathParams.addCertStore(certStore);
|
1133 | 1135 | }
|
| 1136 | + |
| 1137 | + // Parse CRLDistributionPoints ourselves so users don't have to set the |
| 1138 | + // `com.sun.security.enableCRLDP=true` system property |
| 1139 | + fetchCrlDistributionPoints(certChain, certFactory).ifPresent(pathParams::addCertStore); |
| 1140 | + |
1134 | 1141 | pathParams.setDate(Date.from(clock.instant()));
|
1135 | 1142 | cpv.validate(blobCertPath, pathParams);
|
1136 | 1143 |
|
1137 | 1144 | return parseResult.blob;
|
1138 | 1145 | }
|
1139 | 1146 |
|
1140 |
| - private static ParseResult parseBlob(ByteArray jwt) throws IOException, Base64UrlException { |
| 1147 | + static ParseResult parseBlob(ByteArray jwt) throws IOException, Base64UrlException { |
1141 | 1148 | Scanner s = new Scanner(new ByteArrayInputStream(jwt.getBytes())).useDelimiter("\\.");
|
1142 | 1149 | final ByteArray jwtHeader = ByteArray.fromBase64Url(s.next());
|
1143 | 1150 | final ByteArray jwtPayload = ByteArray.fromBase64Url(s.next());
|
@@ -1176,7 +1183,7 @@ private static ByteArray verifyHash(ByteArray contents, Set<ByteArray> acceptedC
|
1176 | 1183 | }
|
1177 | 1184 |
|
1178 | 1185 | @Value
|
1179 |
| - private static class ParseResult { |
| 1186 | + static class ParseResult { |
1180 | 1187 | private MetadataBLOB blob;
|
1181 | 1188 | private ByteArray jwtHeader;
|
1182 | 1189 | private ByteArray jwtPayload;
|
@@ -1213,4 +1220,68 @@ List<X509Certificate> fetchHeaderCertChain(
|
1213 | 1220 | return Collections.singletonList(trustRootCertificate);
|
1214 | 1221 | }
|
1215 | 1222 | }
|
| 1223 | + |
| 1224 | + /** |
| 1225 | + * Parse the CRLDistributionPoints extension of each certificate, fetch each distribution point |
| 1226 | + * and assemble them into a {@link CertStore} ready to be injected into {@link |
| 1227 | + * PKIXParameters#addCertStore(CertStore)} to provide CRLs for the verification procedure. |
| 1228 | + * |
| 1229 | + * <p>We do this ourselves so that users don't have to set the `com.sun.security.enableCRLDP=true` |
| 1230 | + * system property. This is required by the default SUN provider in order to enable |
| 1231 | + * CRLDistributionPoints resolution. |
| 1232 | + * |
| 1233 | + * <p>Any CRLDistributionPoints entries in unknown format are ignored and log a warning. |
| 1234 | + */ |
| 1235 | + private Optional<CertStore> fetchCrlDistributionPoints( |
| 1236 | + List<X509Certificate> certChain, CertificateFactory certFactory) |
| 1237 | + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException { |
| 1238 | + final List<URL> crlDistributionPointUrls = |
| 1239 | + certChain.stream() |
| 1240 | + .flatMap( |
| 1241 | + cert -> { |
| 1242 | + log.debug( |
| 1243 | + "Attempting to parse CRLDistributionPoints extension of cert: {}", |
| 1244 | + cert.getSubjectX500Principal()); |
| 1245 | + try { |
| 1246 | + return CertificateParser.parseCrlDistributionPointsExtension(cert) |
| 1247 | + .getDistributionPoints() |
| 1248 | + .stream(); |
| 1249 | + } catch (Exception e) { |
| 1250 | + log.warn( |
| 1251 | + "Failed to parse CRLDistributionPoints extension of cert: {}", |
| 1252 | + cert.getSubjectX500Principal(), |
| 1253 | + e); |
| 1254 | + return Stream.empty(); |
| 1255 | + } |
| 1256 | + }) |
| 1257 | + .collect(Collectors.toList()); |
| 1258 | + |
| 1259 | + if (crlDistributionPointUrls.isEmpty()) { |
| 1260 | + return Optional.empty(); |
| 1261 | + |
| 1262 | + } else { |
| 1263 | + final List<CRL> crldpCrls = |
| 1264 | + crlDistributionPointUrls.stream() |
| 1265 | + .map( |
| 1266 | + crldpUrl -> { |
| 1267 | + log.debug("Attempting to download CRL distribution point: {}", crldpUrl); |
| 1268 | + try { |
| 1269 | + return Optional.of( |
| 1270 | + certFactory.generateCRL( |
| 1271 | + new ByteArrayInputStream(download(crldpUrl).getBytes()))); |
| 1272 | + } catch (CRLException e) { |
| 1273 | + log.warn("Failed to import CRL from distribution point: {}", crldpUrl, e); |
| 1274 | + return Optional.<CRL>empty(); |
| 1275 | + } catch (Exception e) { |
| 1276 | + log.warn("Failed to download CRL distribution point: {}", crldpUrl, e); |
| 1277 | + return Optional.<CRL>empty(); |
| 1278 | + } |
| 1279 | + }) |
| 1280 | + .flatMap(OptionalUtil::stream) |
| 1281 | + .collect(Collectors.toList()); |
| 1282 | + |
| 1283 | + return Optional.of( |
| 1284 | + CertStore.getInstance("Collection", new CollectionCertStoreParameters(crldpCrls))); |
| 1285 | + } |
| 1286 | + } |
1216 | 1287 | }
|
0 commit comments