kxco-post-quantum 1.7.1 → 1.7.2
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 +115 -0
- package/BOUNDARY.md +111 -0
- package/CHANGELOG.md +37 -0
- package/LIFECYCLE.md +124 -0
- package/README.md +3 -0
- package/package.json +5 -1
- package/src/backend.d.ts +18 -0
- package/src/index.d.ts +1 -1
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,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.7.2
|
|
4
|
+
|
|
5
|
+
Documentation, and one typing fix. No source change to the library and no wire
|
|
6
|
+
format change.
|
|
7
|
+
|
|
8
|
+
**Three documents the package needed and did not have.** Buyer readiness
|
|
9
|
+
assessments ask what the product boundary is, what cryptographic agility it
|
|
10
|
+
actually has, and what constrains its lifecycle. All three had real answers in
|
|
11
|
+
the code and the evidence. None was written down, and an answer that exists
|
|
12
|
+
only in a maintainer's head is not evidence.
|
|
13
|
+
|
|
14
|
+
BOUNDARY.md separates what this package performs from what it depends on and
|
|
15
|
+
what it merely offers a caller. Nothing in src/ opens a socket, so the
|
|
16
|
+
operating path has no classical service dependency at all. Release signing is
|
|
17
|
+
ML-DSA-65 with the key committed here; the transport that delivers the release
|
|
18
|
+
is classical TLS, and that is stated rather than folded into the claim.
|
|
19
|
+
|
|
20
|
+
AGILITY.md separates the three things the term is used for: a replacement plan,
|
|
21
|
+
an implemented mechanism, and an interoperable transition. It also names the
|
|
22
|
+
four kinds of agility this package does not give you, including that a
|
|
23
|
+
parameter set outside the published five needs a release rather than a
|
|
24
|
+
configuration change.
|
|
25
|
+
|
|
26
|
+
LIFECYCLE.md carries supported versions, the runtime ceiling, and the blocking
|
|
27
|
+
supplier dependency with its mitigations ranked. The end-of-support window is
|
|
28
|
+
marked not yet decided rather than invented.
|
|
29
|
+
|
|
30
|
+
All three are copied into the evidence bundle. An assessor reads the bundle,
|
|
31
|
+
not the repository.
|
|
32
|
+
|
|
33
|
+
**requireNativeBackend is now in the typings.** 1.7.0 shipped it, and shipped
|
|
34
|
+
it exported from index.js and documented in the README while declared in
|
|
35
|
+
neither backend.d.ts nor index.d.ts. A TypeScript caller therefore could not
|
|
36
|
+
reach the one control in this package that enforces a no-fallback policy, which
|
|
37
|
+
is precisely the deployment the feature exists for. Runtime behaviour was
|
|
38
|
+
always correct; the type surface hid it.
|
|
39
|
+
|
|
3
40
|
## 1.7.1
|
|
4
41
|
|
|
5
42
|
Release integrity. No source change to the library.
|
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/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
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kxco-post-quantum",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
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",
|
|
@@ -109,12 +109,15 @@
|
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
111
|
"files": [
|
|
112
|
+
"AGILITY.md",
|
|
112
113
|
"BENCHMARKS.md",
|
|
114
|
+
"BOUNDARY.md",
|
|
113
115
|
"CHANGELOG.md",
|
|
114
116
|
"CONFORMANCE.md",
|
|
115
117
|
"DEPENDENCIES.md",
|
|
116
118
|
"LICENCE-PRODUCT.md",
|
|
117
119
|
"LICENSE",
|
|
120
|
+
"LIFECYCLE.md",
|
|
118
121
|
"MIGRATION.md",
|
|
119
122
|
"README.md",
|
|
120
123
|
"SECURITY.md",
|
|
@@ -129,6 +132,7 @@
|
|
|
129
132
|
"@noble/post-quantum": "0.7.0"
|
|
130
133
|
},
|
|
131
134
|
"scripts": {
|
|
135
|
+
"fuzz": "node fuzz/fuzz.mjs",
|
|
132
136
|
"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
137
|
"test:vectors": "node test/run-vectors.js",
|
|
134
138
|
"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'
|