@blamejs/pki 0.5.9 → 0.5.10

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 CHANGED
@@ -4,7 +4,19 @@ 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.9 — 2026-08-18
7
+ ## v0.5.10 — 2026-08-18
8
+
9
+ The documentation and the package's own source comments settle on one spelling of the words they use in both, and a gate keeps them there.
10
+
11
+ ### Added
12
+
13
+ - `npm run check:spelling` reports any word in the repository that has a second accepted spelling. It runs in `npm run gates`, on every pull request, and again before the published tarball is packed. The check is whole-word and case-insensitive, both to avoid a failure mode: a substring match reports `publicEncrypt` as a misspelling, and a case-sensitive one walks past the same word capitalized or upper-cased. It self-tests on planted forms before reporting, so a word list that has stopped matching cannot pass as a clean tree.
14
+
15
+ ### Changed
16
+
17
+ - Documentation and source comments now use the US spellings behavior, recognize, unrecognized, labeled, honored, license, defense, neighbor, authorize, initialization, enrollment, signaled, modeled, favor and fulfill. 267 occurrences across 98 files no longer carry a second spelling: the README, the security policy, thirteen release notes and the changelog generated from them, the status-lifecycle record, the comments and error text in lib/, the test suite, and the release and wiki tooling. That figure counts words whose spelling changed, so a line edited for another reason that happened to contain one of these words is not counted twice. One word settles the other way: catalogue, which this repository already used by 185 uses to 32, so the checked form is that one and the US spelling is what now reports. Simple Certificate Enrolment Protocol is RFC 8894's title, quoted as published and allowed only on a line carrying that title in full, so the exception cannot spread to the word.
18
+
19
+ ## v0.5.9 — 2026-08-17
8
20
 
9
21
  A certificate can now carry an internationalized email address, which this toolkit could read and never write.
10
22
 
@@ -15,7 +27,7 @@ A certificate can now carry an internationalized email address, which this toolk
15
27
 
16
28
  ### Fixed
17
29
 
18
- - pki.smime.verify's sender binding is now exercised against certificates carrying an otherName. Two behaviours that previously had no conformance vector are pinned: a certificate whose subjectAltName carries an SmtpUTF8Mailbox does not let a legacy subject distinguished-name emailAddress speak for it, and an otherName unrelated to email, such as a Microsoft user principal name, neither erases a matching rfc822Name nor turns a definite non-match into an undecidable one.
30
+ - pki.smime.verify's sender binding is now exercised against certificates carrying an otherName. Two behaviors that previously had no conformance vector are pinned: a certificate whose subjectAltName carries an SmtpUTF8Mailbox does not let a legacy subject distinguished-name emailAddress speak for it, and an otherName unrelated to email, such as a Microsoft user principal name, neither erases a matching rfc822Name nor turns a definite non-match into an undecidable one.
19
31
 
20
32
  ## v0.5.8 — 2026-08-17
21
33
 
@@ -24,7 +36,7 @@ Four verdicts that answered a question nobody had asked now say what they checke
24
36
  ### Added
25
37
 
26
38
  - pki.cms.decrypt reports originAuthenticated, authenticatedBy and originatorInfo. authenticated is a claim about the content and the key that opened it; it never described who sent the message. originAuthenticated is false for every recipient type the toolkit supports: a ktri or ephemeral-static kari message is minted by anyone holding the recipient's public key, and a pwri or kekri message by any co-recipient sharing the secret. authenticatedBy names what the integrity rests on. originatorInfo is now surfaced rather than decoded and discarded, and is documented as unauthenticated: it sits outside the AEAD's authenticated data, so it is a hint the sender chose, and any certificate it carries must be validated before use. To bind a sender, verify a signature over the plaintext.
27
- - pki.smime.verify accepts expectedSender and reports a sender block of { checked, expected, source, identities, match }. A signature proves a key signed; it does not prove the message came from the mailbox the reader sees. match is true only when the signer certificate asserts the address, compared under RFC 5280 section 7.5: the local-part exactly, the host-part case-insensitively. The address is read from the subjectAltName rfc822Name entries (RFC 8550 section 4.4.3), and where the extension carries none, from the subject distinguished name's PKCS #9 emailAddress attribute, which RFC 8550 section 3 requires a receiving agent to recognise. Where both are present the extension is authoritative, so a stale subject value cannot satisfy expectedSender while the extension names a different mailbox. It is three-valued: false when every identity was comparable and none matched, null when the question went unanswered, and null is not a pass, so a caller enforcing sender binding tests match === true. identities lists what the certificate actually asserts. With no expectedSender a single outer From is used and reported as source: "from", which is advisory, because on a message without header protection that header is attacker-controlled.
39
+ - pki.smime.verify accepts expectedSender and reports a sender block of { checked, expected, source, identities, match }. A signature proves a key signed; it does not prove the message came from the mailbox the reader sees. match is true only when the signer certificate asserts the address, compared under RFC 5280 section 7.5: the local-part exactly, the host-part case-insensitively. The address is read from the subjectAltName rfc822Name entries (RFC 8550 section 4.4.3), and where the extension carries none, from the subject distinguished name's PKCS #9 emailAddress attribute, which RFC 8550 section 3 requires a receiving agent to recognize. Where both are present the extension is authoritative, so a stale subject value cannot satisfy expectedSender while the extension names a different mailbox. It is three-valued: false when every identity was comparable and none matched, null when the question went unanswered, and null is not a pass, so a caller enforcing sender binding tests match === true. identities lists what the certificate actually asserts. With no expectedSender a single outer From is used and reported as source: "from", which is advisory, because on a message without header protection that header is attacker-controlled.
28
40
 
29
41
  ### Changed
30
42
 
@@ -34,7 +46,7 @@ Four verdicts that answered a question nobody had asked now say what they checke
34
46
 
35
47
  - An rfc822Name identity comparison no longer folds the host-part with a Unicode-aware lowercase. U+212A KELVIN SIGN lowercases to ASCII k, so ban<U+212A>.com and bank.com are different byte strings, separately registrable, that compared equal and read as one email identity. The host-part is now folded across A-Z only, which is the case-insensitive ASCII comparison RFC 5280 section 7.5 authorizes and no more. The local-part was already compared exactly and stays that way: RFC 8398 section 5 requires that it not be transformed in any way, including by case folding.
36
48
  - pki.merkle.verifyConsistency refuses a proof whose older tree is empty and whose newer tree is not, as merkle/no-consistency-claim. RFC 6962 section 2.1.2 defines a consistency proof for 0 < oldSize < newSize. An empty tree is a prefix of every tree by definition, so there was no proof to check and nothing bound the newRoot that was passed: any value returned true, including a root from a different log. Two empty trees are unchanged and still check each root against the empty root hash.
37
- - pki.cmc.verify refuses a Full PKI Response that carries nothing tying it to a request, as cmc/unbound-response. Every binding the module could check was previously conditional on the caller having supplied the matching value, and nothing in the verdict reported whether any of them ran, so a response captured from an earlier successful enrollment against the same CA verified identically. Pass what the request retained (transactionId, senderNonce, whose echo is the replay defence of RFC 5272 section 6.6, or dataReturn), or allowUnbound: true to interpret a response that could be a replay of any earlier exchange.
49
+ - pki.cmc.verify refuses a Full PKI Response that carries nothing tying it to a request, as cmc/unbound-response. Every binding the module could check was previously conditional on the caller having supplied the matching value, and nothing in the verdict reported whether any of them ran, so a response captured from an earlier successful enrollment against the same CA verified identically. Pass what the request retained (transactionId, senderNonce, whose echo is the replay defense of RFC 5272 section 6.6, or dataReturn), or allowUnbound: true to interpret a response that could be a replay of any earlier exchange.
38
50
 
39
51
  ## v0.5.7 — 2026-08-16
40
52
 
@@ -212,12 +224,12 @@ CMC (Certificate Management over CMS) ships end to end: build a Full PKI Request
212
224
  - pki.cmc.build takes transactionId, senderNonce and dataReturn as named spec fields, the same three pki.cmc.verify checks a response against. They previously had to be hand-encoded into spec.controls, and an unrecognized spec field was accepted in silence, so a request written the obvious way built, signed and sent with no exchange binding at all. Neither end could detect that, because the verifier only enforces the halves the client says it sent. An unknown spec field is now refused.
213
225
  - pki.cms.verify and pki.cmc.verify parse a private copy of a Buffer input, so the value they report and the bytes the signature was checked against are provably the same. Both decode the message synchronously and check signatures in a later turn, which left every byte range the parse surfaced, the signed content above all, a view into the caller's memory across that gap. A buffer rewritten in between could yield a result describing one message while the signature covered another, and the everyday way to hit it is a pooled read buffer recycled across concurrent verifies. A PEM string or an already-parsed object is untouched, so neither call accepts less than it did.
214
226
  - Every example in the API documentation runs against the shipped package as part of the test suite, with no fixtures supplied to it, and the ones that did not run have been corrected. An example that quietly depended on a value the surrounding text never defined would previously have failed only for the operator who pasted it.
215
- - pki.webauthn.verify copies opts.rootCertificates synchronously, so pinned attestation roots cannot be swapped out from under the check. The roots are not read until the attestation verifier resolves, a later promise turn, and both the array and each DER buffer stayed caller-owned across it: a caller recycling the array or overwriting a certificate's bytes in that gap had the attestation anchored against the replacement roots while the verdict still reported anchoredTo: "rootCertificates". Both are now copied at the entry point, the same defence the assertion input already had, and the documented parsed-certificate form is deep-copied and never passed by reference, since the anchor comparison reads its nested subject and subjectPublicKeyInfo buffers. opts.safetyNetRoots carried the identical window one level down, since a format verifier reads it a microtask after the call returns, and is snapshotted at the same boundary.
227
+ - pki.webauthn.verify copies opts.rootCertificates synchronously, so pinned attestation roots cannot be swapped out from under the check. The roots are not read until the attestation verifier resolves, a later promise turn, and both the array and each DER buffer stayed caller-owned across it: a caller recycling the array or overwriting a certificate's bytes in that gap had the attestation anchored against the replacement roots while the verdict still reported anchoredTo: "rootCertificates". Both are now copied at the entry point, the same defense the assertion input already had, and the documented parsed-certificate form is deep-copied and never passed by reference, since the anchor comparison reads its nested subject and subjectPublicKeyInfo buffers. opts.safetyNetRoots carried the identical window one level down, since a format verifier reads it a microtask after the call returns, and is snapshotted at the same boundary.
216
228
  - In a compound attestation, an element the metadata catalogue does not list can no longer launder a revoked sibling. The two governance failures are not equal: metadata-not-found is the one outcome a caller may fall back to opts.rootCertificates on, and that fallback covers the whole statement. Governance stopped at the first failing element, and a compound's element order is not signed, so placing an unlisted element first raised the fallback error before a listed-but-revoked sibling was ever consulted, and the statement anchored against the pinned roots instead. Every element is now governed, and every listed element chain-validated against the roots its own entry registers, before any outcome is chosen. So a disqualifying status report outranks an unlisted sibling from either position, and a listed element whose path reaches the caller's pinned roots but not its own registered roots can no longer ride out on that sibling's fallback either. A compound in which no element is listed still falls back as documented.
217
229
  - pki.ocsp.sign copies the responder key synchronously. The key is not read until several promise turns after the call, and capturing only the reference stopped responder.key being reassigned but not the PKCS#8 bytes, or a composite key's components, being rewritten in place. Either produced a response carrying this responder's identifier and embedded certificate over a signature made by different key material, which no relying party can verify. A CryptoKey is opaque and a PEM string immutable, so both were already safe. A composite descriptor is cloned whether or not its components currently hold bytes: one carrying two PEM strings has nothing mutable inside it, but the object is still the caller's, and reassigning a component reaches the deferred sign just as rewriting a buffer would.
218
230
  - An Apple attestation certificate whose anonymous-attestation extension carries more than the nonce is refused. AppleAnonymousAttestation is a SEQUENCE of exactly one field wrapping exactly one value, but the decoder read the first child and ignored the rest, so a certificate with a trailing field beside the nonce, a second value inside the EXPLICIT [1] wrapper, or a non-SEQUENCE outer value was accepted. That extension exists to carry the value the attestation binds to, so an ambiguous encoding of it is not a shape the verifier gets to pick a reading from; arity is now enforced as part of the declared type.
219
231
  - A key identifier and a pre-encoded CertID must now be bytes, and are no longer coerced. pki.cms.sign takes a key-only signer's keyIdentifier and pki.ocsp.sign takes a response entry's certID straight into the encoding, and both previously ran the value through Buffer.from, which accepts far more than it should mean: Buffer.from(20) allocates twenty zero octets and Buffer.from("a1b2") takes the ASCII of the text where a reader means two octets. Either produced a structurally valid but wrong SignerIdentifier or CertID, inside a message that then gets signed and that no verifier can match back. A Buffer or Uint8Array is accepted as before; anything else is now cms/bad-input or ocsp/bad-input.
