@small-tech/auto-encrypt 2.1.0 → 2.3.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 +10 -8
- package/index.js +37 -34
- package/lib/Account.js +6 -4
- package/lib/AcmeRequest.js +32 -29
- package/lib/Authorisation.js +9 -7
- package/lib/Certificate.js +26 -24
- package/lib/Configuration.js +11 -16
- package/lib/Directory.js +26 -24
- package/lib/HttpServer.js +7 -5
- package/lib/Identity.js +8 -6
- package/lib/LetsEncryptServer.js +4 -2
- package/lib/Nonce.js +6 -4
- package/lib/Order.js +17 -15
- package/lib/acme-requests/AuthorisationRequest.js +5 -3
- package/lib/acme-requests/CertificateRequest.js +5 -3
- package/lib/acme-requests/CheckOrderStatusRequest.js +5 -3
- package/lib/acme-requests/FinaliseOrderRequest.js +5 -3
- package/lib/acme-requests/NewAccountRequest.js +4 -2
- package/lib/acme-requests/NewOrderRequest.js +5 -3
- package/lib/acme-requests/ReadyForChallengeValidationRequest.js +5 -3
- package/lib/acmeCsr.js +3 -3
- package/lib/identities/AccountIdentity.js +5 -3
- package/lib/identities/CertificateIdentity.js +5 -3
- package/lib/staging/fakelerootx1.pem +30 -27
- package/lib/staging/monkeyPatchTls.js +6 -6
- package/lib/test-helpers/index.js +39 -31
- package/lib/util/Pluralise.js +3 -1
- package/lib/util/Throws.js +7 -3
- package/lib/util/async-foreach.js +3 -1
- package/lib/util/log.js +3 -1
- package/lib/util/waitFor.js +3 -1
- package/lib/x.509/rfc5280.js +171 -84
- package/package.json +20 -21
- package/typedefs/lib/AcmeRequest.js +1 -1
- package/CHANGELOG +0 -99
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
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.)
|
|
4
4
|
|
|
5
|
+
__Note:__ this is the CommonJS (CJS) branch of Auto Encrypt. Please see the main branch for the ECMAScript Modules (ESM) version. Security updates are backported to this branch.
|
|
6
|
+
|
|
5
7
|
## How it works
|
|
6
8
|
|
|
7
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.
|
|
@@ -11,7 +13,7 @@ When not provisioning certificates, Auto Encrypt will also forward HTTP calls to
|
|
|
11
13
|
## Installation
|
|
12
14
|
|
|
13
15
|
```sh
|
|
14
|
-
npm i @small-tech/auto-encrypt
|
|
16
|
+
npm i @small-tech/auto-encrypt@cjs
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
## Usage
|
|
@@ -21,7 +23,7 @@ npm i @small-tech/auto-encrypt
|
|
|
21
23
|
1. Import the module:
|
|
22
24
|
|
|
23
25
|
```js
|
|
24
|
-
|
|
26
|
+
const AutoEncrypt = require('@small-tech/auto-encrypt')
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
2. Prefix your server creation code with a reference to the Auto Encrypt class:
|
|
@@ -45,7 +47,7 @@ The following code creates an HTTPS server running on port 443 with [OCSP Stapli
|
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
```js
|
|
48
|
-
|
|
50
|
+
const AutoEncrypt = require('@small-tech/auto-encrypt')
|
|
49
51
|
|
|
50
52
|
const server = AutoEncrypt.https.createServer((request, response) => {
|
|
51
53
|
response.end('Hello, world')
|
|
@@ -73,7 +75,7 @@ You can specify the domains you want the certificate to support, whether the Let
|
|
|
73
75
|
### Example
|
|
74
76
|
|
|
75
77
|
```js
|
|
76
|
-
|
|
78
|
+
const AutoEncrypt = require('@small-tech/auto-encrypt')
|
|
77
79
|
|
|
78
80
|
const options = {
|
|
79
81
|
// Regular HTTPS server and TLS server options, if any, go here.
|
|
@@ -111,7 +113,7 @@ If you want to help improve Auto Encrypt or better understand how it is structur
|
|
|
111
113
|
### Regular https
|
|
112
114
|
|
|
113
115
|
```js
|
|
114
|
-
|
|
116
|
+
const AutoEncrypt = require('@small-tech/auto-encrypt')
|
|
115
117
|
|
|
116
118
|
const server = AutoEncrypt.https.createServer({ domains: ['dev.ar.al'] }, (request, response) => {
|
|
117
119
|
response.end('Hello, world!')
|
|
@@ -132,7 +134,7 @@ server.close(() => {
|
|
|
132
134
|
|
|
133
135
|
```js
|
|
134
136
|
const express = require('express')
|
|
135
|
-
|
|
137
|
+
const AutoEncrypt = require('@small-tech/auto-encrypt')
|
|
136
138
|
|
|
137
139
|
const app = express()
|
|
138
140
|
app.get('/', (request, response) => {
|
|
@@ -219,7 +221,7 @@ A complete [small technology](https://small-tech.org/about/#small-technology) to
|
|
|
219
221
|
|
|
220
222
|
## Tests and coverage
|
|
221
223
|
|
|
222
|
-
This project aims for > 80% coverage. At a recent check, coverage was at
|
|
224
|
+
This project aims for > 80% coverage. At a recent check, coverage was at 95.29% (statements), 82.69% (branch), 95.19% (functions), 95.68% (lines).
|
|
223
225
|
|
|
224
226
|
To see the current state of code coverage, run `npm run coverage`.
|
|
225
227
|
|
|
@@ -266,7 +268,7 @@ We exist in part thanks to patronage by people like you. If you share [our visio
|
|
|
266
268
|
|
|
267
269
|
## Copyright
|
|
268
270
|
|
|
269
|
-
© 2020-
|
|
271
|
+
© 2020 - present [Aral Balkan](https://ar.al), [Small Technology Foundation](https://small-tech.org).
|
|
270
272
|
|
|
271
273
|
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.
|
|
272
274
|
|
package/index.js
CHANGED
|
@@ -11,18 +11,19 @@
|
|
|
11
11
|
* @copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
12
12
|
* @license AGPLv3 or later.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
|
|
15
|
+
const os = require('os')
|
|
16
|
+
const util = require('util')
|
|
17
|
+
const https = require('https')
|
|
18
|
+
const ocsp = require('ocsp')
|
|
19
|
+
const monkeyPatchTls = require('./lib/staging/monkeyPatchTls')
|
|
20
|
+
const LetsEncryptServer = require('./lib/LetsEncryptServer')
|
|
21
|
+
const Configuration = require('./lib/Configuration')
|
|
22
|
+
const Certificate = require('./lib/Certificate')
|
|
23
|
+
const Pluralise = require('./lib/util/Pluralise')
|
|
24
|
+
const Throws = require('./lib/util/Throws')
|
|
25
|
+
const HttpServer = require('./lib/HttpServer')
|
|
26
|
+
const log = require('./lib/util/log')
|
|
26
27
|
|
|
27
28
|
// Custom errors thrown by the autoEncrypt function.
|
|
28
29
|
const throws = new Throws({
|
|
@@ -43,13 +44,13 @@ const throws = new Throws({
|
|
|
43
44
|
* @alias module:@small-tech/auto-encrypt
|
|
44
45
|
* @hideconstructor
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
-
static letsEncryptServer = null
|
|
48
|
-
static defaultDomains = null
|
|
49
|
-
static domains = null
|
|
50
|
-
static settingsPath = null
|
|
51
|
-
static listener = null
|
|
52
|
-
static certificate = null
|
|
47
|
+
class AutoEncrypt {
|
|
48
|
+
static #letsEncryptServer = null
|
|
49
|
+
static #defaultDomains = null
|
|
50
|
+
static #domains = null
|
|
51
|
+
static #settingsPath = null
|
|
52
|
+
static #listener = null
|
|
53
|
+
static #certificate = null
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Enumeration.
|
|
@@ -65,7 +66,7 @@ export default class AutoEncrypt {
|
|
|
65
66
|
* people to add AutoEncrypt to their existing apps by requiring the module
|
|
66
67
|
* and prefixing their https.createServer(…) line with AutoEncrypt:
|
|
67
68
|
*
|
|
68
|
-
* @example
|
|
69
|
+
* @example const AutoEncrypt = require('@small-tech/auto-encrypt')
|
|
69
70
|
* const server = AutoEncrypt.https.createServer()
|
|
70
71
|
*
|
|
71
72
|
* @static
|
|
@@ -73,7 +74,7 @@ export default class AutoEncrypt {
|
|
|
73
74
|
static get https () { return AutoEncrypt }
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
static ocspCache = null
|
|
77
|
+
static #ocspCache = null
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
80
|
* Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js
|
|
@@ -143,12 +144,12 @@ export default class AutoEncrypt {
|
|
|
143
144
|
const configuration = new Configuration({ settingsPath, domains, server: letsEncryptServer})
|
|
144
145
|
const certificate = new Certificate(configuration)
|
|
145
146
|
|
|
146
|
-
this
|
|
147
|
-
this
|
|
148
|
-
this
|
|
149
|
-
this
|
|
150
|
-
this
|
|
151
|
-
this
|
|
147
|
+
this.#letsEncryptServer = letsEncryptServer
|
|
148
|
+
this.#defaultDomains = defaultDomains
|
|
149
|
+
this.#domains = domains
|
|
150
|
+
this.#settingsPath = settingsPath
|
|
151
|
+
this.#listener = listener
|
|
152
|
+
this.#certificate = certificate
|
|
152
153
|
|
|
153
154
|
function sniError (symbolName, callback, emoji, ...args) {
|
|
154
155
|
const error = Symbol.for(symbolName)
|
|
@@ -226,7 +227,7 @@ export default class AutoEncrypt {
|
|
|
226
227
|
*/
|
|
227
228
|
static shutdown () {
|
|
228
229
|
this.clearOcspCacheTimers()
|
|
229
|
-
this
|
|
230
|
+
this.#certificate.stopCheckingForRenewal()
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
//
|
|
@@ -254,7 +255,7 @@ export default class AutoEncrypt {
|
|
|
254
255
|
// By turning on OCSP Stapling, you can improve the performance of your website, provide better privacy protections
|
|
255
256
|
// … and help Let’s Encrypt efficiently serve as many people as possible.
|
|
256
257
|
//
|
|
257
|
-
// (Source: https://letsencrypt.org/docs/integration-guide
|
|
258
|
+
// (Source: https://letsencrypt.org/docs/integration-guide/#implement-ocsp-stapling)
|
|
258
259
|
|
|
259
260
|
this.ocspCache = new ocsp.Cache()
|
|
260
261
|
const cache = this.ocspCache
|
|
@@ -294,12 +295,12 @@ export default class AutoEncrypt {
|
|
|
294
295
|
// Custom object description for console output (for debugging).
|
|
295
296
|
static [util.inspect.custom] () {
|
|
296
297
|
return `
|
|
297
|
-
|
|
298
|
+
# AutoEncrypt (static class)
|
|
298
299
|
|
|
299
|
-
- Using Let’s Encrypt ${this
|
|
300
|
-
- Managing TLS for ${this
|
|
301
|
-
- Settings stored at ${this
|
|
302
|
-
- Listener ${typeof this
|
|
300
|
+
- Using Let’s Encrypt ${this.#letsEncryptServer.name} server.
|
|
301
|
+
- Managing TLS for ${this.#domains.toString().replace(',', ', ')}${this.#domains === this.#defaultDomains ? ' (default domains)' : ''}.
|
|
302
|
+
- Settings stored at ${this.#settingsPath === null ? 'default settings path' : this.#settingsPath}.
|
|
303
|
+
- Listener ${typeof this.#listener === 'function' ? 'is set' : 'not set'}.
|
|
303
304
|
`
|
|
304
305
|
}
|
|
305
306
|
|
|
@@ -307,3 +308,5 @@ export default class AutoEncrypt {
|
|
|
307
308
|
throws.error(Symbol.for('StaticClassCannotBeInstantiatedError'))
|
|
308
309
|
}
|
|
309
310
|
}
|
|
311
|
+
|
|
312
|
+
module.exports = AutoEncrypt
|
package/lib/Account.js
CHANGED
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
//
|
|
15
15
|
////////////////////////////////////////////////////////////////////////////////
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const fs = require('fs-extra')
|
|
18
|
+
const Throws = require('./util/Throws')
|
|
19
|
+
const NewAccountRequest = require('./acme-requests/NewAccountRequest')
|
|
20
20
|
|
|
21
21
|
const throws = new Throws({
|
|
22
22
|
// No custom errors are thrown by this class.
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
class Account {
|
|
26
26
|
//
|
|
27
27
|
// Async factory method.
|
|
28
28
|
//
|
|
@@ -65,3 +65,5 @@ export default class Account {
|
|
|
65
65
|
get kid () { return this.data.kid }
|
|
66
66
|
set kid (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'kid') }
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
module.exports = Account
|
package/lib/AcmeRequest.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* @license AGPLv3 or later.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const jose = require('jose')
|
|
10
|
+
const prepareRequest = require('bent')
|
|
11
|
+
const types = require('../typedefs/lib/AcmeRequest')
|
|
12
|
+
const Nonce = require('./Nonce')
|
|
13
|
+
const Throws = require('./util/Throws')
|
|
14
|
+
const log = require('./util/log')
|
|
15
15
|
|
|
16
16
|
const throws = new Throws({
|
|
17
17
|
[Symbol.for('AcmeRequest.classNotInitialisedError')]:
|
|
@@ -28,33 +28,33 @@ const throws = new Throws({
|
|
|
28
28
|
*
|
|
29
29
|
* @alias module:lib/AcmeRequest
|
|
30
30
|
*/
|
|
31
|
-
|
|
32
|
-
static initialised = false
|
|
33
|
-
static directory = null
|
|
34
|
-
static accountIdentity = null
|
|
35
|
-
static nonce = null
|
|
36
|
-
static
|
|
31
|
+
class AcmeRequest {
|
|
32
|
+
static #initialised = false
|
|
33
|
+
static #directory = null
|
|
34
|
+
static #accountIdentity = null
|
|
35
|
+
static #nonce = null
|
|
36
|
+
static #account = null
|
|
37
37
|
|
|
38
38
|
static initialise (directory = throws.ifMissing(), accountIdentity = throws.ifMissing()) {
|
|
39
|
-
this
|
|
40
|
-
this
|
|
41
|
-
this
|
|
42
|
-
this
|
|
39
|
+
this.#directory = directory
|
|
40
|
+
this.#accountIdentity = accountIdentity
|
|
41
|
+
this.#nonce = new Nonce(directory)
|
|
42
|
+
this.#initialised = true
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
static uninitialise () {
|
|
46
|
-
this
|
|
47
|
-
this
|
|
48
|
-
this
|
|
49
|
-
this
|
|
50
|
-
this
|
|
46
|
+
this.#directory = null
|
|
47
|
+
this.#accountIdentity = null
|
|
48
|
+
this.#nonce = null
|
|
49
|
+
this.#account = null
|
|
50
|
+
this.#initialised = false
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
static set account (_account = throws.ifMissing()) { this
|
|
54
|
-
static get account () { return this
|
|
53
|
+
static set account (_account = throws.ifMissing()) { this.#account = _account }
|
|
54
|
+
static get account () { return this.#account }
|
|
55
55
|
|
|
56
56
|
constructor () {
|
|
57
|
-
if (!AcmeRequest
|
|
57
|
+
if (!AcmeRequest.#initialised) {
|
|
58
58
|
throws.error(Symbol.for('AcmeRequest.classNotInitialisedError'))
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -145,7 +145,7 @@ export default class AcmeRequest {
|
|
|
145
145
|
|
|
146
146
|
// Always save the fresh nonce returned from API calls.
|
|
147
147
|
const freshNonce = response.headers['replay-nonce']
|
|
148
|
-
AcmeRequest
|
|
148
|
+
AcmeRequest.#nonce.set(freshNonce)
|
|
149
149
|
|
|
150
150
|
// The response returned is the raw response object. Let’s consume
|
|
151
151
|
// it and return a more relevant response.
|
|
@@ -208,11 +208,11 @@ export default class AcmeRequest {
|
|
|
208
208
|
// ===== the arguments array as the latter does not reflect default parameters.
|
|
209
209
|
const originalRequestDetails = [command, payload, useKid, successCodes, url, nonce]
|
|
210
210
|
|
|
211
|
-
url = url || AcmeRequest
|
|
211
|
+
url = url || AcmeRequest.#directory[`${command}Url`]
|
|
212
212
|
|
|
213
213
|
const protectedHeader = {
|
|
214
214
|
alg: 'RS256',
|
|
215
|
-
nonce: nonce || await AcmeRequest
|
|
215
|
+
nonce: nonce || await AcmeRequest.#nonce.get(),
|
|
216
216
|
url
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -221,10 +221,10 @@ export default class AcmeRequest {
|
|
|
221
221
|
protectedHeader.kid = AcmeRequest.account.kid
|
|
222
222
|
} else {
|
|
223
223
|
// If we’re not using the kid, we must use the public JWK (see RFC 8555 § 6.2 Request Authentication)
|
|
224
|
-
protectedHeader.jwk = AcmeRequest
|
|
224
|
+
protectedHeader.jwk = AcmeRequest.#accountIdentity.publicJWK
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
const signedRequest = jose.JWS.sign.flattened(payload, AcmeRequest
|
|
227
|
+
const signedRequest = jose.JWS.sign.flattened(payload, AcmeRequest.#accountIdentity.key, protectedHeader)
|
|
228
228
|
|
|
229
229
|
const httpsHeaders = {
|
|
230
230
|
'Content-Type': 'application/jose+json',
|
|
@@ -244,3 +244,6 @@ export default class AcmeRequest {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
+
|
|
248
|
+
module.exports = AcmeRequest
|
|
249
|
+
|
package/lib/Authorisation.js
CHANGED
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
//
|
|
14
14
|
////////////////////////////////////////////////////////////////////////////////
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
const EventEmitter = require('events')
|
|
17
|
+
const log = require('./util/log')
|
|
18
|
+
const AuthorisationRequest = require('./acme-requests/AuthorisationRequest')
|
|
19
|
+
const ReadyForChallengeValidationRequest = require('./acme-requests/ReadyForChallengeValidationRequest')
|
|
20
|
+
const HttpServer = require('./HttpServer')
|
|
21
|
+
const waitFor = require('./util/waitFor')
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
class Authorisation extends EventEmitter {
|
|
24
24
|
|
|
25
25
|
// Async factory method. Use this to instantiate.
|
|
26
26
|
// TODO: add check to ensure factory method is used.
|
|
@@ -181,3 +181,5 @@ export default class Authorisation extends EventEmitter {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
|
|
185
|
+
module.exports = Authorisation
|
package/lib/Certificate.js
CHANGED
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|
* @license AGPLv3 or later.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
const fs = require('fs-extra')
|
|
10
|
+
const tls = require('tls')
|
|
11
|
+
const util = require('util')
|
|
12
|
+
const moment = require('moment')
|
|
13
|
+
const log = require('./util/log')
|
|
14
|
+
const x509 = require('./x.509/rfc5280')
|
|
15
|
+
const Account = require('./Account')
|
|
16
|
+
const AccountIdentity = require('./identities/AccountIdentity')
|
|
17
|
+
const Directory = require('./Directory')
|
|
18
|
+
const Order = require('./Order')
|
|
19
|
+
const CertificateIdentity = require('./identities/CertificateIdentity')
|
|
20
|
+
const AcmeRequest = require('./AcmeRequest')
|
|
21
|
+
const Throws = require('./util/Throws')
|
|
22
22
|
|
|
23
23
|
const throws = new Throws({
|
|
24
24
|
// No custom errors are thrown by this class.
|
|
@@ -30,7 +30,7 @@ const throws = new Throws({
|
|
|
30
30
|
* @alias module:lib/Certificate
|
|
31
31
|
* @param {String[]} domains List of domains this certificate covers.
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
class Certificate {
|
|
34
34
|
/**
|
|
35
35
|
* Get a SecureContext that can be used in an SNICallback.
|
|
36
36
|
*
|
|
@@ -174,13 +174,13 @@ export default class Certificate {
|
|
|
174
174
|
// written but before we had a chance to clean up the old files.)
|
|
175
175
|
if (fs.existsSync(certificateIdentityPath) && fs.existsSync(certificatePath)) {
|
|
176
176
|
log(' 🚑 ❨auto-encrypt❩ A new certificate was also found. Going to delete the old one and use that.')
|
|
177
|
-
fs.
|
|
178
|
-
fs.
|
|
177
|
+
fs.removeSync(oldCertificateIdentityPath)
|
|
178
|
+
fs.removeSync(oldCertificatePath)
|
|
179
179
|
} else {
|
|
180
180
|
// The renewal process must have failed. Delete any previous state and restore the old certificate.
|
|
181
181
|
log(' 🚑 ❨auto-encrypt❩ Cleaning up previous state and restoring old certificate…')
|
|
182
|
-
fs.
|
|
183
|
-
fs.
|
|
182
|
+
fs.removeSync(certificateIdentityPath)
|
|
183
|
+
fs.removeSync(certificatePath)
|
|
184
184
|
fs.renameSync(oldCertificateIdentityPath, certificateIdentityPath)
|
|
185
185
|
fs.renameSync(oldCertificatePath, certificatePath)
|
|
186
186
|
}
|
|
@@ -278,15 +278,15 @@ export default class Certificate {
|
|
|
278
278
|
|
|
279
279
|
//
|
|
280
280
|
// In case old files were left behind, remove them first and then rename the current files.
|
|
281
|
-
// (If the directory doesn’t exist, will silently do nothing.)
|
|
281
|
+
// (If the directory doesn’t exist, fs.removeSync() will silently do nothing.)
|
|
282
282
|
//
|
|
283
283
|
const certificateIdentityPath = this.#configuration.certificateIdentityPath
|
|
284
284
|
const oldCertificateIdentityPath = `${certificateIdentityPath}.old`
|
|
285
285
|
const certificatePath = this.#configuration.certificatePath
|
|
286
286
|
const oldCertificatePath = `${certificatePath}.old`
|
|
287
287
|
|
|
288
|
-
fs.
|
|
289
|
-
fs.
|
|
288
|
+
fs.removeSync(oldCertificateIdentityPath)
|
|
289
|
+
fs.removeSync(oldCertificatePath)
|
|
290
290
|
fs.renameSync(certificateIdentityPath, oldCertificateIdentityPath)
|
|
291
291
|
fs.renameSync(certificatePath, oldCertificatePath)
|
|
292
292
|
|
|
@@ -296,8 +296,8 @@ export default class Certificate {
|
|
|
296
296
|
await this.createSecureContext(/* renewCertificate = */ true)
|
|
297
297
|
|
|
298
298
|
// Delete the backup of the old certificate.
|
|
299
|
-
fs.
|
|
300
|
-
fs.
|
|
299
|
+
fs.removeSync(oldCertificateIdentityPath)
|
|
300
|
+
fs.removeSync(oldCertificatePath)
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
|
|
@@ -367,7 +367,7 @@ export default class Certificate {
|
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
parseDetails (certificatePem) {
|
|
370
|
-
const certificate = (
|
|
370
|
+
const certificate = (x509.Certificate.decode(certificatePem, 'pem', {label: 'CERTIFICATE'})).tbsCertificate
|
|
371
371
|
|
|
372
372
|
const serialNumber = certificate.serialNumber
|
|
373
373
|
const issuer = certificate.issuer.value[0][0].value.toString('utf-8').slice(2).trim()
|
|
@@ -417,3 +417,5 @@ export default class Certificate {
|
|
|
417
417
|
`
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
|
+
|
|
421
|
+
module.exports = Certificate
|
package/lib/Configuration.js
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* @license AGPLv3 or later.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
const os = require('os')
|
|
11
|
+
const fs = require('fs-extra')
|
|
12
|
+
const path = require('path')
|
|
13
|
+
const util = require('util')
|
|
14
|
+
const crypto = require('crypto')
|
|
15
|
+
const log = require('./util/log')
|
|
16
|
+
const Throws = require('./util/Throws')
|
|
17
17
|
|
|
18
18
|
// Custom errors thrown by this class.
|
|
19
19
|
const throws = new Throws({
|
|
@@ -26,17 +26,11 @@ function isAnArrayOfStrings (object) {
|
|
|
26
26
|
return Array.isArray(object) && containsOnlyStrings(object)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function ensureDirSync (directory) {
|
|
30
|
-
if (!fs.existsSync(directory)) {
|
|
31
|
-
fs.mkdirSync(directory, { recursive: true })
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
29
|
/**
|
|
36
30
|
* @alias module:lib/Configuration
|
|
37
31
|
* @hideconstructor
|
|
38
32
|
*/
|
|
39
|
-
|
|
33
|
+
class Configuration {
|
|
40
34
|
#server = null
|
|
41
35
|
#domains = null
|
|
42
36
|
#settingsPath = null
|
|
@@ -79,7 +73,7 @@ export default class Configuration {
|
|
|
79
73
|
}
|
|
80
74
|
|
|
81
75
|
// And ensure that the settings path exists in the file system.
|
|
82
|
-
ensureDirSync(this.#settingsPath)
|
|
76
|
+
fs.ensureDirSync(this.#settingsPath)
|
|
83
77
|
|
|
84
78
|
//
|
|
85
79
|
// Create account paths.
|
|
@@ -112,7 +106,7 @@ export default class Configuration {
|
|
|
112
106
|
this.#certificateDirectoryPath = path.join(this.#settingsPath, certificateDirectoryName)
|
|
113
107
|
|
|
114
108
|
// And ensure that the certificate directory path exists in the file system.
|
|
115
|
-
ensureDirSync(this.#certificateDirectoryPath)
|
|
109
|
+
fs.ensureDirSync(this.#certificateDirectoryPath)
|
|
116
110
|
|
|
117
111
|
this.#certificatePath = path.join(this.#certificateDirectoryPath, 'certificate.pem')
|
|
118
112
|
this.#certificateIdentityPath = path.join(this.#certificateDirectoryPath, 'certificate-identity.pem')
|
|
@@ -236,3 +230,4 @@ export default class Configuration {
|
|
|
236
230
|
`
|
|
237
231
|
}
|
|
238
232
|
}
|
|
233
|
+
module.exports = Configuration
|
package/lib/Directory.js
CHANGED
|
@@ -11,25 +11,25 @@
|
|
|
11
11
|
//
|
|
12
12
|
////////////////////////////////////////////////////////////////////////////////
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const util = require('util')
|
|
15
|
+
const prepareRequest = require('bent')
|
|
16
|
+
const log = require('./util/log')
|
|
17
|
+
const Throws = require('./util/Throws')
|
|
18
18
|
|
|
19
19
|
const throws = new Throws()
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
directory = null
|
|
23
|
-
letsEncryptServer = null
|
|
24
|
-
directoryRequest = null
|
|
21
|
+
class Directory {
|
|
22
|
+
#directory = null
|
|
23
|
+
#letsEncryptServer = null
|
|
24
|
+
#directoryRequest = null
|
|
25
25
|
|
|
26
26
|
//
|
|
27
27
|
// Factory method access (async).
|
|
28
28
|
//
|
|
29
|
-
static isBeingInstantiatedViaAsyncFactoryMethod = false
|
|
29
|
+
static #isBeingInstantiatedViaAsyncFactoryMethod = false
|
|
30
30
|
|
|
31
31
|
static async getInstanceAsync (configuration = throws.ifMissing()) {
|
|
32
|
-
Directory
|
|
32
|
+
Directory.#isBeingInstantiatedViaAsyncFactoryMethod = true
|
|
33
33
|
const directory = new Directory(configuration)
|
|
34
34
|
await directory.getUrls()
|
|
35
35
|
return directory
|
|
@@ -40,13 +40,13 @@ export default class Directory {
|
|
|
40
40
|
//
|
|
41
41
|
|
|
42
42
|
// Directory URLs.
|
|
43
|
-
get keyChangeUrl() { return this
|
|
44
|
-
get newAccountUrl() { return this
|
|
45
|
-
get newNonceUrl() { return this
|
|
46
|
-
get newOrderUrl() { return this
|
|
47
|
-
get revokeCertUrl() { return this
|
|
48
|
-
get termsOfServiceUrl() { return this
|
|
49
|
-
get websiteUrl() { return this
|
|
43
|
+
get keyChangeUrl() { return this.#directory.keyChange }
|
|
44
|
+
get newAccountUrl() { return this.#directory.newAccount }
|
|
45
|
+
get newNonceUrl() { return this.#directory.newNonce }
|
|
46
|
+
get newOrderUrl() { return this.#directory.newOrder }
|
|
47
|
+
get revokeCertUrl() { return this.#directory.revokeCert }
|
|
48
|
+
get termsOfServiceUrl() { return this.#directory.meta.termsOfService }
|
|
49
|
+
get websiteUrl() { return this.#directory.meta.website }
|
|
50
50
|
|
|
51
51
|
//
|
|
52
52
|
// Private.
|
|
@@ -54,28 +54,28 @@ export default class Directory {
|
|
|
54
54
|
|
|
55
55
|
constructor(configuration) {
|
|
56
56
|
// Ensure async factory method instantiation.
|
|
57
|
-
if (Directory
|
|
57
|
+
if (Directory.#isBeingInstantiatedViaAsyncFactoryMethod === false) {
|
|
58
58
|
throws.error(Symbol.for('MustBeInstantiatedViaAsyncFactoryMethodError'), 'Directory')
|
|
59
59
|
}
|
|
60
|
-
Directory
|
|
60
|
+
Directory.#isBeingInstantiatedViaAsyncFactoryMethod = false
|
|
61
61
|
|
|
62
|
-
this
|
|
63
|
-
this
|
|
62
|
+
this.#letsEncryptServer = configuration.server
|
|
63
|
+
this.#directoryRequest = prepareRequest('GET', 'json', this.#letsEncryptServer.endpoint)
|
|
64
64
|
|
|
65
|
-
log(` 📕 ❨auto-encrypt❩ Directory is using endpoint ${this
|
|
65
|
+
log(` 📕 ❨auto-encrypt❩ Directory is using endpoint ${this.#letsEncryptServer.endpoint}`)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// (Async) Fetches the latest Urls from the Let’s Encrypt ACME endpoint being used.
|
|
69
69
|
// This will throw if the request fails. Ensure that you catch the error when
|
|
70
70
|
// using it.
|
|
71
|
-
async getUrls() { this
|
|
71
|
+
async getUrls() { this.#directory = await this.#directoryRequest() }
|
|
72
72
|
|
|
73
73
|
// Custom object description for console output (for debugging).
|
|
74
74
|
[util.inspect.custom] () {
|
|
75
75
|
return `
|
|
76
76
|
# Directory
|
|
77
77
|
|
|
78
|
-
Endpoint: ${this
|
|
78
|
+
Endpoint: ${this.#letsEncryptServer.endpoint}
|
|
79
79
|
|
|
80
80
|
## URLs:
|
|
81
81
|
|
|
@@ -89,3 +89,5 @@ export default class Directory {
|
|
|
89
89
|
`
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
module.exports = Directory
|