Order a Certificate

Once you have your account set up, you are ready to order certificates.

Use your Account object to order the certificate, by using the newOrder() method. It returns an OrderBuilder object that helps you to collect the parameters of the order. You can give one or more domain names. Optionally you can also give your desired notBefore and notAfter dates for the generated certificate, but it is at the discretion of the CA to use (or ignore) these values.

Account account = ... // your Account object

Order order = account.newOrder()
        .domains("example.org", "www.example.org", "m.example.org")
        .notAfter(Instant.now().plus(Duration.ofDays(20L)))
        .create();

The Order resource contains a collection of Authorizations that can be read from the getAuthorizations() method. You must process all of them in order to get the certificate, except those with a VALID status.

for (Authorization auth : order.getAuthorizations()) {
  if (auth.getStatus() != Status.VALID) {
    processAuth(auth);
  }
}

Process an Authorization

The Authorization instance contains further details about how you can prove ownership of your domain. An ACME server offers combinations of different authorization methods, called Challenges.

getChallenges() returns a collection of all Challenges offered by the CA for domain ownership validation. You only need to complete one of them to successfully authorize your domain.

The simplest way is to invoke findChallenge(), stating the challenge type your system is able to provide:

Http01Challenge challenge = auth.findChallenge(Http01Challenge.TYPE);

It returns a properly casted Challenge object, or null if your challenge type was not acceptable. In this example, your system is able to respond to a http-01 challenge.

The returned Challenge resource provides all the data that is necessary for a successful verification of your domain ownership. Your response depends on the challenge type (see the documentation of challenges).

After you have performed the necessary steps to set up the response to the challenge (e.g. configuring your web server or modifying your DNS records), the ACME server is told to test your response:

challenge.trigger();

Now you have to wait for the server to test your response and set the authorization status to VALID or INVALID. The easiest (but admittedly also the ugliest) way is to poll the status:

while (auth.getStatus() != Status.VALID) {
  Thread.sleep(3000L);
  auth.update();
}

This is a very simple example. You should limit the number of loop iterations, and also handle the case that the status could turn to INVALID. If you know when the CA server actually requested your response (e.g. when you notice a HTTP request on the response file), you should start polling after that event.

The CA server may start the validation immediately after trigger() is invoked, so make sure your server is ready to respond to requests before invoking trigger(). Otherwise the challenge might fail immediately.

update() may throw an AcmeRetryAfterException, giving an estimated instant in getRetryAfter() when the authorization is completed. You should then wait until that moment has been reached, before trying again. The state of the Authorization instance is still updated when this exception is thrown.

When the authorization status is VALID, you have successfully authorized your domain.

The response you have set up before is not needed any more. You can (and should) remove it now.

Finalize the Order

After successfully completing all authorizations, the order needs to be finalized by providing PKCS#10 CSR file. A single domain may be set as Common Name. Multiple domains must be provided as Subject Alternative Name. You must provide exactly the domains that you had passed to the order() method above, otherwise the finalization will fail. It depends on the CA if other CSR properties (Organization, Organization Unit etc.) are accepted. 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 a new key pair for your domain.

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");
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:

csrb.write(new FileWriter("example.csr"));

After that, finalize the order:

order.execute(csr);

Wildcard Certificates

You can also generate a wildcard certificate that is valid for all subdomains of a domain, by prefixing the domain name with *. (e.g. *.example.org). The domain itself is not covered by the wildcard certificate, and also needs to be added to the order if necessary.

You must be able to prove ownership of the domain that you want to order a wildcard certificate for. The corresponding Authorization resource only refers to that domain, and does not contain the wildcard notation.

The following example creates an Order and a CSR for example.org and *.example.org:

Order order = account.newOrder()
        .domains("example.org", "*.example.org")
        .create();

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

CSRBuilder csrb = new CSRBuilder();
csrb.addDomain("example.org");    // example.org itself, if necessary
csrb.addDomain("*.example.org");  // wildcard for all subdomains
csrb.sign(domainKeyPair);
byte[] csr = csrb.getEncoded();

order.execute(csr);

In the subsequent authorization process, you would have to prove ownership of the example.org domain.

Pre-Authorize a Domain

It is possible to pro-actively authorize a domain. This can be useful to find out what challenges are requested by the CA to authorize a domain, before actually ordering a certificate. It may also help to speed up the ordering process, as already completed authorizations do not need to be completed again when ordering the certificate.

Account account = ... // your Account object
String domain = ...   // Domain name to authorize

Authorization auth = account.preAuthorizeDomain(domain);

Deactivate an Authorization

It is possible to deactivate an Authorization, for example if you sell the associated domain.

auth.deactivate();

Use IP Identifiers

acme4j supports the ACME IP extension. It permits validation of IP addresses instead of domain names. If your CA offers ACME IP support, you can add IP Identifier objects to the order:

Order order = account.newOrder()
        .identifier(Identifier.ip(InetAddress.getByName("192.168.1.2")))
        .identifier(Identifier.dns("example.org"))
        .create();

The example also shows how to add domain names as DNS Identifier objects. Adding domain names via domain() is just a shortcut notation for it.

The CSRBuilder also accepts IP addresses for generating the CSR:

CSRBuilder csrb = new CSRBuilder();
csrb.addIP(InetAddress.getByName("192.168.1.2"));
csrb.sign(domainKeyPair);
byte[] csr = csrb.getEncoded();

Short-Term Automatic Renewal

acme4j supports the ACME STAR extension for short-term automatic renewal of certificates.

To find out if the CA supports the STAR extension, check the metadata:

if (session.getMetadata().isStarEnabled()) {
  // CA supports STAR!
}

If STAR is supported, you can enable recurrent renewals by adding recurrent() to the order parameters:

Order order = account.newOrder()
        .domain("example.org")
        .recurrent()
        .create();

You can use recurrentStart(), recurrentEnd() and recurrentCertificateValidity() to change the time span and frequency of automatic renewals. You cannot use notBefore() and notAfter() in combination with recurrent() though.

The Metadata object also holds the accepted renewal limits (see Metadata.getStarMinCertValidity() and Metadata.getStarMaxRenewal()).