@blamejs/pki 0.5.9 → 0.5.11
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 +80 -19
- package/README.md +1 -1
- package/lib/acme.js +5 -5
- package/lib/asn1-der.js +1 -1
- package/lib/attrcert-sign.js +1 -1
- package/lib/cbor-det.js +4 -4
- package/lib/cmc-build.js +5 -5
- package/lib/cmc-verify.js +14 -11
- package/lib/cms-decrypt.js +2 -2
- package/lib/cms-sign.js +3 -5
- package/lib/cms-verify.js +9 -6
- package/lib/constants.js +1 -1
- package/lib/crl-sign.js +1 -1
- package/lib/crmf-sign.js +1 -1
- package/lib/ct.js +8 -5
- package/lib/est.js +20 -14
- package/lib/guard-all.js +12 -12
- package/lib/guard-bytes.js +568 -82
- package/lib/guard-compress.js +2 -2
- package/lib/guard-der.js +1 -1
- package/lib/guard-identifier.js +694 -6
- package/lib/guard-limits.js +2 -2
- package/lib/guard-parsed.js +10 -6
- package/lib/guard-range.js +1 -1
- package/lib/guard-secret.js +1 -1
- package/lib/guard-text.js +1 -1
- package/lib/guard-time.js +65 -4
- package/lib/hpke.js +1 -1
- package/lib/http-digest.js +1 -1
- package/lib/inspect.js +8 -3
- package/lib/jose.js +1 -1
- package/lib/key.js +48 -6
- package/lib/lint.js +15 -8
- package/lib/ocsp-verify.js +10 -4
- package/lib/ocsp.js +35 -6
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +99 -22
- package/lib/schema-c509.js +5 -5
- package/lib/schema-cmp.js +1 -1
- package/lib/schema-crl.js +1 -1
- package/lib/schema-engine.js +2 -2
- package/lib/schema-pkix.js +1 -1
- package/lib/sigstore.js +6 -2
- package/lib/smime.js +1 -1
- package/lib/trust.js +2 -2
- package/lib/tsp-sign.js +2 -2
- package/lib/validator-attcert.js +3 -3
- package/lib/validator-cose.js +3 -3
- package/lib/webauthn-mds.js +17 -17
- package/lib/webauthn.js +12 -12
- package/lib/webcrypto.js +16 -7
- package/lib/x509-sign.js +1 -1
- package/package.json +4 -2
- package/sbom.cdx.json +6 -6
package/lib/cms-sign.js
CHANGED
|
@@ -264,7 +264,7 @@ function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes,
|
|
|
264
264
|
// A signing-time Time value: UTCTime before 2050, GeneralizedTime from 2050 (RFC 5652 sec. 11.3 /
|
|
265
265
|
// RFC 5280 sec. 4.1.2.5). A caller Date overrides; false omits the attribute (handled above).
|
|
266
266
|
function _timeValue(when) {
|
|
267
|
-
var d = (when
|
|
267
|
+
var d = guard.time.isDate(when) ? when : new Date();
|
|
268
268
|
return d.getUTCFullYear() < 2050 ? b.utcTime(d) : b.generalizedTime(d);
|
|
269
269
|
}
|
|
270
270
|
|
|
@@ -368,8 +368,7 @@ function sign(content, signers, opts) {
|
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
function _sign(content, signers, opts) {
|
|
371
|
-
opts = opts
|
|
372
|
-
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.sign options must be an object");
|
|
371
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.sign options");
|
|
373
372
|
guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
374
373
|
// The arguments were copied at entry (see `sign` above), which is what makes the refusal below
|
|
375
374
|
// hold: flipping signedAttributes from true to false after the call returns would otherwise skip
|
|
@@ -590,8 +589,7 @@ function countersign(cmsInput, signers, opts) {
|
|
|
590
589
|
}
|
|
591
590
|
|
|
592
591
|
function _countersign(cmsInput, signers, opts) {
|
|
593
|
-
opts = opts
|
|
594
|
-
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.countersign options must be an object");
|
|
592
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.countersign options");
|
|
595
593
|
guard.identifier.assertKnownKeys(opts, KNOWN_COUNTERSIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
596
594
|
var list = Array.isArray(signers) ? signers : [signers];
|
|
597
595
|
if (!list.length) throw _err("cms/bad-input", "pki.cms.countersign requires at least one countersigner");
|
package/lib/cms-verify.js
CHANGED
|
@@ -579,7 +579,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
|
|
|
579
579
|
// Coverage residual, and deliberately kept. Every value reaching here was already walked by the
|
|
580
580
|
// decoder, which validates an id-countersignature by content and not by the attribute type. A
|
|
581
581
|
// malformed one therefore refused the whole message before verify was entered, so this catch
|
|
582
|
-
// cannot be reached through the public path. That is settled
|
|
582
|
+
// cannot be reached through the public path. That is settled behavior: a message the decoder
|
|
583
583
|
// has found to be malformed has no sound remainder to report a verdict over, so it is refused
|
|
584
584
|
// whole instead of surfaced as one failed countersignature.
|
|
585
585
|
// cms-verify.test.js pins that through the shipped verbs; this stays because the walk is also
|
|
@@ -693,8 +693,7 @@ function verify(input, opts) {
|
|
|
693
693
|
}
|
|
694
694
|
|
|
695
695
|
function _verify(input, opts) {
|
|
696
|
-
opts = opts
|
|
697
|
-
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.verify options must be an object");
|
|
696
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.verify options");
|
|
698
697
|
// An unrecognized option is refused, not swallowed. This is what kept the missing trust seam
|
|
699
698
|
// silent: a caller writing `trustAnchors` before it existed -- or `trustAnchor` now -- got a
|
|
700
699
|
// verdict that looked anchored and was not. The guard rejects through a (code, message) FACTORY
|
|
@@ -744,7 +743,7 @@ function _verify(input, opts) {
|
|
|
744
743
|
// read a promise turn later, and everything in it stays caller-owned across that gap: the array
|
|
745
744
|
// can be re-pointed, an anchor's DER rewritten, the validation instant moved. Any of those would
|
|
746
745
|
// have the chain judged against a configuration the caller never asked for while the verdict
|
|
747
|
-
// reports the one they did. The same
|
|
746
|
+
// reports the one they did. The same defense the input and the certificates already have.
|
|
748
747
|
var trustCfg = _snapshotTrust(opts);
|
|
749
748
|
return Promise.all(parsed.signerInfos.map(function (si) {
|
|
750
749
|
// res.valid reflects only the PRIMARY signerInfos; a countersignature / unsigned attribute is
|
|
@@ -807,7 +806,11 @@ function _cloneAnchorValue(v, depth) {
|
|
|
807
806
|
// field by field yields `{}` -- an anchor's `distrustAfter: { emailProtection: <Date> }` would
|
|
808
807
|
// survive the snapshot as an empty object and be rejected as an invalid date, disabling the very
|
|
809
808
|
// policy it encodes. Copied by value, like the validation instant.
|
|
810
|
-
|
|
809
|
+
// The instant comes through the guard's intrinsic read, so a caller's `getTime` override can
|
|
810
|
+
// neither throw out of the snapshot nor put a different instant in the copy than the original
|
|
811
|
+
// holds -- which would leave the anchor policy that was checked and the one that is applied two
|
|
812
|
+
// different values.
|
|
813
|
+
if (guard.time.isDate(v)) return new Date(guard.time.instantOf(v));
|
|
811
814
|
// A structure too deep to copy is REFUSED, not shared. Returning it by reference at the cap
|
|
812
815
|
// would leave part of the anchor caller-mutable across the chain walk while the rest was
|
|
813
816
|
// snapshotted -- a guarantee that holds for the shallow fields and quietly lapses for the deep
|
|
@@ -835,7 +838,7 @@ function _snapshotTrust(opts) {
|
|
|
835
838
|
}
|
|
836
839
|
return {
|
|
837
840
|
trustAnchors: anchors,
|
|
838
|
-
time: opts.time
|
|
841
|
+
time: guard.time.isDate(opts.time) ? new Date(guard.time.instantOf(opts.time)) : opts.time,
|
|
839
842
|
requiredEku: Array.isArray(opts.requiredEku) ? opts.requiredEku.slice() : opts.requiredEku,
|
|
840
843
|
checkPurpose: opts.checkPurpose,
|
|
841
844
|
};
|
package/lib/constants.js
CHANGED
|
@@ -165,7 +165,7 @@ var LIMITS = {
|
|
|
165
165
|
// is copied/tokenized so a hostile server cannot drive unbounded work parsing it
|
|
166
166
|
// (CWE-770). 8 KiB clears any realistic multi-scheme Digest challenge.
|
|
167
167
|
HTTP_AUTH_HEADER_MAX_BYTES: BYTES.kib(8),
|
|
168
|
-
// Deterministic-CBOR codec ceilings (RFC 8949), the DER
|
|
168
|
+
// Deterministic-CBOR codec ceilings (RFC 8949), the DER neighbors' siblings:
|
|
169
169
|
// a whole-document cap refused before the walk, a nesting cap, and a per-value
|
|
170
170
|
// bignum ceiling the document cap can't provide. Unlike DER_MAX_INTEGER_BYTES,
|
|
171
171
|
// the bignum cap carries NO +1 sign octet -- a CBOR tag-2/3 bignum body is pure
|
package/lib/crl-sign.js
CHANGED
|
@@ -426,7 +426,7 @@ function _sign(spec, issuer, opts) {
|
|
|
426
426
|
nextU = _timeDer(spec.nextUpdate, "nextUpdate");
|
|
427
427
|
// allow:nan-date-comparison-unguarded -- both operands are guard.time.assertValid'd via _timeDer on the
|
|
428
428
|
// two lines above (thisUpdate + nextUpdate), so an Invalid Date throws before this comparison.
|
|
429
|
-
if (spec.nextUpdate
|
|
429
|
+
if (guard.time.instantOf(spec.nextUpdate) < guard.time.instantOf(spec.thisUpdate)) throw _err("crl/bad-input", "nextUpdate must not be before thisUpdate (RFC 5280 sec. 5.1.2.5)");
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
var extResult = _buildCrlExtensions(spec, { issuerCert: issuerCert, issuerSpki: issuerSpki });
|
package/lib/crmf-sign.js
CHANGED
|
@@ -74,7 +74,7 @@ function _encodeOptionalValidity(validity) {
|
|
|
74
74
|
if (nb != null) parts.push(b.explicit(0, _b.timeDer(nb, "validity notBefore")));
|
|
75
75
|
if (na != null) parts.push(b.explicit(1, _b.timeDer(na, "validity notAfter")));
|
|
76
76
|
// allow:nan-date-comparison-unguarded -- both instants passed timeDer's guard.time.assertValid above.
|
|
77
|
-
if (nb != null && na != null &&
|
|
77
|
+
if (nb != null && na != null && guard.time.instantOf(nb) > guard.time.instantOf(na)) throw _err("crmf/bad-validity", "notBefore must not be after notAfter");
|
|
78
78
|
return b.implicit(4, b.sequence(parts));
|
|
79
79
|
}
|
|
80
80
|
// CertTemplate ::= SEQUENCE { [0..9] all IMPLICIT OPTIONAL }. A request omits serialNumber [1] / signingAlg
|
package/lib/ct.js
CHANGED
|
@@ -666,7 +666,7 @@ function _parseTemporalInterval(ti) {
|
|
|
666
666
|
var start = rfc3339.parse(ti.start_inclusive, _ctErr, "ct/bad-date", "temporal_interval.start_inclusive");
|
|
667
667
|
var end = rfc3339.parse(ti.end_exclusive, _ctErr, "ct/bad-date", "temporal_interval.end_exclusive");
|
|
668
668
|
// allow:nan-date-comparison-unguarded -- start/end are rfc3339.parse results, guaranteed non-NaN (rfc3339.isValid rejects a NaN date).
|
|
669
|
-
if (
|
|
669
|
+
if (guard.time.instantOf(start) >= guard.time.instantOf(end)) throw _ctErr("ct/bad-log-list", "a CT log temporal_interval start_inclusive must be strictly before end_exclusive");
|
|
670
670
|
return { startInclusive: start, endExclusive: end };
|
|
671
671
|
}
|
|
672
672
|
|
|
@@ -700,10 +700,13 @@ function _sameTemporal(a, b) {
|
|
|
700
700
|
// inputs; the remaining c8 sub-arm is a short-circuit path (when `a == null` is true, `b == null` is
|
|
701
701
|
// never evaluated) with no additional reachable input.
|
|
702
702
|
if (a == null || b == null) return false;
|
|
703
|
-
return
|
|
703
|
+
return guard.time.instantOf(a.startInclusive) === guard.time.instantOf(b.startInclusive) &&
|
|
704
|
+
guard.time.instantOf(a.endExclusive) === guard.time.instantOf(b.endExclusive);
|
|
704
705
|
}
|
|
705
706
|
function _logsAgree(a, b) {
|
|
706
|
-
return a.state.name === b.state.name &&
|
|
707
|
+
return a.state.name === b.state.name &&
|
|
708
|
+
guard.time.instantOf(a.state.since) === guard.time.instantOf(b.state.since) &&
|
|
709
|
+
_sameTemporal(a.temporalInterval, b.temporalInterval);
|
|
707
710
|
}
|
|
708
711
|
|
|
709
712
|
/**
|
|
@@ -783,7 +786,7 @@ function parseLogList(json, opts) {
|
|
|
783
786
|
// The covered certificate's notAfter for the temporal gate: an explicit opts.certNotAfter, else derived
|
|
784
787
|
// from a leafCert (entryType 0) via the shipped x509 parser, else null (a precert TBS can't be parsed).
|
|
785
788
|
function _resolveNotAfter(entry, opts) {
|
|
786
|
-
if (opts.certNotAfter
|
|
789
|
+
if (guard.time.isDate(opts.certNotAfter)) return opts.certNotAfter;
|
|
787
790
|
if (entry && entry.entryType === 0 && entry.leafCert != null) {
|
|
788
791
|
// Inline require (circular-load): ct.js is loaded before schema-x509 finishes initializing in the
|
|
789
792
|
// index chain (schema-x509 -> schema-pkix is mid-init when ct.js is first required), so a top-level
|
|
@@ -845,7 +848,7 @@ async function verifySctWithLogList(entry, sct, logList, opts) {
|
|
|
845
848
|
if (!log.state.trusted) {
|
|
846
849
|
if (!log.state.conditional) throw _ctErr("ct/log-untrusted", "the CT log state '" + log.state.name + "' is not trusted");
|
|
847
850
|
var ts = guard.range.uint64(sct.timestamp, _ctErr, "ct/bad-input", "sct.timestamp");
|
|
848
|
-
if (ts >= BigInt(log.state.since
|
|
851
|
+
if (ts >= BigInt(guard.time.instantOf(log.state.since))) throw _ctErr("ct/log-untrusted", "the CT log is retired and the SCT is not timestamped before its retirement (" + log.state.since.toISOString() + ")");
|
|
849
852
|
}
|
|
850
853
|
// Temporal gate (fail-closed): the covered cert's notAfter must be in [start_inclusive, end_exclusive).
|
|
851
854
|
if (log.temporalInterval) {
|
package/lib/est.js
CHANGED
|
@@ -699,8 +699,11 @@ function _attr(typeOid, valueNodes) { return asn1.build.sequence([asn1.build.oid
|
|
|
699
699
|
// attribute whose value is their RFC 4648 base64 (SIZE 1..255). The builder
|
|
700
700
|
// takes caller-supplied binding bytes and never fakes one.
|
|
701
701
|
function challengePasswordFromTlsUnique(channelBinding) {
|
|
702
|
-
|
|
703
|
-
|
|
702
|
+
// The count comes from the guard, so an own `length` on the caller's Buffer cannot report a
|
|
703
|
+
// full one as empty. `length` is shadowable on any instance, and `Object.defineProperty(buf,
|
|
704
|
+
// "length", { get: function () { return 0; } })` had this refuse bytes that were there.
|
|
705
|
+
if (!Buffer.isBuffer(channelBinding) || guard.bytes.lengthOf(channelBinding) === 0) throw E("est/bad-input", "challengePasswordFromTlsUnique requires the channel-binding bytes");
|
|
706
|
+
var b64 = guard.bytes.view(channelBinding, EstError, "est/bad-input", "the channel-binding bytes").toString("base64");
|
|
704
707
|
if (b64.length > 255) throw E("est/tls-unique-too-long", "the base64 tls-unique value exceeds 255 octets (RFC 7030 sec. 3.5)");
|
|
705
708
|
return _attr(OID_CHALLENGE_PASSWORD, [asn1.build.printable(b64)]);
|
|
706
709
|
}
|
|
@@ -954,7 +957,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
954
957
|
// Measure an injected string body as UTF-8 -- the byte width the body is decoded/transfer-decoded at,
|
|
955
958
|
// and what the real socket transport counts -- so a non-ASCII body (multi-byte chars are ~half the
|
|
956
959
|
// count under latin1) cannot undercount past the DoS cap and reach body handling.
|
|
957
|
-
var blen = Buffer.isBuffer(res.body) ? res.body
|
|
960
|
+
var blen = Buffer.isBuffer(res.body) ? guard.bytes.lengthOf(res.body) : Buffer.byteLength(String(res.body == null ? "" : res.body), "utf8");
|
|
958
961
|
if (blen > budgets.maxResponseBytes) throw E("est/response-too-large", "the response body (" + blen + " bytes) exceeds the " + budgets.maxResponseBytes + "-byte cap (RFC 7030 sec. 6)");
|
|
959
962
|
// The transport contract returns lowercased headers, but the injectable seam only promises
|
|
960
963
|
// { status, headers, body }; normalize here so a redirect Location / WWW-Authenticate from an
|
|
@@ -1104,7 +1107,7 @@ function _certsResult(op, res, opts, csrSpki) {
|
|
|
1104
1107
|
if (verdict.status !== "ok") throw E("est/http-error", "an EST " + op + " response must be HTTP 200 or 202 (RFC 7030 sec. 4.1.3 / 4.2.3), got " + res.status);
|
|
1105
1108
|
// Measure the body as UTF-8 for parity with the transport cap above (this check only distinguishes empty
|
|
1106
1109
|
// from non-empty, where the encoding is immaterial, but the shape is kept uniform so no site undercounts).
|
|
1107
|
-
var bodyLen = Buffer.isBuffer(res.body) ? res.body
|
|
1110
|
+
var bodyLen = Buffer.isBuffer(res.body) ? guard.bytes.lengthOf(res.body) : Buffer.byteLength(String(res.body == null ? "" : res.body), "utf8");
|
|
1108
1111
|
if (bodyLen === 0) throw E("est/empty-body", "a 200 " + op + " response carried an empty body (RFC 7030 sec. 4.1.3 / 4.2.3)");
|
|
1109
1112
|
var parsed = parseCertsOnly(transferDecode(res.body));
|
|
1110
1113
|
if (op === "cacerts") return { certificates: parsed.certificates, crls: parsed.crls };
|
|
@@ -1231,7 +1234,7 @@ function fullcmc(baseUrl, request, opts) {
|
|
|
1231
1234
|
_knownOpts(opts, FULLCMC_OPTS, "fullcmc");
|
|
1232
1235
|
der = _cmcRequestDer(request);
|
|
1233
1236
|
// Confirm this IS a Full PKI Request before any of it goes over the wire. The
|
|
1234
|
-
// bytes are about to be
|
|
1237
|
+
// bytes are about to be labeled `smime-type=CMC-request`, so a PKIResponse or
|
|
1235
1238
|
// an unparseable blob handed in by mistake would be POSTed to a CA under a
|
|
1236
1239
|
// label that does not describe it -- and, since only an `issued` outcome is
|
|
1237
1240
|
// correlated, could still come back as a pending or rejected verdict, making
|
|
@@ -1449,11 +1452,14 @@ function _cmcSent(opts, der) {
|
|
|
1449
1452
|
// Uint8Array would leave those forms aliased across the request's whole flight, which is the same
|
|
1450
1453
|
// window this exists to close, open for exactly the inputs that came in by the wider door.
|
|
1451
1454
|
// A DataView is copied over its OWN window, not the whole backing buffer it happens to sit in.
|
|
1455
|
+
// One call, because guard.bytes decides what a byte form is and where its bytes are. The three
|
|
1456
|
+
// hand-rolled branches this replaces read `v.buffer`, `v.byteOffset` and `v.byteLength` off the
|
|
1457
|
+
// value, and all three are accessors a caller's subclass can answer: one that lied copied a
|
|
1458
|
+
// different store than the array held, so the bytes checked here and the bytes sent later were
|
|
1459
|
+
// two different reads.
|
|
1452
1460
|
function _copyBytes(v) {
|
|
1453
|
-
if (
|
|
1454
|
-
|
|
1455
|
-
if (v instanceof ArrayBuffer) return Buffer.from(new Uint8Array(v));
|
|
1456
|
-
return v;
|
|
1461
|
+
if (!guard.bytes.isByteSource(v)) return v;
|
|
1462
|
+
return guard.bytes.snapshotSource(v, EstError, "est/bad-input", "a byte field of the request");
|
|
1457
1463
|
}
|
|
1458
1464
|
|
|
1459
1465
|
// The exchange-binding controls the request ACTUALLY carries (RFC 5272 sec. 6.6
|
|
@@ -1567,7 +1573,7 @@ function _assertAgrees(name, supplied, carried) {
|
|
|
1567
1573
|
} else {
|
|
1568
1574
|
// Through the shared authoring guard, not a bare BigInt(): a `number` above
|
|
1569
1575
|
// Number.MAX_SAFE_INTEGER has already lost the digits that distinguish it from
|
|
1570
|
-
// its
|
|
1576
|
+
// its neighbor, so converting it here would compare a value the caller never
|
|
1571
1577
|
// wrote. A transaction identifier is an unbounded INTEGER on the wire; large
|
|
1572
1578
|
// ones are passed as a bigint, and this says so rather than rounding.
|
|
1573
1579
|
same = carried != null &&
|
|
@@ -1665,13 +1671,13 @@ function _fullcmcResult(res, opts, wanted, sent) {
|
|
|
1665
1671
|
"an EST /fullcmc response must be HTTP 200 or 202 (RFC 7030 sec. 4.3.2), got " + res.status);
|
|
1666
1672
|
}
|
|
1667
1673
|
|
|
1668
|
-
var bodyLen = Buffer.isBuffer(res.body) ? res.body
|
|
1674
|
+
var bodyLen = Buffer.isBuffer(res.body) ? guard.bytes.lengthOf(res.body) : Buffer.byteLength(String(res.body == null ? "" : res.body), "utf8");
|
|
1669
1675
|
if (bodyLen === 0) throw E("est/empty-body", "a 200 /fullcmc response carried an empty body (RFC 7030 sec. 4.3.2)");
|
|
1670
1676
|
var der = transferDecode(res.body);
|
|
1671
1677
|
|
|
1672
1678
|
// FC5 admits two smime-types, and the LABEL must agree with the BYTES: a
|
|
1673
1679
|
// certs-only label over a Full PKI Response (or the reverse) is a server
|
|
1674
|
-
//
|
|
1680
|
+
// mislabeling its own body, and accepting either shape under either label
|
|
1675
1681
|
// would make the content-type check decorative.
|
|
1676
1682
|
// Through the shared case-insensitive accessor: HTTP header names are
|
|
1677
1683
|
// case-insensitive (RFC 9110 sec. 5.1), the classifier already reads them that
|
|
@@ -2040,7 +2046,7 @@ async function _serverkeygenResult(res, opts, derived) {
|
|
|
2040
2046
|
// A 202 is an enrollment posture (RFC 7030 sec. 4.4 treats /serverkeygen as an enroll): surfaced, never slept.
|
|
2041
2047
|
if (verdict.status === "retry") return { retry: true, retryAfterSeconds: verdict.retryAfterSeconds, retryAfterDate: verdict.retryAfterDate };
|
|
2042
2048
|
if (verdict.status !== "ok") throw E("est/http-error", "an EST serverkeygen response must be HTTP 200 or 202 (RFC 7030 sec. 4.4.2), got " + res.status);
|
|
2043
|
-
var bodyLen = Buffer.isBuffer(res.body) ? res.body
|
|
2049
|
+
var bodyLen = Buffer.isBuffer(res.body) ? guard.bytes.lengthOf(res.body) : Buffer.byteLength(String(res.body == null ? "" : res.body), "utf8");
|
|
2044
2050
|
if (bodyLen === 0) throw E("est/empty-body", "a 200 serverkeygen response carried an empty body (RFC 7030 sec. 4.4.2)");
|
|
2045
2051
|
_assertConfidentialCipher(res);
|
|
2046
2052
|
// parseServerKeygenResponse does the per-part transfer-decode; the recipient coherence is bound to the CSR.
|
|
@@ -2142,7 +2148,7 @@ function _csrattrsResult(res, opts) {
|
|
|
2142
2148
|
// A 202 is nonconforming for a policy GET (sec. 4.5.2 lists only 200 / 204 / 404).
|
|
2143
2149
|
if (verdict.status === "retry") throw E("est/http-error", "a /csrattrs response must be HTTP 200, 204, or 404, not 202 (RFC 7030 sec. 4.5.2)");
|
|
2144
2150
|
if (verdict.status !== "ok") throw E("est/http-error", "an EST csrattrs response must be HTTP 200 / 204 / 404 (RFC 7030 sec. 4.5.2), got " + res.status);
|
|
2145
|
-
var bodyLen = Buffer.isBuffer(res.body) ? res.body
|
|
2151
|
+
var bodyLen = Buffer.isBuffer(res.body) ? guard.bytes.lengthOf(res.body) : Buffer.byteLength(String(res.body == null ? "" : res.body), "utf8");
|
|
2146
2152
|
// An empty HTTP body is distinct from a valid EMPTY CsrAttrs (`30 00` / base64 `MAA=`), which parses as {items:[]}.
|
|
2147
2153
|
if (bodyLen === 0) throw E("est/empty-body", "a 200 csrattrs response carried an empty body (RFC 7030 sec. 4.5.2)");
|
|
2148
2154
|
var attrs = csrattrsFmt.parse(transferDecode(res.body));
|
package/lib/guard-all.js
CHANGED
|
@@ -11,35 +11,35 @@
|
|
|
11
11
|
// itself, with no guard re-implemented inline:
|
|
12
12
|
//
|
|
13
13
|
// guard.bytes.view / .source -- untrusted byte-source -> Buffer re-view
|
|
14
|
-
// (detached-buffer fail-open
|
|
14
|
+
// (detached-buffer fail-open defense)
|
|
15
15
|
// guard.text.decode -- byte-source -> string, cap before copy
|
|
16
|
-
// (parser-DoS string-allocation
|
|
16
|
+
// (parser-DoS string-allocation defense)
|
|
17
17
|
// guard.limits.cap / .depthCap -- config-time resource-cap validation
|
|
18
|
-
// (recursion / allocation DoS
|
|
18
|
+
// (recursion / allocation DoS defense)
|
|
19
19
|
// guard.crypto.constantTimeEqual -- length-checked constant-time compare
|
|
20
|
-
// (timing side-channel
|
|
20
|
+
// (timing side-channel defense)
|
|
21
21
|
// guard.range.int / .uint31 / .positiveInt31
|
|
22
22
|
// -- bound a decoded integer before narrowing
|
|
23
|
-
// to Number (silent-narrowing
|
|
23
|
+
// to Number (silent-narrowing defense)
|
|
24
24
|
// guard.time.assertValid / .within -- validate a Date-instant before a temporal
|
|
25
|
-
// comparison (NaN-Date fail-open
|
|
25
|
+
// comparison (NaN-Date fail-open defense)
|
|
26
26
|
// guard.name.dnEqual / .rdnEqual / .assertNoControlBytes / .assertPrintableIa5
|
|
27
27
|
// -- canonical DN identity + name-string integrity
|
|
28
28
|
// guard.name.escapeControlBytes / .escapeDnValue
|
|
29
29
|
// -- render-side name safety: control-byte + RFC 4514 escaping
|
|
30
|
-
// (CVE-2009-2408 truncation / identity
|
|
30
|
+
// (CVE-2009-2408 truncation / identity defense)
|
|
31
31
|
// guard.encoding.base64url / .base64 / .hex
|
|
32
32
|
// -- strict textual-encoding decode, canonical
|
|
33
|
-
// + capped (encoding-malleability
|
|
33
|
+
// + capped (encoding-malleability defense)
|
|
34
34
|
// guard.identifier.assertCanonicalOid
|
|
35
35
|
// -- canonical dotted-decimal OID string form
|
|
36
|
-
// (canonicalization-divergence
|
|
36
|
+
// (canonicalization-divergence defense)
|
|
37
37
|
// guard.compress.bounded -- decompress an untrusted stream under a hard
|
|
38
38
|
// output cap, whole-input (decompression-bomb +
|
|
39
|
-
// trailing-frame malleability
|
|
39
|
+
// trailing-frame malleability defense)
|
|
40
40
|
// guard.header.assertField -- emitted MIME/RFC 5322 header field name +
|
|
41
41
|
// value integrity (CR/LF/NUL header-injection
|
|
42
|
-
//
|
|
42
|
+
// defense, CWE-93)
|
|
43
43
|
// guard.parsed.accept -- a CLAIMED-parsed structure carries every
|
|
44
44
|
// field the consuming code dereferences
|
|
45
45
|
// (type confusion / unverified provenance,
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
// token of a guard (the Buffer.from(x.buffer, byteOffset) re-view, the
|
|
50
50
|
// timingSafeEqual call, the MAX_DECODE_DEPTH_CEILING check) must appear only in
|
|
51
51
|
// its guard module, so a new boundary cannot re-inline the shape and forget the
|
|
52
|
-
//
|
|
52
|
+
// defense.
|
|
53
53
|
|
|
54
54
|
var bytes = require("./guard-bytes");
|
|
55
55
|
var text = require("./guard-text");
|