Introduction to Digital Certificates, Part 4: Creating Certs with Bouncy Castle

By thebearinboulderNo Comments

This is part of a series on Digital Certificates.

Time for some code!

This implementation uses deprecated classes from Bouncy Castle. I’m still working on an implementation using the preferred classes.

The Unit Test

We’re good developers using Test-Driven Development so we start with the unit test. This is an abridged test that doesn’t attempt to verify policy issues.

  1. package com.otterca.repository.util;
  2.  
  3. import static org.testng.Assert.assertEquals;
  4. import static org.testng.Assert.fail;
  5.  
  6. import java.math.BigInteger;
  7. import java.security.KeyPair;
  8. import java.security.KeyPairGenerator;
  9. import java.security.Provider;
  10. import java.security.Security;
  11. import java.security.cert.X509Certificate;
  12. import java.util.Calendar;
  13. import java.util.List;
  14. import java.util.TimeZone;
  15.  
  16. import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
  17. import org.bouncycastle.asn1.x509.GeneralName;
  18. import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
  19. import org.bouncycastle.asn1.x509.X509Extension;
  20. import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
  21. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  22. import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
  23. import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
  24. import org.testng.annotations.Test;
  25.  
  26. /**
  27.  * Unit tests for X509CertificateBuilder.
  28.  *
  29.  * @author bgiles@otterca.com
  30.  */
  31. @SuppressWarnings("deprecation")
  32. public class X509CertificateBuilderTest {
  33.     private final Provider provider;
  34.     private final KeyPairGenerator keyPairGen;
  35.     private final BigInteger serial = BigInteger.ONE;
  36.     private final String subjectName = "DN=subject";
  37.     private final String issuerName = "DN=issuer";
  38.     private final Calendar notBefore;
  39.     private final Calendar notAfter;
  40.     private final KeyPair keyPair;
  41.  
  42.     public X509CertificateBuilderTest() throws Exception {
  43.         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
  44.  
  45.         provider = new BouncyCastleProvider();
  46.         Security.addProvider(provider);
  47.  
  48.         keyPairGen = KeyPairGenerator.getInstance("RSA", provider);
  49.         keyPairGen.initialize(512);
  50.         keyPair = keyPairGen.generateKeyPair();
  51.  
  52.         notBefore = Calendar.getInstance();
  53.         notBefore.set(Calendar.MILLISECOND, 0);
  54.         notAfter = Calendar.getInstance();
  55.         notAfter.setTime(notBefore.getTime());
  56.         notAfter.add(Calendar.YEAR, 1);
  57.     }
  58.  
  59.     /**
  60.      * Test builder when all properties are set.
  61.      *
  62.      * @throws Exception
  63.      */
  64.     @Test
  65.     public void testBuilderSelfSignedCert() throws Exception {
  66.         String email = "email";
  67.         String dnsName = "otterca.com";
  68.         String ipAddress = "127.0.0.1";
  69.         String dirName = "CN=subject";
  70.         String issuerEmail = "issuer email";
  71.         String issuerDnsName = "issuer.otterca.com";
  72.         String issuerIpAddress = "127.0.0.2";
  73.         String issuerDirName = "CN=issuer";
  74.  
  75.         // create certificate
  76.         X509CertificateBuilder builder = new X509CertificateBuilder();
  77.         builder.setSerialNumber(serial);
  78.         builder.setSubject(subjectName);
  79.         builder.setIssuer(subjectName);
  80.         builder.setNotBefore(notBefore.getTime());
  81.         builder.setNotAfter(notAfter.getTime());
  82.         builder.setPublicKey(keyPair.getPublic());
  83.         builder.setEmailAddresses(email);
  84.         builder.setDnsNames(dnsName);
  85.         builder.setIpAddresses(ipAddress);
  86.         builder.setDirectoryNames(dirName);
  87.         builder.setIssuerEmailAddresses(issuerEmail);
  88.         builder.setIssuerDnsNames(issuerDnsName);
  89.         builder.setIssuerIpAddresses(issuerIpAddress);
  90.         builder.setIssuerDirectoryNames(issuerDirName);
  91.  
  92.         X509Certificate cert = builder.build(keyPair.getPrivate());
  93.  
  94.         // perform basic validation.
  95.         cert.verify(keyPair.getPublic());
  96.  
  97.         // verify the mandatory attributes.
  98.         assertEquals(cert.getSerialNumber(), serial);
  99.         assertEquals(cert.getSubjectDN().getName(), subjectName);
  100.         assertEquals(cert.getIssuerDN().getName(), subjectName);
  101.         assertEquals(cert.getNotBefore(), notBefore.getTime());
  102.         assertEquals(cert.getNotAfter(), notAfter.getTime());
  103.         assertEquals(cert.getPublicKey(), keyPair.getPublic());
  104.  
  105.         JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();
  106.  
  107.         // verify that we have a SKID.
  108.         SubjectKeyIdentifier kid = utils.createSubjectKeyIdentifier(keyPair.getPublic());
  109.         SubjectKeyIdentifier skid = new SubjectKeyIdentifierStructure(
  110.                 cert.getExtensionValue(X509Extension.subjectKeyIdentifier.toString()));
  111.         assertEquals(skid.getKeyIdentifier(), kid.getKeyIdentifier());
  112.  
  113.         // verify that we have an AKID.
  114.         AuthorityKeyIdentifier akid = new AuthorityKeyIdentifierStructure(
  115.                 cert.getExtensionValue(X509Extension.authorityKeyIdentifier.toString()));
  116.         assertEquals(akid.getKeyIdentifier(), kid.getKeyIdentifier());
  117.         assertEquals(akid.getAuthorityCertSerialNumber(), serial);
  118.         GeneralName[] akidNames = akid.getAuthorityCertIssuer().getNames();
  119.         assertEquals(akidNames.length, 1);
  120.         assertEquals(akidNames[0].getName().toString(), subjectName);
  121.  
  122.         // verify the subject alternative names.
  123.         for (List obj : cert.getSubjectAlternativeNames()) {
  124.             switch (((Number) obj.get(0)).intValue()) {
  125.             case GeneralName.rfc822Name:
  126.                 assertEquals(obj.get(1), email);
  127.                 break;
  128.             case GeneralName.dNSName:
  129.                 assertEquals(obj.get(1), dnsName);
  130.                 break;
  131.             case GeneralName.iPAddress:
  132.                 assertEquals(obj.get(1), ipAddress);
  133.                 break;
  134.             case GeneralName.directoryName:
  135.                 assertEquals(obj.get(1), dirName);
  136.                 break;
  137.             default:
  138.                 fail("unexpected subject alternative name");
  139.             }
  140.         }
  141.  
  142.         // verify the issuer alternative names.
  143.         for (List obj : cert.getIssuerAlternativeNames()) {
  144.             switch (((Number) obj.get(0)).intValue()) {
  145.             case GeneralName.rfc822Name:
  146.                 assertEquals(obj.get(1), issuerEmail);
  147.                 break;
  148.             case GeneralName.dNSName:
  149.                 assertEquals(obj.get(1), issuerDnsName);
  150.                 break;
  151.             case GeneralName.iPAddress:
  152.                 assertEquals(obj.get(1), issuerIpAddress);
  153.                 break;
  154.             case GeneralName.directoryName:
  155.                 assertEquals(obj.get(1), issuerDirName);
  156.                 break;
  157.             default:
  158.                 fail("unexpected issuer alternative name");
  159.             }
  160.         }
  161.  
  162.         // key usage
  163.         // basic constraint
  164.     }
  165.  
  166.     /**
  167.      * Test builder when missing serial number.
  168.      *
  169.      * @throws Exception
  170.      */
  171.     @Test(expectedExceptions = IllegalArgumentException.class)
  172.     public void testBuilderMissingSerialNumber() throws Exception {
  173.         X509CertificateBuilder builder = new X509CertificateBuilder();
  174.  
  175.         builder.setSubject(subjectName);
  176.         builder.setIssuer(issuerName);
  177.         builder.setNotBefore(notBefore.getTime());
  178.         builder.setNotAfter(notAfter.getTime());
  179.         builder.setPublicKey(keyPair.getPublic());
  180.         builder.build(keyPair.getPrivate());
  181.     }
  182.  
  183.     /**
  184.      * Test builder when missing subject.
  185.      *
  186.      * @throws Exception
  187.      */
  188.     @Test(expectedExceptions = IllegalArgumentException.class)
  189.     public void testBuilderMissingSubject() throws Exception {
  190.         X509CertificateBuilder builder = new X509CertificateBuilder();
  191.  
  192.         builder.setSerialNumber(serial);
  193.         builder.setIssuer(issuerName);
  194.         builder.setNotBefore(notBefore.getTime());
  195.         builder.setNotAfter(notAfter.getTime());
  196.         builder.setPublicKey(keyPair.getPublic());
  197.         builder.build(keyPair.getPrivate());
  198.     }
  199.  
  200.     /**
  201.      * Test builder when missing issuer.
  202.      *
  203.      * @throws Exception
  204.      */
  205.     @Test(expectedExceptions = IllegalArgumentException.class)
  206.     public void testBuilderMissingIssuer() throws Exception {
  207.         X509CertificateBuilder builder = new X509CertificateBuilder();
  208.  
  209.         builder.setSerialNumber(serial);
  210.         builder.setSubject(subjectName);
  211.         builder.setNotBefore(notBefore.getTime());
  212.         builder.setNotAfter(notAfter.getTime());
  213.         builder.setPublicKey(keyPair.getPublic());
  214.         builder.build(keyPair.getPrivate());
  215.     }
  216.  
  217.     /**
  218.      * Test builder when missing 'not before' date.
  219.      *
  220.      * @throws Exception
  221.      */
  222.     @Test(expectedExceptions = IllegalArgumentException.class)
  223.     public void testBuilderMissingNotBefore() throws Exception {
  224.         X509CertificateBuilder builder = new X509CertificateBuilder();
  225.  
  226.         builder.setSerialNumber(serial);
  227.         builder.setSubject(subjectName);
  228.         builder.setIssuer(issuerName);
  229.         builder.setNotAfter(notAfter.getTime());
  230.         builder.setPublicKey(keyPair.getPublic());
  231.         builder.build(keyPair.getPrivate());
  232.     }
  233.  
  234.     /**
  235.      * Test builder when missing 'not after' date.
  236.      *
  237.      * @throws Exception
  238.      */
  239.     @Test(expectedExceptions = IllegalArgumentException.class)
  240.     public void testBuilderMissingNotAfter() throws Exception {
  241.         X509CertificateBuilder builder = new X509CertificateBuilder();
  242.  
  243.         builder.setSerialNumber(serial);
  244.         builder.setSubject(subjectName);
  245.         builder.setIssuer(issuerName);
  246.         builder.setNotBefore(notBefore.getTime());
  247.         builder.setPublicKey(keyPair.getPublic());
  248.         builder.build(keyPair.getPrivate());
  249.     }
  250.  
  251.     /**
  252.      * Test builder when missing public key.
  253.      *
  254.      * @throws Exception
  255.      */
  256.     @Test(expectedExceptions = IllegalArgumentException.class)
  257.     public void testBuilderMissingPublicKey() throws Exception {
  258.         X509CertificateBuilder builder = new X509CertificateBuilder();
  259.  
  260.         builder.setSerialNumber(serial);
  261.         builder.setSubject(subjectName);
  262.         builder.setIssuer(issuerName);
  263.         builder.setNotBefore(notBefore.getTime());
  264.         builder.setNotAfter(notAfter.getTime());
  265.         builder.build(keyPair.getPrivate());
  266.     }
  267.  
  268.     /**
  269.      * Test builder when 'notBefore' date is after 'notAfter' date.
  270.      *
  271.      * @throws Exception
  272.      */
  273.     @Test(expectedExceptions = IllegalArgumentException.class)
  274.     public void testBuilderBadDates() throws Exception {
  275.         X509CertificateBuilder builder = new X509CertificateBuilder();
  276.  
  277.         builder.setSerialNumber(serial);
  278.         builder.setSubject(subjectName);
  279.         builder.setIssuer(issuerName);
  280.         builder.setNotBefore(notAfter.getTime());
  281.         builder.setNotAfter(notBefore.getTime());
  282.         builder.setPublicKey(keyPair.getPublic());
  283.         builder.build(keyPair.getPrivate());
  284.     }
  285. }
