Certificate Revocation and Path Validation

Overview

X.509 digital certificates provide you with standardized formats that allow you to link public keys with particular entities in an otherwise device-independent fashion. Certificate paths, or chains, provide a mechanism that allow you to recognize a given entity as trusted providing you trust the parties involved in creating the validation certificates leading up to that of the entity.

This is not the end of the problem. Certificates can be used to validate a variety of things, including timestamps, other certificates, executable code, and so on. The question then becomes, if the signature on the certificate you want to use is valid, is the use the certificate is being presented to you for the one the issuer of the certificate authorized when the issuer signed it? Even then, if it is being presented for an apparently legitimate use, how do you know the issuer has not since decided to revoke the authorization the issuer has extended?

This chapter introduces some of the common mechanisms both for finding out if an issuer has since withdrawn authorization for the use of a certificate and also what mechanisms are available in Java to validate a certificate path so you can tell that not only is the end entity certificate legitimate, but that every certificate between it and the one you really trust is legitimate as well.

By the end of this chapter you should

Finally, you will also know how to take a random collection of certificates, and possibly certificate revocation lists, in a CertStore and create a certificate path that is valid for a certificate of interest to you.

Getting Started

At this point, you are starting to cover some bigger issues in certificate handling ”or at least the implementations that surround them. To do this, you need a ready supply of certificates, so let's further extend the Utils class to add the necessary functionality.

For the purposes of looking at certificate revocation and path validation, you need at least three certificates. The reasons for this will become obvious as you look further into the chapter, but for the moment I'll just go through the three methods to be added, quickly reviewing some of the ground you have already covered. Although the additions are rather long, they are not complex, and I will break the class up rather than presenting it as one chunk so I can better comment on it.

The first step, of course, is the class header and the initial declaration, given here:

package chapter7; import java.math.BigInteger; import java.security.*; import java.security.cert.X509Certificate; import java.util.Date; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.x509.*; import org.bouncycastle.x509.extension.*; /** * Chapter 7 Utils */ public class Utils extends chapter6.Utils { private static final int VALIDITY_PERIOD = 7 * 24 * 60 * 60 * 1000; // one week

The first certificate you need is the CA root certificate. You can use a self-signed version 1 certificate in this position, so as they are the simplest to make, I'll define the generateRootCert() method to create one of those as follows :

/** * Generate a sample V1 certificate to use as a CA root certificate */ public static X509Certificate generateRootCert(KeyPair pair) throws Exception { X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(1)); certGen.setIssuerDN(new X500Principal("CN=Test CA Certificate")); certGen.setNotBefore(new Date(System.currentTimeMillis())); certGen.setNotAfter( new Date(System.currentTimeMillis() + VALIDITY_PERIOD)); certGen.setSubjectDN(new X500Principal("CN=Test CA Certificate")); certGen.setPublicKey(pair.getPublic()); certGen.setSignatureAlgorithm("SHA1WithRSAEncryption"); return certGen.generateX509Certificate(pair.getPrivate(), "BC"); }

The next certificate you need is an intermediate certificate. You'll usually see one or more of these in a certificate chain, because they not only reduce the number of certificates that have to be signed using the root certificates private key (a good thing), but they also allow the same root to be used as the origin of specialized chains validated for use only for particular applications. This has to be a version 3 certificate, because at the least it should identify itself as a CA certificate using the basic constraints extension.

Here is the code for the generateIntermediateCert() method that will provide the intermediate certificates:

/** * Generate a sample V3 certificate to use as an intermediate CA certificate */ public static X509Certificate generateIntermediateCert( PublicKey intKey, PrivateKey caKey, X509Certificate caCert) throws Exception { X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(1)); certGen.setIssuerDN(caCert.getSubjectX500Principal()); certGen.setNotBefore(new Date(System.currentTimeMillis())); certGen.setNotAfter( new Date(System.currentTimeMillis() + VALIDITY_PERIOD)); certGen.setSubjectDN( new X500Principal("CN=Test Intermediate Certificate")); certGen.setPublicKey(intKey); certGen.setSignatureAlgorithm("SHA1WithRSAEncryption"); certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert)); certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(intKey)); certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(0)); certGen.addExtension( X509Extensions.KeyUsage, true, new KeyUsage( KeyUsage.digitalSignature KeyUsage.keyCertSign KeyUsage.cRLSign)); return certGen.generateX509Certificate(caKey, "BC"); }

Note that the number of parameters passed to the method has gone up. To create a certificate validated by another, details are required from the validation certificate in addition to the signing key and the public key that will be stored in the certificate. There are two other items of interest. The first is that the basic constraints extension indicates that this is a CA certificate and that the end entity certificate must follow it immediately. The second is that the key usage extension now identifies that the certificate can be used for validating other certificates and for validating certificate revocation lists (CRLs). You will see why these two items of interest are important when you look at path validation later.

Finally, you need an end entity certificate, and this is created by the generateEndEntityCert() method. This is also a version 3 certificate with a basic constraints extension that indicates as much, together with a key usage extension that makes it suitable for use with SSL/TLS. Also, as with the intermediate certificate, a subject key identifier extension is added to provide a linkage based on the public key, and an authority key identifier extension is added to provide a linkage back to the validating certificate.

Here is the code for the generateEndEntityCert() method, together with the final close brace for the class definition:

/** * Generate a sample V3 certificate to use as an end entity certificate */ public static X509Certificate generateEndEntityCert( PublicKey entityKey, PrivateKey caKey, X509Certificate caCert) throws Exception { X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(1)); certGen.setIssuerDN(caCert.getSubjectX500Principal()); certGen.setNotBefore(new Date(System.currentTimeMillis())); certGen.setNotAfter( new Date(System.currentTimeMillis() + VALIDITY_PERIOD)); certGen.setSubjectDN(new X500Principal("CN=Test End Certificate")); certGen.setPublicKey(entityKey); certGen.setSignatureAlgorithm("SHA1WithRSAEncryption"); certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert)); certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(entityKey)); certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false)); certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature KeyUsage.keyEncipherment)); return certGen.generateX509Certificate(caKey, "BC"); } }

Set up the Utils class for Chapter 7 and you are ready to proceed with the first topic ”certificate revocation lists.

Certificate Revocation Lists

The original method for dealing with certificate revocation was to use certificate revocation lists, or CRLs. The concept is a fairly simple one to understand. In addition to the root certificate you are using to validate certificates that come your way, you have a CRL for the root certificate that contains a list of the certificates issued for that root certificate that have, for one reason or another, been revoked . Basically it is the same idea as the blacklists of bad credit card numbers given out to shopkeepers before it was possible to do these transactions online. As Figure 7-1 shows, CRLs are distributed by a server and held by the client that needs them to check certificates.

Figure 7-1

As well as providing a blacklist of certificates, a CRL is also said to have a particular scope , and it is the scope that defines what certificates can end up in a CRL. Generally the scope is defined by the identity of the issuer, which is to say the CRL will contain all certificates issued by some particular CA. The scope can be refined to suit, so you can also encounter CRLs where the scope is "all certificates issued by X and revoked for reasons of key compromise," and a CRL is regarded as complete if it lists all certificates that satisfy its scope that have not yet expired .

So, using CRLs to determine whether a given certificate is revoked involves finding a CRL of the correct scope and seeing if the certificate is present in it. The most general way of doing this in the JCA is provided by the CRL class in the java.security.cert package.

The CRL Class

The CRL class provides a high-level abstraction of a certificate revocation list. It is a very simple abstract class with only a few methods on it. Special cases of CRLs that conform to particular implementations , such as the CRLs defined in X.509, extend off the CRL class, and it provides the basic support for telling whether a particular certificate is revoked.

CRL.getType()

The getType() method returns a String representing the type of the CRL. The method is abstract, and in the case of the particular implementation of CRLs you are dealing with in this chapter, you would expect this method to return X.509 .

CRL.isRevoked()

The isRevoked() method takes a single Certificate object as a parameter and returns true if the certificate is present in the CRL. If the Certificate object is not present in the CRL, the method returns false .

When I discuss the X509CRL class, you will see that the full answer to whether a certificate is revoked, and what it means can be a little bit more complicated than this method makes it sound. In practice you generally cast a CRL to the type that extends it in order to understand the full situation with the revocation. So, that being said, it is time to look at the specifics of CRLs as they apply to X.509.

X 509 Certificate Revocation Lists

As with certificates, X.509 also defines a set of structures to be used to represent CRLs. Not surprisingly, these structures are represented using ASN.1 and are also, for the most part, directly supported in the JCA. Before you look at the Java perspective on how to process a X.509 CRL, the first question I should probably answer is how, given an issuer X.509 certificate, you get a CRL for it in the first place.

Given a X.509 certificate, there are a couple of ways you might locate a valid CRL for it: It might be given to you, or the X.509 certificate may contain a CRL distribution points extension that gives you information on where to find the certificate. The extension is identified by the OID "2.5.29.31" (id-ce-cRLDistributionPoints), and its value is defined by the ASN.1 type CRLDistributionPoints.

There is a reasonably lengthy discussion of the type in RFC 3280 in section 4.2.1.14, but to give you a general idea about the extension, its ASN.1 definition is as follows :

CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint

where DistributionPoint is defined as:

DistributionPoint ::= SEQUENCE { distributionPoint [0] DistributionPointName OPTIONAL, reasons [1] ReasonFlags OPTIONAL, cRLIssuer [2] GeneralNames OPTIONAL }

Although the three fields are optional, a DistributionPoint should always contain a distributionPoint or a cRLIssuer.

The reasons field, if specified, gives a subset of reasons that a particular CRL will cover. ReasonFlags is a BIT STRING with the following definition:

ReasonFlags ::= BIT STRING { unused (0), keyCompromise (1), cACompromise (2), affiliationChanged (3), superseded (4), cessationOfOperation (5), certificateHold (6), privilegeWithdrawn (7), aACompromise (8) }

When the reasons field is used, you will often find there are several CRLs that can be downloaded, each representing certificates that are revoked for the particular reasons indicated by the bits that have been set in the reasons field.

The DistributionPointName type is defined as:

DistributionPointName ::= CHOICE { fullName [0] GeneralNames, nameRelativeToCRLIssuer [1] RelativeDistinguishedName }

If it is the distributionPoint field that is set, you will generally find that the DistributionPointName will be a fullName and that the structure will contain a GeneralName representing a URL. The URL will give the location to get the CRL from. In the event that the fullName contains multiple names , they will all provide different ways of obtaining the same CRL. If the nameRelativeToCRLIssuer is present instead, it contains a fragment that should be appended to the issuer name and then used to look up an X.500 directory.

The cRLIssuer field identifies the entity that issued and signed the CRL. If the distributionPoint field is not set but the cRLIssuer field is, then the cRLIssuer should contain at least one X.500 name that can be used to look up the CRL using a directory service such as LDAP.

Having acquired a X.509 CRL, you then have to work out what to do with it. The first stop for doing this in Java is the X509CRL class.

The X509CRL Class

The java.security.cert.X509CRL class provides the basic support for X.509 CRLs in the JCA, and its design and how it interacts with other CRL-related classes could be seen as being related to the ASN.1 definition of its related ASN.1 structure ”the CertificateList.

CertificateList ::= SEQUENCE { tbsCertList TBSCertList, signatureAlgorithm AlgorithmIdentifier, signature BIT STRING }

Looking at the CertificateList structure, you can see that it is very similar to that given for a certificate or certification request. The CertificateList structure simply provides a wrapper that carries the content in the TBSCertList structure and the BIT STRING representation of a signature made using the algorithm and parameters specified by the signatureAlgorithm field.

I will start by looking at the methods applicable to the TBSCertList structure first and then look at the validation of the signature at the end.

X509CRL.getTBSCertList()

The getTBSCertList() method returns the bytes making up the ASN.1 encoding of the TBSCertList structure present in the tbsCertList field. These are the bytes used to provide the input for calculating the signature that is stored in the signature field in the CertificateList structure.

The TBSCertList has the following ASN.1 structure:

TBSCertList ::= SEQUENCE { version Version OPTIONAL, -- if present, must be v2 signature AlgorithmIdentifier, issuer Name, thisUpdate Time, nextUpdate Time OPTIONAL, revokedCertificates SEQUENCE OF SEQUENCE { userCertificate CertificateSerialNumber, revocationDate Time, crlEntryExtensions Extensions OPTIONAL -- if present, must be v2 } OPTIONAL, crlExtensions [0] EXPLICIT Extensions OPTIONAL -- if present, must be v2 }

