Java API for PKI

A number of PKI-related classes and interfaces exist in J2SE SDK. We have already come across KeyStore, KeyPair, PublicKey, PrivateKey and many others, in Chapter 3, Cryptography with Java. Classes representing certificates, certification paths, CRLs, repositories and others are covered here.

Certificates and Certification Paths

There are times when you want to access a certificate or a certification path in your program, either by reading a file or getting it from a keystore. Also, you may want to write it back to a file or store it into a keystore. These operations are made possible via Java API classes Certificate, CertPath, X509Certificate and CertificateFactory, all under package java.security.cert. The abstract class Certificate represents any public key certificate that binds an identity of a principal with a public key and is vouched for by another principal, by way of a digital signature. J2SE includes only one concrete implementation of this class: X509Certificate, representing X.509 certificates.

A Certificate object can be instantiated by reading a suitably encoded stream of bytes using the CertificateFactory engine class. The default CertificateFactory provider supports DER encoded binary X.509 certificates. It also accepts its ASCII equivalent with base-64 encoding. This is illustrated by the program ShowCert.java, which reads a X.509 certificate and prints its contents on the screen.

Listing 4-1. Program to display fields of a X.509 certificate
// File: srcjsbookch4ex1ShowCert.java
import java.util.Iterator;
import java.util.List;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.io.FileInputStream;

