@blamejs/pki 0.8.31 → 0.8.48
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/CHANGELOG.md +393 -0
- package/README.md +66 -15
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +26 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +33 -6
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +228 -2
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/acme.js
CHANGED
|
@@ -53,6 +53,14 @@ var retryAfter = require("./http-retry-after");
|
|
|
53
53
|
var guard = require("./guard-all");
|
|
54
54
|
var intrinsic = require("./guard-intrinsic");
|
|
55
55
|
var _resolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
56
|
+
/** @internal The RECEIVER for that capture, captured too. `Promise.resolve` builds through its
|
|
57
|
+
* receiver, so handing it the live `globalThis.Promise` put the capture back under a constructor a
|
|
58
|
+
* replacement installed after load could take over, and what it decides is the value that gets
|
|
59
|
+
* awaited. */
|
|
60
|
+
var _Promise = intrinsic.Promise;
|
|
61
|
+
/** @internal A refusal returned as a REJECTED promise is a verdict too: read from the global binding, a
|
|
62
|
+
* replacement whose `reject` resolves instead turns the refusal into a pass. */
|
|
63
|
+
var _reject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
56
64
|
var _hasOwn = intrinsic.hasOwn;
|
|
57
65
|
var _isBuffer = intrinsic.isBuffer;
|
|
58
66
|
var _isArrayI = intrinsic.isArray;
|
|
@@ -65,6 +73,8 @@ var _lower = intrinsic.toLowerCase;
|
|
|
65
73
|
var _StringI = intrinsic.String;
|
|
66
74
|
var _stringify = intrinsic.stringify;
|
|
67
75
|
var _push = intrinsic.push;
|
|
76
|
+
var _pop = intrinsic.pop;
|
|
77
|
+
var _shift = intrinsic.shift;
|
|
68
78
|
var _join = intrinsic.join;
|
|
69
79
|
var _concat = intrinsic.concat;
|
|
70
80
|
var _split = intrinsic.uncurry(String.prototype.split);
|
|
@@ -293,21 +303,75 @@ function _codeSlug(kind) {
|
|
|
293
303
|
return _lower(out);
|
|
294
304
|
}
|
|
295
305
|
|
|
296
|
-
|
|
306
|
+
/** @internal The NAMED members of a caller's resource object, read ONCE each into a plain record. A
|
|
307
|
+
* validator that reads the object again for a second check lets an accessor answer the two differently,
|
|
308
|
+
* so the reading is taken here and every check works from it. A caller that runs two validators over
|
|
309
|
+
* one object passes the same reading to both rather than reading it twice.
|
|
310
|
+
*
|
|
311
|
+
* Only the named members are read. An ACME resource may carry extension members and this surface
|
|
312
|
+
* ignores them, and ignoring a member means not reading it: an accessor on a name no specification
|
|
313
|
+
* lists is caller code with nothing to decide here, and reading every own member ran it. A throwing
|
|
314
|
+
* one then left by a path carrying no `acme/` code, and a side-effecting one ran before the listed
|
|
315
|
+
* members had been captured, so it could change one the reading had not reached yet. */
|
|
316
|
+
function _readOnce(obj, names) {
|
|
317
|
+
var read = intrinsic.create(null);
|
|
318
|
+
for (var k = 0; k < names.length; k++) {
|
|
319
|
+
var name = names[k];
|
|
320
|
+
if (_hasOwn(obj, name)) read[name] = obj[name];
|
|
321
|
+
}
|
|
322
|
+
return read;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** @internal The members read from a prior `renewalWindow` result, and the bounds read from a window. */
|
|
326
|
+
var PREVIOUS_WINDOW_NAMES = ["suggestedWindow", "selectedTime"];
|
|
327
|
+
var WINDOW_BOUND_NAMES = ["start", "end"];
|
|
328
|
+
|
|
329
|
+
/** @internal The field names of each resource specification, derived from SPECS once so a reading names
|
|
330
|
+
* what it reads and a new field cannot be left out of one. */
|
|
331
|
+
var SPEC_NAMES = intrinsic.create(null);
|
|
332
|
+
(function () {
|
|
333
|
+
var kinds = _objectKeys(SPECS);
|
|
334
|
+
for (var i = 0; i < kinds.length; i++) {
|
|
335
|
+
var fields = SPECS[kinds[i]], names = [];
|
|
336
|
+
for (var j = 0; j < fields.length; j++) names[j] = fields[j].name;
|
|
337
|
+
SPEC_NAMES[kinds[i]] = names;
|
|
338
|
+
}
|
|
339
|
+
})();
|
|
340
|
+
|
|
341
|
+
function _validate(kind, obj, pre) {
|
|
297
342
|
if (!_isObject(obj)) throw E("acme/bad-" + _codeSlug(kind), "an ACME " + kind + " must be a JSON object");
|
|
298
343
|
var spec = SPECS[kind];
|
|
344
|
+
/** @internal Every own member is read ONCE, here, before any check runs, and every check below decides
|
|
345
|
+
* from this reading. The walk used to read `obj[field.name]` three times per field, for the closed-enum
|
|
346
|
+
* check, the message and the type check, and a `requiredWhen` predicate then read the object again:
|
|
347
|
+
* `o.status === "pending" || o.status === "valid"` is two more reads on its own. An accessor answers
|
|
348
|
+
* each read separately, so a `status` could pass the closed enum as "valid", answer a predicate
|
|
349
|
+
* differently to make a required field optional, and be a third value by the time the type check
|
|
350
|
+
* looked, while this reported the object as conforming. A closed status enum is what validating an
|
|
351
|
+
* ACME state is for.
|
|
352
|
+
*
|
|
353
|
+
* The ORIGINAL object is still returned, which the documented signature fixes; what this changes is
|
|
354
|
+
* that the verdict is about one reading of it. A caller that has already taken the reading passes it
|
|
355
|
+
* in, so two validators over one object do not read it twice between them. */
|
|
356
|
+
var read = pre || _readOnce(obj, SPEC_NAMES[kind]);
|
|
299
357
|
for (var f = 0; f < spec.length; f++) {
|
|
300
358
|
var field = spec[f];
|
|
301
|
-
var present = _hasOwn(
|
|
302
|
-
var required = field.required || (field.requiredWhen && field.requiredWhen(
|
|
359
|
+
var present = _hasOwn(read, field.name);
|
|
360
|
+
var required = field.required || (field.requiredWhen && field.requiredWhen(read));
|
|
303
361
|
if (!present) {
|
|
304
362
|
if (required) throw E("acme/missing-field", "an ACME " + kind + " is missing the required field " + JSON.stringify(field.name));
|
|
305
363
|
continue;
|
|
306
364
|
}
|
|
307
|
-
|
|
308
|
-
|
|
365
|
+
/** @internal ONE read of the field, which the enum check, the message and the type check all read.
|
|
366
|
+
* It was read three times, and an accessor answers each separately: a `status` could pass the closed
|
|
367
|
+
* enum as "valid" and be another value by the time the type check looked, while this reported the
|
|
368
|
+
* object as conforming. A closed status enum is what validating an ACME state is for, so the check
|
|
369
|
+
* passing for a value the object does not carry is the check not happening. */
|
|
370
|
+
var value = read[field.name];
|
|
371
|
+
if (field.enum && field.enum.indexOf(value) === -1) {
|
|
372
|
+
throw E("acme/bad-status", "the " + kind + " " + field.name + " " + JSON.stringify(value) + " is not a recognized value");
|
|
309
373
|
}
|
|
310
|
-
var err = _checkType(kind, field,
|
|
374
|
+
var err = _checkType(kind, field, value, read);
|
|
311
375
|
if (err) throw E("acme/bad-" + _codeSlug(kind), "the " + kind + " field " + JSON.stringify(field.name) + " " + err);
|
|
312
376
|
}
|
|
313
377
|
return obj;
|
|
@@ -413,12 +477,16 @@ var ERROR_NAMESPACE = "urn:ietf:params:acme:error:";
|
|
|
413
477
|
function validateProblem(obj) {
|
|
414
478
|
if (!_isObject(obj)) throw E("acme/bad-problem", "a problem document must be a JSON object");
|
|
415
479
|
if (_hasOwn(obj, "identifier")) throw E("acme/bad-problem", "a top-level problem document must not carry an identifier (RFC 8555 sec. 6.7.1)");
|
|
416
|
-
|
|
480
|
+
/** @internal ONE read of `type`, for both the string test and the namespace test. Read twice, a
|
|
481
|
+
* non-string on the second reached `.indexOf` and raised a bare TypeError carrying no code. */
|
|
482
|
+
var problemType = obj.type;
|
|
483
|
+
if (!_isString(problemType) || problemType.indexOf(ERROR_NAMESPACE) !== 0) throw E("acme/bad-problem", "an ACME problem type must be in the " + ERROR_NAMESPACE + " namespace (RFC 8555 sec. 6.7)");
|
|
417
484
|
if (_hasOwn(obj, "subproblems")) {
|
|
418
485
|
if (!Array.isArray(obj.subproblems)) throw E("acme/bad-problem", "subproblems must be an array");
|
|
419
486
|
for (var i = 0; i < obj.subproblems.length; i++) {
|
|
420
487
|
var sub = obj.subproblems[i];
|
|
421
|
-
|
|
488
|
+
var subType = _isObject(sub) ? sub.type : undefined;
|
|
489
|
+
if (!_isObject(sub) || !_isString(subType) || subType.indexOf(ERROR_NAMESPACE) !== 0) throw E("acme/bad-problem", "each subproblem must be an ACME problem document in the error namespace");
|
|
422
490
|
if (_hasOwn(sub, "identifier")) _validateOrderIdentifier(sub.identifier);
|
|
423
491
|
}
|
|
424
492
|
}
|
|
@@ -478,8 +546,12 @@ function validate(kind, obj) {
|
|
|
478
546
|
function identify(obj) {
|
|
479
547
|
if (!_isObject(obj)) return "unknown";
|
|
480
548
|
var has = function (k) { return _hasOwn(obj, k); };
|
|
549
|
+
/** @internal ONE read of `type`, for both tests below. This is a DISPATCHER: what it returns decides
|
|
550
|
+
* which validator a caller reaches for, and read twice the kind it names could be chosen from one
|
|
551
|
+
* value while the object carries another. */
|
|
552
|
+
var type = obj.type;
|
|
481
553
|
if (has("protected") && has("signature") && _isString(obj.protected) && _isString(obj.signature)) return "jws";
|
|
482
|
-
if (_isString(
|
|
554
|
+
if (_isString(type) && type.indexOf(ERROR_NAMESPACE) === 0) return "problem";
|
|
483
555
|
if (has("newNonce") && has("newAccount")) return "directory";
|
|
484
556
|
if (has("suggestedWindow")) return "renewalInfo";
|
|
485
557
|
if (has("finalize") && has("authorizations")) return "order";
|
|
@@ -650,6 +722,14 @@ function _readAcmeIdentifier(extnValue) {
|
|
|
650
722
|
* await pki.acme.verifyTlsAlpn01(certDer, token, accountJwk, { type: "dns", value: "example.org" });
|
|
651
723
|
*/
|
|
652
724
|
async function verifyTlsAlpn01(certDer, token, accountJwk, identifier) {
|
|
725
|
+
/** @internal The identifier's type and value are read ONCE, at entry, and every check below runs on those
|
|
726
|
+
* locals. Reading them after the key-authorization await let a caller answer the validity check with one
|
|
727
|
+
* name and the comparison against the certificate with another, so a name nothing validated could be
|
|
728
|
+
* matched against the SAN and the identifier would be reported as confirmed. */
|
|
729
|
+
if (!_isObject(identifier)) throw E("acme/bad-input", "an identifier { type, value } is required");
|
|
730
|
+
var idType = identifier.type;
|
|
731
|
+
var idValue = identifier.value;
|
|
732
|
+
if (!_isString(idType)) throw E("acme/bad-input", "an identifier { type, value } is required");
|
|
653
733
|
var cert = x509.parse(certDer);
|
|
654
734
|
var exts = cert.extensions || [];
|
|
655
735
|
var acmeExt = exts.filter(function (e) { return e.oid === OID_ACME_IDENTIFIER; })[0];
|
|
@@ -664,18 +744,17 @@ async function verifyTlsAlpn01(certDer, token, accountJwk, identifier) {
|
|
|
664
744
|
var san = _extDecoders.byOid[OID_SAN](sanExt.value);
|
|
665
745
|
if (!san.names || san.names.length !== 1) throw E("acme/bad-tlsalpn", "the SubjectAltName must carry EXACTLY ONE entry (RFC 8737 sec. 3)");
|
|
666
746
|
var entry = san.names[0];
|
|
667
|
-
if (
|
|
668
|
-
if (identifier.type === "dns") {
|
|
747
|
+
if (idType === "dns") {
|
|
669
748
|
if (entry.tagNumber !== 2) throw E("acme/bad-tlsalpn", "a dns identifier requires a dNSName SAN");
|
|
670
|
-
_assertDnsName(
|
|
671
|
-
if (String(entry.value).toLowerCase() !==
|
|
672
|
-
} else if (
|
|
749
|
+
_assertDnsName(idValue);
|
|
750
|
+
if (String(entry.value).toLowerCase() !== idValue) throw E("acme/bad-tlsalpn", "the SAN dNSName does not match the identifier");
|
|
751
|
+
} else if (idType === "ip") {
|
|
673
752
|
if (entry.tagNumber !== 7) throw E("acme/bad-tlsalpn", "an ip identifier requires a single iPAddress SAN (RFC 8738 sec. 6)");
|
|
674
|
-
_assertIpAddress(
|
|
753
|
+
_assertIpAddress(idValue);
|
|
675
754
|
var sanIp = _ipBytesToText(entry.value);
|
|
676
|
-
if (sanIp === null || sanIp !==
|
|
755
|
+
if (sanIp === null || sanIp !== idValue) throw E("acme/bad-tlsalpn", "the iPAddress SAN does not match the ip identifier (RFC 8738 sec. 6)");
|
|
677
756
|
} else {
|
|
678
|
-
throw E("acme/bad-tlsalpn", "unsupported tls-alpn-01 identifier type " + guard.text.showValue(
|
|
757
|
+
throw E("acme/bad-tlsalpn", "unsupported tls-alpn-01 identifier type " + guard.text.showValue(idType));
|
|
679
758
|
}
|
|
680
759
|
}
|
|
681
760
|
|
|
@@ -716,6 +795,7 @@ function _signOuter(o, payloadObj) {
|
|
|
716
795
|
function postAsGet(o) {
|
|
717
796
|
return _promised(function () {
|
|
718
797
|
if (!_isObject(o)) throw E("acme/bad-input", "a request options object is required");
|
|
798
|
+
o = _gateRequestOpts(o, "postAsGet");
|
|
719
799
|
return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, undefined);
|
|
720
800
|
});
|
|
721
801
|
}
|
|
@@ -754,7 +834,9 @@ function _assertContacts(contacts) {
|
|
|
754
834
|
*/
|
|
755
835
|
function newAccount(o) {
|
|
756
836
|
return _promised(function () {
|
|
757
|
-
if (!_isObject(o)
|
|
837
|
+
if (!_isObject(o)) throw E("acme/bad-input", "newAccount must embed the account public jwk (RFC 8555 sec. 7.3)");
|
|
838
|
+
o = _gateRequestOpts(o, "newAccount");
|
|
839
|
+
if (!_isObject(o.jwk)) throw E("acme/bad-input", "newAccount must embed the account public jwk (RFC 8555 sec. 7.3)");
|
|
758
840
|
var payload = {};
|
|
759
841
|
if (o.contact !== undefined) { _assertContacts(o.contact); payload.contact = o.contact; }
|
|
760
842
|
if (o.termsOfServiceAgreed !== undefined) {
|
|
@@ -797,19 +879,23 @@ var _HMAC_HASH = intrinsic.assign(intrinsic.create(null), { HS256: "SHA-256", HS
|
|
|
797
879
|
* var eab = await pki.acme.externalAccountBinding({ macKey, kid: "abc123", url, accountJwk });
|
|
798
880
|
*/
|
|
799
881
|
async function externalAccountBinding(o) {
|
|
800
|
-
if (!_isObject(o)
|
|
801
|
-
|
|
802
|
-
|
|
882
|
+
if (!_isObject(o)) throw E("acme/bad-input", "externalAccountBinding requires the account public jwk (sec. 7.3.4)");
|
|
883
|
+
/** @internal Every member is read ONCE, at entry, and the checks and the signature both run on those
|
|
884
|
+
* locals. The MAC key import is an await, so reading a member again after it let a caller have one jwk
|
|
885
|
+
* validated and a different one bound into the JWS. */
|
|
886
|
+
var accountJwk = o.accountJwk, kid = o.kid, url = o.url, key = o.macKey;
|
|
803
887
|
var alg = o.alg || "HS256";
|
|
888
|
+
if (!_isObject(accountJwk)) throw E("acme/bad-input", "externalAccountBinding requires the account public jwk (sec. 7.3.4)");
|
|
889
|
+
jose.assertPublicJwk(accountJwk);
|
|
890
|
+
if (!_isString(kid)) throw E("acme/bad-input", "externalAccountBinding requires the CA-issued kid");
|
|
804
891
|
if (!_HMAC_HASH[guard.text.keyOf(alg)]) throw E("acme/bad-input", "an EAB inner JWS must use an HS* MAC algorithm (sec. 7.3.4), not " + guard.text.showValue(alg));
|
|
805
|
-
var key = o.macKey;
|
|
806
892
|
if (Buffer.isBuffer(key)) {
|
|
807
893
|
try { key = await subtle.importKey("raw", key, { name: "HMAC", hash: _HMAC_HASH[alg] }, false, ["sign"]); }
|
|
808
894
|
catch (e) { throw E("acme/bad-input", "the EAB macKey could not be imported as an HMAC key", e); }
|
|
809
895
|
} else if (!key || typeof key !== "object" || key.type !== "secret") {
|
|
810
896
|
throw E("acme/bad-input", "the EAB macKey must be a raw Buffer or an HMAC (secret) CryptoKey");
|
|
811
897
|
}
|
|
812
|
-
return jose.sign({ protected: { alg: alg, kid:
|
|
898
|
+
return jose.sign({ protected: { alg: alg, kid: kid, url: url }, payload: _payloadBuf(accountJwk), key: key, profile: "eab-inner" });
|
|
813
899
|
}
|
|
814
900
|
|
|
815
901
|
function _validateOrderIdentifier(id) {
|
|
@@ -851,7 +937,9 @@ function _validateOrderIdentifier(id) {
|
|
|
851
937
|
*/
|
|
852
938
|
function newOrder(o) {
|
|
853
939
|
return _promised(function () {
|
|
854
|
-
if (!_isObject(o)
|
|
940
|
+
if (!_isObject(o)) throw E("acme/bad-order", "newOrder requires a non-empty identifiers array (RFC 8555 sec. 7.4)");
|
|
941
|
+
o = _gateRequestOpts(o, "newOrder");
|
|
942
|
+
if (!Array.isArray(o.identifiers) || o.identifiers.length === 0) throw E("acme/bad-order", "newOrder requires a non-empty identifiers array (RFC 8555 sec. 7.4)");
|
|
855
943
|
var payload = { identifiers: o.identifiers.map(_validateOrderIdentifier) };
|
|
856
944
|
if (o.notBefore !== undefined) { if (!_isRfc3339(o.notBefore)) throw E("acme/bad-order", "notBefore must be an RFC 3339 date-time"); payload.notBefore = o.notBefore; }
|
|
857
945
|
if (o.notAfter !== undefined) { if (!_isRfc3339(o.notAfter)) throw E("acme/bad-order", "notAfter must be an RFC 3339 date-time"); payload.notAfter = o.notAfter; }
|
|
@@ -879,7 +967,9 @@ function newOrder(o) {
|
|
|
879
967
|
* await pki.acme.newAuthz({ key, alg: "ES256", nonce, url, kid, identifier: { type: "dns", value: "example.org" } });
|
|
880
968
|
*/
|
|
881
969
|
function newAuthz(o) {
|
|
882
|
-
if (!_isObject(o)
|
|
970
|
+
if (!_isObject(o)) throw E("acme/bad-identifier", "newAuthz requires a single identifier object (RFC 8555 sec. 7.4.1)");
|
|
971
|
+
o = _gateRequestOpts(o, "newAuthz");
|
|
972
|
+
if (!_isObject(o.identifier)) throw E("acme/bad-identifier", "newAuthz requires a single identifier object (RFC 8555 sec. 7.4.1)");
|
|
883
973
|
return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, { identifier: _validateIdentifier(o.identifier) });
|
|
884
974
|
}
|
|
885
975
|
|
|
@@ -1026,26 +1116,37 @@ function _assertCsrIdentifiers(parsedCsr, identifiers) {
|
|
|
1026
1116
|
* await pki.acme.finalize({ key, alg: "ES256", nonce, url, kid, csr: csrDer, identifiers, accountJwk });
|
|
1027
1117
|
*/
|
|
1028
1118
|
async function finalize(o) {
|
|
1029
|
-
if (!_isObject(o)
|
|
1119
|
+
if (!_isObject(o)) throw E("acme/bad-input", "finalize requires a DER CSR BufferSource (opts.csr)");
|
|
1120
|
+
/** @internal Every member is read ONCE, at entry. The account-key comparison awaits, so reading
|
|
1121
|
+
* `identifiers` after it let a caller have one list validated and the CSR asserted against another,
|
|
1122
|
+
* which is the check that binds the request to the order. The identifier array is copied element by
|
|
1123
|
+
* element for the same reason: the elements are the caller's too. */
|
|
1124
|
+
var accountJwk = o.accountJwk, identifiers = o.identifiers;
|
|
1125
|
+
var outer = { key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid };
|
|
1126
|
+
if (!guard.bytes.isByteSource(o.csr)) throw E("acme/bad-input", "finalize requires a DER CSR BufferSource (opts.csr)");
|
|
1030
1127
|
var csrBuf = guard.bytes.snapshotSource(o.csr, E, "acme/bad-input", "the finalize CSR");
|
|
1128
|
+
var ids = null;
|
|
1129
|
+
if (identifiers !== undefined) {
|
|
1130
|
+
if (!Array.isArray(identifiers) || identifiers.length === 0) throw E("acme/bad-input", "finalize identifiers must be the non-empty order identifier array");
|
|
1131
|
+
ids = [];
|
|
1132
|
+
for (var ii = 0; ii < identifiers.length; ii++) {
|
|
1133
|
+
if (!_hasOwn(identifiers, ii)) throw E("acme/bad-input", "finalize identifiers[" + ii + "] is missing (a sparse array is not allowed)");
|
|
1134
|
+
var one = identifiers[ii];
|
|
1135
|
+
_validateOrderIdentifier(one);
|
|
1136
|
+
guard.list.append(ids, { type: one.type, value: one.value });
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1031
1139
|
var parsed = csr.parse(csrBuf);
|
|
1032
|
-
if (
|
|
1033
|
-
var accountSpki = await _jwkToSpki(
|
|
1140
|
+
if (accountJwk !== undefined) {
|
|
1141
|
+
var accountSpki = await _jwkToSpki(accountJwk);
|
|
1034
1142
|
if (parsed.subjectPublicKeyInfo && Buffer.isBuffer(parsed.subjectPublicKeyInfo.bytes) &&
|
|
1035
1143
|
parsed.subjectPublicKeyInfo.bytes.equals(accountSpki)) {
|
|
1036
1144
|
throw E("acme/key-reuse", "the finalize CSR public key must not be the account key (RFC 8555 sec. 11.1)");
|
|
1037
1145
|
}
|
|
1038
1146
|
}
|
|
1039
|
-
if (
|
|
1040
|
-
if (!Array.isArray(o.identifiers) || o.identifiers.length === 0) throw E("acme/bad-input", "finalize identifiers must be the non-empty order identifier array");
|
|
1041
|
-
for (var ii = 0; ii < o.identifiers.length; ii++) {
|
|
1042
|
-
if (!_hasOwn(o.identifiers, ii)) throw E("acme/bad-input", "finalize identifiers[" + ii + "] is missing (a sparse array is not allowed)");
|
|
1043
|
-
_validateOrderIdentifier(o.identifiers[ii]);
|
|
1044
|
-
}
|
|
1045
|
-
_assertCsrIdentifiers(parsed, o.identifiers);
|
|
1046
|
-
}
|
|
1147
|
+
if (ids !== null) _assertCsrIdentifiers(parsed, ids);
|
|
1047
1148
|
var payload = { csr: jose.base64url.encode(csrBuf) };
|
|
1048
|
-
return _signOuter(
|
|
1149
|
+
return _signOuter(outer, payload);
|
|
1049
1150
|
}
|
|
1050
1151
|
|
|
1051
1152
|
/**
|
|
@@ -1072,6 +1173,7 @@ async function finalize(o) {
|
|
|
1072
1173
|
function challengeResponse(o) {
|
|
1073
1174
|
return _promised(function () {
|
|
1074
1175
|
if (!_isObject(o)) throw E("acme/bad-input", "a request options object is required");
|
|
1176
|
+
o = _gateRequestOpts(o, "challengeResponse");
|
|
1075
1177
|
var payload = o.payload !== undefined ? o.payload : {};
|
|
1076
1178
|
if (!_isObject(payload)) throw E("acme/bad-input", "a challenge response payload must be a JSON object (RFC 8555 sec. 7.5.1)");
|
|
1077
1179
|
return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, payload);
|
|
@@ -1100,6 +1202,7 @@ function challengeResponse(o) {
|
|
|
1100
1202
|
function deactivate(o) {
|
|
1101
1203
|
return _promised(function () {
|
|
1102
1204
|
if (!_isObject(o)) throw E("acme/bad-input", "a request options object is required");
|
|
1205
|
+
o = _gateRequestOpts(o, "deactivate");
|
|
1103
1206
|
return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, { status: "deactivated" });
|
|
1104
1207
|
});
|
|
1105
1208
|
}
|
|
@@ -1128,6 +1231,7 @@ function deactivate(o) {
|
|
|
1128
1231
|
function updateAccount(o) {
|
|
1129
1232
|
return _promised(function () {
|
|
1130
1233
|
if (!_isObject(o)) throw E("acme/bad-input", "a request options object is required");
|
|
1234
|
+
o = _gateRequestOpts(o, "updateAccount");
|
|
1131
1235
|
_assertContacts(o.contact);
|
|
1132
1236
|
return _signOuter({ key: o.key, alg: o.alg, nonce: o.nonce, url: o.url, kid: o.kid }, { contact: o.contact });
|
|
1133
1237
|
});
|
|
@@ -1160,7 +1264,9 @@ function updateAccount(o) {
|
|
|
1160
1264
|
*/
|
|
1161
1265
|
function revokeCert(o) {
|
|
1162
1266
|
return _promised(function () {
|
|
1163
|
-
if (!_isObject(o)
|
|
1267
|
+
if (!_isObject(o)) throw E("acme/bad-input", "revokeCert requires a DER certificate BufferSource (opts.certificate)");
|
|
1268
|
+
o = _gateRequestOpts(o, "revokeCert");
|
|
1269
|
+
if (!guard.bytes.isByteSource(o.certificate)) throw E("acme/bad-input", "revokeCert requires a DER certificate BufferSource (opts.certificate)");
|
|
1164
1270
|
var certBuf = guard.bytes.snapshotSource(o.certificate, E, "acme/bad-input", "the revokeCert certificate");
|
|
1165
1271
|
x509.parse(certBuf);
|
|
1166
1272
|
var hasKid = _hasOwn(o, "kid");
|
|
@@ -1207,17 +1313,23 @@ function revokeCert(o) {
|
|
|
1207
1313
|
*/
|
|
1208
1314
|
async function keyChange(o) {
|
|
1209
1315
|
if (!_isObject(o)) throw E("acme/bad-input", "a keyChange options object is required");
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1316
|
+
/** @internal Every member is read ONCE, at entry, because the inner signature is an await and the outer
|
|
1317
|
+
* header is built after it. `url` in particular went into both headers from two reads, so a caller
|
|
1318
|
+
* could sign the inner JWS for one URL and the outer for another; RFC 8555 sec. 7.3.5 has the two name
|
|
1319
|
+
* the same key-change resource. */
|
|
1320
|
+
var account = o.account, oldKey = o.oldKey, newJwk = o.newJwk, newAlg = o.newAlg, newKey = o.newKey;
|
|
1321
|
+
var url = o.url, alg = o.alg, nonce = o.nonce, kid = o.kid, key = o.key;
|
|
1322
|
+
if (!_isString(account)) throw E("acme/bad-input", "keyChange requires the account URL (payload account)");
|
|
1323
|
+
if (!_isObject(oldKey)) throw E("acme/bad-input", "keyChange requires the old account public jwk (payload oldKey)");
|
|
1324
|
+
if (!_isObject(newJwk)) throw E("acme/bad-input", "keyChange requires the new account public jwk (inner header jwk)");
|
|
1325
|
+
jose.assertPublicJwk(oldKey);
|
|
1326
|
+
jose.assertPublicJwk(newJwk);
|
|
1215
1327
|
var inner = await jose.sign({
|
|
1216
|
-
protected: { alg:
|
|
1217
|
-
payload: _payloadBuf({ account:
|
|
1218
|
-
key:
|
|
1328
|
+
protected: { alg: newAlg, url: url, jwk: newJwk },
|
|
1329
|
+
payload: _payloadBuf({ account: account, oldKey: oldKey }),
|
|
1330
|
+
key: newKey, profile: "keychange-inner",
|
|
1219
1331
|
});
|
|
1220
|
-
return jose.sign({ protected: { alg:
|
|
1332
|
+
return jose.sign({ protected: { alg: alg, nonce: nonce, url: url, kid: kid }, payload: _payloadBuf(inner), key: key, profile: "acme-outer" });
|
|
1221
1333
|
}
|
|
1222
1334
|
|
|
1223
1335
|
|
|
@@ -1303,11 +1415,20 @@ function parseAriCertId(certId) {
|
|
|
1303
1415
|
* pki.acme.validateRenewalInfo({ suggestedWindow: { start: "2026-01-01T00:00:00Z", end: "2026-01-08T00:00:00Z" } });
|
|
1304
1416
|
*/
|
|
1305
1417
|
function validateRenewalInfo(obj) {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1418
|
+
/** @internal ONE reading of the object, shared with `_validate`, and one read of each bound inside the
|
|
1419
|
+
* window. The window was read twice, once by the field walk and once here, so an accessor could pass
|
|
1420
|
+
* the walk's type check and hand this a non-object, which reached `.start` and raised a bare
|
|
1421
|
+
* TypeError; and each bound was read twice more, for its RFC 3339 test and then for the comparison
|
|
1422
|
+
* that orders the two. */
|
|
1423
|
+
if (!_isObject(obj)) throw E("acme/bad-renewal-info", "an ACME renewalInfo must be a JSON object");
|
|
1424
|
+
var read = _readOnce(obj, SPEC_NAMES.renewalInfo);
|
|
1425
|
+
_validate("renewalInfo", obj, read);
|
|
1426
|
+
var w = read.suggestedWindow;
|
|
1427
|
+
var wStart = _isObject(w) ? w.start : undefined;
|
|
1428
|
+
var wEnd = _isObject(w) ? w.end : undefined;
|
|
1429
|
+
if (!_isObject(w) || !_isRfc3339(wStart) || !_isRfc3339(wEnd)) throw E("acme/bad-renewal-window", "a renewalInfo suggestedWindow must carry RFC 3339 start and end (RFC 9773 sec. 4.2)");
|
|
1309
1430
|
// allow:nan-date-comparison-unguarded -- source-validated by the rfc3339.isValid check above.
|
|
1310
|
-
if (Date.parse(
|
|
1431
|
+
if (Date.parse(wEnd) <= Date.parse(wStart)) throw E("acme/bad-renewal-window", "the renewal window end must be strictly after start (RFC 9773 sec. 4.2)");
|
|
1311
1432
|
return obj;
|
|
1312
1433
|
}
|
|
1313
1434
|
|
|
@@ -1742,6 +1863,19 @@ function _gateOpts(bag, whitelist, name) {
|
|
|
1742
1863
|
guard.identifier.assertKnownKeys(bag, whitelist, E, "acme/bad-input", "unknown " + name + " option ");
|
|
1743
1864
|
return bag;
|
|
1744
1865
|
}
|
|
1866
|
+
/** @internal The options gate for the module-level request builders, which take no option whitelist. It is
|
|
1867
|
+
* `optionsObject` alone, and what that buys is the REFUSAL of an accessor-backed field.
|
|
1868
|
+
*
|
|
1869
|
+
* These verbs validate a member and then serialize it into the payload they SIGN, and each read of an
|
|
1870
|
+
* accessor answers separately: the RFC 5280 CRLReason whitelist in `revokeCert` ran on one reading while
|
|
1871
|
+
* the signed request carried another, so the single value the whitelist exists to exclude could reach the
|
|
1872
|
+
* CA; the same gap put unvalidated contacts, identifiers and dates into signed `newAccount`, `newOrder`
|
|
1873
|
+
* and `updateAccount` payloads. The client methods were never exposed, because they already pass their
|
|
1874
|
+
* options through `_gateOpts` and this same refusal. Routing these through it is the module keeping one
|
|
1875
|
+
* rule rather than two. */
|
|
1876
|
+
function _gateRequestOpts(bag, name) {
|
|
1877
|
+
return guard.identifier.optionsObject(bag, E, "acme/bad-input", "pki.acme " + name + " options");
|
|
1878
|
+
}
|
|
1745
1879
|
/** @internal Refused at the door and again where the value is used. The checked value is RETURNED
|
|
1746
1880
|
* and it is that one the caller uses: reading the option again would ask a second time, and an
|
|
1747
1881
|
* accessor can answer differently on each read. */
|
|
@@ -1845,8 +1979,8 @@ function client(directoryUrl, opts) {
|
|
|
1845
1979
|
if (typeof v !== "string" || v === "") return;
|
|
1846
1980
|
try { jose.base64url.decode(v); }
|
|
1847
1981
|
catch (_e) { return; }
|
|
1848
|
-
while (nonces.length >= CLIENT_MAX_NONCE_POOL) nonces
|
|
1849
|
-
nonces
|
|
1982
|
+
while (nonces.length >= CLIENT_MAX_NONCE_POOL) _shift(nonces);
|
|
1983
|
+
_push(nonces, v);
|
|
1850
1984
|
}
|
|
1851
1985
|
function _isProblem(res) { return intrinsic.stringIndexOf(_lower(String(res.headers["content-type"] || "")), "application/problem+json") !== -1; }
|
|
1852
1986
|
function _serverProblem(res) {
|
|
@@ -1870,25 +2004,31 @@ function client(directoryUrl, opts) {
|
|
|
1870
2004
|
return guard.async.invoked(transport, [{ method: method, url: url, headers: reqHeaders, body: body, tls: _tlsFor(url), proxy: budgets.proxy, timeout: budgets.timeout, maxResponseBytes: budgets.maxResponseBytes }], E, "acme/bad-input", "opts.transport");
|
|
1871
2005
|
}).then(function (res) {
|
|
1872
2006
|
res = res || {};
|
|
2007
|
+
/** @internal The transport is a caller-supplied option, so its response is a caller object: the body
|
|
2008
|
+
* and the status are read ONCE here and every check and the returned value read those locals. Read
|
|
2009
|
+
* per use, the body whose length was capped need not be the body handed back, which is how a
|
|
2010
|
+
* response larger than maxResponseBytes would pass the cap. */
|
|
2011
|
+
var rbody = res.body;
|
|
2012
|
+
var status = res.status;
|
|
1873
2013
|
var h = guard.header.lowerCased(res.headers);
|
|
1874
|
-
var blen =
|
|
1875
|
-
: (Buffer.isBuffer(
|
|
2014
|
+
var blen = rbody == null ? 0 : (typeof rbody === "string" ? Buffer.byteLength(rbody, "utf8")
|
|
2015
|
+
: (Buffer.isBuffer(rbody) ? guard.bytes.lengthOf(rbody) : guard.bytes.lengthOf(guard.bytes.source(rbody, E, "acme/bad-response", "the response body"))));
|
|
1876
2016
|
if (blen > budgets.maxResponseBytes) throw E("acme/response-too-large", "the response body (" + blen + " bytes) exceeds the " + budgets.maxResponseBytes + "-byte cap");
|
|
1877
|
-
if ((method === "GET" || method === "HEAD") &&
|
|
2017
|
+
if ((method === "GET" || method === "HEAD") && status >= 300 && status < 400) {
|
|
1878
2018
|
if (redirects >= budgets.maxRedirects) throw E("acme/too-many-redirects", "the redirect budget of " + budgets.maxRedirects + " was exceeded");
|
|
1879
|
-
if (!_isString(h["location"])) throw E("acme/bad-redirect", "a " +
|
|
2019
|
+
if (!_isString(h["location"])) throw E("acme/bad-redirect", "a " + status + " redirect carried no Location header (RFC 7231 sec. 6.4)");
|
|
1880
2020
|
var next;
|
|
1881
2021
|
try { next = new URL(h["location"], url).href; }
|
|
1882
2022
|
catch (e) { throw E("acme/bad-redirect", "the redirect Location did not resolve to a valid URL", e); }
|
|
1883
2023
|
return _sendFollowing(method, _clientUrl(next), headers, body, redirects + 1);
|
|
1884
2024
|
}
|
|
1885
2025
|
_harvestNonce(h["replay-nonce"]);
|
|
1886
|
-
return { status:
|
|
2026
|
+
return guard.verdict.of({ status: status, headers: h, body: rbody == null ? "" : rbody });
|
|
1887
2027
|
});
|
|
1888
2028
|
}
|
|
1889
2029
|
|
|
1890
2030
|
function _getDirectory() {
|
|
1891
|
-
if (dirCache) return
|
|
2031
|
+
if (dirCache) return _resolve(_Promise, dirCache);
|
|
1892
2032
|
return _send("GET", dirUrl, { accept: "application/json" }, null).then(function (res) {
|
|
1893
2033
|
if (res.status !== 200) throw E("acme/bad-directory", "the ACME directory request returned HTTP " + res.status);
|
|
1894
2034
|
var obj = _json(res);
|
|
@@ -1904,12 +2044,12 @@ function client(directoryUrl, opts) {
|
|
|
1904
2044
|
});
|
|
1905
2045
|
}
|
|
1906
2046
|
function _takeNonce() {
|
|
1907
|
-
if (nonces.length) return
|
|
2047
|
+
if (nonces.length) return _resolve(_Promise, _pop(nonces));
|
|
1908
2048
|
var before = nonces.length;
|
|
1909
2049
|
return _resource("newNonce").then(function (nn) { return _send("HEAD", nn, {}, null); }).then(function (res) {
|
|
1910
2050
|
if (res.status !== 200 && res.status !== 204) { nonces.length = before; throw _serverProblem(res); }
|
|
1911
2051
|
if (!nonces.length) throw E("acme/no-nonce", "the server did not provide a usable Replay-Nonce (RFC 8555 sec. 7.2)");
|
|
1912
|
-
return nonces
|
|
2052
|
+
return _pop(nonces);
|
|
1913
2053
|
});
|
|
1914
2054
|
}
|
|
1915
2055
|
|
|
@@ -1976,7 +2116,7 @@ function client(directoryUrl, opts) {
|
|
|
1976
2116
|
});
|
|
1977
2117
|
}
|
|
1978
2118
|
function _newAuthz(identifier) {
|
|
1979
|
-
return
|
|
2119
|
+
return _resolve(_Promise, undefined).then(function () {
|
|
1980
2120
|
var canon = _validateIdentifier(identifier);
|
|
1981
2121
|
return _resource("newAuthz").then(function (url) {
|
|
1982
2122
|
return _post(url, newAuthz, { identifier: canon }, "kid", undefined, [200, 201]).then(function (res) {
|
|
@@ -2010,7 +2150,7 @@ function client(directoryUrl, opts) {
|
|
|
2010
2150
|
capPolls = budget.maxPolls != null ? guard.limits.cap(budget.maxPolls, "maxPolls", maxPolls, { E: E, code: "acme/bad-input", min: 1, max: 1000 }) : maxPolls;
|
|
2011
2151
|
capWait = budget.maxTotalWait != null ? guard.limits.cap(budget.maxTotalWait, "maxTotalWait", maxTotalWait, { E: E, code: "acme/bad-input", min: 0, max: retryAfter.MAX_RETRY_AFTER_SECONDS }) : maxTotalWait;
|
|
2012
2152
|
url = _clientUrl(url);
|
|
2013
|
-
} catch (e) { return
|
|
2153
|
+
} catch (e) { return _reject(_Promise, e); }
|
|
2014
2154
|
var terminal = kind === "order" ? { ready: true, valid: true, invalid: true } : { valid: true, invalid: true, deactivated: true, expired: true, revoked: true };
|
|
2015
2155
|
var last = null, count = 0, waited = 0;
|
|
2016
2156
|
function step() {
|
|
@@ -2027,7 +2167,7 @@ function client(directoryUrl, opts) {
|
|
|
2027
2167
|
waited += delaySec;
|
|
2028
2168
|
if (waited > capWait) throw E("acme/poll-exhausted", "the " + kind + " poll exceeded the total wait budget of " + capWait + " seconds");
|
|
2029
2169
|
if (typeof budget.onRetryAfter === "function") budget.onRetryAfter(delaySec);
|
|
2030
|
-
return
|
|
2170
|
+
return _resolve(_Promise, sleep(delaySec * constants.TIME.seconds(1))).then(step);
|
|
2031
2171
|
});
|
|
2032
2172
|
}
|
|
2033
2173
|
return step();
|
|
@@ -2092,7 +2232,7 @@ function client(directoryUrl, opts) {
|
|
|
2092
2232
|
}
|
|
2093
2233
|
|
|
2094
2234
|
function _downloadCertificate(url, opts) {
|
|
2095
|
-
return
|
|
2235
|
+
return _resolve(_Promise, undefined).then(function () {
|
|
2096
2236
|
opts = _gateOpts(opts, _METHOD_OPTS.download, "downloadCertificate");
|
|
2097
2237
|
var select = opts.selectChain;
|
|
2098
2238
|
if (select !== undefined && typeof select !== "function") throw E("acme/bad-input", "downloadCertificate opts.selectChain must be a function");
|
|
@@ -2111,7 +2251,7 @@ function client(directoryUrl, opts) {
|
|
|
2111
2251
|
boundToKey: binding.spki !== undefined, boundToIdentifiers: binding.ids !== undefined };
|
|
2112
2252
|
}
|
|
2113
2253
|
if (!select) return result(primary);
|
|
2114
|
-
return
|
|
2254
|
+
return _resolve(_Promise, select(primary)).then(function (primaryMatch) {
|
|
2115
2255
|
if (primaryMatch) return result(primary);
|
|
2116
2256
|
if (linkError) throw linkError;
|
|
2117
2257
|
var leaf = primary.certificate, toFetch = alternates.slice(0, maxAlt), overBudget = alternates.length > maxAlt, idx = 0;
|
|
@@ -2122,7 +2262,7 @@ function client(directoryUrl, opts) {
|
|
|
2122
2262
|
}
|
|
2123
2263
|
return _fetchCertChain(toFetch[idx++]).then(function (cand) {
|
|
2124
2264
|
if (!cand.certificate.equals(leaf)) throw E("acme/bad-alternate", "an alternate chain did not start with the same end-entity certificate (RFC 8555 sec. 7.4.2)");
|
|
2125
|
-
return
|
|
2265
|
+
return _resolve(_Promise, select(cand)).then(function (m) { return m ? result(cand) : next(); });
|
|
2126
2266
|
});
|
|
2127
2267
|
}
|
|
2128
2268
|
return next();
|
|
@@ -2243,13 +2383,22 @@ function client(directoryUrl, opts) {
|
|
|
2243
2383
|
}
|
|
2244
2384
|
|
|
2245
2385
|
function _renewalWindow(certDer, o) {
|
|
2246
|
-
return
|
|
2386
|
+
return _resolve(_Promise, undefined).then(function () {
|
|
2247
2387
|
o = _gateOpts(o, _METHOD_OPTS.renewalWindow, "renewalWindow");
|
|
2248
2388
|
if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "renewalWindow requires a DER certificate BufferSource");
|
|
2249
2389
|
if (o.random !== undefined && typeof o.random !== "function") throw E("acme/bad-input", "renewalWindow opts.random must be a function returning a number in [0, 1]");
|
|
2250
2390
|
if (o.clock !== undefined && typeof o.clock !== "function") throw E("acme/bad-input", "renewalWindow opts.clock must be a function returning epoch milliseconds");
|
|
2251
2391
|
if (o.replaced !== undefined && typeof o.replaced !== "boolean") throw E("acme/bad-input", "renewalWindow opts.replaced must be a boolean");
|
|
2252
2392
|
if (o.previous !== undefined && !_isObject(o.previous)) throw E("acme/bad-input", "renewalWindow opts.previous must be a prior renewalWindow result object");
|
|
2393
|
+
/** @internal `previous` is READ ONCE, here, before the await below, and the stickiness decision
|
|
2394
|
+
* reads this reading. It was validated as an object here and then read three more times after the
|
|
2395
|
+
* renewalInfo fetch: `o.previous.suggestedWindow` twice in one expression, and
|
|
2396
|
+
* `o.previous.selectedTime` once for its string test and again for the parse. An accessor answers
|
|
2397
|
+
* each read separately and the fetch is the window in which to change its answer, so the prior
|
|
2398
|
+
* window this compares against need not be the one the caller validated, and a non-object arriving
|
|
2399
|
+
* after the await reached `.start` and raised a bare TypeError. */
|
|
2400
|
+
var prev = o.previous === undefined ? undefined : _readOnce(o.previous, PREVIOUS_WINDOW_NAMES);
|
|
2401
|
+
var prevWindow = prev && _isObject(prev.suggestedWindow) ? _readOnce(prev.suggestedWindow, WINDOW_BOUND_NAMES) : null;
|
|
2253
2402
|
var _clk = typeof o.clock === "function" ? o.clock : clock;
|
|
2254
2403
|
function clk() { var t = _clk(); if (typeof t !== "number" || !isFinite(t)) throw E("acme/bad-input", "the renewalWindow clock returned a non-finite value"); return t; }
|
|
2255
2404
|
var notAfter = x509.parse(certDer).validity.notAfter;
|
|
@@ -2262,10 +2411,10 @@ function client(directoryUrl, opts) {
|
|
|
2262
2411
|
var startMs = Date.parse(w.start), endMs = Date.parse(w.end);
|
|
2263
2412
|
var notAfterMs = guard.time.instantOf(notAfter);
|
|
2264
2413
|
var effEnd = Math.min(endMs, notAfterMs), effStart = Math.min(startMs, effEnd);
|
|
2265
|
-
var pw =
|
|
2414
|
+
var pw = prevWindow;
|
|
2266
2415
|
var selectedMs;
|
|
2267
|
-
if (pw && pw.start === w.start && pw.end === w.end && _isString(
|
|
2268
|
-
var prevMs = Date.parse(
|
|
2416
|
+
if (pw && pw.start === w.start && pw.end === w.end && _isString(prev.selectedTime)) {
|
|
2417
|
+
var prevMs = Date.parse(prev.selectedTime);
|
|
2269
2418
|
if (!isFinite(prevMs)) throw E("acme/bad-input", "renewalWindow opts.previous.selectedTime must be an RFC 3339 date-time");
|
|
2270
2419
|
selectedMs = Math.max(effStart, Math.min(effEnd, prevMs));
|
|
2271
2420
|
} else {
|
|
@@ -2289,7 +2438,7 @@ function client(directoryUrl, opts) {
|
|
|
2289
2438
|
}
|
|
2290
2439
|
|
|
2291
2440
|
function _scheduleRenewal(certDer, o) {
|
|
2292
|
-
return _resolve(
|
|
2441
|
+
return _resolve(_Promise).then(function () {
|
|
2293
2442
|
o = _gateOpts(o, _METHOD_OPTS.scheduleRenewal, "scheduleRenewal");
|
|
2294
2443
|
if (!guard.bytes.isByteSource(certDer)) throw E("acme/bad-input", "scheduleRenewal requires a DER certificate BufferSource");
|
|
2295
2444
|
if (o.random !== undefined && typeof o.random !== "function") throw E("acme/bad-input", "scheduleRenewal opts.random must be a function returning a number in [0, 1]");
|
|
@@ -2308,7 +2457,7 @@ function client(directoryUrl, opts) {
|
|
|
2308
2457
|
function _clk() { var t = clock(); if (typeof t !== "number" || !intrinsic.isFinite(t)) throw E("acme/bad-input", "the scheduleRenewal clock returned a non-finite value"); return t; }
|
|
2309
2458
|
var startMs = _clk(), checks = 0, tempBackoffMs = tempBaseMs, previous = null;
|
|
2310
2459
|
var cert = guard.bytes.snapshotSource(certDer, AcmeError, "acme/bad-input", "the scheduleRenewal certificate");
|
|
2311
|
-
function _boundedSleep(ms) { if (maxWaitMs != null) { ms = intrinsic.min(ms, intrinsic.max(0, maxWaitMs - (_clk() - startMs))); } return _resolve(
|
|
2460
|
+
function _boundedSleep(ms) { if (maxWaitMs != null) { ms = intrinsic.min(ms, intrinsic.max(0, maxWaitMs - (_clk() - startMs))); } return _resolve(_Promise, sleep(ms)); }
|
|
2312
2461
|
function _sleepThenLoop(ms) { return (maxChecks != null && checks >= maxChecks) ? { reason: "budget" } : _boundedSleep(ms).then(loop); }
|
|
2313
2462
|
function _stop() {
|
|
2314
2463
|
if (!o.shouldStop) return false;
|
|
@@ -2333,7 +2482,7 @@ function client(directoryUrl, opts) {
|
|
|
2333
2482
|
var _rc;
|
|
2334
2483
|
try { _rc = o.renew(decision); }
|
|
2335
2484
|
catch (e) { throw E("acme/bad-input", "the scheduleRenewal renew callback threw", e); }
|
|
2336
|
-
return _resolve(
|
|
2485
|
+
return _resolve(_Promise, _rc).then(function (next) {
|
|
2337
2486
|
if (next == null) return { reason: "renewed", decision: decision };
|
|
2338
2487
|
if (!guard.bytes.isByteSource(next)) throw E("acme/bad-input", "scheduleRenewal opts.renew must return a DER certificate BufferSource or a nullish value");
|
|
2339
2488
|
cert = guard.bytes.snapshotSource(next, AcmeError, "acme/bad-input", "the scheduleRenewal renewed certificate"); previous = null; return loop();
|