As you will see, most of the get() methods associated with the X509CRL class are simply returning the values of the fields in the TBSCertList structure.

Looking at the definition, you can see there is one construct that you have not encountered before:

revokedCertificates SEQUENCE OF SEQUENCE { userCertificate CertificateSerialNumber, revocationDate Time, crlEntryExtensions Extensions OPTIONAL -- if present, must be v2 }

SEQUENCE OF SEQUENCE simply means that the revokedCertificates field is a SEQUENCE made of other SEQUENCE objects that are composed of two, possibly three, fields. An immediate parallel in Java would be an ArrayList of ArrayList objects, although this would not take full advantage of the type safety Java affords you. In this case a specific class is defined to represent each entry of three items in the revokedCertificates sequence ”the X509CRLEntry class. You will take a closer look at this class a bit later.

X509CRL.getVersion()

The getVersion() method returns version number contained in the TBSCertList structure. In this case you would normally expect this to be the value 2.

X509CRL.get IssuerX500Principal()

The getIssuerX500Principal() method returns an X500Principal object representing the value contained in the issuer field of the TBSCertList structure.

You may also see the use of getIssuerDN() instead of getIssuerX500Principal(). Prior to JDK 1.4 the only option was to use the getIssuerDN() method. It returns a java.security.Principal object, the underlying implementation of which was provider-dependent. These days you should avoid using getIssuerDN() wherever possible.

X509CRL.getThisUpdate() and X509CRL.getNextUpdate()

The getThisUpdate() and getNextUpdate() return the values contained in the thisUpdate and nextUpdate fields as Date objects.

The value returned by getThisUpdate() represents the date and time the CRL was created by the issuer.

The value returned by getNextUpdate() represents the date and time at which the CRL should be treated as having expired , and a new one should be requested from the issuer. Although the nextUpdate field is optional, RFC 3280 mandates it and does not specify how to interpret the absence of the field.

X509CRL.get RevokedCertificates()

The getRevokedCertificates() method returns a Set of X509CRLEntry objects. The X509CRLEntry represents the three fields in the SEQUENCE OF SEQUENCE that defines the revokedCertificates field in the TBSCertList structure. If the revokedCertificates field is not present in the TBSCertList, then getRevokedCertificates() will return null.

X509CRL.get RevokedCertificate()

The getRevokedCertificate() method takes a single X509Certificate as a parameter and returns a X509CRLEntry representing the details of its revocation if one exists. If there is no CRL entry present for the certificate, then the method will return null.

X509CRL.getSignature()

The getSignature() method returns the bytes making up the signature stored in the BIT STRING contained in the signature field. These bytes are suitable for verification with a java.security.Signature class.

X509CRL.getSigAlgOID(), and X509CRL.getSigAlgParams()

The getSigAlgOID() method returns the value of the OBJECT IDENTIFIER in the algorithm field of the AlgorithmIdentifier structure in the signatureAlgorithm field in the CertificateList structure. The getSigAlgParams() returns a byte array that represents the DER encoding of the parameters field in the AlgorithmIdentifier structure in the signatureAlgorithm field.

You would have noticed there is a signature field in the TBSCertList structure, which is also of the type AlgorithmIdentifier. The purpose of this is to provide a cross check against the unsigned signatureAlgorithm field in the CertificateList structure. The contents of the signatureAlgorithm field and the signature field will always be equal in a valid CRL.

X509CRL.getSigAlgName()

The getSigAlgName() method will return a more (hopefully) human-friendly version of the name that is associated with the signature algorithm used ”for example, SHA1withDSA rather than the 1.2.840.10040.4.3 that getSigAlgOID() would return.

X509CRL.verify()

The verify() method is used to check that the signature contained in the CRL can be verified using the public key of the entity that was supposed to have signed the CRL. There are two versions of the verify() method, one that just takes the public key to use for verification as a parameter and another that takes the public key to use and a provider name.

Unlike Signature.verify(), X509CRL.verify() is a void and can throw one of five exceptions when it is called. The most likely one, in the event the public key is of the wrong value, is a SignatureException, indicating the signature did not match what was expected. The other four exceptions that can be thrown are NoSuchProviderException if no provider can be found, NoSuchAlgorithmException if the signature algorithm used in the CRL is not recognized, InvalidKeyException if the public key passed in is for the wrong algorithm, and CRLException if there is a problem encoding the CRL in order to calculate any hash required as part of signature verification.

X509CRL.getEncoded()

The getEncoded() method returns a byte array containing the DER encoding of the CRL's ASN.1 structure.

The X509CRLEntry Class

The X509CRLEntry class provides a type-safe way of representing the three fields contained in the SEQUENCE OF SEQUENCE contained in the revokedCertificates field in the TBSCertList. If you remember, the ASN.1 definition of these fields was as follows:

userCertificate CertificateSerialNumber, revocationDate Time, crlEntryExtensions Extensions OPTIONAL

The crlEntryExtensions field was added with version 2 of the X.509 CRL definition. You will see that some of the methods on the X509CRLEntry class actually use values from the crlEntryExtensions field if a particular extension is present. In the case of extensions that are locally defined or not handled by the base class, you can access the extension values using the methods defined on the java.security.cert.X509Extension interface in the same way you did with the X509Certificate class.

X509CRLEntry.get CertificateIssuer()

The getCertificateIssuer() method returns the issuer of the X.509 certificate described by the entry. By default this is the same as the issuer of the CRL; however, you should not assume this is the case, because a CRL entry with a CertificateIssuer extension will change the name of the issuer.

X509CRLEntry.get RevocationDate()

The getRevocationDate() method returns the date on which the revocation of the certificate came into effect.

As you will see a bit later when you look at the InvalidityDate extension, this is not necessarily the earliest time a certificate should be treated as being potentially invalid. You can think of it as representing when the paperwork for doing the revocation was finalized by the CA.

X509CRLEntry.get SerialNumber()

The getSerialNumber() method returns the serial number of the certificate that was revoked. This combined with the return value of the getCertificateIssuer() method will uniquely identify the certificate.

X509CRLEntry.has Extensions()

The hasExtensions() method will return true if the optional crlEntryExtensions field is set in the entry. If the field is not set, the method will return false.

Now take a look at the possible values you can find inside the crlEntryExtensions field.

X 509 CRL Entry Extensions

As you can see from the ASN.1 definition, the crlEntryExtensions field can be absent altogether. RFC 3280 does recommend that two extensions in particular, the reason code extension and the invalidity date extension, be included if the information the extensions represent is available. As with certificates and CRLs themselves , it is also possible for people to define their own extensions for CRL entries. For these purposes, I will just concentrate on the common extensions you might encounter.

The ReasonCode Extension

The reason code extension is used to indicate the reason why the certificate has ended up in the CRL, if that information is available. It is indicated by the OID "2.5.29.21" (id-ce-cRLReason) and is defined in ASN.1 as follows:

reasonCode ::= { CRLReason } CRLReason ::= ENUMERATED { unspecified (0), keyCompromise (1), cACompromise (2), affiliationChanged (3), superseded (4), cessationOfOperation (5), certificateHold (6), removeFromCRL (8), privilegeWithdrawn (9), aACompromise (10) }

If the reason code extension is not present, you should assume a reason code value of unspecified. The RFC 3280 profile actually goes so far as to say if the reason code value is unspecified, the reason code extension should be left out of the entry.

The HoldInstructionCode Extension

The hold instruction code allows a certificate to be temporarily suspended , rather than revoked. It is indicated by the OID "2.5.29.23" (id-ce-holdInstructionCode), and the extension value is an OBJECT IDENTIFIER, which can have one of three values:

holdInstruction OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) x9-57(10040) 2 } id-holdinstruction-none OBJECT IDENTIFIER ::= {holdInstruction 1} id-holdinstruction-callissuer OBJECT IDENTIFIER ::= {holdInstruction 2} id-holdinstruction-reject OBJECT IDENTIFIER ::= {holdInstruction 3}

The OID value determines what action you should take if you are attempting to validate the certificate. The id-holdinstruction-none OID is equivalent to the extension being absent, so, although its use is not encouraged, seeing it means do nothing. If you find id-holdinstruction-callissuer, you must contact the issuer for further instructions or reject the certificate as invalid. Encountering an OID with the value id-holdinstruction-reject means that the certificate should be rejected out of hand.

The tricky bit with putting a certificate on hold is that going on to a CRL is a one-way trip; an entry should never be deleted until the actual certificate it represents expires . If you put a certificate on hold, the only way to make it valid again is to leave it on the CRL and change the reason code from certificateHold to removeFromCRL.

The InvalidityDate Extension

The invalidity date provides the date on which it is known, or suspected, that the certificate became invalid. It is indicated by the OID "2.5.29.24" (id-ce-invalidityDate) and has the following ASN.1 structure:

invalidityDate ::= GeneralizedTime

The idea of introducing this extension was to take away the need to backdate the CRL entry if you wanted to indicate an earlier date for when the certificate had become invalid. The invalidityDate extension solves this problem, because the date it contains may be earlier than the revocation date in the CRL entry, which should represent the date on which the CA processed the revocation for the certificate, not when a particular certificate in the CRL became invalid.

The CertificateIssuer Extension

The certificate issuer extension is used to indicate who the real issuer of a certificate was. It is indicated by the OID "2.5.29.29" (id-ce-certificateIssuer) and has the following ASN.1 structure:

certificateIssuer ::= GeneralNames

Use of this extension requires a certain amount of housekeeping ”for example, when it appears every CRL entry appearing after the CRL containing the extension can be considered to have the same value of the extension. The entry list should be treated like this until another certificate issuer extension is encountered. Until a certificate issuer extension is encountered in the entry list, the certificate issuer for a certificate indicated by an entry defaults to the CRL issuer.

X 509 CRL Extensions

The version 2 profile for CRLs introduced extensions. They provide extensions that take advantage of X.509 certificate extensions. They also attempted to deal with issues such as allowing partial updates and providing information to support cache renewal if it was necessary.

The Extensions type referred to in TBSCertList structure is defined as follows:

Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }

This is the exact same structure you saw in Chapter 6, and there is some overlap among the extensions that can be used.

The AuthorityKeyIdentifier Extension

The purpose of the authority key identifier extension is to identify the public key used to sign the CRL. Note that this does not have to be the same certificate whose certificates are being revoked, and both certificates can have the same issuer. The extension is otherwise the same as the one you looked at in Chapter 6, with the same OID and structure.

The IssuerAlternativeName Extension

The purpose of the issuer alternative name extension is to allow alternate identities to be associated with the issuer of the CRL. The extension has the same structure and identifying OID associated with it as the one with the same name that you looked at in Chapter 6.

The CRLNumber Extension

The CRL number extension is identified by the OID "2.5.29.20" (id-ce-cRLNumber). It contains a positive sequence number, which is defined as follows:

CRLNumber ::= INTEGER (0..MAX)

The purpose of the CRL number is to allow users of the CRL to be able to easily tell if a given CRL supersedes another one. Aconforming CRL issuer will never produce two CRLs for the same scope at different times that have the same CRL number. RFC 3280 specifies that CRL numbers should not be longer than 20 octets, meaning they should be less than 2 159 ”don't forget you have to allow for that sign bit in the encoding of the INTEGER.

The DeltaCRLIndicator Extension

The delta CRL indicator extension is identified by the OID value "2.5.29.27" (id-ce-deltaCRLIndicator), and it has the following ASN.1 type as its value:

BaseCRLNumber ::= CRLNumber

If you find this extension in a CRL, it means that the CRL is delta relative to another CRL: the base CRL, the value of whose CRL number extension will be the same as the BaseCRLNumber the delta extension contains. The idea of a delta in the CRL context is that it contains only what has changed since the base CRL was issued, and it represents an attempt at limiting some of the overheads related to constantly distributing updates for CRLs.

How you merge a delta CRL with an existing one is a bit of an open question in some areas. Again, it is an issue that is only settled by the specific profile you are using. One example of how to do it can be seen in RFC 3280, section 5.2.4.

The IssuingDistributionPoint Extension

The issuing distribution point extension is identified by the OID value "2.5.29.28" (id-ce-issuingDistributionPoint), and it has the following ASN.1 structure:

