Book Home Java Security Search this book

10.4. Certificates

When you are given a public and private key, you often need to provide other people with your public key. If you sign a digital document (using your private key), the recipient of that document will need your public key in order to verify your digital signature.

The inherent problem with a key is that it does not provide any information about the identity to which it belongs; a key is really just a sequence of seemingly arbitrary numbers. If I want you to accept a document that I digitally signed, I could mail you my public key, but you normally have no assurance that the key (and the original email) came from me at all. I could, of course, digitally sign the e-mail so that you knew that it came from me, but there's a circular chain here--without my public key, you cannot verify the digital signature. You would need my public key in order to authenticate the public key I've just sent you.

Certificates solve this problem by having a well-known entity (called a certificate authority, or CA) verify the public key that is being sent to you. A certificate can give you the assurance that the public key in the certificate does indeed belong to the entity that the certificate authority says it does. However, the certificate only validates the public key it contains: just because Fred sends you his public key in a valid certificate does not mean that Fred is to be trusted; it only means that the public key in question does in fact belong to Fred.

In practice, the key may not belong to Fred at all; certificate authorities have different levels at which they assess the identity of the entity named in the certificate. Some of these levels are very stringent and require the CA to do an extensive verification that Fred is who he says he is. Other levels are not stringent at all, and if Fred can produce a few dollars and a credit card, he is assumed to be Fred. Hence, one of the steps in the process of deciding whether or not to trust the entity named in the certificate includes the level at which the certificate authority generated the certificate. Each certificate authority varies in its approach to validating identities, and each publishes its approach to help you understand the potential risks involved in accepting such a certificate.

A certificate contains three pieces of information (as shown in Figure 10-2):

figure

Figure 10-2. Logical representation of a certificate

Because the certificate carries a digital signature of the certificate authority, we can verify that digital signature--and if the verification succeeds, we can be assured that the public key in the certificate does in fact belong to the entity the certificate claims (subject to the level at which the CA verified the subject).

We still have a bootstrapping problem here--how do we obtain the public key of the certificate authority? We could have a certificate that contains the public key of the certificate authority, but who is going to authenticate that certificate?

This bootstrapping problem is one reason why key management (see Chapter 11, "Key Management") is such a hard topic. Most Java-enabled browsers solve this problem by providing the public keys for certain well-known certificate authorities along with the browser. This has worked well in practice, though it clearly is not an airtight solution (especially when the browser is downloaded from some site on the Internet--theoretically, the certificates that come with the browser could be tampered with as they are in transit). Although there are various proposals to strengthen this model, for now we will assume that the certificate of at least one well-known certificate authority is delivered along with the Java application. This situation allows me to mail you a certificate containing my public key; if the certificate is signed by a certificate authority you know about, you are assured that the public key actually belongs to me.

There are many well-known certificate authorities--and therein lies another problem. I may send you a certificate that is signed by the United States Post Office, but that certificate authority may not be one of the certificate authorities you recognize. Simply sending a public key in a certificate does not mean that the recipient of the public key will accept it. A more important implication of this is that a key management system needs to be prepared to assign multiple certificates to a particular individual, potentially one from each of several certificate authorities.

Another implication of this profusion of certificate authorities is that certificates are often supplied as a chain. Let's say that you have the certificate of the U.S. Post Office certificate authority, and I want to send you my certificate that has been generated by the Acme Certificate company. In order for you to accept this certificate, I must send you a chain of certificates: my certificate (certified by the Acme Certificate company), and a certificate for the Acme Certificate company (certified by the U.S. Post Office). This chain of certificates may be arbitrarily long.

The last certificate in this chain--that is, the public key for a certificate authority--is generally stored in a certificate that is self-signed: the certificate authority has signed the certificate that contains its own public key. Self-signed certificates tend to crop up frequently in the Java world as well, since the tools that come with the JDK will create self-signed certificates. The certificates are intended to be submitted to a certificate authority, who will then return a CA-signed certificate. But there's no reason why the certificate itself can't be used as a valid certificate. Whether or not you want to accept a self-signed certificate is up to you, but it obviously carries certain risks.

