Skip to content

Latest commit

 

History

History
606 lines (443 loc) · 31 KB

developer-documentation.md

File metadata and controls

606 lines (443 loc) · 31 KB

Developer Documentation

This documentation includes the implementation details of Auto Encrypt and is intended to aid you if you’re trying to improve, debug, or get a deeper understanding of Auto Encrypt.

If you just want to use Auto Encrypt, please see the public API, as documented in the README.

The developer documentation is generated using jsdoc-to-markdown from the developer-documentation.hbs template.

The dependency diagram generation process as part of this requires Graphviz (dot command) to be installed. e.g., on Ubuntu:

sudo apt install graphviz

To update the documentation:

npm run generate-developer-documentation

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.

Requirements

Auto Encrypt is supported on:

  • Node: LTS (currently 14.16.0).
  • ECMAScript: ES2019

Overview of relationships

Dependency relationship diagram for Auto Correct

Not shown (for clarity): third-party Node modules, the util namespace with helper modules – for logging, error handling, and an async forEach implementation – and the typedefs namespace with JSDoc type definitions.

Generated using dependency cruiser.

Tests

Main test tasks use an automatically-managed local Pebble server instance with settings optimised for performance.

Prerequisites

  1. Add pebble as an alias for 127.0.0.1 and ::1 in your /etc/hosts file. If you’re running in a container (e.g., using DistroBox/podman on Fedora Silverblue, make sure you set this in the etc/hosts file of the host system, not the container)) e.g.

    # Loopback entries; do not change.
    # For historical reasons, localhost precedes localhost.localdomain:
    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4 pebble
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6 pebble
    
  2. To run staging and production tests (you do not need to do this to run the Pebble tests): make sure that your system is reachable from your hostname (e.g., by using a service like ngrok.)

npm -s run test

Tests should also pass with Pebble’s default settings and with the Let’s Encrypt staging server. The full set of test tasks are:

Name Server Sleep? Nonce reject? Debug output?
test Pebble
test-debug Pebble
test-pebble-sleep Pebble
test-pebble-sleep-debug Pebble
test-pebble-sleep-noncereject Pebble
test-pebble-sleep-noncereject-debug Pebble
test-staging Staging n/a n/a
test-staging-debug Staging n/a n/a

Coverage

There are several different code coverage tasks that correspond to the test tasks. Coverage task names begin with coverage instead of test and there are no debug versions for them.

npm -s run coverage

The full set of coverage tasks are:

Name Server Sleep? Nonce reject?
coverage Pebble
coverage-pebble-sleep Pebble
coverage-pebble-sleep-noncereject Pebble
coverage-staging Staging n/a n/a

Modules

@small-tech/auto-encrypt

Automatically provisions and renews Let’s Encrypt™ TLS certificates for Node.js® https servers (including Express.js, etc.)

Implements the subset of RFC 8555 – Automatic Certificate Management Environment (ACME) – necessary for a Node.js https server to provision TLS certificates from Let’s Encrypt using the HTTP-01 challenge on first hit of an HTTPS route via use of the Server Name Indication (SNI) callback.

lib/AcmeRequest

Abstract base request class for carrying out signed ACME requests over HTTPS.

lib/Certificate

Represents a Let’s Encrypt TLS certificate.

lib/Configuration

Global configuration class. Use initialise() method to populate.

Functions

csrAsPem(domains, key)String

Create a CSR given a list of domains and a Jose JWK.rsaKey.

Typedefs

PreparedRequest : Object
ProtectedHeader
HttpsHeaders : Object
ResponseObject

@small-tech/auto-encrypt

Automatically provisions and renews Let’s Encrypt™ TLS certificates for Node.js® https servers (including Express.js, etc.)

Implements the subset of RFC 8555 – Automatic Certificate Management Environment (ACME) – necessary for a Node.js https server to provision TLS certificates from Let’s Encrypt using the HTTP-01 challenge on first hit of an HTTPS route via use of the Server Name Indication (SNI) callback.

