kxco-post-quantum 1.7.1 → 1.7.3

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/AGILITY.md ADDED
@@ -0,0 +1,115 @@
1
+ # Cryptographic agility
2
+
3
+ What has to change, and what does not, when the algorithm changes.
4
+
5
+ "Crypto-agility" is used for three different things: a plan to replace an
6
+ algorithm, a mechanism that performs the replacement, and a transition that
7
+ peers can follow without breaking. They are not the same claim and this
8
+ document keeps them apart. Where the answer is "we do not have that", it says
9
+ so.
10
+
11
+ ## 1. A replacement plan
12
+
13
+ [MIGRATION.md](MIGRATION.md) carries the plan, in four stages: verify both,
14
+ sign both, require both, drop the classical signature. The rule it exists to
15
+ enforce is *do not swap, add then remove*, because a one-release swap strands
16
+ everything signed before it and every peer you do not control.
17
+
18
+ The same shape covers a move between post-quantum parameter sets, with one
19
+ difference: there is no classical side to retire, so stage 4 is reached as soon
20
+ as every consumer verifies the new set.
21
+
22
+ For key establishment the plan is not optional in the same way. Combine the
23
+ ML-KEM shared secret with a classical secret through a KDF, so the session
24
+ holds if either does. `deriveSeed` is what that HKDF step is for.
25
+
26
+ ## 2. An implemented mechanism
27
+
28
+ Three mechanisms exist in this package today.
29
+
30
+ **Parameter set selection.** Five sets are published helpers with their own
31
+ entry points: ML-DSA-65, ML-DSA-87, ML-KEM-768, ML-KEM-1024 and
32
+ SLH-DSA-SHA2-192s. Category 5 is not a future item; `mlDsa87` and `mlKem1024`
33
+ ship today. Changing set is an import change at the call site rather than a
34
+ redesign, because every set is reached through the same function shapes.
35
+
36
+ Be precise about the difference between those five and what the backend can
37
+ express. `backend().parameterSets` reports nine on OpenSSL 3.5, including
38
+ ML-DSA-44, ML-KEM-512 and two further SLH-DSA sets, and
39
+ [BENCHMARKS.md](BENCHMARKS.md) measures all of them. Reporting a set is not the
40
+ same as wrapping it: a set outside the five is visible to an evidence bundle
41
+ and is not an API this package offers.
42
+
43
+ **Two interchangeable backends.** The primitives run in OpenSSL 3.5 where the
44
+ runtime provides them and in JavaScript everywhere else. They produce identical
45
+ wire bytes, which is what makes the substitution safe: a signature made on one
46
+ verifies on the other, and the interoperability matrix in
47
+ [CONFORMANCE.md](CONFORMANCE.md) is what establishes that rather than an
48
+ assertion here.
49
+
50
+ **One abstraction, enforced.** [`eslint-plugin-kxco-pq`](https://www.npmjs.com/package/eslint-plugin-kxco-pq)
51
+ fails a build that reaches past the wrapper into `.ml_dsa65` or `.ml_kem768` on
52
+ the underlying library. The point is not style. A call site bound to a
53
+ primitive is a call site that a backend or parameter-set change has to rewrite
54
+ by hand, and the rule is what keeps the abstraction from being quietly holed.
55
+
56
+ The mechanism reports its own state. `backend()` returns which implementation
57
+ is doing the maths, its OpenSSL version and the sets it can express, so an
58
+ evidence bundle or a support ticket records the answer rather than inferring it
59
+ from `process.version`. It reports and does not switch, deliberately: a runtime
60
+ flag that changed which implementation signed would be a flag that changed what
61
+ a customer's evidence means.
62
+
63
+ The one operator control is the opposite of a switch. `requireNativeBackend()`,
64
+ or `KXCO_PQ_REQUIRE_NATIVE=1` in the environment, refuses to let a process
65
+ continue on the JavaScript backend when the deployment is under a control that
66
+ says the cryptography must execute inside a validated module. It fails at
67
+ import rather than at the first signature. It cannot make OpenSSL appear, and
68
+ it does not claim that the OpenSSL it found is a validated module, which is a
69
+ property of the operator's build.
70
+
71
+ ## 3. An interoperable transition
72
+
73
+ **Algorithm identifiers on the wire.** The JWS module resolves the
74
+ implementation from its own allowlist using the header `alg`, and rejects
75
+ anything outside it. That is what lets a verifier accept two algorithms during
76
+ a transition without becoming vulnerable to the algorithm-confusion failure
77
+ that has broken JWT libraries repeatedly. `JWS_ALGORITHMS` is the allowlist;
78
+ the RFC 9964 names are used, not private ones.
79
+
80
+ **Key identifiers.** `fingerprint()` gives a key a stable kid, and both signing
81
+ and verification take one, so a consumer can be pinned to a specific key while
82
+ the algorithm around it moves.
83
+
84
+ **Two signatures over identical bytes.** The webhook envelope signs
85
+ `${timestamp}.${rawBody}` with HMAC-SHA-256 and with ML-DSA-65, both covering
86
+ exactly the same bytes. A receiver that verifies only one cannot be tricked
87
+ into treating it as covering a different message. This is stage 2 and stage 3
88
+ of the plan, already built.
89
+
90
+ **Evidence that peers accept the output.** The transition claim is only worth
91
+ the interoperability behind it. Bouncy Castle, liboqs, two independent Python
92
+ implementations and OpenSSL 3.5 verify what this package produces, and it
93
+ verifies theirs, both directions, pinned by version, in
94
+ [CONFORMANCE.md](CONFORMANCE.md).
95
+
96
+ ## What this package does not give you
97
+
98
+ - **No runtime negotiation protocol.** Nothing here discovers what a peer
99
+ supports. The JWS allowlist lets a verifier accept more than one algorithm;
100
+ it does not ask a peer which one to use.
101
+ - **No automatic downgrade, ever.** There is no path that silently drops to a
102
+ weaker set when the stronger one is unavailable. A missing set is an error.
103
+ - **The set list is fixed at release.** A parameter set this package does not
104
+ wrap needs a release of this package, not a configuration change. That is the
105
+ honest ceiling of the abstraction.
106
+ - **The JWS allowlist is ML-DSA only.** SLH-DSA and ML-KEM are reached at the
107
+ key and signature layer, not through `alg`.
108
+ - **No TLS group negotiation.** Channel-level agility is not this package's
109
+ surface. See `kxco-pq-tls`.
110
+
111
+ ## Correcting this document
112
+
113
+ Every mechanism above is in `src/` and every evidence claim is in
114
+ [CONFORMANCE.md](CONFORMANCE.md). If a statement here does not match the code,
115
+ that is a defect worth reporting through [SECURITY.md](SECURITY.md).
package/BOUNDARY.md ADDED
@@ -0,0 +1,111 @@
1
+ # Product boundary
2
+
3
+ Which cryptography this package performs, which it depends on, and which it
4
+ merely offers to a caller. The three are routinely collapsed into one claim,
5
+ and collapsing them is how "quantum-safe" gets asserted for a product whose own
6
+ update path is classical.
7
+
8
+ ## What the assessed thing is
9
+
10
+ A library. It has no server, no console and no runtime service connections:
11
+ nothing in `src/` opens a socket. That is a narrow boundary and it makes most
12
+ of the questions below short, but the short answers are the point.
13
+
14
+ Record the configuration alongside any claim, because the answers differ by
15
+ runtime. `backend()` returns it: implementation, OpenSSL version, and the
16
+ parameter sets that implementation can express.
17
+
18
+ ## Start and update
19
+
20
+ **Quantum-safe.** Every release asset is signed with ML-DSA-65 by this
21
+ package's own signing path, and the public key is committed to the repository
22
+ rather than served only beside the artefact. Verification is four lines and is
23
+ in [README.md](README.md#verifying-a-release). Releases also carry SLSA
24
+ provenance recording the workflow that built them, and npm provenance is on.
25
+
26
+ **The classical part, stated.** Fetching the release is TLS to the npm registry
27
+ or to GitHub, and that TLS is classical. So is the transport that delivered the
28
+ git clone. This is outside our control and it is a real residual: an attacker
29
+ who could break that transport today could serve a different artefact, and the
30
+ ML-DSA signature is what would catch it, provided the verifier has the key from
31
+ a second path. That proviso is why the key is in the repository and why the
32
+ README says to compare the two copies.
33
+
34
+ ## Operate
35
+
36
+ **No required service connections.** The library performs no network operation
37
+ at any point. There is no licence check, no telemetry, no key server and no
38
+ call home. Nothing in the operating path can be a classical dependency, because
39
+ there is no operating path outside the caller's process.
40
+
41
+ **Key management is the caller's, with one supported escape.** Keys live in the
42
+ caller's memory unless the caller puts them somewhere better. `kxco-pq-hsm`
43
+ generates ML-DSA keys on a PKCS#11 token with `CKA_EXTRACTABLE=false`, at which
44
+ point this library handles verification only and touches no secret. The channel
45
+ to that token is the HSM vendor's, not ours, and its own post-quantum posture
46
+ is a question for the vendor.
47
+
48
+ **Storage and diagnostics.** Neither exists here. The package writes no files
49
+ and keeps no state between calls.
50
+
51
+ ## Protect records
52
+
53
+ Not this package's role, and it does not pretend otherwise. Log integrity and
54
+ timestamping in the KXCO estate are `kxco-pq-audit`: SHA-256 hash-chained
55
+ entries, each ML-DSA-65 signed, with periodic seals anchored on Armature L1 so
56
+ a verifier who does not trust the log operator has an independent time bound.
57
+
58
+ What this package contributes to that is the signature primitive and nothing
59
+ else.
60
+
61
+ ## Enforce policy
62
+
63
+ A prohibited fallback can be rejected. The package's default is to fall back
64
+ from OpenSSL to JavaScript, and that default is correct for almost everyone
65
+ because both produce identical wire bytes. It is wrong in one situation: a
66
+ deployment under a control requiring the cryptography to execute inside a
67
+ validated module, where a silent fallback means the control is not in force and
68
+ nothing says so.
69
+
70
+ `KXCO_PQ_REQUIRE_NATIVE=1`, or `requireNativeBackend([...])` in code, fails the
71
+ process at import instead. Note the limit of the assertion: it establishes that
72
+ OpenSSL is doing the maths, not that the OpenSSL it found is a validated
73
+ module. That is a property of the operator's build and this package cannot see
74
+ it.
75
+
76
+ ## Retain history
77
+
78
+ **Verification of old records: yes.** A signature made by any version of this
79
+ package verifies under any later version. Wire formats have not changed and the
80
+ conformance evidence is regenerated against pinned vectors on every dependency
81
+ bump, which is what would catch a change that broke them.
82
+
83
+ **Long-term trust: not solved here, and it is not the same question.** This
84
+ package has no notion of key validity windows, revocation or trusted
85
+ timestamps, so it can tell you a signature is arithmetically valid and cannot
86
+ tell you the key was still trusted when it was made. Anything that has to hold
87
+ for years needs a time anchor outside the signature. In the KXCO estate that is
88
+ `kxco-pq-audit`'s on-chain seals. For a deployment outside it, that is a gap
89
+ the deployment has to close.
90
+
91
+ ## Customer-selected outputs
92
+
93
+ The library will sign at whatever parameter set the caller selects, Category 1
94
+ included, and will verify Category 1 signatures from a peer. That is a customer
95
+ output, not a statement about this package's own operation, and the two should
96
+ not be reported as one number.
97
+
98
+ The KXCO estate itself signs at Category 3 with ML-DSA-65, including Armature
99
+ L1 from block 0 and every issued KXCO ID. Support for ML-DSA-87 and ML-KEM-1024
100
+ is a support claim and not a CNSA 2.0 compliance claim, for the reasons set out
101
+ in [CONFORMANCE.md](CONFORMANCE.md#what-this-evidence-does-not-cover).
102
+
103
+ ## The gaps, collected
104
+
105
+ Repeated here so they are not spread across six sections:
106
+
107
+ 1. Release transport is classical TLS, mitigated by an ML-DSA signature and a
108
+ second copy of the key, not eliminated.
109
+ 2. The PKCS#11 channel to an HSM is the vendor's, and outside our assessment.
110
+ 3. No long-term validation: no timestamps, no validity windows, no revocation.
111
+ 4. `requireNativeBackend` asserts OpenSSL, not a validated OpenSSL.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.3
4
+
5
+ One resolution fix that consumers could hit, plus the ACVTS tooling and two
6
+ maturity documents. No change to any algorithm, key format or wire format.
7
+
8
+ **Every export subpath now carries a `default` condition.** All twelve declared
9
+ only `types` and `import`, so a consumer resolving outside an import context got
10
+ `ERR_PACKAGE_PATH_NOT_EXPORTED` and could not load the package at all. That is a
11
+ packaging defect rather than a library one: nothing about the code changed, only
12
+ what Node is willing to resolve. It was found from the other side, in a project
13
+ that had to pass `--conditions=import` on every test invocation to work around
14
+ it, and that workaround can come out once this is published.
15
+
16
+ The addition is purely additive. A subpath that resolved before resolves to the
17
+ same file now.
18
+
19
+ **ACVTS tooling is committed rather than kept locally.** The selftest, the
20
+ validation printer and the demo certificate verifier were written against the
21
+ NIST demo submission and existed only on one machine, which makes them evidence
22
+ nobody else can re-run.
23
+
24
+ **Two PQCMM gaps closed and Level 3 re-declared**, against the PKI Consortium's
25
+ Post-Quantum Cryptography Maturity Model, with the self-assessment written down
26
+ rather than asserted.
27
+
28
+ ## 1.7.2
29
+
30
+ Documentation, and one typing fix. No source change to the library and no wire
31
+ format change.
32
+
33
+ **Three documents the package needed and did not have.** Buyer readiness
34
+ assessments ask what the product boundary is, what cryptographic agility it
35
+ actually has, and what constrains its lifecycle. All three had real answers in
36
+ the code and the evidence. None was written down, and an answer that exists
37
+ only in a maintainer's head is not evidence.
38
+
39
+ BOUNDARY.md separates what this package performs from what it depends on and
40
+ what it merely offers a caller. Nothing in src/ opens a socket, so the
41
+ operating path has no classical service dependency at all. Release signing is
42
+ ML-DSA-65 with the key committed here; the transport that delivers the release
43
+ is classical TLS, and that is stated rather than folded into the claim.
44
+
45
+ AGILITY.md separates the three things the term is used for: a replacement plan,
46
+ an implemented mechanism, and an interoperable transition. It also names the
47
+ four kinds of agility this package does not give you, including that a
48
+ parameter set outside the published five needs a release rather than a
49
+ configuration change.
50
+
51
+ LIFECYCLE.md carries supported versions, the runtime ceiling, and the blocking
52
+ supplier dependency with its mitigations ranked. The end-of-support window is
53
+ marked not yet decided rather than invented.
54
+
55
+ All three are copied into the evidence bundle. An assessor reads the bundle,
56
+ not the repository.
57
+
58
+ **requireNativeBackend is now in the typings.** 1.7.0 shipped it, and shipped
59
+ it exported from index.js and documented in the README while declared in
60
+ neither backend.d.ts nor index.d.ts. A TypeScript caller therefore could not
61
+ reach the one control in this package that enforces a no-fallback policy, which
62
+ is precisely the deployment the feature exists for. Runtime behaviour was
63
+ always correct; the type surface hid it.
64
+
3
65
  ## 1.7.1
4
66
 
5
67
  Release integrity. No source change to the library.
@@ -0,0 +1,227 @@
1
+ # Cryptographic inventory
2
+
3
+ Every use case in this package where cryptography is applied, mapped onto the
4
+ ten categories of the PKI Consortium PQCMM cryptographic inventory taxonomy.
5
+
6
+ The model requires that no category is omitted. A category that does not apply
7
+ is marked not applicable with a written reason, because "we do not do that" and
8
+ "we did not look" are indistinguishable from the outside unless the reason is
9
+ recorded. Four of the ten genuinely do not apply to a library with no sockets
10
+ and no persistent state, and saying so in one line each is the honest answer
11
+ rather than a padded one.
12
+
13
+ Scope: `kxco-post-quantum` 1.7.2. `BOUNDARY.md` is the companion document and
14
+ explains the difference between cryptography this package performs, depends on,
15
+ and merely offers to a caller. The distinction matters here: a caller can use
16
+ this package inside a category that the package itself does not implement.
17
+
18
+ ## 1. Data in transit
19
+
20
+ **Not applicable to the library. Applicable to its delivery.**
21
+
22
+ Nothing in `src/` opens a socket. The library performs no network operation at
23
+ any point: no licence check, no telemetry, no key server, no call home. There
24
+ is no transport for it to protect.
25
+
26
+ Its own delivery is a different matter and is not quantum-safe. Fetching the
27
+ package is classical TLS to the npm registry or to GitHub, as was the transport
28
+ that delivered any git clone. That residual is stated in `BOUNDARY.md` and is
29
+ mitigated by an ML-DSA-65 signature over every release asset plus a second copy
30
+ of the public key committed to the repository, so that a verifier is not
31
+ trusting the same channel twice. Mitigated, not eliminated.
32
+
33
+ What the package offers a caller for this category is the ML-KEM key
34
+ encapsulation below, from which a caller may build a transport. Offering a
35
+ primitive is not the same as protecting a transport, and the two should not be
36
+ reported as one.
37
+
38
+ | Use | Algorithm | Quantum-safe |
39
+ |---|---|---|
40
+ | Package delivery | TLS to registry and GitHub, classical | No, mitigated by signature |
41
+ | Primitive offered to callers | ML-KEM-768, ML-KEM-1024 | Yes |
42
+
43
+ ## 2. Data at rest
44
+
45
+ **Not applicable.** The package writes no files and keeps no state between
46
+ calls. There is no disk, database or object storage in its boundary to encrypt.
47
+
48
+ A caller who persists a key or a seed produced by this package is performing
49
+ data-at-rest protection in their own system, with their own choice of
50
+ algorithm, and this package neither sees nor constrains it. The one supported
51
+ route that removes the question is `kxco-pq-hsm`, which generates ML-DSA keys
52
+ on a PKCS#11 token with `CKA_EXTRACTABLE=false`, after which this package
53
+ handles verification only and touches no secret.
54
+
55
+ ## 3. Identity, authentication and certificates
56
+
57
+ **Applicable, and quantum-safe within the package.**
58
+
59
+ No X.509. No certificate chain, no path validation, no revocation. Identity
60
+ here is a raw key plus a key identifier, and the package deliberately does not
61
+ pretend to be a PKI.
62
+
63
+ | Use | Algorithm | Quantum-safe |
64
+ |---|---|---|
65
+ | Signature and verification | ML-DSA-65, ML-DSA-87 (FIPS 204) | Yes |
66
+ | Hash-based alternative | SLH-DSA, ten parameter sets (FIPS 205) | Yes |
67
+ | Key identifier | SHA-256 over the public key, truncated to 16 hex characters (`kid.js`) | Yes, second-preimage bound |
68
+ | Key interchange | JWK `kty: AKP` per RFC 9964, and PKCS#8 seed export | Format, not an algorithm |
69
+ | JWS signing | ML-DSA over the JWS signing input (`jws.js`) | Yes |
70
+ | Webhook delivery signing | HMAC-SHA-256 **and** ML-DSA-65, both (`webhook.js`) | Yes, hybrid |
71
+
72
+ The `kid` is a truncated SHA-256, so it is a 64-bit identifier and not a
73
+ security boundary. It identifies which key to try; it does not authenticate
74
+ anything. Grover halves the preimage work on SHA-256 and the truncation
75
+ dominates that anyway, which is why nothing in the package makes a trust
76
+ decision on a `kid` alone.
77
+
78
+ ## 4. Code, firmware and update signing
79
+
80
+ **Applicable, and quantum-safe.**
81
+
82
+ Every release asset is signed with ML-DSA-65 by this package's own signing
83
+ path, which means the signing of the package is performed by the thing being
84
+ signed. The public key is committed to the repository at
85
+ `release-signing-key.pub.hex` and published as a release asset, so a verifier
86
+ can obtain it by a path other than the one that delivered the artefact.
87
+ Verification is four lines and is in `README.md`.
88
+
89
+ No firmware. The package ships no binary and no native build of its own; the
90
+ native path calls the OpenSSL already present in the host runtime.
91
+
92
+ | Use | Algorithm | Quantum-safe |
93
+ |---|---|---|
94
+ | Release asset signing | ML-DSA-65 | Yes |
95
+ | Signature over the evidence bundle | ML-DSA-65, `.sig` beside each bundle | Yes |
96
+
97
+ ## 5. Key wrapping and key-encryption keys
98
+
99
+ **Applicable in a limited form, and quantum-safe.**
100
+
101
+ The package wraps no keys. It has no KEK hierarchy, no envelope encryption and
102
+ no key store.
103
+
104
+ What it does have is deterministic derivation, which occupies the same place in
105
+ a design without being key wrapping. `keypairFromMaster` derives a per-purpose
106
+ seed from a caller-held master secret using HKDF-SHA-512 with an empty salt and
107
+ a caller-chosen `info` string, then generates the keypair from that seed. A
108
+ master secret therefore stands in the position a KEK would occupy, and its
109
+ protection is entirely the caller's.
110
+
111
+ | Use | Algorithm | Quantum-safe |
112
+ |---|---|---|
113
+ | Seed derivation from a master secret | HKDF-SHA-512 (`derive.js`) | Yes, symmetric |
114
+ | Key encapsulation offered to callers | ML-KEM-768, ML-KEM-1024 (FIPS 203) | Yes |
115
+ | Hybrid key establishment | `deriveSeed` combines an ML-KEM shared secret with a classical secret through the KDF | Yes, holds if either half holds |
116
+
117
+ ## 6. Random number generation and entropy sources
118
+
119
+ **Applicable, and this is the entry a reader should not skim.**
120
+
121
+ **This package generates no randomness in its JavaScript backend.** There is no
122
+ `randomBytes`, no `getRandomValues`, no `randomFillSync` anywhere in `src/`
123
+ outside the native path. Every keypair is produced from a seed the caller
124
+ supplies, either directly through `keypairFromSeed` or derived from a
125
+ caller-held master through `keypairFromMaster`. Seed length is enforced against
126
+ the FIPS parameter set and a wrong length is a `RangeError`, not a silent pad.
127
+
128
+ The consequence is worth stating plainly: **in the JavaScript backend, the
129
+ quality of every private key in this system is a property of the caller's
130
+ entropy source, not of this package.** A caller who supplies a low-entropy seed
131
+ gets a low-entropy key and the library cannot detect it. This is a deliberate
132
+ design choice, because deterministic derivation is what makes the keys
133
+ reproducible and the conformance vectors pinnable, but it relocates a security
134
+ property rather than solving it.
135
+
136
+ The native backend is different. `crypto.generateKeyPairSync` in `_native.node.js`
137
+ delegates to Node and thence to the OpenSSL DRBG, so on that path entropy comes
138
+ from the operating system.
139
+
140
+ | Path | Entropy source | Whose responsibility |
141
+ |---|---|---|
142
+ | JavaScript backend | Caller-supplied seed | The caller |
143
+ | JavaScript backend, derived | HKDF-SHA-512 over a caller-supplied master | The caller |
144
+ | Native backend keygen | OpenSSL DRBG via `crypto.generateKeyPairSync` | The operating system |
145
+
146
+ No category of quantum attack applies to a DRBG in the way it applies to a
147
+ public-key primitive; the risk here is classical and it is entropy starvation.
148
+
149
+ ## 7. Telemetry, logging and audit-trail integrity
150
+
151
+ **Not applicable.** The package emits no telemetry and writes no log. It keeps
152
+ no state between calls, so there is no audit trail of its own to protect.
153
+
154
+ Log integrity in the wider KXCO estate is `kxco-pq-audit`, a separate package:
155
+ SHA-256 hash-chained entries, each signed with ML-DSA-65, with periodic seals
156
+ anchored on Armature L1 so that a verifier who does not trust the log operator
157
+ still has an independent time bound. What this package contributes to that is
158
+ the signature primitive and nothing else.
159
+
160
+ ## 8. Build, CI/CD and supply-chain signing
161
+
162
+ **Applicable, and quantum-safe for what we sign, classical for what GitHub and
163
+ npm sign.** This split is the honest answer and collapsing it would be an
164
+ overclaim.
165
+
166
+ | Use | Algorithm | Quantum-safe |
167
+ |---|---|---|
168
+ | Release assets and evidence bundle | ML-DSA-65, ours | Yes |
169
+ | SLSA provenance attestation | Sigstore, classical (ECDSA and the Fulcio and Rekor chain) | **No** |
170
+ | npm registry signature and provenance | npm's own, classical | **No** |
171
+ | Reproducible build | Not a signature. The published tarball rebuilds bit-for-bit from its own tag, verified in CI on every run | Not applicable |
172
+ | CycloneDX SBOM | Generated by `npm sbom` from the published tree; integrity carried by the ML-DSA signature over the release | Yes, via the release signature |
173
+
174
+ The reproducible build is the part of this category that does not depend on any
175
+ signature algorithm at all, and it is the reason the classical rows above are a
176
+ weaker residual than they look: a verifier who rebuilds from source is not
177
+ trusting Sigstore, npm or us.
178
+
179
+ ## 9. Attestation and remote attestation
180
+
181
+ **Applicable in the supply-chain sense only.**
182
+
183
+ There is no hardware attestation, no TPM quote, no confidential-computing
184
+ report and no remote-attestation protocol in this package.
185
+
186
+ What exists is build attestation: an in-toto statement at
187
+ `evidence.intoto.jsonl` and an evidence manifest that pins the commit, the
188
+ toolchain, the backend, the dependency versions and every step with its exact
189
+ command, result and duration, with a SHA-256 for each file in the bundle. The
190
+ in-toto and SLSA layer is signed classically by Sigstore, as row 2 of category
191
+ 8 records. The manifest and bundle are additionally signed with ML-DSA-65 by us.
192
+
193
+ ## 10. Backup, archival and long-term storage
194
+
195
+ **Applicable as a stated gap, and this is the weakest category.**
196
+
197
+ Verification of old records holds: a signature made by any version of this
198
+ package verifies under any later version. Wire formats have not changed, and
199
+ the conformance evidence is regenerated against pinned vectors on every
200
+ dependency bump, which is the control that would catch a change breaking them.
201
+
202
+ Long-term trust does not hold and is a different question. This package has no
203
+ notion of key validity windows, no revocation and no trusted timestamps. It can
204
+ tell you a signature is arithmetically valid. It cannot tell you the key was
205
+ still trusted when the signature was made. Anything that has to hold for years
206
+ needs a time anchor outside the signature. Inside the KXCO estate that anchor
207
+ is `kxco-pq-audit`'s on-chain seals. Outside it, this is a gap the deployment
208
+ has to close, and this package should not be cited as closing it.
209
+
210
+ ## Summary
211
+
212
+ | # | Category | Status |
213
+ |---|---|---|
214
+ | 1 | Data in transit | Library N/A; delivery is classical TLS, signature-mitigated |
215
+ | 2 | Data at rest | N/A, no persistence |
216
+ | 3 | Identity, authentication, certificates | Quantum-safe, no X.509 |
217
+ | 4 | Code, firmware, update signing | Quantum-safe |
218
+ | 5 | Key wrapping and KEKs | Quantum-safe, no wrapping; master secret is the caller's |
219
+ | 6 | RNG and entropy | **Relocated to the caller** in the JavaScript backend |
220
+ | 7 | Telemetry, logging, audit integrity | N/A, separate package |
221
+ | 8 | Build, CI/CD, supply chain | Split: ours quantum-safe, Sigstore and npm classical |
222
+ | 9 | Attestation | Build attestation only, classically signed at the Sigstore layer |
223
+ | 10 | Backup, archival, long-term | **Gap.** No timestamps, validity windows or revocation |
224
+
225
+ Three residuals carry forward into `PQCMM.md` and `HNDL.md` rather than being
226
+ recorded only here: classical release transport, the classical Sigstore and npm
227
+ signature layer, and the absence of long-term validation.
package/HNDL.md ADDED
@@ -0,0 +1,84 @@
1
+ # HNDL exposure register
2
+
3
+ Harvest-now-decrypt-later exposure for `kxco-post-quantum` 1.7.2, as required
4
+ at Level 3 of the PKI Consortium PQCMM: which data flows handled by the product
5
+ carry a long-lived confidentiality requirement, and which algorithm protects
6
+ each.
7
+
8
+ ## What HNDL is, and why most of this register is short
9
+
10
+ HNDL is a confidentiality attack. An adversary records ciphertext today and
11
+ decrypts it once a cryptographically relevant quantum computer exists. It
12
+ applies to data whose confidentiality must outlast that machine.
13
+
14
+ It does **not** apply to signatures in the same way. A signature forged in 2040
15
+ is worthless against a document verified in 2026, because the verification
16
+ already happened. The quantum risk to a signature scheme is forgery of *future*
17
+ verifications, which is an integrity and non-repudiation problem with a
18
+ different shape and a different deadline. Registers that fold the two together
19
+ overstate their own exposure, and this one keeps them apart.
20
+
21
+ **This package is predominantly a signing library.** Most of what it handles is
22
+ therefore outside HNDL by nature, and the register is short for a real reason
23
+ rather than an incomplete one. Where confidentiality genuinely is at stake, the
24
+ rows below say so.
25
+
26
+ ## Register
27
+
28
+ | # | Data flow | Long-lived confidentiality? | Protected by | Quantum-safe | Residual |
29
+ |---|---|---|---|---|---|
30
+ | 1 | Private keys and seeds held in caller memory | **Yes, for the key's whole life** | Nothing in this package. Caller's process isolation | Not a cryptographic control | **The primary exposure.** See below |
31
+ | 2 | Master secret used by `keypairFromMaster` | **Yes, and it is worse than a single key** | Nothing in this package. Caller's storage | Not a cryptographic control | Compromise derives every child key, past and future |
32
+ | 3 | ML-KEM shared secret, in transit | Depends entirely on the caller's use | ML-KEM-768 or ML-KEM-1024 (FIPS 203) | **Yes** | The classical half of a hybrid pairing, if the caller adds one |
33
+ | 4 | Payload a caller encrypts under an ML-KEM-derived key | Caller's to determine | The caller's AEAD, not ours | Symmetric, so Grover only | Key size is the caller's choice; this package does not perform the encryption |
34
+ | 5 | Package delivery over TLS to npm or GitHub | **No** | Classical TLS | No | Confidentiality is irrelevant: the package is public. Integrity is the concern and ML-DSA-65 covers it |
35
+ | 6 | Signatures, JWS, webhook envelopes | **No** | ML-DSA-65 or SLH-DSA, plus HMAC-SHA-256 on webhooks | Yes | Integrity risk, not HNDL. Recorded here only so its absence is deliberate |
36
+ | 7 | Evidence bundle and SBOM | **No** | Published deliberately | Not applicable | Public by design |
37
+ | 8 | Keys held on a PKCS#11 token via `kxco-pq-hsm` | Yes, but outside this boundary | `CKA_EXTRACTABLE=false` | Hardware control | The vendor's channel to the token is the vendor's, per `BOUNDARY.md` |
38
+
39
+ ## The two rows that matter
40
+
41
+ **Rows 1 and 2 are the real HNDL exposure of this product, and neither is
42
+ solved by a post-quantum algorithm.**
43
+
44
+ A private key is confidential for its entire life, which is exactly the
45
+ long-lived requirement HNDL describes. An adversary who harvests a stored
46
+ ML-DSA private key today does not need a quantum computer at all; they need the
47
+ key. The algorithm being post-quantum protects the signature against
48
+ cryptanalysis, not the key against theft. So the strongest post-quantum
49
+ posture in this package does nothing for its largest confidentiality asset.
50
+
51
+ Row 2 is worse in one specific way. A master secret under HKDF-SHA-512
52
+ regenerates every derived keypair, for every `info` string, past and future. It
53
+ is a single point whose compromise is not bounded in time. That is the correct
54
+ tradeoff for reproducible keys and pinnable conformance vectors, and it is the
55
+ right design, but the concentration of risk should be visible rather than
56
+ implied.
57
+
58
+ Neither row has a cryptographic mitigation available inside a library. What
59
+ exists is the escape in row 8: `kxco-pq-hsm` generates keys on a PKCS#11 token
60
+ with `CKA_EXTRACTABLE=false`, after which this package handles verification
61
+ only and touches no secret. **A deployment with a long-lived confidentiality
62
+ requirement on its keys should be using that path, and this register exists in
63
+ part to say so.**
64
+
65
+ ## What this register does not cover
66
+
67
+ Callers. This package has no visibility into what a caller encrypts, how long
68
+ they need it confidential, or where they put a key. Rows 3 and 4 are marked as
69
+ the caller's to determine because they genuinely are, and a register claiming
70
+ otherwise would be inventing knowledge it does not have.
71
+
72
+ The absence of long-term validation, recorded as category 10 of
73
+ `CRYPTO-INVENTORY.md`, is an adjacent gap rather than an HNDL one. No
74
+ timestamps, no validity windows, no revocation. It bears on whether an old
75
+ signature can still be trusted, not on whether old ciphertext can be read.
76
+
77
+ ## Review
78
+
79
+ This register is a claim about a shipped version and it ages. It should be
80
+ re-read whenever a new cryptographic use case enters the package, whenever a
81
+ category in `CRYPTO-INVENTORY.md` changes status, and on any change to how
82
+ seeds or master secrets are handled.
83
+
84
+ Last reviewed against 1.7.2 on 2026-09-09.
package/LIFECYCLE.md ADDED
@@ -0,0 +1,124 @@
1
+ # Lifecycle, ceiling and dependencies
2
+
3
+ The questions a buyer needs answered before committing to a migration date,
4
+ rather than after. Maturity describes what has been achieved; none of it says
5
+ whether the thing will still be maintained, how far it can go without new
6
+ hardware, or what could block it. Those are here.
7
+
8
+ ## Guidance followed
9
+
10
+ FIPS 203, 204 and 205 for the primitives, verified against NIST's own ACVP
11
+ vectors rather than asserted. RFC 9964 for the JWS algorithm names. RFC 5869
12
+ for HKDF. Where a standard is supported but compliance is a property of a
13
+ deployment rather than of this package, the distinction is stated instead of
14
+ blurred: see the CNSA 2.0 entry in
15
+ [CONFORMANCE.md](CONFORMANCE.md#what-this-evidence-does-not-cover).
16
+
17
+ Risk review happened before the evidence, not after it:
18
+ [THREAT-MODEL.md](THREAT-MODEL.md) names what is in scope, what is out, and
19
+ where the residual risk actually sits, which is a long-lived signing key in the
20
+ memory of a general-purpose process.
21
+
22
+ Update and agility needs are in [AGILITY.md](AGILITY.md), including the
23
+ mechanisms this package does not have.
24
+
25
+ ## Supported versions
26
+
27
+ One line, moving forward. Releases run v1.x in sequence with no maintenance
28
+ branches, and fixes land in the next release rather than being backported.
29
+ Anyone on an older 1.x upgrades forward to receive a fix.
30
+
31
+ Upgrading forward is intended to be cheap and is treated as a compatibility
32
+ obligation: signatures made by earlier versions verify under later ones, and
33
+ [MIGRATION.md](MIGRATION.md#migrating-between-versions) records what changed at
34
+ each step where a caller had to do anything.
35
+
36
+ **Not yet decided:** a formal end-of-support window, expressed in months rather
37
+ than in practice. Until it is decided, the honest statement is the one above,
38
+ which describes what happens rather than what is promised. A buyer who needs a
39
+ contractual window should ask for one rather than infer it.
40
+
41
+ ## Ceiling without hardware replacement
42
+
43
+ There is no hardware ceiling in this package. It is software, the highest
44
+ parameter sets are already shipped rather than planned, and Category 5 needs no
45
+ different machine than Category 3: `mlDsa87` and `mlKem1024` are published
46
+ entry points today.
47
+
48
+ The ceiling that does exist is the runtime, and it is a performance ceiling
49
+ rather than a capability one:
50
+
51
+ | Runtime | Backend | Effect |
52
+ |---|---|---|
53
+ | Node 24 and later | OpenSSL 3.5 primitives | Roughly 4x to 8x faster, measured per operation in [BENCHMARKS.md](BENCHMARKS.md) |
54
+ | Node 20.19 to 23, browsers | JavaScript | Every algorithm available, identical wire bytes, slower |
55
+
56
+ Nothing becomes unavailable on the slower path. If a request budget cannot
57
+ absorb the JavaScript figures, the fix is a Node upgrade, which is not a
58
+ hardware replacement.
59
+
60
+ Two real ceilings sit outside this package and belong to whatever it is
61
+ deployed with. An HSM can only perform the algorithms its firmware implements,
62
+ and firmware is where hardware replacement genuinely appears; that question
63
+ belongs to `kxco-pq-hsm` and the token vendor. Signature size is the other:
64
+ ML-DSA-65 signatures are far larger than ECDSA, and a protocol with a fixed
65
+ field or an MTU assumption may need changing. [MIGRATION.md](MIGRATION.md) puts
66
+ proving that at stage 1 for exactly this reason.
67
+
68
+ ## Blocking supplier dependency
69
+
70
+ **The dependency.** `@noble/post-quantum`, pinned at exactly 0.7.0, supplies
71
+ the FIPS 203/204/205 arithmetic. This package does not reimplement it.
72
+
73
+ **Why it is a blocker and not just a dependency.** It is the one package in the
74
+ tree with no independent review. The maintainer's own self-audit covers 0.6.1,
75
+ which is not the version shipped. Version 0.7.1 regressed nine NIST SLH-DSA
76
+ verification vectors that 0.7.0 passes, shipped in this package's 1.5.1 and was
77
+ reverted in 1.5.2. So the supply risk here is demonstrated rather than
78
+ theoretical.
79
+
80
+ **Mitigations, in the order they actually help.**
81
+
82
+ 1. *Evidence instead of trust.* Every parameter set is checked against NIST's
83
+ ACVP vectors and cross-checked against liboqs, Bouncy Castle and two Python
84
+ implementations in both directions. That regression was caught by this
85
+ harness within hours of the dependency's release, which is the concrete
86
+ demonstration that the control works.
87
+ 2. *A second implementation on the same wire format.* On Node 24 and later the
88
+ OpenSSL backend performs ML-DSA, ML-KEM and SLH-DSA for the sets OpenSSL
89
+ expresses, and the dependency is not doing that arithmetic at all. The
90
+ exception is precise and worth stating: a FIPS 204 context string takes the
91
+ JavaScript path, because OpenSSL does not express it. So the dependency is
92
+ reduced on modern runtimes rather than removed.
93
+ 3. *An exact pin, enforced.* No ranges in `dependencies`, and the audit harness
94
+ fails the build on one. 0.7.1 is separately blocked in dependabot so it
95
+ cannot return through an automated bump.
96
+ 4. *A conformance gate on every bump.* A change to this dependency is a change
97
+ to the primitives, so it is not merged on a green test run. The full ACVP
98
+ and interoperability evidence has to regenerate clean.
99
+
100
+ **Milestone.** The route off this being a single point of review is external
101
+ audit, and that is on the roadmap rather than done. See below.
102
+
103
+ **Second-order dependency, disclosed.** `@noble/curves` is not imported by name
104
+ anywhere in this package and is on the hot path regardless: both ML-DSA and
105
+ ML-KEM reach it through the primitives. That was established by a reachability
106
+ walk rather than by reading manifests, and it is in
107
+ [DEPENDENCIES.md](DEPENDENCIES.md). It is the most heavily reviewed package in
108
+ the tree.
109
+
110
+ ## Roadmap status, beside the maturity result
111
+
112
+ The commitments and what has not happened yet are in
113
+ [AUDIT.md](AUDIT.md#3-audit-roadmap): an external auditor for the wrapper
114
+ integration patterns, a public bug bounty, and a FIPS 140-3 CMVP application
115
+ for a module deployment using this library with an HSM. Dates depend on
116
+ capacity; the order is committed.
117
+
118
+ Read that table beside any maturity claim, not after it. Nothing on it has been
119
+ delivered, and a roadmap entry is a vendor commitment rather than a result.
120
+
121
+ ## Correcting this document
122
+
123
+ If a figure or a pin here does not match the tree, that is a defect worth
124
+ reporting through [SECURITY.md](SECURITY.md).
package/PQCMM.md ADDED
@@ -0,0 +1,164 @@
1
+ # PQC Maturity Model self-assessment
2
+
3
+ PKI Consortium PQC Maturity Model (PQCMM), assessed against the model as
4
+ published at <https://pkic.org/wg/pqc/pqcmm/>.
5
+
6
+ **Declared level: 3 (Advanced).**
7
+
8
+ A self-assessment is indicative, not authoritative. It is the vendor's own view
9
+ and it has not been independently verified. The model says relying parties
10
+ should treat a self-assessed level as a signal rather than a guarantee, and that
11
+ is how this document should be read. Where a criterion is not met, this document
12
+ says so and names what is missing rather than reporting a partial as a pass.
13
+
14
+ ## Scope
15
+
16
+ | Field | Value |
17
+ |---|---|
18
+ | Product | `kxco-post-quantum` |
19
+ | Version assessed | 1.7.2 |
20
+ | Commit | `90b5778b4d19ed0f78715e143a6a50b9170b70f9`, clean tree |
21
+ | Registry | <https://www.npmjs.com/package/kxco-post-quantum> |
22
+ | Licence | Apache-2.0 |
23
+ | Assurance method | Self-assessment |
24
+ | Date | 2026-09-09 |
25
+
26
+ A PQCMM assessment applies to a single product. The other `kxco-pq-*` packages
27
+ depend on this one and are not covered here; each needs its own assessment.
28
+
29
+ ## Evidence available to an assessor
30
+
31
+ Everything cited below is a permanent unauthenticated URL. No account, no
32
+ request to us, no expiring artifact.
33
+
34
+ | Artefact | Path under the release |
35
+ |---|---|
36
+ | Evidence manifest | `releases/latest/download/manifest-node24.x.json` |
37
+ | Evidence bundle | `releases/latest/download/evidence-node24.x.zip` |
38
+ | CycloneDX SBOM | `releases/latest/download/sbom.cyclonedx.json` |
39
+ | In-toto attestation | `releases/latest/download/evidence.intoto.jsonl` |
40
+ | Release signing key | `releases/latest/download/release-signing-key.pub.hex` |
41
+
42
+ Base: `https://github.com/KnightsbridgeAIQ/kxco-post-quantum/`
43
+
44
+ Two documents in the repository carry the Level 3 evidence and are cited
45
+ throughout: `CRYPTO-INVENTORY.md` for the ten-category cryptographic inventory,
46
+ and `HNDL.md` for the harvest-now-decrypt-later exposure register.
47
+
48
+ The manifest pins the commit, the toolchain (Node 24.20.0, OpenSSL 3.5.7), the
49
+ backend, the dependency versions, and every step with its exact command, its
50
+ result and its duration. Each file in the bundle carries its SHA-256. The
51
+ conformance and interop runs are reproducible from the commands recorded in the
52
+ manifest rather than taken on our word.
53
+
54
+ ## Level 1, Initial: MET
55
+
56
+ | # | Criterion | Answer | Evidence |
57
+ |---|---|---|---|
58
+ | 1 | A quantum-safe algorithm in a released build | Yes | ML-DSA-65 (FIPS 204), ML-KEM-768 (FIPS 203), SLH-DSA-SHA2-192s (FIPS 205) in 1.7.2 on the public npm stable channel. Category 5 sets ML-DSA-87 and ML-KEM-1024 also ship. |
59
+ | 2 | The feature can be enabled | Yes | Available on import. No flag, no custom build, no source modification. |
60
+ | 3 | Documented for evaluation | Yes | `README.md`, with a per-function API reference. |
61
+
62
+ Release channel is stable and public, available to all consumers, with no
63
+ programme enrolment. Ten SLH-DSA parameter sets are exposed, not one.
64
+
65
+ ## Level 2, Foundational: MET
66
+
67
+ | # | Criterion | Answer | Evidence |
68
+ |---|---|---|---|
69
+ | 1 | Quantum-safe algorithms in core production functionality | Yes | Signing, key encapsulation, key derivation and fingerprinting are the product's core, not a preview channel. |
70
+ | 2 | Compatibility with a published standard from a recognised body | Yes | NIST FIPS 203, 204 and 205. |
71
+ | 3a | Standards-conformance testing | Yes | 2,103 NIST ACVP vectors: 1,793 passed, 0 failed, 310 skipped. Every skip is this library refusing a pre-hash weaker than the parameter set, listed individually with its reason. A skip is not counted as a pass. See `CONFORMANCE.md` and manifest steps `acvp`, `acvp-fips205`, `acvp-fips205-siggen`. |
72
+ | 3b | Interoperability against an independent conformant implementation | Yes, four of them | 225 cross-implementation checks, 0 failed, both directions, with negative controls, against liboqs, Bouncy Castle and two pure-Python implementations. Run against both backends. Manifest step `interop`. |
73
+ | 3c | Functional security testing as configured for production | Yes | `npm test` recorded in the manifest, plus the fuzz corpus under `fuzz/`. |
74
+ | 3d | Smoke performance test under representative load | Yes | `BENCHMARKS.md`: per-algorithm p95 and p99 on both backends, on x86-64 and arm64, plus memory. |
75
+ | 4 | Documented for production use including known limitations | Yes | `THREAT-MODEL.md` for what is and is not defended, `BOUNDARY.md` for which cryptography this package performs versus depends on versus offers, plus `LIFECYCLE.md` and `MIGRATION.md`. |
76
+
77
+ Level 2 asks for CAVP or ACVTS results "or equivalent". Ours are the NIST ACVP
78
+ vectors run by us and published with the commands to reproduce them. That is
79
+ equivalent evidence, not a NIST certificate. **There is no CAVP certificate
80
+ number for this product.** An assessor checking the CAVP or CMVP database will
81
+ find nothing, and should record that.
82
+
83
+ ## Level 3, Advanced: MET
84
+
85
+ | # | Criterion | Answer | Evidence |
86
+ |---|---|---|---|
87
+ | 1 | Full cryptographic inventory across every applicable taxonomy category | Yes | `CRYPTO-INVENTORY.md` covers all ten categories. Four are marked not applicable with a written reason; none is omitted. |
88
+ | 2 | Non-quantum-safe features documented and flagged | Yes | `BOUNDARY.md`, "The gaps, collected", and the classical rows of `CRYPTO-INVENTORY.md` categories 1, 8 and 9. |
89
+ | 3 | SBOM or equivalent component inventory | Yes | CycloneDX SBOM as a release asset at a permanent URL, generated by `npm sbom` from the tree that was published. Manifest step `sbom`. |
90
+ | 4 | Crypto agility, two algorithms selectable in the same role | Yes | ML-DSA-65 and SLH-DSA-SHA2-192s are both available in the signature role by configuration, which is two different algorithms rather than two parameter sets of one. `AGILITY.md` separates the replacement plan, the implemented mechanism and the interoperable transition, and states what the package does not give you. |
91
+ | 5 | Documented HNDL exposure register | Yes | `HNDL.md`. Eight data flows, each with its confidentiality horizon, its protecting algorithm and its residual. |
92
+
93
+ Writing the inventory against the taxonomy produced two findings that this
94
+ package's own audit documents had not stated, and both are recorded rather than
95
+ smoothed over.
96
+
97
+ **The library generates no randomness in its JavaScript backend.** Every
98
+ keypair comes from a caller-supplied seed, or from a master secret through
99
+ HKDF-SHA-512. So in that backend the entropy quality of every private key is a
100
+ property of the caller, not of this package, and the package cannot detect a
101
+ weak seed. The native backend differs: `crypto.generateKeyPairSync` uses the
102
+ OpenSSL DRBG. `CRYPTO-INVENTORY.md` category 6.
103
+
104
+ **The largest HNDL exposure in this product has no post-quantum mitigation.**
105
+ Private keys and, more sharply, the master secret behind `keypairFromMaster`
106
+ are confidential for their whole life, which is precisely the long-lived
107
+ requirement HNDL describes. A post-quantum algorithm protects the signature
108
+ against cryptanalysis and does nothing for the key against theft. The available
109
+ answer is the PKCS#11 path in `kxco-pq-hsm`, not an algorithm choice here.
110
+ `HNDL.md`, rows 1 and 2.
111
+
112
+ ## Level 4, Managed: NOT MET
113
+
114
+ | # | Criterion | Answer | Evidence or gap |
115
+ |---|---|---|---|
116
+ | 1 | CBOM maintained | **No** | The product publishes an SBOM, not a CBOM. `CRYPTO-INVENTORY.md` now records each algorithm with its protocol context and usage purpose, which is the human-readable half of what a CBOM carries, but it is prose rather than a machine-readable CBOM and key sizes are stated by parameter set rather than per field. Closing this means emitting a CycloneDX CBOM as a release asset, which is a build change rather than a document. |
117
+ | 2 | Zero-legacy capability across every in-scope component | **Not determined** | Not assessed against the model's wording, which extends to boot, firmware update signing, hardware-bound operations and internal diagnostics. Claiming it without that determination would be an overclaim. |
118
+ | 3 | Symmetric and hash strengths adequate beyond CRQC availability | Yes | SHA-256 for key identifiers and webhook HMAC, SHA-512 under HKDF for seed derivation, via `@noble/hashes` 2.4.0. Per-use detail in `CRYPTO-INVENTORY.md`. |
119
+ | 4 | Hybrid and composite support documented, with contexts | Partial | Hybrid is documented: `webhook` performs HMAC plus ML-DSA-65 delivery signing, and `deriveSeed` exists to combine an ML-KEM shared secret with a classical secret through a KDF. Composite, in the algorithm-fused sense, is not supported and is not currently stated as unsupported. |
120
+
121
+ ## Level 5, Optimized: NOT MET
122
+
123
+ | # | Criterion | Answer | Evidence or gap |
124
+ |---|---|---|---|
125
+ | 1 | Quantum-safe by default, legacy requires explicit enablement | Yes | There is no classical algorithm to fall back to. The default parameter set is Category 3. |
126
+ | 2 | Benchmarked and tuned against operational requirements | Yes | `BENCHMARKS.md`. Two figures stated rather than hidden: ML-DSA signing keeps a rejection-sampling tail on either backend, and SLH-DSA-SHA2-192s signs in seconds rather than milliseconds. |
127
+ | 3 | Primarily follows standards from a recognised body | Yes | FIPS 203, 204 and 205. |
128
+ | 4 | **Independently verified or certified** | **No** | No FIPS 140 validation. No Common Criteria evaluation. The underlying `@noble/post-quantum` 0.7.0 has no published third-party formal security analysis covering the implemented PQC algorithms, so the library-based route in the model's wording is not available either. |
129
+
130
+ Criterion 4 is the binding constraint and it cannot be closed by documentation.
131
+ It requires a paid engagement: a CAVP or CMVP validation through an accredited
132
+ laboratory, a Common Criteria evaluation, or a published independent audit of
133
+ the cryptographic implementation.
134
+
135
+ ## Assessment methodology record
136
+
137
+ The model requires this record to accompany an assessment.
138
+
139
+ - **Evidence method.** Documentation review, plus the published evidence
140
+ manifest, plus verification of the licence state of every published package
141
+ against the public npm registry on 2026-09-09.
142
+ - **Cryptographic library.** `@noble/post-quantum` 0.7.0 and `@noble/hashes`
143
+ 2.4.0 in the JavaScript backend; `node:crypto` on OpenSSL 3.5.7 in the native
144
+ backend. Both versions are recorded in the manifest. `@noble/post-quantum` is
145
+ maintained, and its PQC support is confirmed from its public repository. It
146
+ has **not** been independently audited. Version 0.7.1 is deliberately not
147
+ adopted because it regressed SLH-DSA, so the dependency is pinned at 0.7.0.
148
+ - **Reproduction attempts.** All conformance, interop, test and SBOM steps in
149
+ the manifest ran in CI from a clean tree at commit `90b5778`, on Node
150
+ 24.20.0, linux-x64, on 2026-09-08. Every step reports `ok: true`, and
151
+ `sampling.full` is `true`.
152
+ - **Public database checks.** npm registry, all sixteen published `kxco-*`
153
+ packages, 2026-09-09: every one reports `Apache-2.0`. NIST CAVP and CMVP: no
154
+ certificate exists for this product, confirmed rather than assumed.
155
+ - **Unverified claims.** Every criterion answered Yes above is vendor-stated and
156
+ not independently verified. That is what a self-assessment is. The manifest
157
+ and the bundle exist so that an assessor can convert these into verified
158
+ findings without asking us for anything.
159
+
160
+ ## Corrections
161
+
162
+ If any statement here is wrong, it should be corrected here first, and the
163
+ correction dated. Facts about this package that are commonly recorded wrongly,
164
+ with the one-line check for each, are listed at the top of `README.md`.
package/README.md CHANGED
@@ -41,6 +41,9 @@ languages are **JavaScript and C** (OpenSSL 3.5 on Node 24+).
41
41
  - [THREAT-MODEL.md](./THREAT-MODEL.md): what this defends against and what it does not. Read the side-channel section before deciding where a signing key lives.
42
42
  - [MIGRATION.md](./MIGRATION.md): moving an RSA or ECDSA system across, and moving between versions of this package.
43
43
  - [SECURITY.md](./SECURITY.md): reporting, release integrity, and the dependency policy.
44
+ - [AGILITY.md](./AGILITY.md): what has to change when the algorithm changes. The replacement plan, the mechanisms that exist today, the transition peers can follow, and the four kinds of agility this package does not give you.
45
+ - [BOUNDARY.md](./BOUNDARY.md): which cryptography this package performs, which it depends on, and which it merely offers to a caller. Release signing is ML-DSA-65; the transport that delivers the release is classical TLS, and that is stated rather than folded into the claim.
46
+ - [LIFECYCLE.md](./LIFECYCLE.md): supported versions, the runtime ceiling, and the one blocking supplier dependency with its mitigations. Read the roadmap beside a maturity claim, not after it.
44
47
  - **Every release is reproducible and attested.** The published tarball rebuilds bit-for-bit from its own tag, verified in CI on every run, and each release carries a SLSA provenance attestation plus a CycloneDX SBOM at a permanent unauthenticated URL. A provenance attestation says a build happened in CI; the reproducible build says the artefact is the source. They are different claims and both are checkable without asking us for anything.
45
48
 
46
49
  ---
@@ -333,7 +336,7 @@ Install this package directly when you need ML-DSA or ML-KEM without the rest of
333
336
 
334
337
  ## Security
335
338
 
336
- Cryptographic operations delegate entirely to [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum) and [`@noble/hashes`](https://github.com/paulmillr/noble-hashes) — this package does not reimplement any NIST primitive. `@noble/hashes` falls under Cure53's 2023 audit of the `@noble` ecosystem (`ciphers`, `curves`, `hashes`); `@noble/post-quantum` was **not** in that audit's scope and has been self-audited by its maintainer. See [AUDIT.md](./AUDIT.md) for the full posture.
339
+ Cryptographic operations delegate entirely to [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum) and [`@noble/hashes`](https://github.com/paulmillr/noble-hashes); this package does not reimplement any NIST primitive. `@noble/post-quantum` has never been audited by a third party. It has been self-audited by its maintainer (v0.6.1, April 2026). The other Noble packages were audited separately, at different dates, and none of those engagements covered the post-quantum package: `@noble/hashes` by Cure53 in January 2022, `@noble/curves` by Trail of Bits in February 2023, Kudelski Security in September 2023 and Cure53 in September 2024, and `@noble/ciphers` by Cure53 in September 2024. See [AUDIT.md](./AUDIT.md) for the full posture.
337
340
 
338
341
  To report a vulnerability: [open a private security advisory](https://github.com/KnightsbridgeAIQ/kxco-post-quantum/security/advisories/new) or email **john@knightsbridgelaw.com**. Acknowledgement within 2 business days, triage decision within 5. Full policy, including safe harbour for good-faith research: <https://kxco.ai/security>.
339
342
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxco-post-quantum",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "ML-DSA-65, ML-KEM-768 and SLH-DSA-SHA2-192s with key fingerprinting. OpenSSL 3.5 primitives on Node 24+, JavaScript elsewhere. 2,103 NIST ACVP vectors: 1,793 passed, 0 failed, 310 skipped. 225 interop checks, 0 failed. Reproducible builds and provenance.",
5
5
  "keywords": [
6
6
  "post-quantum",
@@ -12,6 +12,7 @@
12
12
  "dilithium",
13
13
  "kyber",
14
14
  "nist",
15
+ "acvp",
15
16
  "fips-203",
16
17
  "fips-204",
17
18
  "fips-205",
@@ -61,61 +62,79 @@
61
62
  "exports": {
62
63
  ".": {
63
64
  "types": "./src/index.d.ts",
64
- "import": "./src/index.js"
65
+ "import": "./src/index.js",
66
+ "default": "./src/index.js"
65
67
  },
66
68
  "./ml-dsa": {
67
69
  "types": "./src/ml-dsa.d.ts",
68
- "import": "./src/ml-dsa.js"
70
+ "import": "./src/ml-dsa.js",
71
+ "default": "./src/ml-dsa.js"
69
72
  },
70
73
  "./ml-dsa-87": {
71
74
  "types": "./src/ml-dsa-87.d.ts",
72
- "import": "./src/ml-dsa-87.js"
75
+ "import": "./src/ml-dsa-87.js",
76
+ "default": "./src/ml-dsa-87.js"
73
77
  },
74
78
  "./ml-kem": {
75
79
  "types": "./src/ml-kem.d.ts",
76
- "import": "./src/ml-kem.js"
80
+ "import": "./src/ml-kem.js",
81
+ "default": "./src/ml-kem.js"
77
82
  },
78
83
  "./ml-kem-1024": {
79
84
  "types": "./src/ml-kem-1024.d.ts",
80
- "import": "./src/ml-kem-1024.js"
85
+ "import": "./src/ml-kem-1024.js",
86
+ "default": "./src/ml-kem-1024.js"
81
87
  },
82
88
  "./slh-dsa": {
83
89
  "types": "./src/slh-dsa.d.ts",
84
- "import": "./src/slh-dsa.js"
90
+ "import": "./src/slh-dsa.js",
91
+ "default": "./src/slh-dsa.js"
85
92
  },
86
93
  "./derive": {
87
94
  "types": "./src/derive.d.ts",
88
- "import": "./src/derive.js"
95
+ "import": "./src/derive.js",
96
+ "default": "./src/derive.js"
89
97
  },
90
98
  "./webhook": {
91
99
  "types": "./src/webhook.d.ts",
92
- "import": "./src/webhook.js"
100
+ "import": "./src/webhook.js",
101
+ "default": "./src/webhook.js"
93
102
  },
94
103
  "./kid": {
95
104
  "types": "./src/kid.d.ts",
96
- "import": "./src/kid.js"
105
+ "import": "./src/kid.js",
106
+ "default": "./src/kid.js"
97
107
  },
98
108
  "./seed": {
99
109
  "types": "./src/seed.d.ts",
100
- "import": "./src/seed.js"
110
+ "import": "./src/seed.js",
111
+ "default": "./src/seed.js"
101
112
  },
102
113
  "./jws": {
103
114
  "types": "./src/jws.d.ts",
104
- "import": "./src/jws.js"
115
+ "import": "./src/jws.js",
116
+ "default": "./src/jws.js"
105
117
  },
106
118
  "./backend": {
107
119
  "types": "./src/backend.d.ts",
108
- "import": "./src/backend.js"
120
+ "import": "./src/backend.js",
121
+ "default": "./src/backend.js"
109
122
  }
110
123
  },
111
124
  "files": [
125
+ "AGILITY.md",
112
126
  "BENCHMARKS.md",
127
+ "BOUNDARY.md",
113
128
  "CHANGELOG.md",
114
129
  "CONFORMANCE.md",
130
+ "CRYPTO-INVENTORY.md",
115
131
  "DEPENDENCIES.md",
132
+ "HNDL.md",
116
133
  "LICENCE-PRODUCT.md",
117
134
  "LICENSE",
135
+ "LIFECYCLE.md",
118
136
  "MIGRATION.md",
137
+ "PQCMM.md",
119
138
  "README.md",
120
139
  "SECURITY.md",
121
140
  "THREAT-MODEL.md",
@@ -129,6 +148,7 @@
129
148
  "@noble/post-quantum": "0.7.0"
130
149
  },
131
150
  "scripts": {
151
+ "fuzz": "node fuzz/fuzz.mjs",
132
152
  "test": "node --test test/basic.test.js && node --test test/backend.test.js && node --test test/context.test.js && node --test test/category5.test.js && node --test test/edge-cases.test.js && node --test test/seed.test.js && node --test test/browser-smoke.test.js && node test/run-vectors.js",
133
153
  "test:vectors": "node test/run-vectors.js",
134
154
  "generate:vectors": "node test/generate-vectors.js > test/vectors.json",
package/src/backend.d.ts CHANGED
@@ -14,3 +14,21 @@ export function backend(): BackendReport
14
14
 
15
15
  /** Whether a parameter set runs natively here, e.g. isNative('ML-DSA-65'). */
16
16
  export function isNative(alg: string): boolean
17
+
18
+ /**
19
+ * Refuse to run unless the cryptography is executing in the native backend.
20
+ *
21
+ * An assertion, not a switch: it cannot change which implementation signs, it
22
+ * stops a process that has silently landed on the JavaScript backend when the
23
+ * operator required otherwise. Also settable from the environment with
24
+ * `KXCO_PQ_REQUIRE_NATIVE=1`, which applies the check at import.
25
+ *
26
+ * Asserts that OpenSSL is doing the maths. Whether that OpenSSL is a
27
+ * FIPS-validated module is a property of the operator's build and is not
28
+ * visible from here.
29
+ *
30
+ * @param algorithms Parameter sets that must run natively. Omit to require
31
+ * only that the native backend is present.
32
+ * @throws Error with `code: 'ERR_KXCO_PQ_BACKEND'` when the requirement fails.
33
+ */
34
+ export function requireNativeBackend(algorithms?: string[]): BackendReport
package/src/index.d.ts CHANGED
@@ -16,4 +16,4 @@ export * as webhook from './webhook.js'
16
16
  export * as seed from './seed.js'
17
17
  /** Compact JWS using the RFC 9964 algorithm names. Format only, no network. */
18
18
  export * as jws from './jws.js'
19
- export { backend, isNative } from './backend.js'
19
+ export { backend, isNative, requireNativeBackend } from './backend.js'