@blamejs/pki 0.8.12 → 0.8.14
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 +33 -0
- package/README.md +2 -1
- package/index.js +6 -0
- package/lib/acme.js +13 -5
- package/lib/attrcert-sign.js +3 -3
- package/lib/cmc-build.js +9 -9
- package/lib/cmc-verify.js +5 -5
- package/lib/cmp-build.js +3 -4
- package/lib/cmp-session.js +6 -6
- package/lib/cms-verify.js +4 -4
- package/lib/crl-sign.js +1 -1
- package/lib/crmf-sign.js +1 -1
- package/lib/ct.js +2 -2
- package/lib/est.js +12 -13
- package/lib/framework-error.js +3 -0
- package/lib/guard-header.js +15 -1
- package/lib/guard-identifier.js +34 -0
- package/lib/hpke.js +7 -2
- package/lib/http-transport.js +5 -6
- package/lib/identity-match.js +4 -3
- package/lib/ocsp.js +5 -3
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +155 -16
- package/lib/pki-build.js +1 -1
- package/lib/revocation-fetch.js +1 -1
- package/lib/scep.js +21 -9
- package/lib/schema-all.js +10 -1
- package/lib/schema-engine.js +7 -2
- package/lib/schema-pkix.js +40 -0
- package/lib/schema-trustanchor.js +469 -0
- package/lib/schema-x509.js +66 -33
- package/lib/smime.js +5 -1
- package/lib/trust.js +192 -11
- package/lib/trustanchor-build.js +278 -0
- package/lib/validator-tpm.js +1 -1
- package/lib/webauthn-mds.js +1 -1
- package/lib/webauthn.js +9 -10
- package/lib/webcrypto.js +3 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ 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.8.14 — 2026-09-24
|
|
8
|
+
|
|
9
|
+
The 0.8.14 tarball is the one to install for everything 0.8.13 added.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- The changelog entry for this release is dated by the UTC day its commit landed on, which is the day the release tooling and the entry now agree on.
|
|
14
|
+
|
|
15
|
+
## v0.8.13 — 2026-09-24
|
|
16
|
+
|
|
17
|
+
A trust anchor list published as RFC 5914 drives path validation, constraints and all.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `pki.schema.trustanchor.parse(input, caps?)` reads an RFC 5914 `TrustAnchorList` into `{ anchors }`, each entry naming which of the three `TrustAnchorChoice` arms it came from in `kind`: `certificate`, `tbsCert` (a bare `TBSCertificate`, an anchor having no signature to check) or `taInfo`. `parseInfo` reads a bare `TrustAnchorInfo`. `pemDecode` and `pemEncode` carry the `TRUST ANCHOR LIST` label, and `pki.schema.parse` detects and routes the format.
|
|
22
|
+
- A `taInfo` entry is `{ version, pubKey, keyId, taTitle, certPath, exts, excludedExtensions, taTitleLangTag }`. `certPath` carries the anchor's name and its constraints: `policySet`, `policyFlags`, `nameConstr` and `pathLenConstraint`. Every rule RFC 5914 states is enforced: an empty `taName`, a `policySet` carrying policy qualifiers, a negative `pathLenConstraint` and a `requireExplicitPolicy` with no `policySet` are each refused, and where `certPath` carries a certificate its subject, its public key and its `subjectKeyIdentifier` must match `taName`, `pubKey` and `keyId`. The four extension types section 3 excludes from `exts` are listed in `excludedExtensions` and left out of `exts`, because the RFC ignores them rather than refusing the anchor.
|
|
23
|
+
- `pki.trust.parseTrustAnchorList(input, opts?)` turns a list into the trust-store entries `pki.trust.anchor` takes, the same shape `parseCertdata` and `parseCcadbCsv` produce, so a published list drives `pki.path.validate` without being mapped by hand.
|
|
24
|
+
- `pki.path.validate` applies an RFC 5914 anchor's constraints as RFC 5937 section 3.2 states. The anchor's name constraints intersect the caller's permitted subtrees and union its excluded ones, its `policySet` intersects `opts.userInitialPolicySet`, each of its `policyFlags` may turn the matching `initialExplicitPolicy` / `initialPolicyMappingInhibit` / `initialAnyPolicyInhibit` on and never off, and its `pathLenConstraint` bounds the path. An anchor can only narrow what the caller asked for, which is what makes the format's "always enforced" safe to apply.
|
|
25
|
+
- `pki.trustanchor.build(spec, opts?)` writes the list a root program publishes, with `opts.pem` for the PEM form. Every rule the parser enforces is enforced here, so the toolkit does not emit an anchor it would refuse to read, and the result round-trips through the parser before it is returned. The `version` is not a field a caller sets: v1 is the ASN.1 DEFAULT and DER omits it, so each v1 anchor has one encoding.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `pki.trust.anchor` keeps an entry's `purposes` as `null` where the entry states it as `null`, rather than reading it as a set granting nothing. A store that lists a root and grants it no purpose still produces the all-false set, which is what `pki.path.validate` requires `opts.checkPurpose` to judge; a format carrying no purpose bits at all, such as an RFC 5914 anchor, now says nothing instead of saying "trusted for no purpose". An entry that omits `purposes` altogether is unchanged and still defaults to all-false.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- An option supplied as a non-enumerable own property now reaches the code that acts on it. Every verb accepted such an option and some then skipped it, with nothing reported. Two of them let a check pass that should have failed: `pki.webauthn.verifyAssertion` ignored an `expectedRpId` written that way and reported the assertion valid against the wrong relying party, and an `opts.tpmPolicy.objectAttributes` entry went unchecked in `pki.webauthn.verify`. The rest changed what was produced or refused: `pki.attrcert.sign` left the named extension or attribute out of the certificate it signed, `pki.crmf.build` left the control out of the request, and `pki.cmp.session` answered that no enrollment arm or support operation had been named, or classified an `ir` request as central key generation.
|
|
34
|
+
- The same read now applies wherever the toolkit copies or enumerates an object it was handed: `pki.path.validate`, `pki.trust`, `pki.cms.verify`, `pki.cmc`, `pki.est`, `pki.ocsp`, `pki.acme`, `pki.ct`, `pki.scep`, `pki.smime`, `pki.webauthn` and the WebCrypto algorithm surface. It covers the response an `opts.transport` returns, so a `Location`, `WWW-Authenticate`, `Retry-After` or `Content-Type` field a custom transport defines non-enumerably is now found. A caller passing plain objects sees no change.
|
|
35
|
+
- In `pki.path.validate`, an anchor carrying any RFC 5914 constraint could accept a path that the same call without the anchor refused, because the caller's `initialExplicitPolicy`, `initialPolicyMappingInhibit`, `initialAnyPolicyInhibit` and `userInitialPolicySet` were re-read from a copy that kept only the enumerable ones. The caller's options are now read where they were passed.
|
|
36
|
+
- A trust anchor's own `basicConstraints` `pathLenConstraint` now bounds the path below it, as RFC 5937 section 3.2 requires of a basic constraints extension associated with the anchor. It applies wherever the anchor states one: a certificate passed to `pki.path.validate` or `pki.trust.anchor`, the `exts` of an RFC 5914 `TrustAnchorInfo`, and that anchor's `certPath.pathLenConstraint`. Where two of them state a limit, the lower applies. **A path that runs deeper than an anchor certificate's `pathLenConstraint` allows is now refused where it previously validated.** Check the certificates in your trust store for a `pathLenConstraint` if you validate chains with intermediates.
|
|
37
|
+
- `pki.trustanchor.build` refuses a malformed nested record with its own `trustanchor/bad-input` error. A `null` or non-object `taInfo`, `certPath` or `certPath.policyFlags` raised a native `TypeError` from the first field read below it.
|
|
38
|
+
- Parsing no longer writes a decoded field through an accessor planted on `Object.prototype`. Every format parser builds its record in the schema engine by the field names its structure declares, and those writes reached the prototype chain: a getter-only `signatureAlgorithm`, `issuer`, `extensions` or any other declared name made an ordinary `pki.schema.x509.parse` of a valid certificate throw a native `TypeError`, and a setter took the decoded value instead of the record.
|
|
39
|
+
|
|
7
40
|
## v0.8.12 — 2026-09-23
|
|
8
41
|
|
|
9
42
|
A certification path whose policies map to each other validates instead of exhausting the policy-node budget.
|
package/README.md
CHANGED
|
@@ -247,6 +247,7 @@ comment blocks, is at [pkijs.com](https://pkijs.com).
|
|
|
247
247
|
| `pki.schema.c509` | C509 certificates (draft-ietf-cose-cbor-encoded-cert), the compact CBOR profile of X.509, decoded fail-closed under deterministic CBOR. `encode` is the byte-exact inverse: a DER X.509 v3 certificate forward-transforms to a compact type-3 C509 whose reconstruction reproduces the original DER byte for byte, so the original signature still verifies. A `parse` result re-emits its native array in canonical deterministic CBOR, with the registry integer shorthands, the C509 compressions, and the compact draft-20 per-extension value forms: the scalar extensions (keyUsage, basicConstraints, extended key usage, subject key identifier, inhibitAnyPolicy, OCSP No Check, TLS Feature), the general-name-bearing extensions (subjectAltName, issuer alternative name, name constraints, CRL distribution points, freshest CRL, authority and subject information access, and the full authority key identifier) over one shared GeneralNames codec, certificate policies with their CPS-URI and UserNotice qualifiers, policy mappings and policy constraints, subject directory attributes, and the RFC 3779 resource-delegation extensions (IP address blocks and AS identifiers) with their RFC 8360 v2 twins, whose addresses ride either the delta-coded integer form or the byte-string form the specification mandates once an address exceeds eight octets. A value the compact form cannot carry exactly falls back to the byte-string form with its bytes intact; a certificate outside the invertible set throws a typed `C509Error`. Called explicitly, since it is CBOR rather than DER and so is not auto-routed |
|
|
248
248
|
| `pki.schema.crl` | X.509 CRLs (RFC 5280 §5): revoked serials with real-`Date` revocation times, named and partly decoded extensions, each record carrying the raw `extnValue` octets as `valueBytes`. `decodeExtensions` returns the CRL's own extensions and every revoked entry's in one array, each row naming the scope it was read at, and covers the whole §5.2 and §5.3 profile including the issuing distribution point and the delta CRL indicator. `decodeExtension` takes the scope explicitly, since RFC 6960 §4.4.5 carries a CRL entry extension into an OCSP response Fail-closed — `parse`, `pemDecode`, `pemEncode`, `decodeExtensions`, `decodeExtension` |
|
|
249
249
|
| `pki.schema.csr` | PKCS#10 certification requests (RFC 2986): subject DN, public key, requested attributes, signature. `decodeExtensions` is what a CA reads to see what a request asked for, returning the rows of its one `extensionRequest` attribute; a request carrying more than one is refused, and `groups: true` reads each separately Fail-closed — `parse`, `pemDecode`, `pemEncode`, `decodeExtensions`, `decodeExtension` |
|
|
250
|
+
| `pki.schema.trustanchor` | RFC 5914 trust anchor lists: a trust anchor as a structure rather than as a certificate, carrying the constraints a root program otherwise states out of band. `parse` reads a `TrustAnchorList` into `{ anchors }`, each naming which of the three `TrustAnchorChoice` arms it came from: a `certificate`, a bare `tbsCert` (an anchor has no signature to check), or a `taInfo`. A `taInfo` carries the public key, the key identifier, an optional title, and a `certPath` stating the name the anchor is known by plus the policies it may assert, the namespace it is trusted for, and how far below it a path may run. Every rule RFC 5914 states is enforced: an empty `taName`, a policy set carrying qualifiers, a negative path length, and a `requireExplicitPolicy` with no policy set are each refused, and where the anchor carries a certificate its subject, public key and `subjectKeyIdentifier` must match the anchor's own. The four extension types the RFC excludes from `exts` are reported and left out rather than refused, as it says to ignore them. `parseInfo` reads a bare `TrustAnchorInfo`. `pki.trust.parseTrustAnchorList` turns a list into anchors `pki.path.validate` consumes, and `pki.trustanchor.build` writes one Fail-closed — `parse`, `parseInfo`, `pemDecode`, `pemEncode` |
|
|
250
251
|
| `pki.schema.pkcs8` | PKCS#8 private keys (RFC 5208 / 5958): algorithm, raw key bytes, attributes, optional public key. Encrypted keys are recognized but not decrypted. Fail-closed — `parse`, `parseEncrypted`, `pemDecode`, `pemEncode` |
|
|
251
252
|
| `pki.schema.cms` | CMS (RFC 5652 / 5083 / 9629): SignedData (§5, signer infos plus raw signed-attribute bytes for external verification), EnvelopedData (§6, all five RecipientInfo kinds including RFC 5753 key agreement and RFC 9629 KEM recipients with ML-KEM validation), EncryptedData (§8), AuthenticatedData (§9, MAC surface plus raw `authAttrsBytes`), and AuthEnvelopedData (RFC 5083, with RFC 5084 AES-GCM/CCM parameter validation). §11 attribute placement is enforced, countersignatures validate recursively, certificates and CRLs are validated against the closed CHOICE sets and kept raw, and every result is tagged `contentTypeName` Fail-closed — `parse`, `pemDecode`, `pemEncode` |
|
|
252
253
|
| `pki.schema.ocsp` | OCSP requests and responses (RFC 6960): per-certificate status (good, revoked, unknown), responder identity, raw tbs bytes for external verification, certificates kept raw. A response's nonce, and a single response's archive cutoff (§4.4.4) and CRL reference (§4.4.2), are decoded onto their extension entries by name; a malformed one is refused. Non-basic response types are recognized but not decoded. Fail-closed — `parseRequest`, `parseResponse`, `pemDecode`, `pemEncode` |
|
|
@@ -282,7 +283,7 @@ comment blocks, is at [pkijs.com](https://pkijs.com).
|
|
|
282
283
|
| `pki.ocsp` | Online Certificate Status Protocol (RFC 6960), both the responder and relying-party surface. `buildRequest(query, opts)` builds an OCSPRequest for one or more `{ cert, issuer }` pairs, with the CertID hashed under SHA-1 by default per the RFC 5019 lightweight profile (obsoleted by RFC 9919) or under SHA-2, plus an optional RFC 9654 nonce (32 to 128 octets, the requester floor that section sets) and an optional requestor signature, under which the signer certificate's subject is the requestorName unless one is stated (RFC 6960 §4.1.2). `sign(responseData, responder, opts)` produces a signed BasicOCSPResponse over the exact `ResponseData` DER, from the issuing CA directly or a delegated responder, under any `pki.cms.sign` key including the post-quantum ML-DSA and SLH-DSA sets, with `good`, `revoked` (reason and time), or `unknown` per-certificate status, and per-response `singleExtensions` naming the §4.4.4 archive cutoff and the §4.4.2 CRL reference as fields, each read back by name by the parser. The signer refuses what its own lint would grade an error: a pre-encoded singleExtension that RFC 6960 places in requests or in responseExtensions, a CRL entry extension carried at the wrong criticality, and CRL references or entry extensions on a non-issued response, which itself places the §4.4.8 extended revoked definition in responseExtensions without being asked. `buildErrorResponse(status)` produces the unsigned §2.3 error (`tryLater`, `unauthorized`, and the rest). `verify(response, opts)` verifies a response fail-closed against the same hardened gates `pki.path.ocspChecker` runs: the CertID binding, responder authorization (the issuing CA, or a CA-issued delegate bearing id-kp-OCSPSigning and id-pkix-ocsp-nocheck and passing the full out-of-path certificate gates), the signature over `tbsResponseDataBytes`, currency against `thisUpdate` and `nextUpdate`, and the request-nonce echo. It returns `{ status: "good" / "revoked" / "unknown", ... }` and never silently accepts. `verifyRequest(request, opts)` is the responder-side inverse of `buildRequest`: it verifies a client's signed request (RFC 6960 §4.1.1) under the requestor certificate the request carries, trying each embedded certificate since the field is unordered, and reports `signed`, `signatureValid`, every certificate whose key verified the signature and whose subject is the `requestorName` the request states (`signerCerts`, so a key carried under an expired certificate beside its renewal is not hidden behind ordering, while a same-key certificate under another name is not the requestor) with the first one's decoded subject, the full embedded certificate bag (`certs`, including intermediates for path building), and the request's CertIDs; the name check is reported as `requestorNamed` (RFC 6960 §4.1.2) and `valid` requires it, a request no verifying certificate names falls back to listing the verifying ones with `valid: false`, and an unsigned request is reported with `signed: false` rather than refused. Transport-free — `buildRequest`, `sign`, `buildErrorResponse`, `verify`, `verifyRequest` |
|
|
283
284
|
| `pki.ct` | Certificate Transparency (RFC 6962). `parseSctList` decodes the `SignedCertificateTimestampList` a certificate or OCSP response carries, a TLS-presentation-language payload inside the §3.3 double DER wrap, into per-SCT log id, exact `timestamp` (BigInt), named signature algorithm, and raw signature. `reconstructSignedData` rebuilds the exact `digitally-signed` preimage, and `verifySct` verifies an SCT signature against a log's public key, routing an ECDSA signature through the strict DER-conformance gate and verifying through the crypto engine, resolving true or false and throwing a typed error on a structural fault. On the producing side, `encodeSctList` builds the extension value byte for byte as the exact inverse of `parseSctList`, and `signSct` performs a log's signing step. For trust, `parseLogList` ingests the CT log-list JSON into constraint-carrying trusted logs, recomputing each log's id as SHA-256 of its key and refusing a disagreeing id (a swapped key, §3.2) and decoding the state and temporal-interval constraints; `verifySctWithLogList` resolves the log key from an SCT's log id, enforces the state (usable, qualified, and readonly trusted; retired only before retirement; pending and rejected refused) and the temporal-interval window, then delegates the signature check to `verifySct`. `verifyLogListSignature(json, signature, publicKey)` verifies the detached `log_list.sig` over the raw log-list bytes against a caller-pinned signer key (RSASSA-PKCS1-v1.5/SHA-256 and an EC P-256 arm, with forgeable-key defenses failing closed), cross-checked against `openssl dgst`. `fetchLogList(opts)` turns that chain into a live client: it GETs the `log_list.json` and its detached `log_list.sig` over `pki.transport`, verifies the detached signature over the raw fetched bytes against a caller-pinned distributor key before parsing, so an unverified document is never parsed, read, cached, or surfaced, then ingests the same bytes through `parseLogList` and returns the trusted-log set plus the surfaced `version` and `timestamp`. Above the per-SCT layer, `verifySctList` renders a certificate-level verdict over every SCT a certificate carries (§3.3), reporting how many distinct trusted logs verified an SCT (so a duplicated SCT cannot inflate the count) and from how many distinct trusted operators against a caller's CT policy (`minScts` and `minOperators`, both defaulting to the RFC floor of one), where a policy shortfall is a verdict and a per-SCT failure is a recorded row rather than a thrown call; `x509CertEntry` reconstructs the precertificate log entry from a final certificate by removing the SCT-list extension from its TBSCertificate as byte surgery, so its embedded SCTs verify end to end (§3.2). The toolkit bakes in no vendor URL or key, TLS trust is explicit with `rejectUnauthorized` always on, each response is size-capped before the trust chain, and the transport is injectable so the whole path is testable offline — `parseSctList`, `reconstructSignedData`, `verifySct`, `encodeSctList`, `signSct`, `parseLogList`, `verifySctWithLogList`, `verifySctList`, `x509CertEntry`, `verifyLogListSignature`, `fetchLogList` |
|
|
284
285
|
| `pki.merkle` | Merkle-tree proof verification (RFC 6962 / RFC 9162). `leafHash`, `nodeHash`, and `emptyRootHash` build the domain-separated (0x00 leaf, 0x01 node) SHA-256 tree hashes. `verifyInclusion` folds an audit proof back to a root, and `verifyConsistency` reconstructs both the old and new root, which is the append-only guarantee; each is constant-time-compared to a trusted checkpoint root. Fail-closed on bad geometry, sync hashing, transport-free — `leafHash`, `nodeHash`, `emptyRootHash`, `verifyInclusion`, `verifyConsistency` |
|
|
285
|
-
| `pki.trust` | Mozilla and CCADB trust-store ingestion. `parseCertdata` reads the NSS `certdata.txt` object stream and `parseCcadbCsv` the CCADB CSV export, both into one constraint-carrying anchor shape: the per-purpose trust bits, where only `CKT_NSS_TRUSTED_DELEGATOR` grants, and the per-purpose distrust-after dates the bare root list omits. Certificate and trust objects pair by byte-exact issuer and serial rather than adjacency and are cross-checked against the parsed DER, so metadata cannot attach to the wrong root. `anchor()` hands an entry to `pki.path.validate({ trustAnchors, checkPurpose })`, and `anchor(entry, { nameConstraints })` attaches the namespace a root program trusts a root for when that is narrower than the root certificate states, a `{ permitted, excluded }` pair of `{ tag, base }` subtrees that `validate` seeds as the RFC 5280 §6.1.1(h)(i) initial value, intersecting with each certificate's own constraints. Offline, fail-closed, bounded — `parseCertdata`, `parseCcadbCsv`, `anchor` |
|
|
286
|
+
| `pki.trust` | Mozilla and CCADB trust-store ingestion. `parseCertdata` reads the NSS `certdata.txt` object stream and `parseCcadbCsv` the CCADB CSV export, both into one constraint-carrying anchor shape: the per-purpose trust bits, where only `CKT_NSS_TRUSTED_DELEGATOR` grants, and the per-purpose distrust-after dates the bare root list omits. Certificate and trust objects pair by byte-exact issuer and serial rather than adjacency and are cross-checked against the parsed DER, so metadata cannot attach to the wrong root. `anchor()` hands an entry to `pki.path.validate({ trustAnchors, checkPurpose })`, and `anchor(entry, { nameConstraints })` attaches the namespace a root program trusts a root for when that is narrower than the root certificate states, a `{ permitted, excluded }` pair of `{ tag, base }` subtrees that `validate` seeds as the RFC 5280 §6.1.1(h)(i) initial value, intersecting with each certificate's own constraints. `parseTrustAnchorList` reads the IETF's own interchange format for the same thing, an RFC 5914 `TrustAnchorList`, into the same entries, so a list a root program publishes drives `validate` without being mapped by hand. An RFC 5914 anchor's constraints are applied as RFC 5937 §3.2 states, which is that the stricter of the anchor's value and the caller's always wins: its name constraints intersect the caller's permitted subtrees and union its excluded ones, its policy set intersects `userInitialPolicySet`, each of its policy flags may turn the matching `initial*` option on and never off, and its path length bounds the path. `pki.trustanchor.build` writes the list. Offline, fail-closed, bounded — `parseCertdata`, `parseCcadbCsv`, `parseTrustAnchorList`, `anchor` |
|
|
286
287
|
| `pki.shbs` | Stateful hash-based signature verification: HSS/LMS (RFC 8554), carried in X.509 by RFC 9802 and in CMS by RFC 9708, profiled by NIST SP 800-208 for CNSA 2.0 firmware signing. `verify` checks an HSS signature, where every level must pass, and `verifyLms` a single-tree LMS, over the raw public-key and signature blobs the parsers already surface. Pure public-input SHA-256 and SHAKE256 hashing, a data-driven typecode registry, and bounds-before-slice reads; a malformed blob throws a typed `ShbsError` while a well-formed but wrong signature returns `false`. Verification only by design, since stateful signing needs atomic one-time-key state that belongs in an HSM. `pki.path.validate` verifies an HSS-signed certificate on its own, so a chain reaching one needs no separate call: the signature covers the whole tbsCertificate rather than a digest of it (RFC 9802 §7.1), and a CertificateList naming the algorithm takes the same route. It is proven against the self-signed certificate RFC 9802 Appendix A publishes — `verify`, `verifyLms` |
|
|
287
288
|
| `pki.hpke` | Hybrid Public Key Encryption (RFC 9180), the encrypt-to-a-public-key primitive behind TLS ECH, MLS, and OHTTP. `setupS` and `setupR` establish a sender or recipient context (KEM encapsulation plus the HKDF key schedule); the context's `seal` and `open` AEAD-encrypt with a sequence-counter nonce, and `export` derives further secrets; the module-level `seal` and `open` are single-shot wrappers. DHKEM (P-256, P-521, X25519, X448) in all four modes, proven against the RFC 9180 Appendix A vectors, and the post-quantum ML-KEM-512/768/1024 KEMs (IANA 0x0040 to 0x0042) in the base and psk modes, proven against the draft-ietf-hpke-pq Appendix A vectors; HKDF-SHA256/SHA384/SHA512 by AES-GCM / ChaCha20Poly1305 / export-only. DHKEM(P-384) has no vector under an HKDF key schedule and fails closed. Pure composition over `node:crypto`; the PQ/T hybrid KEMs are a registry data-row extension once the CFRG combiner draft stabilizes — `suites`, `setupS`, `setupR`, `seal`, `open` |
|
|
288
289
|
| `pki.kem` | Composite ML-KEM key establishment (draft-ietf-lamps-pq-composite-kem), a post-quantum ML-KEM hybridized with a traditional RSA-OAEP, ECDH, X25519, or X448 so the established secret holds if either component is later broken. `encapsulate` turns a recipient's composite `SubjectPublicKeyInfo` into a 256-bit shared secret and a ciphertext; `decapsulate` recovers the same secret from the ciphertext and the composite PKCS#8 private key. Each component KEM runs independently and the two secrets are mixed through a SHA3-256 combiner that binds the traditional ciphertext, the traditional public key, and a per-algorithm label. The twelve algorithms pair ML-KEM-768 and ML-KEM-1024 with RSA-OAEP 2048/3072/4096, ECDH over P-256/P-384/P-521 and brainpoolP256r1/P384r1, X25519, and X448, each verified against the draft Appendix G known-answer vectors; a malformed key or ciphertext, an unsupported algorithm, or a component decapsulation failure throws a typed `KemError` — `encapsulate`, `decapsulate` |
|
package/index.js
CHANGED
|
@@ -63,6 +63,7 @@ var transport = require("./lib/http-transport");
|
|
|
63
63
|
var jose = require("./lib/jose");
|
|
64
64
|
var acme = require("./lib/acme");
|
|
65
65
|
var trust = require("./lib/trust");
|
|
66
|
+
var trustanchorBuild = require("./lib/trustanchor-build");
|
|
66
67
|
var inspect = require("./lib/inspect");
|
|
67
68
|
var lint = require("./lib/lint");
|
|
68
69
|
var webauthn = require("./lib/webauthn");
|
|
@@ -213,6 +214,11 @@ module.exports = {
|
|
|
213
214
|
// root list omits); pki.trust.anchor hands one to pki.path.validate. Offline:
|
|
214
215
|
// the operator supplies the text; no fetch.
|
|
215
216
|
trust: trust,
|
|
217
|
+
// `trustanchor` writes the RFC 5914 TrustAnchorList a root program publishes:
|
|
218
|
+
// each anchor as a certificate, a bare TBSCertificate, or a TrustAnchorInfo
|
|
219
|
+
// carrying the name it is known by plus the policies, namespace and path
|
|
220
|
+
// length a relying party applies beneath it. pki.schema.trustanchor reads one.
|
|
221
|
+
trustanchor: trustanchorBuild,
|
|
216
222
|
// `inspect` is human-readable inspection -- pki.inspect.certificate(pem|der|parsed)
|
|
217
223
|
// renders an OpenSSL-familiar `x509 -text`-style report from the strict parser +
|
|
218
224
|
// OID registry, naming extension/algorithm OIDs OpenSSL shows only as raw bytes.
|
package/lib/acme.js
CHANGED
|
@@ -1631,7 +1631,10 @@ function _parseLinkValue(raw) {
|
|
|
1631
1631
|
function _parseLinkRelation(headers, base, relName, opts) {
|
|
1632
1632
|
opts = opts || {};
|
|
1633
1633
|
var raw = null;
|
|
1634
|
-
|
|
1634
|
+
var hNames = guard.identifier.optionNames(headers);
|
|
1635
|
+
for (var hi = 0; hi < hNames.length; hi++) {
|
|
1636
|
+
if (hNames[hi].toLowerCase() === "link") { raw = headers[hNames[hi]]; break; }
|
|
1637
|
+
}
|
|
1635
1638
|
if (raw == null) return [];
|
|
1636
1639
|
var baseUrl = new URL(base), baseOrigin = baseUrl.origin, baseHref = baseUrl.href;
|
|
1637
1640
|
var baseKey = _dedupKey(baseHref);
|
|
@@ -1867,8 +1870,7 @@ function client(directoryUrl, opts) {
|
|
|
1867
1870
|
return guard.async.invoked(transport, [{ method: method, url: url, headers: reqHeaders, body: body, tls: _tlsFor(url), proxy: budgets.proxy, timeout: budgets.timeout, maxResponseBytes: budgets.maxResponseBytes }], E, "acme/bad-input", "opts.transport");
|
|
1868
1871
|
}).then(function (res) {
|
|
1869
1872
|
res = res || {};
|
|
1870
|
-
var h =
|
|
1871
|
-
Object.keys(res.headers || {}).forEach(function (k) { h[k.toLowerCase()] = res.headers[k]; });
|
|
1873
|
+
var h = guard.header.lowerCased(res.headers);
|
|
1872
1874
|
var blen = res.body == null ? 0 : (typeof res.body === "string" ? Buffer.byteLength(res.body, "utf8")
|
|
1873
1875
|
: (Buffer.isBuffer(res.body) ? guard.bytes.lengthOf(res.body) : guard.bytes.lengthOf(guard.bytes.source(res.body, E, "acme/bad-response", "the response body"))));
|
|
1874
1876
|
if (blen > budgets.maxResponseBytes) throw E("acme/response-too-large", "the response body (" + blen + " bytes) exceeds the " + budgets.maxResponseBytes + "-byte cap");
|
|
@@ -1940,7 +1942,11 @@ function client(directoryUrl, opts) {
|
|
|
1940
1942
|
}
|
|
1941
1943
|
function _post(url, builder, extra, mode, accept, okStatuses) {
|
|
1942
1944
|
return _postJws(url, function (nonce, override) {
|
|
1943
|
-
var base =
|
|
1945
|
+
var base = guard.identifier.ownOptions(extra);
|
|
1946
|
+
base.key = override ? override.key : accountKey;
|
|
1947
|
+
base.alg = override ? override.alg : alg;
|
|
1948
|
+
base.nonce = nonce;
|
|
1949
|
+
base.url = url;
|
|
1944
1950
|
if (mode === "jwk") base.jwk = override ? _jwkForAlg(accountJwk, override.alg) : accountJwk; else base.kid = _requireKid();
|
|
1945
1951
|
return builder(base);
|
|
1946
1952
|
}, accept, okStatuses, true);
|
|
@@ -2136,7 +2142,9 @@ function client(directoryUrl, opts) {
|
|
|
2136
2142
|
return _resource("revokeCert").then(function (url) {
|
|
2137
2143
|
if (certKeyMode) {
|
|
2138
2144
|
return _postJws(url, function (nonce) {
|
|
2139
|
-
|
|
2145
|
+
var rb = guard.identifier.ownOptions(extra);
|
|
2146
|
+
rb.key = o.certKey; rb.alg = o.certAlg; rb.nonce = nonce; rb.url = url; rb.jwk = o.certJwk;
|
|
2147
|
+
return revokeCert(rb);
|
|
2140
2148
|
}).then(function () { return true; });
|
|
2141
2149
|
}
|
|
2142
2150
|
return _post(url, revokeCert, extra, "kid").then(function () { return true; });
|
package/lib/attrcert-sign.js
CHANGED
|
@@ -202,7 +202,7 @@ function _objectDigestInfoContent(odi) {
|
|
|
202
202
|
function _encodeHolder(holder) {
|
|
203
203
|
if (!holder || typeof holder !== "object" || Buffer.isBuffer(holder)) throw _err("attrcert/bad-input", "holder must be an object with exactly one form");
|
|
204
204
|
guard.identifier.assertKnownKeys(holder, KNOWN_HOLDER_KEYS, _err, "attrcert/bad-input", "unknown holder form ");
|
|
205
|
-
var forms =
|
|
205
|
+
var forms = guard.identifier.optionNames(holder, KNOWN_HOLDER_KEYS).filter(function (k) { return holder[k] != null; });
|
|
206
206
|
if (forms.length !== 1) throw _err("attrcert/bad-input", "holder must carry exactly one form (entityName, baseCertificateID, fromCertificate, or objectDigestInfo), got " + forms.length);
|
|
207
207
|
if (holder.entityName != null) {
|
|
208
208
|
return b.sequence([_b.encodeGeneralNames(_gnList(holder.entityName), 1)]);
|
|
@@ -403,7 +403,7 @@ function _buildAttributes(attrSpec) {
|
|
|
403
403
|
guard.identifier.assertKnownKeys(attrSpec, ATTR_VALUE_ENCODER, _err, "attrcert/bad-input", function (k) {
|
|
404
404
|
return "unknown attribute " + JSON.stringify(k) + "; pass a pre-encoded Attribute DER via the array form for a custom attribute";
|
|
405
405
|
});
|
|
406
|
-
|
|
406
|
+
guard.identifier.optionNames(attrSpec, ATTR_VALUE_ENCODER).forEach(function (k) {
|
|
407
407
|
add(O(ATTR_OID_NAME[k]), ATTR_VALUE_ENCODER[k](attrSpec[k]));
|
|
408
408
|
});
|
|
409
409
|
if (!attrs.length) throw _err("attrcert/bad-attributes", "attributes must carry at least one Attribute (RFC 5755 sec. 4.2.7)");
|
|
@@ -462,7 +462,7 @@ function _buildExtensions(extSpec, ctx) {
|
|
|
462
462
|
});
|
|
463
463
|
var out = [], seen = intrinsic.create(null);
|
|
464
464
|
var akiDeclined = false;
|
|
465
|
-
|
|
465
|
+
guard.identifier.optionNames(extSpec, EXT_META).forEach(function (k) {
|
|
466
466
|
var val = extSpec[k];
|
|
467
467
|
if (val == null) return;
|
|
468
468
|
if (k === "authorityKeyIdentifier" && val === false) { akiDeclined = true; return; }
|
package/lib/cmc-build.js
CHANGED
|
@@ -983,10 +983,10 @@ async function _build(spec, signer, opts) {
|
|
|
983
983
|
function _copySigners(signer, owned) {
|
|
984
984
|
function one(s) {
|
|
985
985
|
if (!s || typeof s !== "object") return s;
|
|
986
|
-
var out = intrinsic.create(null)
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
out[
|
|
986
|
+
var out = intrinsic.create(null);
|
|
987
|
+
var sNames = guard.identifier.optionNames(s);
|
|
988
|
+
for (var si = 0; si < sNames.length; si++) {
|
|
989
|
+
out[sNames[si]] = copyValue(s[sNames[si]], sNames[si] === "key");
|
|
990
990
|
}
|
|
991
991
|
return out;
|
|
992
992
|
}
|
|
@@ -997,11 +997,11 @@ function _copySigners(signer, owned) {
|
|
|
997
997
|
}
|
|
998
998
|
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return _own(Buffer.from(v), true); // allow:byte-source-narrow -- private key bytes (Buffer borrowed / Uint8Array copied+wiped)
|
|
999
999
|
if (!v || typeof v !== "object" || typeof v.type === "string") return v;
|
|
1000
|
-
var c = intrinsic.create(null)
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
var cv = v[
|
|
1004
|
-
c[
|
|
1000
|
+
var c = intrinsic.create(null);
|
|
1001
|
+
var cNames = guard.identifier.optionNames(v);
|
|
1002
|
+
for (var ci = 0; ci < cNames.length; ci++) {
|
|
1003
|
+
var cv = v[cNames[ci]];
|
|
1004
|
+
c[cNames[ci]] = (Buffer.isBuffer(cv) || cv instanceof Uint8Array) ? _own(Buffer.from(cv), true) : cv; // allow:byte-source-narrow -- composite key component
|
|
1005
1005
|
}
|
|
1006
1006
|
return c;
|
|
1007
1007
|
}
|
package/lib/cmc-verify.js
CHANGED
|
@@ -336,8 +336,7 @@ function _copyAnyBytes(v) {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
function _snapshotSent(sent) {
|
|
339
|
-
var out =
|
|
340
|
-
for (k in sent) { if (_hasOwn(sent, k)) out[k] = sent[k]; }
|
|
339
|
+
var out = guard.identifier.ownOptions(sent);
|
|
341
340
|
if (out.transactionId != null) {
|
|
342
341
|
out.transactionIdValue = guard.range.authoredInteger(out.transactionId, E, "cmc/bad-input", "sent.transactionId");
|
|
343
342
|
}
|
|
@@ -355,9 +354,10 @@ function _snapshotSent(sent) {
|
|
|
355
354
|
var takenSecret = null;
|
|
356
355
|
try {
|
|
357
356
|
if (out.recipient && typeof out.recipient === "object") {
|
|
358
|
-
var r = intrinsic.create(null)
|
|
359
|
-
|
|
360
|
-
|
|
357
|
+
var r = intrinsic.create(null);
|
|
358
|
+
var rNames = guard.identifier.optionNames(out.recipient);
|
|
359
|
+
for (var ri = 0; ri < rNames.length; ri++) {
|
|
360
|
+
var rk = rNames[ri];
|
|
361
361
|
r[rk] = _copyAnyBytes(out.recipient[rk]);
|
|
362
362
|
if (rk === "secret" && guard.bytes.isByteSource(r[rk])) takenSecret = r[rk];
|
|
363
363
|
}
|
package/lib/cmp-build.js
CHANGED
|
@@ -552,7 +552,7 @@ function _encodeNestedContent(list) {
|
|
|
552
552
|
|
|
553
553
|
function _encodeBody(bodySpec, key, opts) {
|
|
554
554
|
if (!bodySpec || typeof bodySpec !== "object" || Buffer.isBuffer(bodySpec)) throw _err("cmp/bad-input", "message.body must be a single-key object");
|
|
555
|
-
var keys =
|
|
555
|
+
var keys = guard.identifier.optionNames(bodySpec);
|
|
556
556
|
if (keys.length !== 1) throw _err("cmp/bad-input", "message.body must have exactly one arm, got " + keys.length);
|
|
557
557
|
var arm = keys[0];
|
|
558
558
|
if (!KNOWN_BODY_KEYS[guard.text.keyOf(arm)]) throw _err("cmp/bad-input", "unknown body arm " + guard.text.showValue(arm));
|
|
@@ -1055,7 +1055,7 @@ function _transfer(url, message, opts) {
|
|
|
1055
1055
|
try { proxy = httpTransport.snapshotProxy(opts.proxy); }
|
|
1056
1056
|
catch (e) { throw _err("cmp/bad-input", "the options object could not be read", e); }
|
|
1057
1057
|
var headers = intrinsic.create(null);
|
|
1058
|
-
|
|
1058
|
+
guard.identifier.optionNames(opts.headers).forEach(function (k) {
|
|
1059
1059
|
var lk = k.toLowerCase();
|
|
1060
1060
|
if (lk !== "content-length" && lk !== "transfer-encoding" && lk !== "content-type") headers[k] = opts.headers[k];
|
|
1061
1061
|
});
|
|
@@ -1063,8 +1063,7 @@ function _transfer(url, message, opts) {
|
|
|
1063
1063
|
var tls = _tlsForTransfer(opts);
|
|
1064
1064
|
return guard.async.invoked(transport, [{ method: "POST", url: parsedUrl.href, headers: headers, body: der, tls: tls, proxy: proxy, timeout: timeout, maxResponseBytes: maxResponseBytes }], _err, "cmp/bad-input", "opts.transport").then(function (res) {
|
|
1065
1065
|
res = res || {};
|
|
1066
|
-
var h =
|
|
1067
|
-
Object.keys(res.headers || {}).forEach(function (k) { h[k.toLowerCase()] = res.headers[k]; });
|
|
1066
|
+
var h = guard.header.lowerCased(res.headers);
|
|
1068
1067
|
var body = guard.bytes.isByteSource(res.body)
|
|
1069
1068
|
? guard.bytes.source(res.body, CmpError, "cmp/bad-response", "the CMP response body")
|
|
1070
1069
|
: Buffer.from(String(res.body == null ? "" : res.body), "utf8");
|
package/lib/cmp-session.js
CHANGED
|
@@ -435,7 +435,7 @@ function session(opts) {
|
|
|
435
435
|
* drop the one the caller supplied, and send the session to the default client instead. */
|
|
436
436
|
var _transport = guard.identifier.assertCallableOption(opts, "transport", _err, "cmp/bad-input",
|
|
437
437
|
"opts.transport", "(url, reqDer, opts) => Promise<{ responseBytes, status }>");
|
|
438
|
-
opts =
|
|
438
|
+
opts = guard.identifier.ownOptions(opts);
|
|
439
439
|
if (typeof opts.url !== "string" || !opts.url) throw _err("cmp/bad-input", "opts.url (the CMP endpoint) is required");
|
|
440
440
|
|
|
441
441
|
var isSig = opts.key != null || opts.cert != null;
|
|
@@ -930,12 +930,12 @@ function session(opts) {
|
|
|
930
930
|
* than a record, and is left to the builder's own snapshot. */
|
|
931
931
|
function _snapshotArm(armSpec) {
|
|
932
932
|
if (!_isRecord(armSpec)) return armSpec;
|
|
933
|
-
var copy =
|
|
934
|
-
if (_isRecord(copy.certTemplate)) copy.certTemplate =
|
|
933
|
+
var copy = guard.identifier.ownOptions(armSpec);
|
|
934
|
+
if (_isRecord(copy.certTemplate)) copy.certTemplate = guard.identifier.ownOptions(copy.certTemplate);
|
|
935
935
|
/** @internal The proof of possession is copied for the same reason the template is: the session
|
|
936
936
|
* decides on `pop.type` which modes it will send, and a field answering one way to that decision
|
|
937
937
|
* and another to the builder would put a request on the wire in a mode the session refused. */
|
|
938
|
-
if (_isRecord(copy.pop)) copy.pop =
|
|
938
|
+
if (_isRecord(copy.pop)) copy.pop = guard.identifier.ownOptions(copy.pop);
|
|
939
939
|
return copy;
|
|
940
940
|
}
|
|
941
941
|
/** @internal A record the session copies field by field, as opposed to a byte source, which every
|
|
@@ -1132,7 +1132,7 @@ function session(opts) {
|
|
|
1132
1132
|
* sends the ONE arm it read out of this object, so a key the session does not recognize would
|
|
1133
1133
|
* otherwise be dropped silently instead of naming the caller's mistake. */
|
|
1134
1134
|
guard.identifier.assertKnownKeys(request, ENROLL_ARMS, _err, "cmp/bad-input", "unknown enroll request field ");
|
|
1135
|
-
var arms =
|
|
1135
|
+
var arms = guard.identifier.optionNames(request, ENROLL_ARMS).filter(function (k) { return request[k] != null; });
|
|
1136
1136
|
if (arms.length !== 1 || !ENROLL_ARMS[arms[0]]) {
|
|
1137
1137
|
throw _err("cmp/bad-input", "enroll(request) must carry EXACTLY ONE enrollment arm (ir / cr / kur / p10cr)");
|
|
1138
1138
|
}
|
|
@@ -1569,7 +1569,7 @@ function session(opts) {
|
|
|
1569
1569
|
async function _info(request) {
|
|
1570
1570
|
guard.identifier.assertPlainRecord(request, _err, "cmp/bad-input", "info(request), an object naming one support operation (caCerts / rootCaCert / certReqTemplate / crlUpdate),");
|
|
1571
1571
|
guard.identifier.assertKnownKeys(request, INFO_OPS, _err, "cmp/bad-input", "unknown info request field ");
|
|
1572
|
-
var keys =
|
|
1572
|
+
var keys = guard.identifier.optionNames(request, INFO_OPS), names = [], values = [];
|
|
1573
1573
|
for (var ki = 0; ki < keys.length; ki++) {
|
|
1574
1574
|
var kv = request[keys[ki]];
|
|
1575
1575
|
if (kv == null) continue;
|
package/lib/cms-verify.js
CHANGED
|
@@ -608,10 +608,10 @@ function _cloneAnchorValue(v, depth) {
|
|
|
608
608
|
" levels, so it cannot be copied before the chain is walked; pass the anchor as certificate DER or a { name, publicKey, algorithm } tuple");
|
|
609
609
|
}
|
|
610
610
|
if (Array.isArray(v)) return guard.list.copyMap(v, function (e) { return _cloneAnchorValue(e, depth + 1); });
|
|
611
|
-
var out = intrinsic.create(null)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
out[
|
|
611
|
+
var out = intrinsic.create(null);
|
|
612
|
+
var vNames = guard.identifier.optionNames(v);
|
|
613
|
+
for (var vi = 0; vi < vNames.length; vi++) {
|
|
614
|
+
out[vNames[vi]] = _cloneAnchorValue(v[vNames[vi]], depth + 1);
|
|
615
615
|
}
|
|
616
616
|
return out;
|
|
617
617
|
}
|
package/lib/crl-sign.js
CHANGED
|
@@ -196,7 +196,7 @@ function _validatePreEncodedIdp(inner) {
|
|
|
196
196
|
function _freshestValue(spec) {
|
|
197
197
|
if (!Array.isArray(spec) || !spec.length) throw _err("crl/bad-input", "freshestCRL must be a non-empty array of GeneralNames (or { fullName } distribution points)");
|
|
198
198
|
var dense = _b.reqDenseArray(spec, "freshestCRL");
|
|
199
|
-
var dps = dense.every(function (e) { return e && typeof e === "object" && !Buffer.isBuffer(e) &&
|
|
199
|
+
var dps = dense.every(function (e) { return e && typeof e === "object" && !Buffer.isBuffer(e) && guard.identifier.optionNames(e).length === 1 && e.fullName != null; })
|
|
200
200
|
? dense.map(function (dp) { return dp.fullName; })
|
|
201
201
|
: [dense];
|
|
202
202
|
return b.sequence(dps.map(function (fullName) {
|
package/lib/crmf-sign.js
CHANGED
|
@@ -136,7 +136,7 @@ function _buildAttrTypeAndValues(spec, code, label, valueMap) {
|
|
|
136
136
|
guard.identifier.assertKnownKeys(spec, valueMap, _err, "crmf/bad-input", function (k) {
|
|
137
137
|
return "unknown " + label + " " + JSON.stringify(k) + "; pass a pre-encoded AttributeTypeAndValue DER via the array form for a " + label + " entry outside " + Object.keys(valueMap).join("/");
|
|
138
138
|
});
|
|
139
|
-
|
|
139
|
+
guard.identifier.optionNames(spec, valueMap).forEach(function (k) {
|
|
140
140
|
var enc = valueMap[k];
|
|
141
141
|
var typeOid = O(k);
|
|
142
142
|
if (seen[typeOid]) throw _err(code, "duplicate " + label + " type " + k);
|
package/lib/ct.js
CHANGED
|
@@ -1076,7 +1076,7 @@ function _fetchBody(transport, url, req, label) {
|
|
|
1076
1076
|
}).then(function (res) {
|
|
1077
1077
|
res = res || {};
|
|
1078
1078
|
var h = intrinsic.create(null);
|
|
1079
|
-
|
|
1079
|
+
guard.identifier.optionNames(res.headers).forEach(function (k) { h[k.toLowerCase()] = res.headers[k]; });
|
|
1080
1080
|
var body = guard.bytes.isByteSource(res.body)
|
|
1081
1081
|
? guard.bytes.source(res.body, CtError, "ct/bad-input", label)
|
|
1082
1082
|
: Buffer.from(String(res.body == null ? "" : res.body), "utf8");
|
|
@@ -1149,7 +1149,7 @@ async function fetchLogList(opts) {
|
|
|
1149
1149
|
var timeout = guard.limits.cap(opts.timeout, "timeout", DEFAULT_FETCH_TIMEOUT, { E: _ctErr, code: "ct/bad-input", min: 1, max: MAX_FETCH_TIMEOUT });
|
|
1150
1150
|
var maxResponseBytes = guard.limits.cap(opts.maxResponseBytes, "maxResponseBytes", C.LIMITS.CT_LOG_LIST_MAX_BYTES, { E: _ctErr, code: "ct/bad-input", min: 1, max: C.LIMITS.CT_LOG_LIST_MAX_BYTES });
|
|
1151
1151
|
var headers = intrinsic.create(null);
|
|
1152
|
-
|
|
1152
|
+
guard.identifier.optionNames(opts.headers).forEach(function (k) {
|
|
1153
1153
|
var lk = k.toLowerCase();
|
|
1154
1154
|
if (lk !== "content-length" && lk !== "transfer-encoding") headers[k] = opts.headers[k];
|
|
1155
1155
|
});
|
package/lib/est.js
CHANGED
|
@@ -105,7 +105,8 @@ var CLIENT_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
|
105
105
|
function _withClient(extra) {
|
|
106
106
|
var out = intrinsic.create(null);
|
|
107
107
|
Object.keys(CLIENT_OPTS).forEach(function (k) { out[k] = 1; });
|
|
108
|
-
|
|
108
|
+
var en = guard.identifier.optionNames(extra);
|
|
109
|
+
for (var ei = 0; ei < en.length; ei++) out[en[ei]] = 1;
|
|
109
110
|
return out;
|
|
110
111
|
}
|
|
111
112
|
var CACERTS_OPTS = _withClient(null);
|
|
@@ -886,7 +887,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
886
887
|
function _headersFor(u) {
|
|
887
888
|
digestSent = false;
|
|
888
889
|
if (u.origin !== initialOrigin || (!authValue && !digestChallenge)) return headers;
|
|
889
|
-
var hh =
|
|
890
|
+
var hh = guard.identifier.ownOptions(headers);
|
|
890
891
|
if (digestChallenge) {
|
|
891
892
|
if (!httpDigest.inProtectionSpace(digestChallenge, u.origin, u.pathname + u.search)) return headers;
|
|
892
893
|
var nkey = digestChallenge.nonce;
|
|
@@ -912,7 +913,7 @@ function _drive(method, url, body, headers, opts, transport, budgets) {
|
|
|
912
913
|
if (status === 303 && method !== "GET" && method !== "HEAD") {
|
|
913
914
|
method = "GET";
|
|
914
915
|
body = null;
|
|
915
|
-
if (headers["content-type"]) { headers =
|
|
916
|
+
if (headers["content-type"]) { headers = guard.identifier.ownOptions(headers); delete headers["content-type"]; }
|
|
916
917
|
}
|
|
917
918
|
url = _redirectTarget(url, h.location, method, opts);
|
|
918
919
|
redirects += 1;
|
|
@@ -987,7 +988,7 @@ function _client(op, method, baseUrl, body, headers, opts, proxySnap, onAttempt,
|
|
|
987
988
|
onAttempt: typeof onAttempt === "function" ? onAttempt : null,
|
|
988
989
|
};
|
|
989
990
|
_authScheme(opts);
|
|
990
|
-
return _drive(method, url, body,
|
|
991
|
+
return _drive(method, url, body, guard.identifier.ownOptions(headers), opts, transport, budgets);
|
|
991
992
|
}
|
|
992
993
|
|
|
993
994
|
function _certsResult(op, res, opts, csrSpki) {
|
|
@@ -1313,18 +1314,16 @@ function _assertAgrees(name, supplied, carried) {
|
|
|
1313
1314
|
|
|
1314
1315
|
function _copyRecipient(r) {
|
|
1315
1316
|
if (!r || typeof r !== "object") return r;
|
|
1316
|
-
var out = intrinsic.create(null)
|
|
1317
|
-
|
|
1317
|
+
var out = intrinsic.create(null);
|
|
1318
|
+
var rNames = guard.identifier.optionNames(r);
|
|
1319
|
+
for (var ri = 0; ri < rNames.length; ri++) out[rNames[ri]] = _copyBytes(r[rNames[ri]]);
|
|
1318
1320
|
return out;
|
|
1319
1321
|
}
|
|
1320
1322
|
|
|
1321
1323
|
function _shallowCopy(o) {
|
|
1322
|
-
var out =
|
|
1323
|
-
for (k in o) { if (_hasOwn(o, k)) out[k] = o[k]; }
|
|
1324
|
+
var out = guard.identifier.ownOptions(o);
|
|
1324
1325
|
if (out.auth && typeof out.auth === "object") {
|
|
1325
|
-
|
|
1326
|
-
for (ak in out.auth) { if (_hasOwn(out.auth, ak)) a[ak] = out.auth[ak]; }
|
|
1327
|
-
out.auth = a;
|
|
1326
|
+
out.auth = guard.identifier.ownOptions(out.auth);
|
|
1328
1327
|
}
|
|
1329
1328
|
return out;
|
|
1330
1329
|
}
|
|
@@ -1696,7 +1695,7 @@ function _assertConfidentialCipher(res) {
|
|
|
1696
1695
|
|
|
1697
1696
|
function _ciHeaderList(headers, name) {
|
|
1698
1697
|
headers = headers || {};
|
|
1699
|
-
var lname = name.toLowerCase(), keys =
|
|
1698
|
+
var lname = name.toLowerCase(), keys = guard.identifier.optionNames(headers), parts = [];
|
|
1700
1699
|
for (var i = 0; i < keys.length; i++) {
|
|
1701
1700
|
if (keys[i].toLowerCase() !== lname) continue;
|
|
1702
1701
|
var v = headers[keys[i]];
|
|
@@ -1708,7 +1707,7 @@ function _ciHeaderList(headers, name) {
|
|
|
1708
1707
|
|
|
1709
1708
|
function _ciHeader(headers, name) {
|
|
1710
1709
|
headers = headers || {};
|
|
1711
|
-
var lname = name.toLowerCase(), keys =
|
|
1710
|
+
var lname = name.toLowerCase(), keys = guard.identifier.optionNames(headers), found = null, n = 0;
|
|
1712
1711
|
for (var i = 0; i < keys.length; i++) {
|
|
1713
1712
|
if (keys[i].toLowerCase() !== lname) continue;
|
|
1714
1713
|
n += 1;
|
package/lib/framework-error.js
CHANGED
|
@@ -176,6 +176,8 @@ var AttrCertError = defineClass("AttrCertError", { withCause: true });
|
|
|
176
176
|
|
|
177
177
|
var CrmfError = defineClass("CrmfError", { withCause: true });
|
|
178
178
|
|
|
179
|
+
var TrustAnchorError = defineClass("TrustAnchorError", { withCause: true });
|
|
180
|
+
|
|
179
181
|
var Pkcs12Error = defineClass("Pkcs12Error", { withCause: true });
|
|
180
182
|
|
|
181
183
|
var CmpError = defineClass("CmpError", { withCause: true });
|
|
@@ -238,6 +240,7 @@ module.exports = {
|
|
|
238
240
|
TspError: TspError,
|
|
239
241
|
AttrCertError: AttrCertError,
|
|
240
242
|
CrmfError: CrmfError,
|
|
243
|
+
TrustAnchorError: TrustAnchorError,
|
|
241
244
|
Pkcs12Error: Pkcs12Error,
|
|
242
245
|
CmpError: CmpError,
|
|
243
246
|
PathError: PathError,
|
package/lib/guard-header.js
CHANGED
|
@@ -9,6 +9,9 @@ var _freeze = require("./guard-intrinsic").freeze;
|
|
|
9
9
|
var _charCodeAt = require("./guard-intrinsic").uncurry(String.prototype.charCodeAt);
|
|
10
10
|
var _stringify = require("./guard-intrinsic").stringify;
|
|
11
11
|
var _String = require("./guard-intrinsic").String;
|
|
12
|
+
var _create = require("./guard-intrinsic").create;
|
|
13
|
+
var _toLowerCase = require("./guard-intrinsic").uncurry(String.prototype.toLowerCase);
|
|
14
|
+
var _optionNames = require("./guard-identifier").optionNames;
|
|
12
15
|
|
|
13
16
|
// @enforced-by behavioral -- the CR/LF/NUL value reject + the ftext name check have
|
|
14
17
|
function assertField(name, value, E, code) {
|
|
@@ -28,4 +31,15 @@ function assertField(name, value, E, code) {
|
|
|
28
31
|
return v;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
/** @internal A response's header fields on a prototype-less object, keyed by the lowercased name,
|
|
35
|
+
* which is how RFC 9110 sec. 5.1 says to compare them. Read by own key, so a field an injected
|
|
36
|
+
* transport defines non-enumerably is not lost between the response and the lookup. */
|
|
37
|
+
// @enforced-by option-bag-read-by-enumerable-key
|
|
38
|
+
function lowerCased(headers) {
|
|
39
|
+
var out = _create(null);
|
|
40
|
+
var names = _optionNames(headers);
|
|
41
|
+
for (var i = 0; i < names.length; i++) out[_toLowerCase(names[i])] = headers[names[i]];
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = _freeze({ assertField: assertField, lowerCased: lowerCased });
|
package/lib/guard-identifier.js
CHANGED
|
@@ -100,6 +100,38 @@ function snapshotOptions(obj, known) {
|
|
|
100
100
|
return out;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/** @internal The option names a bag carries, read the way assertKnownKeys read them: every own
|
|
104
|
+
* string key, whatever its descriptor says. Object.keys reports only the enumerable ones, so an
|
|
105
|
+
* option written with `enumerable: false` clears the door and then vanishes from the read.
|
|
106
|
+
* `known` narrows the result to the declared options. */
|
|
107
|
+
// @enforced-by option-bag-read-by-enumerable-key
|
|
108
|
+
function optionNames(obj, known) {
|
|
109
|
+
if (obj === null || obj === undefined) return [];
|
|
110
|
+
var out = [];
|
|
111
|
+
_forEach(_ownKeys(_Object(obj)), function (k) {
|
|
112
|
+
if (typeof k !== "string") return;
|
|
113
|
+
if (known !== null && known !== undefined && !_hasOwn(known, k)) return;
|
|
114
|
+
_append(out, k);
|
|
115
|
+
});
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @internal A caller's options bag copied onto a prototype-less object, keyed by every own string
|
|
120
|
+
* property, not the enumerable ones alone. Each is read once, so an accessor-backed option
|
|
121
|
+
* answers the copy and not the later reads. Inherited properties are left behind: the copy is
|
|
122
|
+
* what the decisions read, and a name reaching it from a prototype is not one the caller set. */
|
|
123
|
+
// @enforced-by option-bag-read-by-enumerable-key
|
|
124
|
+
function ownOptions(obj) {
|
|
125
|
+
var out = _create(null);
|
|
126
|
+
if (obj === null || obj === undefined) return out;
|
|
127
|
+
var o = _Object(obj);
|
|
128
|
+
_forEach(_ownKeys(o), function (k) {
|
|
129
|
+
if (typeof k !== "string") return;
|
|
130
|
+
out[k] = o[k];
|
|
131
|
+
});
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
103
135
|
var _PRISTINE_OBJECT_PROTO = (function () {
|
|
104
136
|
var s = _create(null);
|
|
105
137
|
_forEach(["constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString",
|
|
@@ -528,6 +560,8 @@ module.exports = intrinsic.freeze({
|
|
|
528
560
|
optionsObject: optionsObject,
|
|
529
561
|
refuseAccessorFields: refuseAccessorFields,
|
|
530
562
|
snapshotOptions: snapshotOptions,
|
|
563
|
+
optionNames: optionNames,
|
|
564
|
+
ownOptions: ownOptions,
|
|
531
565
|
readableNames: readableNames,
|
|
532
566
|
readableIndices: readableIndices
|
|
533
567
|
});
|
package/lib/hpke.js
CHANGED
|
@@ -228,7 +228,11 @@ function _extractAndExpand(kem, dh, kemContext) {
|
|
|
228
228
|
try { return _labeledExpand(kdf, sid, eaePrk, "shared_secret", kemContext, kem.Nsecret); }
|
|
229
229
|
finally { guard.secret.zeroize(eaePrk, HpkeError, "hpke/bad-input", "the KEM extract PRK"); }
|
|
230
230
|
}
|
|
231
|
-
function _kemId(kem) {
|
|
231
|
+
function _kemId(kem) {
|
|
232
|
+
var names = guard.identifier.optionNames(KEMS);
|
|
233
|
+
for (var i = 0; i < names.length; i++) if (KEMS[names[i]] === kem) return Number(names[i]);
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
232
236
|
|
|
233
237
|
function _ephemeral(kem, eph) {
|
|
234
238
|
if (eph != null) {
|
|
@@ -429,7 +433,8 @@ var _COMMON_KEYS = intrinsic.assign(intrinsic.create(null), { mode: 1, info: 1,
|
|
|
429
433
|
function _withCommon(extra) {
|
|
430
434
|
var out = intrinsic.create(null);
|
|
431
435
|
Object.keys(_COMMON_KEYS).forEach(function (k) { out[k] = 1; });
|
|
432
|
-
|
|
436
|
+
var en = guard.identifier.optionNames(extra);
|
|
437
|
+
for (var ei = 0; ei < en.length; ei++) out[en[ei]] = 1;
|
|
433
438
|
return out;
|
|
434
439
|
}
|
|
435
440
|
var _SETUP_S_KEYS = _withCommon({
|
package/lib/http-transport.js
CHANGED
|
@@ -279,10 +279,10 @@ function _copyableArray(a) {
|
|
|
279
279
|
function snapshotProxy(p) {
|
|
280
280
|
try {
|
|
281
281
|
if (!_copyableRecord(p)) return p;
|
|
282
|
-
var out =
|
|
283
|
-
if (_copyableRecord(p.auth)) out.auth =
|
|
282
|
+
var out = guard.identifier.ownOptions(p);
|
|
283
|
+
if (_copyableRecord(p.auth)) out.auth = guard.identifier.ownOptions(p.auth);
|
|
284
284
|
if (_copyableRecord(p.tls)) {
|
|
285
|
-
out.tls =
|
|
285
|
+
out.tls = guard.identifier.ownOptions(p.tls);
|
|
286
286
|
var a = p.tls.anchors;
|
|
287
287
|
if (_copyableArray(a)) {
|
|
288
288
|
var copied = [];
|
|
@@ -492,7 +492,7 @@ function httpsTransport(defaults) {
|
|
|
492
492
|
var bodyIsFn = typeof body === "function";
|
|
493
493
|
if (!bodyIsFn && guard.bytes.isByteSource(body)) body = guard.bytes.source(body, E, C("bad-input"), "the request body");
|
|
494
494
|
|
|
495
|
-
var reqHeaders =
|
|
495
|
+
var reqHeaders = guard.identifier.ownOptions(request.headers);
|
|
496
496
|
Object.keys(reqHeaders).forEach(function (k) { var lk = k.toLowerCase(); if (lk === "proxy-authorization" || lk === "proxy-connection") delete reqHeaders[k]; });
|
|
497
497
|
if (bodyIsFn) {
|
|
498
498
|
Object.keys(reqHeaders).forEach(function (k) { var lk = k.toLowerCase(); if (lk === "content-length" || lk === "transfer-encoding" || lk === "expect") delete reqHeaders[k]; });
|
|
@@ -609,8 +609,7 @@ function httpsTransport(defaults) {
|
|
|
609
609
|
if (settled) return;
|
|
610
610
|
settled = true;
|
|
611
611
|
clearTimer();
|
|
612
|
-
var lower =
|
|
613
|
-
Object.keys(res.headers || {}).forEach(function (k) { lower[k.toLowerCase()] = res.headers[k]; });
|
|
612
|
+
var lower = guard.header.lowerCased(res.headers);
|
|
614
613
|
resolve({
|
|
615
614
|
status: res.statusCode,
|
|
616
615
|
headers: lower,
|