acme4j

repository·master·Indexed 20 days ago

https://github.com/shred/acme4j

A mature Java client implementation of the ACME protocol (RFC 8555) used to automate certificate issuance and management. It supports http-01, dns-01, and tls-alpn-01 challenges, and is compatible with major Certificate Authorities including Let's Encrypt, Google Trust Services, Actalis, SSL.com, and ZeroSSL. The library also provides experimental support for S/MIME certificates via the acme4j-smime module and the email-reply-00 challenge.

Tokens
22.2K
Snippets
54
Records
111
Agent score
67%

What's inside acme4j

  1. Overview of acme4j

    master

    acme4j is a Java client for the Automatic Certificate Management Environment (ACME) protocol, as specified in [RFC 8555]. It allows developers to automate the verification and issuance of certificates by connecting to an ACME-compliant Certificate Authority (CA).

    Key capabilities include:

    • Support for http-01, dns-01, and tls-alpn-01 challenges.
    • Compliance with various RFCs for IP identifier validation, short-term renewal, S/MIME certificates, subdomain validation, and renewal information.
    • Compatibility with major CAs such as Let's Encrypt, Google Trust Services, Actalis, SSL.com, and ZeroSSL.
  2. Getting started with acme4j

    master

    acme4j is a Java client library designed to connect to ACME (Automated Certificate Management Environment) servers. It abstracts the complexities of the ACME specification, allowing developers to manage certificates without manual handling of protocol details.

    To obtain a signed certificate, you must follow these three essential steps:

    1. Establish a Session and Connection: Connect to an ACME server.
    2. Manage Account and Login: Create an ACME account and perform a login.
    3. Order a Certificate: Request a certificate for your specific domain.

    Additional capabilities include certificate renewal, revocation, resource persistence, and advanced configuration.

  3. Understand acme4j debug log output

    master

    Debug logs in acme4j provide visibility into the client-server communication following the RFC 8555 protocol. The logs typically show:

    • Actions: The high-level task being performed (e.g., create in AccountBuilder).
    • HTTP Requests: The method (GET, POST, HEAD), the URL, and the payload (e.g., JWS Header and Payload).
    • HTTP Responses: The returned headers (like Cache-Control, Replay-Nonce, Location, and Content-Type) and the response body (shown as Result JSON).

    Interpreting Result JSON

    When a server responds, the Result JSON contains the parsed response body. For successful requests, this includes resource links and status. For errors, it contains a JSON problem document with type and detail fields describing the error.

  4. Understand the ACME certificate ordering workflow

    master

    The core workflow for obtaining a certificate using acme4j follows these steps:

    1. Load/Create Account Key: Load an existing account KeyPair from a file or generate a new one. Warning: Back up this key; losing it means losing access to your account.
    2. Establish Session: Create a Session using the CA's URI.
    3. Manage Account: Find an existing account or register a new one using AccountBuilder. If registering, you must call .agreeToTermsOfService().
    4. Load/Create Domain Key: Load or generate a separate KeyPair used for the domain's encryption.
    5. Create Order: Use acct.newOrder().domains(domains).create() to initiate the request.
    6. Authorize Domains: Iterate through order.getAuthorizations() and perform the required challenges (e.g., HTTP or DNS).
    7. Wait for Readiness: Use order.waitUntilReady(timeout) to wait for the order to be ready for execution.
    8. Execute Order: Call order.execute(domainKeyPair) to finalize the request.
    9. Verify Completion: Use order.waitForCompletion(timeout) and check if the status is Status.VALID.
    10. Retrieve Certificate: Access the certificate via order.getCertificate() and write it to a file.
    // Simplified workflow sketch
    KeyPair userKeyPair = loadOrCreateUserKeyPair();
    Session session = new Session(CA_URI);
    Account acct = findOrRegisterAccount(session, userKeyPair);
    KeyPair domainKeyPair = loadOrCreateDomainKeyPair();
    
    Order order = acct.newOrder().domains(domains).create();
    for (Authorization auth : order.getAuthorizations()) {
        authorize(auth);
    }
    
    order.waitUntilReady(TIMEOUT);
    order.execute(domainKeyPair);
    Status status = order.waitForCompletion(TIMEOUT);
    
    if (status == Status.VALID) {
        Certificate certificate = order.getCertificate();
        certificate.writeCertificate(new FileWriter(DOMAIN_CHAIN_FILE));
    }
  5. Handle 'INVALID' or 'PROCESSING' Order statuses

    master

    After challenges pass as VALID, the Order may enter these states:

    • INVALID: The order failed, likely because required steps for the CA were not completed. Use Order.getError() to retrieve the failure reason and log it (e.g., order.getError().toString()).
    • PROCESSING: The CA is performing background checks. This can take hours or even days. There is no software-side fix for this; you must wait.
  6. Authorize domains via Challenges

    master

    An Order contains Authorization objects (retrieved via getAuthorizations()), one for each domain. You must process all authorizations in a PENDING state before finalizing the order.

    To authorize a domain, you must complete a Challenge. An Authorization offers multiple challenges via getChallenges(), but you only need to complete one to succeed.

    Use findChallenge() to select a challenge by its class type (preferred for compile-time safety) or by its name. Once your infrastructure is ready to respond to the challenge (e.g., DNS or HTTP setup), call challenge.trigger().

    Important:

    • Ensure your server is ready to respond before calling trigger().
    • Keep the challenge response available until the authorization status changes to VALID or INVALID, as the CA may perform multiple checks from different IPs.
    • Poll the status using auth.fetch() until the status is no longer PENDING.
    // 1. Find the pending authorizations
    for (Authorization auth : order.getAuthorizations()) {
      if (auth.getStatus() == Status.PENDING) {
        log.info("Authorizing " + auth.getIdentifier());
    
        // 2. Find a challenge type your system supports (e.g., HTTP-01)
        Optional<Http01Challenge> challenge = auth.findChallenge(Http01Challenge.class);
    
        if (challenge.isPresent()) {
            // 3. Trigger the challenge after setting up your server response
            challenge.get().trigger();
    
            // 4. Poll for completion
            while (!EnumSet.of(Status.VALID, Status.INVALID).contains(auth.getStatus())) {
              Thread.sleep(3000L);
              auth.fetch();
            }
        }
      }
    }
  7. How the email-reply-00 challenge works

    master

    The email-reply-00 challenge (RFC 8823) validates email ownership via a two-part token system:

    1. Token 1: The CA sends an email with a subject starting with the ACME: prefix. The subject contains the first part of the challenge token.
    2. Token 2: The CA provides an EmailReply00Challenge object which contains the second part of the token.

    The full token is the concatenation of Token 1 and Token 2. The client must generate a response email containing a text/plain part with the wrapped key authorization string, formatted as follows:

    -----BEGIN ACME RESPONSE-----
    [Wrapped Key Authorization]
    -----END ACME RESPONSE-----

    Once the response is sent back to the CA, the EmailReply00Challenge must be triggered to complete the proof of ownership.

  8. Handle acme4j exceptions

    master

    Most acme4j methods throw checked exceptions derived from AcmeException. However, there are two specific runtime exceptions: AcmeLazyLoadingException and AcmeProtocolException.

    Exception Hierarchy

    • AcmeException (Checked)
      • AcmeNetworkException (Network errors/timeouts)
      • AcmeRetryAfterException (Server process incomplete)
      • AcmeServerException (Server responded with error)
        • AcmeRateLimitedException (Rate limit exceeded)
        • AcmeUnauthorizedException (Insufficient permissions)
        • AcmeUserActionRequiredException (Human action required, e.g., TOS confirmation)
    • RuntimeException (Unchecked)
      • AcmeLazyLoadingException (Error during implicit state update)
      • AcmeProtocolException (RFC violation/unexpected response)
        • AcmeNotSupportedException (Server lacks requested feature)
  9. Handle RFC7807 error problem documents

    master

    The ACME CA returns errors as RFC7807 problem documents. The acme4j library parses these into a Problem object.

    To handle these errors:

    • For simple logging: Use the .toString() method on the Problem object to get a summary of important fields.
    • For machine-readable details: Use .asJSON() to get a full JSON representation, which is useful if the CA includes non-standard fields.
    • For human-readable details: Use the specific methods provided by the Problem class to access subproblems and details.
  10. Handle 'PENDING' and 'INVALID' Challenge statuses

    master

    When working with ACME challenges, you may encounter the following states:

    • PENDING: The challenge has been triggered and you are waiting for the CA to verify it. Do not remove challenge-related resources (like DNS records or HTML files) until the status changes.
    • INVALID: The CA could not verify the challenge. To diagnose the cause, invoke Challenge.getError() and log the output (e.g., challenge.getError().toString()). Ensure the challenge is fully ready for verification before calling Challenge.trigger().
    • VALID: The challenge was successful.
  11. Understand ACME challenges for domain ownership

    master

    In the ACME protocol, challenges are mechanisms used to prove ownership of a domain. By successfully completing a challenge, a client demonstrates to the ACME server that it has control over the domain or the account associated with it.

    Challenges are categorized into standard ACME specifications and non-standard extensions supported by acme4j.

  12. How ACME Providers work in _acme4j_

    master

    An AcmeProvider is a plugin that allows acme4j to connect to specific Certificate Authorities (CAs) using a specialized URI scheme (e.g., acme://example.com) instead of a direct HTTPS URL.

    When you initialize a Session with an acme: URI, acme4j uses Java's ServiceLoader to find registered providers. It calls accepts(URI) on each provider; if exactly one provider returns true, acme4j calls that provider's resolve(URI) method to obtain the actual directory service URL and establishes the connection.

    Note that the http and https schemes are reserved for the generic provider and cannot be used by custom providers.

    // Using a direct URL (Generic Provider)
    Session session = new Session("https://api.example.org/directory");
    
    // Using a specialized provider via URI
    Session session = new Session("acme://example.org");