package com.otterca.repository.util;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;

import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.testng.annotations.Test;

/**
 * Unit tests for X509CertificateBuilder.
 *
 * @author bgiles@otterca.com
 */
@SuppressWarnings("deprecation")
public class X509CertificateBuilderTest {
    private final Provider provider;
    private final KeyPairGenerator keyPairGen;
    private final BigInteger serial = BigInteger.ONE;
    private final String subjectName = "DN=subject";
    private final String issuerName = "DN=issuer";
    private final Calendar notBefore;
    private final Calendar notAfter;
    private final KeyPair keyPair;

    public X509CertificateBuilderTest() throws Exception {
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        keyPairGen = KeyPairGenerator.getInstance("RSA", provider);
        keyPairGen.initialize(512);
        keyPair = keyPairGen.generateKeyPair();

        notBefore = Calendar.getInstance();
        notBefore.set(Calendar.MILLISECOND, 0);
        notAfter = Calendar.getInstance();
        notAfter.setTime(notBefore.getTime());
        notAfter.add(Calendar.YEAR, 1);
    }

    /**
     * Test builder when all properties are set.
     *
     * @throws Exception
     */
    @Test
    public void testBuilderSelfSignedCert() throws Exception {
        String email = "email";
        String dnsName = "otterca.com";
        String ipAddress = "127.0.0.1";
        String dirName = "CN=subject";
        String issuerEmail = "issuer email";
        String issuerDnsName = "issuer.otterca.com";
        String issuerIpAddress = "127.0.0.2";
        String issuerDirName = "CN=issuer";

        // create certificate
        X509CertificateBuilder builder = new X509CertificateBuilder();
        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(subjectName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.setEmailAddresses(email);
        builder.setDnsNames(dnsName);
        builder.setIpAddresses(ipAddress);
        builder.setDirectoryNames(dirName);
        builder.setIssuerEmailAddresses(issuerEmail);
        builder.setIssuerDnsNames(issuerDnsName);
        builder.setIssuerIpAddresses(issuerIpAddress);
        builder.setIssuerDirectoryNames(issuerDirName);

        X509Certificate cert = builder.build(keyPair.getPrivate());

        // perform basic validation.
        cert.verify(keyPair.getPublic());

        // verify the mandatory attributes.
        assertEquals(cert.getSerialNumber(), serial);
        assertEquals(cert.getSubjectDN().getName(), subjectName);
        assertEquals(cert.getIssuerDN().getName(), subjectName);
        assertEquals(cert.getNotBefore(), notBefore.getTime());
        assertEquals(cert.getNotAfter(), notAfter.getTime());
        assertEquals(cert.getPublicKey(), keyPair.getPublic());

        JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();

        // verify that we have a SKID.
        SubjectKeyIdentifier kid = utils.createSubjectKeyIdentifier(keyPair.getPublic());
        SubjectKeyIdentifier skid = new SubjectKeyIdentifierStructure(
                cert.getExtensionValue(X509Extension.subjectKeyIdentifier.toString()));
        assertEquals(skid.getKeyIdentifier(), kid.getKeyIdentifier());

        // verify that we have an AKID.
        AuthorityKeyIdentifier akid = new AuthorityKeyIdentifierStructure(
                cert.getExtensionValue(X509Extension.authorityKeyIdentifier.toString()));
        assertEquals(akid.getKeyIdentifier(), kid.getKeyIdentifier());
        assertEquals(akid.getAuthorityCertSerialNumber(), serial);
        GeneralName[] akidNames = akid.getAuthorityCertIssuer().getNames();
        assertEquals(akidNames.length, 1);
        assertEquals(akidNames[0].getName().toString(), subjectName);

        // verify the subject alternative names.
        for (List obj : cert.getSubjectAlternativeNames()) {
            switch (((Number) obj.get(0)).intValue()) {
            case GeneralName.rfc822Name:
                assertEquals(obj.get(1), email);
                break;
            case GeneralName.dNSName:
                assertEquals(obj.get(1), dnsName);
                break;
            case GeneralName.iPAddress:
                assertEquals(obj.get(1), ipAddress);
                break;
            case GeneralName.directoryName:
                assertEquals(obj.get(1), dirName);
                break;
            default:
                fail("unexpected subject alternative name");
            }
        }

        // verify the issuer alternative names.
        for (List obj : cert.getIssuerAlternativeNames()) {
            switch (((Number) obj.get(0)).intValue()) {
            case GeneralName.rfc822Name:
                assertEquals(obj.get(1), issuerEmail);
                break;
            case GeneralName.dNSName:
                assertEquals(obj.get(1), issuerDnsName);
                break;
            case GeneralName.iPAddress:
                assertEquals(obj.get(1), issuerIpAddress);
                break;
            case GeneralName.directoryName:
                assertEquals(obj.get(1), issuerDirName);
                break;
            default:
                fail("unexpected issuer alternative name");
            }
        }

        // key usage
        // basic constraint
    }

    /**
     * Test builder when missing serial number.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingSerialNumber() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing subject.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingSubject() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing issuer.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingIssuer() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing 'not before' date.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingNotBefore() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing 'not after' date.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingNotAfter() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing public key.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingPublicKey() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when 'notBefore' date is after 'notAfter' date.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderBadDates() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notAfter.getTime());
        builder.setNotAfter(notBefore.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }
}

The Code

The actual builder. As the javadocs point out the final version should use dependency injection for a few strategy methods.

  1. package com.otterca.repository.util;
  2.  
  3. import java.math.BigInteger;
  4. import java.security.InvalidKeyException;
  5. import java.security.KeyStore;
  6. import java.security.KeyStoreException;
  7. import java.security.NoSuchAlgorithmException;
  8. import java.security.NoSuchProviderException;
  9. import java.security.PrivateKey;
  10. import java.security.PublicKey;
  11. import java.security.SignatureException;
  12. import java.security.cert.CertificateEncodingException;
  13. import java.security.cert.CertificateException;
  14. import java.security.cert.CertificateExpiredException;
  15. import java.security.cert.CertificateNotYetValidException;
  16. import java.security.cert.CertificateParsingException;
  17. import java.security.cert.X509Certificate;
  18. import java.util.ArrayList;
  19. import java.util.Date;
  20. import java.util.List;
  21.  
  22. import javax.annotation.ParametersAreNonnullByDefault;
  23.  
  24. import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
  25. import org.bouncycastle.asn1.x509.BasicConstraints;
  26. import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
  27. import org.bouncycastle.asn1.x509.GeneralName;
  28. import org.bouncycastle.asn1.x509.GeneralNames;
  29. import org.bouncycastle.asn1.x509.KeyUsage;
  30. import org.bouncycastle.asn1.x509.X509Extensions;
  31. import org.bouncycastle.jce.X509Principal;
  32. import org.bouncycastle.x509.X509V3CertificateGenerator;
  33. import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
  34. import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
  35.  
  36. /**
  37.  * Convenience class that builds X509 certificates.
  38.  *
  39.  * @author bgiles@otterca.com
  40.  */
  41. @ParametersAreNonnullByDefault
  42. @SuppressWarnings("deprecation")
  43. public class X509CertificateBuilder {
  44.     public static final String SIGNATURE_ALGORITHM = "SHA256WITHRSA";
  45.     private KeyStore keyStore;
  46.     private X509Certificate issuer;
  47.     private BigInteger serial;
  48.     private X509Principal subjectDN;
  49.     private X509Principal issuerDN;
  50.     private Date notBefore;
  51.     private Date notAfter;
  52.     private PublicKey pubkey;
  53.     private final List<GeneralName> subjectNames = new ArrayList<>();
  54.     private final List<GeneralName> issuerNames = new ArrayList<>();
  55.     private KeyUsage keyUsage;
  56.     private ExtendedKeyUsage extendedKeyUsage;
  57.     private boolean basicConstraint;
  58.     private int pathLengthConstraint = -1;
  59.  
  60.     /**
  61.      * Default constructor that can be used to create self-signed certificates
  62.      * but not certificate chains.
  63.      *
  64.      * @param keyStore
  65.      */
  66.     public X509CertificateBuilder() {
  67.     }
  68.  
  69.     /**
  70.      * Constructor that requires keystore to validate certificate chain for
  71.      * issuer.
  72.      *
  73.      * @param keyStore
  74.      */
  75.     public X509CertificateBuilder(KeyStore keyStore) {
  76.         this.keyStore = keyStore;
  77.     }
  78.  
  79.     /**
  80.      * Reset builder to default state.
  81.      */
  82.     public void reset() {
  83.         issuer = null;
  84.         serial = null;
  85.         subjectDN = null;
  86.         issuerDN = null;
  87.         notBefore = null;
  88.         notAfter = null;
  89.         pubkey = null;
  90.         subjectNames.clear();
  91.         issuerNames.clear();
  92.         keyUsage = null;
  93.         basicConstraint = false;
  94.         pathLengthConstraint = -1;
  95.     }
  96.  
  97.     /**
  98.      * Set serial number.
  99.      *
  100.      * @param serial
  101.      * @return
  102.      */
  103.     public X509CertificateBuilder setSerialNumber(BigInteger serial) {
  104.         this.serial = serial;
  105.         return this;
  106.     }
  107.  
  108.     /**
  109.      * Set subject name (as X509Principal).
  110.      *
  111.      * @param dirName
  112.      * @return
  113.      */
  114.     public X509CertificateBuilder setSubject(String dirName) {
  115.         this.subjectDN = new X509Principal(dirName);
  116.         return this;
  117.     }
  118.  
  119.     /**
  120.      * Set issuer name (as X509Principal).
  121.      *
  122.      * @param dirName
  123.      * @return
  124.      */
  125.     public X509CertificateBuilder setIssuer(String dirName) {
  126.         this.issuerDN = new X509Principal(dirName);
  127.         return this;
  128.     }
  129.  
  130.     /**
  131.      * Set 'notBefore' date.
  132.      *
  133.      * @param notBefore
  134.      * @return
  135.      */
  136.     public X509CertificateBuilder setNotBefore(Date notBefore) {
  137.         this.notBefore = notBefore;
  138.         return this;
  139.     }
  140.  
  141.     /**
  142.      * Set 'notAfter' date.
  143.      *
  144.      * @param notAfter
  145.      * @return
  146.      */
  147.     public X509CertificateBuilder setNotAfter(Date notAfter) {
  148.         this.notAfter = notAfter;
  149.         return this;
  150.     }
  151.  
  152.     /**
  153.      * Set public key.
  154.      *
  155.      * @param pubkey
  156.      * @return
  157.      */
  158.     public X509CertificateBuilder setPublicKey(PublicKey pubkey) {
  159.         this.pubkey = pubkey;
  160.         return this;
  161.     }
  162.  
  163.     /**
  164.      * Set issuer's X509 certificate. This provides issuer's DN and alternate
  165.      * names and public key.
  166.      *
  167.      * @param issuer
  168.      * @param pubkey
  169.      * @return
  170.      */
  171.     public X509CertificateBuilder setIssuer(X509Certificate issuer) {
  172.         this.issuer = issuer;
  173.         return this;
  174.     }
  175.  
  176.     /**
  177.      * Set subject's email addresses (individual, server or CA).
  178.      *
  179.      * @param emailAddresses
  180.      * @return
  181.      */
  182.     public X509CertificateBuilder setEmailAddresses(String... emailAddresses) {
  183.         for (String address : emailAddresses) {
  184.             subjectNames.add(new GeneralName(GeneralName.rfc822Name, address));
  185.         }
  186.         return this;
  187.     }
  188.  
  189.     /**
  190.      * Set subject's DNS names (server or CA?). Note: servers should use their
  191.      * canonical hostname as the X.500 CommonName used as their subjectDN. (See
  192.      * above.) This provides more flexibility but many clients won't look for
  193.      * these extensions.
  194.      *
  195.      * @param dnsNames
  196.      * @return
  197.      */
  198.     public X509CertificateBuilder setDnsNames(String... dnsNames) {
  199.         for (String name : dnsNames) {
  200.             subjectNames.add(new GeneralName(GeneralName.dNSName, name));
  201.         }
  202.         return this;
  203.     }
  204.  
  205.     /**
  206.      * Set subject's IP Address (server).
  207.      *
  208.      * @param ipAddresses
  209.      * @return
  210.      */
  211.     public X509CertificateBuilder setIpAddresses(String... ipAddresses) {
  212.         for (String address : ipAddresses) {
  213.             subjectNames.add(new GeneralName(GeneralName.iPAddress, address));
  214.         }
  215.         return this;
  216.     }
  217.  
  218.     /**
  219.      * Set subject's directory names. I think this refers to alternate X.500
  220.      * principal names, not filesystem directories.
  221.      *
  222.      * @param dirNames
  223.      * @return
  224.      */
  225.     public X509CertificateBuilder setDirectoryNames(String... dirNames) {
  226.         for (String name : dirNames) {
  227.             subjectNames.add(new GeneralName(GeneralName.directoryName, name));
  228.         }
  229.         return this;
  230.     }
  231.  
  232.     /**
  233.      * Set issuer's email addresses (individual, server or CA).
  234.      *
  235.      * @param emailAddresses
  236.      * @return
  237.      */
  238.     public X509CertificateBuilder setIssuerEmailAddresses(String... emailAddresses) {
  239.         for (String address : emailAddresses) {
  240.             issuerNames.add(new GeneralName(GeneralName.rfc822Name, address));
  241.         }
  242.         return this;
  243.     }
  244.  
  245.     /**
  246.      * Set issuer's DNS names (server or CA?). Note: servers should use their
  247.      * canonical hostname as the X.500 CommonName used as their subjectDN. (See
  248.      * above.) This provides more flexibility but many clients won't look for
  249.      * these extensions.
  250.      *
  251.      * @param dnsNames
  252.      * @return
  253.      */
  254.     public X509CertificateBuilder setIssuerDnsNames(String... dnsNames) {
  255.         for (String name : dnsNames) {
  256.             issuerNames.add(new GeneralName(GeneralName.dNSName, name));
  257.         }
  258.         return this;
  259.     }
  260.  
  261.     /**
  262.      * Set issuer's IP Address (server).
  263.      *
  264.      * @param ipAddresses
  265.      * @return
  266.      */
  267.     public X509CertificateBuilder setIssuerIpAddresses(String... ipAddresses) {
  268.         for (String address : ipAddresses) {
  269.             issuerNames.add(new GeneralName(GeneralName.iPAddress, address));
  270.         }
  271.         return this;
  272.     }
  273.  
  274.     /**
  275.      * Set issuer's directory names. I think this refers to alternate X.500
  276.      * principal names, not filesystem directories.
  277.      *
  278.      * @param dirNames
  279.      * @return
  280.      */
  281.     public X509CertificateBuilder setIssuerDirectoryNames(String... dirNames) {
  282.         for (String name : dirNames) {
  283.             issuerNames.add(new GeneralName(GeneralName.directoryName, name));
  284.         }
  285.         return this;
  286.     }
  287.  
  288.     /**
  289.      * Set key usage. TODO: provide parameters that don't expose implementation
  290.      * details.
  291.      *
  292.      * @param usage
  293.      * @return
  294.      */
  295.     public X509CertificateBuilder setKeyUsage(KeyUsage usage) {
  296.         this.keyUsage = usage;
  297.         return this;
  298.     }
  299.  
  300.     /**
  301.      * Set extended key usage. TODO: provide parameters that don't expose
  302.      * implementation details.
  303.      *
  304.      * @param usage
  305.      * @return
  306.      */
  307.     public X509CertificateBuilder setExtendedKeyUsage(ExtendedKeyUsage extendedKeyUsage) {
  308.         this.extendedKeyUsage = extendedKeyUsage;
  309.         return this;
  310.     }
  311.  
  312.     /**
  313.      * Set the certificate's Basic Constraint (can this certificate be used to
  314.      * sign other certificates?). There are no restrictions on certification
  315.      * path length.
  316.      *
  317.      * @param basicConstraint
  318.      * @return
  319.      */
  320.     public X509CertificateBuilder setBasicConstraints(boolean basicConstraint) {
  321.         this.basicConstraint = basicConstraint;
  322.         return this;
  323.     }
  324.  
  325.     /**
  326.      * Set the certificate's Basic Constraint (can this certificate be used to
  327.      * sign other certificates?) and maximum certification path length. A value
  328.      * of 0 means that this certificate can be used to sign leafs but cannot be
  329.      * used to create other signing certs.
  330.      *
  331.      * @param basicConstraint
  332.      * @param pathLengthConstraint
  333.      * @return
  334.      */
  335.     public X509CertificateBuilder setBasicConstraints(boolean basicConstraint,
  336.             int pathLengthConstraint) {
  337.         this.basicConstraint = basicConstraint;
  338.         this.pathLengthConstraint = pathLengthConstraint;
  339.         return this;
  340.     }
  341.  
  342.     /**
  343.      * Perform any other validation checks on issuer. This method can be
  344.      * replaced by an injected strategy.
  345.      *
  346.      * @param ex
  347.      */
  348.     public void checkIssuer(X509CertificateBuilderIllegalArgumentException ex)
  349.             throws CertificateParsingException, KeyStoreException {
  350.         if (issuer == null) {
  351.             return;
  352.         }
  353.         if (keyStore == null) {
  354.             ex.setIssuerCannotSignCertificates(true);
  355.             return;
  356.         }
  357.  
  358.         // look up issuer's certificate chain in our keystore using
  359.         // the issuer's subject DN as the alias.
  360.         String alias = issuer.getSubjectDN().getName();
  361.         if (!keyStore.containsAlias(alias)) {
  362.             ex.setUnknownIssuer(true);
  363.             return;
  364.         }
  365.  
  366.         // validate the certificate chain.
  367.         X509Certificate last = null;
  368.         X509Certificate[] chain = (X509Certificate[]) keyStore.getCertificateChain(alias);
  369.         for (X509Certificate cert : chain) {
  370.             if (cert.getBasicConstraints() < 0) {
  371.                 ex.setIssuerCannotSignCertificates(true);
  372.             }
  373.             try {
  374.                 cert.checkValidity();
  375.             } catch (CertificateExpiredException e) {
  376.                 ex.setIssuerCannotSignCertificates(true);
  377.             } catch (CertificateNotYetValidException e) {
  378.                 ex.setIssuerCannotSignCertificates(true);
  379.             }
  380.             if (last != null) {
  381.                 try {
  382.                     last.verify(cert.getPublicKey());
  383.                 } catch (CertificateException e) {
  384.                     ex.setIssuerCannotSignCertificates(true);
  385.                 } catch (SignatureException e) {
  386.                     ex.setIssuerCannotSignCertificates(true);
  387.                 } catch (InvalidKeyException e) {
  388.                     ex.setIssuerCannotSignCertificates(true);
  389.                 } catch (NoSuchProviderException e) {
  390.                     ex.setIssuerCannotSignCertificates(true);
  391.                 } catch (NoSuchAlgorithmException e) {
  392.                     ex.setIssuerCannotSignCertificates(true);
  393.                 }
  394.             }
  395.             last = cert;
  396.         }
  397.  
  398.         // at this point we have a valid certificate chain and
  399.         // have to trust that the keystore only contains trusted
  400.         // certificates.
  401.     }
  402.  
  403.     /**
  404.      * Perform any other policy check. This method can be replaced by an
  405.      * injected strategy.
  406.      *
  407.      * @param ex
  408.      */
  409.     public void checkPolicy(X509CertificateBuilderIllegalArgumentException ex) {
  410.  
  411.     }
  412.  
  413.     /**
  414.      * Establish any other policy. For instance this might add (or remove)
  415.      * alternative names, add or remove key usage constraints, etc. This method
  416.      * can be replaced by an injected strategy.
  417.      *
  418.      * @param ex
  419.      */
  420.     public void establishPolicy() {
  421.         // ensure keyCertSign is (only) available on signing keys.
  422.         if (basicConstraint) {
  423.             if (keyUsage == null) {
  424.                 keyUsage = new KeyUsage(KeyUsage.keyCertSign);
  425.             } else {
  426.                 keyUsage = new KeyUsage(keyUsage.intValue() | KeyUsage.keyCertSign);
  427.             }
  428.         } else {
  429.             if (keyUsage != null) {
  430.                 keyUsage = new KeyUsage(keyUsage.intValue()
  431.                         & (Integer.MAX_VALUE ^ KeyUsage.keyCertSign));
  432.             }
  433.         }
  434.  
  435.         // other policies..
  436.     }
  437.  
  438.     /**
  439.      * Build the X509 certificate.
  440.      *
  441.      * Implementation note: this uses the deprecated methods until I can find
  442.      * documentation on using the newer classes.
  443.      *
  444.      * @param pkey
  445.      * @return
  446.      * @throws InvalidKeyException
  447.      * @throws NoSuchAlgorithmException
  448.      * @throws SignatureException
  449.      * @throws CertificateEncodingException
  450.      * @throws CertificateParsingException
  451.      */
  452.     public X509Certificate build(PrivateKey pkey) throws InvalidKeyException,
  453.             NoSuchAlgorithmException, SignatureException, CertificateEncodingException,
  454.             CertificateParsingException, KeyStoreException {
  455.  
  456.         // verify we have everything we need....
  457.         X509CertificateBuilderIllegalArgumentException ex = new X509CertificateBuilderIllegalArgumentException();
  458.  
  459.         if (issuer == null) {
  460.             // require issuer cert for everything other than self-signed certs.
  461.             if ((issuerDN != null) && (issuerDN.equals(subjectDN))) {
  462.                 ex.setMissingIssuerCertificate(true);
  463.             }
  464.         } else {
  465.             // verify issuer&#039;s cert is active.
  466.             Date now = new Date();
  467.             if (!(issuer.getNotBefore().before(now) && now.before(issuer.getNotAfter()))) {
  468.                 ex.setInvalidIssuer(true);
  469.             }
  470.  
  471.             // verify our &#039;notBefore&#039; is within range of issuer&#039;s certificate.
  472.             if ((notBefore == null) || notBefore.before(issuer.getNotBefore())) {
  473.                 setNotBefore(issuer.getNotBefore());
  474.             }
  475.             if (notBefore.after(issuer.getNotAfter())) {
  476.                 ex.setUnacceptableDateRange(true);
  477.             }
  478.  
  479.             // verify our &#039;notAfter&#039; is within range of issuer&#039;s certificate.
  480.             if ((notAfter == null) || notAfter.after(issuer.getNotAfter())) {
  481.                 setNotAfter(issuer.getNotAfter());
  482.             }
  483.             if (notAfter.before(issuer.getNotBefore())) {
  484.                 ex.setUnacceptableDateRange(true);
  485.             }
  486.  
  487.             // verify issuer can sign certificates
  488.             int pathLenConstraint = issuer.getBasicConstraints();
  489.             if (pathLenConstraint  0) {
  490.             throw ex;
  491.         }
  492.  
  493.         X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
  494.  
  495.         // set the mandatory properties
  496.         generator.setSerialNumber(serial);
  497.         generator.setIssuerDN(issuerDN);
  498.         generator.setSubjectDN(subjectDN);
  499.         generator.setNotBefore(notBefore);
  500.         generator.setNotAfter(notAfter);
  501.         generator.setPublicKey(pubkey);
  502.         generator.setSignatureAlgorithm(SIGNATURE_ALGORITHM);
  503.  
  504.         // can this certificate be used to sign more certificates?
  505.         // make sure pathlenthconstraint is always lower than issuer's.
  506.         if (basicConstraint) {
  507.             if ((issuer != null) && (pathLengthConstraint > issuer.getBasicConstraints() - 1)) {
  508.                 pathLengthConstraint = issuer.getBasicConstraints() - 1;
  509.             }
  510.             if (pathLengthConstraint >= 0) {
  511.                 generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
  512.                         pathLengthConstraint));
  513.             } else {
  514.                 generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
  515.                         true));
  516.             }
  517.         } else {
  518.             generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
  519.                     false));
  520.         }
  521.  
  522.         // set the X509 optional stuff.
  523.  
  524.         // we always add the subject key identifier.
  525.         SubjectKeyIdentifierStructure skis = new SubjectKeyIdentifierStructure(pubkey);
  526.         generator.addExtension(X509Extensions.SubjectKeyIdentifier, false, skis);
  527.  
  528.         // we always add the authority key identifier if possible.
  529.         if (issuer != null) {
  530.             // signed certificates....
  531.             AuthorityKeyIdentifierStructure akis = new AuthorityKeyIdentifierStructure(issuer);
  532.             generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
  533.         } else if (subjectDN.equals(issuerDN)) {
  534.             // self-signed certificates....
  535.             GeneralNames issuerName = new GeneralNames(new GeneralName(GeneralName.directoryName,
  536.                     issuerDN));
  537.             AuthorityKeyIdentifier akis = new AuthorityKeyIdentifierStructure(pubkey);
  538.             akis = new AuthorityKeyIdentifier(akis.getKeyIdentifier(), issuerName, serial);
  539.             generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
  540.         }
  541.  
  542.         // establish any other policy.
  543.         establishPolicy();
  544.  
  545.         // add subject alternative names if available - this is email, dns
  546.         // names, etc.
  547.         if (!subjectNames.isEmpty()) {
  548.             generator.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
  549.                     subjectNames.toArray(new GeneralName[0])));
  550.         }
  551.  
  552.         // add issuer alternative names if available - this is email, dns
  553.         // names, etc.
  554.         if (!issuerNames.isEmpty()) {
  555.             generator.addExtension(X509Extensions.IssuerAlternativeName, false, new GeneralNames(
  556.                     issuerNames.toArray(new GeneralName[0])));
  557.         }
  558.  
  559.         // add mandatory key usage constraints.
  560.         if (keyUsage != null) {
  561.             generator.addExtension(X509Extensions.KeyUsage, true, keyUsage);
  562.         }
  563.  
  564.         // add optional extended key usage constraints.
  565.         if (extendedKeyUsage != null) {
  566.             generator.addExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage);
  567.         }
  568.  
  569.         X509Certificate cert = generator.generate(pkey);
  570.         return cert;
  571.     }
  572. }
