ACME (RFC 8555) certificate issuance

The RFC 8555 ACME message layer (updated by RFC 8737 tls-alpn-01, RFC 8738 IP identifiers, and RFC 9773 ARI): object validators, request builders, challenge computations, and the ARI certID codec over the pki.jose JWS envelope. This is a MESSAGE LAYER, not an HTTP client: it owns the JWS construction/verification, the resource-object validation (closed status enums, conditional-required fields, immutable arrays), the three RFC 8555 sec. 7.1.6 state machines, the challenge computations (key authorization, http-01, dns-01, tls-alpn-01), the identifier validators (dns / ip / wildcard), and the ARI certID, over an injectable transport.

Every resource object is validated by a declarative spec table (the JSON analog of the ASN.1 schema engine): one definition per surface drives both validate(obj) and the builders. Unknown fields are tolerated (ignored, never reflected); unknown challenge types are surfaced raw. Where ACME output re-enters the DER world (the finalize CSR, the downloaded certificate chain, the revokeCert payload, the ARI inputs), it routes through the shipped pki.schema.csr / pki.schema.x509 parsers, so no new DER detector appears and the format-orchestrator's mutual-exclusion proof is untouched.

pki.acme.assertTransition

since 0.1.25 stable
pki.acme.assertTransition(kind, from, to) -> void

Assert that a status transition of an ACME resource (kind = "challenge"|"authorization"|"order") from from to to is one of the RFC 8555 sec. 7.1.6 legal edges. A same-status observation is allowed (a server may re-report); any other edge throws acme/bad-transition.

Example

pki.acme.assertTransition("order", "pending", "ready");   // ok

References

pki.acme.validateProblem

since 0.1.25 stable
pki.acme.validateProblem(obj) -> obj

Validate an ACME problem document (RFC 7807 + RFC 8555 sec. 6.7): a type in the urn:ietf:params:acme:error: namespace, an optional detail, and subproblems (each itself a problem document, optionally carrying an identifier). A top-level identifier is forbidden (sec. 6.7.1) and throws acme/bad-problem. Returns the object.

Example

pki.acme.validateProblem({ type: "urn:ietf:params:acme:error:malformed" });

References

pki.acme.validate

since 0.1.25 stable
pki.acme.validate(kind, obj) -> obj

