@small-tech/auto-encrypt 4.0.0 → 4.1.1

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 CHANGED
@@ -1,20 +1,18 @@
1
1
  # Auto Encrypt
2
2
 
3
- Adds automatic provisioning and renewal of [Let’s Encrypt](https://letsencrypt.org) TLS certificates with [OCSP Stapling](https://letsencrypt.org/docs/integration-guide/#implement-ocsp-stapling) to [Node.js](https://nodejs.org) [https](https://nodejs.org/dist/latest-v12.x/docs/api/https.html) servers (including [Express.js](https://expressjs.com/), etc.)
3
+ Automatically provisions and renews [Let’s Encrypt](https://letsencrypt.org) TLS certificates on [Node.js](https://nodejs.org) [https](https://nodejs.org/dist/latest-v12.x/docs/api/https.html) servers (including [Kitten](https://kitten.small-web.org), [Polka](https://github.com/lukeed/polka/tree/next), [Express.js](https://expressjs.com/), etc.)
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
- The first time your web site is hit, it will take a couple of seconds to load as your Let’s Encrypt TLS certificates are automatically provisioned for you. From there on, your certificates will be seamlessly renewed 30 days before their expiry date.
9
+ The first time your web site is hit, it takes a couple of seconds to load as your Let’s Encrypt TLS certificates are automatically provisioned for you. From there on, your certificates are seamlessly renewed 30 days before their expiry date.
10
10
 
11
- When not provisioning certificates, Auto Encrypt will also forward HTTP calls to HTTPS on your server.
11
+ When not provisioning certificates, Auto Encrypt also forwards HTTP calls to HTTPS on your server.
12
12
 
13
13
  ## Compatibility
14
14
 
15
- All tests pass on Node.js LTS (18.2+).
16
-
17
- [Tests fail on Node.js 19 (socket hang up error).](https://codeberg.org/small-tech/auto-encrypt/issues/3)
15
+ All tests pass on Node.js LTS (version 22).
18
16
 
19
17
  ## Installation
20
18
 
@@ -49,7 +47,7 @@ npm i @small-tech/auto-encrypt
49
47
 
50
48
  ### Example
51
49
 
52
- The following code creates an HTTPS server running on port 443 with [OCSP Stapling](https://letsencrypt.org/docs/integration-guide/#implement-ocsp-stapling) that automatically provisions and renews TLS certificates from [Let’s Encrypt](https://letsencrypt.org) for the domains _<hostname>_ and _www.<hostname>_.
50
+ 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>_ and _www.<hostname>_.
53
51
 
54
52
  ```js
55
53
  import AutoEncrypt from '@small-tech/auto-encrypt'
@@ -69,7 +67,7 @@ server.close(() => {
69
67
  })
70
68
  ```
71
69
 
72
- 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 Node web server that handles all that and more for you (or to see how to implement privilege escalation seamlessly in your own servers, see [Site.js](https://sitejs.org)).
70
+ 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)).
73
71
 
74
72
  ## Configuration
75
73
 
@@ -77,6 +75,8 @@ You can customise the default configuration by adding Auto Encrypt-specific opti
77
75
 
78
76
  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
77
 
78
+ (Auto Encrypt uses [Node Pebble](https://codeberg.org/small-tech/node-pebble) to enable testing with a local Pebble server from Node.js.)
79
+
80
80
  ### Example
81
81
 
82
82
  ```js
@@ -111,7 +111,7 @@ When you’re ready to exit your app, just call the `server.close()` method as y
111
111
 
112
112
  ## Developer documentation
113
113
 
114
- If you want to help improve Auto Encrypt or better understand how it is structured and operates, please see the [developer documentation](https://github.com/small-tech/auto-encrypt/blob/master/developer-documentation.md).
114
+ 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).
115
115
 
116
116
  ## Examples
117
117
 
@@ -177,17 +177,17 @@ If you’re evaluating this for a “startup” or an enterprise, let us save yo
177
177
 
178
178
  ## Client details
179
179
 
180
- Auto Encrypt does one thing and one thing well: it automatically provisions a Let’s Encrypt TLS certificate for your Node.js https servers using the HTTP-01 challenge method when your server is first hit from its hostname, serves it using OCSP Stapling, and automatically renews your certificate thereafter. And, when not provisioning certificates, it forwards any HTTP requests that your machine gets to HTTPS.
180
+ Auto Encrypt does one thing and one thing well: it automatically provisions a Let’s Encrypt TLS certificate for your Node.js https servers using the HTTP-01 challenge method when your server is first hit from its hostname, and automatically renews your certificate thereafter. When not provisioning certificates, it forwards any HTTP requests that your machine gets to HTTPS.
181
181
 
182
- Auto Encrypt __does not_ and __will not__:
182
+ __Auto Encrypt _does not_ and _will not:___
183
183
 
184
- - Implement wildcard certificates. For most [small tech](https://small-tech.org/about/#small-technology) needs (personal web sites and web apps), you will likely need no more than two domains (the root domain and, due to historic and conventional reasons, the www subdomain). You will definitely not need more than the 100 domains that are supported per certificate. If you do, chances are you are looking to use Auto Encrypt in a startup or corporate setting, which is not what its for.
184
+ - __Implement wildcard certificates.__ For most [small tech](https://small-tech.org/about/#small-technology) needs (personal web sites and web apps), you will likely need no more than two domains (the root domain and, due to historic and conventional reasons, the www subdomain). You will definitely not need more than the 100 domains that are supported per certificate. If you do, chances are you are looking to use Auto Encrypt in a startup or corporate setting, which is not what its for.
185
185
 
186
- - Implement DNS-01 or any other methods that cannot be fully automated.
186
+ - __Implement DNS-01__ or any other methods that cannot be fully automated.
187
187
 
188
188
  ## Staging and production server behaviour and rate limits
189
189
 
190
- By default, Auto Encrypt will use the Let’s Encrypt production environment. This is most likely what you want as it means that your HTTPS server will Just Work™, provisioning its TLS certificate automatically the first time the server is hit via its hostname and from thereon automatically renewing the certificate a month ahead of its expiry date.
190
+ By default, Auto Encrypt uses Let’s Encrypt’s production environment. This is most likely what you want as it means your HTTPS server will Just Work™, i.e., provision its TLS certificate automatically the first time the server is hit via its hostname and from thereon automatically renew the certificate a month ahead of its expiry date.
191
191
 
192
192
  However, be aware that the production server has [rate limits](https://letsencrypt.org/docs/rate-limits/).
193
193
 
@@ -203,24 +203,24 @@ From lower-level to higher-level:
203
203
 
204
204
  ### Auto Encrypt Localhost
205
205
 
206
- - Source: https://source.small-tech.org/site.js/lib/auto-encrypt-localhost
206
+ - Source: https://codeberg.org/small-tech/auto-encrypt-localhost
207
207
  - Package: [@small-tech/auto-encrypt-localhost](https://www.npmjs.com/package/@small-tech/auto-encrypt-localhost)
208
208
 
209
- Automatically provisions and installs locally-trusted TLS certificates for Node.js https servers (including Express.js, etc.) using [mkcert](https://github.com/FiloSottile/mkcert/).
209
+ Automatically provisions and installs locally-trusted TLS certificates for Node.js https servers in 100% JavaScript (without any native dependencies like mkcert and certutil).
210
210
 
211
211
  ### HTTPS
212
212
 
213
213
  - Source: https://source.small-tech.org/site.js/lib/https
214
214
  - Package: [@small-tech/https](https://www.npmjs.com/package/@small-tech/https)
215
215
 
216
- A drop-in [standard Node.js HTTPS module](https://nodejs.org/dist/latest-v12.x/docs/api/https.html) replacement with both automatic development-time (localhost) certificates via Auto Encrypt Localhost and automatic production certificates via Auto Encrypt.
216
+ Drop-in replace for the [standard Node.js HTTPS module](https://nodejs.org/dist/latest-v12.x/docs/api/https.html) replacement that automatically handles TLS certificate provisioning and renewal both at localhost (via Auto Encrypt Localhost) and at hostname (via Auto Encrypt).
217
217
 
218
- ### Site.js
218
+ ### Kitten
219
219
 
220
- - Web site: https://sitejs.org
221
- - Source: https://source.small-tech.org/site.js/app
220
+ - Site: https://kitten.small-web.org
221
+ - Source: https://codeberg.org/kitten/app
222
222
 
223
- A complete [small technology](https://small-tech.org/about/#small-technology) tool for developing, testing, and deploying a secure static or dynamic personal web site or app with zero configuration.
223
+ A [💕 Small Web](https://ar.al/2020/08/07/what-is-the-small-web/) development kit.
224
224
 
225
225
  ## Tests and coverage
226
226
 
@@ -228,7 +228,7 @@ This project aims for > 80% coverage. At a recent check, coverage was at 97.42%
228
228
 
229
229
  To see the current state of code coverage, run `npm run coverage`.
230
230
 
231
- For more details, please see the [developer documentation](https://github.com/small-tech/auto-encrypt/blob/master/developer-documentation.md#tests).
231
+ For more details, please see the [developer documentation](https://codeberg.org/small-tech/auto-encrypt/src/branch/main/developer-documentation.md#tests).
232
232
 
233
233
  ## A note on Linux and the security farce that is “privileged ports”
234
234
 
package/index.js CHANGED
@@ -14,7 +14,6 @@
14
14
  import os from 'os'
15
15
  import util from 'util'
16
16
  import https from 'https'
17
- import ocsp from 'ocsp'
18
17
  import monkeyPatchTls from './lib/staging/monkeyPatchTls.js'
19
18
  import LetsEncryptServer from './lib/LetsEncryptServer.js'
20
19
  import Configuration from './lib/Configuration.js'
@@ -77,9 +76,6 @@ export default class AutoEncrypt {
77
76
  */
78
77
  static get https () { return AutoEncrypt }
79
78
 
80
-
81
- static ocspCache = null
82
-
83
79
  /**
84
80
  * Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js
85
81
  * https servers using the HTTP-01 challenge on first hit of an HTTPS route via use of
@@ -174,7 +170,7 @@ export default class AutoEncrypt {
174
170
  }
175
171
  }
176
172
 
177
- const server = this.addOcspStapling(https.createServer(options, listener))
173
+ const server = https.createServer(options, listener)
178
174
 
179
175
  //
180
176
  // Monkey-patch the server.
@@ -212,25 +208,11 @@ export default class AutoEncrypt {
212
208
  }
213
209
 
214
210
 
215
- /**
216
- * The OCSP module does not have a means of clearing its cache check timers
217
- * so we do it here. (Otherwise, the test suite would hang.)
218
- */
219
- static clearOcspCacheTimers () {
220
- if (this.ocspCache !== null) {
221
- const cacheIds = Object.keys(this.ocspCache.cache)
222
- cacheIds.forEach(cacheId => {
223
- clearInterval(this.ocspCache.cache[cacheId].timer)
224
- })
225
- }
226
- }
227
-
228
211
  /**
229
212
  * Shut Auto Encrypt down. Do this before app exit. Performs necessary clean-up and removes
230
213
  * any references that might cause the app to not exit.
231
214
  */
232
215
  static shutdown () {
233
- this.clearOcspCacheTimers()
234
216
  this.certificate.stopCheckingForRenewal()
235
217
  }
236
218
 
@@ -238,64 +220,6 @@ export default class AutoEncrypt {
238
220
  // Private.
239
221
  //
240
222
 
241
- /**
242
- * Adds Online Certificate Status Protocol (OCSP) stapling (also known as TLS Certificate Status Request extension)
243
- * support to the passed server instance.
244
- *
245
- * @private
246
- * @param {https.Server} server HTTPS server instance without OCSP Stapling support.
247
- * @returns {https.Server} HTTPS server instance with OCSP Stapling support.
248
- */
249
- static addOcspStapling(server) {
250
- // OCSP stapling
251
- //
252
- // Many browsers will fetch OCSP from Let’s Encrypt when they load your site. This is a performance and privacy
253
- // problem. Ideally, connections to your site should not wait for a secondary connection to Let’s Encrypt. Also,
254
- // OCSP requests tell Let’s Encrypt which sites people are visiting. We have a good privacy policy and do not record
255
- // individually identifying details from OCSP requests, we’d rather not even receive the data in the first place.
256
- // Additionally, we anticipate our bandwidth costs for serving OCSP every time a browser visits a Let’s Encrypt site
257
- // for the first time will be a big part of our infrastructure expense.
258
- //
259
- // By turning on OCSP Stapling, you can improve the performance of your website, provide better privacy protections
260
- // … and help Let’s Encrypt efficiently serve as many people as possible.
261
- //
262
- // (Source: https://letsencrypt.org/docs/integration-guide/implement-ocsp-stapling)
263
-
264
- this.ocspCache = new ocsp.Cache()
265
- const cache = this.ocspCache
266
-
267
- server.on('OCSPRequest', (certificate, issuer, callback) => {
268
-
269
- if (certificate == null) {
270
- return callback(new Error('Cannot OCSP staple: certificate not yet provisioned.'))
271
- }
272
-
273
- ocsp.getOCSPURI(certificate, function(error, uri) {
274
- if (error) return callback(error)
275
- if (uri === null) return callback()
276
-
277
- const request = ocsp.request.generate(certificate, issuer)
278
-
279
- cache.probe(request.id, (error, cached) => {
280
- if (error) return callback(error)
281
-
282
- if (cached !== false) {
283
- return callback(null, cached.response)
284
- }
285
-
286
- const options = {
287
- url: uri,
288
- ocsp: request.data
289
- }
290
-
291
- cache.request(request.id, options, callback);
292
- })
293
- })
294
- })
295
-
296
- return server
297
- }
298
-
299
223
  // Custom object description for console output (for debugging).
300
224
  static [util.inspect.custom] () {
301
225
  return `
@@ -6,6 +6,9 @@
6
6
  * @license AGPLv3 or later.
7
7
  */
8
8
 
9
+ import fs from 'fs'
10
+ import path from 'path'
11
+ import { fileURLToPath } from 'url'
9
12
  import jose from 'jose'
10
13
  import prepareRequest from 'bent'
11
14
  import types from '../typedefs/lib/AcmeRequest.js'
@@ -23,6 +26,7 @@ const throws = new Throws({
23
26
  [Symbol.for('AcmeRequest.requestError')]: error => `(${error.status} ${error.type} ${error.detail})`
24
27
  })
25
28
 
29
+ const __dirname = fileURLToPath(new URL('.', import.meta.url))
26
30
  /**
27
31
  * Abstract base request class for carrying out signed ACME requests over HTTPS.
28
32
  *
@@ -34,6 +38,8 @@ export default class AcmeRequest {
34
38
  static accountIdentity = null
35
39
  static nonce = null
36
40
  static __account = null
41
+ /** @type {string} */
42
+ static autoEncryptVersion = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'))).version
37
43
 
38
44
  static initialise (directory = throws.ifMissing(), accountIdentity = throws.ifMissing()) {
39
45
  this.directory = directory
@@ -228,7 +234,7 @@ export default class AcmeRequest {
228
234
 
229
235
  const httpsHeaders = {
230
236
  'Content-Type': 'application/jose+json',
231
- 'User-Agent': 'small-tech.org-acme/1.0.0 node/12.16.0',
237
+ 'User-Agent': `small-tech.org-auto-encrypt/${AcmeRequest.autoEncryptVersion}`,
232
238
  'Accept-Language': 'en-US'
233
239
  }
234
240
 
@@ -369,7 +369,7 @@ export default class Certificate {
369
369
  const issuer = certificate.issuer.value[0][0].value.toString('utf-8').slice(2).trim()
370
370
  const issuedAt = new Date(certificate.validity.notBefore.value)
371
371
  const expiresAt = new Date(certificate.validity.notAfter.value)
372
- const subject = certificate.subject.value[0][0].value.toString('utf-8').slice(2).trim()
372
+ const subject = certificate.subject.value.length > 0 ? certificate.subject.value[0][0].value.toString('utf-8').slice(2).trim() : '(No subject)'
373
373
 
374
374
  const alternativeNames = ((certificate.extensions.filter(extension => {
375
375
  return extension.extnID === 'subjectAlternativeName'
package/lib/Order.js CHANGED
@@ -31,6 +31,7 @@ const throws = new Throws()
31
31
  export default class Order {
32
32
  #data = null
33
33
  #headers = null
34
+ #location = null
34
35
  #order = null
35
36
  #certificate = null
36
37
  #certificateIdentity = null
@@ -65,6 +66,10 @@ export default class Order {
65
66
  get data () { return this.#data }
66
67
  set data (value) {
67
68
  this.#data = value
69
+ // It seems that Let’s Encrypt are no longer sending the location
70
+ // back on status checks, only on order finalisation, so let’s\
71
+ // make sure we don’t accidentally.
72
+ if (this.#data.headers.location !== undefined) this.#location = this.#data.headers.location
68
73
  this.#headers = this.#data.headers
69
74
  this.#order = this.#data.body
70
75
  }
@@ -168,10 +173,12 @@ export default class Order {
168
173
  try {
169
174
  if (numAttempts === 1) {
170
175
  // Finalise using CSR.
176
+ log(' 📝 ❨auto-encrypt❩ Finalising using CSR.')
171
177
  this.data = await (new FinaliseOrderRequest()).execute(this.finaliseUrl, csr)
172
178
  } else {
173
179
  // Check for order status.
174
- this.data = await (new CheckOrderStatusRequest()).execute(this.#headers.location)
180
+ log(' 👀 ❨auto-encrypt❩ Checking for order status.')
181
+ this.data = await (new CheckOrderStatusRequest()).execute(this.#location)
175
182
  }
176
183
  } catch (error) {
177
184
  // TODO: Handle error.
package/lib/acmeCsr.js CHANGED
@@ -53,12 +53,6 @@ function csrAsPem (domains, key) {
53
53
  extensions: [{
54
54
  name: 'subjectAltName',
55
55
  altNames
56
- }, {
57
- // OCSP Must Staple
58
- // RFC 7633. Also see https://scotthelme.co.uk/ocsp-must-staple/
59
- // 1.3.6.1.5.5.7.1.24 = DER(3003020105) (Sequence > Int > 5) *smh* ASN.1 is devil spawn.
60
- id: '1.3.6.1.5.5.7.1.24',
61
- value: forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [ forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.INTEGER, false, forge.asn1.integerToDer(5).getBytes())])
62
56
  }]
63
57
  }])
64
58
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@small-tech/auto-encrypt",
3
- "version": "4.0.0",
4
- "description": "Adds automatic provisioning and renewal of Let’s Encrypt TLS certificates with OCSP Stapling to Node.js https servers (including Express.js, etc.)",
3
+ "version": "4.1.1",
4
+ "description": "Automatically provisions and renews Let’s Encrypt TLS certificates on Node.js https servers (including Kitten, Polka, Express.js, etc.)",
5
5
  "engines": {
6
6
  "node": ">=18.2.0"
7
7
  },
@@ -68,12 +68,11 @@
68
68
  "encodeurl": "^1.0.2",
69
69
  "jose": "^1.28.2",
70
70
  "moment": "^2.29.4",
71
- "node-forge": "^1.3.1",
72
- "ocsp": "^1.2.0"
71
+ "node-forge": "^1.3.1"
73
72
  },
74
73
  "devDependencies": {
75
74
  "@small-tech/esm-tape-runner": "^1.0.3",
76
- "@small-tech/node-pebble": "^4.2.4",
75
+ "@small-tech/node-pebble": "^5.1.3",
77
76
  "@small-tech/tap-monkey": "^1.3.0",
78
77
  "c8": "^7.12.0",
79
78
  "dependency-cruiser": "^12.3.0",