acme-client

repository·master·Indexed 43 days ago

https://github.com/publishlab/node-acme-client

A simple and unopinionated Node.js client for interacting with ACME (Automatic Certificate Management Environment) APIs, such as Let's Encrypt and Google Trust Services. Version 5.4.0 supports http-01, dns-01, and tls-alpn-01 challenge types, providing utilities for account management, order finalization, and automated certificate issuance via the client.auto() method.

Tokens
11.8K
Snippets
41
Records
57
Agent score
50%

What's inside acme-client

  1. Understanding the http-01 challenge type

    master

    The http-01 challenge is the simplest ACME challenge type to implement and is recommended as a default choice. It works by proving domain ownership through a specific file served over HTTP.

    How it works

    1. The ACME authority provides a token.
    2. You must serve a file at the following URL: http://$YOUR_DOMAIN/.well-known/acme-challenge/$TOKEN.
    3. The file content must consist of the token and a thumbprint of your account key.
    4. The ACME authority verifies the challenge by sending HTTP requests to that URL.

    Note on redirects: HTTP redirects are followed during verification. While the ACME spec focuses on HTTP, Let's Encrypt allows redirecting to HTTPS.

    When to use http-01

    • Use it when: You want the simplest implementation and have access to port 80.
    • Avoid it when:
      • You need to issue wildcard certificates (which require DNS-based challenges).
      • Port 80 is unavailable on your server.
      • You have multiple web servers that cannot all respond with the correct token simultaneously.
  2. Understanding the DNS-01 challenge type

    master

    The dns-01 challenge is used to prove ownership of a domain by serving a specific payload within a TXT DNS record.

    Key characteristics:

    • Wildcard Support: It is the only challenge type capable of issuing ACME wildcard certificates.
    • Mechanism: The client generates a base64url encoded SHA256 digest (using a token from the ACME authority and your account key thumbprint). This payload must be placed in a TXT record at the DNS name _acme-challenge.$YOUR_DOMAIN.
    • Delegation: The ACME authority follows CNAME and NS records, allowing you to delegate the challenge response to a different DNS zone.
    • Requirements: Your DNS provider must offer an API that allows for the automated addition and removal of TXT records.

    Trade-offs:

    • Pros: Enables wildcard certificates; works even if ports 80 and 443 are unavailable.
    • Cons: Slower than other challenge types due to DNS propagation delays; requires DNS API automation.
  3. Pros and cons of tls-alpn-01

    master

    When deciding whether to use the tls-alpn-01 challenge, consider the following:

    Pros:

    • Useful in environments where port 80 (HTTP) is unavailable.

    Cons:

    • Complexity: More difficult to implement than http-01 because it requires a proxy and cannot be solved purely in Node.js.
    • Port Requirement: The challenge must be satisfied using port 443 (HTTPS).
    • Wildcard Limitation: Cannot be used to issue wildcard certificates.
    • Distributed Setup: If using multiple web servers, every server must be configured to respond with the correct certificate.
  4. Implementing the tls-alpn-01 challenge type

    master

    The tls-alpn-01 challenge type requires proving domain ownership by serving a specially crafted certificate over HTTPS using the acme-tls/1 ALPN protocol.

    Because this challenge requires responding with self-signed certificates containing a specific id-pe-acmeIdentifier extension, it cannot be implemented purely in Node.js. You must use a proxy (such as Nginx or HAProxy) in front of your Node.js service to inspect the SSL ALPN protocol and route traffic accordingly.

    Architecture Pattern: To avoid serving self-signed certificates to actual users, you must split HTTPS traffic into two distinct Node.js backends:

    1. ALPN Responder: A backend that only serves the challenge response certificates.
    2. HTTPS Server: A backend that serves the valid certificates retrieved from the ACME provider to end-users.

    Traffic Flow:

    • If the ALPN protocol is acme-tls/1 $\rightarrow$ Route to the Node.js ALPN responder.
    • Otherwise $\rightarrow$ Route to the standard Node.js HTTPS server.
    Nginx or HAProxy (0.0.0.0:443)
        *inspect requests SSL ALPN protocol*
            If ALPN == acme-tls/1
                -> Node.js ALPN responder (127.0.0.1:4444)
            Else
                -> Node.js HTTPS server (127.0.0.1:4443)
  5. Migrate from `acme.forge` to `acme.crypto` in v5

    master

    Starting with v5, acme-client introduced a new native crypto interface under acme.crypto that uses Node.js native cryptography APIs. This enables support for ECC/ECDSA (P-256, P384, and P521) for both account private keys and certificates.

    While the legacy .forge interface remains available in v5 and is expected to persist until v6, it is recommended to migrate to the .crypto interface to take advantage of native performance and modern algorithm support.

    Note on Node.js Compatibility: v5 drops support for Node LTS v10, v12, and v14. If you require support for Node v14, you must stay on acme-client v4.

    // The new interface is located at acme.crypto
    // It uses native Node.js APIs and @peculiar/x509 for CSR handling
  6. Use Auto Mode to automate certificate issuance

    master

    The client.auto() method simplifies the entire certificate lifecycle. It handles account creation, order placement, and challenge fulfillment. You must provide a configuration object including a CSR, email, and agreement to terms. You can also provide challengeCreateFn and challengeRemoveFn to handle the actual challenge validation (e.g., via DNS or HTTP).

    const autoOpts = {
        csr: '<PEM encoded CSR>',
        email: 'test@example.com',
        termsOfServiceAgreed: true,
        challengeCreateFn: async (authz, challenge, keyAuthorization) => {},
        challengeRemoveFn: async (authz, challenge, keyAuthorization) => {},
    };
    
    const certificate = await client.auto(autoOpts);
  7. Debug acme-client

    master

    You can debug the client in two ways:

    1. Logger function: Pass a function to acme.setLogger() to intercept and handle log messages.
    2. Environment Variable: Set the DEBUG=acme-client environment variable to use the debug package for console output.
    acme.setLogger((message) => {
        console.log(message);
    });
    DEBUG=acme-client node index.js
  8. Read information from a certificate

    master

    Use readCertificateInfo(certPem) to extract metadata from a PEM-encoded certificate or certificate chain. If a chain is provided, it reads the first certificate. It returns an object containing certificate info, including a domains object with commonName and altNames.

    const info = acme.crypto.readCertificateInfo(certificate);
    const { commonName, altNames } = info.domains;
    
    console.log(`Not after: ${info.notAfter}`);
    console.log(`Not before: ${info.notBefore}`);
    console.log(`Common name: ${commonName}`);
    console.log(`Alt names: ${altNames.join(', ')}`);
  9. Use the AcmeClient class to manage ACME accounts and orders

    master

    The AcmeClient class is the primary interface for interacting with an ACME server. It provides methods to manage account lifecycle (creation, updates, key rotation), retrieve server information (Terms of Service, Account URL), and manage certificate orders (creation, retrieval, and finalization).

    Core Methods

    Account Management

    • getTermsOfServiceUrl(): Returns a Promise<(string|null)> containing the URL to the ACME server's Terms of Service.
    • getAccountUrl(): Returns the string URL of the current ACME account.
    • createAccount([data]): Returns a Promise<object> to create a new account. Accepts optional data.
    • updateAccount([data]): Returns a Promise<object> to update an existing account. Accepts optional data.
    • updateAccountKey(newAccountKey, [data]): Returns a Promise<object> to rotate the account key. Takes the newAccountKey and optional data.

    Order Management

    • createOrder(data): Returns a Promise<object> to initiate a new certificate order. Requires data defining the order requirements.
    • getOrder(order): Returns a Promise<object> to retrieve details for a specific order.
    • finalizeOrder(order, csr): Returns a Promise<object> to finalize an order using a Certificate Signing Request (csr).
    // Example conceptual usage of AcmeClient methods
    const client = new AcmeClient(opts);
    
    // Account operations
    const tosUrl = await client.getTermsOfServiceUrl();
    const account = await client.createAccount();
    
    // Order operations
    const order = await client.createOrder(orderData);
    const finalizedOrder = await client.finalizeOrder(order, csr);