Finally, for all this talk of certificates, you have to consider whether or not they are actually necessary to support your application. If you'll generally be receiving signed items from people you do not know (e.g., a signed JAR file from a web site), then they are absolutely necessary. On the other hand, large-scale computer installations often consider using certificates to authenticate and validate their employees; this results in a computer system that has much better internal security than one that relies solely on passwords. But it is not the certificate that generates the security advantage, it is the use of public key cryptography. The computer installation can achieve the same level of security without using a certificate infrastructure.

Consider the security necessary to support XYZ Corporation's payroll application. When an employee wants to view her payroll statements, she must submit a digitally signed request to do so. Hence, XYZ should distribute to each employee a private key to be used to create the digital signature. XYZ can also store the employee's public keys in a database; when a request comes that claims to be from a particular employee, the payroll server can simply examine the database to obtain that employee's public key and verify the signature. No certificate is required in this case--and in general, no certificate is required when the recipient of the digital signature is already known to have the public key of the entity that signed the data. For applications within a corporation, this is almost always the case.

We issue this caveat about certificates being necessary because certificate support in Java (even in Java 1.2) is not fully complete--while it is possible to set up your own certificate authority to distribute the certificates for your company, it's very hard to write the necessary code to do that in Java (at present). Hence, we'll focus our discussion of the certificate API on accepting (i.e., validating) existing certificates.

10.4.1. The Certificate Class

There are many formats that a certificate can take (depending on the cryptographic algorithms used to produce the certificate). Hence, the Java API abstracts the generic notion of a certificate with the Certificate class (java.security.cert.Certificate):

public abstract class Certificate figure

Provide the necessary (and very basic) operations to support a certificate.

Like many classes in the Java security package, the Certificate class is abstract; it relies upon application-specific classes to provide its implementation. In the case of the JDK, there are classes in the sun package that implement certain certificate formats (but more about that in just a bit).

There are three essential operations that you can perform upon a certificate:

public abstract byte[] getEncoded() figure

Return a byte array of the certificate. All certificates must have a format in which they may be transmitted as a series of bytes, but the details of this encoding format are specific to the type of the certificate. If the encoding cannot be generated, a CertificateEncodingException is thrown.

public abstract void verify(PublicKey pk) figure
public abstract void verify(PublicKey pk, String provider) figure

Verify that the certificate is valid. In order to verify a certificate, you must have the public key of the certificate authority that issued it; a valid certificate is one in which the signature of the certificate authority is valid. A valid certificate does not imply anything about the trustworthiness of the certificate authority or the subject to which the certificate belongs; it merely means that the signature in the certificate is valid for the supplied public key. If the certificate is invalid, this method throws a CertificateException.

The signature is verified according to the digital signature details we'll examine in Chapter 12, "Digital Signatures". The process of creating an object to verify the digital signature as well as the actual verification of the signature may thrown a NoSuchProviderException, a NoSuchAlgorithmException, an InvalidKeyException, or a SignatureException.

public abstract PublicKey getPublicKey() figure

Extract the public key from the certificate--that is, the key that belongs to the subject the certificate vouches for.

These are the basic operations that are valid for any certificate. Notice that while we can encode a certificate into a byte array in order to transmit the certificate, there is nothing in the basic API that allows us to create a certificate from such a byte array. In fact, there's no practical way to instantiate a certificate object at all; the Certificate class is usually used as a base class from which individual certificate types are derived. Fortunately, the next class allows us to import certificates.

10.4.2. The CertificateFactory Class

If you need to import a certificate into a program, you do so by using the CertificateFactory class (java.security.cert.CertificateFactory). That class is an engine class, and it has the following interface:

public static CertificateFactory getInstance(String type)figure
public static CertificateFactory getInstance(String type, String provider)figure

Return a certificate factory that may be used to import certificates of the specified type (optionally implemented by the given provider). A CertificateException will be thrown if the given factory cannot be found or created; if the given provider is not found, a NoSuchProviderException will be thrown. The default Sun security provider has one certificate factory that works with certificates of type X509.

public String getProvider()figure

Return the provider that implemented this factory.

public String getType()figure

Return the type of certificates that this factory can import.

public final Certificate generateCertificate(InputStream is)figure

Return a certificate that has been read in from the specified input stream. For the default Sun security provider, the input stream must be an X509 certificate in RFC 1421 format (that is, a DER-encoded certificate that has been translated into 7-bit ASCII characters); this is the most common format for transmission of X509 certificates.

public final Collection generateCertificates(InputStream is)figure

