@small-tech/auto-encrypt 4.3.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -64
- package/index.d.ts +152 -0
- package/index.js +220 -205
- package/lib/Account.js +28 -20
- package/lib/AcmeRequest.js +187 -117
- package/lib/Authorisation.js +80 -36
- package/lib/Certificate.js +292 -149
- package/lib/Configuration.js +93 -77
- package/lib/Directory.js +28 -17
- package/lib/HttpServer.js +55 -28
- package/lib/IPAddresses.js +114 -0
- package/lib/Identity.js +65 -37
- package/lib/LetsEncryptServer.js +15 -15
- package/lib/Nonce.js +36 -27
- package/lib/Order.js +65 -39
- package/lib/acme-requests/AuthorisationRequest.js +15 -14
- package/lib/acme-requests/CertificateRequest.js +16 -15
- package/lib/acme-requests/CheckOrderStatusRequest.js +19 -17
- package/lib/acme-requests/FinaliseOrderRequest.js +21 -19
- package/lib/acme-requests/NewAccountRequest.js +13 -14
- package/lib/acme-requests/NewOrderRequest.js +30 -17
- package/lib/acme-requests/ReadyForChallengeValidationRequest.js +17 -16
- package/lib/acmeCsr.js +116 -59
- package/lib/identities/AccountIdentity.js +15 -14
- package/lib/identities/CertificateIdentity.js +16 -16
- package/lib/staging/monkeyPatchTls.js +12 -12
- package/lib/test-helpers/index.js +8 -23
- package/lib/util/Pluralise.js +2 -2
- package/lib/util/Throws.js +61 -21
- package/lib/util/async-foreach.js +21 -21
- package/lib/util/fromNow.js +27 -0
- package/lib/util/log.js +5 -1
- package/lib/util/waitFor.js +5 -0
- package/lib/x.509/rfc5280.js +47 -12
- package/package.json +12 -11
- package/typedefs/lib/AcmeRequest.js +36 -25
package/README.md
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
# Auto Encrypt
|
|
2
2
|
|
|
3
|
-
Automatically provisions and renews [Let’s Encrypt](https://letsencrypt.org) TLS certificates on [Node.js](https://nodejs.org) [https](https://nodejs.org/
|
|
3
|
+
Automatically provisions and renews [Let’s Encrypt](https://letsencrypt.org) TLS certificates on [Node.js](https://nodejs.org) [https](https://nodejs.org/docs/latest-v24.x/api/https.html) servers. As used in [@small-tech/https](https://codeberg.org/small-tech/https) and [Kitten](https://kitten.small-web.org).
|
|
4
4
|
|
|
5
5
|
Implements the subset of RFC 8555 – Automatic Certificate Management Environment (ACME) – necessary for a client to support TLS certificate provisioning from Let’s Encrypt using HTTP-01 challenges.
|
|
6
6
|
|
|
7
7
|
## How it works
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Let’s Encrypt TLS certificates are automatically provisioned for you before your server starts.
|
|
10
|
+
|
|
11
|
+
Auto Encrypt uses the new Let’s Encrypt `shortlived` profile *exclusively*, which means certificates are valid for (and renewed before) 160 hours. This should occur at around the 80 hour mark of the certificate’s lifespan, in accordance with data returned by ACME Renewal Information (ARI).
|
|
12
|
+
|
|
13
|
+
Auto Encrypt also implements automatic support for obtaining certificates on IPv4 and IPv6 addresses to enable the use of [Web Numbers](https://ar.al/2025/06/25/web-numbers/) on the Small Web.
|
|
10
14
|
|
|
11
15
|
When not provisioning certificates, Auto Encrypt also forwards HTTP calls to HTTPS on your server.
|
|
12
16
|
|
|
13
17
|
## Compatibility
|
|
14
18
|
|
|
15
|
-
Node.js 18.20.0
|
|
19
|
+
Node.js 18.20.0+ on Linux and macOS.
|
|
20
|
+
|
|
21
|
+
All tests pass on Node.js LTS (version 24).
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
> 💡 The module was tested to run on Windows 10 & 11 but it is no longer supported on Windows as [Microsoft is complicit in Israel’s genocide of the Palestinian people](https://www.bdsmovement.net/microsoft) and [Small Technology Foundation](https://small-tech.org/) stands in solidarity with the [Boycott, Divestment, and Sanctions (BDS) movement](https://www.bdsmovement.net/). **Windows is an ad-infested and surveillance-ridden dumpster fire of an operating system and, alongside supporting genocide, you are putting both yourself and others at risk by using it.**
|
|
24
|
+
>
|
|
25
|
+
> 🇵🇸 To support families facing genocide in Gaza, consider [donating to them via Gaza Verified](https://gaza-verified.org/donate/).
|
|
18
26
|
|
|
19
27
|
## Installation
|
|
20
28
|
|
|
@@ -27,46 +35,53 @@ npm i @small-tech/auto-encrypt
|
|
|
27
35
|
### Instructions
|
|
28
36
|
|
|
29
37
|
1. Import the module:
|
|
30
|
-
|
|
38
|
+
|
|
31
39
|
```js
|
|
32
40
|
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
33
41
|
```
|
|
34
42
|
|
|
35
|
-
2.
|
|
36
|
-
|
|
37
|
-
```js
|
|
38
|
-
// const server = https.createServer(…) becomes
|
|
39
|
-
const server = AutoEncrypt.https.createServer(…)
|
|
40
|
-
```
|
|
43
|
+
2. Create your server:
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
```js
|
|
46
|
+
// const server = https.createServer(…) becomes
|
|
47
|
+
const server = await AutoEncrypt.https.createServer(…)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> 💡 Auto Encrypt supports _one_ server per Node process. If you call `createServer()` again, any arguments you pass will be ignored and your will get the same server back. To create another server in the same process (e.g., when testing), call `await server.close()` first (see below) and then create the new server.
|
|
51
|
+
|
|
52
|
+
3. When done, close your server:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
await server.close()
|
|
56
|
+
console.info ('The server is now closed.')
|
|
57
|
+
```
|
|
49
58
|
|
|
50
59
|
### Example
|
|
51
60
|
|
|
52
|
-
The following code creates an HTTPS server running on port 443 that automatically provisions and renews TLS certificates from [Let’s Encrypt](https://letsencrypt.org) for the domains _<hostname>_
|
|
61
|
+
The following code creates an HTTPS server running on port 443 that automatically provisions and renews TLS certificates from [Let’s Encrypt](https://letsencrypt.org) for the domains _<hostname>_.
|
|
53
62
|
|
|
54
63
|
```js
|
|
55
64
|
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
56
65
|
|
|
57
|
-
const server = AutoEncrypt.https.createServer((
|
|
66
|
+
const server = await AutoEncrypt.https.createServer((_request, response) => {
|
|
58
67
|
response.end('Hello, world')
|
|
59
68
|
})
|
|
60
69
|
|
|
61
|
-
server.listen(
|
|
62
|
-
console.log(`Auto-encrypted HTTPS server is running at ${os.hostname()} and www.${os.hostname()}.`)
|
|
63
|
-
})
|
|
70
|
+
await server.listen()
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
const domain = os.hostname()
|
|
73
|
+
console.info(`Auto-encrypted HTTPS server is running at ${domain}.`)
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(`https://${domain}/`)
|
|
77
|
+
const responseText = await response.text()
|
|
78
|
+
console.info('Server says:', responseText)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(error)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await server.close()
|
|
84
|
+
console.info ('The server is now closed.')
|
|
70
85
|
```
|
|
71
86
|
|
|
72
87
|
Note that on Linux, ports 80 and 443 require special privileges. Please see [A note on Linux and the security farce that is “privileged ports”](#a-note-on-linux-and-the-security-farce-that-is-priviliged-ports). If you just need a [Small Web](https://ar.al/2024/06/24/small-web-computer-science-colloquium-at-university-of-groningen/) server that handles all that and more for you (or to see how to implement privilege escalation seamlessly in your own servers, see [Kitten](https://kitten.small-web.org)).
|
|
@@ -77,6 +92,12 @@ You can customise the default configuration by adding Auto Encrypt-specific opti
|
|
|
77
92
|
|
|
78
93
|
You can specify the domains you want the certificate to support, whether the Let’s Encrypt staging server or a local [Pebble](https://github.com/letsencrypt/pebble) server should be used instead of the default production server (useful during development and testing), and to specify a custom settings path for your Let’s Encrypt account and certificate information to be stored in.
|
|
79
94
|
|
|
95
|
+
You can also set the `ipv4` flag to `true` to make Auto Encrypt auto-detect your external IPv4 address (using https://ip.small-web.org/) and provision a certificate for it.
|
|
96
|
+
|
|
97
|
+
Similarly, you can set the `ipv6` flag to `true` to make Auto Encrypt auto-detect stable IPv6 addresses on your machine (this is done locally) and provision certificates for them.
|
|
98
|
+
|
|
99
|
+
If you pass `ipOnly: true`, then certificates will only be provisioned for IP addresses. This turns off the automatic provisioning of certificates for the machine’s hostname and overrides any domains you may have passed in the `domains` array.
|
|
100
|
+
|
|
80
101
|
(Auto Encrypt uses [Node Pebble](https://codeberg.org/small-tech/node-pebble) to enable testing with a local Pebble server from Node.js.)
|
|
81
102
|
|
|
82
103
|
### Example
|
|
@@ -88,13 +109,14 @@ const options = {
|
|
|
88
109
|
// Regular HTTPS server and TLS server options, if any, go here.
|
|
89
110
|
|
|
90
111
|
// Optional Auto Encrypt options:
|
|
91
|
-
|
|
112
|
+
ipv4: true,
|
|
113
|
+
ipOnly: true,
|
|
92
114
|
server: AutoEncrypt.server.STAGING,
|
|
93
115
|
settingsPath: '/custom/settings/path'
|
|
94
116
|
}
|
|
95
117
|
|
|
96
118
|
// Pass the options object to https.createServer()
|
|
97
|
-
const server = AutoEncrypt.https.createServer(options, listener)
|
|
119
|
+
const server = await AutoEncrypt.https.createServer(options, listener)
|
|
98
120
|
|
|
99
121
|
// …
|
|
100
122
|
```
|
|
@@ -106,38 +128,19 @@ Here is the full list of Auto Encrypt options (all optional) and their defaults:
|
|
|
106
128
|
- `domains`: the hostname of the current computer and the www subdomain at that hostname.
|
|
107
129
|
- `server`: it will use the production server (which has [rate limits](https://letsencrypt.org/docs/rate-limits/)). Valid values are `AutoEncrypt.server.(PEBBLE|STAGING|PRODUCTION)`.
|
|
108
130
|
- `settingsPath`: _~/.small-tech.org/auto-encrypt/_
|
|
131
|
+
- `ipv4`: true/false – whether or not to automatically detect external IPv4 address and add it to the TLS certificate being provisioned.
|
|
132
|
+
- `ipv6`: true/false – whether or not to automatically detect stable IPv6 addresses and add them to the TLS certificate being provisioned.
|
|
133
|
+
- `ipOnly`: When true, the machines hostname is not added to the TLS certificate and neither are the domains passed in the `domains` array, if any.
|
|
109
134
|
|
|
110
135
|
## Making a graceful exit
|
|
111
136
|
|
|
112
|
-
When you’re ready to exit your app, just call the `server.close()` method
|
|
137
|
+
When you’re ready to exit your app, just call the `await server.close()` method. This will allow Auto Encrypt to perform housekeeping like shutting own the HTTP server that is used for answering Let’s Encrypt challenges as well as performing HTTP to HTTPS redirections and to destroy its certificate check update interval which, if not destroyed, will prevent your app from exiting.
|
|
113
138
|
|
|
114
139
|
## Developer documentation
|
|
115
140
|
|
|
116
141
|
If you want to help improve Auto Encrypt or better understand how it is structured and operates, please see the [developer documentation](https://codeberg.org/small-tech/auto-encrypt/src/branch/main/developer-documentation.md).
|
|
117
142
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
### Regular https
|
|
121
|
-
|
|
122
|
-
```js
|
|
123
|
-
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
124
|
-
|
|
125
|
-
const server = AutoEncrypt.https.createServer({ domains: ['dev.ar.al'] }, (request, response) => {
|
|
126
|
-
response.end('Hello, world!')
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
server.listen(() => {
|
|
130
|
-
console.log('Auto-encrypted HTTPS server is running at https://dev.ar.al')
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
// Later…
|
|
134
|
-
|
|
135
|
-
server.close(() => {
|
|
136
|
-
console.log('The server is now closed.')
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Express.js
|
|
143
|
+
### Express.js example
|
|
141
144
|
|
|
142
145
|
```js
|
|
143
146
|
const express = require('express')
|
|
@@ -148,21 +151,18 @@ app.get('/', (request, response) => {
|
|
|
148
151
|
response.end('Hello, world!')
|
|
149
152
|
})
|
|
150
153
|
|
|
151
|
-
const
|
|
152
|
-
|
|
154
|
+
const myDomain = 'dev.ar.al'
|
|
155
|
+
const server = await AutoEncrypt.https.createServer(
|
|
156
|
+
{ domains: [myDomain] },
|
|
153
157
|
app
|
|
154
158
|
)
|
|
155
159
|
|
|
156
|
-
server.listen(
|
|
157
|
-
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
+
await server.listen()
|
|
161
|
+
console.log(`Auto-encrypted Express server is running at https://${myDomain}`)
|
|
160
162
|
|
|
161
163
|
// Later…
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
console.log('The server is now closed.')
|
|
165
|
-
})
|
|
164
|
+
await server.close()
|
|
165
|
+
console.info ('The server is now closed.')
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
## Like this? Fund us!
|
|
@@ -226,7 +226,7 @@ A [💕 Small Web](https://ar.al/2020/08/07/what-is-the-small-web/) development
|
|
|
226
226
|
|
|
227
227
|
## Tests and coverage
|
|
228
228
|
|
|
229
|
-
This project aims for > 80% coverage
|
|
229
|
+
This project aims for > 80% coverage and currently has > 90%.
|
|
230
230
|
|
|
231
231
|
To see the current state of code coverage, run `npm run coverage`.
|
|
232
232
|
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Automatically provisions and renews Let’s Encrypt™ TLS certificates for Node.js® https servers (as used in @small-tech/https and Kitten.)
|
|
3
|
+
|
|
4
|
+
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.
|
|
5
|
+
|
|
6
|
+
The certificates are provisioned, if necessary, at time of server creation, prior to server start with hourly renewal checks and automatic renewals.
|
|
7
|
+
|
|
8
|
+
Auto Encrypt uses the new Let’s Encrypt `shortlived` profile *exclusively*, which means certificates are valid for (and renewed before) 160 hours. This should occur at around the 80 hour mark of the certificate’s lifespan, in accordance with data returned by ACME Renewal Information (ARI).
|
|
9
|
+
|
|
10
|
+
Auto Encrypt also implements automatic support for obtaining certificates on IPv4 and IPv6 addresses to enable the use of Web Numbers on the Small Web (see https://ar.al/2025/06/25/web-numbers/).
|
|
11
|
+
|
|
12
|
+
Remember that Auto Encrypt is for the Small Web (peer-to-peer Web). You might find it useful for everyday web purposes also but it is specifically focused for Small Web use cases and explicitly does not aim to be a generic or exhaustive ACME client.
|
|
13
|
+
|
|
14
|
+
@module @small-tech/auto-encrypt
|
|
15
|
+
@copyright © 2020-present Aral Balkan, Small Technology Foundation.
|
|
16
|
+
@license AGPLv3 or later.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Server, ServerOptions } from 'node:https'
|
|
20
|
+
import { SecureContext } from 'node:tls'
|
|
21
|
+
import { IncomingMessage, ServerResponse } from 'node:http'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
Options for creating an AutoEncrypt server. Extends Node’s https.ServerOptions.
|
|
25
|
+
*/
|
|
26
|
+
export interface AutoEncryptOptions extends ServerOptions {
|
|
27
|
+
/**
|
|
28
|
+
The domains for which to provision and renew certificates.
|
|
29
|
+
*/
|
|
30
|
+
domains?: string[]
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
Type of Let’s Encrypt server to use. Use AutoEncrypt.serverType constants.
|
|
34
|
+
*/
|
|
35
|
+
serverType?: number
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
Path where settings and certificates will be stored.
|
|
39
|
+
*/
|
|
40
|
+
settingsPath?: string
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
Whether to automatically detect and use the machine’s IPv4 address.
|
|
44
|
+
*/
|
|
45
|
+
ipv4?: boolean
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
Whether to automatically detect and use the machine’s stable IPv6 addresses.
|
|
49
|
+
*/
|
|
50
|
+
ipv6?: boolean
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
If true, only IP addresses (IPv4/IPv6 as requested) are used, ignoring the hostname.
|
|
54
|
+
*/
|
|
55
|
+
ipOnly?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
Request listener function signature.
|
|
60
|
+
*/
|
|
61
|
+
export type RequestListener = (req: IncomingMessage, res: ServerResponse) => void
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
Enumeration of Let’s Encrypt server types.
|
|
65
|
+
*/
|
|
66
|
+
export interface ServerType {
|
|
67
|
+
PRODUCTION: 0
|
|
68
|
+
STAGING: 1
|
|
69
|
+
PEBBLE: 2
|
|
70
|
+
MOCK: 3
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
Represents a Let’s Encrypt TLS certificate.
|
|
75
|
+
*/
|
|
76
|
+
export interface Certificate {
|
|
77
|
+
getSecureContext(): Promise<SecureContext | null>
|
|
78
|
+
checkForRenewal(): Promise<void>
|
|
79
|
+
stopCheckingForRenewal(): void
|
|
80
|
+
readonly isProvisioned: boolean
|
|
81
|
+
readonly pem: string | Buffer | Array<string | Buffer> | undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
Represents a Let’s Encrypt server instance.
|
|
86
|
+
*/
|
|
87
|
+
export interface LetsEncryptServer {
|
|
88
|
+
readonly type: number
|
|
89
|
+
readonly name: string
|
|
90
|
+
readonly endpoint: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
The monkey-patched https.Server instance returned by AutoEncrypt.
|
|
95
|
+
*/
|
|
96
|
+
export interface AutoEncryptedServer extends Server {
|
|
97
|
+
listen(...args:any[]): Promise<AutoEncryptedServer>
|
|
98
|
+
async close(): Promise<void>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
Auto Encrypt is a static class. Please do not instantiate.
|
|
103
|
+
|
|
104
|
+
Use: AutoEncrypt.https.createServer(…)
|
|
105
|
+
|
|
106
|
+
@alias module:@small-tech/auto-encrypt
|
|
107
|
+
@hideconstructor
|
|
108
|
+
*/
|
|
109
|
+
export default class AutoEncrypt {
|
|
110
|
+
static letsEncryptServer: LetsEncryptServer | null
|
|
111
|
+
static defaultDomains : string[] | null
|
|
112
|
+
static domains : string[] | null
|
|
113
|
+
static settingsPath : string | null
|
|
114
|
+
static listener : RequestListener | null
|
|
115
|
+
static certificate : Certificate | null
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
Enumeration.
|
|
119
|
+
|
|
120
|
+
@static
|
|
121
|
+
@readonly
|
|
122
|
+
*/
|
|
123
|
+
static readonly serverType: ServerType
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
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:
|
|
127
|
+
|
|
128
|
+
@example import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
129
|
+
const server = AutoEncrypt.https.createServer()
|
|
130
|
+
|
|
131
|
+
@static
|
|
132
|
+
*/
|
|
133
|
+
static readonly https: typeof AutoEncrypt
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
@static
|
|
139
|
+
|
|
140
|
+
@param {AutoEncryptOptions | RequestListener} [options] Configuration options or a request listener.
|
|
141
|
+
@param {RequestListener} [listener] Optional request listener if options were provided.
|
|
142
|
+
@returns {Promise<AutoEncryptedServer>} The server instance returned by Node’s https.createServer() method.
|
|
143
|
+
*/
|
|
144
|
+
static async createServer(options?: AutoEncryptOptions | RequestListener, listener?: RequestListener): Promise<AutoEncryptedServer>
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
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.
|
|
148
|
+
*/
|
|
149
|
+
static async shutdown(): Promise<void>
|
|
150
|
+
|
|
151
|
+
private constructor()
|
|
152
|
+
}
|