Getting root and intermediate certificates from an

2019-06-28 06:43发布

问题:

still being a noob in cryptography I stumble upon simple things every day. And today is just one of those days.

I want to validate smime messages in java with the bouncy castle library, and I think I almost figured it out, but the problem at this moment is the building of the PKIXparameters object. Let's say, I have an end-entity x509certificate with the following structure:

root certificate
 +->intermediate certificate
    +->end-entity certificate

In order to validate message I need to build a chain of trust first, but I cannot figure out how to extract a root and intermediate certificates from the end-entity.

I tried to use end-entity as root but it didn't work:

InputStream isCert = GetFISCertificate();

List list = new ArrayList();
X509Certificate rootCert = (X509Certificate) certificateFactory.generateCertificate(isCert);
list.add(rootCert);
CollectionCertStoreParameters params = new CollectionCertStoreParameters(list);
CertStore store = CertStore.getInstance("Collection", params, BC);

//create cert path
List certChain = new ArrayList();
certChain.add(rootCert);
CertPath certPath = certificateFactory.generateCertPath(certChain);
Set trust = Collections.singleton(new TrustAnchor(rootCert, null));

//validation
CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX", BC);
PKIXParameters pKIXParameters = new PKIXParameters(trust);
pKIXParameters.addCertStore(store);
pKIXParameters.setDate(new Date());
try {
CertPathValidatorResult result = certPathValidator.validate(certPath, pKIXParameters);
System.out.println("certificate path validated");

} catch (CertPathValidatorException e) {
System.out.println("validation failed on certificate number " + e.getIndex() + ", details: " + e.getMessage());
}

Got this exception:

validation failed on certificate number -1, details: Trust anchor for certification path not found.

And btw, can I just use only the end-entity certificate to validate messages, as if it were the self-signed certificate?

回答1:

I've used BouncyCastle 1.56 for this test.

One way to get the issuer's certificate from the end entity is to look for the Authority Information Access extension.

This extension may be present (it's not mandatory) and may contain the URL to get the issuer's certificate (issuer is the certificate "above" the current one, so the end entity's issuer is the intermediate, and the intermediate's issuer is the root).

You can get this extension value with BouncyCastle:

import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.extension.X509ExtensionUtil;

X509Certificate cert = // end entity certificate

// get Authority Information Access extension (will be null if extension is not present)
byte[] extVal = cert.getExtensionValue(Extension.authorityInfoAccess.getId());
AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(X509ExtensionUtil.fromExtensionValue(extVal));

// check if there is a URL to issuer's certificate
AccessDescription[] descriptions = aia.getAccessDescriptions();
for (AccessDescription ad : descriptions) {
    // check if it's a URL to issuer's certificate
    if (ad.getAccessMethod().equals(X509ObjectIdentifiers.id_ad_caIssuers)) {
        GeneralName location = ad.getAccessLocation();
        if (location.getTagNo() == GeneralName.uniformResourceIdentifier) {
            String issuerUrl = location.getName().toString();
            // http URL to issuer (test in your browser to see if it's a valid certificate)
            // you can use java.net.URL.openStream() to create a InputStream and create
            // the certificate with your CertificateFactory
            URL url = new URL(issuerUrl);
            X509Certificate issuer = (X509Certificate) certificateFactory.generateCertificate(url.openStream());
        }
    }
}

So you can use this code with the end entity certificate to get the intermediate. Then you use it again with the intermediate to get the root.

Then you add the root to your TrustAnchor and the validation should work.


Note: But as I said, this extension is not mandatory and may not be present. In this case, getExtensionValue will return null, and the only alternative that I know is to search for the certificates in google and download them (those certificate chains are usually public and not hard to find)



回答2:

btw, if we have out certificate installed in windows, everything is much simpler:

KeyStore ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
String alias = "your alias";
ArrayList<X509Certificate> certsChain = new ArrayList<>();
if (ks.isCertificateEntry(alias)) {
    Certificate[] chain = ks.getCertificateChain(alias);
    System.out.println("Chain length: " + chain.length);
        for(Certificate c : chain) certsChain.add((X509Certificate)c);
}

Collections.reverse(certsChain);
certsChain.forEach(MainClass::printDBG);

boom, and the whole certificates chain is ready