License: AGPLv3 or later.
Copyright: © 2020 Aral Balkan, Small Technology Foundation.

module.exports ⏏

Auto Encrypt is a static class. Please do not instantiate.

Use: AutoEncrypt.https.createServer(…)

Kind: Exported class

module.exports.serverType : LetsEncryptServer.type

Enumeration.

Kind: instance property of module.exports
Read only: true

module.exports.https

By aliasing the https property to the AutoEncrypt static class itself, we enable people to add AutoEncrypt to their existing apps by requiring the module and prefixing their https.createServer(…) line with AutoEncrypt:

Kind: static property of module.exports
Example

import AutoEncrypt from '@small-tech/auto-encrypt'
const server = AutoEncrypt.https.createServer()

module.exports.createServer([options]) ⇒ https.Server

Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js https servers using the HTTP-01 challenge on first hit of an HTTPS route via use of the Server Name Indication (SNI) callback.

Kind: static method of module.exports
Returns: https.Server - The server instance returned by Node’s https.createServer() method.

Param Type Default Description
[options] Object Optional HTTPS options object with optional additional Auto Encrypt-specific configuration settings.
[options.domains] Array.<String> Domain names to provision TLS certificates for. If missing, defaults to the hostname of the current computer and its www prefixed subdomain.
[options.serverType] Enum AutoEncrypt.serverType.PRODUCTION Let’s Encrypt server type to use. AutoEncrypt.serverType.PRODUCTION, ….STAGING, or ….PEBBLE (see LetsEncryptServer.type).
[options.settingsPath] String ~/.small-tech.org/auto-encrypt/ Path to save certificates/keys to.

module.exports.clearOcspCacheTimers()

The OCSP module does not have a means of clearing its cache check timers so we do it here. (Otherwise, the test suite would hang.)

Kind: static method of module.exports

module.exports.shutdown()

Shut Auto Encrypt down. Do this before app exit. Performs necessary clean-up and removes any references that might cause the app to not exit.

Kind: static method of module.exports

module.exports.addOcspStapling(server) ⇒ https.Server

Adds Online Certificate Status Protocol (OCSP) stapling (also known as TLS Certificate Status Request extension) support to the passed server instance.

Kind: static method of module.exports
Returns: https.Server - HTTPS server instance with OCSP Stapling support.
Access: private

Param Type Description
server https.Server HTTPS server instance without OCSP Stapling support.

lib/AcmeRequest

Abstract base request class for carrying out signed ACME requests over HTTPS.

License: AGPLv3 or later.
Copyright: Copyright © 2020 Aral Balkan, Small Technology Foundation.

module.exports ⏏

Abstract base request class for carrying out signed ACME requests over HTTPS.

Kind: Exported class

module.exports.execute(command, payload, useKid, [successCodes], [url], [parseResponseBodyAsJSON]) ⇒ types.ResponseObject

Executes a remote Let’s Encrypt command and either returns the result or throws.

Kind: instance method of module.exports

Param Type Default Description
command String Name of Directory command to invoke e.g. 'newAccount'
payload Object | String Object to use as payload. For no payload, pass empty string.
useKid Boolean true Use Key ID (true) or public JWK (false) (see RFC 8555 § 6.2).
[successCodes] Array.<Number> [200] Return codes accepted as success. Any other code throws.
[url] String If specified, use this URL, ignoring the command parameter.
[parseResponseBodyAsJSON] Boolean true Parse response body as JSON (true) or as string (false).

module.exports._execute(preparedRequest, parseResponseBodyAsJSON) ⇒ types.ResponseObject

Executes a prepared request.

Kind: instance method of module.exports

Param Type Description
preparedRequest types.PreparedRequest The prepared request, ready to be executed.
parseResponseBodyAsJSON Boolean Should the request body be parsed as JSON (true) or should the native response object be returned (false).

module.exports.getBuffer(stream) ⇒ Buffer

Concatenates the output of a stream and returns a buffer. Taken from the bent module.

