Distinguished Names and Certificates
Overview
Asymmetric encryption provides ways of allowing you to distribute keys with relative safety that other people can use to send encrypted messages or verify signatures you have created. The problem, however, is that from the point of view of the people you are distributing the public keys to, the simple presence of a public key is not enough for someone to determine whether it is the public key they have or even if the use they are being asked to put it to is one that you intended.
Distinguished names and the certificates that carry them were created to solve this problem.
This chapter introduces distinguished names, certificates, and certification requests. Distinguished names contain information about the owner of a public key carried by a certificate. Certification requests provide a mechanism by which you can ask some other party, presumably trusted by the people you want to give the certificate to, to issue you with a certificate that can also be trusted. In general, this is done by issuing a certificate that can be verified using another certificate issued by the trusted party that is already in the hands of the people who you want to accept your new certificate.
By the end of this chapter, you should
- Understand what an X.500 name is
- Understand what a public key certificate is, most particularly those that use X.509
- Be able to make use of the Java classes representing X.500 name and certificates
- Be able to generate your own certification requests and certificates
- Be able to create a certificate from a certificate request
- Be able to form multiple certificates into a certificate chain, or path
Finally, you should understand how to make use of the certificate storage class in Java and how to selectively retrieve certificates from it.
Getting Started
For this chapter, you need to add some more functionality to the Utils class: the capability to create a random RSA key pair.
Here is the Utils class for this chapter:
package chapter6; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; /** * Chapter 6 Utils */ public class Utils extends chapter5.Utils { /** * Create a random 1024 bit RSA key pair */ public static KeyPair generateRSAKeyPair() throws Exception { KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); kpGen.initialize(1024, new SecureRandom()); return kpGen.generateKeyPair(); } }
As you can see, it has one new method on it that generates an RSA key pair. Nothing mysterious here! Type the class in to start the chapter6 package off, and you are ready to proceed.
Distinguished Names
The distinguished name, or DN, was originally proposed in X.501 in OSI to describe the X.500 directory structure. The idea of the directory structure was that it would form a hierarchy, similar to the one in Figure 6-1, with each level down a particular branch being identified by a relative distinguished name, or RDN, with the full path being described by a distinguished name , or DN, which is made up of a collection of the RDNs traversed.
Figure 6-1
The two ASN.1 types that make up a distinguished name are as follows :
DistinguishedName ::= RDNSequence RDNSequence ::= SEQUENCE OF RelativeDistinguishedName RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue AttributeTypeAndValue ::= SEQUENCE { type OBJECT IDENTIFIER, value ANY }
So what can go into an RDN? As you can see, the definition is so broad that it led to a famous quote by Bob Jueneman on the IETF-PKIX mailing list: " there is nothing in any of these standards that would prevent me from including a 1-gigabit MPEG movie of me playing with my cat as one of the RDN components of the DN in my certificate." For this reason, a number of other standards such as SET and RFC 2253 define restrictions that can be placed on the OID values used for the type field, the actual structures that can be put in the value field, and even the number of elements that can be in the RDN and DN.
The most common DN you are likely to have seen is a character string equivalent like the following:
"CN=www.bouncycastle.org, OU=Bouncy Castle, O=Legions, C=AU"
Each of the X=some value pairs making up the previous distinguished name, or DN, is what goes into a RelativeDistinguishedName, and as the ASN.1 structure of a DistinguishedName suggests, the String representation of a DN is simply a comma-separated list of X=some value pairs. These are commonly based on RFC 2253, although, as mentioned, you will find that not everybody agrees on what ASN.1 string type a given field should be.
As you have probably guessed, the X referred to in the last paragraph gets converted into an OBJECT IDENTIFIER. Common values for the string versions of X and their details are as follows:
- q CN ”commonName, OID "2.5.4.3", limited to 64 characters
- q OU ”organizationalUnitName, OID "2.5.4.11", limited to 64 characters
- q O ”organizationName, OID "2.5.4.10", limited to 64 characters
- q C ”country, OID "2.5.4.6", limited to 2 characters
- q L ”localityName, OID "2.5.4.7", limited to 64 characters
- q ST ”stateOrProvinceName, OID "2.5.4.8", limited to 64 characters
For the most part the " some value " gets converted to one of a PrintableString or UTF8String unless it has been defined as something else. For example, another common RDN type EMAILADDRESS is defined in PKCS #9 as being of the type IA5String. Strictly speaking, an e-mail address has no place in a DN, as it isn't part of any directory structure that the DN might otherwise be trying to describe. I'll discuss this further when you look at how to do it correctly using certificate extensions.
The main way to define a DN in Java is using the X500Principal class that can be used to create a Java object representing an X.500 DN from an encoding, or from a character string.
The X500Principal Class
The javax.security.auth.x500.X500Principal class was introduced in JDK 1.4. It has constructors that allow you to make one from either an ASN.1-encoded stream, byte array, or a String. In the case of the String constructor, the class constructs itself using the format defined in RFC 2253 and RFC 1779. The string parser used by the String constructor will also recognize RDN types that are defined in RFC 2459. The class also provides implementations of Object.equals() and Object.hashCode() and provides two methods for retrieving the DN contained in the object as either a byte array, using getEncoded(), or a string using getName().
X500Principal.getEncoded()
The getEncoded() method returns the X500Principal's DER encoding. This method, together with the override of Object.equals() and Object.hashCode(), are the three principal methods to use on the class. As you will see when I discuss getName() in more detail, converting a DN from a string back into an encoding is a lot harder than you might imagine. So whenever possible, you are better manipulating the byte array.
X500Principal.getName()
The getName() method returns a String representing the DN. It comes in two flavors, one that takes no parameters and returns the DN as a String, the other that takes a format name as a String and attempts to return the DN formatted according to the format specified. Although this is very useful, if a human needs to read a DN, there is a problem if you want to try to take a random X500Principal out of, say, a certificate, and get back the same encoding by just relying on the String representation.
As you might recall from Chapter 5, there are a number of ways of representing a character string in ASN.1, a few of which will print quite nicely in Java. The problem arises that if the encoding used to create the value for the RDN is easily displayable, but not what is expected, then converting a DN to a String and then back to an ASN.1 encoding may result in a different encoding being produced. Of course, if everyone was just following a single standard, this would not be an issue, but it is quite common to find PrintableString, UTF8String, BMPString, and IA5String in an encoding and then discover that this actually matters to some other piece of software you are using. Unfortunately, none of this encoding information will generally be carried along by a X500Principal when a getName() is called. This is not the fault of the class either; this issue is not an easy one to solve without forcing people to write their own parsers of String -based DNs for every occasion. The trick is to try to avoid using the Strings for anything other than display where possible and simply manipulate the object and save it in its encoded form.
Important |
Converting an encoded X.500 name to a string and back to an X500Principal may mean that the getEncoded() method on the X500Principal does not return the original encoding. Avoid having to do such conversions when possible. |
Public Key Certificates
Certificates, or to be more specific, public key certificates, provide a mechanism that allows a third party, or issuer, to vouch for the fact that a particular public key is linked with a particular owner, or subject. Every certificate has a private key associated with it, and a chain of certificates is a list of certificates where each certificate other than the first one and the last one have had its private key used to sign the next certificate after it. The first certificate, the root certificate, is normally self-signed; you have to accept it as trusted for the certificate chain to be valid. The last certificate, or the end entity certificate, simply provides you with a public key you are interested in, which, assuming you accept the root certificate, you can regard as authentic . The entity responsible for issuing the certificates is referred to as a certificate authority, or more commonly, a CA.
Support for public key certificates was added into Java with JDK 1.1, with further work being done in JDK 1.2, as experience from JDK 1.1 showed that some of the ideas behind the java.security API for certificates were becoming problematic . As of JDK 1.2, the classes for supporting certificates in the JCA can now be found in the package java.security.cert, and the first of these you need to understand is the Certificate class.
The Certificate Class
The java.security.cert.Certificate class provides the basic support for public key certificates. It provides the methods for extracting the public key stored in the certificate, identifying the certificate's type, verifying the signature contained in the certificate, and getting an encoded version of the certificate.
Certificate.getType()
The getType() method returns a String indicating the underlying certificate type. For the most part, you can expect this method to return X.509 , which indicates that the certificate is an X.509 certificate and will probably also be implemented using the extension class java.security.cert.X509Certificate. I will discuss the X509Certificate class a bit later.
Certificate.getPublicKey()
The getPublicKey() method returns the public key that the certificate is carrying.
The key should be suitable for use with whatever provider you used to create the certificate, but your mileage will vary if you try mixing public key certificates created with one provider with encryption or signature algorithms implemented with another. In a case where you do need to mix providers, you may need to use a KeyFactory created for the algorithm provider to transform the key retrieved from the certificate into something that will work with the algorithm provider.
Certificate.verify()
The verify() method checks that the signature contained in the certificate can be verified using the public key of the entity that was supposed to have signed the certificate. 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(), Certificate.verify() is a void and can throw one of five exceptions when it is called. The most likely one, when the public key is the wrong value, is a SignatureException indicating the signature did not match what was expected. The other four exceptions that can be thrown are as follows :
- q NoSuchProviderException if no provider can be found
- q NoSuchAlgorithmException if the signature algorithm used in the certificate is not recognized
- q InvalidKeyException if the public key passed in is for the wrong algorithm
- q CertificateException if there is a problem encoding the certificate in order to calculate any hash required as part of signature verification
Certificate.getEncoded()
The getEncoded() method returns a byte array containing an encoding of a certificate. What this contains depends on the return value of the getType() method, but in the case of an X.509 certificate, the byte array returned by getEncoded() will normally contain the certificate as a DER encoding of its ASN.1 structure.
X 509 Certificates
X.509 certificates, covered here, have their origins in the same OSI standards that produced X.500 and in a lot of ways follow the demise of the original X.500 directory concept. Originally with version 1, introduced in 1988, it was assumed that you could use the Issuer DN of a given certificate to build the certificate chain going back to the root; so given a collection of certificates, you would be able to easily sort them into a chain starting at a root and going to the end entity. This turned out to be too restrictive , and in 1993, version 2 introduced the concepts of unique identifiers for both the issuer and the subject to allow for reuse of DNs but still maintained the directory structure. Finally, the whole "enterprise" collapsed , and the concept of certificate extensions was introduced in version 3 in 1996. This introduced concepts like key usage and completely removed the restriction that a certificate chain had to follow the directory structure, allowing for both a hierarchical approach and a web of trust approach where there is no clear hierarchy, but a lot of cross dependencies.
Of the three different types, you will probably not see any version 2 certificates, or certificates using the unique identifiers that were added in version 2, as the use of the unique identifiers added in version 2 was discourage with the release of version 3. There are still a lot of X.509 version 1 certificates in use, especially for self-signed root certificates. You will find version 3 certificates for virtually all other applications.
Back in the Java world, support for X.509 certificates was explicitly added with the introduction of the java.security.cert package in JDK 1.2. The fundamental classes are the X509Certificate class and the X509Extension interface that's implemented by the X509Certificate class. The X509Extension interface supports the use of version 3 extensions. You'll look at the X509Certificate class first and then move onto the X509Extension interface and the methods on X509Certificate that derive from it.
The X509Certificate Class
The java.security.cert.X509Certificate class provides the basic support for X.509 certificates. The design of the class and the interfaces it implements is based on the following ASN.1 structures published in X.509:
Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING }
As you can see, an X.509 certificate is simply a sequence with three fields in it. As you have probably also guessed, most of the detail is in the tbsCertificate field, which contains the data that was used to generate the signature represented in the signatureValue field using the algorithm given by the details contained in the AlgorithmIdentifier in the signatureAlgorithm field. As you will see in a minute, the TBSCertificate structure contains the details of the issuer of the certificate as well as information on who it was issued for(the subject. The AlgorithmIdentifier structure is the same one you saw in Chapter 5.
I will start by detailing the TBSCertificate structure and going through the relevant methods on the X509Certificate class so that you can see how the ASN.1 structures relate to the class. Apart from giving you extra insight into the way the certificate class works, referring to the ASN.1 structure will also help explain some of the strange problems you may encounter when dealing with X.509 certificates.
X509Certificate.get TBSCertificate()
The getTBSCertificate() method returns the bytes making up the ASN.1 encoding of the TBSCertificate structure. These are the bytes that are used as the input data for calculating the signature the certificate carries, so if you wanted to verify a certificate signature using the Signature class, you could do so using the public key of the certificate issuer and the bytes returned by getTBSCertificate(). The actual structure contained in the ASN.1 encoding that getTBSCertificate() returns is defined in X.509 as follows :
TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, serialNumber CertificateSerialNumber, signature AlgorithmIdentifier, issuer Name, validity Validity, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version shall be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version shall be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version shall be v3 } Name ::= CHOICE { RDNSequence }
and most of the get() methods that follow provide access to fields in this structure. I will ignore the extensions field for the moment, as I will cover it when in the discussion of the X509Extension interface.
X509Certificate.get Version()
The getVersion() method returns the version of the TBSCertificate structure that was used to create the certificate. The ASN.1 definition of Version is defined as follows:
Version ::= INTEGER { v1(0), v2(1), v3(2) }
Ideally, X.509 certificates will always be encoded so that if extensions is present, the version will be v3; if issuerUniqueID and subjectUniqueID are present and extensions is not the version will be v2; and if none of extensions, issuerUniqueID, and subjectUniqueID are present, the version will be v1. However, if you are importing certificates from a variety of possibly unknown sources, the truth is that you cannot rely on the fact that
boolean v3 = ( x509Cert .getVersion() == 3);
setting v3 to true means the extensions field is present, as some organizations have issued certificates that purport to be version 3 but could really be accommodated using version 1.
The getVersion() method diverts from what is in the encoded certificate in that it returns 3 if the certificate is type v3 , 2 if it is of type v2 , and 1 if it is of type v1 . This is different from the actual version number stored in the TBSCertificate structure, since, as you can see from the ASN.1, the numbering starts with 0. In fact, as the TBSCertificate structure is generally DER encoded, in the case of a version 1 certificate, the actual version number will not be present. Therefore, if you need to process the contents of a TBSCertificate structure by hand, be prepared to deal with the version field being missing.
X509Certificate.get SerialNumber()
The getSerialNumber() returns a BigInteger representing the value of the serialNumber field in the TBSCertificate structure. You can see from the definition of TBSCertificate that this is of the type CertificateSerialNumber, which is defined as follows:
CertificateSerialNumber ::= INTEGER
It is very rare for these values to be represented as anything other than a BigInteger, so don't assume you can represent them as an int or a long .
Something else that's also rare, but is possible, is that the serial number will be negative. While the definition allows for it, it seems a bit odd that this happens. The likelihood is that some CAs are written forgetting that an ASN.1 INTEGER is a signed number and the certificate generation code does not allow for a leading zero in the case where the encoding of their internal, probably unsigned, representation has its top bit set. Avoid using negative serial numbers, because there are some applications out there that assume serial numbers are always positive; likewise, avoid assuming the serial number will always be positive. The issuer information, which you will look at next , and the serial number serve to uniquely identify the certificate, but any search based on this will most likely fail or return the wrong certificate if the serial number is negative and you have changed its sign.
X509Certificate.get IssuerX500Principal()
As you saw in the definition of the TBSCertificate structure, the issuer is simply a Name structure, so you use getIssuerX500Principal() to retrieve the DN of the issuer of the certificate. Combined with the serial number, the principal representing the issuer should uniquely identify the certificate.
You have already looked at DNs and the Name structure at the start of the chapter; however, I will make one further comment. Originally, prior to JDK 1.4, there was only X509Certificate.getIssuerDN(), which returned an object representing the X.500 name associated with the issuer of the certificate. The major problem was that it meant the only obvious provider-independent way to get access to the DN was via the Object.toString() method(something you have already seen that is, at best, quite hazardous. Now you can use getIssuerX500Principal(), which returns a X500Principal object, a DN, which has a precise defined behavior, and better yet, a getEncoded() method, so that for the most part, you can avoid converting the DN into a String whenever possible.
If you are using JDK 1.3 or earlier, the Bouncy Castle API also provides a helper class in the org.bouncycastle.jce package called PrincipalUtil, which returns a Bouncy Castle-specific object, X509Principal, an equivalent to the X500Principal class. You can use the PrincipalUtil.getIssuerX509Principal() to extract the issuer from a certificate, and the object you get back will allow you to avoid having to convert the issuer principal into a String in order to do processing on it.
X509Certificate.getNotBefore() and X509Certificate.getNotAfter()
The getNotBefore() and getNotAfter() methods are derived from the validity field in the TBSCertificate structure. The validity field has a type Validity, which is defined as follows:
Validity ::= SEQUENCE { notBefore Time, notAfter Time } Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime }
The notBefore field represents the time at which the certificate starts to be valid, and the getNotBefore() method returns it as a Date object. The notAfter field represents the last time at which the certificate will be valid, and the getNotAfter() method returns it as a Date object.
Note that the Time type can be a UTCTime, which has a two-digit year. You are unlikely to run into a new certificate carrying UTCTime these days. However, if you expect to be using certificates, say, from a legacy application, make sure the provider you are using for loading the certificates is handling the conversion from a two-to a four-digit year correctly. Otherwise , you may find certificates expiring before their time, or staying valid much longer than they should be.
X509Certificate.check Validity()
There are two versions of the checkValidity() method.
Called without parameters, the method checks the certificate's notBefore and notAfter times against the current time and throws an exception if the certificate is not yet valid or has expired .
Called with a single date parameter, the method checks the notBefore and notAfter times against the time represented by the date passed in and throws an exception if the certificate was not yet valid at that date or the certificate had already expired at that date.
The exceptions that will be thrown by checkValidity() if there is a problem with the time checking can be one of two. CertificateNotYetValidException is thrown if the time being checked is prior to the notBefore time, and CertificateExpiredException is thrown if the time being checked is after the notAfter date.
X509Certificate.get SubjectX500 Principal()
The subject field, representing the owner of the public key in the certificate, is also defined in the TBSCertificate structure as a Name , and the getSubjectX500Principal() method returns the DN for the key owner as a X500Principal . In a similar manner to getIssuerX500Principal(), originally this was often done using X509Certificate.getSubjectDN() with the same resultant problems. Whenever possible, avoid the use of getSubjectDN(). If you are using JDK 1.3 or earlier, you can use the Bouncy Castle helper class PrincipalUtil to retrieve the subject principal instead. After the same fashion as the issuer principal, you can call PrincipalUtil.getSubjectX509Principal() to return an object, which will give you more options with processing of the certificate's subject principal.
X509Certificate.get IssuerUniqueID()
You saw earlier that the issuerUniqueID field was defined as:
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
The ASN.1 definition of UniqueIdentifier is defined as:
UniqueIdentifier ::= BIT STRING
In the case of Java, the easiest match for an arbitrary bit string is an array of booleans, so the getissuerUniqueID() method returns a boolean array matching the BIT STRING that was in the issuerUniqueID field if it is present ”a very rare occurrence; you will normally only see one in a version 2 certificate.
X509Certificate.get SubjectUniqueID()
Like the issuerUniqueID, the subjectUniqueID is defined as:
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
and the getSubjectUniqueID() returns an array of booleans representing the BIT STRING value that was present in the subjectUniqueID field, if the field is present. Once again, you would only expect to see this value set in version 2 certificates.
X509Certificate.get Signature()
The getSignature() method returns the bytes making up the signature stored in the BIT STRING contained in the signatureValue field. These bytes are suitable for verification with a java.security.Signature class.
X509Certificate.getSigAlgOID() and X509Certificate.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 Certificate 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 TBSCertificate structure that's also of the type AlgorithmIdentifier. The purpose of this is to provide a cross-check against the unsigned signatureAlgorithm field in the Certificate structure. The contents of the signatureAlgorithm field and the signature field will always be equal in a valid certificate.
X509Certificate.get SigAlgName()
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.
Try It Out: Creating a Self-Signed Version 1 Certificate
This actually gives you enough to look at the process of creating a version 1 X.509 certificate. There is not any support for doing certificate creation directly in the JCA; however, the Bouncy Castle APIs have X.509 certificate generators in the org.bouncycastle.x509 package. Look at the following example using the org.bouncycastle.x509. X509V1CertificateGenerator:
package chapter6; 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.x509.X509V1CertificateGenerator; /** * Basic X.509 V1 Certificate creation. */ public class X509V1CreateExample { public static X509Certificate generateV1Certificate(KeyPair pair) throws InvalidKeyException, NoSuchProviderException, SignatureException { // generate the certificate X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); certGen.setIssuerDN(new X500Principal("CN=Test Certificate")); certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000)); certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000)); certGen.setSubjectDN(new X500Principal("CN=Test Certificate")); certGen.setPublicKey(pair.getPublic()); certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); return certGen.generateX509Certificate(pair.getPrivate(), "BC"); } public static void main(String[] args) throws Exception { // create the keys KeyPair pair = Utils.generateRSAKeyPair(); // generate the certificate X509Certificate cert = generateV1Certificate(pair); // show some basic validation cert.checkValidity(new Date()); cert.verify(cert.getPublicKey()); System.out.println("valid certificate generated"); } }
Running the example should produce the line
valid certificate generated
on standard output.
How It Works
The heart of the example is the generator method( generateV1Certificate() . Going through the setting up of the X509V1CertificateGenerator object, you can see that the set() methods called correspond to the setting of the fields you have just looked at in the TBSCertficate structure and are closely equivalent to the names used in the get() methods implemented by the X509Certificate class. There are a few things worth noting, so I will go through the use of the generator class step-by-step.
First off, the serial number for the certificate is set on the following line:
certGen1.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
The best way to create serial numbers is largely up to the environment you are working in. The main thing is they need to be positive and unique for a given issuer value. Keep in mind that, because it is a BigInteger , you have a few bits you can work with, so it should always be possible to come up with an algorithm that will make sure the numbers are unique.
The next thing that is done is that the issuer of the certificate is assigned. As you would expect, this is simply an X.500 name, so I used an X500Principal to do this.
certGen1.setIssuerDN(new X500Principal("CN=Test Certificate"));
After the issuer, the notBefore and notAfter fields are set.
certGen1.setNotBefore(new Date(System.currentTimeMillis() - 50000)); certGen1.setNotAfter(new Date(System.currentTimeMillis() + 50000));
The generator object uses a GeneralizedTime to represent these dates, so the certificate you are producing will have a four-digit year on it. You can see from the Date objects being used that while the certificate will be valid when the program finishes, it has a total lifetime of only 100 seconds, 50 of which have already gone when it is created.
After setting the dates, you set the subject. Like the issuer this is also an X500Principal and, because the certificate is self-signed, has the same value as the issuer.
certGen1.setSubjectDN(new X500Principal("CN=Test Certificate"));
In the normal case, where the subject, or owner of the public key, is a different entity from the entity issuing the certificate, you would expect the X500Principal passed to the setSubjectDN() method to be different from that passed to setIssuerDN() .
Next, the example sets the public key that will be carried in the certificate:
certGen1.setPublicKey(pair.getPublic());
After this, you specify the signature algorithm that will be used to sign the certificate. In this case you are using SHA-256 with RSA with the signature format, as outlined in PKCS #1 version 1.5.
certGen1.setSignatureAlgorithm("SHA256WithRSAEncryption");
Finally, you generate the certificate, providing a private key to sign it with and the name of the provider you want to use:
return certGen1.generateX509Certificate(pair.getPrivate(), "BC");
Because this certificate is self-signed, it just uses the private key that corresponds to the public key on the certificate. As you will see a bit later when you read about certification requests , you would normally be using a completely different private key to sign a certificate with(the private key you would use would be the one corresponding to the public key on the CA certificate that you issued for yourself so that people could check that certificates claiming to be issued by you really were.
X 509 Extensions
The extensions field in the TBSCertificate structure was added with the release of X.509 version 3. As you can see from the original definition of TBSCertificate, it is optional, explicitly tagged, and of the type Extensions. The ASN.1 definition of Extensions is as follows:
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }
MAX , of course, is implementation-dependent, but will normally always be big enough for the purposes of accommodating any certificates you have to deal with.
You can see that the actual Extension type is a three-member sequence. First, there is the extnID field, which identifies what extension is contained in an Extension structure and how to interpret the bytes in the extnValue field. Next, there is the critical field, which determines whether or not an implementation trying to process a X.509 certificate must be able to understand the extension if it is to process the certificate correctly. Finally, there is the extnValue field, which contains a DER encoding of the type associated with an extension associated with the identifier stored in the extnID field.
X.509 does not cover all the possibilities for extensions. Getting coverage on this is dependent on what profile the certificate is being created for. The signature on a certificate being valid is one thing, but you also need to know that the certificate is being valid for the use it is being presented to you for, and this is where the profile comes in. In the case of certificates, a profile tells you which extensions should be critical, what the allowed values of some of the certificates extensions might be, what extensions might have to be present, and which criteria must be met for a certificate chain to be recognized as valid. For example, I may trust an individual enough to sign a certificate for you to allow you to send signed e-mail, but it is another thing completely to trust someone to the level where I would expect another party to accept an executable signed with a certificate I had issued and think I had verified that the executable was not going to cause mischief. Aprofile provides the guidelines that allow someone that has been presented with a certificate to decide not only whether a particular CA signed the certificate, but also whether the certificate is being presented for a use that the CA issued it for.
There are now quite a few profiles, the most prevalent one probably being the Internet profile, also known as PKIX, which is covered in RFC 3280. Most providers will endeavor to conform to the PKIX profile, but which profile you should be using will also depend a lot on what environment you are working in, as many national standards bodies now have their own idea of what constitutes a good profile, and not all of them are in agreement.
That completes the background information on the outside world's view of extensions. Now you will have a look at the Java support for extensions.
The X509Extension Interface
The X509Extension interface provides some basic methods for investigating the extensions that are present in an X.509 certificate. It allows you to find out which extensions are critical and which are not, to get the value for a particular extension, and, finally, to check if there are any extensions that are unsupported by the profile the extension was created in.
X509Extension.get CriticalExtensionsOIDs()
The getCriticalExtensionsOIDs() method returns a possibly empty set of String objects representing the object identifiers related to the extensions marked as critical ”that is, with the critical field set to TRUE ”in the implementing certificate. If there are no extensions present in the implementing certificate, the method returns null .
X509Extension.get ExtensionValue()
The getExtensionValue() method takes a string representing the object identifier of the extension you are interested in getting the value for. The method returns a byte array containing the DER-encoded OCTET STRING that is present in the extnValue field in the Extension structure associated with the passed in OID, or null if an extension related to that OID is not present.
Note the words "the method returns the OCTET STRING stored in the extnValue field." If you need to do further examinations on the structure associated with the extension, you will need to do something like the following to convert the byte array you get back from the method into the type that is indicated by the extension's OID:
ASN1InputStream aIn = new ASN1InputStream( x509Cert .getExtensionValue( extensionOID )); ASN1OctetString extnValue = (ASN1OctetString)aIn.readObject(); aIn = new ASN1InputStream(extnValue.getOctets()); DERObject extensionType = aIn.readObject();
The value extensionType will then contain the ASN.1 predefined types making up the structure that is associated with the object identifier indicated by extensionOID .
X509Extension.get NonCriticalExtensionOIDs()
The getCriticalExtensoinsOIDs() method returns a possibly empty set of String objects representing the object identifiers related to the extensions not marked as critical ”that is, with the critical field set to the default value of FALSE ”in the implementing certificate. If there are no extensions present in the implementing certificate, the method returns null .
X509Extension.has UnsupportedCriticalExtension()
The hasUnsupportedCriticalExtension() method returns true if the certificate contains an extension that is marked as critical but that is not expected as a critical extension by the profile the certificate was created for; otherwise, the method returns false .
It's actually hard to see what real use this method has now, since from JDK 1.4, an API for doing certificate path validation was introduced. In general, if you are using it, you will probably find it conforms to the PKIX profile; however, if you are serious about checking your certificates, you should use the CertPath API, which is discussed in Chapter 7.
Extensions Supported Directly by X509Certificate
Some of the more common extensions are supported directly by the X509Certificate class. You'll have a look at them here. The directly supported extensions help give you some idea of how the path validation APIs get the information they need to do their job, as well as how usage restrictions can be placed on version 3 certificates.
The common extensions are defined as part of X.500, which has the object identifier "2.5", being off the joint ISO/ITU-T branch. X.500 then assigned arc 29 to certificate extensions, giving it the name id-ce. You will also find these extensions among those documented in RFC 3280.
X509Certificate.get KeyUsage()
The KeyUsage extension is simply a BIT STRING and is associated with the object identifier id-ce-keyUsage , which has the value "2.5.29.15". The extension's ASN.1 definition looks as follows:
KeyUsage ::= BIT STRING { digitalSignature (0), nonRepudiation (1), keyEncipherment (2), dataEncipherment (3), keyAgreement (4), keyCertSign (5), cRLSign (6), encipherOnly (7), decipherOnly (8) }
The getKeyUsage() method returns these as an array of booleans. In the event that not all bits are set in the BIT STRING , the array of booleans is always padded out to eight elements, with the missing elements being set to false .
The presence of a particular bit indicates what roles the issuer of the certificate had in mind when the certificate was generated. The name of the bits and their meanings are as follows:
- q digitalSignature ”The key can be used with signature mechanisms other than those involving certificate signing.
- q nonRepudiation ”The key can be used for verifying signatures used to provide a nonrepudiation service.
- q keyEncipherment ”The key can be used to encrypt other keys.
- q dataEncipherment ”The key can be used to encrypt data other than cryptographic keys.
- q keyAgreement ”The key can be used for key agreement.
- q keyCertSign ”The key may be used for verifying other certificates.
- q cRLSign ”The key can be used to verify a signature on a CRL, or certificate revocation list. You will have closer look at CRLs in Chapter 7.
- q encipherOnly ”This bit is only meaningful if the KeyAgreement bit is set. If both it and the KeyAgreement bit are set, key agreement can be carried out only for the purpose of encrypting data.
- q decipherOnly ”This bit is only meaningful if the KeyAgreement bit is set. If both it and the KeyAgreement bit are set, key agreement can be carried out only for the purpose of decrypting data.
X509Certificate.get SubjectAlternativeNames()
In addition to the details of the subject name provided by the getSubjectX500Principal() , a certificate can also carry other names that the owner of the public key in the certificate can be known by. This is done using the SubjectAltName extension, which allows a range of alternatives to be included for identifying the subject. If you want to include the e-mail of a subject in a certificate, this is really the place to do it, rather than use the older PKCS #9 e-mail address field in the DN.
The SubjectAltName extension is indicated by the presence of the OID "2.5.29.17" ( id-ce-subjectAltName ). The value of the extension is a GeneralNames structure, which is defined as follows and has the following ASN.1 definition:
SubjectAltName ::= GeneralNames GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName GeneralName ::= CHOICE { otherName [0] AnotherName, rfc822Name [1] IA5String, dNSName [2] IA5String, x400Address [3] ORAddress, directoryName [4] Name, ediPartyName [5] EDIPartyName, uniformResourceIdentifier [6] IA5String, iPAddress [7] OCTET STRING, registeredID [8] OBJECT IDENTIFIER } AnotherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY DEFINED BY type-id } EDIPartyName ::= SEQUENCE { nameAssigner [0] DirectoryString OPTIONAL, partyName [1] DirectoryString } DirectoryString ::= CHOICE { teletexString TeletexString, printableString PrintableString, bmpString BMPString, universalString UniversalString, uTF8String UTF8String }
I'll show you how to construct a SubjectAltName extension in the example a bit later. Meanwhile, when it comes to reading one using the getSubjectAlternativeNames() method, the method returns null if the extension is not present, or if it is present, it returns an immutable collection representing the GeneralNames structure.
The mechanism for constructing the collection is as follows. Each entry in the GeneralNames structure is turned into a two-element list, the first element being an integer representing the tag associated with that GeneralName, and the second element being an object representing the value of the GeneralName. The various values are returned either as Java String objects or as byte arrays that contain the DER encoding of the name value. The tags returned as String objects are those for rfc822Name, dNSName, uniformResourceIdentifier, iPAddress, directoryName, and registeredID . IP addresses are converted into strings using dotted quad notation in the case of IPv4 and for IPv6 in the form "a1:a2: :a8", where a1 to a8 each represent one part of the eight 16-bit pieces making up the address. Directory name objects are converted into String objects using RFC 2253 string format. In the case of the others, the DER-encoded value of the choice item is returned in a byte array.
Note, the conversion of directory names to RFC 2253 strings may not always have the desired result. If you need to process directory names in the SubjectAltName extension, you may need to unpack the extension by hand instead using X509Extension.getExtensionValue() .
X509Certificate.get IssuerAlternativeNames()
The IssuerAltName extension is indicated by the presence of the OID "2.5.29.18" ( id-ce-issuerAltName ). It is defined in the same way as the SubjectAltName extension, as a GeneralNames structure, and consequently also returns a collection that has been formatted according to the rules outlined for getSubjectAlternativeNames() .
X509Certificate.get BasicConstraints()
The BasicConstraints extension is indicated by the presence of the OID "2.5.29.19" ( id-ce-basicConstraints ). The getBasicConstraints() method returns an int value, and to understand why, you need to look at the ASN.1 definition of the BasicConstraints structure that is as follows:
BasicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER (0..MAX) OPTIONAL }
The return value of the method is derived from the value of the pathLenConstraint , which is meaningful only if the cA field is set to TRUE , meaning that the subject of the certificate is a certificate authority. If the cA field is FALSE , then the extension indicates that the certificate is an end entity certificate and should not be used to sign another one.
When pathLenConstraint is meaningful it indicates how many certificates can follow the one that contains the extension. For example, if the value is 0, then any certificate verifiable by the certificate containing the extension must be an end entity certificate. If pathLenConstraint is left out and the cA field is TRUE , then any number of CA certificates can follow the certificate containing the extension.
So in respect of the getBasicConstraints() method: If the cA field is TRUE and the pathLenConstraint has been set, then the value returned is the int representing the value in pathLenconstraint; if pathLenConstraint is not present, then Integer.MAX_VALUE is returned instead. If the cA field is FALSE , then getBasicConstraints() returns ˆ’ 1 , indicating the certificate containing the extension can only come at the end of the chain.
X509Certificate.get ExtendedKeyUsage()
The ExtendedKeyUsage extension was created when it turned out a more flexible arrangement than the one offered by the KeyUsage extension was required. It is associated with the object identifier "2.5.29.37" ( id-ce-extKeyUsage ) and its ASN.1 definition is as follows:
ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId KeyPurposeId ::= OBJECT IDENTIFIER
As you can see, the type encoded in the extnValue field is just a sequence of OIDs, so unlike the KeyUsage extension, the ExtendedKeyUsage allows the certificate issuer to use object identifiers to determine what the public key in the certificate can be used for. The greater customization this extension allows in certificate purpose has resulted in it becoming increasingly popular, with a number of organizations and companies publishing lists of object identifiers that can be used as a KeyPurposeId .
The PKIX profile described in RFC 3280 describes a number of these, which are for supporting some of the more standard uses a certificate key can be put to. The current list includes the following:
- q anyExtendedKeyUsage , OID "2.5.29.37.0", key can be used for any purpose.
- q id-kp-serverAuth , OID "1.3.6.1.5.5.7.3.1", key can be used for SSL/TLS server authentication.
- q id-kp-clientAuth , OID "1.3.6.1.5.5.7.3.2", key can be used for SSL/TLS client authentication.
- q id-kp-codeSigning , OID "1.3.6.1.5.5.7.3.3", key can be used for signing executable code.
- q id-kp-emailProtection , OID "1.3.6.1.5.5.7.3.4", key can be used for e-mail encryption/signing.
- q id-kp-timeStamping , OID "1.3.6.1.5.5.7.3.8", key can be used for creating timestamps.
- q id-kp-OCSPSigning , OID "1.3.6.1.5.5.7.3.9", key can be used for signing OCSP (RFC 2560) messages.
As you may have guessed from the "1.3.6.1.5.5.7.3", also known as id-kp , is an object identifier that marks an arc reserved for PKIX extended key purpose object identifiers.
Both KeyUsage and ExtendedKeyUsage extensions can appear in the same certificate, so it is acceptable to include both if you are creating a certificate that contains both, or evaluating one that contains both, just make sure they are consistent with each other. For example, it does not make sense to have a KeyUsage extension with only the dataEncipherment bit set and have an ExtendedKeyUsage extension with the id-kp-codeSigning OID present.
Try It Out: Creating a Self-Signed Version 3 Certificate
Creation of a version 3 certificate is very similar to a version 1 certificate ”the only difference is the presence of extensions. This section creates a version 3 certificate with a couple of the extensions covered in the last section.
Choice of extensions is largely dictated by the purpose the certificate is for, although you will also see in Chapter 7 that extensions exist that serve to provide references to how to verify the certificate is still valid, what other certificate can be used to verify it, and extra information on the public key contained in the certificate. In the example here I'll generate a self-signed certificate marked for use with SSL using the ExtendedKeyUsage extension, as well as set the appropriate bits in the KeyUsage extension. The certificate will also be marked as unsuitable for use as a CA cert by using the BasicConstraints extension, and finally I'll include an e-mail address for the certificate's subject using the SubjectAltName extension.
package chapter6; 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.BasicConstraints; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.x509.X509V3CertificateGenerator; /** * Basic X.509 V3 Certificate creation with TLS flagging. */ public class X509V3CreateExample { public static X509Certificate generateV3Certificate(KeyPair pair) throws InvalidKeyException, NoSuchProviderException, SignatureException { // generate the certificate X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); certGen.setIssuerDN(new X500Principal("CN=Test Certificate")); certGen.setNotBefore(new Date(System.currentTimeMillis() - 50000)); certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000)); certGen.setSubjectDN(new X500Principal("CN=Test Certificate")); certGen.setPublicKey(pair.getPublic()); certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false)); certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature KeyUsage.keyEncipherment)); certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)); certGen.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames( new GeneralName(GeneralName.rfc822Name, "test@test.test"))); return certGen.generateX509Certificate(pair.getPrivate(), "BC"); } public static void main(String[] args) throws Exception { // create the keys KeyPair pair = Utils.generateRSAKeyPair(); // generate the certificate X509Certificate cert = generateV3Certificate(pair); // show some basic validation cert.checkValidity(new Date()); cert.verify(cert.getPublicKey()); System.out.println("valid certificate generated"); } }
How It Works
As you can see, this is example is very similar to the last one, so the basics of the certificate construction are essentially what was described earlier. The difference in this case is the use of the X509V3CertificateGenerator class and that you are now adding some extensions to the certificate.
Looking at the example, note that each call to the X509V3CertificateGenerator.addExtension() method takes three parameters reflecting the three fields required in an extension that you saw in the ASN.1 definition earlier:
Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }
The first and second parameters, representing extnID and critical , are self-explanatory. The last parameter, which is used to fill in the extnValue field, needs a bit more examination, though. For example, take the adding of a KeyUsage extension, which is done on the following lines:
certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature KeyUsage.keyEncipherment));
The last parameter is an ASN1Encodable object representing the value of the extension. What happens internally to the generator is that the value is encoded as a DER-encoded byte array, which is then used to create an OCTET STRING . It is this OCTET STRING that is then assigned as the value of the extnValue field. Likewise, if you were to add
byte[] usageExt = cert.getExtensionValue(X509Extensions.KeyUsage.getId());
the byte array usageExt would represent the DER encoding of the OCTET STRING , and the octets contained in the OCTET STRING would represent the DER encoding of KeyUsage .
One further point of interest about the example is the choice of whether or not to make a particular extension critical by passing true as the value for the critical field. As mentioned earlier, this largely depends on the profile that the certificate is being created for. In the case of the example, the choices have been based on the PKIX profile detailed in RFC 3280. If you look up RFC 3280, you will see that BasicConstraints in an end entity certificate may be marked as critical; in the example I have taken the option of doing so. KeyUsage , on the other hand, should be marked as critical, so it is. ExtendedKeyUsage may also be critical; however, if it is, the key can be used only for the purpose allowed by the extension, and the purpose given in the extension must also correspond with the purposes allowed by the KeyUsage extension if it is present. In my case, I wanted to assert the purpose of the certificate key strongly, so I have marked ExtendedKeyUsage as critical. Finally, I added the SubjectAltName extension, which in this case has a purely informational role and so is marked as noncritical.
Reading and Writing Certificates
There are two principal encodings used for reading and writing individual certificates: native DER format and the PEM format, which is described in some early RFCs on securing e-mail, RFC 1421 to RFC 1424, which were about Privacy Enhanced E-mail. The PEM format is basically a base 64-encoded version of the DER encoding with an ASCII header and footer attached.
Getting the DER encoding of a certificate is easy ”just call the Certificate.getEncoded() method on the certificate object. Writing PEM-encoded certificates is not directly supported in the JCA, but the Bouncy Castle APIs include a class for generating them called the PEMWriter , which lives in the org. bouncycastle.openssl package. I will show you examples of using both these methods of writing certificates in the discussion of the class you use for reading certificate streams ”the CertificateFactory class.
The CertificateFactory Class
Like other classes in the JCA, objects of the type java.security.cert.CertificateFactory are not created directly but are instead created using the getInstance() factory pattern, and as with other factory-based classes, the return value of getInstance() follows the provider precedence rules that the Java runtime has been set up with.
The CertificateFactory class has a number of methods on it for reading in certificates, certificate revocation lists, and certificate paths. For the moment you will just look at the methods for reading certificates, because certificate revocation lists and certificate paths are covered in the next chapter.
Certificate Factory.generateCertificate()
The generateCertificate() method takes an InputStream as a parameter and proceeds to read certificates from it. If more than one certificate is present in the stream, and the stream supports InputStream.mark() and InputStream.reset(), generateCertificate() will return one certificate per invocation until it runs out of certificates, at which stage it returns null . If there is trailing data, however, a CertificateException will be thrown.
Certificate Factory.generateCertificates()
The generateCertificates() method also takes an InputStream as a parameter but, in this case, reads the entire stream and returns a, possibly empty, Collection class containing the certificates that were read. Like the generateCertificate() method, a CertificateException is thrown in the event of a parsing error.
Try It Out: Using the CertificateFactory Class
This example reads a single certificate defined in an InputStream . It uses the earlier Try It Out ("Creating a Self-Signed Version 1 Certificate") to provide the source data, but you could just as easily get the data from a file.
package chapter6; import java.io.*; import java.security.*; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; /** * Basic example of using a CertificateFactory. */ public class CertificateFactoryExample { public static void main(String[] args) throws Exception { // create the keys KeyPair pair = Utils.generateRSAKeyPair();; // create the input stream ByteArrayOutputStream bOut = new ByteArrayOutputStream(); bOut.write(X509V1CreateExample.generateV1Certificate(pair).getEncoded()); bOut.close(); InputStream in = new ByteArrayInputStream(bOut.toByteArray()); // create the certificate factory CertificateFactory fact = CertificateFactory.getInstance("X.509","BC"); // read the certificate X509Certificate x509Cert = (X509Certificate)fact.generateCertificate(in); System.out.println("issuer: " + x509Cert.getIssuerX500Principal()); } }
Running the example should produce the line:
issuer: CN=Test Certificate
on standard output.
How It Works
An InputStream for the CertificateFactory is created using a byte array primed with the DER encoding of a X.509 certificate, and the certificate is then recovered using the generateCertificate() method.
One interesting modification you could try is to change the example so that it writes the certificate data in PEM format instead. You can do this using the Bouncy Castle org.bouncycastle.openssl.PEMWriter class , and after adding the appropriate import statement, replace the line
bOut.write(X509V1CreateExample.generateV1Certificate(pair).getEncoded());
with the lines
PEMWriter pemWrt = new PEMWriter(new OutputStreamWriter(bOut)); pemWrt.writeObject(X509V1CreateExample.generateV1Certificate(pair)); pemWrt.close();
You should see that the certificate still reads okay; however, if you add the line
System.out.println(Utils.toString(bOut.toByteArray()));
after the call to bOut.close() , you will see that you now have a base 64-encoded version of the certificate between the headers "----- BEGIN CERTIFICATE -----" and "----- END CERTIFICATE -----".
Try It Out: Reading Multiple Certificates
As mentioned earlier, the CertificateFactory also allows you to retrieve more than one certificate from a stream. The following example shows how to this by looping; in the How It Works section I'll discuss the method that returns a Collection . Try running it and then read on.
package chapter6; import java.io.*; import java.security.*; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.*; /** * Basic example of reading multiple certificates with a CertificateFactory. */ public class MultipleCertificateExample { public static void main(String[] args) throws Exception { // create the keys KeyPair pair = Utils.generateRSAKeyPair(); // create the input stream ByteArrayOutputStream bOut = new ByteArrayOutputStream(); bOut.write(X509V1CreateExample.generateV1Certificate(pair).getEncoded()); bOut.write(X509V3CreateExample.generateV3Certificate(pair).getEncoded()); bOut.close(); InputStream in = new ByteArrayInputStream(bOut.toByteArray()); // create the certificate factory CertificateFactory fact = CertificateFactory.getInstance("X.509","BC"); // read the certificate X509Certificate x509Cert; Collection collection = new ArrayList(); while((x509Cert = (X509Certificate)fact.generateCertificate(in)) != null) { collection.add(x509Cert); } Iterator it = collection.iterator(); while (it.hasNext()) { System.out.println("version: " + ((X509Certificate)it.next()).getVersion()); } } }
Running the example, you should see it print the version numbers for the certificates it finds; in this case, the output will read
version: 1 version: 3
indicating that the factory managed to read the two certificates that were written.
How It Works
This example follows from the discussion after the previous example using the CertificateFactory class. The only difference this time is that you have primed the byte array used for InputStream generation with two certificates rather than one and then looped, adding each certificate to a collection as you read it.
There are a couple of points that do need to be covered about it, though. The first one is that in the case you are looking at here, you can use the fact.generateCertificate() method in a loop because a ByteArrayInputStream supports the InputStream.mark() and InputStream.reset() methods. If this was not the case, you would need to add the line
in = new BufferedInputStream(in);
prior to entering the while loop.
The second point is that rather than using the loop, you could have just used the CertificateFactory. generateCertificates() method, which returns a Collection class containing all the certificates it found in the InputStream . You can try that as an alternative by replacing the lines
Collection collection = new ArrayList(); while((x509Cert = (X509Certificate)fact.generateCertificate(in)) != null) { collection.add(x509Cert); }
with the single line
Collection collection = fact.generateCertificates(in);
and you will see that the example produces the same result. As you can imagine, this is likely to be less trouble, as otherwise you have make sure whatever stream you are reading from can support the mark() and reset() methods on InputStream .
If the purpose of reading multiple certificates is to read a certificate path , or chain, there is a good chance you should be using the CertPath API instead and the CertificateFactory methods that are relevant to it. You look at how to do this in Chapter 7; for now the next thing to look at is how to request a certificate from a CA so you can actually build a certificate chain in the first place.
Certification Requests
Acertification request is a structure that is supplied to a CA, giving the CA the details that a key owner wants to appear in a certificate the owner wants the CA to create. Certification requests need to accommodate a couple of needs. Obviously, they need to contain the public key and some identifying information for the person who owns it. They also need to be tamper-proof so that the CA can be confident that the information that the CA is about to include in a certificate generated in response to the request is valid.
A variety of vendors have, from time to time, produced their own format for creating certification requests, but by far the most common and supported structure is the format defined by RSA Security in the standards document PKCS #10.
CertificationRequest ::= SEQUENCE { certificationRequestInfo CertificationRequestInfo, signature Algorithm AlgorithmIdentifier, signature BIT STRING } CertificationRequestInfo ::= SEQUENCE { version INTEGER, subject Name , subjectPKInfo SubjectPublicKeyInfo, attributes [0] Attributes } Attributes ::= SET OF Attribute
The CertificationRequest structure looks very similar to that of a X.509 certificate. It consists of a block of signed data; an AlgorithmIdentifier identifying the signature algorithm used and a BIT STRING containing the actual signature that was created using the DER encoding of the CertificationRequestInfo structure as input. This is not especially surprising, as it is doing a similar job. The big difference is that a CertificationRequest is always self-signed ”that is, the public key contained in the subjectPKInfo object inside the CertificationRequestInfo structure is always the key that is used to verify the signature. This means the owner of the private key corresponding to the public key in the CertificationRequestInfo structure must have been the one who created the signed request.
The CertificationRequestInfo structure is considerably simpler than the TBSCertificate structure, however. Apart from a version number, the structure consists of the subject field, which contains the X.500 name that is to appear as the subject in the issued certificate. Then there is the subjectPKInfo field, which contains the public key that is to appear in the certificate. Finally, there is an attributes field, which is a set of Attribute structures.
The Attribute structure is the same one that you have seen before; the only special note required is that, in the case of PKCS #10, the legitimate attribute types are defined in PKCS #9. There are two of them: the challengePassword attribute and the extensionRequest attribute.
The challengePassword attribute is used to send the CA a password that can be used to request revocation of the certificate being issued. The OID associated with the attrType field for challengePassword is pkcs-9-at-challengePassword , which has the value "1.2.840.113549.1.9.7". The SET assigned to the attrValues field in the challengePassword can only contain one entry, which is of the type DirectoryString , the same type you saw in the description of the GeneralName type. What is actually put in the DirectoryString is then up to the CA. If you need to use this attribute, you will find the requirements are likely to vary from place to place.
The extensionRequest attribute is used to request specific extensions to be added to the certificate when it is issued. The attrType value for extensionRequest is pkcs-9-at-extensionRequest ”the OID value "1.2.840.113549.1.9.14". Unlike the challengePassword , the contents of the attrValues field is more rigorously defined. There can only be one entry in the SET , and it is an Extensions structure. It is the same one you saw as part of the TBSCertificate structure in the X.509 certificate definition, so the use of this attribute is uniform across CAs.
You will look at the use of the extensionRequest attribute a bit later, but at this point I would like you to look at an example of how to create a basic certification request.
Try It Out: Creating a Certification Request
A good place to start with certification requests is to just create a basic one. Have a look at the following example. It creates a certification request that is suitable for creating a basic certificate ”one that simply provides a common name in the subject.
package chapter6; import java.io.OutputStreamWriter; import java.security.KeyPair; import java.security.KeyPairGenerator; import javax.security.auth.x500.X500Principal; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.openssl.PEMWriter; /** * Generation of a basic PKCS #10 request. */ public class PKCS10CertRequestExample { public static PKCS10CertificationRequest generateRequest( KeyPair pair) throws Exception { return new PKCS10CertificationRequest( "SHA256withRSA", new X500Principal("CN=Requested Test Certificate"), pair.getPublic(), null, pair.getPrivate()); } public static void main(String[] args) throws Exception { // create the keys KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); kpGen.initialize(1024, Utils.createFixedRandom()); KeyPair pair = kpGen.generateKeyPair(); PKCS10CertificationRequest request = generateRequest(pair); PEMWriter pemWrt = new PEMWriter( new OutputStreamWriter(System.out)); pemWrt.writeObject(request); pemWrt.close(); } }
Running the example produces a PEM-encoded certification request, which should look as follows :
-----BEGIN CERTIFICATE REQUEST----- MIIBYjCBzAIBADAlMSMwIQYDVQQDExpSZXF1ZXN0ZWQgVGVzdCBDZXJ0aWZpY2F0 ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsO4slP/KdZQsZyYn3asTWDtX E1YN+QQbbHELK7boPQa91YHv5DV/SgucThoXXCtSA45d3dQhrEbZ2+HRBarZIylk Nc+VmcV1qFX5KsD9wCYPMtdAYYog6jz259yCOKPDXPm787Q5t9h2zV3Ml1i0eWhC cdRYiWHQ5g20W4Bq3GsCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBGzajsmJyWULew wXlAqC4RcaaNnSxGv25DRSmRyUcngWhTe/T8JEi5HN5s8ELQUQ62/Y7XLyUyd/g6 h/DHjuSDg6W6NdxdOV61L+mFnlck6XJsevvm8DANvKiEQxPCegwIeUoyhYLw0Mip K0SnuvXv52WLd9Mv7TP8VHAtmfqRng== -----END CERTIFICATE REQUEST-----
As mentioned earlier, the PEM encoding is simply a base 64 encoding of the DER encoding of the certification request structure between a header and footer. One thing to watch out for: There are two variations on what should be in the header and footer in a certification request. OpenSSL uses "CERTIFICATE REQUEST," but other applications also use "NEW CERTIFICATE REQUEST."
How It Works
The basic information required to go into a certification request is the public key it is meant to contain and the X.500 name of the owner of the public key. The request needs to be signed using the private key corresponding to the public key, and a signature algorithm needs to be specified. All this is encapsulated in the following lines:
return new PKCS10CertificationRequest( "SHA256withRSA", new X500Principal("CN=Test Certificate"), pair.getPublic(), null, pair.getPrivate());
Having created the request, you need to turn it into a format suitable for passing onto a CA so they can issue a certificate based on it. This example uses PEM, which is a format suitable for sending through e-mail or cutting and pasting into a Web form. In cases where the CA can accept the request directly as a DER-encoded object, you might have simply written the request out to a file.
This will get you a basic certificate with whatever default extensions the CA adds to it. What do you do if you need to request a particular extension rather than the default ones, or just add extra information to the certificate that the CA is willing to include? The answer is to make use of the extensionRequest attribute. Take a look at how that is done now.
Try It Out: Adding Extensions to a Certification Request
Here is a modified version of the last example. It adds a SubjectAltName extension to the certification request. You add this to the attributes section of the certification request by using the extensionRequest attribute. The idea is that the CA picks up the attribute as part of processing the certification request.
Have a look at the example and try running it.
package chapter6; import java.io.OutputStreamWriter; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.util.Vector; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.pkcs.Attribute; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.openssl.PEMWriter; /** * Generation of a basic PKCS #10 request with an extension. */ public class PKCS10ExtensionExample { public static PKCS10CertificationRequest generateRequest( KeyPair pair) throws Exception { // create a SubjectAlternativeName extension value GeneralNames subjectAltName = new GeneralNames( new GeneralName(GeneralName.rfc822Name, "test@test.test")); // create the extensions object and add it as an attribute Vector oids = new Vector(); Vector values = new Vector(); oids.add(X509Extensions.SubjectAlternativeName); values.add(new X509Extension(false, new DEROctetString(subjectAltName))); X509Extensions extensions = new X509Extensions(oids, values); Attribute attribute = new Attribute( PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new DERSet(extensions)); return new PKCS10CertificationRequest( "SHA256withRSA", new X500Principal("CN=Requested Test Certificate"), pair.getPublic(), new DERSet(attribute), pair.getPrivate()); } public static void main(String[] args) throws Exception { // create the keys KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC"); kpGen.initialize(1024, Utils.createFixedRandom()); KeyPair pair = kpGen.generateKeyPair(); PKCS10CertificationRequest request = generateRequest(pair); PEMWriter pemWrt = new PEMWriter( new OutputStreamWriter(System.out)); pemWrt.writeObject(request); pemWrt.close(); } }
You should get the following lines:
-----BEGIN CERTIFICATE REQUEST----- MIIBkDCB+gIBADAlMSMwIQYDVQQDExpSZXF1ZXN0ZWQgVGVzdCBDZXJ0aWZpY2F0 ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsO4slP/KdZQsZyYn3asTWDtX E1YN+QQbbHELK7boPQa91YHv5DV/SgucThoXXCtSA45d3dQhrEbZ2+HRBarZIylk Nc+VmcV1qFX5KsD9wCYPMtdAYYog6jz259yCOKPDXPm787Q5t9h2zV3Ml1i0eWhC cdRYiWHQ5g20W4Bq3GsCAwEAAaAsMCoGCSqGSIb3DQEJDjEdMBswGQYDVR0RBBIw EIEOdGVzdEB0ZXN0LnRlc3QwDQYJKoZIhvcNAQELBQADgYEAZGPA0Jyw49cHPJjG bloqKAPNlBO200AiRFsHnkOQ1DopJff3mW+FdszAc9g6rTB4/YAiM4r0E314e0vm XSlW2q8sp+c2XJO7PUIdJIuAUnvSmMb/uwXFP2SzLdjLcmymMsnFfjvwkht0K2it O5HuUDuhLnxEimGlUEBrfkdrsH0= -----END CERTIFICATE REQUEST-----
Note how the output is very similar to that of the last example; the only change is toward the end where the encoding of the attribute data for the certification request has been added.
How It Works
I mentioned earlier that the extensionRequest attribute was defined in PKCS #9 and that the attribute value associated with it was defined as follows:
ExtensionRequest ::= Extensions
where the Extensions structure is the same one you saw defined for the TBSCertificate structure earlier.
Now if you look back at the example you will see that the lines
Vector oids = new Vector(); Vector values = new Vector(); oids.add(X509Extensions.SubjectAlternativeName); values.add(new X509Extension(false, new DEROctetString(subjectAltName))); X509Extensions extensions = new X509Extensions(oids, values);
are creating the Extensions structure referred to previously, and then an appropriately labeled Attribute structure is created to carry the Extensions structure in the lines
Attribute attribute = new Attribute( PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, new DERSet(extensions));
This is then passed into the constructor for the PKCS10CertificationRequest object wrapped in a DERSet object so that the resulting value can be used to fill in the attributes field in the PKCS #10 CertificationRequestInfo structure.
Of course, in real life, a CA would probably do more than just run some code to perform this operation. The CA should at least make sure the person requesting the additional extensions has the right to have them in place and that any identifying information included in extensions such as the SubjectAltName extension is actually correct. However, from the point of view of the technology, you can see how it is done.
Now that you know how to create certification requests and add attributes to them as required, it is time to look at how the process works in full, and how certificate chains are created as a result of it. I'll discuss that now as you look at writing a basic CA.
Writing a Simple Certificate Authority
Now you will look at taking a certification request and using it and an existing root certificate to generate a new certificate. As you can see from Figure 6-2, this is essentially what a certificate authority does in its most basic form.
Figure 6-2
Creating a certificate with a signature that can be verified by another certificate creates a relationship between the two certificates that is normally expressed in terms of a certificate path or a certificate chain, and the validation of these certificate paths is one of the things that various profiles concern themselves with. In Java terms the simplest way to think of a certificate path is as an array of certificates, with the first certificate in the array being the end entity certificate, whose signature can be verified by the next certificate in the array, and the last certificate in the array is the root certificate, which is normally self-signed and has to be accepted on trust.
In X.509 terms there are a couple of steps to finding out which certificate has signed which. First you would expect if one certificate can be used to verify another one, that same certificate will have a subject that is equal to the issuer on the certificate being verified. Second, you would obviously expect that, given that the subject of any certificate is the issuer of another, the key on the first certificate will validate the signature on the second. Of course, an issuer may have issued several certificates, so another two extensions are provided with X.509 version 3, which can be used to make it easier to work out, given an unordered set of certificates, what order the certificates are meant to be in. The extensions are the AuthorityKeyIdentifier and the SubjectKeyIdentifier .
The OID and value of the AuthorityKeyIdentifier extension are defined as follows :
id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 } AuthorityKeyIdentifier ::= SEQUENCE { keyIdentifier [0] KeyIdentifier OPTIONAL, authorityCertIssuer [1] GeneralNames OPTIONAL, authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL } KeyIdentifier ::= OCTET STRING
As you can see, the kind of information contained in the AuthorityKeyIdentifier is what you would imagine would be associated with the issuer of the certificate it is attached to. For example, setting the authorityCertIssuer and authorityCertSerialNumber to the issuer and serial number of the verifying certificate is usually enough to identify the issuer's certificate uniquely. The keyIdentifier field allows the issuer's certificate to be identified in another way as well ”by associating an OCTET STRING with the key that is in the verifying certificate. You can make use of this information to find the issuer's certificate if the issuer's certificate includes the SubjectKeyIdentifier extension.
The OID and value of the SubjectKeyIdentifier extension are defined as follows:
id-ce-subjectKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 14 } SubjectKeyIdentifier ::= KeyIdentifier
This value can be anything, but two currently recommended approaches are as follows:
- q Calculate the 160-bit SHA-1 hash of the value of the BIT STRING field subjectPublicKey, excluding the tag value, length, and number of pad bits.
- q Perform the same operation as previously; however, use only the least significant 60 bits of the SHA-1 value and prepend the bit string 0100 to the start of it, giving a 64-bit number.
People also use other ways of generating the bytes, such as a sequence of monotonically increasing integers; it doesn't really matter. The important thing is that a clash is highly unlikely , and if the keyIdentifier field in an AuthorityKeyIdentifier is used, it is taken from the value of the SubjectKeyIdentifier of the issuer certificate corresponding to the AuthorityKeyIdentifier .
The PKIX profile, described in RFC 3280, specifies that while neither of these extensions should be marked as critical, they should be included in any certificate generated. As you can see, used properly, the two extensions provide essentially the same facility as pointers, but in an X.509 landscape, and make building certificate chains on the fly a lot easier.
Try It Out: Creating a Certificate from a Certification Request
This example takes the process one step farther and creates a certificate. In it two key pairs get created: one that is used to create a certification request using the same method as the last example; the other that is used to create a root certificate using use a method defined in an earlier example ("Try It Out: Creating a Self-Signed Version 1 Certificate"). The certificate that gets created from the certification request is a version 3 certificate containing the extensions used in the version 3 creation Try It Out ("Creating a Self-Signed Version 3 Certificate") plus two more. The extra extensions are the AuthorityKeyIdentifier and the SubjectKeyIdentifier , which, as you have read, are required for RFC 3280 compliance.
package chapter6; import java.io.OutputStreamWriter; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Enumeration; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.pkcs.Attribute; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; /** * An example of a basic CA. */ public class PKCS10CertCreateExample { public static X509Certificate[] buildChain() throws Exception { // create the certification request KeyPair pair = Utils.generateRSAKeyPair(); PKCS10CertificationRequest request = PKCS10ExtensionExample.generateRequest(pair); // create a root certificate KeyPair rootPair = Utils.generateRSAKeyPair(); X509Certificate rootCert = X509V1CreateExample.generateV1Certificate(rootPair); // validate the certification request if (!request.verify("BC")) { System.out.println("request failed to verify!"); System.exit(1); } // create the certificate using the information in the request X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); certGen.setIssuerDN(rootCert.getSubjectX500Principal()); certGen.setNotBefore(new Date(System.currentTimeMillis())); certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000)); certGen.setSubjectDN(request.getCertificationRequestInfo().getSubject()); certGen.setPublicKey(request.getPublicKey("BC")); certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(rootCert)); certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(request.getPublicKey("BC"))); certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false)); certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature KeyUsage.keyEncipherment)); certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)); // extract the extension request attribute ASN1Set attributes = request.getCertificationRequestInfo().getAttributes(); for (int i = 0; i != attributes. size (); i++) { Attribute attr = Attribute.getInstance(attributes.getObjectAt(i)); // process extension request if (attr.getAttrType().equals( PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) { X509Extensions extensions = X509Extensions.getInstance( attr.getAttrValues().getObjectAt(0)); Enumeration e = extensions.oids(); while (e.hasMoreElements()) { DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); X509Extension ext = extensions.getExtension(oid); certGen.addExtension(oid, ext.isCritical(), ext.getValue().getOctets()); } } } X509Certificate issuedCert = certGen.generateX509Certificate( rootPair.getPrivate()); return new X509Certificate[] { issuedCert, rootCert }; } public static void main(String[] args) throws Exception { X509Certificate[] chain = buildChain(); PEMWriter pemWrt = new PEMWriter( new OutputStreamWriter(System.out)); pemWrt.writeObject(chain[0]); pemWrt.writeObject(chain[1]); pemWrt.close(); } }
Run this and you should see the program print out the two PEM-encoded certificates. The first one will be much larger, being the issued certificate and carrying the necessary extensions to indicate its usage and origins. The second one will appear a lot smaller, as it is a version 1 certificate with no extensions attached.
How It Works
The example proceeds through a number of steps. The createChain() method starts out pretending it is a client and then starts to behave as a CA instead.
First, in "client mode," a certification request is created; then, in "CA mode," a root certificate is created.
Next, still in CA mode, the certification request is validated and then the client certificate is created. This step introduces a couple of new classes as well: AuthorityKeyIdentifierStructure and SubjectKeyIdentifier structure, both of which are defined in the org.bouncycastle.x509. extension package.
The AuthorityKeyIdentifierStructure class is a helper class that allows you to create the necessary structure for the value field in an AuthorityKeyIdentifier extension. Like the other extension objects you looked at previously, it generates an ASN1Encodable object, which is then encoded as an OCTET STRING internally and added to the certificate. The SubjectKeyIdentifierStructure object is also a helper class and calculates a KeyIdentifier for the passed in public key using the SHA-1 method and returns it wrapped in an OCTET STRING suitable for re-encoding into a SubjectKeyIdentifier extension value. You add the AuthorityKeyIdentifier extension so you have a link with the certificate that can be used to verify you. The SubjectKeyIdentifier gets added in case anyone needs to create a link back to your certificate in the future.
Once the AuthorityKeyIdentifier, SubjectKeyIdentifier , and other extensions have been added, the example then extracts the attributes from the certification request and looks for an extensionRequest attribute. If it finds one, it then creates an X509Extensions object by extracting the value from the attribute set and iterates through it, retrieving each extension as an org.bouncycastle.asn1. x509.X509Extension object and adding it to the certificate generator using the addExtension() method that takes a DER-encoded byte array. Too easy!
Well, in some ways it is too easy. In real life you would probably want to do a bit more. To start with, the CA code isn't checking that it is overwriting one of its own extensions when it adds one from the user . Nor is any validation being done on the contents of the extension being added. As mentioned earlier with extensions that contain identifying information on the owner of the certificate, it is worth making sure the identifying information is for the person you think it is and that the information inserted is only what is acceptable. For example, you might allow your local users to add their e-mail addresses to their certificates if they want to. So the first form of validation you would want to do on the SubjectAltName extension is make sure the only value in it is an rfc822Name , and then you would want to check that it is an e-mail address that makes sense. If you are running a business, you probably don't want to issue certificates to your staff that tell people that the staff member's private business e-mail address is evil@evil_competitor.com, do you?
After the extensions are parsed and added in the certification request, the resulting certificate chain, consisting of the root certificate and the client certificate, is printed out. In the normal course of events this is what would be sent by the CA to the client; the only difference is that in the real world, clients would never need to expose their private keys. The CA would also normally use the same private key and root certificate for signing a number of certificates and may have a number of intermediate certificates resulting in a certificate path, or chain, similar to the one seen in Figure 6-3.
Figure 6-3
Certificate Paths and Stores
Dealing with collections of certificates that are dependent on each other in some ways is the rule rather than the exception, and the JCA has two classes that can help you read, manipulate, and analyze collections of certificates. The first one, the CertPath class, provides you with a more useful alternative to using arrays for representing certificate chains that supports encoding and can be used with the path validation classes. The other one, the CertStore class, provides you with a collection class that can be searched in a variety of ways using implementations of a selection interface CertSelector .
The CertPath Class
The java.security.cert.CertPath class provides a carrier for certificate chains, or paths. Objects of the class are not created directly; instead, you use one of the CertificateFactory.generateCertPath() methods . The CertPath class also provides overrides for the Object.equals() and Object.hashCode() methods, which allow CertPath objects representing the same certificate paths to be meaningfully compared and categorized.
There are three generateCertPath() methods on the CertificateFactory class. The first one takes an InputStream as a parameter and expects the stream to contain the certificate path encoded in whatever the default encoding is for the factory. The second allows you to specify an alternative encoding if the factory object supports it. The third one simply takes a list of certificates.
You can find out what encodings are supported for reading and writing certificate paths by calling the CertificateFactory.getCertPathEncodings() method on the factory object you are using. The method returns an Iterator of the names of the available encodings, with the first encoding in the Iterator being the default one.
CertPath.getType()
The getType() method returns the type of the certificate contained in the CertPath . In this situation you would expect this to be "X.509", but there are other proposals out there, so this may change.
CertPath.getCertificates()
The getCertificates() method returns an immutable, possibly empty, list of certificate objects representing the certificates making up the certificate path.
CertPath.getEncoded()
There are two getEncoded() methods. The first takes no parameters and simply returns a byte array representing the certificate path using the default encoding for the CertPath object. The second takes a String representing an encoding format name as a parameter and returns a byte array containing the certificate path encoded using the requested format if it is available. The getEncoded() methods can throw a CertificateEncodingException if a problem occurs during the encoding or the requested encoding format is not available.
CertPath.getEncodings()
The getEncodings() method returns an Iterator containing String objects giving the names of the encoding methods supported by the CertPath object. Typically names are "PKIPATH, PEM," or "PKCS7."
Now check out an example using the PEM format.
Try It Out: Writing a CertPath
Look at the following example. You are already familiar with the PEM format, as it was used in the last Try It Out ("Creating a Certificate from a Certification Request"). This example produces the same encoding for the certificate path, but by using the CertPath class instead.
package chapter6; import java.io.ByteArrayInputStream; import java.security.cert.CertPath; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; /** * Basic example of creating and encoding a CertPath. */ public class CertPathExample { public static void main(String[] args) throws Exception { X509Certificate[] chain = PKCS10CertCreateExample.buildChain(); // create the factory and path object CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC"); CertPath certPath = fact.generateCertPath(Arrays.asList(chain)); byte[] encoded = certPath.getEncoded("PEM"); System.out.println(Utils.toString(encoded)); // re-read the CertPath CertPath newCertPath = fact.generateCertPath( new ByteArrayInputStream(encoded), "PEM"); if (newCertPath.equals(certPath)) { System.out.println("CertPath recovered correctly"); } } }
Running the example will print the two certificates out as it did for the create example, showing you that a PEM-encoded path has been created. After the certificates are listed it should then print
CertPath recovered correctly
This output shows that the certificate path was re-created correctly from the encoded form.
How It Works
The example is quite simple. As you can see, both CertPath classes are created using the generateCertPath() method on the CertificateFactory class. In the first case you are just converting the array generated by PKCS10CertCreateExample.buildChain() into a list and then passing it to the generateCertPath() method to get back a CertPath object.
After that you encode the certificate path into "PEM" format, print the result, and then build a new CertPath object by using the generateCertPath() method that takes an InputStream and an encoding. The recovered CertPath is then compared with the original one using Object.equals() . As the message is printed, you can see that Object.equals() has been overridden, and you know that the path has been reconstructed correctly.
As you will see in Chapter 7, this is not the final word on the CertPath class; however, the next thing you will look at is another useful class for manipulating individual certificates ”the CertStore class.
The CertStore Class
One of the purposes of the CertStore class is used as a means to provide access to certificates through the provider-based architecture in the JCA. Creating a CertStore involves using a similar getInstance() factory pattern used by other classes in the JCA, and the CertStore.getInstance() follows the same precedence rules as other JCA getInstance() methods if the provider is not specified. In addition to the type of CertStore to create and an optional provider, the CertStore.getInstance() method also takes parameters that are used to initialize the CertStore . You'll look at how the parameters object is typically created in the next example.
The feature of the CertStore class I will concentrate on here is that the getCertificates() method provides a mechanism for arbitrarily searching for and retrieving certificates from a CertStore object. The getCertificates() method is used by invoking it with an object implementing the CertSelector interface as a parameter. It is the object implementing CertSelector that is used by the CertStore to determine which certificates are being searched for.
The CertSelector interface carries two methods on it. One is Object.clone() , so any implementation of a CertSelector should be cloneable. 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 certificate is one of those it is looking for. As X.509 certificates are so commonplace, the JCA provides an implementation of CertSelector that can be used for retrieving X509Certificate objects from a CertStore . The class that provides this implementation is the X509CertSelector class.
The X509CertSelector Class
The java.security.cert.X509CertSelector class provides an implementation of the CertSelector interface that allows you to search a CertStore for certificates 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 an X.509 certificate that might be present in a CertStore object.
The X509CertSelector works by offering a range of set() methods, each of which can be used to provide values to be matched against in a X.509 certificate. If no set() methods are called, an X509CertSelector will match every certificate in the store. If more than one set() method is called, the X509CertSelector will only match certificates for whichever criteria specified via a set() method is matched.
I'll describe the most basic methods here; however, if you look at the JavaDoc for the class, you will see that the X509CertSelector also allows you to search on a variety of certificate extensions, including, but not limited to, SubjectKeyIdentifier, AuthorityKeyIdentifier, KeyUsage, SubjectAltName, and IssuerAltName.
X509CertSelector.set Certificate()
The setCertificate() method allows you to set up the selector so that it matches only the passed-in certificate. This is very useful if you want to easily tell if a given certificate is present in a CertStore.
X509CertSelector.set Issuer()
The setIssuer() method allows you to match certificates with the issuer field of the TBSCertificate structure set to the passed-in issuer. If the method is set to null , it matches any issuer.
There are two versions of setIssuer() worth using. The first takes an X500Principal directly but is only available in JDK 1.5 and later. The other one takes a byte array representing the DER encoding of the X.500 name you are looking for. If you are using a JVM earlier than JDK 1.5, the latter method is the one you should use.
X509CertSelector.set SerialNumber()
The setSerialNumber() takes a BigInteger as a parameter and will match any certificate with the same value in the serialNumber field of the TBSCertificate structure. If passed a null (the default), it will match any serial number.
X509CertSelector.set Subject()
The setSubject() method allows you to match certificates with the subject field of the TBSCertificate structure set to the passed-in subject. If the method is set to null , it matches any subject. The setSubject() method offers the same alternatives as setIssuer() in terms of possible parameters, with the same restrictions on JVM version.
Try It Out: Using a CertStore and a X509CertSelector
This example shows the use of a basic CertStore and a X509CertSelector . It uses the PKCS10CreateExample.createChain() method to generate the certificates again, but in this case it uses the certificate array to create a CertStore , which it then searches using an X509CertSelector .
package chapter6; import java.security.cert.CertStore; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Iterator; import javax.security.auth.x500.X500Principal; /** * Example using a CertStore and a CertSelector */ public class CertStoreExample { public static void main(String[] args) throws Exception { X509Certificate[] chain = PKCS10CertCreateExample.buildChain(); // create the store CollectionCertStoreParameters params = new CollectionCertStoreParameters( Arrays.asList(chain)); CertStore store = CertStore.getInstance("Collection", params); // create the selector X509CertSelector selector = new X509CertSelector(); selector.setSubject( new X500Principal("CN=Requested Test Certificate").getEncoded()); // print the subjects of the results Iterator certsIt = store.getCertificates(selector).iterator(); while (certsIt.hasNext()) { X509Certificate cert = (X509Certificate)certsIt.next(); System.out.println(cert.getSubjectX500Principal()); } } }
Running the example should just produce the following output:
CN=Requested Test Certificate
indicating, as you would expect, that the end entity certificate in the chain has been matched by the selector.
How It Works
Like the previous example this one also follows a simple formula. The certificate chain is created and used to create a list. The list is turned into a suitable parameters object and used to create a CertStore object. A X509CertSelector is then created with a specific subject set on it and is used to retrieve one of the certificates present in the CertStore .
The parameters object you are using in this case, an instance of a CollectionCertStoreParameters class, is the simplest case for creating a CertStore . The CollectionCertStoreParameters class implements the java.security.cert.CertStoreParameters interface and just serves as a carrier for a collection of certificates, and possibly CRLs, into the underlying provider to create the CertStore . The CertStoreParameters interface is basically a marker interface. It also adds Cloneable, and it is this marker interface that the construction process for a CertStore looks for when it is receiving parameters. This flexibility is required, as a CertStore may have a database or a server process behind it, not just a collection of in memory certificates. For another example of CertStoreParameters , for use with an LDAP server if supported by the provider, have a look at java.security.cert.LDAPCertStoreParameters . You can probably imagine other ways to implement the CertStoreParameters interface so that it works with your favorite database instead.
This brings you to the end of the basics for dealing with certificates, most especially those defined in X.509 and used in the Internet PKIX profile as described in RFC 3280.
Summary
In this chapter, you looked at the fundamentals for understanding and using public key certificates based around the X.509 standard. You saw how certificate extensions are used and studied one of the mechanisms used for creating certificate requests , detailed in PKCS #10. In addition, you saw how dependencies can exist between certificates and how these are represented using certificate paths.
Over the course of this chapter you have learned the following:
- What an X.500 name is and how it relates to the X500Principal class
- What a public key certificate is, most particularly ones that use X.509
- How to make use of the Java classes and interfaces relating to certificates
- How to generate your own PKCS #10 certification requests and X.509 certificates using the Bouncy Castle APIs
- How to create PEM encodings of ASN.1-encoded objects
- How to interpret a PKCS #10 certification request and create a certificate from it, as well as some of the issues that need to be considered when you are doing so
- How to create CertPath objects and use them to compare and encode certificate paths
Finally, you also saw how to make use of the CertStore API and use implementations of CertSelector to selectively retrieve certificates from a CertStore .
Certificates provide you with a mechanism of publishing your identity, and by allowing for certificate chains, or paths, you are able to get others to vouch that a certificate you want them to accept is recognized by someone they trust. The problem then becomes what do you do if the private key on the certificate you have issued is compromised? Perhaps it turns out with another certificate you have signed, the signature was gained by deception. How do you have that certificate revoked ? Having allowed for revocation, how do you then tell that a certificate you have obtained from someone else is still valid? If one of the certificates in the path from the root certificate you trust to the end certificate you want to trust is no longer valid, how would you know? I will answer these questions in the next chapter, which discusses certificate revocation and path validation.
Exercises
1. |
What is the biggest danger of trying to manipulate X.500 names as String objects in Java? |
|
2. |
How do you add an e-mail address for a certificate subject to a version 3 X.509 certificate? |
|
3. |
How do you request the CA to add a certificate extension when you issue a certification request? |
|
4. |
What is a certificate path , or chain? What roles do the root certificate and the end entity certificates play, respectively? |
|
5. |
What is the easiest way to generate a particular type of encoding for a certificate path? |
|
Answers
1. |
What is the biggest danger of trying to manipulate X.500 names as String objects in Java? Some application software expects the X.500 name to have a specific encoding when it processes it, and converting from an encoded DN to String and back can lose this information. The reason this happens is that while several ASN.1 string types can be transformed into a Java String trivially, there is no way to determine which of those ASN.1 string types were used originally if you want to transform back the other way. |
2. |
How do you add an e-mail address for a certificate subject to a version 3 X.509 certificate? You should add the e-mail address to a SubjectAlt Name extension attached to the certificate. Use the GeneralName choice item for the rfc822 Name. |
3. |
How do you request the CA to add a certificate extension when you issue a certification request? Create an extensionRequest attribute and add an Extensions structure containing the extensions you want as the contents of the attribute value set. This attribute is then added to the attribute set that is carried with the certification request and processed by the CA. |
4. |
What is a certificate path, or chain? What roles do the root certificate and the end entity certificates play, respectively? A certificate path, or chain, represents a linkage from an end entity certificate back to a root certificate where each certificate in the chain is signed by the next one after it. The root certificate is generally self-signed and has to be accepted on trust. It is the certificate the others derive their validity from. The end entity certificate cannot be used to validate any other members of the chain and is the certificate you are trying to validate when the chain is examined. |
5. |
What is the easiest way to generate a particular type of encoding for a certificate path? Convert the path, or chain, into a CertPath object and then use the CertPath.getEncoded() method requesting the format desired. You can find out which formats the CertPath objects of a particular provider can generate by calling CertPath.getEncodings() . |