@sam-lord/acme

    3.4.1 • Public • Published

    Let's Encrypt™ + JavaScript = ACME.js

    | Built by Root for Hub

    Automated Certificate Management Environment

    ACME (RFC 8555) is the protocol that powers Let's Encrypt.

    ACME.js is a low-level client that speaks RFC 8555 to get Free SSL certificates through Let's Encrypt.

    Looking for an easy, high-level client? Check out Greenlock.js.

    Quick Start

    var acme = ACME.create({ maintainerEmail, packageAgent, notify });
    await acme.init(directoryUrl);
    
    // Create Let's Encrypt Account
    var accountOptions = { subscriberEmail, agreeToTerms, accountKey };
    var account = await acme.accounts.create(accountOptions);
    
    // Validate Domains
    var certificateOptions = { account, accountKey, csr, domains, challenges };
    var pems = await acme.certificates.create(certificateOptions);
    
    // Get SSL Certificate
    var fullchain = pems.cert + '\n' + pems.chain + '\n';
    await fs.promises.writeFile('fullchain.pem', fullchain, 'ascii');

    Online Demo

    See https://greenlock.domains

    Features

    | 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.

    Use Cases

    • Home Servers
    • IoT
    • Enterprise On-Prem
    • Web Hosting
    • Cloud Services
    • Localhost Development

    API

    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

    API Overview

    The core API can be show in just four functions:

    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

    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
    challenges the 'http-01', 'alpn-01', and/or 'dns-01' challenge plugins (get, set, and remove callbacks) to use
    csr a Certificate Signing Request (CSR), which may be generated with @root/csr, openssl, or another
    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
    https://acme-staging-v02.api.letsencrypt.org/directory
    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 function (ev, args) { ... }
    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. This person will receive critical security and API change notifications.
    • subscriberEmail should be the email of the admin of the hosting service. This person agrees to the Let's Encrypt Terms of Service and will be notified when a certificate fails to renew.
    • customerEmail 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.

    Events

    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.

    Example (Full Walkthrough)

    See examples/README.md

    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

    examples/README.md covers all of these steps, with comments.

    Install

    To make it easy to generate, encode, and decode keys and certificates, ACME.js uses Keypairs.js and CSR.js

    Node.js
    npm install --save @root/acme
    var ACME = require('@root/acme');
    WebPack
    <meta charset="UTF-8" />

    (necessary in case the webserver headers don't specify plain/text; charset="UTF-8")

    var ACME = require('@root/acme');
    Vanilla JS
    <meta charset="UTF-8" />

    (necessary in case the webserver headers don't specify plain/text; charset="UTF-8")

    <script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.all.js"></script>

    acme.min.js

    <script src="https://unpkg.com/@root/acme@3.0.0/dist/acme.all.min.js"></script>

    Use

    var ACME = window['@root/acme'];

    Challenge Callbacks

    The challenge callbacks are documented in the test suite, essentially:

    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 -

    Running the Tests

    npm test

    Usa a dns-01 challenge

    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.

    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"}'

    For Example

    # Get the repo and change directories into it
    git clone https://git.rootprojects.org/root/acme.js
    pushd acme.js/
    
    # Install the challenge plugin you'll use for the tests
    npm install --save-dev acme-dns-01-digitalocean

    Create a .env config

    You'll need a .env in the project root that looks something like the one in examples/example.env:

    # Copy the sample .env file
    rsync -av examples/example.env .env
    
    # Edit the config file to use a domain in your account, and your API token
    #vim .env
    code .env
    
    # Run the tests
    node tests/index.js

    Developing

    Join @rootprojects #general on Keybase if you'd like to chat with us.

    Contributions

    Did this project save you some time? Maybe make your day? Even save the day?

    Please say "thanks" via Paypal or 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 financially - 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

    Commercial Support

    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.

    Legal & Rules of the Road

    ACME.js™ 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

    Install

    npm i @sam-lord/acme

    DownloadsWeekly Downloads

    31

    Version

    3.4.1

    License

    MPL-2.0

    Unpacked Size

    90.6 kB

    Total Files

    19

    Last publish

    Collaborators

    • avatar