Kind: instance method of module.exports
Returns: Buffer - The concatenated output of the Node stream.

Param Type Description
stream stream A Node stream.

module.exports.prepare(command, payload, useKid, [successCodes], [url]) ⇒ types.PreparedRequest

Separate the preparation of a request from the execution of it so we can easily test that different request configurations conform to our expectations.

Kind: instance method of module.exports

Param Type Default Description
command String (Required) Name of Let’s Encrypt command to invoke (see Directory). (sans 'Url' suffix). e.g. 'newAccount', 'newOrder', etc.
payload Object | String (Required) Either an object to use as the payload or, if there is no payload, an empty string.
useKid Boolean (Required) Should request use a Key ID (true) or, public JWK (false). (See RFC 8555 § 6.2 Request Authentication)
[successCodes] Array.<Number> [200] Optional array of codes that signals success. Any other code throws.
[url] String If specified, will use this URL directly, ignoring the value in the command parameter.

lib/Certificate

Represents a Let’s Encrypt TLS certificate.

License: AGPLv3 or later.
Copyright: Copyright © 2020 Aral Balkan, Small Technology Foundation.

module.exports ⏏

Represents a Let’s Encrypt TLS certificate.

Kind: Exported class

new module.exports(domains)

Creates an instance of Certificate.

Param Type Description
domains Array.<String> List of domains this certificate covers.

module.exports.attemptToRecoverFromFailedRenewalAttemptIfNecessary()

Check if certificate-identity.pem.old or certificate.pem.old files exist. If they do, it means that something went wrong while certificate was trying to be renewed. So restore them and use them and hopefully the next renewal attempt will succeed or at least buy the administrator of the server some time to fix the issue.

Kind: instance method of module.exports

module.exports.getSecureContext() ⇒ Promise.<tls.SecureContext>

Get a SecureContext that can be used in an SNICallback.

Kind: instance method of module.exports
Returns: Promise.<tls.SecureContext> - A promise for a SecureContext that can be used in creating https servers.
Category: async

module.exports.createSecureContext(renewCertificate) ⇒ Promise

Creates and caches a secure context, provisioning a TLS certificate in the process, if necessary.

Kind: instance method of module.exports
Returns: Promise - Fulfils immediately if certificate exists and does not need to be renewed. Otherwise, fulfils when certificate has been provisioned.
Category: async
Access: private

Param Type Default Description
renewCertificate Boolean false If true, will start the process of renewing the certificate (but will continue to return the existing certificate until it is ready).

module.exports.provisionCertificate() ⇒ Promise

Provisions a new Let’s Encrypt TLS certificate, persists it, and starts checking for renewals on it every day, starting with the next day.

Kind: instance method of module.exports
Returns: Promise - Fulfils once a certificate has been provisioned.
Category: async
Access: private

module.exports.renewCertificate() ⇒ Promise

Starts the certificate renewal process by requesting the creation of a fresh secure context.

Kind: instance method of module.exports
Returns: Promise - Resolves once certificate is renewed and new secure context is created and cached.
Category: async
Access: private

module.exports.checkForRenewal() ⇒ Promise

Checks if the certificate needs to be renewed (if it is within 30 days of its expiry date) and, if so, renews it. While the method is async, the result is not awaited on usage. Instead, it is a fire-and-forget method that’s called via a daily interval.

Kind: instance method of module.exports
Returns: Promise - Fulfils immediately if certificate doesn’t need renewal. Otherwise, fulfils once certificate has been renewed.
Category: async
Access: private

module.exports.startCheckingForRenewal([alsoCheckNow]) ℗

Starts checking for certificate renewals every 24 hours.

Kind: instance method of module.exports
Category: sync
Access: private

Param Type Default Description
[alsoCheckNow] boolean false If true, will also immediately check for renewal when the function is called (use this when loading a previously-provisioned and persisted certificate from disk).

module.exports.stopCheckingForRenewal() ℗

Stops the timer that checks for renewal daily. Use this during housekeeping before destroying this object.