package com.otterca.repository.util;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.ParametersAreNonnullByDefault;

import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
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.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

/**
 * Convenience class that builds X509 certificates.
 *
 * @author bgiles@otterca.com
 */
@ParametersAreNonnullByDefault
@SuppressWarnings("deprecation")
public class X509CertificateBuilder {
    public static final String SIGNATURE_ALGORITHM = "SHA256WITHRSA";
    private KeyStore keyStore;
    private X509Certificate issuer;
    private BigInteger serial;
    private X509Principal subjectDN;
    private X509Principal issuerDN;
    private Date notBefore;
    private Date notAfter;
    private PublicKey pubkey;
    private final List<GeneralName> subjectNames = new ArrayList<>();
    private final List<GeneralName> issuerNames = new ArrayList<>();
    private KeyUsage keyUsage;
    private ExtendedKeyUsage extendedKeyUsage;
    private boolean basicConstraint;
    private int pathLengthConstraint = -1;

    /**
     * Default constructor that can be used to create self-signed certificates
     * but not certificate chains.
     *
     * @param keyStore
     */
    public X509CertificateBuilder() {
    }

    /**
     * Constructor that requires keystore to validate certificate chain for
     * issuer.
     *
     * @param keyStore
     */
    public X509CertificateBuilder(KeyStore keyStore) {
        this.keyStore = keyStore;
    }

