@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
package/lib/Identity.js
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// DomainIdentity).
|
|
9
|
-
//
|
|
10
|
-
// The private key uses the RS256 algorithm with a 2048-bit key.
|
|
11
|
-
//
|
|
12
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
13
|
-
// License: AGPLv3 or later.
|
|
14
|
-
//
|
|
15
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
Identity (abstract base class; do not use directly).
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
6
|
+
License: AGPLv3 or later.
|
|
7
|
+
*/
|
|
16
8
|
|
|
17
9
|
import util from 'util'
|
|
18
10
|
import fs from 'fs'
|
|
19
|
-
import
|
|
11
|
+
import crypto from 'node:crypto'
|
|
20
12
|
import Throws from './util/Throws.js'
|
|
21
13
|
import log from './util/log.js'
|
|
22
14
|
|
|
23
15
|
const throws = new Throws({
|
|
24
|
-
[Symbol.for('UnsupportedIdentityType')]: identityFilePath => `The identity file path passed (${identityFilePath}) is for an unsupported identity type.`
|
|
16
|
+
[Symbol.for('UnsupportedIdentityType')]: (/** @type {string} */ identityFilePath) => `The identity file path passed (${identityFilePath}) is for an unsupported identity type.`
|
|
25
17
|
})
|
|
26
18
|
|
|
27
19
|
export default class Identity {
|
|
20
|
+
/** @type { crypto.KeyObject } */
|
|
21
|
+
_key
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
Generates, stores, loads, and saves an identity from/to the file
|
|
25
|
+
storage settings path. Meant to be subclassed and instantiated by different
|
|
26
|
+
singletons for different types of Identity (e.g., AccountIdentity and
|
|
27
|
+
DomainIdentity).
|
|
28
28
|
|
|
29
|
+
The private key uses the RS256 algorithm with a 2048-bit key.
|
|
30
|
+
|
|
31
|
+
@param { import('./Configuration.js').default | void } configuration
|
|
32
|
+
@param { string | void } identityFilePathKey
|
|
33
|
+
*/
|
|
29
34
|
constructor (configuration = throws.ifMissing(), identityFilePathKey = throws.ifMissing()) {
|
|
30
|
-
|
|
35
|
+
/** @type {string} */
|
|
36
|
+
const identityFilePath = /** @type { import('./Configuration.js').default } */ (configuration)[/** @type {string} */ (identityFilePathKey)]
|
|
31
37
|
|
|
32
38
|
if (identityFilePath === undefined) {
|
|
33
39
|
throws.error(Symbol.for('UnsupportedIdentityType'))
|
|
@@ -39,47 +45,67 @@ export default class Identity {
|
|
|
39
45
|
|
|
40
46
|
if (!fs.existsSync(this.#identityFilePath)) {
|
|
41
47
|
// The identity file does not already exist, generate and save it.
|
|
42
|
-
|
|
48
|
+
const { privateKey } = crypto.generateKeyPairSync('rsa', {
|
|
49
|
+
modulusLength: 2048
|
|
50
|
+
})
|
|
51
|
+
this._key = privateKey
|
|
43
52
|
fs.writeFileSync(this.#identityFilePath, this.privatePEM, 'utf-8')
|
|
44
53
|
} else {
|
|
45
54
|
// Load the key from storage.
|
|
46
55
|
const _privatePEM = fs.readFileSync(this.#identityFilePath, 'utf-8')
|
|
47
|
-
this._key =
|
|
56
|
+
this._key = crypto.createPrivateKey(_privatePEM)
|
|
48
57
|
}
|
|
58
|
+
|
|
59
|
+
// Pre-calculate thumbprint synchronously using node:crypto as jose.calculateJwkThumbprint is async.
|
|
60
|
+
// RFC 7638: for RSA, the thumbprint is calculated over 'e', 'kty', and 'n' in lexicographical order.
|
|
61
|
+
const jwk = this.publicJWK
|
|
62
|
+
const thumbprintSource = JSON.stringify({
|
|
63
|
+
e: jwk.e,
|
|
64
|
+
kty: jwk.kty,
|
|
65
|
+
n: jwk.n
|
|
66
|
+
})
|
|
67
|
+
this.#thumbprint = crypto.createHash('sha256').update(thumbprintSource).digest('base64url')
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
//
|
|
52
71
|
// Accessors.
|
|
53
72
|
//
|
|
54
73
|
|
|
55
|
-
// The
|
|
56
|
-
|
|
57
|
-
get key () { return this._key }
|
|
74
|
+
// The KeyObject instance.
|
|
75
|
+
get key () { return this._key }
|
|
58
76
|
|
|
59
|
-
// Returns the private key in PEM format.
|
|
60
|
-
get privatePEM () { return this._key.
|
|
77
|
+
// Returns the private key in PEM format.
|
|
78
|
+
get privatePEM () { return this._key.export({ type: 'pkcs8', format: 'pem' }).toString() }
|
|
61
79
|
|
|
62
80
|
// The JWK thumbprint as calculated according to
|
|
63
81
|
// RFC 7638 (https://tools.ietf.org/html/rfc7638).
|
|
64
|
-
get thumbprint () { return this
|
|
82
|
+
get thumbprint () { return this.#thumbprint }
|
|
65
83
|
|
|
66
84
|
// Returns JWK-formatted objects.
|
|
67
|
-
|
|
68
|
-
get
|
|
69
|
-
|
|
85
|
+
get privateJWK () { return this._key.export({ format: 'jwk' }) }
|
|
86
|
+
get publicJWK () {
|
|
87
|
+
const jwk = this.privateJWK
|
|
88
|
+
delete jwk.d
|
|
89
|
+
delete jwk.p
|
|
90
|
+
delete jwk.q
|
|
91
|
+
delete jwk.dp
|
|
92
|
+
delete jwk.dq
|
|
93
|
+
delete jwk.qi
|
|
94
|
+
return jwk
|
|
95
|
+
}
|
|
70
96
|
|
|
71
97
|
// The file path of the private key (saved in PEM format).
|
|
72
|
-
get filePath () { return this.#identityFilePath
|
|
98
|
+
get filePath () { return this.#identityFilePath }
|
|
73
99
|
|
|
74
100
|
//
|
|
75
101
|
// Control access to read-only properties.
|
|
76
102
|
//
|
|
77
|
-
set key (
|
|
78
|
-
set privatePEM (
|
|
79
|
-
set thumbprint (
|
|
80
|
-
set privateJWK (
|
|
81
|
-
set publicJWK (
|
|
82
|
-
set filePath (
|
|
103
|
+
set key (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'key') }
|
|
104
|
+
set privatePEM (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'privatePEM') }
|
|
105
|
+
set thumbprint (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'thumbprint') }
|
|
106
|
+
set privateJWK (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'privateJWK') }
|
|
107
|
+
set publicJWK (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'publicJWK') }
|
|
108
|
+
set filePath (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'filePath') }
|
|
83
109
|
|
|
84
110
|
// Custom object description for console output (for debugging).
|
|
85
111
|
[util.inspect.custom] () {
|
|
@@ -93,7 +119,7 @@ export default class Identity {
|
|
|
93
119
|
|
|
94
120
|
## Properties
|
|
95
121
|
|
|
96
|
-
- .key : the
|
|
122
|
+
- .key : the crypto.KeyObject instance
|
|
97
123
|
- .privatePEM : PEM representation of the private key
|
|
98
124
|
- .thumbprint : JWK thumbprint calculated according to RFC 7638
|
|
99
125
|
- .privateJWK : JavaScript object representation of JWK (private key)
|
|
@@ -107,5 +133,7 @@ export default class Identity {
|
|
|
107
133
|
//
|
|
108
134
|
// Private
|
|
109
135
|
//
|
|
136
|
+
/** @type {string} */
|
|
110
137
|
#identityFilePath = null
|
|
138
|
+
#thumbprint = null
|
|
111
139
|
}
|
package/lib/LetsEncryptServer.js
CHANGED
|
@@ -2,15 +2,15 @@ import util from 'util'
|
|
|
2
2
|
|
|
3
3
|
export default class LetsEncryptServer {
|
|
4
4
|
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
Enumeration.
|
|
6
|
+
|
|
7
|
+
@property PRODUCTION Use the production server.
|
|
8
|
+
@property STAGING Use the staging server.
|
|
9
|
+
@property PEBBLE Use a local pebble testing server.
|
|
10
|
+
@property MOCK Use local mock server.
|
|
11
|
+
@readonly
|
|
12
|
+
@static
|
|
13
|
+
*/
|
|
14
14
|
static type = {
|
|
15
15
|
PRODUCTION: 0,
|
|
16
16
|
STAGING: 1,
|
|
@@ -19,10 +19,10 @@ export default class LetsEncryptServer {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
Creates an instance of LetsEncryptServer.
|
|
23
|
+
@param {Number} type
|
|
24
|
+
@memberof LetsEncryptServer
|
|
25
|
+
*/
|
|
26
26
|
constructor (type) {
|
|
27
27
|
this.#type = type
|
|
28
28
|
}
|
|
@@ -50,7 +50,7 @@ export default class LetsEncryptServer {
|
|
|
50
50
|
#endpoints = [
|
|
51
51
|
'https://acme-v02.api.letsencrypt.org/directory',
|
|
52
52
|
'https://acme-staging-v02.api.letsencrypt.org/directory',
|
|
53
|
-
'https://
|
|
54
|
-
'http://
|
|
53
|
+
'https://127.0.0.1:14000/dir',
|
|
54
|
+
'http://127.0.0.1:9829/directory'
|
|
55
55
|
]
|
|
56
56
|
}
|
package/lib/Nonce.js
CHANGED
|
@@ -1,36 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
22
|
-
|
|
23
|
-
import prepareRequest from 'bent'
|
|
1
|
+
/**
|
|
2
|
+
Nonce
|
|
3
|
+
|
|
4
|
+
In order to protect ACME resources from any possible replay attacks,
|
|
5
|
+
ACME POST requests have a mandatory anti-replay mechanism. This
|
|
6
|
+
mechanism is based on the server maintaining a list of nonces that it
|
|
7
|
+
has issued, and requiring any signed request from the client to carry
|
|
8
|
+
such a nonce. – RFC 8555 § 6.5
|
|
9
|
+
|
|
10
|
+
Before sending a POST request to the server, an ACME client needs to
|
|
11
|
+
have a fresh anti-replay nonce to put in the "nonce" header of the
|
|
12
|
+
JWS. In most cases, the client will have gotten a nonce from a
|
|
13
|
+
previous request. However, the client might sometimes need to get a
|
|
14
|
+
new nonce, e.g., on its first request to the server or if an existing
|
|
15
|
+
nonce is no longer valid. – RFC 8555 § 7.2
|
|
16
|
+
|
|
17
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
18
|
+
License: AGPLv3 or later.
|
|
19
|
+
*/
|
|
20
|
+
|
|
24
21
|
import log from './util/log.js'
|
|
25
22
|
import Throws from './util/Throws.js'
|
|
26
23
|
|
|
27
24
|
const throws = new Throws()
|
|
28
25
|
|
|
26
|
+
/**
|
|
27
|
+
@typedef { import('./Directory.js').default } Directory
|
|
28
|
+
*/
|
|
29
|
+
|
|
29
30
|
export default class Nonce {
|
|
31
|
+
/**
|
|
32
|
+
@param { Directory | void } directory
|
|
33
|
+
*/
|
|
30
34
|
constructor (directory = throws.ifMissing()) {
|
|
31
|
-
this.#directory = directory
|
|
35
|
+
this.#directory = /** @type { Directory } */ (directory);
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
/** @param {string} freshNonce */
|
|
34
39
|
set (freshNonce) {
|
|
35
40
|
if (freshNonce === undefined || freshNonce === null) {
|
|
36
41
|
log(' ⚠ ❨auto-encrypt❩ nonce.set called with undefined/null. Not saving nonce. No effect on functionality. ')
|
|
@@ -59,9 +64,9 @@ export default class Nonce {
|
|
|
59
64
|
// code 200 (OK). The server MUST also respond to GET requests for this
|
|
60
65
|
// resource, returning an empty body (while still providing a Replay-
|
|
61
66
|
// Nonce header) with a status code of 204 (No Content). – RFC 8555 § 7.2
|
|
62
|
-
|
|
63
|
-
const newNonceResponse = await
|
|
64
|
-
freshNonce = newNonceResponse.headers
|
|
67
|
+
|
|
68
|
+
const newNonceResponse = await fetch(this.#directory.newNonceUrl, { method: 'HEAD' })
|
|
69
|
+
freshNonce = newNonceResponse.headers.get('replay-nonce')
|
|
65
70
|
|
|
66
71
|
// Note: we do not persist the freshNonce in this.#freshNonce as we
|
|
67
72
|
// ===== are returning it and thus we consider it used.
|
|
@@ -70,6 +75,10 @@ export default class Nonce {
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
// Private
|
|
78
|
+
|
|
79
|
+
/** @type { Directory } */
|
|
73
80
|
#directory = null
|
|
81
|
+
|
|
82
|
+
/** @type { string } */
|
|
74
83
|
#freshNonce = null
|
|
75
84
|
}
|
package/lib/Order.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
Order
|
|
3
|
+
|
|
4
|
+
(Please use async factory method Order.getInstanceAsync() to instantiate.)
|
|
5
|
+
|
|
6
|
+
Represents a Let’s Encrypt order.
|
|
7
|
+
See RFC 8555 § 7.1.3 (Order Objects), 7.4 (Applying for Certificate Issuance)
|
|
8
|
+
|
|
9
|
+
Copyright © 2020-present Aral Balkan, Small Technology Foundation.
|
|
10
|
+
License: AGPLv3 or later.
|
|
11
|
+
*/
|
|
14
12
|
|
|
15
13
|
import fsPromises from 'fs/promises'
|
|
16
14
|
import Authorisation from './Authorisation.js'
|
|
@@ -35,17 +33,35 @@ export default class Order {
|
|
|
35
33
|
#order = null
|
|
36
34
|
#certificate = null
|
|
37
35
|
#certificateIdentity = null
|
|
36
|
+
|
|
37
|
+
/** @type { Array<Authorisation> } */
|
|
38
38
|
#authorisations = []
|
|
39
39
|
|
|
40
|
+
/** @type { import('./Configuration.js').default } */
|
|
41
|
+
configuration
|
|
42
|
+
|
|
43
|
+
/** @type { import('./identities/AccountIdentity.js').default } */
|
|
44
|
+
accountIdentity
|
|
45
|
+
|
|
46
|
+
/** @type { string } */
|
|
47
|
+
ariCertId
|
|
48
|
+
|
|
40
49
|
//
|
|
41
50
|
// Factory method (async).
|
|
42
51
|
//
|
|
43
52
|
static isBeingInstantiatedViaFactoryMethod = false
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
/**
|
|
55
|
+
Async factory method. Use this to instantiate this class.
|
|
56
|
+
|
|
57
|
+
@param { import('./Configuration.js').default | void } configuration (Required) Configuration instance.
|
|
58
|
+
@param { import('./identities/AccountIdentity.js').default | void } accountIdentity (Required) Account identity.
|
|
59
|
+
@param { string | void } ariCertId (Required) ARI certificate ID.
|
|
60
|
+
*/
|
|
61
|
+
static async createAsync (configuration = throws.ifMissing(), accountIdentity = throws.ifMissing(), ariCertId = throws.ifMissing()) {
|
|
46
62
|
Order.isBeingInstantiatedViaFactoryMethod = true
|
|
47
|
-
const instance =
|
|
48
|
-
await
|
|
63
|
+
const instance = new Order(configuration, accountIdentity, ariCertId)
|
|
64
|
+
await instance.init()
|
|
49
65
|
return instance
|
|
50
66
|
}
|
|
51
67
|
|
|
@@ -74,35 +90,37 @@ export default class Order {
|
|
|
74
90
|
this.#order = this.#data.body
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
set certificate (
|
|
78
|
-
set certificateIdentity (
|
|
79
|
-
set authorisations (
|
|
80
|
-
set finaliseUrl (
|
|
81
|
-
set identifiers (
|
|
82
|
-
set
|
|
83
|
-
set
|
|
84
|
-
set
|
|
85
|
-
set
|
|
86
|
-
set headers (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'headers')) }
|
|
93
|
+
set certificate (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'certificate') }
|
|
94
|
+
set certificateIdentity (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'certificateIdentity') }
|
|
95
|
+
set authorisations (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'authorisations') }
|
|
96
|
+
set finaliseUrl (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'finaliseUrl') }
|
|
97
|
+
set identifiers (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'identifiers') }
|
|
98
|
+
set status (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'status') }
|
|
99
|
+
set expires (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'expires') }
|
|
100
|
+
set certificateUrl (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'certificateUrl') }
|
|
101
|
+
set headers (_value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'headers') }
|
|
87
102
|
|
|
88
103
|
//
|
|
89
104
|
// Private.
|
|
90
105
|
//
|
|
91
106
|
|
|
92
107
|
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
Creates an instance of Order.
|
|
109
|
+
|
|
110
|
+
@param { import('./Configuration.js').default | void } configuration (Required) Configuration instance.
|
|
111
|
+
@param { import('./identities/AccountIdentity.js').default | void } accountIdentity (Required) Account identity.
|
|
112
|
+
@param { string | void } ariCertId (Required) ARI certificate ID.
|
|
113
|
+
*/
|
|
114
|
+
constructor (configuration = throws.ifMissing(), accountIdentity = throws.ifMissing(), ariCertId = throws.ifMissing()) {
|
|
98
115
|
// Ensure singleton access.
|
|
99
116
|
if (Order.isBeingInstantiatedViaFactoryMethod === false) {
|
|
100
117
|
throw new Error('Order constructor is private. Please instantiate using :await Order.getInstanceAsync().')
|
|
101
118
|
}
|
|
102
119
|
|
|
103
|
-
this.configuration = configuration
|
|
104
|
-
this.domains = configuration.domains
|
|
105
|
-
this.accountIdentity = accountIdentity
|
|
120
|
+
this.configuration = /** @type { import('./Configuration.js').default } */ (configuration)
|
|
121
|
+
this.domains = this.configuration.domains
|
|
122
|
+
this.accountIdentity = /** @type { import('./identities/AccountIdentity.js').default } */ (accountIdentity)
|
|
123
|
+
this.ariCertId = /** @type { string } */ (ariCertId)
|
|
106
124
|
|
|
107
125
|
Order.isBeingInstantiatedViaFactoryMethod = false
|
|
108
126
|
}
|
|
@@ -110,10 +128,18 @@ export default class Order {
|
|
|
110
128
|
|
|
111
129
|
async init () {
|
|
112
130
|
try {
|
|
113
|
-
this.data = await ((new NewOrderRequest()).execute(this.configuration))
|
|
131
|
+
this.data = await ((new NewOrderRequest()).execute(this.configuration, this.ariCertId))
|
|
114
132
|
} catch (error) {
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
if (this.ariCertId !== null) {
|
|
134
|
+
log(` ⚠ ❨auto-encrypt❩ Failed to create order with ARI replacement info (${error.message}). Retrying without it…`)
|
|
135
|
+
try {
|
|
136
|
+
this.data = await ((new NewOrderRequest()).execute(this.configuration, null))
|
|
137
|
+
} catch (retryError) {
|
|
138
|
+
throw new Error(retryError)
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
throw new Error(error)
|
|
142
|
+
}
|
|
117
143
|
}
|
|
118
144
|
|
|
119
145
|
this.#authorisations = []
|
|
@@ -128,7 +154,7 @@ export default class Order {
|
|
|
128
154
|
// instances will handle settings up to answer their challenges themselves.
|
|
129
155
|
await asyncForEach(
|
|
130
156
|
this.data.body.authorizations,
|
|
131
|
-
async authorisationUrl => {
|
|
157
|
+
async ( /** @type { string } */ authorisationUrl ) => {
|
|
132
158
|
// An authorisation only returns when it is validated.
|
|
133
159
|
// TODO: handle errors.
|
|
134
160
|
const authorisation = await Authorisation.getInstanceAsync(authorisationUrl, this.accountIdentity)
|
|
@@ -159,7 +185,7 @@ export default class Order {
|
|
|
159
185
|
this.#certificateIdentity = new CertificateIdentity(this.configuration)
|
|
160
186
|
|
|
161
187
|
// Generate a Certificate Signing Request in the unique format that ACME expects.
|
|
162
|
-
const csr =
|
|
188
|
+
const csr = acmeCsr(this.domains, this.certificateIdentity.key)
|
|
163
189
|
|
|
164
190
|
let numAttempts = 0
|
|
165
191
|
while (this.status !== 'valid' && this.status !== 'invalid') {
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
AuthorisationRequest
|
|
3
|
+
|
|
4
|
+
Representations an authorisation that needs to be fulfilled.
|
|
5
|
+
|
|
6
|
+
See RFC 8555 § 7.5 (Identifier Authorisation)
|
|
7
|
+
|
|
8
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
9
|
+
License: AGPLv3 or later.
|
|
10
|
+
*/
|
|
13
11
|
|
|
14
12
|
import AcmeRequest from '../AcmeRequest.js'
|
|
15
13
|
import Throws from '../util/Throws.js'
|
|
@@ -17,6 +15,9 @@ import Throws from '../util/Throws.js'
|
|
|
17
15
|
const throws = new Throws()
|
|
18
16
|
|
|
19
17
|
export default class AuthorisationRequest extends AcmeRequest {
|
|
18
|
+
/**
|
|
19
|
+
@param { string | void } authorisationUrl
|
|
20
|
+
*/
|
|
20
21
|
async execute (authorisationUrl = throws.ifMissing()) {
|
|
21
22
|
// This is a POST-as-GET request so it doesn’t have a payload.
|
|
22
23
|
// See RFC 8555 § 6.3 (GET and POST-as-GET requests).
|
|
@@ -24,12 +25,12 @@ export default class AuthorisationRequest extends AcmeRequest {
|
|
|
24
25
|
|
|
25
26
|
// Note: a 201 (Created) is returned if the account is new, a 200 (Success) is returned
|
|
26
27
|
// ===== if an existing account is found. (RFC 8555 § 7.3 & 7.3.1).
|
|
27
|
-
const response = await super.
|
|
28
|
+
const response = await super.request(
|
|
28
29
|
/* command = */ '', // see URL, below.
|
|
29
30
|
/* payload = */ noPayload,
|
|
30
31
|
/* useKid = */ true,
|
|
31
32
|
/* successCodes = */ [200],
|
|
32
|
-
/* url = */ authorisationUrl
|
|
33
|
+
/* url = */ /** @type { string } */ (authorisationUrl)
|
|
33
34
|
)
|
|
34
35
|
return response
|
|
35
36
|
}
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// See RFC 8555 § 7.4.2 (Downloading the Certificate).
|
|
8
|
-
// The certificate type is application/pem-certificate-chain (RFC 8555 § 9.1).
|
|
9
|
-
//
|
|
10
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
11
|
-
// License: AGPLv3 or later.
|
|
12
|
-
//
|
|
13
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
CertificateRequest
|
|
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
|
import Throws from '../util/Throws.js'
|
|
@@ -18,6 +11,14 @@ import Throws from '../util/Throws.js'
|
|
|
18
11
|
const throws = new Throws()
|
|
19
12
|
|
|
20
13
|
export default class CertificateRequest extends AcmeRequest {
|
|
14
|
+
/**
|
|
15
|
+
Requests download of the TLS certificate for a validated order.
|
|
16
|
+
|
|
17
|
+
See RFC 8555 § 7.4.2 (Downloading the Certificate).
|
|
18
|
+
The certificate type is application/pem-certificate-chain (RFC 8555 § 9.1).
|
|
19
|
+
|
|
20
|
+
@param { string | void } certificateUrl
|
|
21
|
+
*/
|
|
21
22
|
async execute (certificateUrl = throws.ifMissing()) {
|
|
22
23
|
// This is a POST-as-GET request so it doesn’t have a payload.
|
|
23
24
|
// See RFC 8555 § 6.3 (GET and POST-as-GET requests).
|
|
@@ -25,12 +26,12 @@ export default class CertificateRequest extends AcmeRequest {
|
|
|
25
26
|
|
|
26
27
|
// Note: a 201 (Created) is returned if the account is new, a 200 (Success) is returned
|
|
27
28
|
// ===== if an existing account is found. (RFC 8555 § 7.3 & 7.3.1).
|
|
28
|
-
const response = await super.
|
|
29
|
+
const response = await super.request(
|
|
29
30
|
/* command = */ '', // see URL, below.
|
|
30
31
|
/* payload = */ noPayload,
|
|
31
32
|
/* useKid = */ true,
|
|
32
33
|
/* successCodes = */ [200],
|
|
33
|
-
/* url = */ certificateUrl,
|
|
34
|
+
/* url = */ /** @type { string } */ (certificateUrl),
|
|
34
35
|
/* parseResponseBodyAsJSON = */ false
|
|
35
36
|
)
|
|
36
37
|
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// the order is valid.
|
|
9
|
-
//
|
|
10
|
-
// See RFC 8555 § 7.4 (Applying for Certificate Issuance).
|
|
11
|
-
//
|
|
12
|
-
// Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
13
|
-
// License: AGPLv3 or later.
|
|
14
|
-
//
|
|
15
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
1
|
+
/**
|
|
2
|
+
|
|
3
|
+
CheckOrderStatusRequest
|
|
4
|
+
|
|
5
|
+
Copyright © 2020 Aral Balkan, Small Technology Foundation.
|
|
6
|
+
License: AGPLv3 or later.
|
|
7
|
+
*/
|
|
16
8
|
|
|
17
9
|
import AcmeRequest from '../AcmeRequest.js'
|
|
18
10
|
import Throws from '../util/Throws.js'
|
|
@@ -20,16 +12,26 @@ import Throws from '../util/Throws.js'
|
|
|
20
12
|
const throws = new Throws()
|
|
21
13
|
|
|
22
14
|
export default class CheckOrderStatusRequest extends AcmeRequest {
|
|
15
|
+
/**
|
|
16
|
+
If the order was not valid at time of finalise call (this doesn’t happen
|
|
17
|
+
as per the Let’s Encrypt implementation – Boulder – but could under RFC 8555),
|
|
18
|
+
then we need to send a POST-as-GET request to the finalise url to wait until
|
|
19
|
+
the order is valid.
|
|
20
|
+
|
|
21
|
+
See RFC 8555 § 7.4 (Applying for Certificate Issuance).
|
|
22
|
+
|
|
23
|
+
@param { string | void } orderUrl
|
|
24
|
+
*/
|
|
23
25
|
async execute (orderUrl = throws.ifMissing()) {
|
|
24
26
|
|
|
25
27
|
const payload = '' // POST-as-GET
|
|
26
28
|
|
|
27
|
-
const response = await super.
|
|
29
|
+
const response = await super.request(
|
|
28
30
|
/* command = */ '', // see URL, below.
|
|
29
31
|
/* payload = */ payload,
|
|
30
32
|
/* useKid = */ true,
|
|
31
33
|
/* successCodes = */ [200],
|
|
32
|
-
/* url = */ orderUrl
|
|
34
|
+
/* url = */ /** @type { string } */ (orderUrl)
|
|
33
35
|
)
|
|
34
36
|
|
|
35
37
|
return response
|