220
- - An OCSP response whose nonce does not echo the request no longer downgrades a revoked verdict to unknown. pki.ocsp.verify applies that downgrade to good only. Revocation does not go stale the way non-revocation does, so discarding a signed, current, authorized revoked response because it was replayed would hand a soft-failing caller the certificate the responder just refused: the anti-replay defence would become the thing that accepts it. The verdict was also self-contradictory, reporting status unknown while carrying revocationReason keyCompromise. nonceMatched: false still reports that the response was not bound to this request, and the field is now three-state and always present (true bound, false not bound, null when the client sent no nonce), so a caller can tell a check that ran from one that was never asked for.
232
+ - An OCSP response whose nonce does not echo the request no longer downgrades a revoked verdict to unknown. pki.ocsp.verify applies that downgrade to good only. Revocation does not go stale the way non-revocation does, so discarding a signed, current, authorized revoked response because it was replayed would hand a soft-failing caller the certificate the responder just refused: the anti-replay defense would become the thing that accepts it. The verdict was also self-contradictory, reporting status unknown while carrying revocationReason keyCompromise. nonceMatched: false still reports that the response was not bound to this request, and the field is now three-state and always present (true bound, false not bound, null when the client sent no nonce), so a caller can tell a check that ran from one that was never asked for.
221
233
  - Corrections to the repository documentation, each of which would have misled a reader who acted on it. SUPPORT.md described additive APIs as minor releases; pre-1.0 they ship as patches, and a minor is an explicit decision recorded in the release notes. ARCHITECTURE.md, CONTRIBUTING.md and the interop test guide showed pki.x509.parse, which does not exist: parsing is pki.schema.x509.parse, and pki.<format> is the issuing half throughout. ARCHITECTURE.md listed namespaces as future that have shipped, and omitted the schema, guard and validator families entirely. THREAT-MODEL.md marked path validation, signature verification, CMS decryption and ML-KEM decapsulation as targeted when all four have shipped, and linked to a section that no longer exists. ROADMAP.md reported CRL reason-shard accumulation and delta-CRL merge as planned in one entry while describing them as shipped in another. ML-KEM encapsulation and decapsulation were described as roadmap items; both ship and are what the CMS KEMRecipientInfo arm rides on. The interoperability acceptance gate was described as running against NSS, Windows CAPI and macOS Keychain alongside OpenSSL; only the OpenSSL cross-checks are wired, and the others are roadmap. The format detectors behind pki.schema.parse were described as mutually exclusive regardless of registration order; order is load-bearing where two overlap, so a CMP PKIMessage sits ahead of the OCSP-request probe and a v1 attribute certificate ahead of X.509. The fuzzing guide listed 27 of the 55 harnesses. The README carried two separate entries for pki.tls. The published pki.transport response contract named three fields where the transport returns four: the omitted tls field is what pki.est.serverkeygen reads to assert the channel can protect a server-generated private key, and a transport that reports no cipher is trusted, so an operator injecting a substitute built to the documented contract silently skipped that check. EST channel binding was described as shipped; the challengePassword builder and the server-instruction flag ship, but nothing produces the RFC 5929 tls-unique value and the shared transport does not expose it, so the attribute cannot be driven end to end.
222
234
 
223
235
  ## v0.4.15 — 2026-08-10
@@ -251,7 +263,7 @@ Every key-establishment secret this library allocates is now wiped when it stops
251
263
 
252
264
  ### Changed
253
265
 
254
- - Raw secret key material is now reached through a single path that clears the copy it hands out, so a new operation cannot obtain that material without the wipe. Behaviour of the public API is unchanged.
266
+ - Raw secret key material is now reached through a single path that clears the copy it hands out, so a new operation cannot obtain that material without the wipe. Behavior of the public API is unchanged.
255
267
 
256
268
  ### Fixed
257
269
 
@@ -289,7 +301,7 @@ A KEM shared secret and the key it derives are now wiped as soon as they stop be
289
301
  ### Changed
290
302
 
291
303
  - The ML-KEM ciphertext-length check FIPS 203 sec. 7.3 requires of a decapsulating party is now performed by the crypto engine, so a caller reaching decapsulateBits directly is covered too; before, only the CMS path that happens to call it today was. It reports webcrypto/bad-kem-ciphertext, naming the parameter set and both lengths, where the failure was previously indistinguishable from any other decapsulation fault; a ciphertext whose length is valid for some other parameter set is refused on those terms, and no longer reads as merely short. The check is on length alone: a correct-length ciphertext that has been tampered with still resolves to a pseudo-random shared secret, because turning that into an error would give an attacker a decryption oracle. No engine detail reaches a CMS caller: a structurally valid message whose decryption fails for any secret-dependent reason still reports the single uniform cms/decrypt-failed verdict. A message whose ML-KEM ciphertext length does not match the parameter set the message itself declares is a separate case and always was (including a length that would be valid for a different set), because the strict parser rejects it up front and names it: the mismatch is a structural fault, decidable from the message alone, with nothing about it depending on a key.
292
- - The ML-KEM parameter sizes resolve from one registry instead of three separate tables in three modules. The encapsulation-key lengths were already duplicated verbatim in two of them, and each new consumer meant another copy that could drift; a parameter set is a property of the algorithm identifier, so it now lives beside the registry that resolves one. Behaviour is unchanged.
304
+ - The ML-KEM parameter sizes resolve from one registry instead of three separate tables in three modules. The encapsulation-key lengths were already duplicated verbatim in two of them, and each new consumer meant another copy that could drift; a parameter set is a property of the algorithm identifier, so it now lives beside the registry that resolves one. Behavior is unchanged.
293
305
 
294
306
  ### Fixed
295
307
 
@@ -312,7 +324,7 @@ A CMS message can no longer declare one content cipher and be opened with anothe
312
324
 
313
325
  ### Fixed
314
326
 
315
- - A CMS content cipher is now bound to the container that declares it. An EnvelopedData must name a CBC cipher and an AuthEnvelopedData an AEAD one, checked before the content-encryption key is used; a mismatch is refused as an unsupported algorithm naming both the cipher and the container. Previously only the cipher's key length was resolved, and because AES-CBC and AES-GCM share key lengths, an EnvelopedData whose algorithm identifier had been changed to the same-size AES-GCM identifier decrypted successfully as unauthenticated CBC while reporting the AEAD algorithm in its result. A caller inspecting contentEncryptionAlgorithm to establish that the content was authenticated was answered from a field the decryption had not honoured. The reverse pairing was refused only incidentally, by a later dereference of parameters the AEAD path expects, and no stated rule refused it.
327
+ - A CMS content cipher is now bound to the container that declares it. An EnvelopedData must name a CBC cipher and an AuthEnvelopedData an AEAD one, checked before the content-encryption key is used; a mismatch is refused as an unsupported algorithm naming both the cipher and the container. Previously only the cipher's key length was resolved, and because AES-CBC and AES-GCM share key lengths, an EnvelopedData whose algorithm identifier had been changed to the same-size AES-GCM identifier decrypted successfully as unauthenticated CBC while reporting the AEAD algorithm in its result. A caller inspecting contentEncryptionAlgorithm to establish that the content was authenticated was answered from a field the decryption had not honored. The reverse pairing was refused only incidentally, by a later dereference of parameters the AEAD path expects, and no stated rule refused it.
316
328
  - The password-recipient inner cipher is resolved through the same identifier-keyed table. It previously required a CBC mode by matching the algorithm identifier's display name, which pki.oid.register can rebind, so a caller that had registered a name over a built-in one could change which ciphers that check admitted.
317
329
 
318
330
  ## v0.4.11 — 2026-08-09
@@ -323,20 +335,20 @@ A WebAuthn attestation can now be bound to the roots the authenticator's own mod
323
335
 
324
336
  - pki.webauthn.verifyMetadataBlob reads a FIDO Metadata Service (MDS v3) BLOB and returns its entries indexed by aaguid. The BLOB is a JWS: its signature is checked under the certificate in its own header, that chain is validated to one of the roots the caller pins, and only then is the payload parsed. The ordering is the point, because a reader that parses first hands an attacker every structure behind the signature.
325
337
  - The catalogue's freshness is enforced, and enforced again wherever it is used. A BLOB whose sequence number does not exceed the one the caller already holds is refused as a rollback, and requireRollbackCheck makes supplying that number mandatory so the check cannot be skipped by forgetting the option. A BLOB past its nextUpdate is refused as stale. A verified result is an ordinary object a relying party may cache, so its expiry is re-checked each time it is passed to verify, and a catalogue fetched while current cannot keep authorizing an authenticator whose status reports have since revoked it. Both checks fail closed, and the caller's own allowStale decision rides on the result, so it need not be repeated.
326
- - Passing the verified result to pki.webauthn.verify as opts.metadata binds the attestation to its own model: the authenticator's registered attestation roots are resolved from its identifier, and its trust path must fully validate to one of them. That means signature chaining, validity and constraints: the same path validation any certificate chain gets. An authenticator whose model the catalogue does not list, whose entry registers no attestation root, or whose status reports disqualify it, is refused. Which reports disqualify is selectable: any report ever filed, only the most recent one so that a later remediation clears an earlier revocation, or a predicate of the caller's own. An unrecognised status is ignored by default, as the specification requires, or treated as disqualifying on request.
338
+ - Passing the verified result to pki.webauthn.verify as opts.metadata binds the attestation to its own model: the authenticator's registered attestation roots are resolved from its identifier, and its trust path must fully validate to one of them. That means signature chaining, validity and constraints: the same path validation any certificate chain gets. An authenticator whose model the catalogue does not list, whose entry registers no attestation root, or whose status reports disqualify it, is refused. Which reports disqualify is selectable: any report ever filed, only the most recent one so that a later remediation clears an earlier revocation, or a predicate of the caller's own. An unrecognized status is ignored by default, as the specification requires, or treated as disqualifying on request.
327
339
  - Authenticators that carry no aaguid are covered too. A U2F authenticator declares no model identity, and the catalogue keys it by the key identifiers of its attestation certificates instead; both key spaces are indexed and looked up, so a U2F registration binds to its registered roots and is not refused as unlisted. The identifier is read from the entry and from its metadata statement, since live entries populate both, and is computed as RFC 5280 sec. 4.2.1.2 method 1 defines it. A compound attestation is covered as well: its elements carry independent trust paths, and every one of them must reach a registered root.
328
340
  - Which identifier is allowed to select an entry depends on what the attestation signature covers. The fido-u2f signature is computed over named fields and does not include the aaguid, so for that format the certificate decides and the declared aaguid is ignored; otherwise setting it to a listed model that shares the vendor's registered root would resolve to that model's entry and skip the real one's status reports. For relying parties: res.aaguid reports what the authenticator presented and is not signature-bound for that format; res.metadata.aaguid names the entry that actually matched.
329
- - Only a catalogue this library verified can decide anything. A metadata result is recognised by its provenance. Shape is not enough on its own, so an object restored from a cache is refused as a catalogue. It has been through none of the signature and chain checks, and its contents are whatever an attacker able to write that cache chose. Re-verify the BLOB instead, which the freshness rule asks for anyway. The verified result is also frozen. The catalogue that decides a later verification is therefore the one the signature covered, unedited.
341
+ - Only a catalogue this library verified can decide anything. A metadata result is recognized by its provenance. Shape is not enough on its own, so an object restored from a cache is refused as a catalogue. It has been through none of the signature and chain checks, and its contents are whatever an attacker able to write that cache chose. Re-verify the BLOB instead, which the freshness rule asks for anyway. The verified result is also frozen. The catalogue that decides a later verification is therefore the one the signature covered, unedited.
330
342
  - A stored attestation is anchored at the instant its own format judged it. An android-safetynet response carries its signing time and its service chain has usually expired since, so the metadata anchor check reuses that instant in place of the current clock; otherwise it would refuse the very registration the format verifier had just accepted. An explicit opts.time still takes precedence.
331
343
  - Status reports are read against the instant being judged: a report dated in the future has not taken effect, so it cannot displace a revocation that is in force now, and a deliberately historical verification does not see reports filed after the time it asks about.
332
- - A status report that names a single certificate is judged against the certificate actually presented. A whole batch of authenticators is commonly listed under one entry, so a key-compromise report naming one attestation certificate denies only that one, whatever else the entry covers; a report that names nothing, or names something that does not decode, still applies to the entry as a whole. Trust anchors are recognised by name and public key. Self-issuedness is not the test, so a chain terminating in a cross-signed form of a root you supplied still anchors to it.
344
+ - A status report that names a single certificate is judged against the certificate actually presented. A whole batch of authenticators is commonly listed under one entry, so a key-compromise report naming one attestation certificate denies only that one, whatever else the entry covers; a report that names nothing, or names something that does not decode, still applies to the entry as a whole. Trust anchors are recognized by name and public key. Self-issuedness is not the test, so a chain terminating in a cross-signed form of a root you supplied still anchors to it.
333
345
  - The BLOB's own signing certificate must be permitted to sign. A certificate that carries a key-usage extension omitting digitalSignature is refused before its key is used to check the BLOB signature, so a certificate restricted to some other purpose cannot confer metadata-signing authority just because it chains to the root you supplied.
334
346
  - pki.webauthn.metadataFor looks an entry up against a verified result only, never raw bytes, so a lookup cannot be answered out of a catalogue nobody verified. It takes either identifier (an aaguid or an attestation-certificate key identifier), dispatching on the form, which are disjoint by shape. pki.webauthn.metadataAnchors decodes an entry's registered attestation roots one entry at a time, since a handful of certificates in the live metadata do not parse under a strict decoder and decoding everything up front would let one vendor's malformed root refuse the entire catalogue for every other authenticator in it.
335
347
 
336
348
  ### Changed
337
349
 
338
- - pki.webauthn.verify now rejects an unrecognised option key, where it used to ignore one, and validates opts.time at the boundary. Every option it takes either gates the verdict or supplies the trust material a gate needs, so a misspelled key was not harmless: asking for metadata enforcement and mistyping the key left the gate switched off and returned a pass the caller believed had been checked against the catalogue. An invalid time is now reported as the configuration fault it is, and no longer surfaces later as an authenticator trust failure.
339
- - Configuration objects across certificate, CRL, attribute-certificate, CSR, and CMP issuance now reject an unrecognised option key through one shared check, replacing a dozen separate ones. Two cases that a hand-written check gets wrong are fixed everywhere at once: a key that every JavaScript object inherits, such as constructor or toString, is no longer accepted as a recognised option name, and an option object built by parsing JSON that carries its own __proto__ key is now inspected; it used to be skipped. The wording of every rejection is unchanged. This matters because the failure is silent in the quietest possible way: a misspelled option key leaves the default in force, so a caller who asked for a stricter check gets the looser behaviour and no error anywhere.
350
+ - pki.webauthn.verify now rejects an unrecognized option key, where it used to ignore one, and validates opts.time at the boundary. Every option it takes either gates the verdict or supplies the trust material a gate needs, so a misspelled key was not harmless: asking for metadata enforcement and mistyping the key left the gate switched off and returned a pass the caller believed had been checked against the catalogue. An invalid time is now reported as the configuration fault it is, and no longer surfaces later as an authenticator trust failure.
351
+ - Configuration objects across certificate, CRL, attribute-certificate, CSR, and CMP issuance now reject an unrecognized option key through one shared check, replacing a dozen separate ones. Two cases that a hand-written check gets wrong are fixed everywhere at once: a key that every JavaScript object inherits, such as constructor or toString, is no longer accepted as a recognized option name, and an option object built by parsing JSON that carries its own __proto__ key is now inspected; it used to be skipped. The wording of every rejection is unchanged. This matters because the failure is silent in the quietest possible way: a misspelled option key leaves the default in force, so a caller who asked for a stricter check gets the looser behavior and no error anywhere.
340
352
 