    /**
     * Reset builder to default state.
     */
    public void reset() {
        issuer = null;
        serial = null;
        subjectDN = null;
        issuerDN = null;
        notBefore = null;
        notAfter = null;
        pubkey = null;
        subjectNames.clear();
        issuerNames.clear();
        keyUsage = null;
        basicConstraint = false;
        pathLengthConstraint = -1;
    }

    /**
     * Set serial number.
     *
     * @param serial
     * @return
     */
    public X509CertificateBuilder setSerialNumber(BigInteger serial) {
        this.serial = serial;
        return this;
    }

    /**
     * Set subject name (as X509Principal).
     *
     * @param dirName
     * @return
     */
    public X509CertificateBuilder setSubject(String dirName) {
        this.subjectDN = new X509Principal(dirName);
        return this;
    }

    /**
     * Set issuer name (as X509Principal).
     *
     * @param dirName
     * @return
     */
    public X509CertificateBuilder setIssuer(String dirName) {
        this.issuerDN = new X509Principal(dirName);
        return this;
    }

    /**
     * Set 'notBefore' date.
     *
     * @param notBefore
     * @return
     */
    public X509CertificateBuilder setNotBefore(Date notBefore) {
        this.notBefore = notBefore;
        return this;
    }

    /**
     * Set 'notAfter' date.
     *
     * @param notAfter
     * @return
     */
    public X509CertificateBuilder setNotAfter(Date notAfter) {
        this.notAfter = notAfter;
        return this;
    }

