@blamejs/pki 0.5.4 → 0.5.5
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 +22 -0
- package/MIGRATING.md +31 -0
- package/lib/attrcert-sign.js +11 -7
- package/lib/cmp-session.js +23 -10
- package/lib/cmp-verify.js +12 -4
- package/lib/cms-decrypt.js +71 -30
- package/lib/crl-sign.js +19 -7
- package/lib/est.js +77 -1
- package/lib/guard-all.js +6 -0
- package/lib/guard-encoding.js +35 -6
- package/lib/guard-identifier.js +27 -1
- package/lib/guard-json.js +44 -8
- package/lib/guard-name.js +31 -8
- package/lib/guard-parsed.js +443 -0
- package/lib/hpke.js +36 -3
- package/lib/jose.js +8 -0
- package/lib/lint.js +20 -4
- package/lib/merkle.js +9 -0
- package/lib/ocsp.js +38 -8
- package/lib/path-validate.js +88 -44
- package/lib/pkcs12-build.js +34 -7
- package/lib/pki-build.js +12 -1
- package/lib/schema-crl.js +7 -1
- package/lib/schema-ocsp.js +6 -1
- package/lib/schema-pkcs12.js +7 -2
- package/lib/schema-pkix.js +14 -0
- package/lib/schema-x509.js +14 -1
- package/lib/sign-scheme.js +22 -5
- package/lib/smime.js +47 -0
- package/lib/trust.js +121 -10
- package/lib/validator-cose.js +86 -1
- package/lib/validator-tpm.js +8 -3
- package/lib/webauthn-mds.js +10 -18
- package/lib/webauthn.js +40 -18
- package/lib/x509-sign.js +6 -2
- package/package.json +5 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.5.5 — 2026-08-15
|
|
8
|
+
|
|
9
|
+
A verdict is computed over the bytes the parser read, an identity is derived from the bytes that carry it, and the guards match no patterns.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The package resolves one entry point. require("@blamejs/pki") is unchanged; a path INTO the package, such as require("@blamejs/pki/lib/schema-x509"), no longer resolves. Every module under lib/ carries @internal in its own header and none has ever appeared in the API snapshot that freezes the public surface -- they were reachable because the package declared no exports map, not because they were offered, and one of them mints the provenance record the integrity verbs above rely on. Everything the internals do is on pki.*: the decoders are pki.schema.<format>.parse, the codec is pki.asn1, the OID registry is pki.oid, the error classes are pki.errors. MIGRATING.md carries the recipe.
|
|
14
|
+
- pki.merkle, pki.jose, pki.hpke, pki.smime and pki.est refuse an option they do not recognize, which completes the toolkit -- every module that takes options now does. A misspelled option is the one input that reads as an omission rather than as a value, so the caller who asked for something stricter gets the looser default and is told nothing: a misspelled psk leaves an HPKE psk-mode setup with no pre-shared key, a misspelled key leaves pki.jose.verify accepting whichever key the message names, a misspelled leafIndex leaves a Merkle inclusion proof about a leaf the caller never chose, a misspelled strictMicalg accepts the S/MIME digest mismatch it was set to reject, and a misspelled expectedRecipientKeyId drops the recipient pin on an EST server-generated private key. The accepted set is per verb rather than per module, because the surfaces differ -- form means something to pki.smime.sign and nothing to pki.smime.encrypt, strict cannot run on pki.est.cacerts at all, and HPKE's two ends read the same object from opposite sides, so senderPublicKey does nothing at the sender and senderKey nothing at the recipient. A merged set would accept each verb's options everywhere and reproduce the silence in a wider form, which is why an option passed to the wrong end of an HPKE exchange is refused with a message saying where it belongs rather than ignored -- the caller who passed it usually believes they authenticated something. Four options pki.smime.sign has always forwarded (hcp, sid, signedAttributes, additionalSignedAttributes) and EST's auth object are now documented; they worked before and were absent from the reference.
|
|
15
|
+
- The guards match no patterns. A guard runs on the most hostile input the toolkit sees, and a pattern engine's cost on a rejecting string is a property of the pattern rather than of the length -- the one thing a caller's size cap cannot bound. Nine patterns across five guards are now explicit character walks, each one pass. Three carried a second defect settled by the rewrite: the whitespace fold above, a JSON number grammar written twice (once as the scan and once as a pattern re-matching what the scan had just read), and an RFC 4514 escape that ran a pattern replace and then a second loop over the same attacker-supplied value.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- A claimed-parsed structure must carry every field the matching pki.schema parser produces. The rule already existed at one door -- pki.path.build refused a partial claimed-parsed certificate -- while pki.path.validate, which build hands its result to, tested only for a truthy tbsBytes and passed the object into the RFC 5280 sec. 6.1 walk. Eleven doors now share it: pki.path.validate and build, pki.path.crlChecker, pki.crl.verify / isRevoked and the issuer of pki.crl.sign, the issuer of pki.x509.sign, pki.attrcert.sign, pki.ocsp's certificate argument, pki.lint, and the caller root certificates pki.webauthn takes for attestation and for android-safetynet. Completeness is measured against the parser rather than against what any one verb reads, because a field absent from an object is not a field with a safe default: an extension entry with no critical property read as non-critical, so a certificate rejected for an unknown critical extension when passed as bytes validated when passed as an object; a missing serialNumber surfaced as an error from the ASN.1 layer; and a missing issuer.bytes produced an OCSP request whose issuerNameHash covered nothing. Passing bytes, PEM, or the parser's own unmodified output is unaffected.
|
|
20
|
+
- A certificate or CRL a verdict is taken from is re-derived from the bytes its parser read. pki.path.validate and build, pki.path.crlChecker, pki.crl.verify and pki.crl.isRevoked all reach a decision, and completeness alone cannot carry one: a certificate is one signature over one byte range, but a parsed certificate presents that range, the signature, and every field the range encodes as separate properties. Keep a real CA certificate's signed bytes and signature and replace only its subjectPublicKeyInfo, and every field is well-formed, the signature verifies over the original range, and the substituted key is then what verifies the next certificate in the chain -- a forged chain built out of a genuine certificate. Emptying extensions is the same move against basicConstraints, keyUsage, name constraints and the unknown-critical rule; emptying a CRL's revokedCertificates leaves a correctly signed CRL reporting a revoked certificate as good. pki.schema.x509.parse and pki.schema.crl.parse now record what they read, these verbs parse it again from that record, and a certificate or CRL a caller assembled rather than parsed is refused. Passing bytes, PEM, or the parser's own unmodified output is unaffected.
|
|
21
|
+
- pki.ocsp.verify, pki.path.verifyOcspResponse, pki.pkcs12.verifyMac and pki.pkcs12.open compute their verdict over the bytes the parser read. A signature check has three parts -- the signature, the algorithm that verifies it, and the byte range it covers -- and on a parsed response all three are separate properties: pair a real CA's signature over a certificate that CA issued with that certificate's own signed bytes and algorithm, relabel the three, and every part of the check passes for a response the responder never produced. A PKCS#12 store has the same shape with two parts, the range the MAC covers and the bags handed back as verified, so one object could say verify this and return that. The parsers now record what they parsed and these verbs re-derive from that record, so an object edited or rebuilt after parsing is not what the verdict describes. Passing the parser's own result still works and is unchanged.
|
|
22
|
+
- pki.attrcert.sign derives both halves of a Holder's identity from the signed bytes. Issuer and serial together ARE the identity being bound; the issuer was decoded from tbsBytes while the serial was read off the object, so a parsed certificate with one field replaced produced a Holder naming a real issuer with a serial nobody issued.
|
|
23
|
+
- pki.trust.anchor answers from what the store read. A root program's metadata -- these purposes, until this date -- is a statement about a KEY, so an entry rebuilt with a substituted publicKey carried the program's word onto a key it never saw; the (name, key) pair is now re-derived from the certificate the store parsed, and an entry carrying store metadata without that provenance is refused. The purposes and distrust dates come from the same place and are copied on the way out, so neither editing a store entry nor writing through a returned anchor changes what that anchor authorizes -- pki.trust.anchor(entry).purposes.serverAuth = true no longer opens a gate the store never opened, and an anchor reports the store's bits and dates however the caller has since handled the entry. A caller asserting their own bare (name, key) anchor carries no metadata and is unaffected.
|
|
24
|
+
- A private key decoded for the crypto engine is wiped once the engine has imported it. A signer or recipient key may be given as a Buffer, a Uint8Array, or a PEM string; the first is the caller's own memory and is used in place, while the other two are decoded into a new buffer inside the toolkit -- a second copy of a private key, which until now stayed readable in the heap until the garbage collector happened to reuse the page. It is cleared on the failure path too, so a malformed key or a tampered message is not a way to leave one behind. A Buffer you supply is never written to: it is yours, you still hold it, and clearing it would destroy the key rather than protect it.
|
|
25
|
+
- pki.webauthn.verifyAssertion holds both accepted forms of a stored credential key to the same rules. The COSE bytes went through the curve and length rules, the 2048-bit RSA modulus floor and the exponent checks; the object form went through none, so one key was refused in one form and imported for signature verification in the other. Which form a relying party stores is a question about what their datastore round-trips, not about how carefully their credential is checked.
|
|
26
|
+
- A certificate's keyUsage is read the same way at every boundary that asks what the certificate may do. keyUsage is a NamedBitList, so DER drops its trailing zero bits (X.690 sec. 11.2.2) and RFC 5280 sec. 4.2.1.3 requires at least one bit set. Four boundaries read the bits themselves and applied neither rule, so one certificate could be authorized in one place and called malformed everywhere else: pki.crl.verify accepting a CRL signer, pki.tsp.verify accepting a timestamp authority, pki.cms.encrypt accepting a recipient, and the FIDO metadata reader accepting the leaf that signs a catalogue.
|
|
27
|
+
- The distinguished-name comparison that decides name chaining, revocation-issuer matching and name constraints folds the four ASCII whitespace characters X.520's caseIgnoreMatch names, and no others. It had been collapsing whitespace with a pattern, which also folds vertical tab, form feed, no-break space and every Unicode space separator -- equating names X.520 keeps distinct.
|
|
28
|
+
|
|
7
29
|
## v0.5.4 — 2026-08-15
|
|
8
30
|
|
|
9
31
|
A path verdict says whether revocation was ever established, a trust anchor's own distrust metadata can no longer sit inert, and a CRL is asked what only a certificate can answer.
|
package/MIGRATING.md
CHANGED
|
@@ -7,3 +7,34 @@ Some breaking changes cannot warn at runtime: an on-disk format break or a wire-
|
|
|
7
7
|
## No active deprecations
|
|
8
8
|
|
|
9
9
|
The toolkit has no `deprecate()`-marked surface awaiting removal.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Out-of-band breaking changes
|
|
14
|
+
|
|
15
|
+
Listed newest-first.
|
|
16
|
+
|
|
17
|
+
### v0.5.5 — `require("@blamejs/pki/lib/...")`
|
|
18
|
+
|
|
19
|
+
The package resolves one entry point; a path into the package no longer resolves.
|
|
20
|
+
|
|
21
|
+
`require("@blamejs/pki")` and `import ... from "@blamejs/pki"` are unchanged. What no
|
|
22
|
+
longer resolves is a path INTO the package:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
require("@blamejs/pki/lib/schema-x509") // ERR_PACKAGE_PATH_NOT_EXPORTED
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Every module under `lib/` carries `@internal` in its own header and none has ever appeared
|
|
29
|
+
in the API snapshot that freezes the public surface. They were reachable because the package
|
|
30
|
+
declared no `exports` map, not because they were offered -- and one of them mints the
|
|
31
|
+
provenance record the OCSP and PKCS#12 integrity verbs rely on, which reachable from outside
|
|
32
|
+
could be minted for any object.
|
|
33
|
+
|
|
34
|
+
Everything the internals do is on `pki.*`: the decoders are `pki.schema.<format>.parse`, the
|
|
35
|
+
codec is `pki.asn1`, the OID registry is `pki.oid`, the error classes are `pki.errors`. If you
|
|
36
|
+
are reaching for something with no `pki.*` route, that is a gap worth reporting rather than a
|
|
37
|
+
module worth importing -- the internals change shape between patch releases and carry no
|
|
38
|
+
compatibility promise.
|
|
39
|
+
|
|
40
|
+
`require("@blamejs/pki/package.json")` still resolves, for tooling that reads the version.
|
package/lib/attrcert-sign.js
CHANGED
|
@@ -83,12 +83,13 @@ var _tbsNameBytes = pkiBuild.tbsNameField; // the AA issuerName / holder baseC
|
|
|
83
83
|
// Parse a certificate DER/PEM (or accept a parsed certificate), re-typing a raw x509/* parse fault to the
|
|
84
84
|
// attrcert domain so a malformed AA cert / holder cert surfaces attrcert/*, not a foreign CertificateError.
|
|
85
85
|
function _parseCert(cert, what) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
// Re-derived from the bytes its parser read. Issuer and serial together ARE the Holder's identity,
|
|
87
|
+
// so a certificate assembled from parts could bind an attribute certificate to a holder that no
|
|
88
|
+
// issuer ever named.
|
|
89
|
+
return guard.parsed.acceptDerived(cert, "certificate", function (bytes) {
|
|
90
|
+
try { return x509.parse(bytes); }
|
|
91
|
+
catch (e) { if (e instanceof AttrCertError) throw e; throw _err("attrcert/bad-input", what + " is not a well-formed certificate", e); }
|
|
92
|
+
}, _err, "attrcert/bad-input", what);
|
|
92
93
|
}
|
|
93
94
|
// The raw content octets of an OBJECT IDENTIFIER (past its own tag+len) -- the body of a [0] IMPLICIT OID.
|
|
94
95
|
function _oidContent(name) {
|
|
@@ -149,8 +150,11 @@ function _encodeHolder(holder) {
|
|
|
149
150
|
if (holder.fromCertificate != null) {
|
|
150
151
|
// Bind to a public-key certificate's identity: baseCertificateID = { issuer = the PKC's issuer DN as a
|
|
151
152
|
// directoryName, serial = the PKC serialNumber } (RFC 5755 sec. 4.1 / 7.3).
|
|
153
|
+
// BOTH halves from the signed bytes. issuer and serial together ARE the identity, so deriving
|
|
154
|
+
// the issuer from tbsBytes while reading the serial off the object let the two name different
|
|
155
|
+
// certificates: a Holder with a genuine issuer DN and whatever serial the caller wrote.
|
|
152
156
|
var pkc = _parseCert(holder.fromCertificate, "holder.fromCertificate");
|
|
153
|
-
var content = _issuerSerialContent({ issuer: [{ directoryName: _tbsNameBytes(pkc, "issuer") }], serial: pkc
|
|
157
|
+
var content = _issuerSerialContent({ issuer: [{ directoryName: _tbsNameBytes(pkc, "issuer") }], serial: pkiBuild.tbsSerialNumber(pkc) });
|
|
154
158
|
return b.sequence([b.contextConstructed(0, content)]);
|
|
155
159
|
}
|
|
156
160
|
// objectDigestInfo [2] IMPLICIT ObjectDigestInfo.
|
package/lib/cmp-session.js
CHANGED
|
@@ -129,12 +129,24 @@ function _boundedPool(base, added) {
|
|
|
129
129
|
// the unsigned extraCerts ordering, so a corrupted-signature copy sharing a valid issuer's TBS must NOT collapse
|
|
130
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 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 rather than a verdict,
|
|
134
|
+
// and no collision attack on it is apparent -- carrying another certificate's exact tbsBytes AND
|
|
135
|
+
// signature means being that certificate. But "no attack is apparent" is the reasoning that put a
|
|
136
|
+
// completeness-only door on nine deciding boundaries, so it is not the reasoning this uses: one
|
|
137
|
+
// derivation for certificates, everywhere, and the exceptions have to argue for themselves.
|
|
138
|
+
//
|
|
139
|
+
// Failure returns null rather than throwing, which is this function's own contract and is why the
|
|
140
|
+
// door is wrapped: an underivable identity is a redundant pool slot, while a wrong merge (dropping
|
|
141
|
+
// a distinct or the only valid certificate) is not. So a rebuilt entry simply does not dedupe.
|
|
132
142
|
function _certIdentity(cert) {
|
|
133
143
|
try {
|
|
134
|
-
var p =
|
|
135
|
-
if (!
|
|
136
|
-
return p.tbsBytes.toString("base64") + "|" +
|
|
137
|
-
} catch (_e) {
|
|
144
|
+
var p = guard.parsed.acceptDerived(cert, "certificate", x509.parse, _err, "cmp/bad-input", "a pool certificate");
|
|
145
|
+
if (!guard.parsed.isCert(p)) return null;
|
|
146
|
+
return p.tbsBytes.toString("base64") + "|" + p.signatureValue.bytes.toString("base64");
|
|
147
|
+
} catch (_e) {
|
|
148
|
+
return null; // underivable: kept as its own slot, never merged onto another certificate's
|
|
149
|
+
}
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
// A canonical identity for a SubjectPublicKeyInfo: the algorithm OID + the AlgorithmIdentifier parameters +
|
|
@@ -383,12 +395,13 @@ function session(opts) {
|
|
|
383
395
|
var _es = opts.expectedSender;
|
|
384
396
|
try {
|
|
385
397
|
if (_es && Buffer.isBuffer(_es.tbsBytes)) { // the documented already-parsed form (pki.schema.x509.parse output), detected like _certIdentity
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
|
|
398
|
+
// The door's RETURN is what gets pinned, not the object handed in. coerceCert re-derives the
|
|
399
|
+
// certificate from the bytes its parser read, so calling it only as a check and then storing
|
|
400
|
+
// the caller's object keeps every edit the re-derivation exists to discard: this pin is
|
|
401
|
+
// compared against each response signer's subject and SAN, so an edited one would accept a
|
|
402
|
+
// different CMP signer than the caller meant to pin. A validator that normalizes has its
|
|
403
|
+
// return value as its contract -- using it as a predicate throws that contract away.
|
|
404
|
+
_expectedSenderCert = (_engine && _engine.coerceCert) ? _engine.coerceCert(_es) : _es;
|
|
392
405
|
}
|
|
393
406
|
else if (Buffer.isBuffer(_es) || _es instanceof Uint8Array) { _expectedSenderDer = Buffer.from(_es); _expectedSenderCert = x509.parse(_expectedSenderDer); }
|
|
394
407
|
else if (typeof _es === "string") { _expectedSenderDer = x509.pemDecode(_es); _expectedSenderCert = x509.parse(_expectedSenderDer); }
|
package/lib/cmp-verify.js
CHANGED
|
@@ -506,11 +506,19 @@ async function _verifySignature(m, protectedPart, protectionAlg, protection, opt
|
|
|
506
506
|
// A canonical certificate identity (tbs + signature) for the extraCerts pool dedup: it keys a Buffer /
|
|
507
507
|
// Uint8Array (parse) and an already-parsed candidate object identically (path.build accepts both forms), so
|
|
508
508
|
// an extraCert duplicating a caller intermediate is dropped regardless of which representation the caller used.
|
|
509
|
+
// The key is derived from the bytes the parser recorded, through the same door every other
|
|
510
|
+
// certificate boundary uses -- never from the fields of the object handed in. Deriving it from a
|
|
511
|
+
// caller-shaped object would let two different certificates collapse onto one key, which is the one
|
|
512
|
+
// outcome this must not have. Failure returns null (the caller's contract here): an underivable
|
|
513
|
+
// identity leaves a redundant pool slot, which is safe, while a wrong merge is not.
|
|
509
514
|
function _certKey(c) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
515
|
+
try {
|
|
516
|
+
var p = guard.parsed.acceptDerived(c, "certificate", x509.parse, _err, "cmp/bad-input", "a pool certificate");
|
|
517
|
+
if (!guard.parsed.isCert(p)) return null;
|
|
518
|
+
return p.tbsBytes.toString("base64") + "|" + p.signatureValue.bytes.toString("base64");
|
|
519
|
+
} catch (_e) {
|
|
520
|
+
return null; // underivable: kept as its own slot, never merged onto another certificate's
|
|
521
|
+
}
|
|
514
522
|
}
|
|
515
523
|
|
|
516
524
|
async function _chainSigner(signer, m, opts, extra) {
|
package/lib/cms-decrypt.js
CHANGED
|
@@ -211,41 +211,55 @@ async function _acquireCek(ri, km, opts) {
|
|
|
211
211
|
// ktri: OAEP or PKCS#1 v1.5 (v1.5 = decrypt-only + RFC 3218 implicit rejection).
|
|
212
212
|
async function _ktriCek(ri, km) {
|
|
213
213
|
var kea = ri.keyEncryptionAlgorithm;
|
|
214
|
-
var
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
214
|
+
var k = _normKeyDer(km.key);
|
|
215
|
+
// The wipe is in a `finally` so it happens on the reject path too: a message crafted to fail the
|
|
216
|
+
// unwrap must not be a way to leave the key copy in memory.
|
|
217
|
+
try {
|
|
218
|
+
if (kea.oid === O("rsaesOaep")) {
|
|
219
|
+
var hash = _oaepHashFromParams(kea.parameters);
|
|
220
|
+
var pub = await subtle.importKey("pkcs8", k.der, { name: "RSA-OAEP", hash: hash }, false, ["decrypt"]);
|
|
221
|
+
return Buffer.from(await subtle.decrypt({ name: "RSA-OAEP" }, pub, ri.encryptedKey));
|
|
222
|
+
}
|
|
223
|
+
if (kea.oid === O("rsaEncryption")) {
|
|
224
|
+
// RFC 3218 sec. 2.3.2 implicit rejection: NEVER surface a v1.5 failure here. Any decode fault
|
|
225
|
+
// yields a fresh random CEK of the content-alg length; the mismatch emerges at stage 3.
|
|
226
|
+
var keyObj = nodeCrypto.createPrivateKey({ key: k.der, format: "der", type: "pkcs8" });
|
|
227
|
+
try { return nodeCrypto.privateDecrypt({ key: keyObj, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }, ri.encryptedKey); }
|
|
228
|
+
catch (_e) {
|
|
229
|
+
return null; // signal: use a random CEK (length decided at open time)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Coverage residual: reachable only from a hostile message (our encrypt emits only OAEP; OpenSSL
|
|
233
|
+
// emits OAEP or rsaEncryption) -- a fail-closed reject the fuzz harness exercises.
|
|
234
|
+
throw _err("cms/unsupported-algorithm", "unsupported ktri keyEncryptionAlgorithm " + kea.oid);
|
|
235
|
+
} finally {
|
|
236
|
+
_releaseKeyDer(k);
|
|
226
237
|
}
|
|
227
|
-
// Coverage residual: reachable only from a hostile message (our encrypt emits only OAEP; OpenSSL
|
|
228
|
-
// emits OAEP or rsaEncryption) -- a fail-closed reject the fuzz harness exercises.
|
|
229
|
-
throw _err("cms/unsupported-algorithm", "unsupported ktri keyEncryptionAlgorithm " + kea.oid);
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
// kari: reconstruct Z from the originatorKey + recipient private key, KDF -> KEK, AES-KW unwrap.
|
|
233
241
|
async function _kariCek(ri, km) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// several recipients under one ephemeral key.
|
|
241
|
-
var rek = (km.cert != null && _kariRekFor(ri, x509.parse(_normCertDer(km.cert)))) || ri.recipientEncryptedKeys[0];
|
|
242
|
-
var kekBytes = WRAP_KEK_LENGTHS[wrapAlg.oid];
|
|
243
|
-
if (!kekBytes) throw _err("cms/unsupported-algorithm", "unsupported kari key-wrap");
|
|
244
|
-
var ukm = ri.ukm || null;
|
|
242
|
+
// The copy is taken and the protected region opens IMMEDIATELY. Everything between them would
|
|
243
|
+
// otherwise be an unprotected window, and it is not a narrow one: reading the wrap algorithm, the
|
|
244
|
+
// originator's key, the recipient's certificate and the matching RecipientEncryptedKey all parse
|
|
245
|
+
// attacker-supplied structure and all throw on malformed input. A message crafted to fail any one
|
|
246
|
+
// of them would be a way to leave the key copy in the heap -- the exact outcome the wipe is for.
|
|
247
|
+
var k = _normKeyDer(km.key);
|
|
245
248
|
// The agreement secret and the KEK derived from it are both allocated here; one `finally` clears
|
|
246
249
|
// whichever branch produced them, including when the unwrap below throws on a tampered key.
|
|
247
250
|
var kek, z, mz;
|
|
248
251
|
try {
|
|
252
|
+
var keyDer = k.der;
|
|
253
|
+
var kea = ri.keyEncryptionAlgorithm;
|
|
254
|
+
var wrapAlg = _kariWrap(kea);
|
|
255
|
+
var scheme = kea.oid;
|
|
256
|
+
var origSpki = _originatorSpki(ri.originator);
|
|
257
|
+
// Unwrap THIS recipient's RecipientEncryptedKey (matched by rid), not element 0 -- a kari may list
|
|
258
|
+
// several recipients under one ephemeral key.
|
|
259
|
+
var rek = (km.cert != null && _kariRekFor(ri, x509.parse(_normCertDer(km.cert)))) || ri.recipientEncryptedKeys[0];
|
|
260
|
+
var kekBytes = WRAP_KEK_LENGTHS[wrapAlg.oid];
|
|
261
|
+
if (!kekBytes) throw _err("cms/unsupported-algorithm", "unsupported kari key-wrap");
|
|
262
|
+
var ukm = ri.ukm || null;
|
|
249
263
|
if (_isMont(origSpki)) {
|
|
250
264
|
var mont = _montName(origSpki);
|
|
251
265
|
var recipPriv = await subtle.importKey("pkcs8", keyDer, { name: mont.name }, false, ["deriveBits"]);
|
|
@@ -275,6 +289,7 @@ async function _kariCek(ri, km) {
|
|
|
275
289
|
return await _aesKwUnwrap(kek, rek.encryptedKey);
|
|
276
290
|
} finally {
|
|
277
291
|
guard.secret.zeroizeAll([z, mz, kek], CmsError, "cms/bad-input", "the key-agreement shared secret");
|
|
292
|
+
_releaseKeyDer(k);
|
|
278
293
|
}
|
|
279
294
|
}
|
|
280
295
|
|
|
@@ -333,7 +348,12 @@ async function _kemriCek(ri, km) {
|
|
|
333
348
|
var kekBytes = Number(k.kekLength);
|
|
334
349
|
var wrapAlg = k.wrap;
|
|
335
350
|
if (WRAP_KEK_LENGTHS[wrapAlg.oid] !== kekBytes) throw _fail(); // M29 re-check on the consumer path
|
|
336
|
-
|
|
351
|
+
// The decapsulation key copy is released as soon as the engine has imported it -- the import is
|
|
352
|
+
// the only thing that reads it, so its lifetime does not need to span the decapsulation below.
|
|
353
|
+
var keyCopy = _normKeyDer(km.key);
|
|
354
|
+
var priv;
|
|
355
|
+
try { priv = await subtle.importKey("pkcs8", keyCopy.der, { name: wcName }, false, ["decapsulateBits"]); }
|
|
356
|
+
finally { _releaseKeyDer(keyCopy); }
|
|
337
357
|
var ss = null, kek = null, ssAb = null, kekAb = null;
|
|
338
358
|
try {
|
|
339
359
|
// The engine hands back an ArrayBuffer it allocated, and the Buffer below is a copy of it. Both
|
|
@@ -706,12 +726,33 @@ async function _verifyAuthenticatedData(parsed, km, opts) {
|
|
|
706
726
|
// * the `macKey == null` half of the random-key substitution fires only for a hand-crafted RSA v1.5
|
|
707
727
|
// ktri (this producer emits OAEP); its behaviour is identical to the tested below-floor path (a
|
|
708
728
|
// random key -> the MAC verify fails uniformly), so the < 16 vector covers the substitution.
|
|
729
|
+
// _normKeyDer(key) -> { der, owned } -- the recipient private key as PKCS#8 DER, and whether the
|
|
730
|
+
// buffer is one THIS module made.
|
|
731
|
+
//
|
|
732
|
+
// The distinction decides who may wipe it. A caller handing in their own Buffer keeps a live
|
|
733
|
+
// reference and will use it again; wiping that would destroy the key out from under them. The other
|
|
734
|
+
// two forms produce a NEW buffer here -- a Uint8Array is copied, a PEM string is decoded -- and that
|
|
735
|
+
// buffer is a second copy of a private key which nothing else can reach, so it lives until the
|
|
736
|
+
// garbage collector happens to reuse the page unless this module clears it.
|
|
737
|
+
//
|
|
738
|
+
// Returning the flag rather than always copying keeps the caller's buffer un-duplicated: making our
|
|
739
|
+
// own copy of every key so we could uniformly wipe it would ADD a copy of the secret to solve the
|
|
740
|
+
// problem of having one.
|
|
709
741
|
function _normKeyDer(key) {
|
|
710
|
-
if (Buffer.isBuffer(key)) return key;
|
|
711
|
-
if (key instanceof Uint8Array) return Buffer.from(key);
|
|
712
|
-
if (typeof key === "string") {
|
|
742
|
+
if (Buffer.isBuffer(key)) return { der: key, owned: false };
|
|
743
|
+
if (key instanceof Uint8Array) return { der: Buffer.from(key), owned: true };
|
|
744
|
+
if (typeof key === "string") {
|
|
745
|
+
var der;
|
|
746
|
+
try { der = pkcs8.pemDecode(key); }
|
|
747
|
+
catch (e) { throw _err("cms/bad-input", "the recipient private-key PEM could not be decoded", e); }
|
|
748
|
+
return { der: der, owned: true };
|
|
749
|
+
}
|
|
713
750
|
throw _err("cms/bad-input", "the recipient private key must be a PKCS#8 DER Buffer or PEM string");
|
|
714
751
|
}
|
|
752
|
+
// Wipe a key buffer this module owns. A no-op for the caller's own Buffer, which is theirs.
|
|
753
|
+
function _releaseKeyDer(k) {
|
|
754
|
+
if (k && k.owned) guard.secret.zeroize(k.der, CmsError, "cms/bad-input", "the recipient private-key copy");
|
|
755
|
+
}
|
|
715
756
|
function _normCertDer(cert) {
|
|
716
757
|
if (Buffer.isBuffer(cert)) return cert;
|
|
717
758
|
if (cert instanceof Uint8Array) return Buffer.from(cert);
|
package/lib/crl-sign.js
CHANGED
|
@@ -375,9 +375,10 @@ function _buildRevoked(entryList, isDelta) {
|
|
|
375
375
|
// ---- the primitives --------------------------------------------------------
|
|
376
376
|
|
|
377
377
|
function _parseIssuerCert(cert) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
// The issuer certificate decides who may have signed this CRL: its key verifies the signature and
|
|
379
|
+
// its keyUsage says whether it may sign CRLs at all. A caller-assembled one could carry a real
|
|
380
|
+
// CA's name and cRLSign bit beside a substituted key, so it is re-derived like every other.
|
|
381
|
+
return guard.parsed.acceptDerived(cert, "certificate", x509Schema.parse, _err, "crl/bad-input", "issuer.cert");
|
|
381
382
|
}
|
|
382
383
|
|
|
383
384
|
// RFC 5280 sec. 4.2.1.3 -- a certificate whose key signs CRLs asserts the cRLSign keyUsage bit. When the
|
|
@@ -508,10 +509,15 @@ function _sign(spec, issuer, opts) {
|
|
|
508
509
|
*/
|
|
509
510
|
function sign(spec, issuer, opts) { return Promise.resolve().then(function () { return _sign(spec, issuer, opts); }); }
|
|
510
511
|
|
|
512
|
+
// A CRL these verbs answer from is re-derived from the bytes its parser read. Completeness -- every
|
|
513
|
+
// field present with the right type -- is not enough for a verdict: the signature covers a byte
|
|
514
|
+
// range, while the revocation list and the scope extensions are separate properties of the parsed
|
|
515
|
+
// object. Keep a correctly signed CRL's `tbsBytes` and signature and empty `revokedCertificates`,
|
|
516
|
+
// and the signature still verifies while `isRevoked` answers from the edited list. An emptied
|
|
517
|
+
// `crlExtensions` does the same to scope: a shard that may only speak for some reasons, or a delta
|
|
518
|
+
// that may not answer alone, becomes one that answers for everything.
|
|
511
519
|
function _coerceCrl(crl) {
|
|
512
|
-
|
|
513
|
-
if (crl && typeof crl === "object" && crl.tbsBytes && crl.signatureValue && crl.signatureAlgorithm) return crl;
|
|
514
|
-
throw _err("crl/bad-input", "crl must be a CRL DER Buffer, a PEM string, or a parsed CRL (from pki.schema.crl.parse)");
|
|
520
|
+
return guard.parsed.acceptDerived(crl, "crl", crlSchema.parse, _err, "crl/bad-input", "the CRL");
|
|
515
521
|
}
|
|
516
522
|
|
|
517
523
|
// The issuer's key AND, when the caller supplied a certificate rather than a bare key, the
|
|
@@ -523,7 +529,13 @@ function _resolveIssuer(issuer) {
|
|
|
523
529
|
if (Buffer.isBuffer(issuer)) { _assertValidSpki(issuer, "issuer SPKI"); return { spki: issuer, cert: null }; }
|
|
524
530
|
if (issuer.cert != null) { var ic = _parseIssuerCert(issuer.cert); return { spki: ic.subjectPublicKeyInfo.bytes, cert: ic }; }
|
|
525
531
|
if (issuer.publicKey != null) { var spki = _reqDer(issuer.publicKey, "issuer.publicKey"); _assertValidSpki(spki, "issuer.publicKey"); return { spki: spki, cert: null }; }
|
|
526
|
-
|
|
532
|
+
// A parsed certificate passed directly. It reaches the same identity and cRLSign checks the
|
|
533
|
+
// { cert } form does, so it goes through the same door: an object carrying an SPKI but missing
|
|
534
|
+
// the subject those checks compare would decide them on undefined.
|
|
535
|
+
if (issuer.subjectPublicKeyInfo && issuer.subjectPublicKeyInfo.bytes) {
|
|
536
|
+
var pc = _parseIssuerCert(issuer);
|
|
537
|
+
return { spki: pc.subjectPublicKeyInfo.bytes, cert: pc };
|
|
538
|
+
}
|
|
527
539
|
throw _err("crl/bad-input", "issuer must be { cert }, { publicKey } (SPKI DER), or a raw SPKI Buffer");
|
|
528
540
|
}
|
|
529
541
|
|
package/lib/est.js
CHANGED
|
@@ -86,6 +86,59 @@ var OID_TEMPLATE = oid.byName("certificationRequestInfoTemplate");
|
|
|
86
86
|
|
|
87
87
|
var OPERATIONS = ["cacerts", "simpleenroll", "simplereenroll", "fullcmc", "serverkeygen", "csrattrs"];
|
|
88
88
|
|
|
89
|
+
// ---- the option surface each verb accepts -----------------------------------
|
|
90
|
+
//
|
|
91
|
+
// A misspelled option reads as an omission rather than as a value: nothing is out of range and
|
|
92
|
+
// nothing fails to parse, so the caller who asked for something stricter gets the looser default
|
|
93
|
+
// and is told nothing. That is worst here, where the options carry the security posture of a
|
|
94
|
+
// network exchange -- a misspelled `tls` leaves the anchors unset (the no-anchors refusal names
|
|
95
|
+
// the missing pin, so this one is caught), a misspelled `strict` accepts the extra certificates it
|
|
96
|
+
// was set to reject, a misspelled `expectedRecipientKeyId` drops a recipient pin on a
|
|
97
|
+
// server-generated private key, and a misspelled `oldCert` fails the re-enrollment outright.
|
|
98
|
+
//
|
|
99
|
+
// Every network verb shares the client surface, because every one of them goes through _client and
|
|
100
|
+
// the redirect / authentication plumbing it drives. The per-verb tables extend it rather than
|
|
101
|
+
// restating it, so a key added to the client reaches every verb at once and none of them drifts.
|
|
102
|
+
var CLIENT_OPTS = {
|
|
103
|
+
transport: 1, tls: 1, label: 1, timeout: 1, maxResponseBytes: 1, maxRedirects: 1, now: 1,
|
|
104
|
+
auth: 1, username: 1, password: 1, allowCrossOriginRedirect: 1,
|
|
105
|
+
};
|
|
106
|
+
function _withClient(extra) {
|
|
107
|
+
var out = {};
|
|
108
|
+
Object.keys(CLIENT_OPTS).forEach(function (k) { out[k] = 1; });
|
|
109
|
+
Object.keys(extra || {}).forEach(function (k) { out[k] = 1; });
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
// `strict` is enroll-only: _certsResult reads it after the /cacerts branch has already returned, so
|
|
113
|
+
// accepting it on cacerts would advertise a check that cannot run there.
|
|
114
|
+
var CACERTS_OPTS = _withClient(null);
|
|
115
|
+
var SIMPLEENROLL_OPTS = _withClient({ strict: 1 });
|
|
116
|
+
var SIMPLEREENROLL_OPTS = _withClient({ strict: 1, oldCert: 1 });
|
|
117
|
+
// expectedRecipientKind is NOT here: it is derived from the CSR's own advertised attribute, never
|
|
118
|
+
// taken from the caller, so listing it would offer a pin that nothing reads.
|
|
119
|
+
var SERVERKEYGEN_OPTS = _withClient({
|
|
120
|
+
requestedEncryption: 1, expectedRecipientKeyId: 1, expectedRecipientIssuerSerial: 1,
|
|
121
|
+
});
|
|
122
|
+
var CSRATTRS_OPTS = _withClient(null);
|
|
123
|
+
var FULLCMC_OPTS = _withClient({
|
|
124
|
+
transactionId: 1, senderNonce: 1, dataReturn: 1,
|
|
125
|
+
responderCerts: 1, responseRecipient: 1, allowUnverifiedResponse: 1,
|
|
126
|
+
});
|
|
127
|
+
// The two verbs that take options without going near the network.
|
|
128
|
+
var CLASSIFY_OPTS = { op: 1, now: 1 };
|
|
129
|
+
var PATHS_OPTS = { label: 1 };
|
|
130
|
+
var PARSE_SERVERKEYGEN_OPTS = {
|
|
131
|
+
requestedEncryption: 1, expectedRecipientKeyId: 1, expectedRecipientKind: 1,
|
|
132
|
+
expectedRecipientIssuerSerial: 1,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function _knownOpts(opts, known, verb) {
|
|
136
|
+
guard.identifier.assertKnownKeys(opts, known, E, "est/bad-input", function (k) {
|
|
137
|
+
return "unknown option " + JSON.stringify(k) + " for pki.est." + verb + " -- accepted: " +
|
|
138
|
+
Object.keys(known).sort().join(", ");
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
89
142
|
// ---- the RFC 8951 sec. 3/3.1 transfer codec (CTE-header-blind) -----------
|
|
90
143
|
|
|
91
144
|
/**
|
|
@@ -397,6 +450,7 @@ function _partMediaType(contentType) {
|
|
|
397
450
|
|
|
398
451
|
function parseServerKeygenResponse(body, contentType, opts) {
|
|
399
452
|
opts = opts || {};
|
|
453
|
+
_knownOpts(opts, PARSE_SERVERKEYGEN_OPTS, "parseServerKeygenResponse");
|
|
400
454
|
var parts = splitMultipartMixed(body, contentType);
|
|
401
455
|
if (parts.length !== 2) throw E("est/bad-multipart", "a serverkeygen response must have exactly two parts (RFC 7030 sec. 4.4.2)");
|
|
402
456
|
var keyPart = null, certPart = null, encrypted = false;
|
|
@@ -524,6 +578,7 @@ var NOT_IMPLEMENTED_OPS = { fullcmc: 1 };
|
|
|
524
578
|
*/
|
|
525
579
|
function classifyResponse(status, headers, body, opts) {
|
|
526
580
|
opts = opts || {};
|
|
581
|
+
_knownOpts(opts, CLASSIFY_OPTS, "classifyResponse");
|
|
527
582
|
var op = opts.op;
|
|
528
583
|
// Fail closed on an operation whose response this client cannot validate:
|
|
529
584
|
// A named op this client cannot validate is a typo, not a pass. An
|
|
@@ -618,6 +673,7 @@ function classifyResponse(status, headers, body, opts) {
|
|
|
618
673
|
*/
|
|
619
674
|
function paths(baseUrl, opts) {
|
|
620
675
|
opts = opts || {};
|
|
676
|
+
_knownOpts(opts, PATHS_OPTS, "paths");
|
|
621
677
|
var prefix = String(baseUrl).replace(/\/+$/, "") + "/.well-known/est";
|
|
622
678
|
if (opts.label != null) {
|
|
623
679
|
var label = String(opts.label);
|
|
@@ -1152,6 +1208,11 @@ function fullcmc(baseUrl, request, opts) {
|
|
|
1152
1208
|
var der, wanted, sent;
|
|
1153
1209
|
try {
|
|
1154
1210
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw E("est/bad-input", "pki.est.fullcmc options must be an object");
|
|
1211
|
+
// Inside the try, so the refusal is a REJECTION like every other failure of this verb: it is
|
|
1212
|
+
// documented as Promise-returning, and a synchronous throw escapes the `.catch(...)` a caller
|
|
1213
|
+
// has already written. It stays in the synchronous capture rather than moving into a later turn,
|
|
1214
|
+
// because that is what closes the race described above.
|
|
1215
|
+
_knownOpts(opts, FULLCMC_OPTS, "fullcmc");
|
|
1155
1216
|
der = _cmcRequestDer(request);
|
|
1156
1217
|
// Confirm this IS a Full PKI Request before any of it goes over the wire. The
|
|
1157
1218
|
// bytes are about to be labelled `smime-type=CMC-request`, so a PKIResponse or
|
|
@@ -1750,6 +1811,10 @@ function _enroll(op, baseUrl, csrInput, opts) {
|
|
|
1750
1811
|
* - `tls` -- { anchors, useSystemStore, cert, key, minVersion, servername, checkServerIdentity }.
|
|
1751
1812
|
* - `label` -- an OPTIONAL CA label path segment; `timeout` / `maxResponseBytes` / `maxRedirects` -- budgets.
|
|
1752
1813
|
* - `now` -- receipt time (epoch ms) to render a 202 Retry-After HTTP-date as seconds.
|
|
1814
|
+
* - `auth` -- HTTP authentication: `{ scheme: "basic" | "digest", username, password, allowMD5, allowLegacyQop, maxStaleRetries }`.
|
|
1815
|
+
* There is no `"auto"`: the scheme is chosen here, not by whatever a server offers. `username` / `password`
|
|
1816
|
+
* at the top level are the older form and mean Basic. Answered only after the transport authenticated the server.
|
|
1817
|
+
* - `allowCrossOriginRedirect` -- opt in to following a cross-origin redirect on an unsafe method.
|
|
1753
1818
|
* @example
|
|
1754
1819
|
* // a live CA uses the default pki.transport.https; here an injected transport returns a canned bag
|
|
1755
1820
|
* var r = await pki.est.cacerts("https://ca.example",
|
|
@@ -1759,6 +1824,11 @@ function _enroll(op, baseUrl, csrInput, opts) {
|
|
|
1759
1824
|
function cacerts(baseUrl, opts) {
|
|
1760
1825
|
opts = opts || {};
|
|
1761
1826
|
return Promise.resolve().then(function () {
|
|
1827
|
+
// Inside the promise, like every other refusal these verbs make. They are documented as
|
|
1828
|
+
// Promise-returning, so a caller writes `.catch(...)` -- and a check that throws synchronously
|
|
1829
|
+
// escapes that catch entirely, turning a misspelled option into an uncaught exception rather
|
|
1830
|
+
// than the rejection the caller is already handling.
|
|
1831
|
+
_knownOpts(opts, CACERTS_OPTS, "cacerts");
|
|
1762
1832
|
return _client("cacerts", "GET", baseUrl, null, { accept: "application/pkcs7-mime" }, opts);
|
|
1763
1833
|
}).then(function (res) { return _certsResult("cacerts", res, opts, null); });
|
|
1764
1834
|
}
|
|
@@ -1795,7 +1865,10 @@ function cacerts(baseUrl, opts) {
|
|
|
1795
1865
|
*/
|
|
1796
1866
|
function simpleenroll(baseUrl, csrInput, opts) {
|
|
1797
1867
|
opts = opts || {};
|
|
1798
|
-
return Promise.resolve().then(function () {
|
|
1868
|
+
return Promise.resolve().then(function () {
|
|
1869
|
+
_knownOpts(opts, SIMPLEENROLL_OPTS, "simpleenroll");
|
|
1870
|
+
return _enroll("simpleenroll", baseUrl, csrInput, opts);
|
|
1871
|
+
});
|
|
1799
1872
|
}
|
|
1800
1873
|
|
|
1801
1874
|
/**
|
|
@@ -1826,6 +1899,7 @@ function simpleenroll(baseUrl, csrInput, opts) {
|
|
|
1826
1899
|
function simplereenroll(baseUrl, csrInput, opts) {
|
|
1827
1900
|
opts = opts || {};
|
|
1828
1901
|
return Promise.resolve().then(function () {
|
|
1902
|
+
_knownOpts(opts, SIMPLEREENROLL_OPTS, "simplereenroll");
|
|
1829
1903
|
if (!opts.oldCert) throw E("est/bad-input", "simplereenroll requires opts.oldCert (the certificate being renewed, RFC 7030 sec. 4.2.2)");
|
|
1830
1904
|
reenrollGuard(opts.oldCert, _csrDer(csrInput)); // est/reenroll-* on mismatch, BEFORE the POST
|
|
1831
1905
|
return _enroll("simplereenroll", baseUrl, csrInput, opts);
|
|
@@ -1989,6 +2063,7 @@ async function _serverkeygenResult(res, opts, derived) {
|
|
|
1989
2063
|
function serverkeygen(baseUrl, csrInput, opts) {
|
|
1990
2064
|
opts = opts || {};
|
|
1991
2065
|
return Promise.resolve().then(function () {
|
|
2066
|
+
_knownOpts(opts, SERVERKEYGEN_OPTS, "serverkeygen");
|
|
1992
2067
|
var csrDer = _csrDer(csrInput);
|
|
1993
2068
|
var derived = _serverkeygenEncryptionFromCsr(csrDer);
|
|
1994
2069
|
if (opts.requestedEncryption !== undefined && !!opts.requestedEncryption !== derived.requestedEncryption) throw E("est/bad-input", "opts.requestedEncryption (" + !!opts.requestedEncryption + ") contradicts the CSR's advertised key-encryption attribute (" + derived.requestedEncryption + ") (RFC 7030 sec. 4.4.1)");
|
|
@@ -2056,6 +2131,7 @@ function _csrattrsResult(res, opts) {
|
|
|
2056
2131
|
function csrattrs(baseUrl, opts) {
|
|
2057
2132
|
opts = opts || {};
|
|
2058
2133
|
return Promise.resolve().then(function () {
|
|
2134
|
+
_knownOpts(opts, CSRATTRS_OPTS, "csrattrs");
|
|
2059
2135
|
return _client("csrattrs", "GET", baseUrl, null, { accept: "application/csrattrs" }, opts);
|
|
2060
2136
|
}).then(function (res) { return _csrattrsResult(res, opts); });
|
|
2061
2137
|
}
|
package/lib/guard-all.js
CHANGED
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
// guard.header.assertField -- emitted MIME/RFC 5322 header field name +
|
|
41
41
|
// value integrity (CR/LF/NUL header-injection
|
|
42
42
|
// defence, CWE-93)
|
|
43
|
+
// guard.parsed.accept -- a CLAIMED-parsed structure carries every
|
|
44
|
+
// field the consuming code dereferences
|
|
45
|
+
// (type confusion / unverified provenance,
|
|
46
|
+
// CWE-843 / CWE-345)
|
|
43
47
|
//
|
|
44
48
|
// Each shape is enforced by a codebase-patterns detector: the characteristic
|
|
45
49
|
// token of a guard (the Buffer.from(x.buffer, byteOffset) re-view, the
|
|
@@ -60,6 +64,7 @@ var identifier = require("./guard-identifier");
|
|
|
60
64
|
var header = require("./guard-header");
|
|
61
65
|
var compress = require("./guard-compress");
|
|
62
66
|
var secret = require("./guard-secret");
|
|
67
|
+
var parsed = require("./guard-parsed");
|
|
63
68
|
|
|
64
69
|
module.exports = {
|
|
65
70
|
bytes: bytes,
|
|
@@ -75,4 +80,5 @@ module.exports = {
|
|
|
75
80
|
header: header,
|
|
76
81
|
compress: compress,
|
|
77
82
|
secret: secret,
|
|
83
|
+
parsed: parsed,
|
|
78
84
|
};
|