How to Generate Certificates and CRLs
The following provides information and examples for creating certificates and Certificate Revocation Lists (CRLs) using the Bouncy Castle API and EJBCA.
As an introduction, see How to Generate Key Pairs and Certification Requests, proving relevant information for interfacing to EJBCA using the Bouncy Castle APIs.
The following usage examples can be used separately or to complement the Training - PKI at the Edge (session 2) which introduces useful concepts and provides additional context.
Overview
The original name for the standard used is X.509, but the most common profile used is described in the IETF standard Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile, RFC 5280.
Utility Methods
This section introduces a couple of utility methods to make the examples easier to follow.
Certificates and CRLs have a limited lifetime, so the first utility method just allows creating a Date object representing some time a certain number of hours in the future.
/**
* Calculate a date in seconds (suitable for the PKIX profile - RFC 5280)
*
* @param hoursInFuture hours ahead of now, may be negative.
* @return a Date set to now + (hoursInFuture * 60 * 60) seconds
*/
public static Date calculateDate(int hoursInFuture)
{
long secs = System.currentTimeMillis() / 1000;
return new Date((secs + (hoursInFuture * 60 * 60)) * 1000);
}
Certificates also have serial numbers. A certificate issuer should never issue two certificates with the same serial number. There are a variety of ways to calculate serial numbers and the following example shows incrementing a long which is initialized using the current time.
private static long serialNumberBase = System.currentTimeMillis();
/**
* Calculate a serial number using a monotonically increasing value.
*
* @return a BigInteger representing the next serial number in the sequence.
*/
public static synchronized BigInteger calculateSerialNumber()
{
return BigInteger.valueOf(serialNumberBase++);
}
Trust Anchors
A certificate that we normally recognize as identifying a machine, an individual, or some other distinct “thing”, usually gains its authoritative provenance from a chain of certificates, or a certificate path, that leads to the certificate providing identity. At the other end of this chain is a Trust Anchor, a certificate that must be accepted at face value for the path to be accepted.
The following example code generates a Trust Anchor that is valid from now until 365 days away.
public static X509Certificate createTrustAnchor(
KeyPair keyPair, String sigAlg)
throws OperatorCreationException, CertificateException
{
X500Name name = new X500Name("CN=Trust Anchor");
X509v1CertificateBuilder certBldr = new JcaX509v1CertificateBuilder(
name,
calculateSerialNumber(),
calculateDate(0),
calculateDate(24 * 365),
name,
keyPair.getPublic());
ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
.setProvider("BC").build(keyPair.getPrivate());
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");
return converter.getCertificate(certBldr.build(signer));
}
This certificate is self-signed and since it is the certificate that represents the thing that trust is built on, something other than cryptography needs to be relied on to give it authority.
The example code generates a version 1 certificate and originally Trust Anchors were always version 1 certificates. Today it is more common to use version 3 certificates so a maximum path length and other constraints can be supplied, allowing the holder of the Trust Anchor’s corresponding private key to keep some control over the use of the Trust Anchor certificate. The next section outlines version 3 certificates and extensions.
Certificate Authority Certificates
A Trust Anchor in itself is not usually enough to start issuing certificates identifying individuals. As Trust Anchors are the one thing that has to be accepted at face value, there is usually a bit of work involved in deploying them and you want the private key for one to be locked away and rarely used. Thus, there is usually one or more Certificate Authority (CA) certificates between a Trust Anchor and an End Entity certificate. End Entity certificates are the certificates that we usually associate with individuals, email addresses, or websites.
Review the following code example that sets up a CA certificate.
public static X509Certificate createCACertificate(
X509Certificate signerCert, PrivateKey signerKey,
String sigAlg, PublicKey certKey, int followingCACerts)
throws GeneralSecurityException,
OperatorCreationException, CertIOException
{
X500Principal subject = new X500Principal("CN=Certificate Authority");
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
signerCert.getSubjectX500Principal(),
calculateSerialNumber(),
calculateDate(0),
calculateDate(24 * 60),
subject,
certKey);
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
certBldr.addExtension(Extension.basicConstraints,
true, new BasicConstraints(followingCACerts))
.addExtension(Extension.keyUsage,
true, new KeyUsage(KeyUsage.keyCertSign
| KeyUsage.cRLSign));
ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
.setProvider("BC").build(signerKey);
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");
return converter.getCertificate(certBldr.build(signer));
}
In this example, the builder used is the one for version 3 certificates. The main reason for using a version 3 builder is to make use of extensions. Extensions are useful as they provide a mechanism for the certificate issuer to describe how they would like the public key for the subject they are signing for to be used.
Two extensions are used in the example:
The first extension is represented by the BasicConstraints class, used to determine whether this certificate is a CA certificate and how many CA certificates can follow it in the chain (with 0 meaning the chain ends with the next certificate in the chain).
The second extension is represented by the KeyUsage class and specifies what the public key in the certificate can be used for. In this case, the extension says it can be used for signing other certificates and/or signing Certificate Revocation Lists (CRLs) (see How to Generate Certificates and CRLs#Certificate Revocation).
Note that a boolean (true)
is used as well. This flag is the “isCritical” flag that tells anyone looking at the certificate whether a particular extension must be understood by someone seeking to use the certificate. In this case, by setting the flag to true for both extensions, the issuer is telling anyone who cannot understand what the BasicConstraints
and KeyUsage
extensions are, that they should not be trying to use the certificate.
End Entity Certificates
The last certificate in a certificate path is the End Entity certificate. In the ordinary interpretation of a certificate path, the end entity certificate is usually the one you would be trying to make use of and need to validate.
The following displays a code example:
public static X509Certificate createEndEntity(
X509Certificate signerCert, PrivateKey signerKey,
String sigAlg, PublicKey certKey)
throws CertIOException, OperatorCreationException, CertificateException
{
X500Principal subject = new X500Principal("CN=End Entity");
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
signerCert.getSubjectX500Principal(),
calculateSerialNumber(),
calculateDate(0),
calculateDate(24 * 31),
subject,
certKey);
certBldr.addExtension(Extension.basicConstraints,
true, new BasicConstraints(false))
.addExtension(Extension.keyUsage,
true, new KeyUsage(KeyUsage.digitalSignature));
ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
.setProvider("BC").build(signerKey);
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");
return converter.getCertificate(certBldr.build(signer));
}
The code looks like the code for the CA certificate, but with different extension values. The most important difference is the value of BasicConstraints
where the constructor has simply been called with the boolean false.
This tells anyone looking at the certificate that it cannot be used as a CA certificate. The false value tells an observer that this certificate is the end of the path and must not have any other certificates following it.
Other Common Extensions
Extensions appear frequently throughout all the X.509 structures. Extensions provide a simple uniform mechanism for including additional information, either covered by the existing standard, or even in an internal proprietary standard. Two good examples are the subject key id extension and the authority key id extension.
The subject key id extension allows you to associate a simple octet string, represented by the ASN.1 type SubjectKeyIdentifier, with your certificate. Usually, this value is calculated from a SHA-1 hash of the public key the certificate contains. In Bouncy Castle, you can create a SubjectKeyIdentier as follows:
public static SubjectKeyIdentifier createSubjectKeyIdentifier(PublicKey key)
throws NoSuchAlgorithmException
{
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
return extUtils.createSubjectKeyIdentifier(key);
}
And you can include it in your certificate using the following code:
certBldr.addExtension(Extension.subjectKeyIdentifier,
false, createSubjectKeyIdentifier(certKey))
The reduced hash value is always much smaller in size than its corresponding certificate and you will find places in various PKIX protocols where the subject key id will be included rather than the entire certificate, or just the public key.
public static AuthorityKeyIdentifier createAuthorityKeyIdentifier(X509Certificate issuer)
throws NoSuchAlgorithmException, CertificateEncodingException
{
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
return extUtils.createAuthorityKeyIdentifier(issuer);
}
Request Certificate
The following sections put all the pieces together and provide examples for requesting a certificate using PKCS10 and Certificate Request Message Format (CRMF, described in RFC 4211).
Request a Certificate Using PKCS10
By creating certificate signing requests using Bouncy Castle, you can now get a “real” certificate from a “real” CA.
First, you need to create a CSR that contains the information required by the end entity profile. Then, wrap the CSR in JSON and send it to EJBCA over a mutually authenticated TLS connection. Use a client certificate stored in a P12-file and use tools included in Java to process it.
The following shows a code example using Google GSON and Apache’s HTTP client:
private static KeyPair generateKeyPair() throws Exception {
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
private static PKCS10CertificationRequest generateCsr(final KeyPair keyPair) throws Exception {
final X500Name subjectDN = new X500NameBuilder(BCStyle.INSTANCE)
.addRDN(BCStyle.CN, CN)
.build();
final ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
extensionsGenerator.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.dNSName, "pki-at-the-edge.com")));
return new JcaPKCS10CertificationRequestBuilder(subjectDN, keyPair.getPublic())
.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionsGenerator.generate())
.build(
new JcaContentSignerBuilder("SHA256withRSA")
.build(keyPair.getPrivate()));
}
private static void sendCsrToEjbcaAndGetCertificate(final PKCS10CertificationRequest csr) throws Exception {
final KeyStore keyStore = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
keyStore.load(new ByteArrayInputStream(Base64.decode(KEYSTORE)), "foo123".toCharArray());
final Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("certificate_request",
"-----BEGIN CERTIFICATE REQUEST-----\n" +
new String(Base64.encode(csr.getEncoded())) + "\n" +
"-----END CERTIFICATE REQUEST-----");
jsonMap.put("certificate_profile_name", "Workshop Certificate");
jsonMap.put("end_entity_profile_name", "Workshop Certificate");
jsonMap.put("certificate_authority_name", "Bouncy Castle Test CA");
// This user will be created when requesting a certificate from EJBCA
jsonMap.put("username", CN);
jsonMap.put("password", "foo123");
jsonMap.put("include_chain", false);
final SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, "foo123".toCharArray())
.build();
final HttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.build();
final HttpPost httpPost = new HttpPost(URL);
final StringEntity entity = new StringEntity(new GsonBuilder().create().toJson(jsonMap));
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
out.println("Posting the following payload to " + URL + ": " + System.lineSeparator() + new GsonBuilder().setPrettyPrinting().create().toJson(jsonMap));
final HttpResponse response = httpClient.execute(httpPost);
out.println("Received the following response from EJBCA:" + System.lineSeparator() + EntityUtils.toString(response.getEntity()));
}
Request a Certificate Using CRMF
To request a certificate using CRMF in EJBCA, you must use CMP.
The key pair generation is the same as for PKCS#10:
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
keyPairGenerator.initialize(2048);
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
Next, create a CertReqMessage that contains the CRMF. The CertReqMessage is then wrapped in a ProtectedPKIMessage which is authenticated using a HMAC.
final SecureRandom random = new SecureRandom();
final BigInteger certReqId = BigInteger.valueOf(System.currentTimeMillis());
final byte[] senderNonce = new byte[16];
random.nextBytes(senderNonce);
final CertificateRequestMessage certReqMsg = new CertificateRequestMessage(new JcaCertificateRequestMessageBuilder(certReqId)
.setPublicKey(keyPair.getPublic())
.setSubject(subjectDn)
.setProofOfPossessionRaVerified()
.build()
.getEncoded());
final X500Name sender = new X500Name("CN=BC");
final X500Name recipient = new X500Name("CN=Bouncy Castle Test CA");
final ProtectedPKIMessage pkiMessage = new ProtectedPKIMessageBuilder(new GeneralName(sender), new GeneralName(recipient))
.setMessageTime(new Date())
.setSenderNonce(senderNonce)
.setBody(new PKIBody(PKIBody.TYPE_INIT_REQ, new CertReqMessages(certReqMsg.toASN1Structure())))
.build(new PKMACBuilder(new JcePKMACValuesCalculator().setProvider(BouncyCastleProvider.PROVIDER_NAME)).build(hmacPassword.toCharArray()));
Lastly, send the ProtectedPKIMessage to EJBCA using an HTTP POST.
final HttpClient httpClient = HttpClients.createDefault();
final HttpPost httpPost = new HttpPost(URL);
final ByteArrayEntity entity = new ByteArrayEntity(pkiMessage.toASN1Structure().getEncoded());
httpPost.setEntity(entity);
httpPost.setHeader("Content-type", "application/pkixcmp");
final HttpResponse response = httpClient.execute(httpPost);
System.out.println("Received the following PKIMessage from EJBCA:");
final PKIMessage responsePkiMessage = PKIMessage.getInstance(ASN1Primitive.fromByteArray(EntityUtils.toByteArray(response.getEntity())));
System.out.println(ASN1Dump.dumpAsString(responsePkiMessage, true));
Certificate Revocation
The use of digital signatures to authenticate certificates does leave the question of what a CA does if it has to change its mind. A certificate valid for two years is not going to be of much use if something happens within six months that compromise the private key associated with it.
This is where Certificate Revocation Lists (CRLs) come in. A CRL, is a list of certificate serial numbers that a CA has withdrawn its approval of. The actual serial numbers appear in CRL entries and a CRL entry usually includes the reason for the revocation. Often a CRL will be signed by the same entity issuing the certificates, but in some cases, a CA will nominate some other signer to issue CRLs on its behalf.
Create an Empty CRL
A CRL initially does not need to have any revoked certificates listed in it. While it might seem odd to start with an empty CRL, the list will also include information about its update frequency. The update frequency dictates how often the CRL might be updated by the CA and usually it is an initial empty CRL that establishes this.
The following shows a basic method for creating an empty CRL:
public static X509CRL createEmptyCRL(
PrivateKey caKey,
String sigAlg,
X509Certificate caCert)
throws IOException, GeneralSecurityException, OperatorCreationException
{
X509v2CRLBuilder crlGen = new JcaX509v2CRLBuilder(caCert.getSubjectX500Principal(),
calculateDate(0));
crlGen.setNextUpdate(calculateDate(24 * 7));
// add extensions to CRL
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
crlGen.addExtension(Extension.authorityKeyIdentifier, false,
extUtils.createAuthorityKeyIdentifier(caCert));
ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
.setProvider("BC").build(caKey);
JcaX509CRLConverter converter = new JcaX509CRLConverter().setProvider("BC");
return converter.getCRL(crlGen.build(signer));
}
The call to crlGen.setNextUpdate()
provides information to the one receiving this CRL about when you are planning to issue an updated CRL. This example uses 7 days, but the value varies depending on what makes sense for your application.
Past the next update date, a user should be aware that the CRL may now provide an incomplete view of the CAs revocations and should acquire an updated copy before making any decisions about a certificate issued by the CA.
Add a Revocation
Now that you have a CRL with a published update policy, you need to add a revocation to it.
Adding a CRL entry is just a matter of adding it to an existing CRL and then regenerating the CRL signature. The Bouncy Castle CRL builder supports taking an existing CRL and then modifying it by adding CRL entries, as shown in the following example method:
public X509CRL addRevocationToCRL(
PrivateKey caKey,
String sigAlg,
X509CRL crl,
X509Certificate certToRevoke)
throws IOException, GeneralSecurityException, OperatorCreationException
{
X509v2CRLBuilder crlGen = new JcaX509v2CRLBuilder(crl);
crlGen.setNextUpdate(calculateDate(24 * 7));
// add revocation
ExtensionsGenerator extGen = new ExtensionsGenerator();
CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
extGen.addExtension(Extension.reasonCode, false, crlReason);
crlGen.addCRLEntry(certToRevoke.getSerialNumber(),
new Date(), extGen.generate());
ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
.setProvider("BC").build(caKey);
JcaX509CRLConverter converter = new JcaX509CRLConverter().setProvider("BC");
return converter.getCRL(crlGen.build(signer));
}
The reason is associated with the CRL entry via an extension on the CRL entry. In this example, the reason is “Privilege Withdrawn”, in other words, a change of mind. This is a useful reason for situations where a private key has not been compromised but has gone out of use. For example, due to the person associated with the private key’s certificate leaving their employment, or changing roles. In a situation like this, signatures generated that can be verified using the revoked certificate that is dated prior to the time of revocation might still be accepted. On the other hand, if the reason given is “Key Compromise” (another valid value) you should be careful about accepting anything signed by the revoked certificate’s private key at face value.
Related Content
Next, try out the exercises and challenge yourself by trying to solve some tasks intended to strengthen your knowledge by allowing you to test and solve tasks on your own.
OCSP
Sometimes sending CRLs around becomes either too slow, as in the update times need to be too small for what is practical, or too difficult, usually because the CRL has become too large. OCSP (Online Certificate Status Protocol), described in RFC 6960, is designed to deal with this and is supported in Bouncy Castle, in Java 8 and later, and is built into EJBCA. For more information, refer to EJBCA Documentation on OCSP .