341
353
  ### Fixed
342
354
 
@@ -351,7 +363,7 @@ A TPM attestation now reports the credential key's own object attributes and acc
351
363
  - A verified TPM attestation reports the credential key's object attributes as named flags, the raw attribute word, and the key's access-policy digest. Previously both fields were read past and discarded, so a relying party that wanted to know whether the key was bound to its TPM had to re-parse the public area itself.
352
364
  - opts.tpmPolicy requires any of those attributes by name, in either direction, since an attribute may be required set or required clear; it refuses the attestation naming which one disagreed. A single profile, hardware-bound, is the shorthand for the six attributes every genuine attestation examined agrees on: the key is bound to one TPM and one parent, the TPM generated it, it can sign, and it is neither a restricted key nor an X.509 signing key. An explicit attribute layers over the profile, so one flag can be overridden without losing the rest. Three attributes are deliberately absent from it, because they differ across genuine authenticators and requiring any of them would reject working hardware.
353
365
  - The policy also covers the key's access policy: requiring that one is present at all and is not the empty policy, and requiring it to be one of an allow-list of digests, compared in constant time. Two structural opt-ins are available for callers who want them: rejecting an attribute word that sets a bit the specification reserves, and rejecting attribute combinations the specification does not define.
354
- - A mistyped policy is refused when it is set: an unknown key at any level, an unknown profile, an unknown attribute name, a non-boolean value, or an allow-list entry that is not a certificate digest all fail immediately, so a typo can never silently disable the check a caller believes they enabled. Names that every JavaScript object inherits are not accepted as policy names either, since a lookup would otherwise report them as recognised and leave the policy applying nothing. Requiring that the TPM generated the key without also requiring the key be non-duplicable is refused for the same reason. On its own it establishes nothing, because the key could have been imported.
366
+ - A mistyped policy is refused when it is set: an unknown key at any level, an unknown profile, an unknown attribute name, a non-boolean value, or an allow-list entry that is not a certificate digest all fail immediately, so a typo can never silently disable the check a caller believes they enabled. Names that every JavaScript object inherits are not accepted as policy names either, since a lookup would otherwise report them as recognized and leave the policy applying nothing. Requiring that the TPM generated the key without also requiring the key be non-duplicable is refused for the same reason. On its own it establishes nothing, because the key could have been imported.
355
367
  - Requesting a TPM policy also requires an attestation that can satisfy it. The policy is evaluated against the TPM public area, which only a TPM attestation carries, so an attestation in any other format would never reach it, and a relying party that demanded a TPM-bound key would have accepted a credential with no attestation at all. Such a request is now refused up front, naming the format, and a compound attestation qualifies only when it actually contains a TPM statement.
356
368
 
357
369
  ### Fixed
