@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/lib/Identity.js CHANGED
@@ -1,33 +1,39 @@
1
- ////////////////////////////////////////////////////////////////////////////////
2
- //
3
- // Identity (abstract base class; do not use directly).
4
- //
5
- // Generates, stores, loads, and saves an identity from/to the file
6
- // storage settings path. Meant to be subclassed and instantiated by different
7
- // singletons for different types of Identity (e.g., AccountIdentity and
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 jose from 'jose'
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
- const identityFilePath = configuration[identityFilePathKey]
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
- this._key = jose.JWK.generateSync('RSA')
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 = jose.JWK.asKey(_privatePEM)
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 JSON Web Key (JWK) instance.
56
- // https://github.com/panva/jose/blob/master/docs/README.md#jwk-json-web-key.
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.s
60
- get privatePEM () { return this._key.toPEM(/* private = */ true) }
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._key.thumbprint }
82
+ get thumbprint () { return this.#thumbprint }
65
83
 
66
84
  // Returns JWK-formatted objects.
67
- // https://github.com/panva/jose/blob/master/docs/README.md#keytojwkprivate.
68
- get privateJWK () { return this._key.toJWK(/* private = */ true) }
69
- get publicJWK () { return this._key.toJWK() }
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 (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'key') }
78
- set privatePEM (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'privatePEM') }
79
- set thumbprint (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'thumbprint') }
80
- set privateJWK (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'privateJWK') }
81
- set publicJWK (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), 'publicJWK') }
82
- set filePath (value) { throws.error(Symbol.for('ReadOnlyAccessorError'), '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 jose.JWK.RSAKey instance
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
  }
@@ -2,15 +2,15 @@ import util from 'util'
2
2
 
3
3
  export default class LetsEncryptServer {
4
4
  /**
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
- */
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
- *Creates an instance of LetsEncryptServer.
23
- * @param {LetsEncryptServer.type} type
24
- * @memberof LetsEncryptServer
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://localhost:14000/dir',
54
- 'http://localhost:9829/directory'
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
- // Nonce
4
- //
5
- // In order to protect ACME resources from any possible replay attacks,
6
- // ACME POST requests have a mandatory anti-replay mechanism. This
7
- // mechanism is based on the server maintaining a list of nonces that it
8
- // has issued, and requiring any signed request from the client to carry
9
- // such a nonce. – RFC 8555 § 6.5
10
- //
11
- // Before sending a POST request to the server, an ACME client needs to
12
- // have a fresh anti-replay nonce to put in the "nonce" header of the
13
- // JWS. In most cases, the client will have gotten a nonce from a
14
- // previous request. However, the client might sometimes need to get a
15
- // new nonce, e.g., on its first request to the server or if an existing
16
- // nonce is no longer valid. – RFC 8555 § 7.2
17
- //
18
- // Copyright © 2020 Aral Balkan, Small Technology Foundation.
19
- // License: AGPLv3 or later.
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
- const newNonceRequest = prepareRequest('HEAD', this.#directory.newNonceUrl)
63
- const newNonceResponse = await newNonceRequest()
64
- freshNonce = newNonceResponse.headers['replay-nonce']
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
- // Order
4
- //
5
- // (Please use async factory method Order.getInstanceAsync() to instantiate.)
6
- //
7
- // Represents a Let’s Encrypt order.
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
+ 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
- static async getInstanceAsync (configuration = throws.ifMissing(), accountIdentity = throws.ifMissing()) {
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 = Order.instance = new Order(configuration, accountIdentity)
48
- await Order.instance.init()
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 (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'certificate')) }
78
- set certificateIdentity (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'certificateIdentity')) }
79
- set authorisations (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'authorisations')) }
80
- set finaliseUrl (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'finaliseUrl')) }
81
- set identifiers (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'identifiers')) }
82
- set authorisations (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'authorisations')) }
83
- set status (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'status')) }
84
- set expires (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'expires')) }
85
- set certificateUrl (value) { throws.error(Symbol.for('ReadOnlyAccessorError', 'certificateUrl')) }
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
- * Creates an instance of Order.
94
- *
95
- * @param {Configuration} configuration (Required) Configuration instance.
96
- */
97
- constructor (configuration = throws.ifMissing(), accountIdentity = throws.ifMissing()) {
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
- // TODO: Handle error.
116
- throw new Error(error)
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 = await acmeCsr(this.domains, this.certificateIdentity.key)
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
- // AuthorisationRequest
4
- //
5
- // Representations an authorisation that needs to be fulfilled.
6
- //
7
- // See RFC 8555 § 7.5 (Identifier Authorisation)
8
- //
9
- // Copyright © 2020 Aral Balkan, Small Technology Foundation.
10
- // License: AGPLv3 or later.
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.execute(
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
- // CertificateRequest
4
- //
5
- // Requests download of the TLS certificate for a validated order.
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.execute(
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
- // CheckOrderStatusRequest
4
- //
5
- // If the order was not valid at time of finalise call (this doesn’t happen
6
- // as per the Let’s Encrypt implementation – Boulder – but could under RFC 8555),
7
- // then we need to send a POST-as-GET request to the finalise url to wait until
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.execute(
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