使用 BouncyCastle 接受过期的 CRL
Accepting expired CRL with BouncyCastle
我正在使用 bouncy castle 离线验证 X509 证书,运行 遇到旧 CRL 的问题。我还没有找到接受过期 CRL 的可能性,在我看来,如果证书被撤销,它应该在 CRL 到期后保持撤销状态。此外,如果 CRL 为空,我只想接受这个,此时我无法获得更新的 CRL。
澄清一下,这就是用例:
- 2015年创建证书,有效期2015-2020
- 2017 年吊销带有 CRL 的证书,密钥被盗,只创建 1 年的 CRL,因为我犯了一个错误或计划滚动并且永远不会绕过它
- 查看2019年的证书,CRL过期,bouncy castle再次接受被吊销的证书——这显然不是我想要的
目前我正在将吊销检查设置为 false 并自行执行检查。我在网上找不到任何关于此的信息。
这是我的代码:
final X509CertSelector endConstraints = new X509CertSelector();
endConstraints.setSerialNumber(signer.getSID().getSerialNumber());
final PKIXBuilderParameters buildParams = new PKIXBuilderParameters(trustAnchors, endConstraints);
//a CertStore object with Certificates and CRLs
buildParams.addCertStore(certificates);
//currently deactivated
buildParams.setRevocationEnabled(false);
final CertPathBuilder builder = CertPathBuilder.getInstance(SignedFileVerifier.CERTIFICATE_PATH_ALGORITHM, SignedFileVerifier.PROVIDER);
final CertPathBuilderResult result = builder.build(buildParams);
//here I manually check the CRLs, which I don't want to do
checkRevocation(result.getCertPath().getCertificates(), certificates, trustAnchors);
//if this passes I return the found certificate
return (X509Certificate) result.getCertPath().getCertificates().get(0);
确切的例外是:
Caused by: org.bouncycastle.jce.exception.ExtCertPathValidatorException: No CRLs found for issuer "cn=goodOldIssuerCA0,ou=jUnit Test Issuer,o=BOGO Company,c=AT"
at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCertA(Unknown Source)
at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi.engineValidate(Unknown Source)
at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi.build(Unknown Source)
at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi.build(Unknown Source)
...
完全禁用 CRL 检查。不,我不是在开玩笑。安全社区的普遍共识是revocation is broken。现代浏览器甚至懒得检查 CRL。如果您可以在您的浏览器中启用严格的 CRL 检查(许多浏览器甚至不再让您启用),那么您会发现大部分网络都无法访问。
基本上我的整个问题都发生在包 org.bouncycastle.jce.provider
中的方法 PKIXCRLUtil#findCRLs
中。这是用于加载 CRL 的方法,并始终在此处检查日期:
if (crl.getNextUpdate().after(validityDate))
{
X509Certificate cert = crlselect.getCertificateChecking();
if (cert != null)
{
if (crl.getThisUpdate().before(cert.getNotAfter()))
{
finalSet.add(crl);
}
}
else
{
finalSet.add(crl);
}
}
我最终使用的代码如下。基本上我首先将所有 public 键按名称组合到一个映射中(也许序列号会更好?),然后遍历我在链中拥有的所有证书。首先我得到证书颁发者 public 密钥,因为我需要它来验证 CRL 来自同一颁发者。然后我创建一个 X509CRLSelector
颁发者并加载该颁发者的所有 CRL。然后我遍历我在商店中找到的 CRL,通过颁发者 public 密钥验证它们,检查证书是否被吊销,如果是这种情况则抛出异常。在我当前的实现中,如果没有找到 CRL 也没关系,这可以通过检查 selectedCRLs
是否为空来添加。
private void checkRevocation(final List<X509Certificate> certificates, final CertStore revocationLists, final Set<TrustAnchor> trustAnchors) throws GeneralSecurityException {
final Map<String, PublicKey> publicKeyMap = extractPublicKeys(certificates, trustAnchors);
//check the whole chain, we don't know if the issuer or the signer was revoked
for(final X509Certificate certificate : certificates){
final X500Principal issuerX500Principal = certificate.getIssuerX500Principal();
//get the issuer of this certificate
final PublicKey issuerPublicKey = publicKeyMap.get(issuerX500Principal.getName());
if(issuerPublicKey == null){
throw new GeneralSecurityException("Unable to find issuer for certificate '" + certificate.getSubjectX500Principal() + "'");
}
final X509CRLSelector crlSelector = new X509CRLSelector();
//we only use the issuer, not the date or time, don't want CRLs to expire
crlSelector.addIssuer(issuerX500Principal);
//get all CRLs that match this issuer
final Collection<? extends CRL> selectedCRLs = revocationLists.getCRLs(crlSelector);
for(final CRL crl : selectedCRLs){
final X509CRL x509CRL = (X509CRL)crl;
//check first if the crl is really published by the issuer
x509CRL.verify(issuerPublicKey);
//check if the current certificate was revoked
final X509CRLEntry revokedCertificate = x509CRL.getRevokedCertificate(certificate);
//if we found a revoked certificate throw an exception
if(revokedCertificate != null){
throw new GeneralSecurityException(String.format("Unable to use certificate '%1$s', revocation after %2$tF %2$tT, reason: %3$s",
certificate.getSubjectX500Principal(), revokedCertificate.getRevocationDate(), revokedCertificate.getRevocationReason()));
}
}
}
}
private Map<String, PublicKey> extractPublicKeys(final List<X509Certificate> certificates, final Set<TrustAnchor> trustAnchors) {
final Map<String, PublicKey> certificateMap = new HashMap<>();
for(final X509Certificate certificate : certificates){
certificateMap.put(certificate.getSubjectX500Principal().getName(), certificate.getPublicKey());
}
for(final TrustAnchor trustAnchor : trustAnchors){
final X509Certificate certificate = trustAnchor.getTrustedCert();
certificateMap.put(certificate.getSubjectX500Principal().getName(), certificate.getPublicKey());
}
return certificateMap;
}
我正在使用 bouncy castle 离线验证 X509 证书,运行 遇到旧 CRL 的问题。我还没有找到接受过期 CRL 的可能性,在我看来,如果证书被撤销,它应该在 CRL 到期后保持撤销状态。此外,如果 CRL 为空,我只想接受这个,此时我无法获得更新的 CRL。
澄清一下,这就是用例:
- 2015年创建证书,有效期2015-2020
- 2017 年吊销带有 CRL 的证书,密钥被盗,只创建 1 年的 CRL,因为我犯了一个错误或计划滚动并且永远不会绕过它
- 查看2019年的证书,CRL过期,bouncy castle再次接受被吊销的证书——这显然不是我想要的
目前我正在将吊销检查设置为 false 并自行执行检查。我在网上找不到任何关于此的信息。
这是我的代码:
final X509CertSelector endConstraints = new X509CertSelector();
endConstraints.setSerialNumber(signer.getSID().getSerialNumber());
final PKIXBuilderParameters buildParams = new PKIXBuilderParameters(trustAnchors, endConstraints);
//a CertStore object with Certificates and CRLs
buildParams.addCertStore(certificates);
//currently deactivated
buildParams.setRevocationEnabled(false);
final CertPathBuilder builder = CertPathBuilder.getInstance(SignedFileVerifier.CERTIFICATE_PATH_ALGORITHM, SignedFileVerifier.PROVIDER);
final CertPathBuilderResult result = builder.build(buildParams);
//here I manually check the CRLs, which I don't want to do
checkRevocation(result.getCertPath().getCertificates(), certificates, trustAnchors);
//if this passes I return the found certificate
return (X509Certificate) result.getCertPath().getCertificates().get(0);
确切的例外是:
Caused by: org.bouncycastle.jce.exception.ExtCertPathValidatorException: No CRLs found for issuer "cn=goodOldIssuerCA0,ou=jUnit Test Issuer,o=BOGO Company,c=AT"
at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCertA(Unknown Source)
at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi.engineValidate(Unknown Source)
at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi.build(Unknown Source)
at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi.build(Unknown Source)
...
完全禁用 CRL 检查。不,我不是在开玩笑。安全社区的普遍共识是revocation is broken。现代浏览器甚至懒得检查 CRL。如果您可以在您的浏览器中启用严格的 CRL 检查(许多浏览器甚至不再让您启用),那么您会发现大部分网络都无法访问。
基本上我的整个问题都发生在包 org.bouncycastle.jce.provider
中的方法 PKIXCRLUtil#findCRLs
中。这是用于加载 CRL 的方法,并始终在此处检查日期:
if (crl.getNextUpdate().after(validityDate))
{
X509Certificate cert = crlselect.getCertificateChecking();
if (cert != null)
{
if (crl.getThisUpdate().before(cert.getNotAfter()))
{
finalSet.add(crl);
}
}
else
{
finalSet.add(crl);
}
}
我最终使用的代码如下。基本上我首先将所有 public 键按名称组合到一个映射中(也许序列号会更好?),然后遍历我在链中拥有的所有证书。首先我得到证书颁发者 public 密钥,因为我需要它来验证 CRL 来自同一颁发者。然后我创建一个 X509CRLSelector
颁发者并加载该颁发者的所有 CRL。然后我遍历我在商店中找到的 CRL,通过颁发者 public 密钥验证它们,检查证书是否被吊销,如果是这种情况则抛出异常。在我当前的实现中,如果没有找到 CRL 也没关系,这可以通过检查 selectedCRLs
是否为空来添加。
private void checkRevocation(final List<X509Certificate> certificates, final CertStore revocationLists, final Set<TrustAnchor> trustAnchors) throws GeneralSecurityException {
final Map<String, PublicKey> publicKeyMap = extractPublicKeys(certificates, trustAnchors);
//check the whole chain, we don't know if the issuer or the signer was revoked
for(final X509Certificate certificate : certificates){
final X500Principal issuerX500Principal = certificate.getIssuerX500Principal();
//get the issuer of this certificate
final PublicKey issuerPublicKey = publicKeyMap.get(issuerX500Principal.getName());
if(issuerPublicKey == null){
throw new GeneralSecurityException("Unable to find issuer for certificate '" + certificate.getSubjectX500Principal() + "'");
}
final X509CRLSelector crlSelector = new X509CRLSelector();
//we only use the issuer, not the date or time, don't want CRLs to expire
crlSelector.addIssuer(issuerX500Principal);
//get all CRLs that match this issuer
final Collection<? extends CRL> selectedCRLs = revocationLists.getCRLs(crlSelector);
for(final CRL crl : selectedCRLs){
final X509CRL x509CRL = (X509CRL)crl;
//check first if the crl is really published by the issuer
x509CRL.verify(issuerPublicKey);
//check if the current certificate was revoked
final X509CRLEntry revokedCertificate = x509CRL.getRevokedCertificate(certificate);
//if we found a revoked certificate throw an exception
if(revokedCertificate != null){
throw new GeneralSecurityException(String.format("Unable to use certificate '%1$s', revocation after %2$tF %2$tT, reason: %3$s",
certificate.getSubjectX500Principal(), revokedCertificate.getRevocationDate(), revokedCertificate.getRevocationReason()));
}
}
}
}
private Map<String, PublicKey> extractPublicKeys(final List<X509Certificate> certificates, final Set<TrustAnchor> trustAnchors) {
final Map<String, PublicKey> certificateMap = new HashMap<>();
for(final X509Certificate certificate : certificates){
certificateMap.put(certificate.getSubjectX500Principal().getName(), certificate.getPublicKey());
}
for(final TrustAnchor trustAnchor : trustAnchors){
final X509Certificate certificate = trustAnchor.getTrustedCert();
certificateMap.put(certificate.getSubjectX500Principal().getName(), certificate.getPublicKey());
}
return certificateMap;
}