A simple and unopinionated ACME client.
This module is written to handle communication with a Boulder/Let's Encrypt-style ACME API.
- RFC 8555 - Automatic Certificate Management Environment (ACME): https://tools.ietf.org/html/rfc8555
- Boulder divergences from ACME: https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md
On September 15, 2022, Let's Encrypt will stop accepting Certificate Signing
Requests signed using the obsolete SHA-1 hash. This change affects all
acme-client versions lower than 3.3.2 and 4.2.4. Please upgrade ASAP to
ensure that your certificates can still be issued following this date.
A more detailed explanation can be found at the Let's Encrypt forums.
| acme-client | Node.js | |
|---|---|---|
| v5.x | >= v16 | Upgrade guide |
| v4.x | >= v10 | Changelog |
| v3.x | >= v8 | Changelog |
| v2.x | >= v4 | Changelog |
| v1.x | >= v4 | Changelog |
$ npm install acme-clientimport acme from "acme-client";
const accountPrivateKey = "<PEM encoded private key>";
const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging,
accountKey: accountPrivateKey,
});acme.directory.buypass.staging;
acme.directory.buypass.production;
acme.directory.letsencrypt.staging;
acme.directory.letsencrypt.production;
acme.directory.zerossl.production;To enable external account binding when creating your ACME account, provide your KID and HMAC key to the client constructor.
const client = new acme.Client({
directoryUrl: "https://acme-provider.example.com/directory-url",
accountKey: accountPrivateKey,
externalAccountBinding: {
kid: "YOUR-EAB-KID",
hmacKey: "YOUR-EAB-HMAC-KEY",
},
});During the ACME account creation process, the server will check the supplied account key and either create a new account if the key is unused, or return the existing ACME account bound to that key.
In some cases, for example with some EAB providers, this account creation step
may be prohibited and might require you to manually specify the account URL
beforehand. This can be done through accountUrl in the client constructor.
const client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.staging,
accountKey: accountPrivateKey,
accountUrl: "https://acme-v02.api.letsencrypt.org/acme/acct/12345678",
});You can fetch the clients current account URL, either after creating an account
or supplying it through the constructor, using getAccountUrl():
const myAccountUrl = client.getAccountUrl();For key pairs acme-client utilizes native Node.js cryptography APIs,
supporting signing and generation of both RSA and ECDSA keys. The module
jsrsasign is used to generate and
parse Certificate Signing Requests.
These utility methods are exposed through .crypto.
- Documentation: docs/crypto.md
const privateRsaKey = await acme.crypto.createPrivateRsaKey();
const privateEcdsaKey = await acme.crypto.createPrivateEcdsaKey();
const [certificateKey, certificateCsr] = await acme.crypto.createCsr({
commonName: "*.example.com",
altNames: ["example.com"],
});For convenience an auto() method is included in the client that takes a single
config object. This method will handle the entire process of getting a
certificate for one or multiple domains.
- Documentation: docs/client.md#AcmeClient+auto
- Full example: examples/auto.js
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);When ordering a certificate using auto mode, acme-client uses a priority list
when selecting challenges to respond to. Its default value is
['http-01', 'dns-01'] which translates to "use http-01 if any challenges
exist, otherwise fall back to dns-01".
While most challenges can be validated using the method of your choosing, please
note that wildcard certificates can only be validated through dns-01. More
information regarding Let's Encrypt challenge types
can be found here.
To modify challenge priority, provide a list of challenge types in
challengePriority:
await client.auto({
...,
challengePriority: ['http-01', 'dns-01']
});When using auto mode, acme-client will first validate that challenges are
satisfied internally before completing the challenge at the ACME provider. In
some cases (firewalls, etc) this internal challenge verification might not be
possible to complete.
To completely disable acme-clients internal challenge verification, enable
skipChallengeVerification:
await client.auto({
...,
skipChallengeVerification: true
});For more fine-grained control you can interact with the ACME API using the methods documented below.
- Documentation: docs/client.md
- Full example: examples/api.js
const account = await client.createAccount({
termsOfServiceAgreed: true,
contact: ["mailto:test@example.com"],
});
const order = await client.createOrder({
identifiers: [
{ type: "dns", value: "example.com" },
{ type: "dns", value: "*.example.com" },
],
});