Return a collection of certificates that have been defined in the given input stream. For the default Sun provider, the input stream in this case may have a single RFC 1421 formatted certificate, or it may contain a certificate chain in PKCS#7 format.

public final CRL generateCRL(InputStream is)figure

Define a certificate revocation list from the data in the input stream.

public final Collection generateCRLs(InputStream is)figure

Define a collection of CRLs from the data in the input stream.

Note that the CertificateFactory class cannot generate a new certificate--it may only import a certificate from an input stream. This is one reason why it's hard to provide a certificate authority based solely on the standard Java API. In the next section, we'll see an example of reading a certificate through this interface.

The CertificateFactory is an engine class, so it has a companion SPI class--the CertificateFactorySpi class--that can be used if you want to implement your own certificate factory. Implementing such a class follows the familiar rules of engine classes: you must define a constructor that takes the type name as a parameter and then, for each of the public methods listed above, you must implement a corresponding engine method with the same parameters. Certificates are complicated things, and parsing their encoding is a complicated procedure, so we won't bother showing an example of the engine class.

10.4.3. The X509Certificate Class

As we mentioned, there are many certificate formats that could be in use by a key management system; one of the most common of these is the X509 format. X509 has gone through a few revisions; the version supported by the Java API is version 3. This format is an ANSI standard for certificates, and while there are PGP and other certificate formats in the world, the X509 format is dominant. This is the only format of certificate for which Java provides a standard API; if you want to support another certificate format, you must implement your own subclass of Certificate.

The X509Certificate class (java.security.cert.X509Certificate) is defined as follows:

public abstract class X509Certificate extends Certificate implements X509Extension figure

Provide an infrastructure to support X509 version 3 formatted certificates.

An X509 certificate has a number of properties that are not shared by its base class:

These properties can be retrieved with the following set of methods:

public abstract void checkValidity() figure
public abstract void checkValidity(Date d) figure

Check that the specified date (or today if no date is specified) is within the start and end dates for which the certificate is valid. If the specified date is before the start date of the certificate, a CertificateNotYetValidException is thrown; if it is after the end date of the certificate, a CertificateExpiredException is thrown.

public abstract int getVersion() figure

Return the version of the X509 specification that this certificate was created with. For the Sun implementation, this will be version 3.

public abstract BigInteger getSerialNumber() figure

Return the serial number of the certificate.

public abstract Principal getIssuerDN() figure

Extract the distinguished name of the certificate authority from the certificate and use that name to instantiate a principal object.

public abstract Principal getSubjectDN() figure

Extract the distinguished name of the subject entity in the certificate and use that name to instantiate a principal object.

public abstract Date getNotBefore() figure

Return the first date on which the certificate is valid.

public abstract Date getNotAfter() figure

Return the date after which the certificate is invalid.

From a programmatic view, these are the most useful of the attributes of a certificate. If your X509 certificate is contained in the file sdo.cer, you could import and print out information about the certificate as follows:

Class Definition