Kind: instance method of module.exports
Category: sync
Access: private

lib/Configuration

Global configuration class. Use initialise() method to populate.

License: AGPLv3 or later.
Copyright: © 2020 Aral Balkan, Small Technology Foundation.

module.exports ⏏

Kind: Exported class

new module.exports(settings)

Initialise the configuration. Must be called before accessing settings. May be called more than once.

Param Type Description
settings Object Settings to initialise configuration with.
settings.domains Array.<String> List of domains Auto Encrypt will manage TLS certs for.
settings.server LetsEncryptServer Let’s Encrypt Server to use.
settings.settingsPath String Root settings path to use. Will use default path if null.

module.exports.server : LetsEncryptServer

The Let’s Encrypt Server instance.

Kind: instance property of module.exports
Read only: true

module.exports.domains : Array.<String>

List of domains that Auto Encrypt will manage TLS certificates for.

Kind: instance property of module.exports
Read only: true

module.exports.settingsPath : String

The root settings path. There is a different root settings path for pebble, staging and production modes.

Kind: instance property of module.exports
Read only: true

module.exports.accountPath : String

Path to the account.json file that contains the Key Id that uniquely identifies and authorises your account in the absence of a JWT (see RFC 8555 § 6.2. Request Authentication).

Kind: instance property of module.exports
Read only: true

module.exports.accountIdentityPath : String

The path to the account-identity.pem file that contains the private key for the account.

Kind: instance property of module.exports
Read only: true

module.exports.certificatePath : String

The path to the certificate.pem file that contains the certificate chain provisioned from Let’s Encrypt.

Kind: instance property of module.exports
Read only: true

module.exports.certificateDirectoryPath : String

The directory the certificate and certificate identity (private key) PEM files are stored in.

Kind: instance property of module.exports
Read only: true

module.exports.certificateIdentityPath : String

The path to the certificate-identity.pem file that holds the private key for the TLS certificate.

Kind: instance property of module.exports
Read only: true

csrAsPem(domains, key) ⇒ String

Create a CSR given a list of domains and a Jose JWK.rsaKey.

Kind: global function
Returns: String - A CSR in PEM format.

Param Type
domains Array.<String>
key JWK.rsaKey

PreparedRequest : Object

Kind: global typedef
Properties

Name Type Description
protectedHeader ProtectedHeader JSON Web Signature (JWS) Protected Header (See RFC 7515 § A.6.1)
signedRequest JWS.FlattenedJWS Flattened JWS
httpsRequest bent.RequestFunction.<bent.ValidResponse> Asynchronous HTTPs request, ready to be executed.
httpsHeaders HttpsHeaders Hardcoded HTTPS headers.

ProtectedHeader

Kind: global typedef
Properties

Name Type Description
alg String Hardcoded to 'RS256', currently the only algorithm supported by Let’s Encrypt (LE).
nonce String Nonce (a value that’s used only once to thwart replay attacks).
url String URL of the command on Let’s Encrypt’s servers.
kid String Key ID returned by LE (per RFC 8555 § 6.2, set either this or jwk, not both).
jwk JWKRSAKey Public JWK (per RFC 8555 § 6.2, set either this or jwk, not both).

HttpsHeaders : Object

Kind: global typedef
Properties

Name Type Description
'Content-Type' String Hardcoded to 'application/jose+json'
'User-Agent' String Hardcoded to 'small-tech.org-acme/1.0.0 node/12.16.0'
'Accept-Language String Hardcoded to 'en-US'

ResponseObject

Kind: global typedef
Properties

Name Type Description
headers Object Native HTTPS response headers object.
body Object | String The response body as a native object or as a string.

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.

Copyright

© 2020-2021 Aral Balkan, Small Technology Foundation.

Let’s Encrypt is a trademark of the Internet Security Research Group (ISRG). All rights reserved. Node.js is a trademark of Joyent, Inc. and is used with its permission. We are not endorsed by or affiliated with Joyent or ISRG.

License

AGPL version 3.0 or later.