    /**
     * Set public key.
     *
     * @param pubkey
     * @return
     */
    public X509CertificateBuilder setPublicKey(PublicKey pubkey) {
        this.pubkey = pubkey;
        return this;
    }

    /**
     * Set issuer's X509 certificate. This provides issuer's DN and alternate
     * names and public key.
     *
     * @param issuer
     * @param pubkey
     * @return
     */
    public X509CertificateBuilder setIssuer(X509Certificate issuer) {
        this.issuer = issuer;
        return this;
    }

    /**
     * Set subject's email addresses (individual, server or CA).
     *
     * @param emailAddresses
     * @return
     */
    public X509CertificateBuilder setEmailAddresses(String... emailAddresses) {
        for (String address : emailAddresses) {
            subjectNames.add(new GeneralName(GeneralName.rfc822Name, address));
        }
        return this;
    }

    /**
     * Set subject's DNS names (server or CA?). Note: servers should use their
     * canonical hostname as the X.500 CommonName used as their subjectDN. (See
     * above.) This provides more flexibility but many clients won't look for
     * these extensions.
     *
     * @param dnsNames
     * @return
     */
    public X509CertificateBuilder setDnsNames(String... dnsNames) {
        for (String name : dnsNames) {
            subjectNames.add(new GeneralName(GeneralName.dNSName, name));
        }
        return this;
    }

