@small-tech/auto-encrypt 4.2.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 +219 -116
- package/lib/Account.js +28 -20
- package/lib/AcmeRequest.js +187 -117
- package/lib/Authorisation.js +80 -36
- package/lib/Certificate.js +299 -145
- 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 -10
- package/typedefs/lib/AcmeRequest.js +36 -25
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// See RFC 8555 § 7.4 (Applying for Certificate Issuance).
|
|
8
|
-
//
|
|
9
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
10
|
-
// License: AGPLv3 or later.
|
|
11
|
-
//
|
|
12
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
FinaliseOrderRequest
|
|
3
|
+
|
|
4
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
5
|
+
License: AGPLv3 or later.
|
|
6
|
+
*/
|
|
13
7
|
|
|
14
8
|
import AcmeRequest from '../AcmeRequest.js'
|
|
15
9
|
import Throws from '../util/Throws.js'
|
|
@@ -17,16 +11,24 @@ import Throws from '../util/Throws.js'
|
|
|
17
11
|
const throws = new Throws()
|
|
18
12
|
|
|
19
13
|
export default class FinaliseOrderRequest extends AcmeRequest {
|
|
14
|
+
/**
|
|
15
|
+
Attempts to finalise an order by posting the passed CSR (see RFC 2986).
|
|
16
|
+
|
|
17
|
+
See RFC 8555 § 7.4 (Applying for Certificate Issuance).
|
|
18
|
+
|
|
19
|
+
@param { string | void } finaliseUrl
|
|
20
|
+
@param { string | void } csr
|
|
21
|
+
*/
|
|
20
22
|
async execute (finaliseUrl = throws.ifMissing(), csr = throws.ifMissing()) {
|
|
21
23
|
|
|
22
|
-
const payload = { csr }
|
|
24
|
+
const payload = { csr: /** @type { string } */ (csr) }
|
|
23
25
|
|
|
24
|
-
const response = await super.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const response = await super.request(
|
|
27
|
+
/* command = */ '', // see URL, below.
|
|
28
|
+
/* payload = */ payload,
|
|
29
|
+
/* useKid = */ true,
|
|
30
|
+
/* successCodes = */ [200],
|
|
31
|
+
/* url = */ /** @type { string } */ (finaliseUrl)
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
return response
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
// See RFC 8555 § 7.3 (Account Management).
|
|
9
|
-
//
|
|
10
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
11
|
-
// License: AGPLv3 or later.
|
|
12
|
-
//
|
|
13
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
NewAccountRequest
|
|
3
|
+
|
|
4
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
5
|
+
License: AGPLv3 or later.
|
|
6
|
+
*/
|
|
14
7
|
|
|
15
8
|
import AcmeRequest from '../AcmeRequest.js'
|
|
16
9
|
|
|
17
10
|
export default class NewAccountRequest extends AcmeRequest {
|
|
11
|
+
/**
|
|
12
|
+
Requests a new account (or existing account if one already exists) and saves
|
|
13
|
+
the returned kid for future use.
|
|
14
|
+
|
|
15
|
+
See RFC 8555 § 7.3 (Account Management).
|
|
16
|
+
*/
|
|
18
17
|
async execute () {
|
|
19
18
|
// Set the only required element.
|
|
20
19
|
const payload = { termsOfServiceAgreed: true }
|
|
21
20
|
|
|
22
21
|
// Note: a 201 (Created) is returned if the account is new, a 200 (Success) is returned
|
|
23
22
|
// ===== if an existing account is found. (RFC 8555 § 7.3 & 7.3.1).
|
|
24
|
-
const response = await super.
|
|
23
|
+
const response = await super.request('newAccount', payload, /* useKid = */ false, /* successCodes = */[200, 201])
|
|
25
24
|
|
|
26
25
|
// This is what we will be using in the kid field in the future
|
|
27
26
|
// **in place of** the JWK.
|
|
@@ -1,27 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// NewOrderRequest
|
|
4
|
-
//
|
|
5
|
-
// Creates a new order request to start the process of obtaining
|
|
6
|
-
// Let’s Encrypt TLS certificates.
|
|
7
|
-
//
|
|
8
|
-
// See RFC 8555 § 7.1.3 (Order Objects), 7.4 (Applying for Certificate Issuance)
|
|
9
|
-
//
|
|
10
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
11
|
-
// License: AGPLv3 or later.
|
|
12
|
-
//
|
|
13
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
NewOrderRequest
|
|
14
3
|
|
|
4
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
5
|
+
License: AGPLv3 or later.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import net from 'node:net'
|
|
15
9
|
import AcmeRequest from '../AcmeRequest.js'
|
|
16
10
|
import Throws from '../util/Throws.js'
|
|
17
11
|
const throws = new Throws()
|
|
18
12
|
|
|
19
13
|
export default class NewOrderRequest extends AcmeRequest {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
/**
|
|
15
|
+
Creates a new order request to start the process of obtaining
|
|
16
|
+
Let’s Encrypt TLS certificates.
|
|
17
|
+
|
|
18
|
+
See RFC 8555 § 7.1.3 (Order Objects), 7.4 (Applying for Certificate Issuance)
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
@param { import('../Configuration.js').default | void } configuration
|
|
21
|
+
@param { string | void } ariCertId
|
|
22
|
+
*/
|
|
23
|
+
async execute (configuration = throws.ifMissing(), ariCertId = throws.ifMissing()) {
|
|
24
|
+
const identifiers = /** @type { import('../Configuration.js').default } */(configuration).domains.map(domain => {
|
|
25
|
+
// Determine whether this is a domain name or an IP address (IPv4 or IPv6)
|
|
26
|
+
return { type: net.isIP(domain) ? 'ip' : 'dns', value: domain} }
|
|
27
|
+
)
|
|
28
|
+
// As of end of December, 2025, Auto Encrypt only supports
|
|
29
|
+
// Let’s Encrypt’s new short-lived profiles.
|
|
30
|
+
// See: https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/
|
|
31
|
+
const payload = { identifiers, profile: 'shortlived' }
|
|
32
|
+
// If this is a renewal, mark the certificate it’s replacing (ARI)
|
|
33
|
+
// See: https://letsencrypt.org/2024/04/25/guide-to-integrating-ari-into-existing-acme-clients/#step-6-indicating-which-certificate-is-replaced-by-this-new-order
|
|
34
|
+
if (ariCertId !== null) {
|
|
35
|
+
payload.replaces = ariCertId
|
|
36
|
+
}
|
|
37
|
+
const response = await super.request('newOrder', payload, /* useKid = */ true, /* successCodes = */ [201])
|
|
25
38
|
return response
|
|
26
39
|
}
|
|
27
40
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// request to the challenge URL (not the authorization URL).
|
|
8
|
-
//
|
|
9
|
-
// – RFC 8555 § 7.5.1 (Responding to Challenges)
|
|
10
|
-
//
|
|
11
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
12
|
-
// License: AGPLv3 or later.
|
|
13
|
-
//
|
|
14
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
ReadyForChallengeValidationRequest
|
|
3
|
+
|
|
4
|
+
Copyright © 2020-present Aral Balkan, Small Technology Foundation.
|
|
5
|
+
License: AGPLv3 or later.
|
|
6
|
+
*/
|
|
15
7
|
|
|
16
8
|
import AcmeRequest from '../AcmeRequest.js'
|
|
17
9
|
import Throws from '../util/Throws.js'
|
|
@@ -19,15 +11,24 @@ import Throws from '../util/Throws.js'
|
|
|
19
11
|
const throws = new Throws()
|
|
20
12
|
|
|
21
13
|
export default class ReadyForChallengeValidationRequest extends AcmeRequest {
|
|
14
|
+
/**
|
|
15
|
+
The client indicates to the server that it is ready for the challenge
|
|
16
|
+
validation by sending an empty JSON body ("{}") carried in a POST
|
|
17
|
+
request to the challenge URL (not the authorization URL).
|
|
18
|
+
|
|
19
|
+
– RFC 8555 § 7.5.1 (Responding to Challenges)
|
|
20
|
+
|
|
21
|
+
@param { string | void } challengeUrl
|
|
22
|
+
*/
|
|
22
23
|
async execute (challengeUrl = throws.ifMissing()) {
|
|
23
24
|
const emptyPayload = {}
|
|
24
25
|
|
|
25
|
-
const response = await super.
|
|
26
|
+
const response = await super.request(
|
|
26
27
|
/* command = */ '', // see URL, below.
|
|
27
28
|
/* payload = */ emptyPayload,
|
|
28
29
|
/* useKid = */ true,
|
|
29
30
|
/* successCodes = */ [200],
|
|
30
|
-
/* url = */ challengeUrl
|
|
31
|
+
/* url = */ /** @type { string } */ (challengeUrl)
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
return response
|
package/lib/acmeCsr.js
CHANGED
|
@@ -1,87 +1,144 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
/**
|
|
2
|
+
ACME CSR
|
|
3
|
+
|
|
4
|
+
Given a regular Certification Request in PEM format, returns an
|
|
5
|
+
ACME-formatted CSR (single-line PEM without the header or footer and encoded
|
|
6
|
+
in base64Url instead of base64 format).
|
|
7
|
+
|
|
8
|
+
See RFC 8555 § 7.4 (Applying for Certificate Issuance).
|
|
9
|
+
|
|
10
|
+
Copyright © 2020-present Aral Balkan, Small Technology Foundation.
|
|
11
|
+
License: AGPLv3 or later.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import net from 'node:net'
|
|
15
|
+
import crypto from 'node:crypto'
|
|
16
|
+
import {
|
|
17
|
+
CertificationRequest,
|
|
18
|
+
CertificationRequestInfo,
|
|
19
|
+
SubjectPublicKeyInfo,
|
|
20
|
+
Extensions
|
|
21
|
+
} from './x.509/rfc5280.js'
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
Return an ACME-formatted (RFC 8555) CSR given a list of domains and a crypto.KeyObject.
|
|
25
|
+
|
|
26
|
+
@param { string[] } domains
|
|
27
|
+
@param { crypto.KeyObject } key
|
|
28
|
+
@returns { string } An ACME-formatted CSR in PEM format.
|
|
29
|
+
*/
|
|
27
30
|
export default function (domains, key) { return pemToAcmeCsr(csrAsPem(domains, key)) }
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
Create a CSR given a list of domains and a crypto.KeyObject.
|
|
34
|
+
|
|
35
|
+
@param { string[] } domains
|
|
36
|
+
@param { crypto.KeyObject } key
|
|
37
|
+
@returns { string } A CSR in PEM format.
|
|
38
|
+
*/
|
|
36
39
|
function csrAsPem (domains, key) {
|
|
37
|
-
|
|
40
|
+
const altNames = domains.map(domain => {
|
|
41
|
+
if (net.isIP(domain)) {
|
|
42
|
+
// IP address.
|
|
43
|
+
let ipBytes
|
|
44
|
+
if (net.isIPv4(domain)) {
|
|
45
|
+
ipBytes = Buffer.from(domain.split('.').map(Number))
|
|
46
|
+
} else {
|
|
47
|
+
// IPv6.
|
|
48
|
+
const parts = domain.split('::')
|
|
49
|
+
const left = parts[0] ? parts[0].split(':') : []
|
|
50
|
+
const right = parts[1] ? parts[1].split(':') : []
|
|
51
|
+
const missing = 8 - (left.length + right.length)
|
|
52
|
+
const expanded = [...left, ...Array(missing).fill('0'), ...right]
|
|
53
|
+
ipBytes = Buffer.from(expanded.flatMap(x => {
|
|
54
|
+
const hex = x.padStart(4, '0')
|
|
55
|
+
return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16)]
|
|
56
|
+
}))
|
|
57
|
+
}
|
|
38
58
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
return { type: 'iPAddress', value: ipBytes }
|
|
60
|
+
} else {
|
|
61
|
+
// Domain.
|
|
62
|
+
return { type: 'dNSName', value: domain }
|
|
63
|
+
}
|
|
64
|
+
})
|
|
43
65
|
|
|
44
|
-
|
|
66
|
+
const spki = crypto.createPublicKey(key).export({ type: 'spki', format: 'der' })
|
|
67
|
+
const subjectPKInfo = SubjectPublicKeyInfo.decode(spki, 'der')
|
|
45
68
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
const certificationRequestInfo = {
|
|
70
|
+
version: 0,
|
|
71
|
+
// According to RFC 8555, we *either* need to specify the subject or
|
|
72
|
+
// the subjectAltName so skip the subject. (We use an empty subject.)
|
|
73
|
+
subject: { type: 'rdnSequence', value: [] },
|
|
74
|
+
subjectPKInfo,
|
|
75
|
+
attributes: [
|
|
76
|
+
{
|
|
77
|
+
type: [1, 2, 840, 113549, 1, 9, 14], // extensionRequest
|
|
78
|
+
values: [
|
|
79
|
+
Extensions.encode([
|
|
80
|
+
{
|
|
81
|
+
extnID: 'subjectAlternativeName',
|
|
82
|
+
critical: false,
|
|
83
|
+
extnValue: altNames
|
|
84
|
+
}
|
|
85
|
+
], 'der')
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
49
90
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
name: 'extensionRequest',
|
|
53
|
-
extensions: [{
|
|
54
|
-
name: 'subjectAltName',
|
|
55
|
-
altNames
|
|
56
|
-
}]
|
|
57
|
-
}])
|
|
91
|
+
const tbsDer = CertificationRequestInfo.encode(certificationRequestInfo, 'der')
|
|
92
|
+
const signature = crypto.sign('sha256', tbsDer, key)
|
|
58
93
|
|
|
59
|
-
csr
|
|
94
|
+
const csr = {
|
|
95
|
+
certificationRequestInfo,
|
|
96
|
+
signatureAlgorithm: {
|
|
97
|
+
algorithm: [1, 2, 840, 113549, 1, 1, 11], // sha256WithRSAEncryption
|
|
98
|
+
parameters: Buffer.alloc(0)
|
|
99
|
+
},
|
|
100
|
+
signature: {
|
|
101
|
+
data: signature,
|
|
102
|
+
unused: 0
|
|
103
|
+
}
|
|
104
|
+
}
|
|
60
105
|
|
|
61
|
-
const
|
|
106
|
+
const der = CertificationRequest.encode(csr, 'der')
|
|
107
|
+
const pem = `-----BEGIN CERTIFICATE REQUEST-----\n${der.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE REQUEST-----`
|
|
62
108
|
return pem
|
|
63
109
|
}
|
|
64
110
|
|
|
111
|
+
/**
|
|
112
|
+
@param { string } csr
|
|
113
|
+
*/
|
|
65
114
|
function pemToAcmeCsr (csr) {
|
|
66
115
|
csr = pemToHeaderlessSingleLinePem(csr)
|
|
67
116
|
csr = base64ToBase64Url(csr)
|
|
68
117
|
return csr
|
|
69
118
|
}
|
|
70
119
|
|
|
71
|
-
|
|
72
|
-
|
|
120
|
+
/**
|
|
121
|
+
Strip the PEM headers and covert to a non-newline delimited Base64Url-encoded
|
|
122
|
+
string as required by RFC 8555 (would be nice if this was explicitly-mentioned in the spec).
|
|
123
|
+
|
|
124
|
+
@param { string } str
|
|
125
|
+
*/
|
|
73
126
|
function pemToHeaderlessSingleLinePem (str) {
|
|
74
127
|
return str
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
128
|
+
.replace('-----BEGIN CERTIFICATE REQUEST-----', '')
|
|
129
|
+
.replace('-----END CERTIFICATE REQUEST-----', '')
|
|
130
|
+
.replace(/\n/g, '')
|
|
78
131
|
}
|
|
79
132
|
|
|
80
|
-
|
|
133
|
+
/**
|
|
134
|
+
Convert base64-encoded string into a base64Url-encoded string.
|
|
135
|
+
|
|
136
|
+
@param { string } str
|
|
137
|
+
*/
|
|
81
138
|
function base64ToBase64Url (str) {
|
|
82
139
|
return str
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
140
|
+
.replace(/\+/g, '-')
|
|
141
|
+
.replace(/\//g, '_')
|
|
142
|
+
.replace(/=/g, '')
|
|
143
|
+
.replace(/\r/g, '')
|
|
87
144
|
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
// ~/.small-tech.org/auto-encrypt/account.pem
|
|
9
|
-
//
|
|
10
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
11
|
-
// License: AGPLv3 or later.
|
|
12
|
-
//
|
|
13
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
AccountIdentity
|
|
3
|
+
|
|
4
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
5
|
+
License: AGPLv3 or later.
|
|
6
|
+
*/
|
|
14
7
|
|
|
15
8
|
import Identity from '../Identity.js'
|
|
16
9
|
import Throws from '../util/Throws.js'
|
|
17
10
|
const throws = new Throws()
|
|
18
11
|
|
|
19
12
|
export default class AccountIdentity extends Identity {
|
|
13
|
+
/**
|
|
14
|
+
Generates, stores, loads, and saves the account identity. The default
|
|
15
|
+
account identity file path is:
|
|
16
|
+
|
|
17
|
+
~/.small-tech.org/auto-encrypt/account.pem
|
|
18
|
+
|
|
19
|
+
@param {import('../Configuration.js').default | void} configuration
|
|
20
|
+
*/
|
|
20
21
|
constructor (configuration = throws.ifMissing()) {
|
|
21
|
-
super(configuration, 'accountIdentityPath')
|
|
22
|
+
super(/** @type { import('../Configuration.js').default } */ (configuration), 'accountIdentityPath')
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -3,24 +3,24 @@ import Throws from '../util/Throws.js'
|
|
|
3
3
|
const throws = new Throws()
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
Generates, stores, loads, and saves the certificate identity. The default
|
|
7
|
+
certificate identity file path is:
|
|
8
|
+
|
|
9
|
+
~/.small-tech.org/auto-encrypt/certificate-identity.pem
|
|
10
|
+
|
|
11
|
+
@class CertificateIdentity
|
|
12
|
+
@extends {Identity}
|
|
13
|
+
@copyright Aral Balkan, Small Technology Foundation
|
|
14
|
+
@license AGPLv3 or later
|
|
15
|
+
*/
|
|
16
16
|
export default class CertificateIdentity extends Identity {
|
|
17
17
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
Creates an instance of CertificateIdentity.
|
|
19
|
+
|
|
20
|
+
@param { import('../Configuration.js').default | void } configuration (Required) Configuration instance.
|
|
21
|
+
@memberof CertificateIdentity
|
|
22
|
+
*/
|
|
23
23
|
constructor (configuration = throws.ifMissing()) {
|
|
24
|
-
super(configuration, 'certificateIdentityPath')
|
|
24
|
+
super(/** @type { import('../Configuration.js').default } */ (configuration), 'certificateIdentityPath')
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
Monkey patches the TLS module to accept run-time root and intermediary Certificate Authority (CA) certificates.
|
|
3
|
+
|
|
4
|
+
Based on the method provided by David Barral at https://link.medium.com/6xHYLeUVq5.
|
|
5
|
+
|
|
6
|
+
@module
|
|
7
|
+
@copyright Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
8
|
+
@license AGPLv3 or later.
|
|
9
|
+
*/
|
|
10
10
|
import fs from 'fs'
|
|
11
11
|
import tls from 'tls'
|
|
12
12
|
import path from 'path'
|
|
@@ -15,10 +15,10 @@ import { fileURLToPath } from 'url'
|
|
|
15
15
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
Monkey patches the TLS module to accept the Let’s Encrypt staging certificate.
|
|
19
|
+
|
|
20
|
+
@alias module:lib/MonkeyPatchTls
|
|
21
|
+
*/
|
|
22
22
|
export default function monkeyPatchTLS () {
|
|
23
23
|
const originalCreateSecureContext = tls.createSecureContext
|
|
24
24
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
//////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
Unit test helpers.
|
|
3
|
+
*/
|
|
6
4
|
|
|
7
5
|
import fs from 'fs'
|
|
8
6
|
import os from 'os'
|
|
@@ -27,7 +25,7 @@ export class MockServer {
|
|
|
27
25
|
static async getInstanceAsync (responseHandler = throws.ifMissing()) {
|
|
28
26
|
this.#isBeingInstantiatedViaAsyncFactoryMethod = true
|
|
29
27
|
const instance = new MockServer(responseHandler)
|
|
30
|
-
await instance.create(
|
|
28
|
+
await instance.create()
|
|
31
29
|
this.#isBeingInstantiatedViaAsyncFactoryMethod = false
|
|
32
30
|
return instance
|
|
33
31
|
}
|
|
@@ -96,16 +94,16 @@ export class TestContext {
|
|
|
96
94
|
//
|
|
97
95
|
|
|
98
96
|
export function timeIt(func) {
|
|
99
|
-
const startTime = new Date()
|
|
97
|
+
const startTime = new Date().getTime()
|
|
100
98
|
const returnValue = func()
|
|
101
|
-
const endTime = new Date()
|
|
99
|
+
const endTime = new Date().getTime()
|
|
102
100
|
return { returnValue, duration: endTime - startTime }
|
|
103
101
|
}
|
|
104
102
|
|
|
105
103
|
export async function timeItAsync(func) {
|
|
106
|
-
const startTime = new Date()
|
|
104
|
+
const startTime = new Date().getTime()
|
|
107
105
|
const returnValue = await func()
|
|
108
|
-
const endTime = new Date()
|
|
106
|
+
const endTime = new Date().getTime()
|
|
109
107
|
return { returnValue, duration: endTime - startTime }
|
|
110
108
|
}
|
|
111
109
|
|
|
@@ -164,16 +162,3 @@ export function createTestSettingsPath () {
|
|
|
164
162
|
fs.rmSync(testSettingsPath, { recursive: true, force: true })
|
|
165
163
|
return testSettingsPath
|
|
166
164
|
}
|
|
167
|
-
|
|
168
|
-
export function initialiseStagingConfigurationWithOneDomainAtTestSettingsPath () {
|
|
169
|
-
Configuration.reset()
|
|
170
|
-
Configuration.initialise({
|
|
171
|
-
domains: ['dev.ar.al'],
|
|
172
|
-
server: new LetsEncryptServer(LetsEncryptServer.type.STAGING),
|
|
173
|
-
settingsPath: createTestSettingsPath()
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function setupStagingConfigurationWithOneDomainAtTestSettingsPath () {
|
|
178
|
-
initialiseStagingConfigurationWithOneDomainAtTestSettingsPath()
|
|
179
|
-
}
|
package/lib/util/Pluralise.js
CHANGED
|
@@ -10,10 +10,10 @@ export default class Pluralise {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
static isAre (array) {
|
|
13
|
-
return array.length === 1 ? 'is' : 'are'
|
|
13
|
+
return array === undefined || array.length === 1 ? 'is' : 'are'
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
static word (word, array) {
|
|
17
|
-
return array.length === 1 ? word : `${word}${this.requiresEs(word) ? 'es' : 's'}`
|
|
17
|
+
return array === undefined || array.length === 1 ? word : `${word}${this.requiresEs(word) ? 'es' : 's'}`
|
|
18
18
|
}
|
|
19
19
|
}
|