Feb 16, 2001 12:00 AM PT

Construct secure networked applications with certificates, Part 2

Learn to use X.509 certificates

To build secure applications, you must learn the tools of the trade. To help familiarize you with these concepts, I introduced you to public-key cryptography in Part 1 and explained how it avoids the key-exchange problems that accompany secret-key cryptography. I also explored the relationship between trust and the scalability of public-key cryptography, and explained how certificates and a public-key infrastructure (PKI) enable trust on a wider scale than public-key cryptography can achieve on its own. Finally, I described certificates and certificate chains, and explained how they relate to CAs (certificate authorities).

Many different flavors of certificates are available, including SDSI (simple distributed security infrastructure), PGP (pretty good privacy), and X.509. This month, to further expand your security vocabulary, I will describe the certificate format that leads the pack and is a key component of the emerging PKI standards: the X.509 certificate.

You can read the whole series on certificates:

The X.509 format in detail

The International Telecommunication Union (ITU) developed and published the X.509 certificate format, which was selected by the Public Key Infrastructure X.509 (PKIX) working group of the Internet Engineering Task Force (IETF). If acronyms indicate strength, X.509 clearly has powerful allies.

Using a notation called ASN.1 (Abstract Syntax Notation One), the X.509 standard defines a certificate's format. ASN.1 is a standardized language that describes abstract data types in a platform-independent manner.

The "Internet X.509 Public Key Infrastructure -- Certificate and CRL Profile" document (see Resources for a link) published by the PKIX working group describes an X.509 certificate format in terms of ASN.1 notation. It's a fascinating read if you're interested in that sort of thing.

A data type -- such as a certificate -- defined in ASN.1 isn't useful until it can unambiguously define how to represent an instance of a data type as a series of bits. To give the data type that functionality, ASN.1 uses the Distinguished Encoding Rules (DER), which define how to uniquely encode any ASN.1 object.

With a copy of an X.509 certificate's ASN.1 definition and a knowledge of the DER, you can write a Java application that will read and write X.509 certificates and interoperate with similar applications written in other programming languages. Luckily, you will probably never have to go to that much trouble because the Java 2 Platform, Standard Edition (J2SE) comes with built-in support for X.509 certificates.

X.509 for (almost) nothing

All of the certificate-related classes and interfaces reside in the package java.security.cert. Like the other members of Sun's family of security APIs, the certificate package was designed around the factory paradigm, in which one or more Java classes define a generic interface to a package's intended functionality. The classes are abstract, so applications cannot instantiate them directly. Instead, a factory class's instance creates and returns instances of the abstract classes' particular subtypes. The factory paradigm circumvents Java's strong typing, but in return, permits the code to run without recompilation in a broader range of environments.

The java.security.cert.Certificate and java.security.cert.CRL abstract classes define the interface. They represent certificates and certificate revocation lists (CRLs), respectively. The CertificateFactory class is their factory.

The java.security.cert package contains concrete implementations of the Certificate and CRL abstract classes: the X509Certificate and X509CRL classes. These two classes implement basic certificate and CRL functionality, then extend it with X.509-specific functionality. When a CertificateFactory instance returns an instance of either class, a program can either use it as-is or explicitly cast it to the X.509 form.

In the java.security.cert package, interface X509Extension defines an interface to an X.509 certificate's extensions. Extensions are optional components that provide a mechanism for certificate creators to associate additional information with a certificate. For example, a certificate may use the KeyUsage extension to indicate that it can be used for code signing.

The java.security.cert package also includes a Service Provider Interface (SPI) class. A cryptographic service provider that wishes to support a certificate type extends the SPI. Java 2 comes with an SPI for X.509 certificates.

Let's take a more detailed look at the classes and interfaces in the java.security.cert package. For brevity's sake, I will discuss only the most useful methods. For more comprehensive coverage, I encourage you to read Sun's documentation. (See Resources.)

java.security.cert.CertificateFactory