    /**
     * Set subject's IP Address (server).
     *
     * @param ipAddresses
     * @return
     */
    public X509CertificateBuilder setIpAddresses(String... ipAddresses) {
        for (String address : ipAddresses) {
            subjectNames.add(new GeneralName(GeneralName.iPAddress, address));
        }
        return this;
    }

    /**
     * Set subject's directory names. I think this refers to alternate X.500
     * principal names, not filesystem directories.
     *
     * @param dirNames
     * @return
     */
    public X509CertificateBuilder setDirectoryNames(String... dirNames) {
        for (String name : dirNames) {
            subjectNames.add(new GeneralName(GeneralName.directoryName, name));
        }
        return this;
    }

    /**
     * Set issuer's email addresses (individual, server or CA).
     *
     * @param emailAddresses
     * @return
     */
    public X509CertificateBuilder setIssuerEmailAddresses(String... emailAddresses) {
        for (String address : emailAddresses) {
            issuerNames.add(new GeneralName(GeneralName.rfc822Name, address));
        }
        return this;
    }

    /**
     * Set issuer's DNS names (server or CA?). Note: servers should use their
     * canonical hostname as the X.500 CommonName used as their subjectDN. (See
     * above.) This provides more flexibility but many clients won't look for
     * these extensions.
     *
     * @param dnsNames
     * @return
     */
    public X509CertificateBuilder setIssuerDnsNames(String... dnsNames) {
        for (String name : dnsNames) {
            issuerNames.add(new GeneralName(GeneralName.dNSName, name));
        }
        return this;
    }

    /**
     * Set issuer's IP Address (server).
     *
     * @param ipAddresses
     * @return
     */
    public X509CertificateBuilder setIssuerIpAddresses(String... ipAddresses) {
        for (String address : ipAddresses) {
            issuerNames.add(new GeneralName(GeneralName.iPAddress, address));
        }
        return this;
    }

    /**
     * Set issuer's directory names. I think this refers to alternate X.500
     * principal names, not filesystem directories.
     *
     * @param dirNames
     * @return
     */
    public X509CertificateBuilder setIssuerDirectoryNames(String... dirNames) {
        for (String name : dirNames) {
            issuerNames.add(new GeneralName(GeneralName.directoryName, name));
        }
        return this;
    }

    /**
     * Set key usage. TODO: provide parameters that don't expose implementation
     * details.
     *
     * @param usage
     * @return
     */
    public X509CertificateBuilder setKeyUsage(KeyUsage usage) {
        this.keyUsage = usage;
        return this;
    }

    /**
     * Set extended key usage. TODO: provide parameters that don't expose
     * implementation details.
     *
     * @param usage
     * @return
     */
    public X509CertificateBuilder setExtendedKeyUsage(ExtendedKeyUsage extendedKeyUsage) {
        this.extendedKeyUsage = extendedKeyUsage;
        return this;
    }

    /**
     * Set the certificate's Basic Constraint (can this certificate be used to
     * sign other certificates?). There are no restrictions on certification
     * path length.
     *
     * @param basicConstraint
     * @return
     */
    public X509CertificateBuilder setBasicConstraints(boolean basicConstraint) {
        this.basicConstraint = basicConstraint;
        return this;
    }

    /**
     * Set the certificate's Basic Constraint (can this certificate be used to
     * sign other certificates?) and maximum certification path length. A value
     * of 0 means that this certificate can be used to sign leafs but cannot be
     * used to create other signing certs.
     *
     * @param basicConstraint
     * @param pathLengthConstraint
     * @return
     */
    public X509CertificateBuilder setBasicConstraints(boolean basicConstraint,
            int pathLengthConstraint) {
        this.basicConstraint = basicConstraint;
        this.pathLengthConstraint = pathLengthConstraint;
        return this;
    }

    /**
     * Perform any other validation checks on issuer. This method can be
     * replaced by an injected strategy.
     *
     * @param ex
     */
    public void checkIssuer(X509CertificateBuilderIllegalArgumentException ex)
            throws CertificateParsingException, KeyStoreException {
        if (issuer == null) {
            return;
        }
        if (keyStore == null) {
            ex.setIssuerCannotSignCertificates(true);
            return;
        }

        // look up issuer's certificate chain in our keystore using
        // the issuer's subject DN as the alias.
        String alias = issuer.getSubjectDN().getName();
        if (!keyStore.containsAlias(alias)) {
            ex.setUnknownIssuer(true);
            return;
        }

        // validate the certificate chain.
        X509Certificate last = null;
        X509Certificate[] chain = (X509Certificate[]) keyStore.getCertificateChain(alias);
        for (X509Certificate cert : chain) {
            if (cert.getBasicConstraints() < 0) {
                ex.setIssuerCannotSignCertificates(true);
            }
            try {
                cert.checkValidity();
            } catch (CertificateExpiredException e) {
                ex.setIssuerCannotSignCertificates(true);
            } catch (CertificateNotYetValidException e) {
                ex.setIssuerCannotSignCertificates(true);
            }
            if (last != null) {
                try {
                    last.verify(cert.getPublicKey());
                } catch (CertificateException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (SignatureException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (InvalidKeyException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (NoSuchProviderException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (NoSuchAlgorithmException e) {
                    ex.setIssuerCannotSignCertificates(true);
                }
            }
            last = cert;
        }

        // at this point we have a valid certificate chain and
        // have to trust that the keystore only contains trusted
        // certificates.
    }

    /**
     * Perform any other policy check. This method can be replaced by an
     * injected strategy.
     *
     * @param ex
     */
    public void checkPolicy(X509CertificateBuilderIllegalArgumentException ex) {

    }

    /**
     * Establish any other policy. For instance this might add (or remove)
     * alternative names, add or remove key usage constraints, etc. This method
     * can be replaced by an injected strategy.
     *
     * @param ex
     */
    public void establishPolicy() {
        // ensure keyCertSign is (only) available on signing keys.
        if (basicConstraint) {
            if (keyUsage == null) {
                keyUsage = new KeyUsage(KeyUsage.keyCertSign);
            } else {
                keyUsage = new KeyUsage(keyUsage.intValue() | KeyUsage.keyCertSign);
            }
        } else {
            if (keyUsage != null) {
                keyUsage = new KeyUsage(keyUsage.intValue()
                        & (Integer.MAX_VALUE ^ KeyUsage.keyCertSign));
            }
        }

        // other policies..
    }

    /**
     * Build the X509 certificate.
     *
     * Implementation note: this uses the deprecated methods until I can find
     * documentation on using the newer classes.
     *
     * @param pkey
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws CertificateEncodingException
     * @throws CertificateParsingException
     */
    public X509Certificate build(PrivateKey pkey) throws InvalidKeyException,
            NoSuchAlgorithmException, SignatureException, CertificateEncodingException,
            CertificateParsingException, KeyStoreException {

        // verify we have everything we need....
        X509CertificateBuilderIllegalArgumentException ex = new X509CertificateBuilderIllegalArgumentException();

        if (issuer == null) {
            // require issuer cert for everything other than self-signed certs.
            if ((issuerDN != null) && (issuerDN.equals(subjectDN))) {
                ex.setMissingIssuerCertificate(true);
            }
        } else {
            // verify issuer&#039;s cert is active.
            Date now = new Date();
            if (!(issuer.getNotBefore().before(now) && now.before(issuer.getNotAfter()))) {
                ex.setInvalidIssuer(true);
            }

            // verify our &#039;notBefore&#039; is within range of issuer&#039;s certificate.
            if ((notBefore == null) || notBefore.before(issuer.getNotBefore())) {
                setNotBefore(issuer.getNotBefore());
            }
            if (notBefore.after(issuer.getNotAfter())) {
                ex.setUnacceptableDateRange(true);
            }

            // verify our &#039;notAfter&#039; is within range of issuer&#039;s certificate.
            if ((notAfter == null) || notAfter.after(issuer.getNotAfter())) {
                setNotAfter(issuer.getNotAfter());
            }
            if (notAfter.before(issuer.getNotBefore())) {
                ex.setUnacceptableDateRange(true);
            }

            // verify issuer can sign certificates
            int pathLenConstraint = issuer.getBasicConstraints();
            if (pathLenConstraint  0) {
            throw ex;
        }

        X509V3CertificateGenerator generator = new X509V3CertificateGenerator();

        // set the mandatory properties
        generator.setSerialNumber(serial);
        generator.setIssuerDN(issuerDN);
        generator.setSubjectDN(subjectDN);
        generator.setNotBefore(notBefore);
        generator.setNotAfter(notAfter);
        generator.setPublicKey(pubkey);
        generator.setSignatureAlgorithm(SIGNATURE_ALGORITHM);

        // can this certificate be used to sign more certificates?
        // make sure pathlenthconstraint is always lower than issuer's.
        if (basicConstraint) {
            if ((issuer != null) && (pathLengthConstraint > issuer.getBasicConstraints() - 1)) {
                pathLengthConstraint = issuer.getBasicConstraints() - 1;
            }
            if (pathLengthConstraint >= 0) {
                generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
                        pathLengthConstraint));
            } else {
                generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
                        true));
            }
        } else {
            generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
                    false));
        }

