Free SSL certificates for Node.js and Browsers. Issued via Let's Encrypt
ACME.js is a _low-level_ client for Let's Encrypt.
Looking for an easy, _high-level_ client? Check out Greenlock.js.
See https://greenlock.domains
| 15k gzipped | 55k minified | 88k (2,500 loc) source with comments |
Supports the latest (Nov 2019) release of Let's Encrypt in a small, lightweight, Vanilla JS package.
- [x] Let's Encrypt v2
- [x] ACME RFC 8555
- [x] November 2019
- [x] POST-as-GET
- [ ] StartTLS Everywhere™ (in-progress)
- [x] IDN (i.e. .中国)
- [x] ECDSA and RSA keypairs
- [x] JWK
- [x] PEM
- [x] DER
- [x] Native Crypto in Node.js
- [x] WebCrypto in Browsers
- [x] Domain Validation Plugins
- [x] tls-alpn-01
- [x] http-01
- [x] dns-01
- [x] Wildcards
- [x] Localhost
- [x] Private Networks
- [x] Create your own
- [x] Vanilla JS\*
- [x] No Transpiling Necessary!
- [x] Node.js
- [x] Browsers
- [x] WebPack
- [x] Zero External Dependencies
- [x] Commercial Support
- [x] Safe, Efficient, Maintained
\* Although we use async/await in the examples,
the codebase is written entirely in Common JS.
- Home Servers
- IoT
- Enterprise On-Prem
- Web Hosting
- Cloud Services
- Localhost Development
The public API encapsulates the three high-level steps of the ACME protocol:
1. API Discovery
2. Account Creation
- Subscriber Agreement
3. Certificate Issuance
- Certificate Request
- Authorization Challenges
- Challenge Presentation
- Certificate Redemption
The core API can be show in just four functions:
``js`
ACME.create({ maintainerEmail, packageAgent, notify });
acme.init(directoryUrl);
acme.accounts.create({ subscriberEmail, agreeToTerms, accountKey });
acme.certificates.create({
customerEmail, // do not use
account,
accountKey,
csr,
domains,
challenges
});
Helper Functions
`js`
ACME.computeChallenge({
accountKey,
hostname: 'example.com',
challenge: { type: 'dns-01', token: 'xxxx' }
});
| Parameter | Description |
| ------------------ | ----------------------------------------------------------------------------------------------------------- |
| account | an object containing the Let's Encrypt Account ID as "kid" (misnomer, not actually a key id/thumbprint) |
| accountKey | an RSA or EC public/private keypair in JWK format |
| agreeToTerms | set to true to agree to the Let's Encrypt Subscriber Agreement |get
| challenges | the 'http-01', 'alpn-01', and/or 'dns-01' challenge plugins (, set, and remove callbacks) to use |@root/csr
| csr | a Certificate Signing Request (CSR), which may be generated with , openssl, or another |https://acme-staging-v02.api.letsencrypt.org/directory
| customerEmail | Don't use this. Given as an example to differentiate between Maintainer, Subscriber, and End-User |
| directoryUrl | should be the Let's Encrypt Directory URL |function (ev, args) { ... }
| domains | the list of altnames (subject first) that are listed in the CSR and will be listed on the certificate |
| maintainerEmail | should be a contact for the author of the code to receive critical bug and security notices |
| notify | all callback for logging events and errors in the form |
| packageAgent | should be an RFC72321-style user-agent string to append to the ACME client (ex: mypackage/v1.1.1) |
| skipChallengeTests | do not do a self-check that the ACME-issued challenges will pass (not recommended) |
| skipDryRun: false | do not do a self-check with self-issued challenges (not recommended) |
| subscriberEmail | should be a contact for the service provider to receive renewal failure notices and manage the ACME account |
Maintainer vs Subscriber vs Customer
- maintainerEmail should be the email address of the author of the code.subscriberEmail
This person will receive critical security and API change notifications.
- should be the email of the admin of the hosting service.customerEmail
This person agrees to the Let's Encrypt Terms of Service and will be notified
when a certificate fails to renew.
- should be the email of individual who owns the domain.
This is optional (not currently implemented).
Generally speaking YOU are the _maintainer_ and you or your employer is the _subscriber_.
If you (or your employer) is running any type of service
you SHOULD NOT pass the _customer_ email as the subscriber email.
If you are not running a service (you may be building a CLI, for example),
then you should prompt the user for their email address, and they are the subscriber.
These notify events are intended for _logging_ and debugging, NOT as a data API.
| Event Name | Example Message |
| -------------------- | --------------------------------------------------------------------------------- |
| certificate_order | { subject: 'example.com', altnames: ['...'], account: { key: { kid: '...' } } } |challenge_select
| | { altname: '*.example.com', type: 'dns-01' } |challenge_status
| | { altname: '*.example.com', type: 'dns-01', status: 'pending' } |challenge_remove
| | { altname: '*.example.com', type: 'dns-01' } |certificate_status
| | { subject: 'example.com', status: 'valid' } |warning
| | { message: 'what went wrong', description: 'what action to take about it' } |error
| | { message: 'a background process failed, and it may have side-effects' } |
Note: DO NOT rely on undocumented properties. They are experimental and will break.
If you have a use case for a particular property open an issue - we can lock it down and document it.
A basic example includes the following:
1. Initialization
- maintainer contact
- package user-agent
- log events
2. Discover API
- retrieves Terms of Service and API endpoints
3. Get Subscriber Account
- create an ECDSA (or RSA) Account key in JWK format
- agree to terms
- register account by the key
4. Prepare a Certificate Signing Request
- create a RSA (or ECDSA) Server key in PEM format
- select domains
- choose challenges
- sign CSR
- order certificate
To make it easy to generate, encode, and decode keys and certificates,
ACME.js uses Keypairs.js
and CSR.js
`js`
npm install --save acme
`js`
var ACME = require('acme');
`html`
(necessary in case the webserver headers don't specify plain/text; charset="UTF-8")
`js`
var ACME = require('acme');
`html`
(necessary in case the webserver headers don't specify plain/text; charset="UTF-8")
`html`
acme.min.js
`html`
Use
`js`
var ACME = window['@root/acme'];
The challenge callbacks are documented in the test suite,
essentially:
`js`
function create(options) {
var plugin = {
init: async function(deps) {
// for http requests
plugin.request = deps.request;
},
zones: async function(args) {
// list zones relevant to the altnames
},
set: async function(args) {
// set TXT record
},
get: async function(args) {
// get TXT records
},
remove: async function(args) {
// remove TXT record
},
// how long to wait after all TXT records are set
// before presenting them for validation
propagationDelay: 5000
};
return plugin;
}
The http-01 plugin is similar, but without zones or propagationDelay.
Many challenge plugins are already available for popular platforms.
Search acme-http-01- or acme-dns-01- on npm to find more.
| Type | Service | Plugin |
| ----------- | ----------------------------------------------------------------------------------- | ------------------------ |
| dns-01 | CloudFlare | acme-dns-01-cloudflare |
| dns-01 | Digital Ocean | acme-dns-01-digitalocean |
| dns-01 | DNSimple | acme-dns-01-dnsimple |
| dns-01 | DuckDNS | acme-dns-01-duckdns |
| http-01 | File System / Web Root | acme-http-01-webroot |
| dns-01 | GoDaddy | acme-dns-01-godaddy |
| dns-01 | Gandi | acme-dns-01-gandi |
| dns-01 | NameCheap | acme-dns-01-namecheap |
| dns-01 | Name.com | acme-dns-01-namedotcom |
| dns-01 | Route53 (AWS) | acme-dns-01-route53 |
| http-01 | S3 (AWS, Digital Ocean, Scaleway) | acme-http-01-s3 |
| dns-01 | Vultr | acme-dns-01-vultr |
| dns-01 | Build your own | acme-dns-01-test |
| http-01 | Build your own | acme-http-01-test |
| tls-alpn-01 | Contact us | - |
`bash`
npm test
Although you can run the tests from a public facing server, its easiest to do so using a dns-01 challenge.
You will need to use one of the acme-dns-01-* plugins
to run the test locally.
`bash`
ENV=DEV
MAINTAINER_EMAIL=letsencrypt+staging@example.com
SUBSCRIBER_EMAIL=letsencrypt+staging@example.com
BASE_DOMAIN=test.example.com
CHALLENGE_TYPE=dns-01
CHALLENGE_PLUGIN=acme-dns-01-digitalocean
CHALLENGE_OPTIONS='{"token":"xxxxxxxxxxxx"}'
`bashGet the repo and change directories into it
git clone https://git.rootprojects.org/root/acme.js
pushd acme.js/
Create a
.env configYou'll need a
.env in the project root that looks something like the one in examples/example.env:`bash
Copy the sample .env file
rsync -av examples/example.env .envEdit the config file to use a domain in your account, and your API token
#vim .env
code .envRun the tests
node tests/index.js
``Did this project save you some time? Maybe make your day? Even save the day?
Please say "thanks" via Paypal or Patreon:
- Paypal: \$5 | \$10 | Any amount:
- Patreon:
Where does your contribution go?
Root is a collection of experts
who trust each other and enjoy working together on deep-tech,
Indie Web projects.
Our goal is to operate as a sustainable community.
Your contributions - both in code and _especially_ monetarily -
help to not just this project, but also our broader work
of projects that fuel the Indie Web.
Also, we chat on Keybase
in #rootprojects
Do you need...
- more features?
- bugfixes, on _your_ timeline?
- custom code, built by experts?
- commercial support and licensing?
You're welcome to contact us in regards to IoT, On-Prem,
Enterprise, and Internal installations, integrations, and deployments.
We have both commercial support and commercial licensing available.
We also offer consulting for all-things-ACME and Let's Encrypt.
ACME.jsk™ is a trademark of AJ ONeal
The rule of thumb is "attribute, but don't confuse". For example:
> Built with ACME.js (a Root project).
Please contact us if have any questions in regards to our trademark,
attribution, and/or visible source policies. We want to build great software and a great community.
ACME.js |
MPL-2.0 |
Terms of Use |
Privacy Policy