@@ -445,7 +457,7 @@ pki.tls encodes and decodes RFC 8879 compressed certificate messages (the larges
445
457
 
446
458
  ### Changed
447
459
 
448
- - pki.cms.sign and pki.cms.verify compute their message digests through the crypto engine; neither holds a private digest table any more. Behaviour is unchanged; the digest algorithms a signer and a verifier accept are now defined in one place.
460
+ - pki.cms.sign and pki.cms.verify compute their message digests through the crypto engine; neither holds a private digest table any more. Behavior is unchanged; the digest algorithms a signer and a verifier accept are now defined in one place.
449
461
 
450
462
  ### Fixed
451
463
 
@@ -1400,7 +1412,7 @@ SLH-DSA signatures verify in certification-path validation, and the post-quantum
1400
1412
  ### Fixed
1401
1413
 
1402
1414
  - The shared AlgorithmIdentifier decoder now rejects a present parameters field on the algorithms whose parameters MUST be absent, namely ML-DSA, SLH-DSA, Ed25519, Ed448, X25519, and X448 (RFC 9909 §3, RFC 9814 §4, RFC 9881 §2, RFC 8410 §3), failing closed with a <format>/bad-algorithm-parameters code. Previously a stray explicit NULL or arbitrary bytes in that field were surfaced raw. The rule is enforced once in the shared decoder, so every format that names an algorithm inherits it; a conforming identifier, which omits the field, is unaffected.
1403
- - Certification-path validation now enforces issuer-key / signature-algorithm consistency for the one-shot families whose public-key OID equals the signature OID (EdDSA, ML-DSA, and SLH-DSA), rejecting a mismatch with a path/algorithm-mismatch reason (RFC 9814 §4). Because the underlying WebCrypto import binds a public key of a different type to the requested algorithm name and verifies with the real key, a certificate or CRL signed by one key type but labelling its signatureAlgorithm as another one-shot type could otherwise validate; the check closes that algorithm-confusion path for both the certificate signature and the CRL revocation checker.
1415
+ - Certification-path validation now enforces issuer-key / signature-algorithm consistency for the one-shot families whose public-key OID equals the signature OID (EdDSA, ML-DSA, and SLH-DSA), rejecting a mismatch with a path/algorithm-mismatch reason (RFC 9814 §4). Because the underlying WebCrypto import binds a public key of a different type to the requested algorithm name and verifies with the real key, a certificate or CRL signed by one key type but labeling its signatureAlgorithm as another one-shot type could otherwise validate; the check closes that algorithm-confusion path for both the certificate signature and the CRL revocation checker.
1404
1416
 
1405
1417
  ## v0.1.20 — 2026-07-09
1406
1418
 
@@ -1580,7 +1592,7 @@ A PKCS#10 certification-request parser joins the pki.schema family.
1580
1592
 
1581
1593
  ### Changed
1582
1594
 
1583
- - C.TIME.ms is renamed to C.TIME.milliseconds, so every C.TIME duration helper now reads as a full word (milliseconds, seconds, minutes, hours, days, weeks). The behaviour is unchanged: it still returns an integer millisecond count.
1595
+ - C.TIME.ms is renamed to C.TIME.milliseconds, so every C.TIME duration helper now reads as a full word (milliseconds, seconds, minutes, hours, days, weeks). The behavior is unchanged: it still returns an integer millisecond count.
1584
1596
 
1585
1597
  ### Security
1586
1598
 
@@ -1650,7 +1662,7 @@ WebCrypto EC key import validates the curve against the key material.
1650
1662
 
1651
1663
  ### Security
1652
1664
 
1653
- - pki.webcrypto.subtle.importKey now derives an imported EC key's named curve from the key material and enforces it across the spki, pkcs8 and jwk formats. Previously it trusted the caller-supplied namedCurve without checking it against the key, so a key on an unsupported curve (for example secp256k1) imported as an approved curve, and a key on one curve could be labelled as another, an algorithm-confusion vector in which the CryptoKey's algorithm disagreed with its key material. A curve the framework does not support is now rejected (NotSupportedError) and a namedCurve that does not match the key is rejected (DataError); generateKey already enforced this, and import now matches it. The raw-key format was already validated against its declared curve and is unchanged.
1665
+ - pki.webcrypto.subtle.importKey now derives an imported EC key's named curve from the key material and enforces it across the spki, pkcs8 and jwk formats. Previously it trusted the caller-supplied namedCurve without checking it against the key, so a key on an unsupported curve (for example secp256k1) imported as an approved curve, and a key on one curve could be labeled as another, an algorithm-confusion vector in which the CryptoKey's algorithm disagreed with its key material. A curve the framework does not support is now rejected (NotSupportedError) and a namedCurve that does not match the key is rejected (DataError); generateKey already enforced this, and import now matches it. The raw-key format was already validated against its declared curve and is unchanged.
1654
1666
 
1655
1667
  ## v0.1.2 — 2026-07-04
1656
1668
 
package/README.md CHANGED
@@ -225,7 +225,7 @@ comment blocks, is at [pkijs.com](https://pkijs.com).
225
225
  | `pki.jose` | Flattened JWS (RFC 7515) and JWK thumbprints (RFC 7638). `sign` and `verify` run a Flattened JWS against declarative profiles (ACME outer, EAB inner, keyChange inner) that carry the required and forbidden header rules as data. `base64url` is the strict RFC 4648 §5 codec, rejecting padding, non-alphabet characters, and non-canonical trailing bits. `parseJson` is a bounded reader that refuses duplicate members at any depth. `thumbprint` is the RFC 7638 / 8037 / 9964 canonical digest. The algorithm registry binds each `alg` to its key type (ES/RS/PS/EdDSA/ML-DSA), leaving no code path for `alg:none`, an RS256→HS256 key confusion, or an all-zero ECDSA signature; `assertPublicJwk` refuses a JWK carrying private material, so an exported private key is never published. `opts.key` names the key a message must be signed under and governs: where the profile also permits an embedded header `jwk`, the two must be the same key — compared as RFC 7638 thumbprints, so member order cannot make equal keys differ — and a disagreement is refused rather than resolved in the message's favor. `keySource` reports which key answered, since a signature checked against a key you named is a different claim from one checked against the key the message carried — `sign`, `verify`, `base64url`, `parseJson`, `thumbprint`, `assertPublicJwk` |
226
226
  | `pki.acme` | ACME (RFC 8555 / 8737 / 8738 / 9773). `client(directoryUrl, opts)` is a stateful client driving a live CA directory over `pki.transport`: `newAccount`, `newOrder`, `newAuthz`, `getOrder`, `getAuthorization`, `getChallenge`, `respondToChallenge`, `finalize`, `pollOrder`, `pollAuthorization`, and `downloadCertificate` walk the issuance flow, with `newAuthz` pre-authorizing a single identifier (§7.4.1) and `downloadCertificate` choosing among alternate chains (`Link rel="alternate"`, §7.4.2, via a `selectChain` predicate bounded by `maxAlternates`). `revokeCert` (account-key or certificate-key signed), `keyChange`, `deactivateAccount`, `deactivateAuthorization`, `renewalInfo` (ARI), and `renewalWindow` (the RFC 9773 §4.2/4.3 renewal decision) complete the lifecycle. Every URL is HTTPS only, an explicit trust anchor is required, each request carries a fresh single-use nonce with a bounded badNonce retry, reads are POST-as-GET, polling is bounded and sleeps on a Retry-After via an injectable sleeper capped by a poll count and a total-wait budget, and every response body is size-capped. The transport is injectable via `opts.transport`. Over the message layer it composes resource-object validators (closed status enums, conditional-required fields, unknown fields ignored), the three §7.1.6 state machines, the request builders (newAccount with External Account Binding, newOrder with `replaces`, finalize with a CSR identifier-set match and account-key-reuse rejection, challenge responses, deactivation, revokeCert in both key modes, the keyChange nested JWS, POST-as-GET), the http-01 / dns-01 / tls-alpn-01 challenge computations, the dns and ip identifier validators, and the ARI certID with serial sign-padding preserved — `client`, `validate`, `identify`, `assertTransition`, the builders, `keyAuthorization`, `http01`, `dns01`, `tlsAlpn01Extension`, `verifyTlsAlpn01`, `ariCertId` |
227
227
  | `pki.schema.smime` | S/MIME ESS signed-attribute values (RFC 5035 / RFC 8551). `parseSigningCertificate` and `parseSigningCertificateV2` bind a signature to its signing certificate (cert hash, hash algorithm, issuer `GeneralNames` and serial); `parseSmimeCapabilities` decodes the ordered capability list; `decodeAttribute` dispatches a CMS attribute by OID, enforcing the single-value rule and deferring on unknown types. A companion decoder for CMS signed attributes rather than an auto-routed format — `parseSigningCertificate`, `parseSigningCertificateV2`, `parseSmimeCapabilities`, `decodeAttribute` |
228
- | `pki.cmc` | Build and interpret CMC messages (RFC 5272). `build(spec, signer)` assembles a Full PKI Request across all three request arms (PKCS#10, CRMF, other) and signs it through `pki.cms.sign` under `id-cct-PKIData`. Body-part identifiers are unique across the whole message and never the reserved 0; a caller's clash is refused rather than renumbered, since a control may already reference it. An Identity Proof V2 witness is computed over the `reqSequence` bytes exactly as emitted (§6.2.1 step 1) rather than a re-serialization, a POP Link Witness is emitted only alongside the POP Link Random control §6.3.1.1 requires beside it, and a renewal carries neither Identification nor Identity Proof in either version. `verify(response, sent)` takes what the CA returned plus the state the client retained and reduces it to one terminal outcome: `issued`, `pending`, `confirm-required`, `pop-required`, or `rejected`. It binds the exchange first — Transaction Identifier, the Sender and Recipient Nonce echo compared in constant time and by full value so a truncation cannot match, and the Data Return echo — with each check applying only if the client sent that half, and, once sent, an absent or differing echo being a refusal, which is the replay defence. `bodyPartIDs` extends the same rule to what the response is about: a status reporting on a body part the request never sent is refused, which the transaction and nonce cannot catch, because a server can echo both correctly while answering about a different message. Several status controls are permitted and the worst governs, so a failure cannot hide behind an earlier success; the absence of any status control is success, per §6.1.2. The carrier's signature must verify (§3.2.1.3.4): a conforming SignedData carries its own signer certificate, so the ordinary build-then-verify flow needs nothing extra and the verdict reports `signatureVerified: true`. Where the signer is found nowhere the posture is fail-closed with a named opt-out — supply `certs` with the responder's certificate, or `allowUnverified: true`, in which case the verdict reports `signatureVerified: false`. Doing neither is refused, the opt-out never excuses a signature that is present and wrong, and a carrier with no signer at all is refused outright. The response's own `cmsSequence` and `otherMsgs` come back raw, since a request whose only arm was the other-message form has no certificate to return and §4.1 puts its answer there. Nothing is trusted: issued certificates are read from the CMS certificate bag (§4.2) and surfaced raw for `pki.path.validate`, and a Publish Trust Anchors control is surfaced with `trusted: false` rather than added to a store — `build`, `verify` |
228
+ | `pki.cmc` | Build and interpret CMC messages (RFC 5272). `build(spec, signer)` assembles a Full PKI Request across all three request arms (PKCS#10, CRMF, other) and signs it through `pki.cms.sign` under `id-cct-PKIData`. Body-part identifiers are unique across the whole message and never the reserved 0; a caller's clash is refused rather than renumbered, since a control may already reference it. An Identity Proof V2 witness is computed over the `reqSequence` bytes exactly as emitted (§6.2.1 step 1) rather than a re-serialization, a POP Link Witness is emitted only alongside the POP Link Random control §6.3.1.1 requires beside it, and a renewal carries neither Identification nor Identity Proof in either version. `verify(response, sent)` takes what the CA returned plus the state the client retained and reduces it to one terminal outcome: `issued`, `pending`, `confirm-required`, `pop-required`, or `rejected`. It binds the exchange first — Transaction Identifier, the Sender and Recipient Nonce echo compared in constant time and by full value so a truncation cannot match, and the Data Return echo — with each check applying only if the client sent that half, and, once sent, an absent or differing echo being a refusal, which is the replay defense. `bodyPartIDs` extends the same rule to what the response is about: a status reporting on a body part the request never sent is refused, which the transaction and nonce cannot catch, because a server can echo both correctly while answering about a different message. Several status controls are permitted and the worst governs, so a failure cannot hide behind an earlier success; the absence of any status control is success, per §6.1.2. The carrier's signature must verify (§3.2.1.3.4): a conforming SignedData carries its own signer certificate, so the ordinary build-then-verify flow needs nothing extra and the verdict reports `signatureVerified: true`. Where the signer is found nowhere the posture is fail-closed with a named opt-out — supply `certs` with the responder's certificate, or `allowUnverified: true`, in which case the verdict reports `signatureVerified: false`. Doing neither is refused, the opt-out never excuses a signature that is present and wrong, and a carrier with no signer at all is refused outright. The response's own `cmsSequence` and `otherMsgs` come back raw, since a request whose only arm was the other-message form has no certificate to return and §4.1 puts its answer there. Nothing is trusted: issued certificates are read from the CMS certificate bag (§4.2) and surfaced raw for `pki.path.validate`, and a Publish Trust Anchors control is surfaced with `trusted: false` rather than added to a store — `build`, `verify` |
229
229
  | `pki.schema.cmc` | Decode CMC messages (RFC 5272 as updated by RFC 6402): a Full PKI Request (`PKIData`) or Full PKI Response (`PKIResponse`) riding inside a CMS SignedData, reached by the encapsulated content type (`id-cct-PKIData` / `id-cct-PKIResponse`). Controls are surfaced in wire order with their values raw, so an unrecognized one is data rather than a fault. Tagged requests decode across all three arms. The status verdicts (`CMCStatusInfo` v1 and `CMCStatusInfoV2`) are collected as an ordered list, and the RFC 6402 two-module `OtherStatusInfo` ambiguity — `pendInfo` and `extendedFailInfo` are both untagged SEQUENCEs in the 1988 module, told apart only by their first element — is resolved by inspection and refused when it cannot be told apart. Body-part identity is unique across the whole message rather than per sequence, 0 is reserved as the reference to the enclosing PKIData, and the `reqSequence` bytes are surfaced exactly as they appeared so an Identity Proof witness is computed over the wire bytes. A companion decoder for CMS content rather than an auto-routed format — `parse`, `parsePkiData`, `parsePkiResponse` |
230
230
  | `pki.schema.engine` | The declarative ASN.1 structure-schema engine every format parser composes — `walk`, `encode`, `embeddedDer`, and the schema combinators |
231
231
  | `pki.path` | Certification-path validation (RFC 5280 §6). `validate` runs the §6.1 state machine over an ordered path and a trust anchor: signature chaining across RSA, ECDSA, EdDSA, ML-DSA, SLH-DSA and hybrid composite ML-DSA (a composite is accepted only when both its post-quantum and traditional components verify), validity windows, name chaining, basic constraints and path length, key usage, name constraints, and the certificate-policy tree. It returns a structured verdict with per-check reason codes and enforces a `pki.trust` anchor's per-purpose distrust-after dates and delegator purposes through `checkPurpose` — an anchor that carries either one with no purpose named to select by is refused as a configuration fault rather than validated as though it carried none. The verdict says what was established. `revocationChecked` takes the weakest outcome on the path: `false` with no checker supplied, `"determined"` when every certificate got an explicit good or revoked answer, `"waived"` when `softFail` turned an undetermined one into a pass, and `"undetermined"` when one could not be answered at all — which includes a checker that throws, a fault `softFail` does not waive because `softFail` opts into an undetermined answer, not into a broken checker. `anchorConstraints` names the purpose the anchor was judged under and whether each of its two constraints applied. `crlChecker` supplies CRL-based revocation, covering partitioned and sharded CRLs, whose §6.3.3 Distribution Point ↔ IDP correspondence lets corresponding shards accumulate reason coverage until all eight revocation reasons are covered, and delta CRLs, merged onto the complete CRL they may be combined with (§5.2.4) so a held certificate its delta releases reads good, while a delta that merges with nothing still reports what it lists and still withholds good. `ocspChecker` supplies OCSP-based revocation (RFC 6960: CertID binding, responder authorization, signature, currency) over the same pluggable hook. `build(leaf, opts)` is the discovering complement (RFC 4158): from a leaf, an untrusted pool of candidate CA certificates, and a trust store, it finds the ordered leaf-to-anchor path `validate` accepts, using name chaining plus the RFC 4158 §3.5 sort hints (an AKI/SKI match, an anchor-adjacent issuer, CA plus keyCertSign, validity at the check time — ordering hints, never filters), a depth-first search with backtracking so the first accepted path wins, and a bounded search (chain-length cap, candidate-expansion cap, identity-tuple visited set) so a cross-certificate cycle or Bridge-CA fan-out terminates deterministically. Every accept flows through `validate`, and its verdict is cross-checked against `openssl verify`. Opt-in AIA `caIssuers` fetching (`opts.fetchAia: true`) discovers a missing intermediate from a certificate's Authority Information Access URL (§4.2.2.1) over `pki.transport`, triggered only on a pool miss and bounded against SSRF and amplification: HTTPS only, a total fetch budget that caps fetching silently rather than throwing, a per-cert URL cap, a build-wide URL dedupe, a response-size and certificate-count cap, no redirect following, and every fault a silent skip. The TLS trust (`opts.tls`) stays distinct from the PKI `trustAnchors`, and every fetched certificate remains untrusted pool material that still flows through `validate` (never a trust anchor). It is off by default, so the default build is byte-identical offline. Pure and re-entrant — `validate`, `build`, `crlChecker`, `ocspChecker` |
package/lib/acme.js CHANGED
@@ -2024,7 +2024,7 @@ function client(directoryUrl, opts) {
2024
2024
  if (!authz.identifier || authz.identifier.type !== canon.type || authz.identifier.value !== canon.value || authz.wildcard === true) throw E("acme/identifier-mismatch", "the returned authorization does not match the requested identifier (RFC 8555 sec. 7.4.1)");
2025
2025
  // A pre-authorization's status "MUST be 'pending' unless the server has out-of-band information about the
2026
2026
  // client's authorization status" (sec. 7.4.1): the normal case is a fresh "pending" authz the caller then
2027
- // fulfils, but the CA MAY instead return an already-"valid" authz (the identifier is already authorized for
2027
+ // fulfills, but the CA MAY instead return an already-"valid" authz (the identifier is already authorized for
2028
2028
  // this account -- no challenge to answer). Both are usable; only a terminal failed state ("invalid"/
2029
2029
  // "deactivated"/"expired"/"revoked") is not the authorization the pre-auth flow can proceed with.
2030
2030
  if (authz.status !== "pending" && authz.status !== "valid") throw E("acme/unexpected-authorization-status", "newAuthz returned an authorization that is neither pending nor already valid (status " + JSON.stringify(authz.status) + "); RFC 8555 sec. 7.4.1");
package/lib/cmc-build.js CHANGED
@@ -83,7 +83,7 @@ var KNOWN_SPEC_KEYS = {
83
83
  // The exchange binding (RFC 5272 sec. 6.6 / 6.4). First-class here because
84
84
  // pki.cmc.verify names these same three when checking the response: a builder
85
85
  // that made the caller hand-encode them while the verifier took them by name is
86
- // the asymmetry that lets a request ship with no replay defence.
86
+ // the asymmetry that lets a request ship with no replay defense.
87
87
  transactionId: 1, senderNonce: 1, dataReturn: 1,
88
88
  };
89
89
 
@@ -208,7 +208,7 @@ function encodeRequest(req, ids, index) {
208
208
  // A tcr arm is a PKCS#10 CertificationRequest, so it is parsed and never
209
209
  // taken on the tag: the readback of the assembled message checks the CMC
210
210
  // structure around it, and would pass an empty SEQUENCE here as happily as a
211
- // real request. Signing a request body that is not one produces an enrolment
211
+ // real request. Signing a request body that is not one produces an enrollment
212
212
  // no CA can act on, and the producer is where that is cheap to catch.
213
213
  try { csr.parse(_der(req.tcr, "a tcr certificationRequest")); }
214
214
  catch (e) {
@@ -503,7 +503,7 @@ function _build(spec, signer, opts) {
503
503
  // A misspelled or unsupported spec field fails open in the quietest way there is:
504
504
  // the message builds, is signed, and simply does not carry what was asked for.
505
505
  // That is worst for the exchange-binding fields below: a request built without
506
- // them has no replay defence, and pki.cmc.verify cannot enforce a binding the
506
+ // them has no replay defense, and pki.cmc.verify cannot enforce a binding the
507
507
  // client never sent, so the omission is invisible from both ends.
508
508
  guard.identifier.assertKnownKeys(spec, KNOWN_SPEC_KEYS, E, "cmc/bad-input", "unknown spec field ");
509
509
  var requests = spec.requests || [];
@@ -588,7 +588,7 @@ function _build(spec, signer, opts) {
588
588
 
589
589
  // The exchange binding (RFC 5272 sec. 6.6 Transaction Identifier / Sender Nonce,
590
590
  // sec. 6.4 Data Return). These are what pki.cmc.verify checks the response
591
- // against, and a request that omits them has no replay defence, so they are
591
+ // against, and a request that omits them has no replay defense, so they are
592
592
  // named fields here and not something the caller hand-encodes into
593
593
  // spec.controls and can silently get wrong.
594
594
  // A named field and a hand-encoded control of the same type would emit the
@@ -797,7 +797,7 @@ function _assertKeyOnlySignerBinding(so, requests) {
797
797
  var req = requests[i];
798
798
  // Both key-bearing arms: sec. 3.2 says the signing key may belong to a request
799
799
  // "included in the TaggedRequest tcr or crm fields", so reading only PKCS#10
800
- // would reject every conforming CRMF enrolment of a brand-new key.
800
+ // would reject every conforming CRMF enrollment of a brand-new key.
801
801
  var declaredBy;
802
802
  try {
803
803
  declaredBy = req && req.tcr != null ? _csrKeyIdentity(req.tcr)
package/lib/cmc-verify.js CHANGED
@@ -139,7 +139,7 @@ function _assertBound(body, sent) {
139
139
  var got = asn1.read.integer(asn1.decode(_singleValue(txControl, "Transaction Identifier")));
140
140
  // Compared against the value the authoring guard validated, never a fresh
141
141
  // conversion of the caller's input -- a number too large to hold an integer
142
- // exactly has already lost the digits that distinguish it from its neighbour.
142
+ // exactly has already lost the digits that distinguish it from its neighbor.
143
143
  if (got !== sent.transactionIdValue) {
144
144
  throw E("cmc/transaction-mismatch",
145
145
  "the response Transaction Identifier " + got + " does not match the request's " + sent.transactionId);
@@ -226,7 +226,7 @@ function _assertBound(body, sent) {
226
226
  // caller who retained nothing runs none of them and still gets a full verdict
227
227
  // (`issued`, signature verified, certificates surfaced) off a Full PKI Response
228
228
  // captured from any earlier exchange with the same CA. That is the CWE-294 this
229
- // module names as its defence, and leaving it opt-in leaves the defence off for
229
+ // module names as its defense, and leaving it opt-in leaves the defense off for
230
230
  // whoever did not know to ask.
231
231
  //
232
232
  // The posture here is the one _assertAuthentic already takes for the carrier
@@ -244,7 +244,7 @@ function _assertBound(body, sent) {
244
244
  if (!bound.boundToRequest && sent.allowUnbound !== true) {
245
245
  throw E("cmc/unbound-response",
246
246
  "nothing ties this response to a request. Pass what the request retained (`transactionId`, " +
247
- "`senderNonce`, whose echo is the replay defence of RFC 5272 sec. 6.6, or `dataReturn`) so the " +
247
+ "`senderNonce`, whose echo is the replay defense of RFC 5272 sec. 6.6, or `dataReturn`) so the " +
248
248
  "echo can be checked, or `allowUnbound: true` to interpret a response that could be a replay of " +
249
249
  "any earlier exchange with this CA");
250
250
  }
@@ -288,7 +288,7 @@ function _governingStatus(statuses, outcome) {
288
288
  * none. `sent` is what the client retained from its request --
289
289
  * `transactionId`, `senderNonce`, `dataReturn`, and `bodyPartIDs` (every identifier the request
290
290
  * carried). Each of those is checked only if it was sent, and once sent an absent or differing
291
- * echo is a refusal: that asymmetry is the replay defence. `bodyPartIDs` is the same rule applied
291
+ * echo is a refusal: that asymmetry is the replay defense. `bodyPartIDs` is the same rule applied
292
292
  * to what the response is ABOUT -- a status naming a body part the request never sent is refused
293
293
  * with `cmc/body-part-unknown`, which the transaction and nonce cannot catch, since a server can
294
294
  * echo both correctly while reporting on something else.
@@ -429,7 +429,7 @@ function _snapshotSent(sent) {
429
429
  // integer through. A Transaction Identifier is an unbounded INTEGER on the wire,
430
430
  // so one above Number.MAX_SAFE_INTEGER has already been rounded by the time it
431
431
  // arrives as a `number` -- 9007199254740993 is 9007199254740992 before this code
432
- // sees it, and a response echoing that NEIGHBOURING identifier would compare
432
+ // sees it, and a response echoing that NEIGHBORING identifier would compare
433
433
  // equal. Held here rather than converted, so a value too large to be a number is
434
434
  // refused with the shape to use instead (a bigint) rather than silently bound to
435
435
  // something adjacent.
@@ -599,7 +599,7 @@ function _assertAuthentic(body, sent, responseBytes) {
599
599
  .then(function (res) {
600
600
  if (res.valid) return true;
601
601
  // EVERY failing signer, not just the first. A response may carry several,
602
- // and the opt-out may only be honoured when none of them was actually
602
+ // and the opt-out may only be honored when none of them was actually
603
603
  // checked and rejected: one signer whose certificate is missing must not
604
604
  // let a DIFFERENT signer's failed signature through beside it.
605
605
  var failed = (res.signers || []).filter(function (s) { return !s.ok; });
@@ -710,7 +710,7 @@ function _shape(body, sent, signatureVerified, bound) {
710
710
  * `spec.transactionId` (number|bigint), `spec.senderNonce` and `spec.dataReturn` (bytes) attach the
711
711
  * exchange-binding controls (RFC 5272 sec. 6.6 / 6.4), the same three `pki.cmc.verify` checks the
712
712
  * response against. They are named fields, not something to hand-encode into
713
- * `spec.controls`, because a request that quietly omits them has no replay defence and neither end
713
+ * `spec.controls`, because a request that quietly omits them has no replay defense and neither end
714
714
  * can tell: the verifier only enforces the halves the client says it sent. An unrecognized spec
715
715
  * field is refused for the same reason: a misspelling would otherwise build and sign a message
716
716
  * that simply does not carry what was asked for.
@@ -131,7 +131,7 @@ function _toDer(input) {
131
131
 
132
132
  // ---- stage 1: recipient selection (not secret-dependent) -------------------
133
133
  // Returns every recipient the key material may target. An explicit recipientIndex or an rid match
134
- // yields exactly one; unlabelled pwri / kekri recipients yield all matches for the caller to try.
134
+ // yields exactly one; unlabeled pwri / kekri recipients yield all matches for the caller to try.
135
135
  function _selectCandidates(recips, km, opts) {
136
136
  if (!recips.length) throw _err("cms/no-matching-recipient", "the message carries no RecipientInfos");
137
137
  if (opts.recipientIndex != null) {
@@ -741,7 +741,7 @@ async function _verifyAuthenticatedData(parsed, km, opts) {
741
741
  // || macAlg.oid` and `digestAlgorithm.name || .oid` never reach the `.oid` fallback because the
742
742
  // algorithm name was already validated against MAC_HASH / DIGEST_WC above.
743
743
  // * the `macKey == null` half of the random-key substitution fires only for a hand-crafted RSA v1.5
744
- // ktri (this producer emits OAEP); its behaviour is identical to the tested below-floor path (a
744
+ // ktri (this producer emits OAEP); its behavior is identical to the tested below-floor path (a
745
745
  // random key -> the MAC verify fails uniformly), so the < 16 vector covers the substitution.
746
746
  // _normKeyDer(key) -> { der, owned }: the recipient private key as PKCS#8 DER, and whether the
747
747
  // buffer is one this module made.
package/lib/cms-verify.js CHANGED
@@ -579,7 +579,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
579
579
  // Coverage residual, and deliberately kept. Every value reaching here was already walked by the
580
580
  // decoder, which validates an id-countersignature by content and not by the attribute type. A
581
581
  // malformed one therefore refused the whole message before verify was entered, so this catch
582
- // cannot be reached through the public path. That is settled behaviour: a message the decoder
582
+ // cannot be reached through the public path. That is settled behavior: a message the decoder
583
583
  // has found to be malformed has no sound remainder to report a verdict over, so it is refused
584
584
  // whole instead of surfaced as one failed countersignature.
585
585
  // cms-verify.test.js pins that through the shipped verbs; this stays because the walk is also
@@ -744,7 +744,7 @@ function _verify(input, opts) {
744
744
  // read a promise turn later, and everything in it stays caller-owned across that gap: the array
745
745
  // can be re-pointed, an anchor's DER rewritten, the validation instant moved. Any of those would
746
746
  // have the chain judged against a configuration the caller never asked for while the verdict
747
- // reports the one they did. The same defence the input and the certificates already have.
747
+ // reports the one they did. The same defense the input and the certificates already have.
748
748
  var trustCfg = _snapshotTrust(opts);
749
749
  return Promise.all(parsed.signerInfos.map(function (si) {
750
750
  // res.valid reflects only the PRIMARY signerInfos; a countersignature / unsigned attribute is
package/lib/constants.js CHANGED
@@ -165,7 +165,7 @@ var LIMITS = {
165
165
  // is copied/tokenized so a hostile server cannot drive unbounded work parsing it
166
166
  // (CWE-770). 8 KiB clears any realistic multi-scheme Digest challenge.
167
167
  HTTP_AUTH_HEADER_MAX_BYTES: BYTES.kib(8),
168
- // Deterministic-CBOR codec ceilings (RFC 8949), the DER neighbours' siblings:
168
+ // Deterministic-CBOR codec ceilings (RFC 8949), the DER neighbors' siblings:
169
169
  // a whole-document cap refused before the walk, a nesting cap, and a per-value
170
170
  // bignum ceiling the document cap can't provide. Unlike DER_MAX_INTEGER_BYTES,
171
171
  // the bignum cap carries NO +1 sign octet -- a CBOR tag-2/3 bignum body is pure
package/lib/est.js CHANGED
@@ -1231,7 +1231,7 @@ function fullcmc(baseUrl, request, opts) {
1231
1231
  _knownOpts(opts, FULLCMC_OPTS, "fullcmc");
1232
1232
  der = _cmcRequestDer(request);
1233
1233
  // Confirm this IS a Full PKI Request before any of it goes over the wire. The
1234
- // bytes are about to be labelled `smime-type=CMC-request`, so a PKIResponse or
1234
+ // bytes are about to be labeled `smime-type=CMC-request`, so a PKIResponse or
1235
1235
  // an unparseable blob handed in by mistake would be POSTed to a CA under a
1236
1236
  // label that does not describe it -- and, since only an `issued` outcome is
1237
1237
  // correlated, could still come back as a pending or rejected verdict, making
@@ -1567,7 +1567,7 @@ function _assertAgrees(name, supplied, carried) {
1567
1567
  } else {
1568
1568
  // Through the shared authoring guard, not a bare BigInt(): a `number` above
1569
1569
  // Number.MAX_SAFE_INTEGER has already lost the digits that distinguish it from
1570
- // its neighbour, so converting it here would compare a value the caller never
1570
+ // its neighbor, so converting it here would compare a value the caller never
1571
1571
  // wrote. A transaction identifier is an unbounded INTEGER on the wire; large
1572
1572
  // ones are passed as a bigint, and this says so rather than rounding.
1573
1573
  same = carried != null &&
@@ -1671,7 +1671,7 @@ function _fullcmcResult(res, opts, wanted, sent) {
1671
1671
 
1672
1672
  // FC5 admits two smime-types, and the LABEL must agree with the BYTES: a
1673
1673
  // certs-only label over a Full PKI Response (or the reverse) is a server
1674
- // mislabelling its own body, and accepting either shape under either label
1674
+ // mislabeling its own body, and accepting either shape under either label
1675
1675
  // would make the content-type check decorative.
1676
1676
  // Through the shared case-insensitive accessor: HTTP header names are
1677
1677
  // case-insensitive (RFC 9110 sec. 5.1), the classifier already reads them that
package/lib/guard-all.js CHANGED
@@ -11,35 +11,35 @@
11
11
  // itself, with no guard re-implemented inline:
12
12
  //
13
13
  // guard.bytes.view / .source -- untrusted byte-source -> Buffer re-view
14
- // (detached-buffer fail-open defence)
14
+ // (detached-buffer fail-open defense)
15
15
  // guard.text.decode -- byte-source -> string, cap before copy
16
- // (parser-DoS string-allocation defence)
16
+ // (parser-DoS string-allocation defense)
17
17
  // guard.limits.cap / .depthCap -- config-time resource-cap validation
18
- // (recursion / allocation DoS defence)
18
+ // (recursion / allocation DoS defense)
19
19
  // guard.crypto.constantTimeEqual -- length-checked constant-time compare
20
- // (timing side-channel defence)
20
+ // (timing side-channel defense)
21
21
  // guard.range.int / .uint31 / .positiveInt31
22
22
  // -- bound a decoded integer before narrowing
23
- // to Number (silent-narrowing defence)
23
+ // to Number (silent-narrowing defense)
24
24
  // guard.time.assertValid / .within -- validate a Date-instant before a temporal
25
- // comparison (NaN-Date fail-open defence)
25
+ // comparison (NaN-Date fail-open defense)
26
26
  // guard.name.dnEqual / .rdnEqual / .assertNoControlBytes / .assertPrintableIa5
27
27
  // -- canonical DN identity + name-string integrity
28
28
  // guard.name.escapeControlBytes / .escapeDnValue
29
29
  // -- render-side name safety: control-byte + RFC 4514 escaping
30
- // (CVE-2009-2408 truncation / identity defence)
30
+ // (CVE-2009-2408 truncation / identity defense)
31
31
  // guard.encoding.base64url / .base64 / .hex
32
32
  // -- strict textual-encoding decode, canonical
33
- // + capped (encoding-malleability defence)
33
+ // + capped (encoding-malleability defense)
34
34
  // guard.identifier.assertCanonicalOid
35
35
  // -- canonical dotted-decimal OID string form
36
- // (canonicalization-divergence defence)
36
+ // (canonicalization-divergence defense)
37
37
  // guard.compress.bounded -- decompress an untrusted stream under a hard
38
38
  // output cap, whole-input (decompression-bomb +
39
- // trailing-frame malleability defence)
39
+ // trailing-frame malleability defense)
40
40
  // guard.header.assertField -- emitted MIME/RFC 5322 header field name +
41
41
  // value integrity (CR/LF/NUL header-injection
42
- // defence, CWE-93)
42
+ // defense, CWE-93)
43
43
  // guard.parsed.accept -- a CLAIMED-parsed structure carries every
44
44
  // field the consuming code dereferences
45
45
  // (type confusion / unverified provenance,
@@ -49,7 +49,7 @@
49
49
  // token of a guard (the Buffer.from(x.buffer, byteOffset) re-view, the
50
50
  // timingSafeEqual call, the MAX_DECODE_DEPTH_CEILING check) must appear only in
51
51
  // its guard module, so a new boundary cannot re-inline the shape and forget the
52
- // defence.
52
+ // defense.
53
53
 
54
54
  var bytes = require("./guard-bytes");
55
55
  var text = require("./guard-text");
@@ -11,7 +11,7 @@ var async = require("./guard-async");
11
11
  // guard-bytes -- fail-closed coercion of an untrusted byte-source input to a
12
12
  // Buffer view. One of the enforced choke points of the guard family: a
13
13
  // codebase-patterns detector requires every byte-input boundary to route
14
- // through here, so the defence below cannot be forgotten at a new boundary.
14
+ // through here, so the defense below cannot be forgotten at a new boundary.
15
15
  //
16
16
  // Defends the detached-buffer fail-OPEN: a transferred / structuredClone'd
17
17
  // Buffer or view has a detached backing ArrayBuffer and reads as ZERO-LENGTH.
@@ -85,7 +85,7 @@ function source(input, ErrorClass, code, label) {
85
85
 
86
86
  // snapshot(input, ErrorClass, code, label) -> private Buffer copy | throws ErrorClass
87
87
  //
88
- // The parse-then-verify time-of-check/time-of-use defence. A verification entry
88
+ // The parse-then-verify time-of-check/time-of-use defense. A verification entry
89
89
  // point that PARSES its input synchronously and then VERIFIES a signature over
90
90
  // the same bytes in a later promise turn is reading the caller's memory twice
91
91
  // with an await in between. Every byte range the parse surfaced (the signed
@@ -108,7 +108,7 @@ function snapshot(input, ErrorClass, code, label) {
108
108
  }
109
109
 
110
110
  // snapshotSource(input, ErrorClass, code, label) -> private Buffer copy | throws
111
- // The same parse-then-verify defence as `snapshot`, over the whole W3C BufferSource
111
+ // The same parse-then-verify defense as `snapshot`, over the whole W3C BufferSource
112
112
  // (raw ArrayBuffer / DataView / any typed-array view) and not only the
113
113
  // Buffer / Uint8Array contract. A parser that accepts a BufferSource must snapshot
114
114
  // the same set: leaving an ArrayBuffer or a DataView aliased reopens the window for
@@ -360,7 +360,7 @@ function _copyEntries(src, dst, ErrorClass, code, label, cap, depth, collect) {
360
360
  // caller passed disappears from Object.keys and an unknown-option check walks a different object
361
361
  // than the verb goes on to read. Shadowing matters for the same reason it is done at all: an own
362
362
  // copy is what makes an inherited value stop tracking the caller's prototype. A method is left
363
- // where it is, since copying a function would only move it, and it is the prototype's behaviour,
363
+ // where it is, since copying a function would only move it, and it is the prototype's behavior,
364
364
  // not the caller's data.
365
365
  function _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect) {
366
366
  var keys = _namesToCopy(src, dst);
@@ -22,7 +22,7 @@
22
22
  // unboundedly many encodings, and a digest over the compressed object would
23
23
  // stop identifying what it decompresses to.
24
24
  //
25
- // Both defences are applied here, once, for every algorithm, so a new consumer
25
+ // Both defenses are applied here, once, for every algorithm, so a new consumer
26
26
  // cannot pick up one and miss the other. Verified uniform across zlib, brotli
27
27
  // and zstd: each reports consumed input as `engine.bytesWritten` under
28
28
  // `info: true`, each raises ERR_BUFFER_TOO_LARGE on a cap breach, and each
@@ -125,7 +125,7 @@ function bounded(algorithm, stream, cap, E, codes, label) {
125
125
  throw new TypeError("guard.compress.bounded: stream must be a Buffer (route the input through guard.bytes.view first)");
126
126
  }
127
127
  // The ceiling is an authoring input: an undefined / NaN / fractional / negative
128
- // cap would silently disable the bomb defence. It must also be at least 1 --
128
+ // cap would silently disable the bomb defense. It must also be at least 1 --
129
129
  // node rejects maxOutputLength 0 with a different fault than a cap breach, so a
130
130
  // zero cap would surface as a malformed-stream verdict instead of a config error.
131
131
  if (!Number.isInteger(cap) || cap < 1) {
package/lib/guard-der.js CHANGED
@@ -26,7 +26,7 @@
26
26
  // Every entry point takes the CALLER's error factory and code, so a c509 splice reports in
27
27
  // the c509 domain and a certificate builder in the x509 domain.
28
28
  // asn1-der composes this guard family, so requiring the codec at module scope would read its
29
- // exports mid-initialisation and see an empty object. The require and the table it builds are
29
+ // exports mid-initialization and see an empty object. The require and the table it builds are
30
30
  // therefore both deferred to first use -- the documented circular-load exception to top-of-file
31
31
  // requires. Guards sit BELOW the codec in the dependency order; this is the one place that
32
32
  // order is inverted, and it is inverted lazily.
@@ -84,9 +84,9 @@ function assertCanonicalOid(str, E, code, label, boundsCode) {
84
84
  // The shape this replaces was hand-written in a dozen callers, and it is worth one home because
85
85
  // getting it wrong fails open in the quietest possible way: an options object whose key is
86
86
  // misspelled silently carries the default, so a caller who asked for a stricter check gets the
87
- // looser behaviour and no error. Two details are easy to lose in a re-inline and are fixed here.
87
+ // looser behavior and no error. Two details are easy to lose in a re-inline and are fixed here.
88
88
  // `known` is consulted with hasOwnProperty and not a truthiness test, so an inherited Object
89
- // member ("constructor", "toString") cannot read as a recognised key; and the walk is over own
89
+ // member ("constructor", "toString") cannot read as a recognized key; and the walk is over own
90
90
  // enumerable keys, so a `__proto__` arriving from JSON is inspected instead of skipped.
91
91
  //
92
92
  // @enforced-by behavioral -- an options-shape walk has no rename-proof code shape; the RED vectors
@@ -99,7 +99,7 @@ function assertCanonicalOid(str, E, code, label, boundsCode) {
99
99
  //
100
100
  // @enforced-by guard-shape-reinlined
101
101
  // The shape requires the throw: an identical walk whose body filters on the same table (copying the
102
- // recognised keys onward instead of rejecting the unrecognised ones) is a different operation with
102
+ // recognized keys onward instead of rejecting the unrecognized ones) is a different operation with
103
103
  // no fail-open risk, and must not be dragged through a guard that only knows how to reject.
104
104
  // @guard-shape Object\.keys\(\w+(?:\.\w+)*\)\.forEach\(function \((\w+)\) \{\s*if \(![\w.]+\[\1\]\) (?:\{\s*)?throw
105
105
  function assertKnownKeys(obj, known, E, code, message) {
@@ -90,7 +90,7 @@ function depthCap(value, key, dflt) {
90
90
  function counter(max, E, code, label) {
91
91
  // The ceiling is an authoring input: an undefined / NaN / fractional max
92
92
  // builds a counter whose `n > max` never fires, leaving a silently dead fanout
93
- // defence. Reject at construction (config-time TypeError).
93
+ // defense. Reject at construction (config-time TypeError).
94
94
  if (!Number.isInteger(max) || max < 0) {
95
95
  throw new TypeError("guard.limits.counter: max must be a non-negative integer");
96
96
  }
@@ -117,7 +117,7 @@ function counter(max, E, code, label) {
117
117
  // over-size RED vectors driving the composing verifiers are the guard.
118
118
  function byteCap(buf, max, E, code, label) {
119
119
  // The ceiling is an authoring input: an undefined / NaN / fractional / negative
120
- // max makes `length > max` never fire, and the size defence is then silently
120
+ // max makes `length > max` never fire, and the size defense is then silently
121
121
  // dead. Reject at config time (TypeError), regardless of the value currency E selects.
122
122
  if (!Number.isInteger(max) || max < 0) {
123
123
  throw new TypeError("guard.limits.byteCap: max must be a non-negative integer");
@@ -44,7 +44,7 @@ var bytes = require("./guard-bytes");
44
44
  // ArrayBuffer. Testing `Buffer.isBuffer(x) || x instanceof Uint8Array` is narrower than what the
45
45
  // verbs document, and the narrowness is invisible in the good case: an ArrayBuffer would fall
46
46
  // through to the object branch and be refused as a rebuilt structure, which reads to the caller as
47
- // "your certificate is malformed" when what happened is that their container was not recognised.
47
+ // "your certificate is malformed" when what happened is that their container was not recognized.
48
48
  // guard.bytes is what finally reads them, and it accepts all four, so the door has to as well.
49
49
  function _isBytes(x) {
50
50
  return Buffer.isBuffer(x) || ArrayBuffer.isView(x) || x instanceof ArrayBuffer;
@@ -105,7 +105,7 @@ function uint64(value, E, code, label) {
105
105
  // take. Accepts a bigint, or a Number that is a safe integer (isSafeInteger, not
106
106
  // isInteger, because a Number above 2^53 has already lost precision by the time it
107
107
  // arrives), so a large identifier MUST come as a bigint and cannot be silently
108
- // rounded to a neighbour. Everything else is a config-time reject.
108
+ // rounded to a neighbor. Everything else is a config-time reject.
109
109
  //
110
110
  // Distinct from int() on purpose: int() BOUNDS a value the wire decoded and narrows
111
111
  // it to Number; this one is unbounded and returns BigInt, because the field has no
@@ -72,7 +72,7 @@ function zeroize(value, ErrorClass, code, label) {
72
72
  // @enforced-by behavioral -- this is a loop over zeroize, which carries the family's only
73
73
  // rename-proof shape (the `.fill(0)` above). It introduces no shape of its own, so a lexical
74
74
  // detector here would anchor on a renameable symbol and go silently green (drift rule sec. 3).
75
- // The behavioural guards are guard-secret.test.js (holes tolerated, every member cleared) and the
75
+ // The behavioral guards are guard-secret.test.js (holes tolerated, every member cleared) and the
76
76
  // CMS vectors that assert the shared secret and KEK are wiped on both the success and failure paths.
77
77
  function zeroizeAll(list, ErrorClass, code, label) {
78
78
  if (!list) return list;
package/lib/guard-text.js CHANGED
@@ -50,7 +50,7 @@ function decode(input, maxBytes, ErrorClass, spec) {
50
50
  if (Buffer.isBuffer(input)) {
51
51
  // Re-view through the byte guard first so a detached backing ArrayBuffer (a
52
52
  // transferred / structuredClone'd Buffer, which reads as zero-length) fails
53
- // closed here -- the same detached-buffer defence the byte boundaries get --
53
+ // closed here -- the same detached-buffer defense the byte boundaries get --
54
54
  // instead of being decoded as an empty string. Then cap, then decode.
55
55
  input = bytes.view(input, ErrorClass, spec.badInput, spec.label);
56
56
  if (input.length > maxBytes) throw new ErrorClass(spec.tooLarge, spec.label + " exceeds the size cap");
package/lib/hpke.js CHANGED
@@ -390,7 +390,7 @@ function _recipPrivate(suite, sk) {
390
390
  // psk / auth / auth-psk. An unknown mode must fail closed, never key-schedule
391
391
  // with an out-of-registry mode byte.
392
392
  // The sender and the recipient read the same option object from OPPOSITE ends, so each direction
393
- // gets its own table. A shared union recognises every name at both ends and so accepts the one that
393
+ // gets its own table. A shared union recognizes every name at both ends and so accepts the one that
394
394
  // cannot do anything there: `senderPublicKey` handed to setupS, or
395
395
  // `senderKey` handed to setupR, is silently ignored, which is the exact silence these tables exist
396
396
  // to remove, in a wider form. An option that means nothing where it was passed is a misunderstanding
@@ -144,7 +144,7 @@ function _validateDigest(paramText, E, code) {
144
144
  ? p.domain.value.replace(/^\s+|\s+$/g, "").split(/\s+/) : null;
145
145
  // charset (RFC 7616 sec. 3.3): the one permitted value is the unquoted token "UTF-8". A quoted charset, or any
146
146
  // other value, is malformed, since answering it would hash the credentials in the wrong encoding, so it is
147
- // rejected (and thus skipped during multi-offer selection in favour of a conforming offer).
147
+ // rejected (and thus skipped during multi-offer selection in favor of a conforming offer).
148
148
  if (p.charset && (p.charset.quoted || String(p.charset.value).toUpperCase() !== "UTF-8")) throw E(code, "the Digest charset must be the unquoted token UTF-8 (RFC 7616 sec. 3.3)");
149
149
  // stale (RFC 7616 sec. 3.3) is the unquoted token "true" or "false". A quoted or otherwise invalid value
150
150
  // (stale=maybe) is malformed and rejected (thus skipped in selection), never parsed as a false flag that
package/lib/jose.js CHANGED
@@ -368,7 +368,7 @@ function assertPublicJwk(jwk) {
368
368
  // `key` NAMES the key this message must be signed under and `profile` selects which header rules
369
369
  // apply -- both narrow what verifies, so a misspelling of either silently widens it back to the
370
370
  // default. That is the shape this check exists for: the caller asked for something stricter and got
371
- // the looser behaviour, with nothing said.
371
+ // the looser behavior, with nothing said.
372
372
  var _VERIFY_KEYS = { key: 1, profile: 1 };
373
373
  async function verify(jws, opts) {
374
374
  opts = opts || {};
package/lib/lint.js CHANGED
@@ -231,7 +231,7 @@ function _subjectCNs(cert) {
231
231
  return out;
232
232
  }
233
233
 
234
- // An extension's criticality is honoured (RFC 5280 4.2: a consumer MUST reject a critical extension
234
+ // An extension's criticality is honored (RFC 5280 4.2: a consumer MUST reject a critical extension
235
235
  // it cannot process) only when this toolkit actually processes its semantics. That authority is
236
236
  // path-validate's PROCESSED_EXTENSIONS (the RFC 5280 sec. 6.1 path-processing set), never the decoder
237
237
  // table: certExtensionDecoders also decodes many extensions purely for display (qcStatements, the MS
package/lib/ocsp.js CHANGED
@@ -530,7 +530,7 @@ function verify(response, opts) {
530
530
  // the closed direction for `revoked`: revocation does not expire the way
531
531
  // non-revocation does, so discarding a signed, current, authorized revoked
532
532
  // verdict because it was replayed would hand a soft-fail caller the certificate
533
- // the responder just refused -- turning the anti-replay defence into the thing
533
+ // the responder just refused -- turning the anti-replay defense into the thing
534
534
  // that accepts a revoked certificate. `nonceMatched: false` still reports that
535
535
  // this response was not bound to this request.
536
536
  if (!matched && verdict.status === "good") {
package/lib/oid.js CHANGED
@@ -22,7 +22,7 @@
22
22
  * attribute types, `2.5.29` for the extensions, `2.16.840.1.101.3.4` for
23
23
  * the NIST algorithms), and each member names only its trailing arc. The
24
24
  * full OID is derived from base + leaf at load, so the arc hierarchy that
25
- * IS the OID namespace is modelled directly instead of re-spelled per
25
+ * IS the OID namespace is modeled directly instead of re-spelled per
26
26
  * entry. It covers the RFC 5280 attribute types and extensions, the
27
27
  * classical signature / public-key / digest algorithms, and the
28
28
  * NIST-assigned post-quantum arcs (ML-DSA, ML-KEM, SLH-DSA). Operators
@@ -306,7 +306,7 @@ function resolveDescriptor(sigAlg) {
306
306
  // defense). For the one-shot families whose public key shares the signature OID
307
307
  // (EdDSA, ML-DSA, SLH-DSA), Node's WebCrypto imports an SPKI of a DIFFERENT type
308
308
  // under the requested name and verifies with the real key, so an Ed25519-signed
309
- // certificate labelled SLH-DSA would otherwise validate. Enforce structurally:
309
+ // certificate labeled SLH-DSA would otherwise validate. Enforce structurally:
310
310
  // the issuer SPKI's algorithm OID MUST equal the signature algorithm OID. (For
311
311
  // RSA/ECDSA -- different key vs signature OIDs -- WebCrypto's import already rejects
312
312
  // a mismatched key type, so `sameKeyOid` is not set and this is a no-op.)
@@ -1753,7 +1753,7 @@ function classifyCrls(parsed) {
1753
1753
  // sec. 5.2.4 @3447 makes deltaCRLIndicator a MUST-be-critical extension. A non-critical one
1754
1754
  // is non-conforming, so it does not earn the NEW capability of being merged -- merging can
1755
1755
  // RELEASE a certificate, and that must rest on a conforming indicator. It is still classified
1756
- // as a delta and still consulted for revocation, which is the shipped behaviour and the
1756
+ // as a delta and still consulted for revocation, which is the shipped behavior and the
1757
1757
  // conservative direction.
1758
1758
  if (crlNumberWithinBound(n) && deltaCritical) { rec.baseCrlNumber = n; rec.mergeable = true; }
1759
1759
  } catch (_e) {
@@ -2393,7 +2393,7 @@ function ocspChecker(responses) {
2393
2393
  * `pki.schema.ocsp.parseResponse` result. A REBUILT parsed response is refused. A
2394
2394
  * signature check has three parts -- the signature, the algorithm that verifies it,
2395
2395
  * and the bytes it covers -- and on a parsed object all three are separate properties:
2396
- * a genuine CA signature over a certificate that CA issued, relabelled, verifies as a
2396
+ * a genuine CA signature over a certificate that CA issued, relabeled, verifies as a
2397
2397
  * ResponseData signature for a response that never existed. The parser marks what it
2398
2398
  * returns, so those three are known to have been derived together from one byte
2399
2399
  * string; `Object.assign`, spread and a JSON round-trip all drop the mark, which is
@@ -2795,7 +2795,7 @@ async function _fetchAiaIssuers(current, aia) {
2795
2795
  for (var c = 0; c < certs.length; c++) {
2796
2796
  var parsed;
2797
2797
  try { parsed = coerceCert(certs[c]); }
2798
- catch (_e3) { /* allow:swallow-unverified verified-unreachable: every cert here already passed the IDENTICAL x509.parse in _aiaParseBody (single-DER validates `body`; certs-only validates each via parseCertsOnly), so coerceCert re-parsing the same bytes cannot throw -- the guard stays as defence-in-depth */ continue; }
2798
+ catch (_e3) { /* allow:swallow-unverified verified-unreachable: every cert here already passed the IDENTICAL x509.parse in _aiaParseBody (single-DER validates `body`; certs-only validates each via parseCertsOnly), so coerceCert re-parsing the same bytes cannot throw -- the guard stays as defense-in-depth */ continue; }
2799
2799
  out.push(parsed);
2800
2800
  }
2801
2801
  }
@@ -3111,7 +3111,7 @@ module.exports = {
3111
3111
  // The set of extension OIDs whose CRITICAL semantics this validator processes (RFC 5280 sec. 6.1).
3112
3112
  // Exposed so a linter can distinguish "processed" from "merely decoded" and stay consistent with
3113
3113
  // the path-validation verdict on a critical extension -- a decoder in certExtensionDecoders is NOT
3114
- // by itself proof the criticality is honoured. Both sets are frozen so a caller cannot mutate them.
3114
+ // by itself proof the criticality is honored. Both sets are frozen so a caller cannot mutate them.
3115
3115
  PROCESSED_EXTENSIONS: PROCESSED_EXTENSIONS,
3116
3116
  // Extensions that ARE processed for an intermediate CA but are unprocessed on the target/leaf, so a
3117
3117
  // critical instance on the target fails closed (RFC 5280 sec. 6.1.5(f)) -- policyMappings is
@@ -1650,7 +1650,7 @@ function _sdaToDer(node, isNative) {
1650
1650
  if (tname === undefined) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attribute type int " + ti + " has no C509 sec. 8.6 registry row");
1651
1651
  // countryName / serialNumber carry a CHARACTER restriction (draft sec. 3.1.4 "SHALL contain only
1652
1652
  // characters from the 74-character ASCII subset permitted by PrintableString"), and not a sign override --
1653
- // _reconAttrValue asserts the charset and honours the declared string type. Requiring the negative sign
1653
+ // _reconAttrValue asserts the charset and honors the declared string type. Requiring the negative sign
1654
1654
  // here would also make these attributes unrepresentable in a NATIVE certificate, whose ints SHALL all be
1655
1655
  // non-negative (same sec.), so the rule is the charset, not the sign.
1656
1656
  for (var vi = 0; vi < valuesNode.children.length; vi++) {
@@ -1846,7 +1846,7 @@ function _reconAttrValue(rdn) {
1846
1846
  if (rdn.type === "emailAddress") return b.ia5(s);
1847
1847
  // serialNumber / countryName carry a CHARACTER restriction, not a string-type override: draft sec. 3.1.4
1848
1848
  // "SHALL contain only characters from the 74-character ASCII subset permitted by PrintableString". Enforce
1849
- // that on the CHARACTERS and still honour the sign for the string type. Coercing them to PrintableString
1849
+ // that on the CHARACTERS and still honor the sign for the string type. Coercing them to PrintableString
1850
1850
  // regardless of sign would make the +N and -N encodings of one value reconstruct IDENTICAL DER, so a single
1851
1851
  // X.509 signature would cover two distinct C509 encodings (a malleability window in the type-3 transform).
1852
1852
  // b.printable IS the PrintableString charset authority -- run it for the assert even when the sign selects
package/lib/schema-cmp.js CHANGED
@@ -460,7 +460,7 @@ var CERT_RESPONSE = schema.seq([
460
460
  // accepted (0) or grantedWithMods (1). Any other status (rejection, waiting,
461
461
  // the revocation / keyUpdate warnings) denies or defers the request, so a
462
462
  // certificate under it is a malformed response even when no explicit failInfo
463
- // bit is set (a rejection is commonly signalled by status alone). Keying the
463
+ // bit is set (a rejection is commonly signaled by status alone). Keying the
464
464
  // rule off failInfo presence alone would let a bare-rejection status ship a
465
465
  // certificate (RFC 9810 sec. 5.3.4).
466
466
  if (certifiedKeyPair !== null && status.status.code !== 0 && status.status.code !== 1) {
package/lib/schema-crl.js CHANGED
@@ -41,7 +41,7 @@ var CRL_REASONS = pkix.CRL_REASON_NAMES;
41
41
 
42
42
  // Extension-value decoding is keyed off the stable dotted OID (resolved once at
43
43
  // load from the canonical name) and not the mutable display name, so a caller's
44
- // pki.oid.register() display override cannot change parse behaviour.
44
+ // pki.oid.register() display override cannot change parse behavior.
45
45
  var OID_CRL_NUMBER = oid.byName("cRLNumber");
46
46
  var OID_REASON_CODE = oid.byName("reasonCode");
47
47
  var OID_INVALIDITY_DATE = oid.byName("invalidityDate");
@@ -40,11 +40,11 @@ function _fail(ctx, code, message) {
40
40
  }
41
41
 
42
42
  // ---- shape assertions ------------------------------------------------
43
- // A schema's `assert` mode is a real behaviour-preservation control: some
43
+ // A schema's `assert` mode is a real behavior-preservation control: some
44
44
  // hand-written guards checked the universal SEQUENCE tag (algorithmIdentifier,
45
45
  // Name), others checked only that the node had children (Validity, SPKI, the
46
46
  // tbs body, an AttributeTypeAndValue). Collapsing them to one "is a SEQUENCE"
47
- // check would silently change behaviour on SET-wrapped input, so each is kept.
47
+ // check would silently change behavior on SET-wrapped input, so each is kept.
48
48
 
49
49
  function _assertShape(schema, node, ctx) {
50
50
  var mode = schema.assert || "sequence";
@@ -1228,7 +1228,7 @@ function certExtensionDecoders(ns) {
1228
1228
 
1229
1229
  // qcStatements ::= SEQUENCE OF QCStatement (RFC 3739 sec. 3.2.6). QCStatement ::= SEQUENCE {
1230
1230
  // statementId OBJECT IDENTIFIER, statementInfo ANY DEFINED BY statementId OPTIONAL }. Known statementIds
1231
- // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalog) decode their statementInfo; an unknown statementId
1231
+ // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalogue) decode their statementInfo; an unknown statementId
1232
1232
  // is preserved OPAQUE (the RFC 3739 open type), never rejected. The DEFINED-BY selection is an OID re-dispatch.
1233
1233
  function _qcStr(node, tag, C, what) {
1234
1234
  if (!node || node.tagClass !== "universal" || node.tagNumber !== tag) throw ns.E(C, what);
package/lib/smime.js CHANGED
@@ -846,7 +846,7 @@ function _capped(msg) {
846
846
  * compared under RFC 5280 sec. 7.5: the local-part exactly, the host-part case-insensitively. The
847
847
  * address is read from the `subjectAltName` `rfc822Name` entries (RFC 8550 sec. 4.4.3) and, where the
848
848
  * extension carries none, from the subject DN's PKCS #9 `emailAddress` attribute, which RFC 8550 sec. 3
849
- * requires a receiving agent to recognise. Where both are present the extension wins. Only a signer
849
+ * requires a receiving agent to recognize. Where both are present the extension wins. Only a signer
850
850
  * whose signature verified contributes an identity, so a tampered message cannot report a binding. `match` is THREE-valued and a caller enforcing sender binding tests
851
851
  * `match === true` -- `false` means every identity was comparable and none matched, and `null` means the
852
852
  * question was not answered (no `expectedSender` and no single outer `From`, a signer certificate
@@ -8,7 +8,7 @@
8
8
  // validator-cose -- the SINGLE home for "is this a conformant WebAuthn credential
9
9
  // COSE_Key" (RFC 9052 sec. 7 structure + RFC 9053 EC2/OKP/RSA key parameters + the
10
10
  // CTAP2 canonical-CBOR profile WebAuthn sec. 6.5.1 imposes). Sibling to the guard
11
- // family: where a guard owns a CVE-class fail-closed defence once, a validator owns a
11
+ // family: where a guard owns a CVE-class fail-closed defense once, a validator owns a
12
12
  // decoded TYPE's COMPLETE conformance rule set once, so a format module composes the
13
13
  // family instead of re-deriving a partial subset inline (the drift that leaks MUSTs
14
14
  // out one review round at a time). Enforced by the validator-shape-reinlined
@@ -242,10 +242,10 @@ function assertKeyMaterial(key, E, code, unsupportedCode) {
242
242
  // PROFILE: the declared alg must match the key type (and, for EC2, the curve).
243
243
  var prof = ALG_PROFILE[String(key.alg)];
244
244
  // An algorithm this verifier does not implement is not a malformed key. The key can be perfectly
245
- // well-formed (the same bytes may parse under a neighbouring algorithm id), and a relying
245
+ // well-formed (the same bytes may parse under a neighboring algorithm id), and a relying
246
246
  // party migrating credential rows written elsewhere needs to tell "I cannot check this
247
247
  // algorithm" from "these bytes are wrong", since only one of those is fixable by re-registering.
248
- // Callers that do not distinguish the two pass one code and keep the previous behaviour.
248
+ // Callers that do not distinguish the two pass one code and keep the previous behavior.
249
249
  if (!prof) throw new E(unsupportedCode || code, "unsupported credential key algorithm " + key.alg);
250
250
  if (prof.kty !== key.kty) throw bad("credential key algorithm " + key.alg + " is inconsistent with key type " + key.kty);
251
251
  if (prof.crv != null && prof.crv !== key.crv) throw bad("credential key algorithm " + key.alg + " requires a different curve");
@@ -85,7 +85,7 @@ function _schemeE(kind, message, cause) { return new WebauthnError("webauthn/" +
85
85
  // WebCrypto algorithms.
86
86
  //
87
87
  // Null-prototype: `alg` is attacker-supplied, and an inherited Object member would otherwise
88
- // resolve to a truthy non-row and read as a recognised algorithm.
88
+ // resolve to a truthy non-row and read as a recognized algorithm.
89
89
  function _deriveBlobAlgs() {
90
90
  var out = Object.create(null);
91
91
  jose.sigAlgs().forEach(function (row) {
@@ -120,7 +120,7 @@ function _blobAlgParams(algRow, leafAlgName) {
120
120
 
121
121
  // The status values that deny trust (MDS v3.0 sec. 3.1.4). An unknown status is IGNORED for the
122
122
  // gate and surfaced raw -- the specification requires that a verifier not fail on a status it does
123
- // not recognise -- unless the caller opts into refusing them.
123
+ // not recognize -- unless the caller opts into refusing them.
124
124
  var DISQUALIFYING = Object.assign(Object.create(null), {
125
125
  REVOKED: 1, ATTESTATION_KEY_COMPROMISE: 1, USER_KEY_REMOTE_COMPROMISE: 1,
126
126
  USER_KEY_PHYSICAL_COMPROMISE: 1, USER_VERIFICATION_BYPASS: 1,
@@ -140,7 +140,7 @@ var _BLOB_OPTS = Object.assign(Object.create(null), {
140
140
  function _isPlainObject(v) { return !!v && typeof v === "object" && !Array.isArray(v); }
141
141
 
142
142
  // The results this module actually produced. Membership is the PROVENANCE check: a metadata object
143
- // is only allowed to decide trust if verifyMetadataBlob made it. Recognising it by shape instead
143
+ // is only allowed to decide trust if verifyMetadataBlob made it. Recognizing it by shape instead
144
144
  // would accept anything carrying the right property names, including a catalogue deserialized
145
145
  // from a cache, where nothing establishes that the signature and chain were ever checked, and an
146
146
  // attacker who can write that cache chooses which roots an authenticator is allowed to chain to.
@@ -207,7 +207,7 @@ function _isAnchorItself(cert, anchor) {
207
207
  //
208
208
  // An already-parsed certificate is taken as-is, never re-encoded and re-parsed, but the
209
209
  // recognition test names every field the anchor is later read for: the subject name, the public
210
- // key bytes, and the signature algorithm OID. Recognising an object on a looser test lets something
210
+ // key bytes, and the signature algorithm OID. Recognizing an object on a looser test lets something
211
211
  // that is merely certificate-SHAPED through: a parsed CSR satisfies "has a subject and an SPKI", and
212
212
  // a hand-built object literal satisfies it too and then raises a raw TypeError from deep inside the
213
213
  // path validator, which is an untyped throw escaping a public verb.
@@ -250,8 +250,8 @@ function _verifyMetadataBlob(blob, opts) {
250
250
  throw _err("webauthn/bad-input", "opts.previousNo must be a non-negative safe integer");
251
251
  }
252
252
  // The boolean options are checked for TYPE, not merely compared against true. A caller who wrote
253
- // `rejectUnknownStatus: "true"` from a config file is asking for the stricter behaviour; comparing
254
- // against `true` silently records the policy as off and the authenticator carrying an unrecognised
253
+ // `rejectUnknownStatus: "true"` from a config file is asking for the stricter behavior; comparing
254
+ // against `true` silently records the policy as off and the authenticator carrying an unrecognized
255
255
  // status is then accepted -- the fail-open the option exists to prevent.
256
256
  ["requireRollbackCheck", "allowStale", "rejectUnknownStatus"].forEach(function (k) {
257
257
  if (opts[k] !== undefined && typeof opts[k] !== "boolean") {
@@ -326,7 +326,7 @@ function _verifyMetadataBlob(blob, opts) {
326
326
  maxBytes: C.MDS_BLOB_HEADER_MAX_BYTES, maxDepth: C.JSON_MAX_DEPTH,
327
327
  tooLarge: "webauthn/too-large", badJson: "webauthn/bad-metadata-blob",
328
328
  // Every code the guard can raise is named. An omitted one falls back to the framework default
329
- // and the module's own defences -- duplicate-member smuggling, the depth cap -- surface under
329
+ // and the module's own defenses -- duplicate-member smuggling, the depth cap -- surface under
330
330
  // a generic code no webauthn/* consumer can switch on.
331
331
  tooDeep: "webauthn/bad-metadata-blob", duplicateMember: "webauthn/bad-metadata-blob",
332
332
  badInput: "webauthn/bad-metadata-blob", label: "the metadata BLOB header",
@@ -449,7 +449,7 @@ function _verifyMetadataBlob(blob, opts) {
449
449
  // anchors through this same walk while keeping the verdict its own callers already handle. A second copy
450
450
  // of the walk is not merely duplication: the anchor-stripping rule is subtle (a terminal
451
451
  // certificate that IS the anchor is identified by subject name AND public key, which is what
452
- // recognises a cross-signed root), and a copy that got it slightly differently would refuse a valid
452
+ // recognizes a cross-signed root), and a copy that got it slightly differently would refuse a valid
453
453
  // chain in one place and accept a self-validated one in another.
454
454
  function _chainToAnchor(chain, anchors, at, what, code) {
455
455
  var subject = what || "metadata BLOB certificate chain";
@@ -509,7 +509,7 @@ function _staleAfter(nextUpdate) {
509
509
  // has to be re-established every time it decides something, not only when it was parsed. A
510
510
  // catalogue fetched before its nextUpdate and reused a month later would otherwise keep authorizing
511
511
  // an authenticator whose status reports have since revoked it, which is precisely what nextUpdate
512
- // exists to prevent. The caller's original allowStale decision is carried on the result and honoured
512
+ // exists to prevent. The caller's original allowStale decision is carried on the result and honored
513
513
  // here, so opting out stays opted out and never silently reappears at the point of use.
514
514
  function assertFresh(metadata, at, label) {
515
515
  if (!metadata || metadata.allowStale === true || typeof metadata.nextUpdate !== "string") return;
@@ -696,7 +696,7 @@ function metadataFor(metadata, identifier) {
696
696
  return null;
697
697
  }
698
698
 
699
- // The options metadataAnchors recognises. Null-prototype for the same reason every other
699
+ // The options metadataAnchors recognizes. Null-prototype for the same reason every other
700
700
  // caller-keyed table in this namespace is: a supplied key must not resolve to an inherited member.
701
701
  var _ANCHOR_OPTS = Object.assign(Object.create(null), { metadata: 1, time: 1, certificate: 1 });
702
702
 
@@ -846,13 +846,13 @@ function statusDenied(entry, metadata, leaf, at) {
846
846
  if (s === null) return false;
847
847
  // Scope was settled above, so anything still here applies to this authenticator.
848
848
  if (DISQUALIFYING[s]) return true;
849
- // An unrecognised status is IGNORED for the gate unless the caller opted in: the specification
849
+ // An unrecognized status is IGNORED for the gate unless the caller opted in: the specification
850
850
  // requires a verifier not to fail on a status value it does not know.
851
851
  return rejectUnknown && !_KNOWN_STATUS[s];
852
852
  });
853
853
  }
854
854
 
855
- // Status values this library recognises as non-disqualifying, so `rejectUnknownStatus` can tell an
855
+ // Status values this library recognizes as non-disqualifying, so `rejectUnknownStatus` can tell an
856
856
  // unknown value from a known-good one.
857
857
  var _KNOWN_STATUS = Object.assign(Object.create(null), {
858
858
  NOT_FIDO_CERTIFIED: 1, SELF_ASSERTION_SUBMITTED: 1, FIDO_CERTIFIED: 1, FIDO_CERTIFIED_L1: 1,
package/lib/webauthn.js CHANGED
@@ -474,8 +474,8 @@ var ATT_STMT_MAJOR = { compound: 4 };
474
474
  var _AAGUID_SIGNED_BY_FMT = Object.assign(Object.create(null), { "fido-u2f": false });
475
475
  function _aaguidIsSigned(fmt) { return _AAGUID_SIGNED_BY_FMT[fmt] !== false; }
476
476
 
477
- // The options pki.webauthn.verify recognises. Null-prototype, so a caller-supplied key cannot
478
- // resolve to an inherited Object member and read as recognised.
477
+ // The options pki.webauthn.verify recognizes. Null-prototype, so a caller-supplied key cannot
478
+ // resolve to an inherited Object member and read as recognized.
479
479
  var _VERIFY_OPTS = Object.assign(Object.create(null), {
480
480
  time: 1, metadata: 1, tpmPolicy: 1, safetyNetRoots: 1, verifySafetyNetJws: 1, requireCtsProfileMatch: 1,
481
481
  expectedRpId: 1, requireUserPresence: 1, requireUserVerification: 1, allowedAlgorithms: 1,
@@ -511,7 +511,7 @@ var _FORMAT_SCOPED_BOOLEAN_OPTS = ["verifySafetyNetJws", "requireCtsProfileMatch
511
511
  // so a caller that recycles the array or overwrites a certificate's bytes in between
512
512
  // would have the attestation anchored against the replacement roots while the verdict
513
513
  // still reports `anchoredTo: "rootCertificates"` (CWE-367 reaching a wrong trust
514
- // decision). Same defence, and the same reason, as the assertion input's snapshot.
514
+ // decision). Same defense, and the same reason, as the assertion input's snapshot.
515
515
  // Shape faults are NOT raised here: the array is validated where it is consumed, so
516
516
  // supplying a malformed pin alongside a metadata catalogue that answers keeps failing
517
517
  // exactly where it did before.
@@ -645,7 +645,7 @@ function _applyCallerRoots(res, supplied, vopts, onlyPaths) {
645
645
  var at = vopts.time !== undefined ? vopts.time : new Date();
646
646
  // EVERY path, not merely one: a compound's elements are independent claims, and
647
647
  // accepting the whole because one element anchored would let an unanchored element
648
- // ride along on its neighbour's trust.
648
+ // ride along on its neighbor's trust.
649
649
  return paths.reduce(function (p, info) {
650
650
  return p.then(function () {
651
651
  var pathAt = vopts.time !== undefined ? vopts.time : (info.at || at);
@@ -1114,7 +1114,7 @@ var VERIFIERS = {
1114
1114
  });
1115
1115
  });
1116
1116
  }, Promise.resolve()).then(function () {
1117
- // sec. 8.9 lists the supported attestation type as "Any" and authorises returning
1117
+ // sec. 8.9 lists the supported attestation type as "Any" and authorizes returning
1118
1118
  // "implementation-specific values representing any combination of outputs". A distinct type
1119
1119
  // rather than a merge: collapsing to the strongest element would let a wrapper upgrade a
1120
1120
  // caller's attestationType check, and collapsing to the weakest would spuriously fail one.
@@ -1388,7 +1388,7 @@ function verify(attestationObject, clientDataHash, opts) {
1388
1388
  // Every option here either GATES the verdict or supplies the trust material a gate needs, so a
1389
1389
  // misspelled key is not a harmless no-op: `metdata` leaves the metadata gate switched off and the
1390
1390
  // call returns a pass the caller believes was checked against the catalogue. Reject an
1391
- // unrecognised key at the boundary, and validate the one shared option every arm reads, so a bad
1391
+ // unrecognized key at the boundary, and validate the one shared option every arm reads, so a bad
1392
1392
  // instant is a config fault here rather than a trust failure reported from deep inside a chain.
1393
1393
  try {
1394
1394
  if (!_isPlainObject(opts)) throw _err("webauthn/bad-input", "opts must be an object");
@@ -1406,7 +1406,7 @@ function verify(attestationObject, clientDataHash, opts) {
1406
1406
  // The boolean switches are typed HERE rather than in the arm that reads them. Validated only
1407
1407
  // inside the android-safetynet arm, `requireCtsProfileMatch: "true"` reaching any other format
1408
1408
  // is never examined at all -- a truthy string that demands nothing, silently accepted. A
1409
- // recognised key with an unusable value is the same class of caller mistake as an unrecognised
1409
+ // recognized key with an unusable value is the same class of caller mistake as an unrecognized
1410
1410
  // key, and belongs at the same boundary.
1411
1411
  _FORMAT_SCOPED_BOOLEAN_OPTS.forEach(function (k) {
1412
1412
  if (opts[k] !== undefined && typeof opts[k] !== "boolean") throw _err("webauthn/bad-input", "opts." + k + " must be a boolean");
@@ -1597,7 +1597,7 @@ function verify(attestationObject, clientDataHash, opts) {
1597
1597
  function _applyMetadata(res, att, opts) {
1598
1598
  var md = opts.metadata;
1599
1599
  // Provenance, not shape: only a catalogue this toolkit actually verified may decide which roots
1600
- // an authenticator is allowed to chain to. Recognising it by its property names would accept a
1600
+ // an authenticator is allowed to chain to. Recognizing it by its property names would accept a
1601
1601
  // hand-built object, or one restored from a cache an attacker can write -- neither of which has
1602
1602
  // been through the signature and chain checks that give the catalogue its authority.
1603
1603
  if (!mds.isVerifiedResult(md)) {
@@ -1703,7 +1703,7 @@ function _applyMetadata(res, att, opts) {
1703
1703
  mds.assertFresh(md, at, "the metadata supplied as opts.metadata");
1704
1704
  // EVERY path must pass, not merely one of them: for a compound attestation each element is an
1705
1705
  // independent claim, and accepting the whole because one element anchored would let an
1706
- // unanchored -- or revoked -- element ride along on its neighbour's trust.
1706
+ // unanchored -- or revoked -- element ride along on its neighbor's trust.
1707
1707
  // Govern EVERY path BEFORE any is chain-validated, and let the SEVEREST outcome decide
1708
1708
  // rather than the first one reached. A compound's elements are independent claims and
1709
1709
  // its element order is NOT signed, so aborting on the first failure lets that order pick
@@ -1836,7 +1836,7 @@ void constants;
1836
1836
  * (the default; any disqualifying report ever filed), `"latest-by-date"` (only the
1837
1837
  * most recent report counts, so a later remediation clears an earlier revocation),
1838
1838
  * or a function receiving the raw report array and returning true to deny.
1839
- * - `rejectUnknownStatus` -- treat a status this toolkit does not recognise as
1839
+ * - `rejectUnknownStatus` -- treat a status this toolkit does not recognize as
1840
1840
  * disqualifying. Off by default: the specification requires an unknown status be
1841
1841
  * ignored, never failed on.
1842
1842
  *
@@ -2146,7 +2146,7 @@ function parseAuthenticatorData(bytes) {
2146
2146
  return _parseAuthData(_bytesArg(bytes, "authenticatorData"), _err);
2147
2147
  }
2148
2148
 
2149
- // The options pki.webauthn.verifyAssertion recognises, null-prototype for the same
2149
+ // The options pki.webauthn.verifyAssertion recognizes, null-prototype for the same
2150
2150
  // reason _VERIFY_OPTS is.
2151
2151
  var _ASSERT_OPTS = Object.assign(Object.create(null), {
2152
2152
  authenticatorData: 1, clientDataHash: 1, clientDataJSON: 1, signature: 1,
package/lib/webcrypto.js CHANGED
@@ -1344,7 +1344,7 @@ function exportAnyKey(key, E, code) {
1344
1344
  // A separately-installed copy of this engine is the same code under a different class identity, so
1345
1345
  // its keys fail the `instanceof` above while carrying the very handle this engine reads -- and no
1346
1346
  // WebCrypto implementation but that copy would accept them. Read the handle directly. The
1347
- // extractable check above stays AHEAD of this: the handle can always be read, so honouring the
1347
+ // extractable check above stays AHEAD of this: the handle can always be read, so honoring the
1348
1348
  // key's own extractable promise is this branch's responsibility, not the crypto library's.
1349
1349
  var handle = key._handle;
1350
1350
  if (handle instanceof nodeCrypto.KeyObject && handle.type === key.type) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -68,9 +68,11 @@
68
68
  "test": "node test/smoke.js",
69
69
  "lint": "eslint --max-warnings 0 .",
70
70
  "fuzz": "npm ci --prefix fuzz && npx --prefix fuzz jazzer fuzz/asn1-der.fuzz.js -- -max_total_time=60",
71
- "gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js",
71
+ "gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js && node scripts/check-spelling-consistency.js",
72
+ "check:spelling": "node scripts/check-spelling-consistency.js",
72
73
  "coverage": "c8 --include=lib/** --include=index.js --reporter=text-summary --reporter=lcov node test/smoke.js",
73
74
  "check:prose": "node scripts/check-operator-prose.js",
75
+ "check:prose:all": "node -e \"process.env.PKI_PROSE_CHECKS='all';var r=require('child_process').spawnSync(process.execPath,['scripts/check-operator-prose.js'],{stdio:'inherit'});if(r.error)throw r.error;process.exit(r.signal?1:r.status)\"",
74
76
  "check:swallows": "node scripts/check-swallow-coverage.js",
75
77
  "coverage:gated": "npm run coverage && npm run check:swallows",
76
78
  "coverage:unified": "node scripts/coverage-unified.js",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:0541b2cd-7339-4ee1-9122-38999da30d35",
5
+ "serialNumber": "urn:uuid:b51c65a4-3df8-4dec-af37-301d0b285af6",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-18T06:52:40.138Z",
8
+ "timestamp": "2026-08-18T10:39:56.596Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/pki@0.5.9",
22
+ "bom-ref": "@blamejs/pki@0.5.10",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.9",
25
+ "version": "0.5.10",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
29
- "purl": "pkg:npm/%40blamejs/pki@0.5.9",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.10",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/pki@0.5.9",
57
+ "ref": "@blamejs/pki@0.5.10",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]