public class PrintCert {
	public static void main(String args[]) {
		try {
			FileInputStream fr = new FileInputStream("sdo.cer");
			CertificateFactory cf = 						
					   CertificateFactory.getInstance("X509");
			X509Certificate c = (X509Certificate) 
								cf.generateCertificate(fr);
		System.out.println("Read in the following certificate:");
			System.out.println("\tCertificate for: " +
									 c.getSubjectDN());
			System.out.println("\tCertificate issued by: " +
									 c.getIssuerDN());
			System.out.println("\tThe certificate is valid from " +
						c.getNotBefore() + " to " + c.getNotAfter());
			System.out.println("\tCertificate SN# " +
									 c.getSerialNumber());
			System.out.println("\tGenerated with " +
									 c.getSigAlgName());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Running this program would produce the following output:

Class Definition

Read in the following certificate:
	Certificate for:
		CN=Scott Oaks, OU=SMCC, O=Sun Microsystems, L=NY, S=NY, C=US
	Certificate issued by:
		CN=Scott Oaks, OU=SMCC, O=Sun Microsystems, L=NY, S=NY, C=US
	The certificate is valid from Sun Oct 19 11:40:24 EDT 1997 to
		Sat Jan 17 10:40:24 EST 1998
	Certificate SN# 3895020084
	Generated with SHA1withDSA

10.4.4. Advanced X509Certificate Methods

There are a number of other methods of the X509Certificate class. For the purposes of this book, these methods are not generally useful; they enable you to perform more introspection on the certificate itself. We'll list these methods here simply as a matter of record.

public abstract byte[] getTBSCertificate() figure

Get the DER-encoded TBS certificate. The TBS certificate is the body of the actual certificate; it contains all the naming and key information held in the certificate. The only information in the actual certificate that is not held in the TBS certificate is the name of the algorithm used to sign the certificate and the signature itself.

The TBS certificate is used as the input data to the signature algorithm when the certificate is signed or verified.

public abstract byte[] getSignature() figure

Get the raw signature bytes of the certificate. These bytes could be used to verify the signature explicitly (e.g., using the methods we'll describe in Chapter 12, "Digital Signatures") instead of relying upon the verify() method to do so.

public abstract String getSigAlgName() figure

Return the name of the algorithm that was used to sign the certificate. For the Sun implementation, this will always be SHA1withDSA.

public String getSigAlgOID() figure

Return the OID of the signature algorithm used to produce the certificate.

public abstract byte[] getSigAlgParams() figure

Return the DER-encoded parameters that were used to generate the signature. In general, this will return null, since the parameters are usually specified by the certificate authority's public key.

public abstract byte[] getIssuerUniqueID() figure

Return the unique identifier for the issuer of the certificate. The presence of a unique identifier for each issuer allows the names to be reused, although in general it is recommended that certificates not make use of the unique identifier.

public abstract byte[] getSubjectUniqueID() figure

Return the unique identifier for the subject of the certificate (again, this is unused in general).

public abstract BitSet getKeyUsage() figure

Return the key usage extension, which defines the purpose of the key: the key may be used for digital signing, nonrepudiation, key encipherment, data encipherment, key agreement, certificate signing, and more. The key usage is an extension to the X509 specification and need not be present in all X509 certificates.

public abstract int getBasicConstraints() figure

An X509 certificate may contain an optional extension that identifies whether the subject of the certificate is a certificate authority. If the subject is a CA, this extension returns the number of certificates that may follow this certificate in a certification chain.

10.4.5. Revoked Certificates

Occasionally, a certificate authority needs to revoke a certificate it has issued--perhaps the certificate was issued under false pretenses, or maybe the user of the certificate has engaged in illegal conduct using the certificate. Under circumstances such as these, the expiration date attached to the certificate is insufficient protection; the certificate must be immediately invalidated.

This invalidation occurs as the result of a CRL--a certificate revocation list. Certificate authorities are responsible for issuing certificate revocation lists that contain (predictably) a list of certificates the authority has revoked. Validators of certificates are required to consult this list before accepting the validity of a certificate.

Unfortunately, the means by which an authority issues a CRL is one of those areas that is in flux, and while the interfaces to support revoked certificates have been established, they are not completely integrated into most certificate systems. In particular, the validate() method of the Certificate class does not automatically consult any CRL. The CRL itself is typically obtained in an out-of-band fashion (just as the certificates of the authority were obtained); once you have a CRL, you can check to see if a particular certificate in which you are interested is on the list.

While the notion of revoked certificates in not necessarily specific to an X509 certificate, the Java implementation is. Revoked certificates themselves are represented by the X509CRLEntry class (java.security.cert.X509CRLEntry):

public abstract class X509CRLEntry implements X509Extensionfigure

The methods of this class are simple and are based upon the fields present in a revoked X509 certificate:

public abstract BigInteger getSerialNumber() figure

Return the serial number of the revoked certificate.

public abstract Date getRevocationDate() figure

Return the date on which the certificate was revoked.

public abstract boolean hasExtensions() figure

Indicate whether the implementation of the class has any X509 extensions.

Revoked certificates are modeled by the X509CRL class (java.security.cert.X509CRL):

public abstract class X509CRL implements X509Extension figure

Provide the support for an X509-based certificate revocation list.

Instances of the X509CRLEntry class are obtained by the getInstance() method of the CertificateFactory. Once the class has been instantiated, you may operate upon it with these methods. As you can see, there is a strong synergy between the methods that are used to operate upon an X509 certificate and those used to operate upon a CRL:

public abstract void verify(PublicKey pk) figure
public abstract void verify(PublicKey pk, String sigProvider) figure

Verify that the signature that accompanied the CRL is valid (based on the standard signature verification we'll look at in Chapter 12, "Digital Signatures"). The public key should be the public key of the certificate authority that issued the CRL.

An error in the underlying signature object may generate a NoSuchAlgorithmException, a NoSuchProviderException, an InvalidKeyException, or a SignatureException.

public abstract int getVersion() figure

Return the version of the CRL. The present version of the X509 CRL specification is 2.

public abstract Principal getIssuerDN() figure

Extract the distinguished name of the issuer of the CRL and return a principal object that contains that name.

public abstract Date getThisUpdate() figure

Extract and return the date when the authority issued this CRL.

public abstract Date getNextUpdate() figure

Extract and return the date when the authority expects to issue its next CRL. This value may not be present in the CRL, in which case null is returned.

public abstract X509CRLEntry getRevokedCertificate(BigInteger bn)figure

Instantiate and return a revoked certificate object based on the given serial number. If the serial number is invalid, a CRLException is thrown.

public abstract Set getRevokedCertificates() figure

Instantiate a revoked certificate object for each certificate in the CRL and return the set of those objects. This method may throw a CRLException.

public abstract byte[] getEncoded() figure

Return the DER-encoded CRL itself. This method may throw a CRLException.

public abstract byte[] getTBSCertList() figure

Return the DER-encoded TBS certificate list--that is, all the data that came with the CRL aside from the name of the algorithm used to sign the CRL and the digital signature itself. This data can be used to verify the signature directly. Parsing of the underlying data may throw a CRLException or an X509ExtensionException.

public abstract byte[] getSignature figure

Return the actual bytes of the signature.

public abstract String getSigAlgName() figure

Return the name of the signature algorithm that was used to sign the CRL.

public abstract String getSigAlgOID() figure

Return the OID string of the signature algorithm that was used to sign the CRL.

public abstract byte[] getSigAlgParams() figure

Return the DER-encoded algorithms used in the signature generation. This generally returns null, as those parameters (if any) usually accompany the authority's public key.

There is one more method of the X509CRL class, which it inherits from its superclass, the CRL class (java.security.cert.CRL):

public abstract boolean isRevoked(Certificate c)figure

Indicate whether or not the given certificate has been revoked by this CRL.

When all is said and done, the point of the CRL class (and the revoked certificate class) is to provide you with the tools necessary to see if a particular certificate has been invalidated. This checking is up to your application to perform; you might choose to implement it as follows:

Class Definition

public Certificate importCertificate(byte data[])
									throws CertificateException {
	X509Certificate c = null;
	try {
		CertificateFactory cf = CertificateFactory.getInstance("X509");
		ByteArrayInputStream bais = new ByteArrayInputStream(data);
		c = (X509Certificate) cf.generateCertificate(bais);
		Principal p = c.getIssuerDN();
		PublicKey pk = getPublicKey(p);
		c.verify(pk);
		InputStream crlFile = lookupCRLFile(p);
		cf = CertificateFactory.getInstance("X509CRL");
		X509CRL crl = (X509CRL) cf.generateCRL(crlFile);
		if (crl.isRevoked(c))
			throw new CertificateException("Certificate revoked");
	} catch (NoSuchAlgorithmException nsae) {
		throw new CertificateException("Can't verify certificate");
	} catch (NoSuchProviderException nspe) {
		throw new CertificateException("Can't verify certificate");
	} catch (SignatureException se) {
		throw new CertificateException("Can't verify certificate");
	} catch (InvalidKeyException ike) {
		throw new CertificateException("Can't verify certificate");
	} catch (CRLException ce) {
		// treat as no crl
	}
	return c;
}

This method encapsulates importing a certificate and checking its validity. It is passed the DER-encoded data of the certificate to check (this data must have been read from a file or other input stream, as we showed earlier). Then we consult the certificate to find out who issued it, obtain the public key of the issuer, and validate the certificate. Before we return, however, we obtain the latest CRL of the issuing authority and ensure that the certificate we're checking has not been revoked; if it has been, we throw a CertificateException.

We've glossed over two details in this method: how we obtain the public key of the authority that issued the certificate, and how we get the CRL list associated with that authority. Implementing these methods is the crux of a key/certificate management system, and we'll show some ideas on how to implement the key lookup in Chapter 11, "Key Management". Obtaining the CRL is slightly more problematic, since you must have access to a source for the CRL data. Once you have that data, however, it's trivial to create the CRL via the generateCRL() method.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.