public class ShowCert {
  private static void display(String str){
    System.out.println(str);
  }
  public static void printX509Cert(X509Certificate cert, String indent){
    display(indent + "Certificate:");
    display(indent + "  Data:
");
    display(indent + "    Version: " + cert.getVersion());
    display(indent + "    Serial Number: " + cert.getSerialNumber());
    display(indent + "    Signature Algorithm: " +
                                               cert.getSigAlgName());
    display(indent + "    Issuer: " + cert.getIssuerX500Principal());
    display(indent + "    Validity:");
    display(indent + "      Not Before: " + cert.getNotBefore());
    display(indent + "      Not After: " + cert.getNotAfter());
    display(indent + "    Subject: " + cert.getSubjectX500Principal());
    display(indent + "    Extensions: 
");

    display(indent + "      X509v3 Basic Constraints:");

    int pathLen = cert.getBasicConstraints();
    if (pathLen != -1)// Not a CA
      display(indent + "        CA: TRUE, pathLen: " + pathLen);
    else
      display(indent + "        CA: FALSE");
    // Code to print key usage and extended key usage skipped ...
  }

  public static void main(String[] args) throws Exception{
    if (args.length < 1){
      display("Usage:: java ShowCert <certfile>");
      return;
    }
    String certfile = args[0];
    FileInputStream fis = new FileInputStream(certfile);
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    Certificate cert = cf.generateCertificate(fis);
    printX509Cert((X509Certificate)cert, "");
  }
}

This program instantiates a X509Certificate object with a byte stream read from a file, accesses its various fields, and prints them on the screen. For reasons of brevity, portions of code responsible for displaying key usage and extended key usage fields are not shown in Listing 4-1. You can find the complete source code in the source files included in the JSTK software.

Compiling and running this program, with the certificate file retrieved from a Windows certificate store, as explained in the section Digital Certificates, verisign.cer as argument, produces the following output:

C:ch4ex1>java ShowCert verisign.cer
Certificate:
  Data:
    Version: 3
    Serial Number: 18003314428068055389803205999447867387
    Signature Algorithm: MD2withRSA
    Issuer: OU=Class 1 Public Primary Certification Authority,
            O="VeriSign, Inc.", C=US
    Validity:
      Not Before: Mon May 11 17:00:00 PDT 1998
      Not After: Mon May 12 16:59:59 PDT 2008
    Subject: CN=VeriSign Class 1 CA Individual Subscriber-Persona Not 
       Validated, OU="www.verisign.com/repository/RPA Incorp. By Ref.,
       LIAB.LTD(c)98", OU=VeriSign Trust Network, O="VeriSign, Inc."
    Extensions:
      X509v3 Basic Constraints:
        CA: TRUE, pathLen: 0
      Key Usage: keyCertSign, cRLSign

As usual, the backslash (character '') is used to indicate continuation of the line. Look at the output carefully and match the field names with the ones shown in Figure 4-1. You should be able to find almost a one-to-one mapping. Now, try running the program ShowCert on the other base-64 encoded certificate file verisignb64.cer. What output do you see?

An interesting point about class X509Certificate is that it has a number of getter functions but no setter functions. Essentially, it allows you read-only access to the fields. You cannot use this class to construct a X509Certificate object, starting with the field values. You can, though, get the underlying DER encoded byte array by calling getEncoded() method on an instance of this class. This comes in handy when you get the X509Certificate instance through means other than instantiation from a DER encoded byte array.

Class CertPath encapsulates a collection of certificates. It represents a certification path, the first element of the path being the target certificate followed by the certificate of its issuer and so on, terminating at the certificate of the root CA. Quite like a Certificate object, a CerthPath object can be instantiated by reading a suitably encoded stream of bytes using CertificateFactory engine class. A certification path follows the structure defined by PKCS#7 standard or is an ASN.1 sequence of X.509 certificates. The former is identified by type "PKCS7" and the later by type "PkiPath". The sample program ShowCertPath.java, shown in Listing 4-2, attempts to parse the input file first as a "PkiPath" file and then as "PKCS7" file.

Listing 4-2. Displaying certificates within a certification path
// File: srcjsbookch4ex1ShowCertPath.java
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertPath;
import java.security.cert.CertificateFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.BufferedInputStream;

public class ShowCertPath{
  public static void printCertPath(CertPath cp){
    List list = cp.getCertificates();
    Iterator li = list.iterator();
    System.out.println("CertPath:");
    int index = 0;
    while (li.hasNext()){
      System.out.println("CertPath Component: " + index );
      X509Certificate cert = (X509Certificate)li.next();
      ShowCert.printX509Cert(cert, "  ");
      ++index;
    }
  }

  public static void main(String[] args) throws Exception{
    if (args.length < 1){
      System.out.println("Usage:: java ShowCertPath <certpathfile>");
      return;
    }
    String certpathfile = args[0];
    FileInputStream fis = new FileInputStream(certpathfile);
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    File file = new File(certpathfile);
    int bufsize = (int)file.length() + 1024; // Use big-enough buffer
    BufferedInputStream bis =
      new BufferedInputStream(new FileInputStream(file), bufsize);
    bis.mark(bufsize);

    CertPath cp = null;
    try {
      cp = cf.generateCertPath(bis, "PkiPath");
    } catch (CertificateException ce) {
      bis.reset();
      try {
        cp = cf.generateCertPath(bis, "PKCS7");
      } catch (CertificateException cei) {
        System.out.println("CertPath format not recognized.");
        return;
      }
    }
    printCertPath(cp);
  }
}

There is no way to determine the encoding and type of content in the input byte stream other than by examining the byte stream and matching it against the specification of known structures. This is why the code first tries parsing as per "PkiPath" encoding and then "PKCS7" encoding. Because the first parsing would move the current position in the byte stream and we need to start afresh for the next parsing, we use mark() and reset() methods on the underlying InputStream object.

As you can see from the body of printCertPath() method, a CertPath object essentially keeps a list of Certificate objects. In fact, we reused method printX509Certificate() of class ShowCert to print individual certificates, iterating over the list using an Iterator.

Compile and run this program with certification path file verisign.p7b as argument. The output should appear as:

CertPath:
CertPath Component: 0
  Certificate:
    Data:
      ...skip certificate details...
CertPath Component: 1
  Certificate:
    Data:
      Version: 1
      Serial Number: 273460089105783944861631223625220794965
      Signature Algorithm: MD2withRSA
      Issuer: OU=Class 1 Public Primary Certification Authority, 
              O="VeriSign, Inc.", C=US
      Validity:
        Not Before: Sun Jan 28 16:00:00 PST 1996
        Not After: Tue Aug 01 16:59:59 PDT 2028
      Subject: OU=Class 1 Public Primary Certification Authority,
               O="VeriSign, Inc.", C=US
      Extensions:
        X509v3 Basic Constraints:
          CA: FALSE

You can see that the certification path has two certificates.

Certificate Revocation List

There are situations when a certificate must be cancelled. Say you have a digital certificate from your employer that lets you access the corporate intranet from the Internet and your employment status has changed. Or you have reason to believe that your private key may be in the wrong hands and could be misused. You want a way to cancel or revoke your certificate. But you can't simply go and destroy all copies of your certificate for the simple reason that you may not know how many copies exist, where and with whom. What you can do is to inform your CA and the CA will place the serial number of your certificate in a list of revoked certificates, also known as CRL. CRL is essentially a CA signed digital document having the list of serial numbers corresponding to revoked certificates. As all certificates issued by a CA have distinct serial numbers, the serial number uniquely identifies a certificate. Also, the digital signature of the CA makes the CRL authentic and tamper-evident.

A CA is expected to issue CRLs periodically and make them available on the public Internet for download. Anyone who needs to validate a certificate should consult an up-to-date CRL to make sure that the certificate has not been revoked. We come back to this point when we talk about validating certificates in a subsequent section.

In a Java program, a CRL is represented by an instance of class X509CRL and each entry within the CRL is represented by instances of class X509CRLEntry, both under the package java.security.cert. A CRL is typically DER encoded and can be read using CertificateFactory class. Listing 4-3 shows the sample code to read a CRL from a file and display its contents.

Listing 4-3. Displaying the contents of a Certificate Revocation List
import java.util.Iterator;
import java.util.Set;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.CertificateFactory;
import java.io.FileInputStream;

public class ShowCRL {
  private static void display(String str){
    System.out.println(str);
  }
  public static void printX509CRL(X509CRL crl){
    display("CRL:");
    display("  Version: " + crl.getVersion());
    display("  Signature Algorithm: " + crl.getSigAlgName());
    display("  Issuer: " + crl.getIssuerX500Principal());
    display("  This Update: " + crl.getThisUpdate());
    display("  Next Update: " + crl.getNextUpdate());

    Set revokedCerts = crl.getRevokedCertificates();
    if (revokedCerts == null) return;
    Iterator itr = revokedCerts.iterator();
    int index = 0;
    while (itr.hasNext()){
      printX509CRLEntry((X509CRLEntry)itr.next(), index);
      ++index;
    }
  }
  public static void printX509CRLEntry(X509CRLEntry crlEntry,
        int index){
    display("  CRLEntry[" + index + "]:");
    display("    Serial Number: " + crlEntry.getSerialNumber());
    display("    Revocation Date: " + crlEntry.getRevocationDate());
  }
  public static void main(String[] args) throws Exception{
    if (args.length < 1){
      System.out.println("Usage:: java ShowCRL <crlfile>");
      return;
    }
    String crlfile = args[0];
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    FileInputStream fis = new FileInputStream(crlfile);
    X509CRL crl = (X509CRL)cf.generateCRL(fis);
    printX509CRL(crl);
  }
}

As you can see, a CRL includes the version field, the signature algorithm used to sign it, the issuer, the time of CRL generation and the time of the next scheduled generation. Besides this, it contains a list of certificate serial numbers with the revocation date.

JSTK utility certtool packages the capability of the example programs ShowCert.java, ShowCertPath.java and ShowCRL.java, all within a single command. To display the contents of a X.509 certificate, certification path or CRL, simply run the command "certtool show –infile filename".

Repository of Certificates and CRLs

Java API includes class java.security.cert.CertStore to access a potentially huge repository of certificates and CRLs from different sources. All these certificates and CRLs need not be trusted or even issued by trusted entities. Also, as the entries of such a repository are public and tamper-evident, they can be accessed by anyone over insecure channels.

One needs such a repository to validate a certification path or construct a certification path for a given certificate and a trust anchor from its own set of trusted certificates. The need to access CRLs is easy to see for validation. The need for accessing untrusted certificates is less obvious, especially when certificates are accompanied by a complete certification path. If all the certificates belonged to a single hierarchical PKI architecture with a single root CA and each certificate included its complete certification path, there would be no need to access untrusted certificates. As we saw in the PKI Architectures section, it is possible to have hierarchical architectures with multiple roots, cross-issued certificates among CAs of different hierarchies and bridge CAs. So it is possible that the entity validating the certificate may not trust the trust anchor of the supplied certification path and, at the same time, is able to construct a certification path with another trust point.

Class CertStore can be initialized with either a java.util.Collection object of X509Ceritifcate and X509CRL objects or with the server name and port number of an LDAP server having certificate and CRLs as per RFC 2587 standard. You can then access individual certificates and CRLs matching a selection criterion from CertStore.

Construction and validation of certification paths make use of class CertStore. For experimentation, it is sometimes useful to initialize a CertStore from a persistent file and manipulate the persistent file by adding and removing certificates and CRLs. This is exactly what JSTK utility reptool does.

Building Certification Paths

Java API classes CertPathBuilder, PKIXBuilderParameters and PKIXCertPathBuilderResult, all under the package java.security.cert, allow one to construct a validated certification path. The best way to understand this is to take a look at the following code fragment:

String trustStoreFile = "test.ts";  // test.ts has trusted certificates
String type = "JCEKS";  // keystore type of trust store file.
String dn = "CN=Blah, OU=Bar, O=Foo, C=US"; // dn of target cert.
Collection rep = ... // Initialized from file based respository.

CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
KeyStore trustStore = KeyStore.getInstance(type);
trustStore.load(new FileInputStream(trustStoreFile), null);
X509CertSelector targetConstraints = new X509CertSelector();
targetConstraints.setSubject(dn);
PKIXBuilderParameters pkixParams =
new PKIXBuilderParameters(trustStore, targetConstraints);

CollectionCertStoreParameters params =
new CollectionCertStoreParameters(rep);
CertStore cs = CertStore.getInstance("Collection", params);
pkixParams.addCertStore(cs);

PKIXCertPathBuilderResult result =
(PKIXCertPathBuilderResult)cpb.build(pkixParams);
CertPath cp = result.getCertPath();

This code fragment works with the following as input:

  • The distinguished name of the target certificate

  • A repository of certificates and CRLs

  • List of trusted certificates in a keystore

An instance of CertPathBuilder of type "PKIX" is created to build the validated certification path. Its build() method takes an instance of PKIXBuilderParameters, which essentially is an encapsulation of all the input values listed above, and returns an instance of PKIXCertPathBuilderResult. On successful execution of build(), you can retrieve the complete certification path from the returned object.

Look at the BuildCertPathCommand.java file, a component of certtool, for the complete working code.

Validating Certification Paths

The Java API classes for validating a certification path and the sequence of steps are similar to those of building a certification path, and the details are not covered here. If you are interested in specifics then look at the certtool source file ValidateCertPathCommand.java.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.159.116