Certificates

Once you completed all the previous steps, it’s time to request the signed certificate.

Request a Certificate

To do so, prepare a PKCS#10 CSR file. A single domain may be set as Common Name. Multiple domains must be provided as Subject Alternative Name. Other properties (Organization, Organization Unit etc.) depend on the CA. Some may require these properties to be set, while others may ignore them when generating the certificate.

CSR files can be generated with command line tools like openssl. Unfortunately the standard Java does not offer classes for that, so you’d have to resort to Bouncy Castle if you want to create a CSR programmatically. In the acme4j-utils module, there is a CSRBuilder for your convenience. You can also use KeyPairUtils for generating the domain key pair.

Do not just use your account key pair as domain key pair, but always generate a separate pair of keys!

KeyPair domainKeyPair = ... // KeyPair to be used for HTTPS encryption

CSRBuilder csrb = new CSRBuilder();
csrb.addDomain("example.org");
csrb.setOrganization("The Example Organization")
csrb.sign(domainKeyPair);
byte[] csr = csrb.getEncoded();

It is a good idea to store the generated CSR somewhere, as you will need it again for renewal:

try (FileWriter fw = new FileWriter("example.csr")) {
    csrb.write(fw);
}

Now all you need to do is to pass in a binary representation of the CSR and request the certificate:

Certificate cert = registration.requestCertificate(csr);

cert.getLocation() returns an URL where the signed certificate can be downloaded from. Optionally (if delivered by the ACME server) cert.getChainLocation() returns the URL of the first part of the CA chain.

The Certificate object offers methods to download the certificate and the certificate chain.

X509Certificate cert = cert.download();
X509Certificate[] chain = cert.downloadChain();
X509Certificate[] fullChain = cert.downloadFullChain();
  • cert is the issued certificate.
  • chain is the issuer chain that corresponds to cert.
  • fullChain is the issued certificate (at index 0), followed by the issuer chain.

Congratulations! You have just created your first certificate via acme4j.

download() may throw an AcmeRetryAfterException, giving an estimated time in getRetryAfter() for when the certificate is ready for download. You should then wait until that moment has been reached, before trying again.

To recreate a Certificate object from the location, just bind it:

URL locationUrl = ... // location URL from cert.getLocation()
Certificate cert = Certificate.bind(session, locationUrl);

Saving Certificates

Most web servers, like Apache, nginx, but also other servers like postfix or dovecot, need a combined certificate file that contains the leaf certificate itself, and the certificate chain up to the root certificate. acme4j-utils offers a method that helps to write the necessary file:

try (FileWriter fw = new FileWriter("cert-chain.crt")) {
    CertificateUtils.writeX509Certificates(fw, fullChain);
}

Some older servers may need the leaf certificate and the certificate chain in different files. Use this snippet to write both files:

try (FileWriter fw = new FileWriter("cert.pem")) {
    CertificateUtils.writeX509Certificate(cert, fw);
}
try (FileWriter fw = new FileWriter("chain.pem")) {
    CertificateUtils.writeX509Certificates(fw, chain);
}

These utility methods should be sufficient for most use cases. If you need the certificate written in a different format, see the source code of CertificateUtils to find out how certificates are written using Bouncy Castle.

Multiple Domains

The example above generates a certificate per domain. However, you would usually prefer to use a single certificate for multiple domains (for example, the domain itself and the www. subdomain).

You first need to authorize each (sub)domain separately.

After all the domains are authorized, generate a single CSR with all the domains provided as Subject Alternative Name (SAN). If you use the CSRBuilder, just add all of the domains to the builder:

KeyPair domainKeyPair = ... // KeyPair to be used for HTTPS encryption

CSRBuilder csrb = new CSRBuilder();
csrb.addDomain("example.org");
csrb.addDomain("www.example.org");
csrb.addDomain("m.example.org");
// add more domains if necessary...

csrb.sign(domainKeyPair);
byte[] csr = csrb.getEncoded();

The generated certificate will be valid for all of the domains.

Note that wildcard certificates are currently not supported by the ACME protocol.

The number of domains per certificate may also be limited. See your CA’s documentation for the limits.

Renewal

Certificates are only valid for a limited time, and need to be renewed before expiry. To find out the expiry date of a X509Certificate, invoke its getNotAfter() method.

For renewal, just request a new certificate using the original CSR:

PKCS10CertificationRequest csr = CertificateUtils.readCSR(
    new FileInputStream("example.csr"));

Certificate cert = registration.requestCertificate(csr);
X509Certificate cert = cert.download();

Instead of loading the original CSR, you can also generate a new one. So renewing a certificate is basically the same as requesting a new certificate.

If registration.requestCertificate(csr) throws an AcmeUnauthorizedException, the authorizations of some or all involved domains have expired. In this case, you need to go through the authorization process again, before requesting the renewed certificate.

Revocation

To revoke a certificate, just invoke the respective method:

cert.revoke();

Optionally, you can provide a revocation reason that the ACME server may use when generating OCSP responses and CRLs.

cert.revoke(RevocationReason.KEY_COMPROMISE);

Revocation without account key pair

If you have lost your account key, you can still revoke a certificate as long as you still own the key pair that was used for signing the CSR. Certificate provides a special method for this case.

First, create a new Session object, but use the key pair that was used for siging the CSR. Now invoke the revoke() method and pass the Session, the certificate to be revoked, and (optionally) a revocation reason.

KeyPair domainKeyPair = ... // the key pair that was used for signing the CSR
URI acmeServerUri = ... // uri of the ACME server
X509Certificate cert = ... // certificate to revoke

Session session = new Session(acmeServerUri, domainKeyPair);
Certificate.revoke(session, cert, RevocationReason.KEY_COMPROMISE);

Note that there is no way to revoke a certificate if you should lose both your account’s key pair and your domain’s key pair.