The story begins with java.security.cert.CertificateFactory. The CertificateFactory class has static methods that create a CertificateFactory instance for a specific type of certificate, and methods that create both certificates and CRLs from data supplied in an input stream. I will briefly describe the most important methods, then explain how to use these methods when generating X.509 certificates and CRLs. Later in the article, I'll present code that demonstrates the methods in action.

  • public static CertificateFactory getInstance(String stringType) and public static CertificateFactory getInstance(String stringType, String stringProvider) instantiate and return an instance of a certificate factory for the certificate type specified by the stringType parameter. For example, if the value of stringType is the string "X.509," both methods will return an instance of the CertificateFactory class suitable for creating instances of the classes X509Certificate and X509CRL. The second method accepts the name of a specific cryptographic service provider as an argument and uses that provider instead of the default.
  • public final Certificate generateCertificate(InputStream inputstream) instantiates and returns a certificate using data read from the supplied InputStream instance. If the stream contains more than one certificate and the stream supports the mark() and reset() operations, the method will read one certificate and leave the stream positioned before the next.
  • public final Collection generateCertificates(InputStream inputstream) instantiates and returns a certificate collection using data read from the supplied InputStream instance. If the given stream does not support mark() and reset(), the method will consume the entire stream.
  • public final CRL generateCRL(InputStream inputstream) instantiates and returns a CRL using data read from the supplied InputStream instance. If the stream contains more than one CRL and supports the mark() and reset() operations, the method will read one CRL and leave the stream positioned before the next.
  • public final Collection generateCRLs(InputStream inputstream) instantiates and returns a collection of CRLs using data read from the supplied InputStream instance. If the given stream does not support mark() and reset(), public final Collection generateCRLs(InputStream inputstream) will consume the entire stream.

It is important to understand how those four methods behave when generating X.509 instances from a stream of data. Let's take a look.

The generateCertificate() and generateCRL() methods expect the input stream's contents to contain DER-encoded representations of a certificate or a CRL, respectively.

Both the generateCertificates() and generateCRLs() methods expect the contents of the input stream to contain either a sequence of DER-encoded representations or a PKCS#7 (Public-Key Cryptography Standard #7)-compliant certificate or CRL set. (See Resources for links.)

java.security.cert.Certificate

java.security.cert.Certificate defines the interface common to all types of certificates: X.509, PGP, and a small handful of others. This class's most important methods are:

  • public abstract PublicKey getPublicKey() returns the public key related to the certificate instance on which this method is being called.
  • public abstract byte [] getEncoded() returns that certificate's encoded form.
  • public abstract void verify(PublicKey publickey) and public abstract void verify(PublicKey publickey, String stringProvider) verify that the private key corresponding to the supplied public key signed the certificate in question. If the keys do not match, both methods throw a SignatureException.

java.security.cert.X509Certificate

The class java.security.cert.X509Certificate extends the Certficate class described above and adds X.509-specific functionality. This class is important because you usually interact with certificates at this level, not as the base class.

  • public abstract byte [] getEncoded() returns the encoded form of that certificate, as above. The method uses the DER encoding for the certificate.

Most of java.security.cert.X509Certificate's additional functionality consists of query methods that return information about the certificate. I presented most of that information in Part 1. Here are the methods:

  • public abstract int getVersion() returns the certificate's version.
  • public abstract Principal getSubjectDN() returns information that identifies the certificate's subject.
  • public abstract Principal getIssuerDN() returns information that identifies the certificate's issuer, which is typically the CA, but can be the subject if the certificate is self-signed.
  • public abstract Date getNotBefore() and public abstract Date getNotAfter() return values that restrict the time period in which the issuer is willing to vouch for the subject's public key.
  • public abstract BigInteger getSerialNumber() returns the certificate's serial number. The combination of a certificate's issuer name and serial number is its unique identification. That fact is crucial for certificate revocation, which I will discuss in more detail next month.
  • public abstract String getSigAlgName() and public abstract String getSigAlgOID() return information about the algorithm used to sign the certificate.

