@blamejs/pki 0.5.6 → 0.5.8
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 +419 -378
- package/MIGRATING.md +65 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +24 -20
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +104 -104
- package/lib/cmc-verify.js +108 -32
- package/lib/cmp-build.js +31 -26
- package/lib/cmp-session.js +74 -72
- package/lib/cmp-verify.js +72 -58
- package/lib/cms-compress.js +8 -9
- package/lib/cms-decrypt.js +92 -76
- package/lib/cms-encrypt.js +33 -34
- package/lib/cms-sign.js +100 -54
- package/lib/cms-verify.js +141 -82
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +31 -25
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +19 -15
- package/lib/csr-sign.js +13 -9
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +103 -59
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +378 -15
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +144 -75
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +32 -20
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +71 -56
- package/lib/pki-build.js +23 -22
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +169 -36
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +19 -16
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +22 -18
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +21 -20
- package/lib/webauthn-mds.js +67 -67
- package/lib/webauthn.js +34 -34
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +24 -15
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/cmp-session.js
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
// surface (build / transfer / wellKnownUrl / verify) so the whole pki.cmp namespace wires through here
|
|
8
8
|
// (index.js requires this file as `cmp`).
|
|
9
9
|
//
|
|
10
|
-
// pki.cmp.session -- the
|
|
10
|
+
// pki.cmp.session -- the stateful CMP enrollment-transaction orchestrator (the pki.acme.client analogue).
|
|
11
11
|
// It composes the shipped message layer (build / transfer / verify) into a single enroll(request): mint a
|
|
12
|
-
// stable transactionID, build + protect + transfer a request,
|
|
12
|
+
// stable transactionID, build + protect + transfer a request, verify every response's protection before
|
|
13
13
|
// reading its body, chain the nonces (recipNonce echoes the peer's senderNonce, a fresh senderNonce per
|
|
14
14
|
// request) and the transactionID (RFC 9810 sec. 5.1.1 anti-replay / anti-interleave), interpret the
|
|
15
15
|
// CertResponse PKIStatus (grant -> extract the cert, waiting -> a bounded pollReq/pollRep loop, rejection
|
|
16
16
|
// -> a terminal verdict), and confirm (certConf -> pkiConf, unless implicitConfirm was granted). The
|
|
17
|
-
// single invariant is transfer -> verify (fail-closed) ->
|
|
17
|
+
// single invariant is transfer -> verify (fail-closed) -> only then read the body off the verdict.
|
|
18
18
|
|
|
19
19
|
var cmp = require("./cmp-verify"); // re-exports build / transfer / wellKnownUrl / verify (the whole message layer)
|
|
20
20
|
var asn1 = require("./asn1-der");
|
|
@@ -35,12 +35,12 @@ function _err(code, message, cause) { return new CmpError(code, message, cause);
|
|
|
35
35
|
var OID_IMPLICIT_CONFIRM = oid.byName("implicitConfirm"); // classify a granted implicitConfirm by its IMMUTABLE OID
|
|
36
36
|
|
|
37
37
|
// The certification-path build/validate engine, injected by path-validate (the crl/ocsp/cmp-verify seam) so
|
|
38
|
-
// the session can
|
|
38
|
+
// the session can validate the issued leaf certificate's signature + chain; x509.parse is structural only.
|
|
39
39
|
var _engine = null;
|
|
40
40
|
function setEngine(engine) { _engine = engine; }
|
|
41
41
|
|
|
42
|
-
// The signature algorithms whose OID conveys
|
|
43
|
-
// PQC signatures (ML-DSA / SLH-DSA, internally hashed).
|
|
42
|
+
// The signature algorithms whose OID conveys no message hash: EdDSA (the hash is the scheme) and the FIPS
|
|
43
|
+
// PQC signatures (ML-DSA / SLH-DSA, internally hashed). Only these may take the certConf SHA-256 + explicit
|
|
44
44
|
// hashAlg fallback (RFC 9810 sec. 5.3.18); any other indeterminate/non-signature AlgorithmIdentifier (e.g.
|
|
45
45
|
// rsaEncryption, an ML-KEM OID) is refused. Resolved to a dotted-OID set at load (never a runtime lookup).
|
|
46
46
|
var HASHLESS_SIG_OIDS = {};
|
|
@@ -58,8 +58,8 @@ var SIG_OID_TO_HASH = {};
|
|
|
58
58
|
});
|
|
59
59
|
var OID_RSASSA_PSS = oid.byName("rsassaPss");
|
|
60
60
|
var OID_RSA_ENCRYPTION = oid.byName("rsaEncryption");
|
|
61
|
-
// Message-digest OID -> the WebCrypto digest name, for the RSASSA-PSS hashAlgorithm parameter
|
|
62
|
-
// the
|
|
61
|
+
// Message-digest OID -> the WebCrypto digest name, for the RSASSA-PSS hashAlgorithm parameter, dispatched by
|
|
62
|
+
// the immutable OID (never oid.name, which pki.oid.register can rename). Resolved to a dotted-OID map at load.
|
|
63
63
|
var HASH_OID_TO_DIGEST = {};
|
|
64
64
|
[["sha256", "SHA-256"], ["sha384", "SHA-384"], ["sha512", "SHA-512"]].forEach(function (row) { var o = oid.byName(row[0]); if (o) HASH_OID_TO_DIGEST[o] = row[1]; });
|
|
65
65
|
// A composite signature's declared prehash (COMPOSITE_ALGS[oid].ph) -> the certConf hashAlg field value.
|
|
@@ -83,7 +83,7 @@ var DEFAULT_CERT_REQ_ID = 0; // the CRMF single-request certReqId when the cal
|
|
|
83
83
|
var P10CR_CERT_REQ_ID = -1; // a PKCS#10 request has no CRMF id; a conforming cp identifies it with the -1 sentinel (RFC 9483)
|
|
84
84
|
var ENROLL_ARMS = { ir: 1, cr: 1, kur: 1, p10cr: 1 }; // the initial request arms enroll() accepts
|
|
85
85
|
// The response body arm each enrollment request is answered by (RFC 9810 sec. 5.3.2/5.3.4): ir->ip, cr/p10cr
|
|
86
|
-
// ->cp, kur->kup. A response
|
|
86
|
+
// ->cp, kur->kup. A response on a different cert-response arm than the request is misrouted, never confirmed.
|
|
87
87
|
var RESPONSE_ARM = { ir: "ip", cr: "cp", kur: "kup", p10cr: "cp" };
|
|
88
88
|
|
|
89
89
|
// PKIStatus codes (RFC 9810 sec. 5.2.3): 0 accepted, 1 grantedWithMods, 2 rejection, 3 waiting.
|
|
@@ -100,18 +100,18 @@ function _certReqIdEq(a, b) { return a != null && b != null && BigInt(a) === Big
|
|
|
100
100
|
function _asCertList(v) { return v == null ? [] : (Array.isArray(v) ? v.slice() : [v]); }
|
|
101
101
|
|
|
102
102
|
// Append session-supplied pool material (a CA-delivered caPubs, a cached protection-signer chain part-derived
|
|
103
|
-
// from the response's
|
|
103
|
+
// from the response's unsigned extraCerts) to a `base` pool (the caller's own intermediates) up to the path
|
|
104
104
|
// builder's candidate ceiling, deduped against each other AND the base's Buffer entries so a copy of an existing
|
|
105
105
|
// candidate never spends a slot. The base is never truncated (if it alone exceeds the ceiling, that is a genuine
|
|
106
106
|
// config error path.build reports). So neither a legitimate caPubs nor a meddler's extraCerts flood can push a
|
|
107
|
-
// valid caller pool over the ceiling and fail an otherwise-valid grant. Used for
|
|
107
|
+
// valid caller pool over the ceiling and fail an otherwise-valid grant. Used for both the response signer path
|
|
108
108
|
// (cmp.verify) and the issued-leaf path (path.build), the two places session material joins a caller pool.
|
|
109
109
|
function _boundedPool(base, added) {
|
|
110
|
-
// Dedup the
|
|
111
|
-
// remaining room to zero, and evict genuinely needed added material even though the
|
|
110
|
+
// Dedup the base (the priority pool) first: duplicate copies would otherwise inflate its length, drive the
|
|
111
|
+
// remaining room to zero, and evict genuinely needed added material even though the distinct candidate count is
|
|
112
112
|
// small (path.build dedups internally, so dropping exact duplicates is behavior-preserving). An uncanonicalizable
|
|
113
113
|
// entry is kept as-is (path.build judges it) and cannot dedup an added cert. The base holds the priority material
|
|
114
|
-
// (a signed response's
|
|
114
|
+
// (a signed response's own delivered issuers + the cached chain, or the grant's caPubs) so it is never truncated;
|
|
115
115
|
// the added caller pool fills the remaining room to the candidate ceiling.
|
|
116
116
|
var ceiling = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES;
|
|
117
117
|
var seen = Object.create(null), distinctBase = [];
|
|
@@ -122,21 +122,21 @@ function _boundedPool(base, added) {
|
|
|
122
122
|
added.forEach(function (c) { var k = _certIdentity(c); if (k != null && seen[k]) return; if (k != null) seen[k] = 1; useful.push(c); });
|
|
123
123
|
return distinctBase.concat(useful.slice(0, room));
|
|
124
124
|
}
|
|
125
|
-
// A canonical byte identity for a certificate in
|
|
126
|
-
// an already-parsed certificate object
|
|
125
|
+
// A canonical byte identity for a certificate in any form path.build accepts (a DER Buffer, a PEM string, or
|
|
126
|
+
// an already-parsed certificate object), so a caller intermediate supplied as PEM or parsed still dedups against
|
|
127
127
|
// a byte-identical DER caPubs / cached certificate and does not spend a scarce candidate slot on a copy. The
|
|
128
|
-
// identity is the tbsCertificate bytes
|
|
129
|
-
// the unsigned extraCerts ordering, so a corrupted-signature copy sharing a valid issuer's TBS must
|
|
130
|
-
// onto it and evict the valid one. Returns null when the identity cannot be derived; a
|
|
128
|
+
// identity is the tbsCertificate bytes plus the signature (mirroring cmp.verify's _certKey): a meddler controls
|
|
129
|
+
// the unsigned extraCerts ordering, so a corrupted-signature copy sharing a valid issuer's TBS must not collapse
|
|
130
|
+
// onto it and evict the valid one. Returns null when the identity cannot be derived; a non-deduped entry is safe
|
|
131
131
|
// (a redundant slot), a wrong merge (dropping a distinct or the only valid certificate) is not.
|
|
132
|
-
// The identity comes from the
|
|
133
|
-
// parser recorded, never the fields of the object handed in. This is a dedupe
|
|
134
|
-
// and no collision attack on it is apparent
|
|
132
|
+
// The identity comes from the same derivation every other certificate door uses: the bytes the
|
|
133
|
+
// parser recorded, never the fields of the object handed in. This is a dedupe, not a verdict,
|
|
134
|
+
// and no collision attack on it is apparent, since carrying another certificate's exact tbsBytes and
|
|
135
135
|
// signature means being that certificate. But "no attack is apparent" is the reasoning that put a
|
|
136
136
|
// completeness-only door on nine deciding boundaries, so it is not the reasoning this uses: one
|
|
137
137
|
// derivation for certificates, everywhere, and the exceptions have to argue for themselves.
|
|
138
138
|
//
|
|
139
|
-
// Failure returns null
|
|
139
|
+
// Failure returns null instead of throwing, which is this function's own contract and is why the
|
|
140
140
|
// door is wrapped: an underivable identity is a redundant pool slot, while a wrong merge (dropping
|
|
141
141
|
// a distinct or the only valid certificate) is not. So a rebuilt entry simply does not dedupe.
|
|
142
142
|
function _certIdentity(cert) {
|
|
@@ -150,9 +150,9 @@ function _certIdentity(cert) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
// A canonical identity for a SubjectPublicKeyInfo: the algorithm OID + the AlgorithmIdentifier parameters +
|
|
153
|
-
// the raw subjectPublicKey BIT STRING. The parameters
|
|
154
|
-
// RSASSA-PSS constraint set) and are kept
|
|
155
|
-
// both naming the same key. So the issued-cert key-match compares
|
|
153
|
+
// the raw subjectPublicKey BIT STRING. The parameters are part of the key identity (an EC curve OID, an
|
|
154
|
+
// RSASSA-PSS constraint set) and are kept, with one exception: rsaEncryption, whose parameters are NULL or
|
|
155
|
+
// omitted, both naming the same key. So the issued-cert key-match compares keys (with their constraints), not byte
|
|
156
156
|
// encodings, and neither rejects an equivalent rsaEncryption re-encoding nor accepts a constraint-changed key.
|
|
157
157
|
function _spkiKeyIdentity(spkiDer) {
|
|
158
158
|
var node = asn1.decode(spkiDer); // SEQUENCE { AlgorithmIdentifier { OID, params? }, BIT STRING }
|
|
@@ -161,10 +161,10 @@ function _spkiKeyIdentity(spkiDer) {
|
|
|
161
161
|
var pn = algId.children[1];
|
|
162
162
|
var params;
|
|
163
163
|
if (algOid === OID_RSA_ENCRYPTION) {
|
|
164
|
-
// rsaEncryption parameters MUST be absent or a NULL (RFC 3279 sec. 2.3.1). Normalize
|
|
165
|
-
// equivalent forms to "" so a NULL-vs-omitted re-encoding matches.
|
|
166
|
-
// STRING the parser/importer may tolerate, or a changed parameter) keeps its bytes
|
|
167
|
-
// certificate a stricter consumer rejects gets a
|
|
164
|
+
// rsaEncryption parameters MUST be absent or a NULL (RFC 3279 sec. 2.3.1). Normalize only those two
|
|
165
|
+
// equivalent forms to "" so a NULL-vs-omitted re-encoding matches. Any other value (a malformed empty OCTET
|
|
166
|
+
// STRING the parser/importer may tolerate, or a changed parameter) keeps its bytes, so a parameter-changed
|
|
167
|
+
// certificate a stricter consumer rejects gets a distinct identity and the key-match refuses it.
|
|
168
168
|
params = (pn == null || _isDerNull(pn)) ? "" : pn.bytes.toString("latin1");
|
|
169
169
|
} else {
|
|
170
170
|
// Every other algorithm's parameters ARE part of the key identity (an EC curve OID, an RSASSA-PSS constraint set).
|
|
@@ -176,28 +176,28 @@ function _spkiKeyIdentity(spkiDer) {
|
|
|
176
176
|
// as equivalent to an absent parameter.
|
|
177
177
|
function _isDerNull(pn) { return pn.tagClass === "universal" && pn.tagNumber === 5 && pn.content.length === 0; }
|
|
178
178
|
|
|
179
|
-
// The
|
|
180
|
-
// omits extraCerts can rebuild the signer path
|
|
179
|
+
// The bounded, distinct, parseable extraCerts of an already-verified response, cached so a later leg that
|
|
180
|
+
// omits extraCerts can rebuild the signer path, mirroring cmp.verify's own extraCerts bounding: dedup, drop
|
|
181
181
|
// any non-X.509 entry, cap at MAX_EXTRA_CERTS, and stop after MAX_EXTRA_SCAN entries. So a meddler appending a
|
|
182
182
|
// flood of unsigned certs cannot make an otherwise-valid enrollment fail when the cache reaches path.build.
|
|
183
183
|
var MAX_EXTRA_CERTS = 32, MAX_EXTRA_SCAN = 256;
|
|
184
|
-
// The caller intermediates pool is capped
|
|
185
|
-
//
|
|
186
|
-
// each <= MAX_EXTRA_CERTS). So the authenticated certs
|
|
187
|
-
// no priority attempt has to choose between them, and a chain assembled from
|
|
188
|
-
var CAPUBS_MAX = 2 * MAX_EXTRA_CERTS; // the leaf's authenticated issuer material (caPubs)
|
|
189
|
-
// The caller intermediates pool is capped below the path-builder ceiling by the room the session's
|
|
190
|
-
// can occupy. A
|
|
191
|
-
// response's extraCerts + cached
|
|
192
|
-
// the shared secret and
|
|
184
|
+
// The caller intermediates pool is capped below the path-builder candidate ceiling, reserving room for the CA's
|
|
185
|
+
// own authenticated material (a response's extraCerts + the cached signer chain, or the grant's caPubs + cache,
|
|
186
|
+
// each <= MAX_EXTRA_CERTS). So the authenticated certs and the whole caller pool always fit in one candidate pool:
|
|
187
|
+
// no priority attempt has to choose between them, and a chain assembled from both sources validates in one build.
|
|
188
|
+
var CAPUBS_MAX = 2 * MAX_EXTRA_CERTS; // the leaf's authenticated issuer material (caPubs), bounded so caPubs + cached chain + the caller pool fit under the ceiling
|
|
189
|
+
// The caller intermediates pool is capped below the path-builder ceiling by the room the session's own material
|
|
190
|
+
// can occupy. A signature session reserves CAPUBS_MAX (a grant's caPubs, leaf validation) plus MAX_EXTRA_CERTS (the
|
|
191
|
+
// response's extraCerts + cached signer chain, the signer-path pool). A MAC session authenticates the response by
|
|
192
|
+
// the shared secret and never adds a signer chain (its cache gate is isSig), so it reserves CAPUBS_MAX alone; its
|
|
193
193
|
// sole pool is _validateLeaf's caPubs + caller. So every candidate pool the session builds holds the whole caller
|
|
194
|
-
// pool
|
|
194
|
+
// pool and all authenticated material at once: one build, no priority choice, no retry.
|
|
195
195
|
var SESSION_MAX_INTERMEDIATES = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES - CAPUBS_MAX - MAX_EXTRA_CERTS; // signature
|
|
196
196
|
var SESSION_MAX_INTERMEDIATES_MAC = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES - CAPUBS_MAX; // MAC (no signer chain reserved)
|
|
197
197
|
// A verify verdict whose failure a DIFFERENT candidate pool or the cached/prebound signer might still recover:
|
|
198
198
|
// extraCerts is outside the protected part, so a signer that did not resolve, a wrong-key/wrong-subject decoy
|
|
199
199
|
// selected first, or an untrusted chain can all be retried. A transaction-integrity failure (transactionID /
|
|
200
|
-
// recipNonce mismatch) is
|
|
200
|
+
// recipNonce mismatch) is deliberately absent: it is decoy-independent, and re-running would only mask the desync.
|
|
201
201
|
function _isRecoverableVerify(code) {
|
|
202
202
|
return code === "cmp/signer-cert-not-found" || code === "cmp/protection-failed" ||
|
|
203
203
|
code === "cmp/sender-mismatch" || code === "cmp/untrusted-signer";
|
|
@@ -219,8 +219,8 @@ function _responseExtraCerts(responseBytes) {
|
|
|
219
219
|
return out;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
// Normalize a caller-supplied CRMF certReqId to the value the session echoes + matches, the
|
|
223
|
-
// crmf-sign._certReqId encodes it: a number or bigint is kept; a
|
|
222
|
+
// Normalize a caller-supplied CRMF certReqId to the value the session echoes + matches, the same way
|
|
223
|
+
// crmf-sign._certReqId encodes it: a number or bigint is kept; a string (decimal or 0x-hex) is parsed via
|
|
224
224
|
// BigInt so a supported form ("5" / "0x5") is not silently replaced by the default; anything else -> `dflt`.
|
|
225
225
|
function _normalizeCertReqId(cid, dflt) {
|
|
226
226
|
if (typeof cid === "bigint" || typeof cid === "number") return cid;
|
|
@@ -251,11 +251,11 @@ function _pssDigest(paramsBytes) {
|
|
|
251
251
|
return null;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
// The certConf certHash algorithm (RFC 9810 sec. 5.3.18): use the
|
|
255
|
-
// If the signatureAlgorithm OID conveys the hash (sha256WithRSAEncryption / ecdsaWithSHA384 / ...) use
|
|
256
|
-
// hash and
|
|
254
|
+
// The certConf certHash algorithm (RFC 9810 sec. 5.3.18): use the same hash the certificate signature uses.
|
|
255
|
+
// If the signatureAlgorithm OID conveys the hash (sha256WithRSAEncryption / ecdsaWithSHA384 / ...) use that
|
|
256
|
+
// hash and omit hashAlg. For id-RSASSA-PSS the hash is carried in the params, so decode it and likewise omit
|
|
257
257
|
// hashAlg. Only when the hash is genuinely not conveyed (Ed25519 / Ed448; ML-DSA / SLH-DSA) is SHA-256 used
|
|
258
|
-
// and
|
|
258
|
+
// and declared in the explicit hashAlg field so the CA recomputes certHash under the same stated hash.
|
|
259
259
|
function _certConfHash(certDer) {
|
|
260
260
|
var sa;
|
|
261
261
|
try { sa = x509.parse(certDer).signatureAlgorithm; }
|
|
@@ -265,10 +265,10 @@ function _certConfHash(certDer) {
|
|
|
265
265
|
// hashAlg; id-RSASSA-PSS -> the hash from its parameters, no hashAlg.
|
|
266
266
|
if (HASHLESS_SIG_OIDS[sa.oid]) return { digest: "SHA-256", hashAlg: "sha256" };
|
|
267
267
|
// A composite signature (draft-ietf-lamps-pq-composite-sigs): compute certHash under the composite's own
|
|
268
|
-
// declared
|
|
269
|
-
// not a certConf-representable hash (SHAKE256, which the CMP CertStatus hashAlg cannot name),
|
|
268
|
+
// declared prehash digest (COMPOSITE_ALGS[oid].ph) and declare it in the explicit hashAlg. If the prehash is
|
|
269
|
+
// not a certConf-representable hash (SHAKE256, which the CMP CertStatus hashAlg cannot name), fail closed:
|
|
270
270
|
// substituting SHA-256 would send a certHash under a hash that contradicts the signature's declared prehash,
|
|
271
|
-
// which a conforming CA rejects. Refuse
|
|
271
|
+
// which a conforming CA rejects. Refuse, never confirm under a false algorithm (RFC 9810 sec. 5.3.18).
|
|
272
272
|
var comp = compositeSig.COMPOSITE_ALGS[sa.oid];
|
|
273
273
|
if (comp) {
|
|
274
274
|
var ha = COMPOSITE_PH_HASHALG[comp.ph];
|
|
@@ -281,8 +281,8 @@ function _certConfHash(certDer) {
|
|
|
281
281
|
if (pd) return { digest: pd, hashAlg: null };
|
|
282
282
|
throw _err("cmp/bad-cert-response", "the issued RSASSA-PSS certificate's hash cannot be resolved from its parameters (RFC 4055); the certConf hash is indeterminate");
|
|
283
283
|
}
|
|
284
|
-
// Any other AlgorithmIdentifier
|
|
285
|
-
//
|
|
284
|
+
// Any other AlgorithmIdentifier (an unregistered OID, or a registered non-signature / indeterminate one
|
|
285
|
+
// such as rsaEncryption or an ML-KEM OID): the required certConf hash cannot be determined. Do not guess
|
|
286
286
|
// SHA-256 (a wrong certHash the CA rejects); fail the transaction closed before confirming.
|
|
287
287
|
throw _err("cmp/bad-cert-response", "the issued certificate's signature algorithm does not determine a certConf hash (an unrecognized or non-signature algorithm); the transaction is refused (RFC 9810 sec. 5.3.18)");
|
|
288
288
|
}
|
|
@@ -295,46 +295,46 @@ function _certConfHash(certDer) {
|
|
|
295
295
|
* @spec RFC 9810, RFC 9811, RFC 9483
|
|
296
296
|
* @related pki.cmp.build, pki.cmp.verify, pki.cmp.transfer
|
|
297
297
|
*
|
|
298
|
-
* A stateful RFC 9810 CMP enrollment-transaction orchestrator
|
|
298
|
+
* A stateful RFC 9810 CMP enrollment-transaction orchestrator, the `pki.acme.client` analogue. It drives
|
|
299
299
|
* an enrollment (`ir` / `cr` / `kur` / `p10cr`) end to end over the shared `pki.transport` (inject
|
|
300
300
|
* `opts.transport`, else a fail-closed `pki.transport.https`), composing the shipped message layer
|
|
301
301
|
* (`build` / `transfer` / `verify`). It mints a stable 128-bit `transactionID`, and on every request a
|
|
302
|
-
*
|
|
303
|
-
* anti-replay / anti-interleave). The load-bearing invariant: every response is protection-
|
|
304
|
-
* nonce-bound to this exchange
|
|
302
|
+
* fresh `senderNonce`, echoing the peer's last `senderNonce` back as `recipNonce` (RFC 9810 sec. 5.1.1
|
|
303
|
+
* anti-replay / anti-interleave). The load-bearing invariant: every response is protection-verified and
|
|
304
|
+
* nonce-bound to this exchange before its body is read, so a meddler who flips an HTTP response cannot
|
|
305
305
|
* forge a granted status or a poison `checkAfter`. A `waiting` status drives a bounded `pollReq`/`pollRep`
|
|
306
306
|
* loop (an injectable sleeper, capped by `maxPolls` + `maxTotalWait`); a grant extracts the issued cert and
|
|
307
307
|
* confirms it (`certConf` -> `pkiConf`, unless an `implicitConfirm` was granted). A verified `rejection` /
|
|
308
|
-
* `error` or a poll-budget timeout is a terminal typed
|
|
308
|
+
* `error` or a poll-budget timeout is a terminal typed verdict the caller reads (`outcome`:
|
|
309
309
|
* `issued` / `rejected` / `poll-timeout`); a tampered / unverifiable / desynchronized response is a
|
|
310
|
-
* hard-stop `CmpError` throw. Exactly
|
|
311
|
-
* (PBMAC1). A crypto-valid response is not enough
|
|
310
|
+
* hard-stop `CmpError` throw. Exactly one protection flavor: `{ key, cert }` (signature) XOR `{ mac }`
|
|
311
|
+
* (PBMAC1). A crypto-valid response is not enough: the signer must chain to a supplied trust anchor
|
|
312
312
|
* (signature) or the shared secret must match (MAC); a valid-but-untrusted response is a hard stop, so the
|
|
313
313
|
* signature flavor REQUIRES `opts.trustAnchors` at construction. Returns a session with `enroll(request)`,
|
|
314
314
|
* and read-only `transactionID` + `transcript` (each leg's request/response bytes, retained up to a
|
|
315
315
|
* transaction-wide cap; a later leg beyond the cap keeps its metadata + `byteLength` but drops the payload as
|
|
316
316
|
* `bytes: null, truncated: true`, so a padded-response flood across polls cannot exhaust memory). The granted certificate must be valid X.509, carry the key
|
|
317
|
-
* the request submitted (else it is a misrouted certificate the caller cannot use), and
|
|
318
|
-
* flavor
|
|
319
|
-
* `certHash` uses the certificate's signature hash
|
|
317
|
+
* the request submitted (else it is a misrouted certificate the caller cannot use), and, for the signature
|
|
318
|
+
* flavor, have its signature + chain validated to a supplied trust anchor before it is confirmed. The `certConf`
|
|
319
|
+
* `certHash` uses the certificate's signature hash: from the OID when it conveys one, or from the
|
|
320
320
|
* RSASSA-PSS parameters when it does not, with `hashAlg` omitted; only a truly hashless signature
|
|
321
321
|
* (Ed25519 / Ed448, ML-DSA / SLH-DSA) computes under SHA-256 and declares an explicit `hashAlg`
|
|
322
322
|
* (RFC 9810 sec. 5.3.18). The `certReqId` echoed in `pollReq` / `certConf` and matched in every
|
|
323
323
|
* `CertResponse` is the caller's CRMF request id (`request.ir.certReqId`, ...) when supplied, else the
|
|
324
|
-
* single-request default. One transaction per session
|
|
324
|
+
* single-request default. One transaction per session: a second or concurrent `enroll`, or a batched
|
|
325
325
|
* CRMF request, is refused (a local build error leaves the session retryable). The returned `chain` is the
|
|
326
326
|
* issued leaf plus any authenticated `caPubs` the CA delivered (chain material, never trust anchors); a
|
|
327
327
|
* server-generated (central key generation) private key is out of scope and the grant is refused.
|
|
328
328
|
*
|
|
329
329
|
* @opts
|
|
330
330
|
* - `url` -- REQUIRED: the CMP endpoint URL.
|
|
331
|
-
* - `key` + `cert` -- signature protection (the enrolling key pair + its cert), XOR `mac: { secret, ... }`
|
|
332
|
-
* - `trustAnchors` -- REQUIRED for the signature flavor (chains + authenticates the CA's response signer cert); OPTIONAL for a MAC session, where it validates the
|
|
333
|
-
* - `sender` / `recipient` -- header GeneralNames; default the signer cert's subject DN (sender) and a NULL-DN (recipient). A signature-protection certificate with an
|
|
331
|
+
* - `key` + `cert` -- signature protection (the enrolling key pair + its cert), XOR `mac: { secret, ... }` for PBMAC1 protection.
|
|
332
|
+
* - `trustAnchors` -- REQUIRED for the signature flavor (chains + authenticates the CA's response signer cert); OPTIONAL for a MAC session, where it validates the issued certificate's own signature + chain before confirmation (not the response protection). `intermediates` supplies an extra chain pool.
|
|
333
|
+
* - `sender` / `recipient` -- header GeneralNames; default the signer cert's subject DN (sender) and a NULL-DN (recipient). A signature-protection certificate with an empty subject (identified only by its subjectAltName) REQUIRES an explicit `sender`, because the empty subject cannot name the requester for a peer that binds the sender to the SAN.
|
|
334
334
|
* - `senderKID` / `recipKID` -- optional key identifiers emitted on every request header, so a CA selecting among several shared secrets (senderKID) or recipient keys resolves the right credential.
|
|
335
|
-
* - `expectedSender` -- optional CA signer
|
|
335
|
+
* - `expectedSender` -- optional CA signer certificate (DER Buffer / PEM string / already-parsed `pki.schema.x509.parse` object); when set, every signed response's authenticated header sender MUST bind to it under the RFC 5280 sec. 7 subject-or-subjectAltName rule cmp.verify uses, so a re-encoded-but-equivalent DN and an empty-subject CA named only by a directoryName SAN both match. Given as bytes/PEM it also resolves a first response that omits its own extraCerts (a CA that assumes the client already holds its certificate). Absent, the session pins the first signed response's signer certificate and requires every later leg's sender to bind to it, rejecting a switch to a different trusted signer while permitting same-identity certificate/key rotation.
|
|
336
336
|
* - `implicitConfirm` -- request implicit confirmation (skip the certConf leg when the CA grants it).
|
|
337
|
-
* - `acceptCert` -- an async policy `(certDer, { status, grantedWithMods }) => boolean` consulted before the certConf; return true to accept, anything else to veto (a `grantedWithMods` certificate the CA changed). A veto sends a
|
|
337
|
+
* - `acceptCert` -- an async policy `(certDer, { status, grantedWithMods }) => boolean` consulted before the certConf; return true to accept, anything else to veto (a `grantedWithMods` certificate the CA changed). A veto sends a rejecting certConf and yields `outcome: "rejected"` with the certificate still surfaced. Incompatible with `implicitConfirm` (no reject leg exists), so the combination throws at construction.
|
|
338
338
|
* - `transport` -- injectable transport(request) -> {status, headers, body}; default pki.transport.https.
|
|
339
339
|
* - `tls` / `headers` / `timeout` / `maxResponseBytes` -- transport config + budgets.
|
|
340
340
|
* - `maxPolls` / `maxTotalWait` / `sleep` -- poll-loop budgets + an injectable sleeper; `time` -- verify-time clock.
|
|
@@ -892,8 +892,10 @@ function session(opts) {
|
|
|
892
892
|
catch (_e) { /* allow:swallow-unverified an unparseable p10cr CSR fails closed at the cmp.build boundary; the key-match is simply not applied to a request that never sends */ return null; }
|
|
893
893
|
}
|
|
894
894
|
var pk = armSpec && armSpec.certTemplate ? armSpec.certTemplate.publicKey : null;
|
|
895
|
-
|
|
896
|
-
|
|
895
|
+
// SNAPSHOT: this is held in session state across the transport round trip and then compared
|
|
896
|
+
// against the issued certificate's key, so an alias would let the caller rewrite the key the
|
|
897
|
+
// response is checked against after the request went out.
|
|
898
|
+
if (Buffer.isBuffer(pk) || pk instanceof Uint8Array) return guard.bytes.snapshot(pk, CmpError, "cmp/bad-input", "the certTemplate publicKey");
|
|
897
899
|
return null;
|
|
898
900
|
}
|
|
899
901
|
|