issuingDistributionPoint ::= SEQUENCE { distributionPoint [0] DistributionPointName OPTIONAL, onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE, onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE, onlySomeReasons [3] ReasonFlags OPTIONAL, indirectCRL [4] BOOLEAN DEFAULT FALSE, onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }

The issuingDistributionPoint identifies the distribution point and scope for a particular CRL, and it indicates whether the CRL covers revocation for the following:

In addition, it may provide an actual location for obtaining the most current version of the CRL in the distribution point name.

The FreshestCRL Extension

The freshest CRL extension identifies how delta CRL information can be obtained for the CRL it is found in. It should only be seen in a complete CRL and never a CRL that has a delta CRL extension in it. It is identified by the OID value "2.5.29.46" (id-ce-freshestCRL) and has the following ASN.1 definition:

FreshestCRL ::= CRLDistributionPoints

As you can see, this is the same type that you looked at when you started discussing X.509 CRLs in the context of the CRL distribution points extension in a X.509 certificate, and as it happens, the information in the freshest CRL extension should be interpreted in the same way.

Try It Out: Creating a CRL

Have a look at the following example; it creates a basic CRL with one entry that is revoking a certificate issued by the CA certificate owner with the serial number 2. I have added two extensions to the CRL in line with the requirements for conformance in RFC 3280. Neither of the extensions are marked as critical, as the profile detailed in RFC 3280 does not require it.

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.PrivateKey; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509Certificate; import java.util.Date; import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.x509.X509V2CRLGenerator; import org.bouncycastle.x509.extension.*; /** * Basic Example of generating and using a CRL. */ public class X509CRLExample { public static X509CRL createCRL( X509Certificate caCert, PrivateKey caKey, BigInteger revokedSerialNumber) throws Exception { X509V2CRLGenerator crlGen = new X509V2CRLGenerator(); Date now = new Date(); crlGen.setIssuerDN(caCert.getSubjectX500Principal()); crlGen.setThisUpdate(now); crlGen.setNextUpdate(new Date(now.getTime() + 100000)); crlGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); crlGen.addCRLEntry(revokedSerialNumber, now, CRLReason.privilegeWithdrawn); crlGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert)); crlGen.addExtension(X509Extensions.CRLNumber, false, new CRLNumber(BigInteger.valueOf(1))); return crlGen.generateX509CRL(caKey, "BC"); } public static void main(String[] args) throws Exception { // create CA keys and certificate KeyPair caPair = Utils.generateRSAKeyPair(); X509Certificate caCert = Utils.generateRootCert(caPair); BigInteger revokedSerialNumber = BigInteger.valueOf(2); // create a CRL revoking certificate number 2 X509CRL crl = createCRL(caCert, caPair.getPrivate(), revokedSerialNumber); // verify the CRL crl.verify(caCert.getPublicKey(), "BC"); // check if the CRL revokes certificate number 2 X509CRLEntry entry = crl.getRevokedCertificate(revokedSerialNumber); System.out.println("Revocation Details:"); System.out.println(" Certificate number: " + entry.getSerialNumber()); System.out.println(" Issuer : " +crl.getIssuerX500Principal()); if (entry.hasExtensions()) { byte[] ext = entry.getExtensionValue( X509Extensions.ReasonCode.getId()); if (ext != null) { DEREnumerated reasonCode = (DEREnumerated)X509ExtensionUtil.fromExtensionValue(ext); System.out.println(" Reason Code : "+reasonCode.getValue()); } } } }

Running the example produces the following output:

Revocation Details: Certificate number: 2 Issuer : CN=Test CA Certificate Reason Code : 9

As you can see, the example has produced a CRL that contains a revocation for the certificate with the serial number 2, issued by the principal CN=Test CA Certificate. The reason code is given as number 9, which if you look at the back at the definition of CRLReason indicates that it is the value for privilegeWithdrawn.

How It Works

As you can see from the example, there are a number of steps to creating a CRL.

After creating the generator, you have to set the issuer DN for the certificate you want to revoke. In this case I have done this by using the subject of the issuer certificate, as in:

crlGen.setIssuerDN(caCert.getSubjectX500Principal());

Had I been doing this using the certificate that was being revoked, that is, the one that was issued, this line would have looked different, because I would have used the issuer of the issued certificate ”something like:

crlGen.setIssuerDN( issuedCert .getIssuerX500Principal());

The next steps involve setting the time that this update was generated at using crlGen.setThisUpdate() and setting a time at which the CRL you are generating should no longer be considered reliable using crlGen.setNextUpdate(). After that you set the type of signature you want to sign the CRL with by calling crlGen.setSignatureAlgorithm().

After that you add the CRL entry by calling crlGen.addCRLEntry(). In this case, I'm adding only one, but as you can imagine, a lot of CRLs carry more than one entry. Looking at the call you can see there are three parameters:

crlGen.addCRLEntry(revokedSerialNumber, now, CRLReason.privilegeWithdrawn);

The first two are the serial number of the certificate being revoked, which fills in the userCertificate field of the CRL entry, and the next one fills in the revocationDate field, which represents the date on which the CRL issuer processed the revocation. The last parameter is the reason the certificate was revoked, and this is added as a ReasonCode extension in the crlEntryExtensions field of the CRL entry.

If you wanted to alert a user of the CRL that the certificate should have been revoked earlier than when you processed it, you would have written something like:

crlGen.addCRLEntry( revokedSerialNumber, now, CRLReason.privilegeWithdrawn, earlierDate );

and the value in earlierDate would have been added using an InvalidityDate extension on the CRL entry.

Finally, you add extensions providing a link back to the CA certificate using the AuthorityKeyIdentifier extension, assign a number to this CRL using the CRLNumber extension, and then generate the CRL, signing it with the issuer's private key.

Reading CRLs using the CertificateFactory Class

In addition to methods for reading certificates and certificate paths, the CertificateFactory class also supports the reading of CRLs from input streams. The methods are CertificateFactory. generateCRL() and CertificateFactory.generateCRLs().

CertificateFactory.generateCRL()

Other than the fact it returns a CRL, the generateCRL() method behaves in the same way as the generateCertificate() method does. It takes an InputStream as a parameter, which is meant to contain encoded CRLs. If the factory is of a specific type, such as X.509, the CRL object returned can be cast to whatever specific class is provided to support that type, such as, in the case of X.509, the X509CRL class. If an error is encountered the method will throw a CRLException.

Remember that, as with the generateCertificate() method, if you are trying to read multiple CRLs using this method the InputStream passed in must support InputStream.mark() and InputStream. reset(); otherwise, only the first CRL in the stream will be returned. In general if you are trying to read multiple CRLs from an InputStream, it is better to use the generateCRLs() method.

CertificateFactory.generateCRLs()

The generateCRLs() method returns a Collection containing all the CRLs that were found in the InputStream passed in. If a problem is encountered parsing the stream, a CRLException will be thrown.

Try It Out: Building a CRL Using the CertificateFactory

Have a look at the following example. It uses the CertificateFactory.generateCRL() method to recover an encoded CRL from an input stream. As you can see, generateCRL() method is used in the same way as the generateCertificate() method was.

package chapter7; import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509Certificate; /** * Reading a CRL with a CertificateFactory */ public class CRLCertFactoryExample { public static void main(String[] args) throws Exception { // create CA keys and certificate KeyPair caPair = Utils.generateRSAKeyPair(); X509Certificate caCert = Utils.generateRootCert(caPair); BigInteger revokedSerialNumber = BigInteger.valueOf(2); // create a CRL revoking certificate number 2 X509CRL crl = X509CRLExample.createCRL( caCert, caPair.getPrivate(), revokedSerialNumber); // encode it and reconstruct it ByteArrayInputStream bIn = new ByteArrayInputStream(crl.getEncoded()); CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC"); crl = (X509CRL)fact.generateCRL(bIn); // verify the CRL crl.verify(caCert.getPublicKey(), "BC"); // check if the CRL revokes certificate number 2 X509CRLEntry entry = crl.getRevokedCertificate(revokedSerialNumber); System.out.println("Revocation Details:"); System.out.println(" Certificate number: " + entry.getSerialNumber()); System.out.println(" Issuer : " +crl.getIssuerX500Principal()); } }

Running the example, you will see the following output:

Revocation Details: Certificate number: 2 Issuer : CN=Test CA Certificate

indicating that the CRL was successfully recovered, verified, and had the details you expect.

How It Works

The CertificateFactory.generateCRL() method requires an InputStream as a parameter, and in this case you just used a ByteArrayInputStream derived from the output of the getEncoded() method on the CRL. As you can imagine, this was done more for the convenience of the example. In a normal situation you would probably be reading the information from a file or a stream derived from an HTTP response.

In the case of the example, because I know there is only one CRL present in the stream, I have just called generateCRL(), like this:

crl = (X509CRL)fact.generateCRL(bIn);

Had I been allowing for more than one CRL, and I was sure the stream supported mark() and reset(), I could have used

while ((crl = (X509CRL)fact.generateCRL(bIn)) != null) { // processing code ... }

Or, preferably, I could have used the method returning a Collection, generateCRLs(), as follows:

Collection crlCollection = fact.generateCRLs(bIn);

and then iterated through the contents of the Collection to do processing.

As it happens, objects of the CertStore class can also be used to store CRLs. So the other advantage of the generateCRLs() method is that, as it returns a Collection, its return value is ready-made for creating a CollectionCertStoreParameters object and creating a CertStore.

The X509CRLSelector Class

The java.security.cert.X509CRLSelector class provides an implementation of the CRLSelector interface that allows you to search a CertStore for CRLs that match on a variety of X.509 related fields. It has a variety of set() methods on it that allow you to specify criteria for matching a X.509 CRL that might be present in a CertStore object.

The CRLSelector interface carries two methods on it. One is Object.clone(), so any implementation of a CRLSelector should be clonable. The other is a match() method that takes a single certificate as a parameter and returns true or false depending on whether the code in the match() methods implementation decides the CRL passed in is one of those it is looking for.

The X509CRLSelector provides a range of set() methods, each of which can be used to provide values to be matched against in a X.509 CRL. If no set() methods are called, an X509CRLSelector will match every CRL in the store. If more than one set() method is called, the X509CRLSelector will only match CRLs for which every criteria specified via a set() method is matched.

You will just look at the more common ones here. For a fuller description of what is available, you should check the JavaDoc for the class.

X509CRLSelector.addIssuer() and X509CRLSelector.addIssuerName()

The addIssuer() method was introduced in JDK 1.5 and takes an X500Principal representing the issuer being looked for. It will cause the selector to match any issuer passed to it.

The addIssuerName() method predates 1.5. It has two versions, one of which takes a byte array representing an ASN.1 encoding of an X.500 Name, the other that takes a String version of the distinguished name. Use only the byte array version. The usual problems with converting X.500 Names into strings from their encoded forms and back into their encoded forms apply here also.

X509CRLSelector.set DateAndTime()

The setDateAndTime() method takes a Date object as a parameter and matches any CRLs whose thisUpdate and nextUpdate fields have values that bracket the value of the Date passed in.

X509CRLSelector.setMaxCRL() and X509CRLSelector.setMinCRL()

Both setMaxCRL() and setMinCRL() take a BigInteger as a parameter that represents either the maximum or minimum value the CRLNumber extension can have.

If only the setMaxCRL() method is called, or setMinCRL() is called with a null value as well, then any CRL with a CRLNumber extension whose value is less than or equal to the value of the parameter passed to setMaxCRL() will be matched.

If only the setMinCRL() method is called, or setMaxCRL() is called with a null value as well, then any CRL with a CRLNumber extension whose value is greater than or equal to the value of the parameter passed to setMinCRL() will be matched.

Try It Out: Retrieving a CRL from a CertStore

This example shows a simple use of an X509CRLSelector. It builds a CertStore, which contains just the CRL you have been using from previous examples and then retrieves it using the selector class. Try running it and have a look at what it does.

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.CertStore; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509CRLSelector; import java.security.cert.X509Certificate; import java.util.*; /** * Using the X509CRLSelector and the CertStore classes. */ public class CRLCertStoreExample { public static void main(String[] args) throws Exception { // create CA keys and certificate KeyPair caPair = Utils.generateRSAKeyPair(); X509Certificate caCert = Utils.generateRootCert(caPair); BigInteger revokedSerialNumber = BigInteger.valueOf(2); // create a CRL revoking certificate number 2 X509CRL crl = X509CRLExample.createCRL( caCert, caPair.getPrivate(), revokedSerialNumber); // place the CRL into a CertStore CollectionCertStoreParameters params = new CollectionCertStoreParameters( Collections.singleton(crl)); CertStore store = CertStore.getInstance( "Collection", params, "BC"); X509CRLSelector selector = new X509CRLSelector(); selector.addIssuerName(caCert.getSubjectX500Principal().getEncoded()); Iterator it = store.getCRLs(selector).iterator(); while (it.hasNext()) { crl = (X509CRL)it.next(); // verify the CRL crl.verify(caCert.getPublicKey(), "BC"); // check if the CRL revokes certificate number 2 X509CRLEntry entry = crl.getRevokedCertificate(revokedSerialNumber); System.out.println("Revocation Details:"); System.out.println(" Certificate number: " + entry.getSerialNumber()); System.out.println(" Issuer : " + crl.getIssuerX500Principal()); } } }

Running the example, you will see the following output:

Revocation Details: Certificate number: 2 Issuer : CN=Test CA Certificate

indicating that the CRL was successfully located in the CertStore.

How It Works

You can see this example is very similar to the one discussed in the context of the X509CertSelector in Chapter 6. The CertStore is set up in exactly the same way as it would be if you were adding certificates to it. You then create a selector that will match any CRL with the issuer name set to the subject of the CA certificate using the following code:

selector.addIssuerName(caCert.getSubjectX500Principal().getEncoded());

which you can see essentially matches the manner in which the CRL is created. One note on this line of code: I have used X500Principal.getEncoded() to avoid the issue of having to convert the issuer DN into a String. Doing this is no longer necessary in JDK 1.5, because you can pass an X500Principal directly, but I have written the example so that it should work with JDK 1.4 as well.

Once you have set up the selector, it is simply a matter of calling store.getCRLs(), passing it the selector, and getting back a collection representing the matching CRLs. Too easy!

Online Certificate Status Protocol

There are a number of problems with CRLs that can make them unmanageable quite quickly if you have a rapid turnover in certificates. Issues with distributing updates, having all your clients connect to renew when the last CRL you sent them expires , and not being able to get a CRL out in time all add up. In the same manner that financial institutions found making online facilities available preferable to continually sending out black lists of credit cards, Online Certificate Status Protocol (OCSP), which is described in RFC 2560, is an answer to the online method for working out the revocation status of a certificate. As Figure 7-2 shows, an OCSP server, or responder , provides real-time responses to client queries about the status of certificates.

Figure 7-2

In addition to defining the basic protocol, RFC 2560 also defines a number of new extension types that use the same extensions structure that you have already seen in certificates and CRLs. The new extension types are all identified off the following OID:

id-pkix-ocsp OBJECT IDENTIFIER ::= { 1 3 6 1 5 5 7 48 1 }

Take note of this value, because further on in this section, I refer to the identifiers associated with the various extensions in reference to it.

The JCA does not directly support OCSP at the moment. The Bouncy Castle APIs do, however, so you will look at how you would implement OCSP using the Bouncy Castle API. There are three main aspects to the OCSP protocol: how a request is formed, how a response is formed , and how a certificate is identified in a request or a response. You will have a look at how a certificate is identified first.

The CertificateID Class

The org.bouncycastle.ocsp.CertificateID class provides a high-level implementation of the CertID structure that appears in RFC 2560. The CertID structure is used to uniquely identify certificates that are the subject of an OCSP request or response and has the following ASN.1 definition:

CertID ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier, issuerNameHash OCTET STRING, issuerKeyHash OCTET STRING, serialNumber CertificateSerialNumber }

As you can see, the last three fields of the CertID structure contain the information used to identify the certificate and the first field that identifies the algorithm that is used to create the second and third fields, both of which are based on message digests.

Objects of the type CertificateID are constructed directly using a String representing the hash algorithm, an issuer certificate, and the serial number of the certificate of interest. The necessary calculations are then done as part of construction, and the resulting values can be retrieved using a series of get() methods .

CertificateID.get HashAlgOID()

The getHashAlgOID() method returns a String representing the OBJECT IDENTIFIER value that identifies the hash algorithm used to create the values returned by getIssuerNameHash() and getIssuerKeyHash().

CertificateID.get IssuerNameHash()

The getIssuerNameHash() method returns a byte array containing the octets contained in the OCTET STRING that represents the issuerNameHash field. The value of the octets is calculated by feeding the bytes representing the DER encoding of the issuer name field of the certificate being identified into the hash algorithm.

CertificateID.get IssuerKeyHash()

The getIssuerKeyHash() method returns a byte array containing the octets contained in the OCTET STRING that represents the issuerKeyHash field. The value of the octets is calculated by feeding the DER encoding (excluding the tag and length octets) of the BIT STRING present in the subjectPublicKey field in the SubjectPublicKeyInfo structure assigned to the subjectPublicKeyInfo field in the issuer's certificate structure.

CertificateID.getSerialNumber()

The getSerialNumber() method returns a BigInteger representing the serial number of the certificate the CertificateID represents.

The OCSPReq Class

The org.bouncycastle.ocsp.OCSPReq class is an implementation of the ASN.1 type OCSPRequest defined in RFC 2560. As its name implies, the OCSPRequest object provides the data structure that must be filled in to produce a valid OCSP request. It is defined with the following ASN.1 structure:

OCSPRequest ::= SEQUENCE { tbsRequest TBSRequest, optionalSignature [0] EXPLICIT Signature OPTIONAL } Signature ::= SEQUENCE { signatureAlgorithm AlgorithmIdentifier, signature BIT STRING, certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL}

Note that in this case the signature is optional ”in general, most OCSP responders do not expect signed requests. The provision for the signature is to allow a server to be set up so that only authorized parties can talk to it. In such a situation, the signature becomes mandatory and allows the server to quickly reject requests it might be getting from unauthorized parties.

In addition to the AlgorithmIdentifier identifying the mechanism used to construct the signature and the BIT STRING representing the actual signature value, there is also provision in the Signature structure for including a sequence of certificates with the request using the certs field. If the certificate sequence is present, it should contain a valid certificate path leading from a certificate the server recognizes to the certificate that can be used to verify the signature on the request. The presence of this field allows the entity running the server to give its authorized users the right to extend their access rights to other parties unknown to the entity running the server. If the certs field is missing, then the server must be able to validate the signature directly.

The OCSPReq class is constructed using a byte array representing the DER-encoding of an OCSPRequest structure or by using an object of the OCSPReqGenerator class, the use of which you will see in the next example.

As you will see next, the ASN.1 structure for TBSRequest includes an optional Extensions field, so the class also implements the java.security.cert.X509Extension interface.

OCSPReq.getTBSRequest()

The getTBSRequest() method returns a byte array representing the DER encoding of the value in the tbsRequest field in the OCSPRequest structure. In the case where the optional signature is used, it is the DER encoding of the tbsRequest field, which is used to calculate the signature.

The tbsRequest field is of type TBSRequest, which has the following structure:

TBSRequest ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, requestorName [1] EXPLICIT GeneralName OPTIONAL, requestList SEQUENCE OF Request, requestExtensions [2] EXPLICIT Extensions OPTIONAL } Version ::= INTEGER { v1(0) }

and the get() methods on the OCSPReq class are a reflection of these.

OCSPReq.getVersion()

The getVersion() method simply returns the version number of the request. At the moment you would expect this to always be the value 1.

OCSPReq.getRequestorName()

The getRequestorName() method returns a GeneralName object representing the requestor. This field is optional, unless the requestor has chosen to sign the request, in which case the field must be set.

OCSPReq.getRequestList()

The getRequestList() method returns an array of org.bouncycastle.ocsp.Req objects. These represent the values found in the requestList field, which is a sequence of Request structures. The Request structure has the following ASN.1 definition:

Request ::= SEQUENCE { reqCert CertID, singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL }

and the Req class has a getCertID() method on it for retrieving the reqCert field as a CertificateID, as well as implementing the java.security.cert.X509Extension interface to allow retrieval of the values stored in the singleRequestExtensions field.

OCSPReq.isSigned()

The isSigned() method returns true if the OCSP request has been signed. Signing of an OCSP request is purely optional, unless it is mandated by the OCSP responder you want to talk to. If a request is signed, you should find that the getRequestorName() method returns the GeneralName identifying the requestor.

If isSigned() returns true , you will also get non-null return values from OCSPReq.getSignature() and OCSPReq.getSignatureAlgOID(). There may also be a certificate chain associated with the request that you can retrieve using getCertificates().

OCSPReq.getSignature() and OCSPReq.getSignatureAlgOID()

The getSignature() and getSignatureAlgOID() return the bytes making up the signature and a String representing the OID identifying the algorithm that has been used to create the signature.

If isSigned() returns false, both the methods will return null .

OCSPReq.getCertificates()

If a request is signed the requestor may also include one or more certificates that can be used to verify the signature and its origins. The getCertificates() method returns a CertStore that contains the certificates, if any, that are contained in the request.

If isSigned() returns false, this method will return null .

OCSP Request Extensions

Request extensions can appear in both the requestExtensions field in the TBSRequest structure and the singleRequestExtensions field. The following standard extensions are currently defined for inclusion in OCSP requests.

The Nonce Extension

The nonce extension is used to bind a request to a response to prevent replay attacks. As the name implies, the nonce value is something that the client should only use once within a reasonably small period; the extension appears in the requestExtensions field in a request and should be echoed back as an extension in the response.

The nonce extension is identified by the following identifier as the extension type:

id-pkix-ocsp-nonce OBJECT IDENTIFIER ::= { id-pkix-ocsp 2 }

What appears as the extension value is purely up to the client.

The Acceptable Response Types Extension

In situations where a server may be able to provide a variety of response messages, in addition to the basic one, an OCSP client may specify the response types it understands. It can do this by including the acceptable response types extension in the requestExtensions field of its request and an AcceptableResponses structure as the extension value. AcceptableResponses has the following ASN.1 definition:

AcceptableResponses ::= SEQUENCE OF OBJECT IDENTIFIER

where the OBJECT IDENTIFIER values represent the particular response type the client can handle.

The acceptable response types extension is identified by the following identifier as the extension type:

id-pkix-ocsp-response OBJECT IDENTIFIER ::= { id-pkix-ocsp 4 }

The Service Locator Extension

In some cases an OCSP responder may be able reroute requests to another OCSP responder, which is known to be authoritative from information in the certificate being checked. If this is possible, the corresponding information in the certificate's authority information access extension should be copied into an extension in the singleRequestExtensions field associated with the certificate.

The extension is identified by the identifier

id-pkix-ocsp-service-locator OBJECT IDENTIFIER ::= { id-pkix-ocsp 7 }

and is defined as having the following value:

ServiceLocator ::= SEQUENCE { issuer Name, locator AuthorityInfoAccessSyntax OPTIONAL }

The AuthorityInfoAcessSyntax is defined as follows :

AuthorityInfoAccessSyntax ::= SEQUENCE SIZE (1..MAX) OF AccessDescription AccessDescription ::= SEQUENCE { accessMethod OBJECT IDENTIFIER, accessLocation GeneralName }

and represents the same structure used in the authority information access structure in a X.509 certificate.

In a X.509 certificate the authority information access extension is identified by the OBJECT IDENTIFIER value 1.3.6.1.5.5.7.1.1 (id-pe-authorityInfoAccess) and, in the case of OCSP, the accessMethod will be set to the OBJECT IDENTIFIER id-ad-ocsp, which equates to 1.3.6.1.5.5.7.48.1 if the accessLocation in the AccessDescription structure is referring to an OCSP responder.

At this point you have covered the basics of OCSP request generation. Take a look at an example.

Try It Out: OCSP Request Generation

This example generates an unsigned OCSP request that incorporates a nonce extension into the request being generated. As you would expect, it also demonstrates use of the OCSPReqGenerator class to create the request. Have a look at the code and try running it.

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.Vector; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.ocsp.CertificateID; import org.bouncycastle.ocsp.OCSPException; import org.bouncycastle.ocsp.OCSPReq; import org.bouncycastle.ocsp.OCSPReqGenerator; import org.bouncycastle.ocsp.Req; /** * Example of unsigned OCSP request generation. */ public class OCSPClientExample { public static OCSPReq generateOCSPRequest( X509Certificate issuerCert, BigInteger serialNumber) throws OCSPException { // Generate the id for the certificate we are looking for CertificateID id = new CertificateID( CertificateID.HASH_SHA1, issuerCert, serialNumber); // basic request generation with nonce OCSPReqGenerator gen = new OCSPReqGenerator(); gen.addRequest(id); // create details for nonce extension BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis()); Vector oids = new Vector(); Vector values = new Vector(); oids.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); values.add(new X509Extension( false, new DEROctetString(nonce.toByteArray()))); gen.setRequestExtensions(new X509Extensions(oids, values)); return gen.generate(); } public static void main(String[] args) throws Exception { // create certificates and CRLs KeyPair rootPair = Utils.generateRSAKeyPair(); KeyPair interPair = Utils.generateRSAKeyPair(); X509Certificate rootCert = Utils.generateRootCert(rootPair); X509Certificate interCert = Utils.generateIntermediateCert( interPair.getPublic(), rootPair.getPrivate(), rootCert); OCSPReq request = generateOCSPRequest( rootCert, interCert.getSerialNumber()); Req[] requests = request.getRequestList(); for (int i = 0; i != requests.length; i++) { CertificateID certID = requests[i].getCertID(); System.out.println("OCSP Request to check certificate number " + certID.getSerialNumber()); } } }

Running the example produces the following output:

OCSP Request to check certificate number 1

indicating that you have created an OCSP request containing a single request to verify certificate number 1 ”the serial number of interCert .

How It Works

Generating a basic OCSP request is fairly simple. First, you collect the certificate IDs of the certificates you want to use. Then you bundle them up in a request, possibly adding some extensions, and send the request off.

The generateOCSPRequest() method of the example basically follows this process. The initial line

CertificateID id = new CertificateID( CertificateID.HASH_SHA1, issuerCert, serialNumber);

creates a certificate ID calculated using the SHA-1 algorithm.

After that, the ID generated is added to the sequence of Request structures in the requestList of the TBSRequest structure that forms the body of the OCSP request using gen.addRequest(). In this case there are no extensions required in the Request structure; if there were, I would have used the addRequest() method that takes a X509Extensions object as well, as in:

gen.addRequest(id, requestExtensions );

where requestExtensions would be appropriately set with the extensions I wanted to appear in the singleRequestExtensions field in the Request structure.

The last step before generation is to add the extensions I want present in the TBSRequest structure. In the example I have added a nonce extension, which as you probably recall should just contain some value for the responder to echo back to you. In my case I've just used the current time, but any value that will be unique over a particular period will do here. The idea of the nonce is to allow you to match requests to responses, as well as identify duplicate responses if you see them.

Finally, you generate the request by calling gen.generate(), which returns an unsigned OCSP request. If you were generating a signed request, you would have to use a generate() method that takes the details for the signature and, possibly, the optional certificate chain. If you were doing this, the return statement would look more like:

return gen.generate( "SHA256WithRSA", requestorPrivateKey , requestorX509Chain , "BC");

where requestorPrivateKey and requestorX509Chain represent the private key for the requestor and the certificates required to correctly verify the signature.

That's it for generating a request. Now you'll look at what comes back from an OCSP responder.

The OCSPResp Class

The org.bouncycastle.ocsp.OCSPResp class is an implementation of the ASN.1 type OCSPResponse defined in RFC 2560. As its name implies, the OCSPResponse object provides the data structure that defines a valid OCSP response. It has the following ASN.1 structure:

OCSPResponse ::= SEQUENCE { responseStatus OCSPResponseStatus, responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } OCSPResponseStatus ::= ENUMERATED { successful (0), --Response has valid confirmations malformedRequest (1), --Illegal confirmation request internalError (2), --Internal error in issuer tryLater (3), --Try again later --(4) is not used sigRequired (5), --Must sign the request unauthorized (6) --Request unauthorized }

In the event that the responseStatus field indicates successful, the answers to the OCSP requests that have being processed are in the ResponseBytes structure, which is defined as follows:

ResponseBytes ::= SEQUENCE { responseType OBJECT IDENTIFIER, response OCTET STRING }

where the OCTET STRING in the response field should contain the DER encoding of some detailed response object that has been created by the server.

The OCSPResp class is normally constructed using a byte array representing the DER encoding of the OCSP response or, if you are an OCSP responder, using an object of the type OCSPRespGenerator. Functionally it consists of two get() methods: getStatus() returns an object representing the responseStatus field, and getResponseObject() returns an object that should be cast to the appropriate object that represents the ASN.1 structure encoded in the response field of ResponseBytes.

Although the use of the ResponseBytes structure provides quite a bit of flexibility when it comes to responders deciding how best to provide information back to clients, RFC 2560 defines a basic response structure that everyone should be able to support. Basic OCSP responders return a DER encoding of a BasicOCSPResponse structure in the response field of the ResponseBytes structure. That this is the type of the response is indicated by the responseType in the ResponseBytes structure being set to id-pkix-ocsp-basic, which has the following value:

id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }

The BasicOCSPResponse structure itself is defined as:

BasicOCSPResponse ::= SEQUENCE { tbsResponseData ResponseData, signatureAlgorithm AlgorithmIdentifier, signature BIT STRING, certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }

As you can see, this is very similar in layout to the OCSPRequest structure, but the signature is not optional. The certs field still is optional, but in this case it is more likely to be present, as an OCSP responder would be likely to renew its signing key frequently. The result is that the OCSP client would normally expect to have a "permanent" root certificate for the OCSP responder, which would then be used to verify the chain stored in the certs field ”the target certificate of which would be used to verify the signature on the response.

API support for the BasicOCSPResponse structure is provided in the Bouncy Castle API by the BasicOCSPResp class, and in the normal case, it is a BasicOCSPResp object that will be returned by the OCSPResp.getResponseObject() method.

The BasicOCSPResp Class

Other than obtaining a BasicOCSPResp object using the getResponseObject() method on an OCSPResp object, if you are an OCSP responder, an object of the type org.bouncycastle.ocsp.BasicOCSPResp can also be created using a generator class ”in this case the BasicOCSPRespGenerator.

You'll now look at the methods on the BasicOCSPResp class now.

BasicOCSPResp.get TBSResponseData()

The getTBSResponseData() method returns a byte array representing the DER-encoded ResponseData structure. The ResponseData structure is defined in RFC 2560 as follows:

ResponseData ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, responderID ResponderID, producedAt GeneralizedTime, responses SEQUENCE OF SingleResponse, responseExtensions [1] EXPLICIT Extensions OPTIONAL }

As you can see, the responseExtensions field in the ResponseData structure is a tagged version of the Extensions type, so the BasicOCSPResp class also implements the java.security.cert.X509Extension interface to provide access to the extensions contained in the response.

BasicOCSPResponse.get Version()

The getVersion() method returns an int representing the version number of the ResponseData structure that was contained in the BasicOCSPResponse. At the moment you would expect this method to return the value 1.

BasicOCSPResponse.get ResponderID()

The getResponderID() returns a value object containing a ResponderID structure.

The ResponderID structure is defined as follows:

ResponderID ::= CHOICE { byName [1] Name, byKey [2] KeyHash } KeyHash ::= OCTET STRING

In the event that the ResponderID is not an X.500 name, the key hash is calculated in the same way as the issuerKeyHash field in a CertID; only in this case, it is the public key of the entity signing the response that is used to calculate the hash, not the issuer of the original certificate.

BasicOCSPResponse.get ProducedAt()

The getProducedAt() method returns a Date representing the time at which the response was signed by the OCSP responder.

BasicOCSPResponse.get Responses()

The getResponses() method returns an array of SingleResp objects that represent the responses to the requests about individual certificates that were contained in the OCSP request.

The SingleResponse structure has the following ASN.1 definition:

SingleResponse ::= SEQUENCE { certID CertID, certStatus CertStatus, thisUpdate GeneralizedTime, nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, singleExtensions [1] EXPLICIT Extensions OPTIONAL }

and the SingleResp class provides the methods getCertID(), getCertStatus(), getThisUpdate(), and getNextUpdate() to return the values of the various fields as well implementing the java.security.cert.X509Extension interface to give access to the various extensions the singleExtensions field might contain. The getCertStatus() method returns a null if the certificate is okay; otherwise , it returns an object detailing the issues with the certificate. Both getThisUpdate() and getNextUpdate() return Date representations of the GeneralizedTime contained in the SingleReponse structure.

The getThisUpdate() method returns the time at which the status reported was known to be correct. If getNextUpdate() returns null, meaning the nextUpdate field is not present, then the responder is indicating that newer status information is always available for the certificate. Otherwise, if it is set and the local system time is outside the interval between the return values of getThisUpdate() and getNextUpdate(), then the response should be considered unreliable and another status request should be made.

OCSP Response Extensions

Response extensions can appear in both the responseExtensions field in the TBSResponse structure and the singleExtensions field. Apart from the nonce extension, which you have already seen and should be included in the responseExtensions field of any response to a request that contained a nonce extension, there are several other extensions that you can use in responses.

The CRL References Extension

In some cases, such as for auditing purposes, it can be useful for the OCSP responder to specify the CRL it found a revoked or "on hold" certificate on. This information will be included in the singleExtensions field of the SingleResponse the CRL is associated with, and the CRL may be specified using the URL at which the CRL is available, the CRL number, or the time at which the relevant CRL was created.

The extension is identified by the following identifier as the extension type:

id-pkix-ocsp-crl OBJECT IDENTIFIER ::= { id-pkix-ocsp 3 }

and contains the following structure as the extension value:

CrlID ::= SEQUENCE { crlUrl [0] EXPLICIT IA5String OPTIONAL, crlNum [1] EXPLICIT INTEGER OPTIONAL, crlTime [2] EXPLICIT GeneralizedTime OPTIONAL }

The Archive Cutoff Extensions

In the case of a regular CRL, an expired certificate is normally removed from the certificate list it is contained in. In OCSP, a responder may choose to maintain a certificate's information past the expiry date of the certificate to enable its clients to help revalidate signatures verified by certificates well past their expiration date. The date obtained by subtracting the retention interval from the date of particular response is defined as the certificate's "archive cutoff" date.

OCSP responders that provide support for this should include the cutoff date in the singleExtensions for each SingleResponse they generate. The extension is identified by the object identifier:

id-pkix-ocsp-archive-cutoff OBJECT IDENTIFIER ::= { id-pkix-ocsp 6 }

and has the value of the type ArchiveCutoff, which is defined as follows:

ArchiveCutoff ::= GeneralizedTime

X.509 CRL Entry Extensions

Any of the extensions you looked at in the section on X.509 CRL entry extensions can appear in the singleExtensions field in one of the SingleResponse structures making up the ResponseData in a basic OCSP response.

This brings you to the end of the response extensions; it is time for another example.

Try It Out: Generating an OCSP Response

This example is much larger than most of the previous ones you have looked at, so I've broken it up a bit so I can comment on some parts of it as I go. Before you start typing, don't forget you can find the source for the examples on the book's Web site.

This chunk represents the class header. There is not a great deal to say here other than you'll notice that I'm including PrivateKey, as a response must be signed.

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; import java.util.Vector; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.ocsp.*; /** * Example of OCSP response generation. */ public class OCSPResponderExample {

The next chunk provides a definition for the generateOCSPResponse() method:

public static OCSPResp generateOCSPResponse( OCSPReq request, PrivateKey responderKey, PublicKey pubKey, CertificateID revokedID) throws NoSuchProviderException, OCSPException { BasicOCSPRespGenerator basicRespGen = new BasicOCSPRespGenerator(pubKey); X509Extensions reqExtensions = request.getRequestExtensions(); if (reqExtensions != null) { X509Extension ext = reqExtensions.getExtension( OCSPObjectIdentifiers.id_pkix_ocsp_nonce); if (ext != null) { Vector oids = new Vector(); Vector values = new Vector(); oids.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); values.add(ext); basicRespGen.setResponseExtensions( new X509Extensions(oids, values)); } } Req[] requests = request.getRequestList(); for (int i = 0; i != requests.length; i++) { CertificateID certID = requests[i].getCertID(); // this would normally be a lot more general! if (certID.equals(revokedID)) { basicRespGen.addResponse(certID, new RevokedStatus(new Date(), CRLReason.privilegeWithdrawn)); } else { basicRespGen.addResponse(certID, CertificateStatus.GOOD); } } BasicOCSPResp basicResp = basicRespGen.generate( "SHA256WithRSA", responderKey, null, new Date(), "BC"); OCSPRespGenerator respGen = new OCSPRespGenerator(); return respGen.generate(OCSPRespGenerator.SUCCESSFUL, basicResp); }

As you can see, the method takes the request being processed, the responder's private and public key, and the certificate ID of the certificate that is regarded as revoked. The method body is a lot simpler than it would be in the real world, as you would normally be looking up a CRL or some other data structure to find out exactly what the story with a certificate contained in a particular request is about. However, it does give you enough to be able to play with the OCSP response generation, so it will serve the purposes here.

The next chunk you look at is just a method that returns a status message when passed a responder key pair, the CA certificate, a serial number for a certificate issued by the CA that has been revoked, and the X.509 certificate you actually want to check. The method does this by generating the appropriate request for the certificate you want to check, generating a response based on the request, and then evaluating the response and returning the appropriate String .

Here's the code for the method. As you can see, the OCSP request is being generated using the generateOCSPRequest() method you defined in the Try It Out, "OCSP Request Generation."

public static String getStatusMessage( KeyPair responderPair, X509Certificate caCert, BigInteger revokedSerialNumber, X509Certificate cert) throws Exception { OCSPReq request = OCSPClientExample.generateOCSPRequest( caCert, cert.getSerialNumber()); CertificateID revokedID = new CertificateID( CertificateID.HASH_SHA1, caCert, revokedSerialNumber); OCSPResp response = generateOCSPResponse( request, responderPair.getPrivate(), responderPair.getPublic(), revokedID); BasicOCSPResp basicResponse = (BasicOCSPResp)response.getResponseObject(); // verify the response if (basicResponse.verify(responderPair.getPublic(), "BC")) { SingleResp[] responses = basicResponse.getResponses(); byte[] reqNonce = request.getExtensionValue( OCSPObjectIdentifiers.id_pkix_ocsp_nonce.getId()); byte[] respNonce = basicResponse.getExtensionValue( OCSPObjectIdentifiers.id_pkix_ocsp_nonce.getId()); // validate the nonce if it is present if (reqNonce == null Arrays.equals(reqNonce, respNonce)) { String message = ""; for (int i = 0; i != responses.length; i++) { message += " certificate number " + responses[i].getCertID().getSerialNumber(); if (responses[i].getCertStatus() == CertificateStatus.GOOD) { return message + " status: good"; } else { return message + " status: revoked"; } } return message; } else { return "response nonce failed to validate"; } } else { return "response failed to verify"; } }

Finally, you have the main driver and trailing brace to close off the class definition. The main driver generates some keys and then checks the status of the intermediate certificate, printing the resulting message:

public static void main( String[] args) throws Exception { KeyPair rootPair = Utils.generateRSAKeyPair(); KeyPair interPair = Utils.generateRSAKeyPair(); X509Certificate rootCert = Utils.generateRootCert(rootPair); X509Certificate interCert = Utils.generateIntermediateCert( interPair.getPublic(), rootPair.getPrivate(), rootCert); System.out.println( getStatusMessage(rootPair, rootCert, BigInteger.valueOf(1), interCert)); } }

Try running the example; you should see the following output:

certificate number 1 status: revoked

showing that after the request generation and response processing, interCert has turned out to be revoked.

If you modify the main driver to use BigInteger.valueOf(2), rather than BigInteger.valueOf(1), you get the following output instead:

certificate number 1 status: good

since revokedID would no longer match the CertificateID for interCert.

How It Works

As you can see, there are two new bodies of functionality in the example. The first is the generation of the response based on the inputs passed in. The second is the validation and processing of the response with the generation of the appropriate response message.

The first stage is carried out in the generateOCSPResponse() method and commences with the construction of the BasicOCSPRespGenerator object as follows:

BasicOCSPRespGenerator basicRespGen = new BasicOCSPRespGenerator(pubKey);

The generator is created to generate the BasicOCSPResponse structure that gets encoded in the response field of the ResponseBytes structure ”the actual structure that appears in the OCSPResponse structure that forms the real OCSP response sent back to the client. As you can see, it takes the responder's public key as a parameter to its constructor. It does this because part of the construction process involves creating a ResponderID that will be included in the ResponseData structure in the basic response.

The next step of the example uses the OCSPReq.getExtensions() method to get the contents of the requestExtensions field if it is present in the request. If it turns out there are extensions, it checks the X509Extensions object that was returned to see whether it contains a nonce extension. If this is also the case, another X509Extensions object containing a copy of the nonce extension found in the request is created and then passed to the basicRespGen.setResponseExtensions() method so that it will be present in the responseExtensions field of the ResponseData structure contained in the basic response.

Having gotten this far, the method then iterates through the individual certificate requests that were contained in the request object, and if one of the certificate requests is for a CertificateID that matches the revokedID object, a response is added indicating the certificate has been revoked. Otherwise, a response is added indicating the certificate is "good," or not revoked. Individual certificate responses can also contain extensions. Had this been done, say, for a certificate response indicating "good," rather than seeing

basicRespGen.addResponse(certID, CertificateStatus.GOOD);

you would have seen

basicRespGen.addResponse( certID, CertificateStatus.GOOD, singleResponseExtensions );

where the singleResponseExtensions is an X509Extensions object containing the appropriate extension for the individual certificate response.

Finally, the signed basic response is generated using the call to basicRespGen.generate(), and this is, in turn , wrapped in an OCSPResp object that's generated using the OCSPRespGenerator object respGen by calling respGen.generate(). The basicRespGen.generate() method specifies the signature algorithm to use and provides the Date object that will be used to fill in the producedAt field in the ResponseData structure. In this case the certificate chain for validating the signature has been left out of the message, as it is optional and it is known that the "client" already has the necessary certificate in its possession.

The second and final stage of the example is carried out in the getStatusMessage() method. After the request is created and the response retrieved, the BasicOCSPResponse is retrieved from response by calling response.getResponseObject() and casting appropriately.

After retrieving the basic response into basicResponse, the signature on the object is verified using the public key of the responder. If the signature verifies and a nonce extension was present in the request, the nonce extension value inside the response is checked against it to see whether they are equal. If both these tests pass, the code then iterates through the individual certificate responses building a message string with the certificate's serial number and "good" or "revoked," depending on how the SingleResp.getCertStatus() method evaluates on the given response, and the message string is returned. In the event something goes wrong, an error message is returned instead.

The returned message is then printed out using the main driver, bringing you to the end of the example code.

One further point on this: The CA certificate is used both to validate the issued certificate and the OCSP response. Although this is acceptable, CAs will delegate the responsibility for validating a response to another certificate and use a different private key. If this is the case, then validating the OCSP response also means confirming that the certificate used for validating the OCSP response was issued by the CA and that it contains an extended key usage extension containing the following OID:

id-kp-OCSPSigning OBJECT IDENTIFIER ::= {id-kp 9}

indicating that the CA signing it signed it for the purpose of signing OCSP responses. If you do not find this OID or you do and the CA did not issue the certificate, the response you are getting is, at best, invalid, or, at worst, fraudulent, so it is important to include a check for this as well.

Certificate Path Validation

As I mentioned at the start of the chapter, the issue of knowing whether a particular certificate path is valid is more than just knowing that each certificate on the path has a signature you can validate. Knowing whether a path is valid is also about whether the certificates you have are being used in the manner intended. For example, a certificate authorized to be used to encrypt secret keys might not be appropriate for validating the signature on a piece of executable code.

Certificate path validation algorithms are provided to make sure the certificate is valid in all these senses. Generally , in addition to verifying the signature on the certificate, path validation algorithms also verify that the extensions are appropriate and that if any are critical, they have been correctly processed . An implementation of a path validation algorithm should fail if it finds an extension marked critical on a certificate in a path it is trying to validate.

As with most interpretation issues related to X.509 certificates, how path validation is done is dependent on what profile you use. In this case, the most relevant one is in Section 6 of RFC 3280, the PKIX profile, which covers a validation mechanism for working out whether a path starting with an end entity certificate you would like to trust leads you back to a certificate you are already prepared to trust.

The certificate you are prepared to trust is also often referred to as a trust anchor , and I will start the journey through the JCA classes for path validation by looking at the class that encapsulates this first.

The TrustAnchor Class

The java.security.cert.TrustAnchor class provides a container for a public key that you have to trust in order to be able to validate a certificate path.

There are three constructors for the TrustAnchor class. All of them can be supplied with optional name constraints via a byte array containing the DER-encoded value of a X.509 NameConstraints extension structure. After that, it is a question of whether you want to make use of a certificate containing the public key and use the constructor that takes an X509Certificate, or if you have to use the public key and an identifying X.500 Name to identify the trust anchor instead. As with most other classes that take X.500 Names , passing the identifying X.500 Name can be done by either passing an X500Principal or a String version of the DN.

The NameConstraints extension is talked about at length in RFC 3280 in section 4.2.1.11, so I will not go into full details here; however, if it is present in a certificate, it is identified by the OID 2.5.29.30 (id-ce-nameConstraints) and its value represents the following ASN.1 structure:

NameConstraints ::= SEQUENCE { permittedSubtrees [0] GeneralSubtrees OPTIONAL, excludedSubtrees [1] GeneralSubtrees OPTIONAL } GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree GeneralSubtree ::= SEQUENCE { base GeneralName, minimum [0] BaseDistance DEFAULT 0, maximum [1] BaseDistance OPTIONAL } BaseDistance ::= INTEGER (0..MAX)

RFC 3280 fixes the field minimum at zero, its default value, and specifies that the maximum should be absent, leaving you only with the base field, which is of the type GeneralName, to contend with in the GeneralSubtrees sequences. Acertificate is only regarded as valid if its subject, and subject alternative name if present, does not match any of the values in the excludedSubtrees if present, and is in the permittedSubtrees if present. How matching gets done depends a little on the profile you are using, but by way of example from RFC 3280, if the base field in the permittedSubtrees sequence is a URI of the value .bouncycastle.org it will match any subdomain of bouncycastle.org. Note this would not match the host bouncycastle.org that would have to be matched by having another URI present containing just bouncycastle.org . As you can imagine, the same applies for e-mail addresses, DNS names, IP addresses, and so on.

Note also that it is only the DER-encoded value of the NameConstraints structure that should be passed to the constructor of the TrustAnchor class ”not Extension object, or the encoding of the OCTET STRING that carries the extension value.

The PKIXParameters Class

The java.security.cert.PKIXParameters class implements the java.security.cert.CertPathParameters interface, which is a marker interface that also adds the requirement that any class implementing it should be clonable.

There are two ways of constructing a PKIXParameters object. The first is by passing a Set containing one or more TrustAnchor objects; the other is to pass a java.security.KeyStore containing the certificates associated with entities you want to trust. I will be discussing the KeyStore class in Chapter 8. So for the examples in this chapter, I will concentrate on the constructor taking the Set parameter. As you will see, the principles are the same in any case.

Having created a PKIXParameters object, you can add a number of optional parameters to the object via set or add methods. If you look at the JavaDoc for this class, you will see there are quite a few options, many that revolve around certificate policy ”which could almost be a book in itself. Although there is a good deal of information on this in RFC 3280, you don't need to deal with it right now, as it will distract from understanding how the path validation mechanisms in Java actually work. For now I will concentrate on the basic methods that you need to get up and running.

PKIXParameters.addCertStore() and PKIXParameters.setCertStores()

The addCertStore() method allows you to add a CertStore to be used for path validation. As an alternative to building up an internal list of CertStore objects in the PKIXParameters object, you can also pass a List of CertStore objects using setCertStores(), which will be used instead. Note that if you use the setCertStores() method, the List you pass in will be copied to prevent further modification.

PKIXParameters.setDate()

The setDate() method takes a single Date and is used to set the time for which the path validation is taking place. If the parameter passed to setDate() is a null value, or setDate() is not called, the validation is assumed to be taking place in the present.

PKIXParameters.setTarget CertConstraints()

The setTargetCertConstraints() method takes a single CertSelector as a parameter and is used to provide the constrains that the end entity, or target, certificate in the path being validated is supposed to satisfy .

In a simple case, where you already believe you have a valid ordered certificate path, it is generally unnecessary to call this method as the default will allow any certificate to match. In the context of path validation, you would use this method if you want to restrict the range of end entity certificates you regard as acceptable.

PKIXParameters.set RevocationEnabled()

The setRevocationEnabled() method takes a single boolean parameter and sets the return value for isRevocationEnabled() to true or false accordingly . The isRevocationEnabled() method is used by the underlying implementation of the path validation algorithm to determine whether you want it to apply the standard revocation checking when path validation is carried out.

By default, the is() method returns true, and if you are using an implementation that offers PKIX validation and you have the necessary CRLs, there is no need to call setRevocationEnabled(). If, on the other hand, you have your own mechanism for dealing with certificate revocation, such as an OCSP implementation, you can use setRevocationEnabled() to turn off the default revocation checking and enable your own using a PKIXCertPathChecker object.

The CertPathValidator Class

The java.security.cert.CertPathValidator class is another JCA class whose implementation is provider-based and is constructed using the getInstance() factory pattern, rather than a direct constructor. Like the other provider-based classes, it follows the precedence rules outlined by the configuration files being used by the JVM. The class has a very simple interface, with two informational methods, getAlgorithm() and getDefaultType(), and a validate() method that is where the actual validation is performed.

CertPathValidator.get DefaultType()

The getDefaultType() method is a static method that returns the value of the certpathvalidator.type property, or PKIX if the property is not set. Like other security properties, if you want to set the value of the certpathvalidator.type property for the JVM, you can do so by setting it in the java.security file.

Using the getDefaultType() method for selecting the algorithm name to use for path validation allows your code to be reconfigured to work with different path validation algorithms by changing a JVM property.

CertPathValidator.get Algorithm()

The getAlgorithm() method returns the name of the profile the validation algorithm is using. For the Internet profile described in RFC 3280, this method will return PKIX .

CertPathValidator.validate()

The validate() method takes two parameters: The first is the CertPath object representing the certificate path being validated, and the second is a PKIXParameters object that provides the trust anchors and other details that might be required during the process of path validation.

The return type of the validate() method is a java.security.cert.CertPathValidatorResult. It is another marker interface that introduces the clone() method. Agiven path validation implementation will return an implementation of the interface that will provide the validation information that is appropriate to the profile being used. For our purposes, as you are using the "PKIX" profile, the validate() method will return a PKIXCertPathValidatorResult.

The PKIXCertPathValidatorResult Class

The java.security.cert.PKIXCertPathValidatorResult class contains methods that allow you to get all the possible outputs of the PKIX certificate path validation algorithm. The major ones are the trust anchor that was used to validate the path you can retrieve using getTrustAnchor() and the public key of the certificate validated by the path you can retrieve using getPublicKey(). If you are dealing with certificates that have policy extensions, as outlined in RFC 3280, you can also get back the policy tree that resulted from validating the path using getPolicyTree().

Try It Out: Validating a Certificate Path

You have enough information now to try validating a certificate path. Have a look at the following class:

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; import java.security.cert.CertPathValidatorResult; import java.security.cert.CertStore; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.*; /** * Basic example of certificate path validation */ public class CertPathValidatorExample { public static void main(String[] args) throws Exception { // create certificates and CRLs KeyPair rootPair = Utils.generateRSAKeyPair(); KeyPair interPair = Utils.generateRSAKeyPair(); KeyPair endPair = Utils.generateRSAKeyPair(); X509Certificate rootCert = Utils.generateRootCert(rootPair); X509Certificate interCert = Utils.generateIntermediateCert( interPair.getPublic(), rootPair.getPrivate(), rootCert); X509Certificate endCert = Utils.generateEndEntityCert( endPair.getPublic(), interPair.getPrivate(), interCert); BigInteger revokedSerialNumber = BigInteger.valueOf(2); X509CRL rootCRL = X509CRLExample.createCRL( rootCert, rootPair.getPrivate(), revokedSerialNumber); X509CRL interCRL = X509CRLExample.createCRL( interCert, interPair.getPrivate(), revokedSerialNumber); // create CertStore to support validation List list = new ArrayList(); list.add(rootCert); list.add(interCert); list.add(endCert); list.add(rootCRL); list.add(interCRL); CollectionCertStoreParameters params = new CollectionCertStoreParameters( list); CertStore store = CertStore.getInstance( "Collection", params, "BC"); // create certificate path CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC"); List certChain = new ArrayList(); certChain.add(endCert); certChain.add(interCert); CertPath certPath = fact.generateCertPath(certChain); Set trust = Collections.singleton(new TrustAnchor(rootCert, null)); // perform validation CertPathValidator validator = CertPathValidator.getInstance("PKIX", "BC"); PKIXParameters param = new PKIXParameters(trust); param.addCertStore(store); param.setDate(new Date()); try { CertPathValidatorResult result = validator.validate(certPath, param); System.out.println("certificate path validated"); } catch (CertPathValidatorException e) { System.out.println("validation failed on certificate number " + e.getIndex() + ", details: " + e.getMessage()); } } }

Run the example, and if nothing has been left out, you will get the following output:

certificate path validated

How It Works

As you can see, the CertPathValidator class pretty well encapsulates the whole process of evaluating a certificate path. Most of the code in the example is related to setting up. There are a few things worth discussing, though.

Have a look at the creation of the CertStore passed in using the PKIXParameters object. As you can see, it contains all the certificates as well as the CRLs for those certificates used to sign others. This is required, as the CertPathValidator implementation uses the CertStore to look up any CRLs or certificates it might need during the process of validating the certificates making up the path. The CRLs are required, because by default, PKIXParameters.isRevocationEnabled() returns true so, consequently, expects to find a valid CRL for any certificate used to sign another certificate in the path, including the trust anchor.

Next , you generate a CertPath to validate using the CertificateFactory.generateCertPath() method. Notice the path does not include the trust anchor. Because of their special role, trust anchors are handled using the TrustAnchor class, and you create a Set of TrustAnchor objects containing the selfsigned root certificate that validates the intermediate certificate in the path.

After creating the CertPath and TrustAnchor objects, you then create a CertPathValidator and a PKIXParameters object based around the Set of TrustAnchor objects. Then it is just a matter of adding the CertStore to the PKIXParameters, assigning a validation date and calling validator.validate() to validate the path. If all goes well, you see the certificate path validated message and you are done.

It is interesting to try prodding the CertPathValidator object to see how it behaves when you change parameters. For example, try commenting out the line

list.add(rootCRL);

and see what happens. Likewise, you could introduce some target constraints on the path by introducing the following lines after the code setting up the param object:

X509CertSelector selector = new X509CertSelector(); selector.setSubject(new X500Principal("CN=No Match")); param.setTargetCertConstraints(selector);

Both cases should cause exceptions to be raised. It's important to think about this, as not all certificate paths you will encounter will be valid, and the only way you'll learn how to deal with this fact is to start experiencing it first-hand. This is one place where a bit of experimentation is a good thing.

If you never add any private critical extensions of your own, or you are content with using CRLs rather than some other revocation mechanism, the standard PKIX CertPathValidator implementation will probably do you quite well. On the other hand, if you want to do any of these things, you need some way of customizing the CertPathValidator. Otherwise , none of your paths will validate correctly.

If you need to customize a CertPathValidator implementation, you can use the PKIXCertPathChecker class, which you look at next.

The PKIXCertPathChecker Class

The java.security.cert.PKIXCertPathChecker class provides you with the ability to introduce your own forms of validation, or other processing, into a CertPathValidator implementation provided by a JCA services provider. PKIXCertPathChecker objects are passed to CertPathValidator implementations using calls to the addCertPathChecker() method or a call to the setCertPathCheckers() method on the PKIXParameters object.

There are a few reasons why you might want to do this. The organization you are working for might have added some critical extensions of its own to X.509 certificates being used internally, so validating a CertPath will involve dealing with these new extensions as well. Otherwise, the provider-based validation will fail, as it cannot recognize the extensions. Alternatively, the CertPathValidator you are relying on might expect to use CRLs, as the standard PKIX one does, and you want to use OCSP instead. Whatever the reason, if you need to customize an existing CertPathValidator, PKIXCertPathChecker is the class you need to use.

The PKIXCertPathChecker class is abstract and has four methods on it that subclasses are expected to implement before they can be used with the CertPathValidator. The methods are init(),isForwardCheckingSupported(),getSupportedExtensions(), and check(), and all implementations of PKIXCertPathChecker are expected to be clonable as well.

PKIXCertPathChecker.init()

The init() method takes a single boolean as a parameter and is used to initialize the state of the hecker. The value passed in will be true if the checker is being initialized for forward checking, false otherwise. Which direction the checking goes in is at the discretion of the writer of the path validation implementation being used, but for the purposes of path validation, forward means that the certificates will be presented in order from the end entity certificate and progress toward the trust anchor.

If the value passed to init is false, the certificates will be presented in reverse direction from the trust anchor to the target.

PKIXCertPathChecker.is ForwardCheckingSupported()

The isForwardCheckingSupported() should return true if the checker supports forward direction processing. All checkers must support reverse processing.

PKIXCertPathChecker.get SupportedExtensions()

The getSupportedExtensions() method returns an immutable Set of String objects representing the OIDs of the X.509 extensions that the checker implementation can handle. If the checker does not handle any specific extensions, getSupportedExtensions() should return null.

PKIXCertPathChecker.check()

The check() method takes two parameters. The first is a Certificate object representing the certificate to be checked. The second is a mutable Set containing the String objects representing the OIDs of the critical X.509 extensions that the CertPathValidator has not been able to resolve.

The check() method is where all the processing gets done. If the purpose of your check method is to perform some validation on the certificate passed in, you should throw a CertPathValidatorException if the certificate fails to pass your tests. If the checking is applied to one or more X.509 extensions, then you should remove the String OIDs from the Set that was passed in as the second parameter. Removing the OIDs will signal to the CertPathValidator that the extensions have been dealt with ”the idea being that once the CertPathValidator has done its own processing and called all the PKIXCertPathChecker implementations, it has been given its internal reference to the Set should indicate the Set is empty.

Try It Out: Using a PKIXCertPathChecker

This following example combines the simple OCSP classes you looked at earlier with a PKIXCertPathChecker to do revocation checking using the PKIXCertPathChecker. As it's rather large, I've broken it up into two parts : The first is a class providing an extension of the PKIXCertPathChecker class, PathChecker, which interacts with the OCSP classes, and the second is just the main driver for it.

As I'm still essentially using the CertPathValidator for driving the process, most of the new code is in the PathChecker class, which appears here:

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.CertPathValidatorException; import java.security.cert.Certificate; import java.security.cert.PKIXCertPathChecker; import java.security.cert.X509Certificate; import java.util.*; class PathChecker extends PKIXCertPathChecker { private KeyPair responderPair; private X509Certificate caCert; private BigInteger revokedSerialNumber; public PathChecker( KeyPair responderPair, X509Certificate caCert, BigInteger revokedSerialNumber) { this.responderPair = responderPair; this.caCert = caCert; this.revokedSerialNumber = revokedSerialNumber; } public void init(boolean forwardChecking) throws CertPathValidatorException { // ignore } public boolean isForwardCheckingSupported() { return true; } public Set getSupportedExtensions() { return null; } public void check(Certificate cert, Collection extensions) throws CertPathValidatorException { X509Certificate x509Cert = (X509Certificate)cert; try { String message = OCSPResponderExample.getStatusMessage( responderPair, caCert, revokedSerialNumber, x509Cert); if (message.endsWith("good")) { System.out.println(message); } else { throw new CertPathValidatorException(message); } } catch (Exception e) { throw new CertPathValidatorException( "exception verifying certificate: " + e, e); } } }

You can see that the first three methods in the class are just to provide implementations for the abstract methods required by the parent class. In this case, because of the simple nature of what you are doing, the only real work is being done in the check() method, which is just using the method you defined in the OCSP responder Try It Out ("Generating an OCSP Response").

The main driver, on the other hand, should look quite familiar to you and is listed here:

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.*; import java.util.*; /** * Basic example of certificate path validation using a PKIXCertPathChecker */ public class CertPathValidatorWithCheckerExample { public static void main(String[] args) throws Exception { // create certificates and CRLs KeyPair rootPair = Utils.generateRSAKeyPair(); KeyPair interPair = Utils.generateRSAKeyPair(); KeyPair endPair = Utils.generateRSAKeyPair(); X509Certificate rootCert = Utils.generateRootCert(rootPair); X509Certificate interCert = Utils.generateIntermediateCert( interPair.getPublic(), rootPair.getPrivate(), rootCert); X509Certificate endCert = Utils.generateEndEntityCert( endPair.getPublic(), interPair.getPrivate(), interCert); BigInteger revokedSerialNumber = BigInteger.valueOf(2); // create CertStore to support validation List list = new ArrayList(); list.add(rootCert); list.add(interCert); list.add(endCert); CollectionCertStoreParameters params = new CollectionCertStoreParameters( list); CertStore store = CertStore.getInstance( "Collection", params, "BC"); // create certificate path CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC"); List certChain = new ArrayList(); certChain.add(endCert); certChain.add(interCert); CertPath certPath = fact.generateCertPath(certChain); Set trust = Collections.singleton(new TrustAnchor(rootCert, null)); // perform validation CertPathValidator validator = CertPathValidator.getInstance("PKIX", "BC"); PKIXParameters param = new PKIXParameters(trust); param.addCertPathChecker(new PathChecker( rootPair, rootCert, revokedSerialNumber)); param.setRevocationEnabled(false); param.addCertStore(store); param.setDate(new Date()); try { CertPathValidatorResult result = validator.validate(certPath, param); System.out.println("certificate path validated"); } catch (CertPathValidatorException e) { System.out.println("validation failed on certificate number " + e.getIndex() + ", details: " + e.getMessage()); } } }

As you can see there are a couple of differences from previous uses of the CertPathValidator in this case. First, because you are relying on the PathChecker to do the revocation checking for you, there are no CRLs. Second, you have to create the PathChecker and make it available to the CertPathValidator by calling the PKIXParameters.addCertPathChecker() method on the param object.

Run the example and you should see the following output:

certificate number 1 status: good certificate number 1 status: good certificate path validated

indicating that the two certificates forming the path back to the trust anchor are not on the OCSP revocation list. Again, you might want to change revokedSerialNumber to be BigInteger.valueOf(1) rather than BigInteger.valueOf(2) and see what happens when the certificates are revoked .

How It Works

The PathChecker object is able to do its job, as the CertPathValidator calls it for each certificate it has to validate in the path. Because the check() method on the PKIXCertPathValidator class is defined as throwing a CertPathValidatorException, the PathChecker object is able to interrupt the validation process as soon as it finds something wrong.

As you can see, the definition of the PathChecker object is fairly straightforward. Because all it is doing is revocation checking, it is able to override all the abstracts methods other than the check() method with methods that just return default values or do nothing. In the case of the check() method, the return value of the OCSPResponderExample.getStatusMessage() is examined and then, providing the status ends with the message "good," a trace message is printed. Otherwise, a CertPathValidatorException is thrown, indicating that a problem was discovered with the passed in certificate.

The only change required to introduce the use of the PathChecker object into the CertPathValidator is the calling of param.addCertPathChecker() to enable its use. The line

param.setRevocationEnabled(false);

is used to tell the CertPathValidator implementation not to expect to use CRLs, as some other revocation mechanism has been enabled ”in this case the one performed by the PathChecker object.

Now you have one thing left to cover in this chapter: how to construct a valid certificate path from a random collection of X.509 certificates and CRLs.

Building a Valid Path from a CertStore

Being able to do validation is all very well, but as you will see in coming chapters, often certificate chains do not arrive well ordered and a collection of certificates and CRLs that arrive with a particular message may even include certificates and CRLs that are not relevant to the particular certificate you are trying to verify. Fortunately, the JCA includes a certificate path builder that works in conjunction with the CertPathValidator to allow you to easily create the certificate path that gets you from the root certificate you trust to the end entity certificate you are trying to validate. The class for doing this is the CertPathBuilder that returns an object implementing a CertPathBuilderResult, an interface that provides a single method, getCertPath(), which allows you to retrieve the constructed CertPath object.

The CertPathBuilder Class

Objects of the java.security.cert.CertPathBuilder class are created using the same getInstance() factory pattern that is followed with the other JCA classes that have underlying implementations implemented by providers. It has a single build() method that will construct a validated CertPath from the parameter information it is provided. The build() method takes a class implementing the java.security.cert.CertPathParameters interface and returns a java.security.cert.CertPathBuilderResult.

Like most of the parameter classes involved in certificate paths, the CertPathParameters interface just forces the introduction of the clone() method in implementations of it. The real functionality, and the methods that support it, are provided in classes that implement the interface. From the point of view of this chapter, the most important one of these is the one supplied with the JCA, the PKIXBuilderParameters class.

The PKIXBuilderParameters Class

The java.security.cert.PKIXBuilderParameters is an extension of the PKIXParameters class you looked at earlier. It adds one extra set() method, setMaxPathLength(), which takes a single int as a parameter and limits the number of non-self-issued certificates that can be put into a path. The main addition the PKIXBuilderParameters class makes to the PKIXParameters class it extends is in the manner of its construction. In addition to the trust anchors required to construct the PKIXParameters, the PKIXBuilderParameters also takes a CertSelector specifying the target constraints for the end entity certificate the path is to be built to.

Using the extra information the target constraints provide, the CertPathBuilder then tries to construct a valid path leading from the end entity certificate to one of the trust anchors that the PKIXBuilderParameters indicates the caller of the build() method is prepared to trust.

Try It Out: Building a Certificate Path Using CertPathBuilder

Here is an example of a simple path build done using the certificates that are provided by the Utils class as source material. Have a look at it and see what it does.

package chapter7; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.CertPath; import java.security.cert.CertPathBuilder; import java.security.cert.CertStore; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.PKIXCertPathBuilderResult; import java.security.cert.TrustAnchor; import java.security.cert.X509CRL; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.*; /** * Basic example of the use of CertPathBuilder. */ public class CertPathBuilderExample { public static void main(String[] args) throws Exception { // create certificates and CRLs KeyPair rootPair = Utils.generateRSAKeyPair(); KeyPair interPair = Utils.generateRSAKeyPair(); KeyPair endPair = Utils.generateRSAKeyPair(); X509Certificate rootCert = Utils.generateRootCert(rootPair); X509Certificate interCert = Utils.generateIntermediateCert( interPair.getPublic(), rootPair.getPrivate(), rootCert); X509Certificate endCert = Utils.generateEndEntityCert( endPair.getPublic(), interPair.getPrivate(), interCert); BigInteger revokedSerialNumber = BigInteger.valueOf(2); X509CRL rootCRL = X509CRLExample.createCRL( rootCert, rootPair.getPrivate(), revokedSerialNumber); X509CRL interCRL = X509CRLExample.createCRL( interCert, interPair.getPrivate(), revokedSerialNumber); // create CertStore to support path building List list = new ArrayList(); list.add(rootCert); list.add(interCert); list.add(endCert); list.add(rootCRL); list.add(interCRL); CollectionCertStoreParameters params = new CollectionCertStoreParameters( list); CertStore store = CertStore.getInstance( "Collection", params, "BC"); // build the path CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC"); X509CertSelector endConstraints = new X509CertSelector(); endConstraints.setSerialNumber(endCert.getSerialNumber()); endConstraints.setIssuer(endCert.getIssuerX500Principal().getEncoded()); PKIXBuilderParameters buildParams = new PKIXBuilderParameters( Collections.singleton(new TrustAnchor(rootCert, null)), endConstraints); buildParams.addCertStore(store); buildParams.setDate(new Date()); PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult)builder.build(buildParams); CertPath path = result.getCertPath(); Iterator it = path.getCertificates().iterator(); while (it.hasNext()) { System.out.println( ((X509Certificate)it.next()).getSubjectX500Principal()); } System.out.println( result.getTrustAnchor().getTrustedCert().getSubjectX500Principal()); } }

Try running the example and you should see the following output:

CN=Test End Certificate CN=Test Intermediate Certificate CN=Test CA Certificate

showing that you have created a path from the certificate with the subject DN CN=Test End Certificate going back to the trust anchor with the subject DN CN=Test CA Certificate .

How It Works

As you can see, the setup for this example is very similar to the previous ones; the real differences arise in the setting up of the PKIXBuilderParameters class and when you go to make use of the CertPathBuilder class.

Looking at the setup of the PKIXBuilderParameters class, you can see it goes through two stages. The first is where you specify the target constraints for the end entity certificate you are looking for. To do this, you uniquely specify the end entity certificate by using its serial number and issuer:

X509CertSelector endConstraints = new X509CertSelector(); endConstraints.setSerialNumber(endCert.getSerialNumber()); endConstraints.setIssuer(endCert.getIssuerX500Principal().getEncoded());

Having set up the constraints, you then use them and the Set of TrustAnchor objects to create a PKIXBuilderParameters object buildParams . After that it is a matter of adding the CertStore providing the source of certificates and CRLs to buildParams and letting the CertPathBuilder do its job.

Note that the path returned by result.getCertPath() does not contain the trust anchor. It is present in the PKIXCertPathBuilderResult object result, but because of its special role is only accessible using the result.getTrustAnchor() method.

This brings you to the end of the discussion of revocation and path validation as it applies to X.509 certificates.

Summary

In this chapter, you looked at the fundamentals of dealing with the validation and verification of X.509 certificates using the PKIX profile outlined in RFC 3280. You have seen two alternative mechanisms for checking whether the issuers have revoked their certificates, and you have also seen the JCA classes for supporting certificate path validation and certificate path building.

Over the course of this chapter, you have learned the following:

Finally, you also learned how to take a random collection of certificates and CRLs in a CertStore and create a certificate path that is valid for an end entity certificate that you need to use.

At this stage, you are able to build private keys and certificate chains for validating them. You have also seen in earlier chapters how to generate symmetric keys as well as how to encrypt private keys using them. As you might imagine, in some situations these all represent objects you might want to store somewhere safely for later retrieval or, possibly, import into another application. In the next chapter, you look at how this problem is solved using the KeyStore class, as well as learn which types of KeyStore implementations are suited to being used with particular applications and how they can be exported or imported as appropriate.

Exercises

1.  

In an application where you are expecting a large number of unexpired revoked certificates to be present at any one time, as well as a large number of clients, what is the most appropriate certificate checking mechanism to deploy on the application clients ”regular CRLs or OCSP?

2.  

If you have to distribute CRLs in the case detailed in Exercise 1, what is one way you can reduce the cost of distributing updates? What is required to support this?

3.  

How can you introduce validation of locally defined critical extensions into a PKIX CertPathValidator without having to change the underlying CertPathValidator provider?

4.  

If you are using a PKIX CertPathBuilder to construct a certificate path for a given target certificate, what is the most straightforward way to construct the target constraints required for the path builder parameters?

Answers

1.  

In an application where you are expecting a large number of unexpired revoked certificates to be present at any one time, as well as a large number of clients, what is the most appropriate certificate checking mechanism to deploy on the application clients: regular CRLs or OCSP?

OCSP. You are most likely to encounter problems with regular CRLs, as the size of the lists being distributed may easily become prohibitive, and having a large number of clients all trying to update their CRLs at the same time may result in severe performance degradation on the CRL server, with the usual unpleasant consequences.

2.  

If you have to distribute CRLs in the case detailed in Exercise 1, what is one way you can reduce the cost of distributing updates? What is required to support this?

You can distribute deltas as updates, rather than sending out a new CRL. If you do this, the complete CRLs you send out need to have the CRLNumber extension present in them and the delta CRLs you send out need to use the DeltaCRLIndicator extension. Bear in mind that you need to have an algorithm, such as the one given in RFC 3280, for adding the deltas to the earlier complete CRL that was sent.

3.  

How can you introduce validation of locally defined critical extensions into a PKIX CertPathValidator without having to change the underlying CertPathValidator provider?

Validation of private critical certificate extensions can be introduced to a PKIX CertPathValidator by using the PKIXCertPathChecker class. Remember that the checker you write must remove the extensions you are processing from the unresolved critical extensions Set thatis passed to the check() method for the validation to succeed.

4.  

If you are using a PKIX CertPathBuilder to construct a certificate path for a given target certificate, what is the most straightforward way to construct the target constraints required for the path builder parameters?

Use the issuer and serial number of the target certificate, because these should always uniquely identify the certificate. For example, given the X509Certificate targetCert that is the target of the path build, the target constraints would be defined as

X509CertSelector targetConstraints = new X509CertSelector(); targetConstraints.setSerialNumber( targetCert .getSerialNumber()); targetConstraints.setIssuer( targetCert .getIssuerX500Principal().getEncoded());

Of course, if you are fortunate enough to be using JDK 1.5 or later, you can leave off the getEncoded() .

Категории