Validate an ACME resource object of a known kind ("directory" | "account" | "order" | "authorization" | "challenge" | "renewalInfo") against its RFC 8555 / RFC 9773 spec: required and conditionally-required fields, closed status enums, URL / RFC 3339 / identifier shapes, and array minimums. Unknown fields are ignored (never reflected). Throws a typed acme/* fault; returns the object.

Example

var orderObj = { status: "pending", expires: "2026-02-01T00:00:00Z",
  identifiers: [{ type: "dns", value: "example.org" }],
  authorizations: ["https://ca.example/authz/1"],
  finalize: "https://ca.example/order/1/finalize" };
pki.acme.validate("order", orderObj).status;   // -> "pending"

References

pki.acme.identify

since 0.1.25 stable
pki.acme.identify(obj) -> string

Classify an ACME JSON object into exactly one kind by its discriminating member set: "jws", "problem", "directory", "order", "authorization", "challenge", "account", "renewalInfo", or "unknown". The discriminators are proven mutually exclusive; a DER structure identifies as "unknown".

Example

var orderObj = { status: "pending", expires: "2026-02-01T00:00:00Z",
  identifiers: [{ type: "dns", value: "example.org" }],
  authorizations: ["https://ca.example/authz/1"],
  finalize: "https://ca.example/order/1/finalize" };
pki.acme.identify(orderObj);   // -> "order"

References

pki.acme.keyAuthorization

since 0.1.25 stable
pki.acme.keyAuthorization(token, accountJwk) -> Promise<string>

The RFC 8555 sec. 8.1 key authorization: token || '.' || base64url(SHA-256 JWK thumbprint of the account key). The token is validated (entropy floor + alphabet) first; the thumbprint is the RFC 7638 canonical digest, so changing the account key changes the key authorization.

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var accountJwk = await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey);
  var token = "example-challenge-token-not-a-secret";   // the real one comes from the CA
  await pki.acme.keyAuthorization(token, accountJwk);   // -> "<token>.<thumbprint>"
}
example();

References

pki.acme.http01

since 0.1.25 stable
pki.acme.http01(token, accountJwk) -> Promise<{ path, body }>

The http-01 challenge computation (RFC 8555 sec. 8.3): the resource path /.well-known/acme-challenge/<token> and the body (the ASCII key authorization, no trailing newline). Validation reaches TCP port 80 over HTTP.

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var accountJwk = await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey);
  var token = "example-challenge-token-not-a-secret";
  var c = await pki.acme.http01(token, accountJwk);
  c.path;   // -> "/.well-known/acme-challenge/<token>"
}
example();

References

pki.acme.dns01

since 0.1.25 stable
pki.acme.dns01(token, accountJwk, domain) -> Promise<{ name, value }>

The dns-01 challenge computation (RFC 8555 sec. 8.4): the TXT record name _acme-challenge.<domain> (exactly one leading *. is stripped for a wildcard order) and the value base64url(SHA-256(keyAuthorization)).

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var accountJwk = await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey);
  var token = "example-challenge-token-not-a-secret";
  var r = await pki.acme.dns01(token, accountJwk, "example.org");
  r.name;   // -> "_acme-challenge.example.org"
}
example();

References

pki.acme.tlsAlpn01Extension

since 0.1.25 stable
pki.acme.tlsAlpn01Extension(token, accountJwk) -> Promise<Buffer>

Build the DER of the critical id-pe-acmeIdentifier extension (RFC 8737 sec. 3): SEQUENCE { extnID 1.3.6.1.5.5.7.1.31, critical TRUE, extnValue OCTET STRING wrapping Authorization ::= OCTET STRING (SIZE 32) of the SHA-256(keyAuthorization) }. Placed in the validation certificate.

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var accountJwk = await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey);
  var token = "example-challenge-token-not-a-secret";
  var extDer = await pki.acme.tlsAlpn01Extension(token, accountJwk);
}
example();

References

pki.acme.verifyTlsAlpn01

since 0.1.25 stable
pki.acme.verifyTlsAlpn01(certDer, token, accountJwk, identifier) -> Promise<void>

Verify a tls-alpn-01 validation certificate (RFC 8737 sec. 3): a critical id-pe-acmeIdentifier extension whose 32-octet Authorization equals SHA-256(keyAuthorization), plus a SubjectAltName with exactly one entry, either a dNSName equal to the dns identifier (case-insensitive) or a single iPAddress for an ip identifier (RFC 8738 sec. 6). Any deviation throws acme/bad-tlsalpn.

Example

async function example() {
  var b = pki.asn1.build;
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var accountJwk = await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey);
  var token = "example-challenge-token-not-a-secret";
  // the validation certificate carries exactly two extensions: the critical
  // acmeIdentifier, and a single-entry SAN naming the identifier being validated
  var acmeExt = await pki.acme.tlsAlpn01Extension(token, accountJwk);
  var sanExt = b.sequence([b.oid("2.5.29.17"),
    b.octetString(b.sequence([b.contextPrimitive(2, Buffer.from("example.org", "ascii"))]))]);
  var kp = await pki.key.generate("Ed25519");
  var certDer = await pki.x509.sign({ subject: "example.org", subjectPublicKey: await pki.key.export(kp.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: [acmeExt, sanExt] }, { key: await pki.key.export(kp.privateKey) });
  await pki.acme.verifyTlsAlpn01(certDer, token, accountJwk, { type: "dns", value: "example.org" });
}
example();

References

pki.acme.postAsGet

since 0.1.25 stable
pki.acme.postAsGet(opts) -> Promise<object>

Build a POST-as-GET request (RFC 8555 sec. 6.3): a JWS whose payload is the EMPTY octet string (payload: ""), distinct from a POST of an empty object ({}). opts carries { key, alg, nonce, url, kid } (an authenticated read is always kid-signed). Returns the flattened JWS.

Example

async function example() {
  await pki.acme.postAsGet({ key, alg: "ES256", nonce, url: orderUrl, kid });
}
example();

References

pki.acme.newAccount

since 0.1.25 stable
pki.acme.newAccount(opts) -> Promise<object>

Build a newAccount request (RFC 8555 sec. 7.3): a jwk-signed JWS (a new account has no kid yet) whose payload MAY carry contact (mailto validated fail-closed), termsOfServiceAgreed, onlyReturnExisting, and an externalAccountBinding (an EAB inner JWS from externalAccountBinding). opts = { key, alg, nonce, url, jwk, contact?, termsOfServiceAgreed?, onlyReturnExisting?, externalAccountBinding? }.

Example

async function example() {
  await pki.acme.newAccount({ key, alg: "ES256", nonce, url, jwk, termsOfServiceAgreed: true });
}
example();

References

pki.acme.externalAccountBinding

since 0.1.25 stable
pki.acme.externalAccountBinding(opts) -> Promise<object>

Build the External Account Binding inner JWS (RFC 8555 sec. 7.3.4): a MAC-only (HS256/HS384/HS512) JWS over the account public JWK, keyed by the CA-issued kid + symmetric macKey (a raw Buffer or an HMAC CryptoKey), url equal to the newAccount URL, NO nonce. opts = { macKey, kid, url, accountJwk, alg? } (alg default HS256). The result is embedded as newAccount's externalAccountBinding.

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var accountJwk = await pki.webcrypto.subtle.exportKey("jwk", ec.publicKey);
  var macKey = Buffer.alloc(32, 7);          // the HMAC key the CA issued out of band
  var url = "https://ca.example/acme/new-acct";
  var eab = await pki.acme.externalAccountBinding({ macKey, kid: "abc123", url, accountJwk });
}
example();

References

pki.acme.newOrder

since 0.1.25 stable
pki.acme.newOrder(opts) -> Promise<object>

Build a newOrder request (RFC 8555 sec. 7.4): a kid-signed JWS whose payload carries a non-empty validated identifiers array (each dns/ip, one leading *. wildcard permitted for dns), optional notBefore/notAfter, and an optional RFC 9773 replaces (the ARI certID of the certificate being renewed). opts = { key, alg, nonce, url, kid, identifiers, notBefore?, notAfter?, replaces? }.

Example

async function example() {
  await pki.acme.newOrder({ key, alg: "ES256", nonce, url, kid, identifiers: [{ type: "dns", value: "example.org" }] });
}
example();

References

pki.acme.newAuthz

since 0.3.29 stable
pki.acme.newAuthz(opts) -> flattened JWS

Build a kid-signed pre-authorization request (RFC 8555 sec. 7.4.1): a Flattened JWS over exactly { identifier: { type, value } }, a single identifier object and not an array. The identifier is validated as an authorization identifier, which rejects a wildcard *. value (pre-authorization cannot authorize a wildcard name); a bad type / value is acme/bad-identifier. opts = { key, alg, nonce, url, kid, identifier }. The client.newAuthz(identifier) verb composes this, POSTs it to the directory newAuthz resource, and returns the validated authorization bound to the requested identifier.

Example

async function example() {
  await pki.acme.newAuthz({ key, alg: "ES256", nonce, url, kid, identifier: { type: "dns", value: "example.org" } });
}
example();

References

pki.acme.finalize

since 0.1.25 stable
pki.acme.finalize(opts) -> Promise<object>

Build a finalize request (RFC 8555 sec. 7.4): a kid-signed JWS whose payload csr is the base64url of the DER PKCS#10 (never PEM). The CSR is parsed with pki.schema.csr.parse; its requested identifier set (SAN + CN) MUST equal the order identifiers (acme/csr-identifier-mismatch), and its public key MUST NOT be the account key (acme/key-reuse, sec. 11.1). opts = { key, alg, nonce, url, kid, csr (DER Buffer), identifiers?, accountJwk? }.

Example

async function example() {
  await pki.acme.finalize({ key, alg: "ES256", nonce, url, kid, csr: csrDer, identifiers, accountJwk });
}
example();

References

pki.acme.challengeResponse

since 0.1.25 stable
pki.acme.challengeResponse(opts) -> Promise<object>

Build a challenge-response POST (RFC 8555 sec. 7.5.1): a kid-signed JWS whose payload is the type-defined response object: {} for the three registered challenge types (http-01 / dns-01 / tls-alpn-01), which is DISTINCT from a POST-as-GET empty payload. opts = { key, alg, nonce, url, kid, payload? } (payload default {}; pass a custom object for a future challenge type).

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var key = ec.privateKey;
  var nonce = "oFvnlFP1wIhRlYS2jTaXbA";                 // from the CA's Replay-Nonce header
  var kid = "https://ca.example/acct/1", challUrl = "https://ca.example/chall/1";
  await pki.acme.challengeResponse({ key, alg: "ES256", nonce, url: challUrl, kid });
}
example();

References

pki.acme.deactivate

since 0.1.25 stable
pki.acme.deactivate(opts) -> Promise<object>

Build a deactivation POST (RFC 8555 sec. 7.3.6 account / sec. 7.5.2 authorization): a kid-signed JWS with the payload {"status":"deactivated"}, the only client-settable status. opts = { key, alg, nonce, url, kid }.

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var key = ec.privateKey;
  var nonce = "oFvnlFP1wIhRlYS2jTaXbA";
  var kid = "https://ca.example/acct/1", authzUrl = "https://ca.example/authz/1";
  await pki.acme.deactivate({ key, alg: "ES256", nonce, url: authzUrl, kid });
}
example();

References

pki.acme.updateAccount

since 0.6.4 stable
pki.acme.updateAccount(opts) -> Promise<object>

Build an account-update POST (RFC 8555 sec. 7.3.2): a kid-signed JWS whose payload carries the account fields a client may set. contact is that field (RFC 6068 mailto hygiene applies; an empty array clears all contacts). opts = { key, alg, nonce, url, kid, contact }. The server ignores updates to status, termsOfServiceAgreed, orders, and unrecognized fields (sec. 7.3.2), so the pki.acme.client wrapper refuses those at the door rather than emit a silently-discarded payload.

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var key = ec.privateKey;
  var nonce = "oFvnlFP1wIhRlYS2jTaXbA";
  var kid = "https://ca.example/acct/1";
  await pki.acme.updateAccount({ key, alg: "ES256", nonce, url: kid, kid, contact: ["mailto:admin@example.org"] });
}
example();

References

pki.acme.revokeCert

since 0.1.25 stable
pki.acme.revokeCert(opts) -> Promise<object>

Build a revokeCert request (RFC 8555 sec. 7.6): a JWS whose payload certificate is the base64url of the DER certificate and optional reason is an assigned RFC 5280 CRLReason (0-6, 8-10; 7 is unassigned). Signed EITHER by the account key (kid mode) OR by the certificate key (jwk mode). Pass exactly one. opts = { key, alg, nonce, url, certificate (DER Buffer), reason?, kid? | jwk? }.

Example

async function example() {
  var ec = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var key = ec.privateKey;
  var nonce = "oFvnlFP1wIhRlYS2jTaXbA";
  var kid = "https://ca.example/acct/1", url = "https://ca.example/acme/revoke-cert";
  var kp = await pki.key.generate("Ed25519");
  var certDer = await pki.x509.sign({ subject: "example.org", subjectPublicKey: await pki.key.export(kp.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") },
    { key: await pki.key.export(kp.privateKey) });
  await pki.acme.revokeCert({ key, alg: "ES256", nonce, url, kid, certificate: certDer, reason: 1 });
}
example();

References

pki.acme.keyChange

since 0.1.25 stable
pki.acme.keyChange(opts) -> Promise<object>

Build a key-change request (RFC 8555 sec. 7.3.5): a nested JWS. The INNER JWS is signed by the NEW account key (embedded jwk, no nonce, url == the keyChange URL) over { account, oldKey }; the OUTER JWS is the account (kid, oldKey) signing that inner object. opts = { key (old private), alg (old), kid (account URL), account (account URL), oldKey (old public JWK), newKey (new private), newJwk (new public JWK), newAlg, nonce, url }.

Example

async function example() {
  var subtle = pki.webcrypto.subtle;
  var oldPair = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var newPair = await pki.key.generate({ name: "ECDSA", namedCurve: "P-256" });
  var oldKey = oldPair.privateKey, newKey = newPair.privateKey;
  var oldJwk = await subtle.exportKey("jwk", oldPair.publicKey);
  var newJwk = await subtle.exportKey("jwk", newPair.publicKey);
  var nonce = "oFvnlFP1wIhRlYS2jTaXbA";
  var kid = "https://ca.example/acct/1", url = "https://ca.example/acme/key-change";
  await pki.acme.keyChange({ key: oldKey, alg: "ES256", kid, account: kid, oldKey: oldJwk, newKey, newJwk, newAlg: "ES256", nonce, url });
}
example();

References

pki.acme.ariCertId

since 0.1.25 stable
pki.acme.ariCertId(certDer) -> string

The RFC 9773 sec. 4.1 ARI certificate identifier of a DER certificate: base64url(AKI keyIdentifier) || '.' || base64url(serial content octets). The serial is the raw DER INTEGER content, and its leading 00 sign-padding byte is preserved (dropping it is the documented mass-404 client bug). Throws acme/bad-certid if the certificate lacks an AKI keyIdentifier.

Example

async function example() {
  // the certificate must carry an authorityKeyIdentifier -- ARI keys off it
  var ca = await pki.key.generate("Ed25519");
  var caKey = await pki.key.export(ca.privateKey);
  var caDer = await pki.x509.sign({ subject: "Example CA", subjectPublicKey: await pki.key.export(ca.publicKey),
    notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectKeyIdentifier: true } }, { key: caKey });
  var kp = await pki.key.generate("Ed25519");
  var certDer = await pki.x509.sign({ subject: "example.org", subjectPublicKey: await pki.key.export(kp.publicKey),
    serialNumber: 0x87654321n, notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
    extensions: { authorityKeyIdentifier: true } }, { cert: caDer, key: caKey });
  pki.acme.ariCertId(certDer);   // -> "<b64u-aki>.<b64u-serial>"
}
example();

References

pki.acme.parseAriCertId

since 0.1.25 stable
pki.acme.parseAriCertId(certId) -> { keyIdentifier, serial }

Parse an ARI certID string (RFC 9773 sec. 4.1) into { keyIdentifier, serial } Buffers. The two dot-joined halves are each strict base64url (padding / non-alphabet rejected); anything but exactly two parts throws acme/bad-certid.

Example

// the two halves are base64url(authorityKeyIdentifier) and base64url(serialNumber)
pki.acme.parseAriCertId("aYhfK4oaay8.AIdlQyE").serial;   // -> Buffer 00 87 65 43 21

References

pki.acme.validateRenewalInfo

since 0.1.25 stable
pki.acme.validateRenewalInfo(obj) -> obj

Validate an ARI RenewalInfo object (RFC 9773 sec. 4.2): a suggestedWindow with RFC 3339 start and end, end strictly after start (an inverted or zero-width window throws acme/bad-renewal-window: the client treats it as no response, defusing a renewal stampede), and an optional explanationURL. Returns the object.

Example

pki.acme.validateRenewalInfo({ suggestedWindow: { start: "2026-01-01T00:00:00Z", end: "2026-01-08T00:00:00Z" } });

References

pki.acme.client

since 0.3.18 stable
pki.acme.client(directoryUrl, opts) -> client

A stateful RFC 8555 ACME client that drives the live directory flow over the shared pki.transport (inject opts.transport, else a fail-closed pki.transport.https). It composes the shipped message layer (the JWS builders + object validators + state machines) and owns only session state: the fetched directory, the single-use nonce pool (a fresh anti-replay nonce per JWS, badNonce bounded- retried with the error's Replay-Nonce), and the account URL captured as the kid. opts.accountKey (a private CryptoKey) + opts.accountJwk (its public JWK) + opts.alg sign every request. Every request is https-only (acme/insecure-url); reads are POST-as-GET; a problem+json response is a typed acme/server-problem; a poll sleeps on a bounded Retry-After via an injectable sleeper and is capped by a poll count and a total-wait budget. Returns a client object: directory, newAccount, newOrder, newAuthz, getOrder / getAuthorization / getChallenge, respondToChallenge, finalize, pollOrder / pollAuthorization, downloadCertificate, revokeCert, deactivateAccount / deactivateAuthorization, updateAccount, listOrders, keyChange, renewalInfo, renewalWindow, scheduleRenewal.

Options

- `accountKey` / `accountJwk` / `alg` -- REQUIRED: the account private key, its public JWK, and the JWS alg.
- `transport` -- injectable `transport(request) -> Promise<{status, headers, body}>`; default pki.transport.https. It must return a promise of the response; anything else is `acme/bad-input`.
- `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity } for the default transport.
- `timeout` / `maxResponseBytes` / `maxRedirects` -- transport budgets; `maxNonceRetries` -- badNonce retry cap (default 1).
- `proxy` -- reach the ACME server through a forward HTTP proxy (`{ url, auth?, tls? }`; see pki.transport).
- `maxPolls` / `maxTotalWait` / `sleep` -- poll-loop budgets + an injectable sleeper; `clock` -- an injectable receipt clock (default Date.now) for a Retry-After HTTP-date.
- `resignKeys` -- optional ordered `[{ alg, key }]` enabling the RFC 8555 sec. 6.2 badSignatureAlgorithm re-sign. When a CA rejects the account `alg` and advertises the algs it supports, the first entry whose `alg` the CA advertised re-signs the request once and retries. Each `key` is a CryptoKey for the SAME account key material under that `alg`: an RSA account key signs RS256 and PS256 from one key, and its registered public JWK verifies either. The caller's order selects and the CA's list only filters, so a spoofed badSignatureAlgorithm cannot force a weaker alg (sec. 10). Absent, or with no advertised match, the badSignatureAlgorithm surfaces unchanged.
- `newAuthz(identifier)` -- pre-authorize a single identifier (RFC 8555 sec. 7.4.1) -> { authorization, url }.
- `downloadCertificate(url, { expectedSpki, identifiers, requireBinding, selectChain, maxAlternates })` -- bind the issued certificate to this order, then pick among RFC 8555 sec. 7.4.2 alternate chains. `expectedSpki` (the DER SubjectPublicKeyInfo this order's CSR asked to have certified) and `identifiers` (the order's own identifier array) are what the returned end-entity certificate is checked against: a different key is `acme/certificate-key-mismatch`, a different identifier set `acme/certificate-identifier-mismatch`. Only `dns` and `ip` identifiers map to a name a certificate carries, so an order identifier of another registered type, a certificate `subjectAltName` that is neither a dNSName nor an iPAddress, and a subject common name that is neither a dns name nor a canonical IP address, are each refused as `acme/unsupported-identifier-type` rather than dropped from the comparison. The certificate's alternative names are its identity; its common name is read only where it asserts none. At least one is required (`acme/binding-required`) unless `requireBinding: false`, which waives the requirement to supply material and never the check on material that is supplied. The result reports `boundToKey` / `boundToIdentifiers`. `selectChain({certificate, chain, certificates})` returns the first truthy candidate (primary first, then bounded `Link rel="alternate"` chains, confined to the download's own origin); the result adds `alternates` (the resolved alternate URLs).
- `renewalWindow(certDer, { random, clock, replaced, previous })` -- the RFC 9773 ARI renewal decision: composes `renewalInfo`, selects a uniform-random instant in the suggested window -> { suggestedWindow, selectedTime, renewNow, retryAfterSeconds, explanationURL }. Pass a prior result back as `previous` to REUSE its selectedTime while the CA's window is unchanged (RFC 9773 sec. 4.2), so repeated refreshes keep one stable renewal instant.
- `scheduleRenewal(certDer, { random, shouldStop, renew, maxChecks, maxWait, longTermRetrySeconds, temporaryBaseSeconds })` -- the RFC 9773 sec. 4.1 auto-sleeping renewal loop over `renewalWindow`. It fetches the ARI decision, sleeps via the client `sleep` until the sooner of the selected instant and the Retry-After, and refetches until the window says renew now, resolving `{ reason: "renew-now", decision }`. It stops early with `{ reason: "expired" }` once the certificate passes its notAfter (a client MUST NOT check RenewalInfo after expiry, sec. 4.3), `{ reason: "stopped" }` when `shouldStop()` returns true (the caller's certificate-replaced signal, sec. 4.3), and `{ reason: "budget" }` when the optional `maxChecks` or `maxWait` (seconds) bound is reached. A transient server or transport error retries on the sec. 4.3.3 schedule: a 5xx backs off exponentially from `temporaryBaseSeconds`, and every other transient error waits `longTermRetrySeconds` (default six hours). A caller or certificate error, including a certificate that carries no authorityKeyIdentifier and so cannot produce an ARI certID, rejects. When `renew(decision)` is supplied it is awaited at renew now instead of resolving; returning a new certificate DER reschedules on it, returning nothing resolves `{ reason: "renewed", decision }`.

Example

async function example() {
  var acme = pki.acme.client("https://acme.example/directory", { accountKey, accountJwk, alg: "ES256", transport });
  var acct = await acme.newAccount({ termsOfServiceAgreed: true });
  var ord = await acme.newOrder({ identifiers: [{ type: "dns", value: "example.org" }] });
}
example();

References