This is part of a series on Digital Certificates.
- Introduction
- IDs
- X.509v3 Certificates
- Creating Certs with Bouncy Castle
- RA, CA and repository
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.
- 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());
- }
- }
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.
- 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's cert is active.
- Date now = new Date();
- if (!(issuer.getNotBefore().before(now) && now.before(issuer.getNotAfter()))) {
- ex.setInvalidIssuer(true);
- }
- // verify our 'notBefore' is within range of issuer's certificate.
- if ((notBefore == null) || notBefore.before(issuer.getNotBefore())) {
- setNotBefore(issuer.getNotBefore());
- }
- if (notBefore.after(issuer.getNotAfter())) {
- ex.setUnacceptableDateRange(true);
- }
- // verify our 'notAfter' is within range of issuer'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;
- }
- }
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's cert is active.
Date now = new Date();
if (!(issuer.getNotBefore().before(now) && now.before(issuer.getNotAfter()))) {
ex.setInvalidIssuer(true);
}
// verify our 'notBefore' is within range of issuer's certificate.
if ((notBefore == null) || notBefore.before(issuer.getNotBefore())) {
setNotBefore(issuer.getNotBefore());
}
if (notBefore.after(issuer.getNotAfter())) {
ex.setUnacceptableDateRange(true);
}
// verify our 'notAfter' is within range of issuer'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.
- 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;
- }
- }
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!
Everything you Never Wanted to Know about PKI but were Forced to Find Out (Peter Gutmann)