        // set the X509 optional stuff.

        // we always add the subject key identifier.
        SubjectKeyIdentifierStructure skis = new SubjectKeyIdentifierStructure(pubkey);
        generator.addExtension(X509Extensions.SubjectKeyIdentifier, false, skis);

        // we always add the authority key identifier if possible.
        if (issuer != null) {
            // signed certificates....
            AuthorityKeyIdentifierStructure akis = new AuthorityKeyIdentifierStructure(issuer);
            generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
        } else if (subjectDN.equals(issuerDN)) {
            // self-signed certificates....
            GeneralNames issuerName = new GeneralNames(new GeneralName(GeneralName.directoryName,
                    issuerDN));
            AuthorityKeyIdentifier akis = new AuthorityKeyIdentifierStructure(pubkey);
            akis = new AuthorityKeyIdentifier(akis.getKeyIdentifier(), issuerName, serial);
            generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
        }

        // establish any other policy.
        establishPolicy();

        // add subject alternative names if available - this is email, dns
        // names, etc.
        if (!subjectNames.isEmpty()) {
            generator.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
                    subjectNames.toArray(new GeneralName[0])));
        }

        // add issuer alternative names if available - this is email, dns
        // names, etc.
        if (!issuerNames.isEmpty()) {
            generator.addExtension(X509Extensions.IssuerAlternativeName, false, new GeneralNames(
                    issuerNames.toArray(new GeneralName[0])));
        }

        // add mandatory key usage constraints.
        if (keyUsage != null) {
            generator.addExtension(X509Extensions.KeyUsage, true, keyUsage);
        }

        // add optional extended key usage constraints.
        if (extendedKeyUsage != null) {
            generator.addExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage);
        }

        X509Certificate cert = generator.generate(pkey);
        return cert;
    }
}

The Exception

The builder throws a specialized IllegalArgumentException if it is missing required values or there’s a problem with them. This exception allows multiple errors to be identified at one time.

The final version will have a better ‘message’ that lists all of the errors.

  1. package com.otterca.repository.util;
  2.  
  3. /**
  4.  * IllegalArgumentException that bundles multiple errors into a single
  5.  * exception.
  6.  *
  7.  * @author bgiles@otterca.com
  8.  */
  9. public class X509CertificateBuilderIllegalArgumentException extends IllegalArgumentException {
  10.     private static final long serialVersionUID = 1L;
  11.     private boolean missingIssuerCertificate;
  12.     private boolean missingSerialNumber;
  13.     private boolean missingSubject;
  14.     private boolean missingIssuer;
  15.     private boolean missingNotBeforeDate;
  16.     private boolean missingNotAfterDate;
  17.     private boolean missingPublicKey;
  18.     private boolean badDateRange;
  19.     private boolean invalidIssuer;
  20.     private boolean issuerCannotSignCertificates;
  21.     private boolean unacceptableDateRange;
  22.     private boolean missingKeyUsage;
  23.     private boolean unknownIssuer;
  24.     private int errorCount = 0;
  25.  
  26.     /**
  27.      * @return the number of errors.
  28.      */
  29.     public int getErrorCount() {
  30.         return errorCount;
  31.     }
  32.  
  33.     /**
  34.      * @return the missingIssuerCertificate
  35.      */
  36.     public boolean isMissingIssuerCertificate() {
  37.         return missingIssuerCertificate;
  38.     }
  39.  
  40.     /**
  41.      * @return the missingSerialNumber
  42.      */
  43.     public boolean isMissingSerialNumber() {
  44.         return missingSerialNumber;
  45.     }
  46.  
  47.     /**
  48.      * @return the missingSubject
  49.      */
  50.     public boolean isMissingSubject() {
  51.         return missingSubject;
  52.     }
  53.  
  54.     /**
  55.      * @return the missingIssuer
  56.      */
  57.     public boolean isMissingIssuer() {
  58.         return missingIssuer;
  59.     }
  60.  
  61.     /**
  62.      * @return the missingNotBeforeDate
  63.      */
  64.     public boolean isMissingNotBeforeDate() {
  65.         return missingNotBeforeDate;
  66.     }
  67.  
  68.     /**
  69.      * @return the missingNotAfterDate
  70.      */
  71.     public boolean isMissingNotAfterDate() {
  72.         return missingNotAfterDate;
  73.     }
  74.  
  75.     /**
  76.      * @return the missingPublicKey
  77.      */
  78.     public boolean isMissingPublicKey() {
  79.         return missingPublicKey;
  80.     }
  81.  
  82.     /**
  83.      * @return the badDateRange
  84.      */
  85.     public boolean isBadDateRange() {
  86.         return badDateRange;
  87.     }
  88.  
  89.     /**
  90.      * @return the invalidIssuer
  91.      */
  92.     public boolean isInvalidIssuer() {
  93.         return invalidIssuer;
  94.     }
  95.  
  96.     /**
  97.      * @return the issuerCannotSignCertificates
  98.      */
  99.     public boolean isIssuerCannotSignCertificates() {
  100.         return issuerCannotSignCertificates;
  101.     }
  102.  
  103.     /**
  104.      * @return the unknownIssuer
  105.      */
  106.     boolean isUnknownIssuer() {
  107.         return unknownIssuer;
  108.     }
  109.  
  110.     /**
  111.      * @return the unacceptableDateRange
  112.      */
  113.     boolean isUnacceptableDateRange() {
  114.         return unacceptableDateRange;
  115.     }
  116.  
  117.     /**
  118.      * @return the missingKeyUsage
  119.      */
  120.     boolean isMissingKeyUsage() {
  121.         return missingKeyUsage;
  122.     }
  123.  
  124.     /**
  125.      * @param missingIssuerCertificate
  126.      *            the missingIssuerCertificate to set
  127.      */
  128.     void setMissingIssuerCertificate(boolean missingIssuerCertificate) {
  129.         this.missingIssuerCertificate = missingIssuerCertificate;
  130.     }
  131.  
  132.     /**
  133.      * @param missingSerialNumber
  134.      *            the missingSerialNumber to set
  135.      */
  136.     void setMissingSerialNumber(boolean missingSerialNumber) {
  137.         errorCount++;
  138.         this.missingSerialNumber = missingSerialNumber;
  139.     }
  140.  
  141.     /**
  142.      * @param missingSubject
  143.      *            the missingSubject to set
  144.      */
  145.     void setMissingSubject(boolean missingSubject) {
  146.         errorCount++;
  147.         this.missingSubject = missingSubject;
  148.     }
  149.  
  150.     /**
  151.      * @param missingIssuer
  152.      *            the missingIssuer to set
  153.      */
  154.     void setMissingIssuer(boolean missingIssuer) {
  155.         errorCount++;
  156.         this.missingIssuer = missingIssuer;
  157.     }
  158.  
  159.     /**
  160.      * @param missingNotBeforeDate
  161.      *            the missingNotBeforeDate to set
  162.      */
  163.     void setMissingNotBeforeDate(boolean missingNotBeforeDate) {
  164.         errorCount++;
  165.         this.missingNotBeforeDate = missingNotBeforeDate;
  166.     }
  167.  
  168.     /**
  169.      * @param missingNotAfterDate
  170.      *            the missingNotAfterDate to set
  171.      */
  172.     void setMissingNotAfterDate(boolean missingNotAfterDate) {
  173.         errorCount++;
  174.         this.missingNotAfterDate = missingNotAfterDate;
  175.     }
  176.  
  177.     /**
  178.      * @param missingPublicKey
  179.      *            the missingPublicKey to set
  180.      */
  181.     void setMissingPublicKey(boolean missingPublicKey) {
  182.         errorCount++;
  183.         this.missingPublicKey = missingPublicKey;
  184.     }
  185.  
  186.     /**
  187.      * @param badDateRange
  188.      *            the badDateRange to set
  189.      */
  190.     void setBadDateRange(boolean badDateRange) {
  191.         errorCount++;
  192.         this.badDateRange = badDateRange;
  193.     }
  194.  
  195.     /**
  196.      * @param invalidIssuer
  197.      *            the invalidIssuer to set
  198.      */
  199.     void setInvalidIssuer(boolean invalidIssuer) {
  200.         errorCount++;
  201.         this.invalidIssuer = invalidIssuer;
  202.     }
  203.  
  204.     /**
  205.      * @param issuerCannotSignCertificates
  206.      *            the issuerCannotSignCertificates to set
  207.      */
  208.     void setIssuerCannotSignCertificates(boolean issuerCannotSignCertificates) {
  209.         errorCount++;
  210.         this.issuerCannotSignCertificates = issuerCannotSignCertificates;
  211.     }
  212.  
  213.     /**
  214.      * @param unacceptableDateRange
  215.      *            the unacceptableDateRange to set
  216.      */
  217.     void setUnacceptableDateRange(boolean unacceptableDateRange) {
  218.         errorCount++;
  219.         this.unacceptableDateRange = unacceptableDateRange;
  220.     }
  221.  
  222.     /**
  223.      * @param missingKeyUsage
  224.      *            the missingKeyUsage to set
  225.      */
  226.     void setMissingKeyUsage(boolean missingKeyUsage) {
  227.         this.missingKeyUsage = missingKeyUsage;
  228.     }
  229.  
  230.     /**
  231.      * @param unknownIssuer
  232.      *            the unknownIssuer to set
  233.      */
  234.     void setUnknownIssuer(boolean unknownIssuer) {
  235.         this.unknownIssuer = unknownIssuer;
  236.     }
  237. }