The following methods return information about the extensions defined for the certificate. Remember, extensions are mechanisms for associating information with a certificate; they only appear on version 3 certificates.

  • public abstract int getBasicConstraints() returns the length of a certificate's constraints path from the BasicConstraints extension, if defined. The constraints path specifies the maximum number of CA certificates that may follow this certificate in a certification path.
  • public abstract boolean [] getKeyUsage() returns the purpose of the certificate as encoded in the KeyUsage extension.
  • public Set getCriticalExtensionOIDs() and public Set getNonCriticalExtensionOIDs() return a collection of object identifiers (OIDs) for the extensions marked critical and noncritical, respectively. An OID is a sequence of integers that universally identifies a resource.

I don't want to leave you without code to play with, so rather than delving into CRLs, which is a complete topic on its own, I'll present the code and leave CRLs for Part 3.

The code

The following class demonstrates how to obtain a certificate factory, how to use that factory to generate a certificate from the DER-encoded representation in a file, and how to extract and display information about the certificate. You'll notice how little you have to worry about the underlying encoding.

import java.util.Set;
import java.util.Iterator;
import java.io.FileInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public
class Main
{
  public
  static
  void
  main(String [] arstring)
  {
    try
    {
      // Get the correct certificate factory.
      CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509");
      // Each file specified on the command line must contain a single
      // DER-encoded X.509 certificate.  The DER-encoded certificate
      // can be in either binary or ASCII format.
      for (int i = 0; i < arstring.length; i++)
      {
        // Open the file.
        FileInputStream fileinputstream = new FileInputStream(arstring[i]);
        // Generate a certificate from the data in the file.
        X509Certificate x509certificate =
          (X509Certificate)certificatefactory.generateCertificate(fileinputstream);
        // First, let's print out information about the certificate itself.
        System.out.println("---Certificate---");
        System.out.println("type = " + x509certificate.getType());
        System.out.println("version = " + x509certificate.getVersion());
        System.out.println("subject = " + x509certificate.getSubjectDN().getName());
        System.out.println("valid from = " + x509certificate.getNotBefore());
        System.out.println("valid to = " + x509certificate.getNotAfter());
        System.out.println("serial number = " + x509certificate.getSerialNumber().toString(16));
        System.out.println("issuer = " + x509certificate.getIssuerDN().getName());
        System.out.println("signing algorithm = " + x509certificate.getSigAlgName());
        System.out.println("public key algorithm = " + x509certificate.getPublicKey().getAlgorithm());
        // Next, let's print out information about the extensions.
        System.out.println("---Extensions---");
        Set setCritical = x509certificate.getCriticalExtensionOIDs();
        if (setCritical != null && setCritical.isEmpty() == false)
          for (Iterator iterator = setCritical.iterator(); iterator.hasNext(); )
            System.out.println(iterator.next().toString() + " *critical*");
        Set setNonCritical = x509certificate.getNonCriticalExtensionOIDs();
        if (setNonCritical != null && setNonCritical.isEmpty() == false)
          for (Iterator iterator = setNonCritical.iterator(); iterator.hasNext(); )
            System.out.println(iterator.next().toString());
        // We're done.
        System.out.println("---");
        // Close the file.
        fileinputstream.close();
      }
    }
    catch (Exception exception)
    {
      exception.printStackTrace();
    }
  }
}

You should be able to compile the class definition in the listing above into a classfile. When you execute the class, you should specify the names of one or more certificate files on the command line.

Conclusion

If you plan to use certificates in your applications, the information on the API above and the sample code available in Resources should point you in the right direction. As far as X.509 is concerned, the material on ASN.1 and DER is interesting, but not directly important; Sun's implementation takes care of the details for you. Next month I will cover certificate revocation and the CRL and X509CRL classes. I'll also elaborate a bit more on the software presented this month.

Todd Sundsted has been writing programs since computers became available in convenient desktop models. Though originally interested in building distributed applications in C++, Todd moved on to the Java programming language when it became the obvious choice for that sort of thing. In addition to writing, Todd is cofounder and chief architect of PointFire.

Learn more about this topic