@small-tech/auto-encrypt 2.3.0 → 3.1.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 +8 -10
- package/index.js +34 -37
- package/lib/Account.js +4 -6
- package/lib/AcmeRequest.js +29 -32
- package/lib/Authorisation.js +7 -9
- package/lib/Certificate.js +20 -26
- package/lib/Configuration.js +16 -11
- package/lib/Directory.js +24 -26
- package/lib/HttpServer.js +5 -7
- package/lib/Identity.js +6 -8
- package/lib/LetsEncryptServer.js +2 -4
- package/lib/Nonce.js +4 -6
- package/lib/Order.js +15 -17
- package/lib/acme-requests/AuthorisationRequest.js +3 -5
- package/lib/acme-requests/CertificateRequest.js +3 -5
- package/lib/acme-requests/CheckOrderStatusRequest.js +3 -5
- package/lib/acme-requests/FinaliseOrderRequest.js +3 -5
- package/lib/acme-requests/NewAccountRequest.js +2 -4
- package/lib/acme-requests/NewOrderRequest.js +3 -5
- package/lib/acme-requests/ReadyForChallengeValidationRequest.js +3 -5
- package/lib/acmeCsr.js +2 -2
- package/lib/identities/AccountIdentity.js +3 -5
- package/lib/identities/CertificateIdentity.js +3 -5
- package/lib/staging/monkeyPatchTls.js +7 -6
- package/lib/test-helpers/index.js +31 -39
- package/lib/util/Pluralise.js +1 -3
- package/lib/util/Throws.js +3 -7
- package/lib/util/async-foreach.js +1 -3
- package/lib/util/log.js +1 -3
- package/lib/util/waitFor.js +1 -3
- package/lib/x.509/rfc5280.js +84 -171
- package/package.json +20 -19
- package/typedefs/lib/AcmeRequest.js +1 -1
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
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
|
-
|
|
7
5
|
## How it works
|
|
8
6
|
|
|
9
7
|
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.
|
|
@@ -13,7 +11,7 @@ When not provisioning certificates, Auto Encrypt will also forward HTTP calls to
|
|
|
13
11
|
## Installation
|
|
14
12
|
|
|
15
13
|
```sh
|
|
16
|
-
npm i @small-tech/auto-encrypt
|
|
14
|
+
npm i @small-tech/auto-encrypt
|
|
17
15
|
```
|
|
18
16
|
|
|
19
17
|
## Usage
|
|
@@ -23,7 +21,7 @@ npm i @small-tech/auto-encrypt@cjs
|
|
|
23
21
|
1. Import the module:
|
|
24
22
|
|
|
25
23
|
```js
|
|
26
|
-
|
|
24
|
+
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
27
25
|
```
|
|
28
26
|
|
|
29
27
|
2. Prefix your server creation code with a reference to the Auto Encrypt class:
|
|
@@ -47,7 +45,7 @@ The following code creates an HTTPS server running on port 443 with [OCSP Stapli
|
|
|
47
45
|
|
|
48
46
|
|
|
49
47
|
```js
|
|
50
|
-
|
|
48
|
+
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
51
49
|
|
|
52
50
|
const server = AutoEncrypt.https.createServer((request, response) => {
|
|
53
51
|
response.end('Hello, world')
|
|
@@ -75,7 +73,7 @@ You can specify the domains you want the certificate to support, whether the Let
|
|
|
75
73
|
### Example
|
|
76
74
|
|
|
77
75
|
```js
|
|
78
|
-
|
|
76
|
+
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
79
77
|
|
|
80
78
|
const options = {
|
|
81
79
|
// Regular HTTPS server and TLS server options, if any, go here.
|
|
@@ -113,7 +111,7 @@ If you want to help improve Auto Encrypt or better understand how it is structur
|
|
|
113
111
|
### Regular https
|
|
114
112
|
|
|
115
113
|
```js
|
|
116
|
-
|
|
114
|
+
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
117
115
|
|
|
118
116
|
const server = AutoEncrypt.https.createServer({ domains: ['dev.ar.al'] }, (request, response) => {
|
|
119
117
|
response.end('Hello, world!')
|
|
@@ -134,7 +132,7 @@ server.close(() => {
|
|
|
134
132
|
|
|
135
133
|
```js
|
|
136
134
|
const express = require('express')
|
|
137
|
-
|
|
135
|
+
import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
138
136
|
|
|
139
137
|
const app = express()
|
|
140
138
|
app.get('/', (request, response) => {
|
|
@@ -221,7 +219,7 @@ A complete [small technology](https://small-tech.org/about/#small-technology) to
|
|
|
221
219
|
|
|
222
220
|
## Tests and coverage
|
|
223
221
|
|
|
224
|
-
This project aims for > 80% coverage. At a recent check, coverage was at
|
|
222
|
+
This project aims for > 80% coverage. At a recent check, coverage was at 97.42% (statements), 92.64% (branch), 91.49% (functions), 97.42% (lines).
|
|
225
223
|
|
|
226
224
|
To see the current state of code coverage, run `npm run coverage`.
|
|
227
225
|
|
|
@@ -268,7 +266,7 @@ We exist in part thanks to patronage by people like you. If you share [our visio
|
|
|
268
266
|
|
|
269
267
|
## Copyright
|
|
270
268
|
|
|
271
|
-
© 2020
|
|
269
|
+
© 2020-present [Aral Balkan](https://ar.al), [Small Technology Foundation](https://small-tech.org).
|
|
272
270
|
|
|
273
271
|
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.
|
|
274
272
|
|
package/index.js
CHANGED
|
@@ -11,19 +11,18 @@
|
|
|
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
|
-
|
|
26
|
-
const log = require('./lib/util/log')
|
|
14
|
+
import os from 'os'
|
|
15
|
+
import util from 'util'
|
|
16
|
+
import https from 'https'
|
|
17
|
+
import ocsp from 'ocsp'
|
|
18
|
+
import monkeyPatchTls from './lib/staging/monkeyPatchTls.js'
|
|
19
|
+
import LetsEncryptServer from './lib/LetsEncryptServer.js'
|
|
20
|
+
import Configuration from './lib/Configuration.js'
|
|
21
|
+
import Certificate from './lib/Certificate.js'
|
|
22
|
+
import Pluralise from './lib/util/Pluralise.js'
|
|
23
|
+
import Throws from './lib/util/Throws.js'
|
|
24
|
+
import HttpServer from './lib/HttpServer.js'
|
|
25
|
+
import log from './lib/util/log.js'
|
|
27
26
|
|
|
28
27
|
// Custom errors thrown by the autoEncrypt function.
|
|
29
28
|
const throws = new Throws({
|
|
@@ -44,13 +43,13 @@ const throws = new Throws({
|
|
|
44
43
|
* @alias module:@small-tech/auto-encrypt
|
|
45
44
|
* @hideconstructor
|
|
46
45
|
*/
|
|
47
|
-
class AutoEncrypt {
|
|
48
|
-
static
|
|
49
|
-
static
|
|
50
|
-
static
|
|
51
|
-
static
|
|
52
|
-
static
|
|
53
|
-
static
|
|
46
|
+
export default class AutoEncrypt {
|
|
47
|
+
static letsEncryptServer = null
|
|
48
|
+
static defaultDomains = null
|
|
49
|
+
static domains = null
|
|
50
|
+
static settingsPath = null
|
|
51
|
+
static listener = null
|
|
52
|
+
static certificate = null
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
55
|
* Enumeration.
|
|
@@ -66,7 +65,7 @@ class AutoEncrypt {
|
|
|
66
65
|
* people to add AutoEncrypt to their existing apps by requiring the module
|
|
67
66
|
* and prefixing their https.createServer(…) line with AutoEncrypt:
|
|
68
67
|
*
|
|
69
|
-
* @example
|
|
68
|
+
* @example import AutoEncrypt from '@small-tech/auto-encrypt'
|
|
70
69
|
* const server = AutoEncrypt.https.createServer()
|
|
71
70
|
*
|
|
72
71
|
* @static
|
|
@@ -74,7 +73,7 @@ class AutoEncrypt {
|
|
|
74
73
|
static get https () { return AutoEncrypt }
|
|
75
74
|
|
|
76
75
|
|
|
77
|
-
static
|
|
76
|
+
static ocspCache = null
|
|
78
77
|
|
|
79
78
|
/**
|
|
80
79
|
* Automatically manages Let’s Encrypt certificate provisioning and renewal for Node.js
|
|
@@ -144,12 +143,12 @@ class AutoEncrypt {
|
|
|
144
143
|
const configuration = new Configuration({ settingsPath, domains, server: letsEncryptServer})
|
|
145
144
|
const certificate = new Certificate(configuration)
|
|
146
145
|
|
|
147
|
-
this
|
|
148
|
-
this
|
|
149
|
-
this
|
|
150
|
-
this
|
|
151
|
-
this
|
|
152
|
-
this
|
|
146
|
+
this.letsEncryptServer = letsEncryptServer
|
|
147
|
+
this.defaultDomains = defaultDomains
|
|
148
|
+
this.domains = domains
|
|
149
|
+
this.settingsPath = settingsPath
|
|
150
|
+
this.listener = listener
|
|
151
|
+
this.certificate = certificate
|
|
153
152
|
|
|
154
153
|
function sniError (symbolName, callback, emoji, ...args) {
|
|
155
154
|
const error = Symbol.for(symbolName)
|
|
@@ -227,7 +226,7 @@ class AutoEncrypt {
|
|
|
227
226
|
*/
|
|
228
227
|
static shutdown () {
|
|
229
228
|
this.clearOcspCacheTimers()
|
|
230
|
-
this
|
|
229
|
+
this.certificate.stopCheckingForRenewal()
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
//
|
|
@@ -255,7 +254,7 @@ class AutoEncrypt {
|
|
|
255
254
|
// By turning on OCSP Stapling, you can improve the performance of your website, provide better privacy protections
|
|
256
255
|
// … and help Let’s Encrypt efficiently serve as many people as possible.
|
|
257
256
|
//
|
|
258
|
-
// (Source: https://letsencrypt.org/docs/integration-guide
|
|
257
|
+
// (Source: https://letsencrypt.org/docs/integration-guide/implement-ocsp-stapling)
|
|
259
258
|
|
|
260
259
|
this.ocspCache = new ocsp.Cache()
|
|
261
260
|
const cache = this.ocspCache
|
|
@@ -295,12 +294,12 @@ class AutoEncrypt {
|
|
|
295
294
|
// Custom object description for console output (for debugging).
|
|
296
295
|
static [util.inspect.custom] () {
|
|
297
296
|
return `
|
|
298
|
-
|
|
297
|
+
# AutoEncrypt (static class)
|
|
299
298
|
|
|
300
|
-
- Using Let’s Encrypt ${this
|
|
301
|
-
- Managing TLS for ${this
|
|
302
|
-
- Settings stored at ${this
|
|
303
|
-
- Listener ${typeof this
|
|
299
|
+
- Using Let’s Encrypt ${this.letsEncryptServer.name} server.
|
|
300
|
+
- Managing TLS for ${this.domains.toString().replace(',', ', ')}${this.domains === this.defaultDomains ? ' (default domains)' : ''}.
|
|
301
|
+
- Settings stored at ${this.settingsPath === null ? 'default settings path' : this.settingsPath}.
|
|
302
|
+
- Listener ${typeof this.listener === 'function' ? 'is set' : 'not set'}.
|
|
304
303
|
`
|
|
305
304
|
}
|
|
306
305
|
|
|
@@ -308,5 +307,3 @@ class AutoEncrypt {
|
|
|
308
307
|
throws.error(Symbol.for('StaticClassCannotBeInstantiatedError'))
|
|
309
308
|
}
|
|
310
309
|
}
|
|
311
|
-
|
|
312
|
-
module.exports = AutoEncrypt
|
package/lib/Account.js
CHANGED
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
//
|
|
15
15
|
////////////////////////////////////////////////////////////////////////////////
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
import fs from 'fs'
|
|
18
|
+
import Throws from './util/Throws.js'
|
|
19
|
+
import NewAccountRequest from './acme-requests/NewAccountRequest.js'
|
|
20
20
|
|
|
21
21
|
const throws = new Throws({
|
|
22
22
|
// No custom errors are thrown by this class.
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
class Account {
|
|
25
|
+
export default class Account {
|
|
26
26
|
//
|
|
27
27
|
// Async factory method.
|
|
28
28
|
//
|
|
@@ -65,5 +65,3 @@ 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
|
+
import jose from 'jose'
|
|
10
|
+
import prepareRequest from 'bent'
|
|
11
|
+
import types from '../typedefs/lib/AcmeRequest.js'
|
|
12
|
+
import Nonce from './Nonce.js'
|
|
13
|
+
import Throws from './util/Throws.js'
|
|
14
|
+
import log from './util/log.js'
|
|
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
|
-
class AcmeRequest {
|
|
32
|
-
static
|
|
33
|
-
static
|
|
34
|
-
static
|
|
35
|
-
static
|
|
36
|
-
static
|
|
31
|
+
export default 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 @@ 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 @@ 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 @@ 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,6 +244,3 @@ 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
|
+
import EventEmitter from 'events'
|
|
17
|
+
import log from './util/log.js'
|
|
18
|
+
import AuthorisationRequest from './acme-requests/AuthorisationRequest.js'
|
|
19
|
+
import ReadyForChallengeValidationRequest from './acme-requests/ReadyForChallengeValidationRequest.js'
|
|
20
|
+
import HttpServer from './HttpServer.js'
|
|
21
|
+
import waitFor from './util/waitFor.js'
|
|
22
22
|
|
|
23
|
-
class Authorisation extends EventEmitter {
|
|
23
|
+
export default 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,5 +181,3 @@ 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
|
+
import fs from 'fs'
|
|
10
|
+
import tls from 'tls'
|
|
11
|
+
import util from 'util'
|
|
12
|
+
import moment from 'moment'
|
|
13
|
+
import log from './util/log.js'
|
|
14
|
+
import { Certificate as X509Certificate } from './x.509/rfc5280.js'
|
|
15
|
+
import Account from './Account.js'
|
|
16
|
+
import AccountIdentity from './identities/AccountIdentity.js'
|
|
17
|
+
import Directory from './Directory.js'
|
|
18
|
+
import Order from './Order.js'
|
|
19
|
+
import CertificateIdentity from './identities/CertificateIdentity.js'
|
|
20
|
+
import AcmeRequest from './AcmeRequest.js'
|
|
21
|
+
import Throws from './util/Throws.js'
|
|
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
|
-
class Certificate {
|
|
33
|
+
export default class Certificate {
|
|
34
34
|
/**
|
|
35
35
|
* Get a SecureContext that can be used in an SNICallback.
|
|
36
36
|
*
|
|
@@ -174,13 +174,11 @@ 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.rmSync(oldCertificateIdentityPath)
|
|
178
|
+
fs.rmSync(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.removeSync(certificateIdentityPath)
|
|
183
|
-
fs.removeSync(certificatePath)
|
|
184
182
|
fs.renameSync(oldCertificateIdentityPath, certificateIdentityPath)
|
|
185
183
|
fs.renameSync(oldCertificatePath, certificatePath)
|
|
186
184
|
}
|
|
@@ -278,15 +276,13 @@ class Certificate {
|
|
|
278
276
|
|
|
279
277
|
//
|
|
280
278
|
// In case old files were left behind, remove them first and then rename the current files.
|
|
281
|
-
// (If the directory doesn’t exist,
|
|
279
|
+
// (If the directory doesn’t exist, will silently do nothing.)
|
|
282
280
|
//
|
|
283
281
|
const certificateIdentityPath = this.#configuration.certificateIdentityPath
|
|
284
282
|
const oldCertificateIdentityPath = `${certificateIdentityPath}.old`
|
|
285
283
|
const certificatePath = this.#configuration.certificatePath
|
|
286
284
|
const oldCertificatePath = `${certificatePath}.old`
|
|
287
285
|
|
|
288
|
-
fs.removeSync(oldCertificateIdentityPath)
|
|
289
|
-
fs.removeSync(oldCertificatePath)
|
|
290
286
|
fs.renameSync(certificateIdentityPath, oldCertificateIdentityPath)
|
|
291
287
|
fs.renameSync(certificatePath, oldCertificatePath)
|
|
292
288
|
|
|
@@ -296,8 +292,8 @@ class Certificate {
|
|
|
296
292
|
await this.createSecureContext(/* renewCertificate = */ true)
|
|
297
293
|
|
|
298
294
|
// Delete the backup of the old certificate.
|
|
299
|
-
fs.
|
|
300
|
-
fs.
|
|
295
|
+
fs.rmSync(oldCertificateIdentityPath)
|
|
296
|
+
fs.rmSync(oldCertificatePath)
|
|
301
297
|
}
|
|
302
298
|
|
|
303
299
|
|
|
@@ -367,7 +363,7 @@ class Certificate {
|
|
|
367
363
|
}
|
|
368
364
|
|
|
369
365
|
parseDetails (certificatePem) {
|
|
370
|
-
const certificate = (
|
|
366
|
+
const certificate = (X509Certificate.decode(certificatePem, 'pem', {label: 'CERTIFICATE'})).tbsCertificate
|
|
371
367
|
|
|
372
368
|
const serialNumber = certificate.serialNumber
|
|
373
369
|
const issuer = certificate.issuer.value[0][0].value.toString('utf-8').slice(2).trim()
|
|
@@ -417,5 +413,3 @@ class Certificate {
|
|
|
417
413
|
`
|
|
418
414
|
}
|
|
419
415
|
}
|
|
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
|
+
import os from 'os'
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
import path from 'path'
|
|
13
|
+
import util from 'util'
|
|
14
|
+
import crypto from 'crypto'
|
|
15
|
+
import log from './util/log.js'
|
|
16
|
+
import Throws from './util/Throws.js'
|
|
17
17
|
|
|
18
18
|
// Custom errors thrown by this class.
|
|
19
19
|
const throws = new Throws({
|
|
@@ -26,11 +26,17 @@ 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
|
+
|
|
29
35
|
/**
|
|
30
36
|
* @alias module:lib/Configuration
|
|
31
37
|
* @hideconstructor
|
|
32
38
|
*/
|
|
33
|
-
class Configuration {
|
|
39
|
+
export default class Configuration {
|
|
34
40
|
#server = null
|
|
35
41
|
#domains = null
|
|
36
42
|
#settingsPath = null
|
|
@@ -73,7 +79,7 @@ class Configuration {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
// And ensure that the settings path exists in the file system.
|
|
76
|
-
|
|
82
|
+
ensureDirSync(this.#settingsPath)
|
|
77
83
|
|
|
78
84
|
//
|
|
79
85
|
// Create account paths.
|
|
@@ -106,7 +112,7 @@ class Configuration {
|
|
|
106
112
|
this.#certificateDirectoryPath = path.join(this.#settingsPath, certificateDirectoryName)
|
|
107
113
|
|
|
108
114
|
// And ensure that the certificate directory path exists in the file system.
|
|
109
|
-
|
|
115
|
+
ensureDirSync(this.#certificateDirectoryPath)
|
|
110
116
|
|
|
111
117
|
this.#certificatePath = path.join(this.#certificateDirectoryPath, 'certificate.pem')
|
|
112
118
|
this.#certificateIdentityPath = path.join(this.#certificateDirectoryPath, 'certificate-identity.pem')
|
|
@@ -230,4 +236,3 @@ class Configuration {
|
|
|
230
236
|
`
|
|
231
237
|
}
|
|
232
238
|
}
|
|
233
|
-
module.exports = Configuration
|
package/lib/Directory.js
CHANGED
|
@@ -11,25 +11,25 @@
|
|
|
11
11
|
//
|
|
12
12
|
////////////////////////////////////////////////////////////////////////////////
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
import util from 'util'
|
|
15
|
+
import prepareRequest from 'bent'
|
|
16
|
+
import log from './util/log.js'
|
|
17
|
+
import Throws from './util/Throws.js'
|
|
18
18
|
|
|
19
19
|
const throws = new Throws()
|
|
20
20
|
|
|
21
|
-
class Directory {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
export default 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
|
|
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 @@ 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 @@ 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,5 +89,3 @@ class Directory {
|
|
|
89
89
|
`
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
module.exports = Directory
|