package com.otterca.repository.util;

/**
 * IllegalArgumentException that bundles multiple errors into a single
 * exception.
 *
 * @author bgiles@otterca.com
 */
public class X509CertificateBuilderIllegalArgumentException extends IllegalArgumentException {
    private static final long serialVersionUID = 1L;
    private boolean missingIssuerCertificate;
    private boolean missingSerialNumber;
    private boolean missingSubject;
    private boolean missingIssuer;
    private boolean missingNotBeforeDate;
    private boolean missingNotAfterDate;
    private boolean missingPublicKey;
    private boolean badDateRange;
    private boolean invalidIssuer;
    private boolean issuerCannotSignCertificates;
    private boolean unacceptableDateRange;
    private boolean missingKeyUsage;
    private boolean unknownIssuer;
    private int errorCount = 0;

    /**
     * @return the number of errors.
     */
    public int getErrorCount() {
        return errorCount;
    }

    /**
     * @return the missingIssuerCertificate
     */
    public boolean isMissingIssuerCertificate() {
        return missingIssuerCertificate;
    }

    /**
     * @return the missingSerialNumber
     */
    public boolean isMissingSerialNumber() {
        return missingSerialNumber;
    }

    /**
     * @return the missingSubject
     */
    public boolean isMissingSubject() {
        return missingSubject;
    }

    /**
     * @return the missingIssuer
     */
    public boolean isMissingIssuer() {
        return missingIssuer;
    }

    /**
     * @return the missingNotBeforeDate
     */
    public boolean isMissingNotBeforeDate() {
        return missingNotBeforeDate;
    }

    /**
     * @return the missingNotAfterDate
     */
    public boolean isMissingNotAfterDate() {
        return missingNotAfterDate;
    }

    /**
     * @return the missingPublicKey
     */
    public boolean isMissingPublicKey() {
        return missingPublicKey;
    }

    /**
     * @return the badDateRange
     */
    public boolean isBadDateRange() {
        return badDateRange;
    }

    /**
     * @return the invalidIssuer
     */
    public boolean isInvalidIssuer() {
        return invalidIssuer;
    }

    /**
     * @return the issuerCannotSignCertificates
     */
    public boolean isIssuerCannotSignCertificates() {
        return issuerCannotSignCertificates;
    }

    /**
     * @return the unknownIssuer
     */
    boolean isUnknownIssuer() {
        return unknownIssuer;
    }

    /**
     * @return the unacceptableDateRange
     */
    boolean isUnacceptableDateRange() {
        return unacceptableDateRange;
    }

    /**
     * @return the missingKeyUsage
     */
    boolean isMissingKeyUsage() {
        return missingKeyUsage;
    }

    /**
     * @param missingIssuerCertificate
     *            the missingIssuerCertificate to set
     */
    void setMissingIssuerCertificate(boolean missingIssuerCertificate) {
        this.missingIssuerCertificate = missingIssuerCertificate;
    }

    /**
     * @param missingSerialNumber
     *            the missingSerialNumber to set
     */
    void setMissingSerialNumber(boolean missingSerialNumber) {
        errorCount++;
        this.missingSerialNumber = missingSerialNumber;
    }

    /**
     * @param missingSubject
     *            the missingSubject to set
     */
    void setMissingSubject(boolean missingSubject) {
        errorCount++;
        this.missingSubject = missingSubject;
    }

    /**
     * @param missingIssuer
     *            the missingIssuer to set
     */
    void setMissingIssuer(boolean missingIssuer) {
        errorCount++;
        this.missingIssuer = missingIssuer;
    }

    /**
     * @param missingNotBeforeDate
     *            the missingNotBeforeDate to set
     */
    void setMissingNotBeforeDate(boolean missingNotBeforeDate) {
        errorCount++;
        this.missingNotBeforeDate = missingNotBeforeDate;
    }

    /**
     * @param missingNotAfterDate
     *            the missingNotAfterDate to set
     */
    void setMissingNotAfterDate(boolean missingNotAfterDate) {
        errorCount++;
        this.missingNotAfterDate = missingNotAfterDate;
    }

    /**
     * @param missingPublicKey
     *            the missingPublicKey to set
     */
    void setMissingPublicKey(boolean missingPublicKey) {
        errorCount++;
        this.missingPublicKey = missingPublicKey;
    }

    /**
     * @param badDateRange
     *            the badDateRange to set
     */
    void setBadDateRange(boolean badDateRange) {
        errorCount++;
        this.badDateRange = badDateRange;
    }

    /**
     * @param invalidIssuer
     *            the invalidIssuer to set
     */
    void setInvalidIssuer(boolean invalidIssuer) {
        errorCount++;
        this.invalidIssuer = invalidIssuer;
    }

    /**
     * @param issuerCannotSignCertificates
     *            the issuerCannotSignCertificates to set
     */
    void setIssuerCannotSignCertificates(boolean issuerCannotSignCertificates) {
        errorCount++;
        this.issuerCannotSignCertificates = issuerCannotSignCertificates;
    }

    /**
     * @param unacceptableDateRange
     *            the unacceptableDateRange to set
     */
    void setUnacceptableDateRange(boolean unacceptableDateRange) {
        errorCount++;
        this.unacceptableDateRange = unacceptableDateRange;
    }

    /**
     * @param missingKeyUsage
     *            the missingKeyUsage to set
     */
    void setMissingKeyUsage(boolean missingKeyUsage) {
        this.missingKeyUsage = missingKeyUsage;
    }

    /**
     * @param unknownIssuer
     *            the unknownIssuer to set
     */
    void setUnknownIssuer(boolean unknownIssuer) {
        this.unknownIssuer = unknownIssuer;
    }
}

Danger, Danger, Will Robinson!

If you read nothing else in this series, read this!

Top 10 PKI Risks

Everything you Never Wanted to Know about PKI but were Forced to Find Out (Peter Gutmann)

java, security

Leave your Comment

Blue Taste Theme created by Jabox