@e-sig/core 0.5.0 → 0.6.0
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/README.md +92 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/pq-cert.d.ts +76 -0
- package/dist/pq-cert.d.ts.map +1 -0
- package/dist/pq-cert.js +295 -0
- package/dist/pq-cert.js.map +1 -0
- package/dist/pq-embed.d.ts +22 -0
- package/dist/pq-embed.d.ts.map +1 -0
- package/dist/pq-embed.js +119 -0
- package/dist/pq-embed.js.map +1 -0
- package/dist/pq-lifecycle.d.ts +59 -0
- package/dist/pq-lifecycle.d.ts.map +1 -0
- package/dist/pq-lifecycle.js +55 -0
- package/dist/pq-lifecycle.js.map +1 -0
- package/dist/pq-seal.d.ts +133 -0
- package/dist/pq-seal.d.ts.map +1 -0
- package/dist/pq-seal.js +261 -0
- package/dist/pq-seal.js.map +1 -0
- package/dist/pq-verify.d.ts +77 -0
- package/dist/pq-verify.d.ts.map +1 -0
- package/dist/pq-verify.js +119 -0
- package/dist/pq-verify.js.map +1 -0
- package/dist/sign-document.d.ts +16 -0
- package/dist/sign-document.d.ts.map +1 -1
- package/dist/sign-document.js +10 -0
- package/dist/sign-document.js.map +1 -1
- package/dist/sign-pdf.d.ts +21 -0
- package/dist/sign-pdf.d.ts.map +1 -1
- package/dist/sign-pdf.js +27 -1
- package/dist/sign-pdf.js.map +1 -1
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Given an HTML document and a person who wants to sign it, this library:
|
|
|
15
15
|
2. Generates or reuses a self-signed RSA-2048 X.509 cert for the signing tenant.
|
|
16
16
|
3. Embeds a PKCS#7 detached signature under the **ETSI.CAdES.detached** subfilter, with the ESS **signing-certificate-v2** attribute binding the signer cert into the signed data. Pass `padesStrict: true` for strict **PAdES B-B** (also drops the PAdES-forbidden `signing-time` attribute).
|
|
17
17
|
4. Produces a PDF that opens cleanly in Preview / Adobe Reader with a valid signature panel — any post-signing edit invalidates the signature. `verifyPdfSignature()` checks this cryptographically (recomputes the digest over the signed ByteRange and RSA-verifies the signature).
|
|
18
|
+
5. *(Optional)* Adds a **post-quantum hybrid seal** — **Ed25519 + ML-DSA-65 (FIPS 204)** — embedded under the classical signature so the PDF stays Acrobat-valid while gaining quantum resistance. See [Post-quantum seal](#post-quantum-seal--ml-dsa-65-fips-204).
|
|
18
19
|
|
|
19
20
|
> **Trust vs. validity.** The signature is cryptographically *valid*, but the cert is *self-issued* — stock Adobe Reader shows "validity unknown" until the cert is trusted (org trust-store import, or plug in an AATL/CA signer). This verifies the signature math and integrity, not third-party trust. See the compliance notes below.
|
|
20
21
|
|
|
@@ -45,6 +46,10 @@ The library gives you **crypto + rendering**. You bring **persistence + UI + aut
|
|
|
45
46
|
| `render-pdf.ts` | HTML → PDF via puppeteer-core; auto-detects Lambda vs local Chrome | ✅ |
|
|
46
47
|
| `sign-pdf.ts` | Combine placeholder injection + PKCS#7 sign (ETSI.CAdES) | ✅ |
|
|
47
48
|
| `verify-pdf.ts` | Structural verifier (parses /ByteRange + PKCS#7 blob, returns diagnostics) | ✅ |
|
|
49
|
+
| `pq-seal.ts` | Hybrid **Ed25519 + ML-DSA-65 (FIPS 204)** post-quantum seal — keygen, sign, verify | ✅ |
|
|
50
|
+
| `pq-embed.ts` | Embed/extract the seal in a PDF as an append-only incremental update | ✅ |
|
|
51
|
+
| `pq-verify.ts` | `verifyPqSeal` + `verifyDocument` (classical **and** post-quantum verdict) | ✅ |
|
|
52
|
+
| `pq-lifecycle.ts` | `ensureActivePqKeys` / `rotatePqKeys` over a bring-your-own `PqKeyStore` | ✅ |
|
|
48
53
|
| `signature-block.ts` | HTML helper to render N signature blocks for multi-party flows | ✅ |
|
|
49
54
|
| `types.ts` | Shared TS types (`Signer`, `SigningCertPem`, …) | ✅ |
|
|
50
55
|
| `index.ts` | Public re-export barrel | ✅ |
|
|
@@ -187,6 +192,93 @@ See `CONSUMING.md` for the full consumer guide.
|
|
|
187
192
|
|
|
188
193
|
---
|
|
189
194
|
|
|
195
|
+
## Post-quantum seal — ML-DSA-65 (FIPS 204)
|
|
196
|
+
|
|
197
|
+
Harvest-now-decrypt-later is a real threat to long-lived signed documents: a
|
|
198
|
+
signature that is only RSA/ECDSA today is forgeable the day a cryptographically
|
|
199
|
+
relevant quantum computer exists. `@e-sig/core` can add a **hybrid post-quantum
|
|
200
|
+
seal** — **Ed25519 + ML-DSA-65** (the FIPS 204 module-lattice signature, née
|
|
201
|
+
Dilithium) — to any signed PDF, without giving up compatibility.
|
|
202
|
+
|
|
203
|
+
**How it stays compatible:** the seal does **not** replace the PKCS#7/PAdES RSA
|
|
204
|
+
signature (no mainstream PDF reader validates ML-DSA in PAdES yet). The seal is
|
|
205
|
+
embedded *first*, as an append-only incremental update, and the classical RSA
|
|
206
|
+
signature is applied on top — so it **cryptographically covers the seal**. Adobe
|
|
207
|
+
Acrobat still shows a valid signature; your verifier additionally confirms the
|
|
208
|
+
quantum-resistant layer. This is the hybrid migration path NIST/CNSA 2.0
|
|
209
|
+
recommend over a hard cutover.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import {
|
|
213
|
+
generateSelfSignedCert,
|
|
214
|
+
generatePqKeyBundle,
|
|
215
|
+
loadPqSigningKeys,
|
|
216
|
+
signPdf,
|
|
217
|
+
verifyDocument,
|
|
218
|
+
} from "@e-sig/core";
|
|
219
|
+
|
|
220
|
+
const cert = generateSelfSignedCert({ subjectName: "Acme Corp" });
|
|
221
|
+
|
|
222
|
+
// One hybrid key bundle per signer/tenant (persist wrapPqKeyBundle(...) at rest;
|
|
223
|
+
// or use ensureActivePqKeys with a PqKeyStore — see below).
|
|
224
|
+
const pqKeys = loadPqSigningKeys(generatePqKeyBundle().bundle);
|
|
225
|
+
|
|
226
|
+
const { signedPdf } = await signPdf({
|
|
227
|
+
pdf: unsigned,
|
|
228
|
+
keyPem: cert.keyPem,
|
|
229
|
+
certPem: cert.certPem,
|
|
230
|
+
reason: "Service Agreement acceptance",
|
|
231
|
+
location: "",
|
|
232
|
+
contactInfo: "jane@example.com",
|
|
233
|
+
name: "Jane Doe",
|
|
234
|
+
pqSeal: { keys: pqKeys }, // ← adds the post-quantum seal
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// One call, two verdicts:
|
|
238
|
+
const v = verifyDocument(signedPdf);
|
|
239
|
+
console.log("classical (PAdES/RSA):", v.classical.ok); // → true (Acrobat-grade)
|
|
240
|
+
console.log("post-quantum (ML-DSA-65):", v.postQuantum.ok); // → true
|
|
241
|
+
console.log("PQ signer fingerprint:", v.postQuantum.mldsa65Fpr);
|
|
242
|
+
// v.ok === true only when BOTH layers verify AND the seal lies inside the
|
|
243
|
+
// RSA-signed region. Tampering with one byte of the document fails BOTH.
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Managed keys.** `ensureActivePqKeys({ store, tenantId, passphrase })` mints +
|
|
247
|
+
wraps a bundle on first use and reuses it thereafter (implement the small
|
|
248
|
+
`PqKeyStore` interface against your DB, same pattern as `CertStore`);
|
|
249
|
+
`rotatePqKeys(...)` rolls to a fresh key while old documents keep verifying
|
|
250
|
+
against the public key embedded in each seal. `signDocument({ pq: { keys } })`
|
|
251
|
+
threads it through the end-to-end orchestrator and records the PQ key id +
|
|
252
|
+
ML-DSA fingerprint in the audit row.
|
|
253
|
+
|
|
254
|
+
**Trust model (v1).** The seal carries the raw ML-DSA-65 public key + its
|
|
255
|
+
SHA-256 fingerprint; a relying party pins/publishes the expected fingerprint
|
|
256
|
+
(TOFU). Assert it in-band:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
verifyDocument(signedPdf, {
|
|
260
|
+
expectedMldsa65Fpr: "<the signer's published fingerprint>", // fails if it differs
|
|
261
|
+
requirePq: true, // fails if no seal present (no silent downgrade)
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**X.509 identity (RFC 9881).** For an enterprise-shaped identity, issue a
|
|
266
|
+
self-signed **ML-DSA-65 X.509 certificate** (OID `2.16.840.1.101.3.4.3.18`) —
|
|
267
|
+
its SubjectPublicKeyInfo *and* signature are ML-DSA-65, so it parses and verifies
|
|
268
|
+
in OpenSSL 3.5+ (validated against 3.6). Bind it to a seal at verify time:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const cert = issueMlDsaCertificate({ keys, subjectName: "Acme Inc" }); // publish cert.certPem
|
|
272
|
+
verifyDocument(signedPdf, { signerCert: cert.certPem }); // fails unless the cert is valid AND owns the seal's key
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`verifyMlDsaCertificate()` checks the self-signature, algorithm, and validity
|
|
276
|
+
window; `certMatchesPqSeal()` ties it to a seal by public-key fingerprint. Both
|
|
277
|
+
seal signatures are required — if either Ed25519 **or** ML-DSA-65 fails (or the
|
|
278
|
+
seal's `fingerprint`/`keyId` don't match its keys), the seal fails.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
190
282
|
## Persisting certs + audit logs across requests
|
|
191
283
|
|
|
192
284
|
For real usage you need to:
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,9 @@ export type { StoredCert, CertStore, AuditLogEntry, AuditLogRow, AuditLogStore,
|
|
|
9
9
|
export { ensureActiveCert, type EnsureCertResult } from "./cert-lifecycle.js";
|
|
10
10
|
export { createEnvelope, resolveSigningToken, recordSignature, declineEnvelope, voidEnvelope, composeEnvelopeHtml, EnvelopeError, type Envelope, type EnvelopeSigner, type EnvelopeStatus, type EnvelopeSignerStatus, type EnvelopeStore, type CreateEnvelopeInput, type CreateEnvelopeResult, type TokenResolution, } from "./envelope.js";
|
|
11
11
|
export { signDocument, type SignDocumentInput, type SignDocumentResult, } from "./sign-document.js";
|
|
12
|
+
export { generatePqKeyBundle, loadPqSigningKeys, publicMaterialForKeys, wrapPqKeyBundle, unwrapPqKeyBundle, buildPqSeal, verifyPqSealSignatures, canonicalJson, PQ_SEAL_VERSION, PQ_SEAL_ALG, type PqKeyBundle, type PqPublicMaterial, type PqSigningKeys, type PqSeal, type PqSealVerification, type BuildPqSealInput, } from "./pq-seal.js";
|
|
13
|
+
export { embedPqSeal, extractPqSeal, hasPqSeal } from "./pq-embed.js";
|
|
14
|
+
export { verifyPqSeal, verifyDocument, type PqSealVerdict, type DocumentVerification, type VerifyPqSealOptions, type VerifyDocumentOptions, } from "./pq-verify.js";
|
|
15
|
+
export { ensureActivePqKeys, rotatePqKeys, type PqKeyStore, type StoredPqKeys, type EnsurePqKeysResult, } from "./pq-lifecycle.js";
|
|
16
|
+
export { issueMlDsaCertificate, parseMlDsaCertificate, verifyMlDsaCertificate, certMatchesPqSeal, ID_ML_DSA_65, type IssueMlDsaCertOptions, type MlDsaCertificate, type ParsedMlDsaCertificate, type MlDsaCertVerdict, } from "./pq-cert.js";
|
|
12
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,WAAW,EACX,aAAa,EACb,eAAe,EACf,eAAe,GAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,MAAM,EACN,cAAc,EACd,iBAAiB,EACjB,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,WAAW,EACX,aAAa,EACb,eAAe,EACf,eAAe,GAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,YAAY,EACZ,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -20,4 +20,10 @@ export { ensureActiveCert } from "./cert-lifecycle.js";
|
|
|
20
20
|
export { createEnvelope, resolveSigningToken, recordSignature, declineEnvelope, voidEnvelope, composeEnvelopeHtml, EnvelopeError, } from "./envelope.js";
|
|
21
21
|
// ---- End-to-end orchestrator ----
|
|
22
22
|
export { signDocument, } from "./sign-document.js";
|
|
23
|
+
// ---- Post-quantum hybrid seal (Ed25519 + ML-DSA-65, FIPS 204) ----
|
|
24
|
+
export { generatePqKeyBundle, loadPqSigningKeys, publicMaterialForKeys, wrapPqKeyBundle, unwrapPqKeyBundle, buildPqSeal, verifyPqSealSignatures, canonicalJson, PQ_SEAL_VERSION, PQ_SEAL_ALG, } from "./pq-seal.js";
|
|
25
|
+
export { embedPqSeal, extractPqSeal, hasPqSeal } from "./pq-embed.js";
|
|
26
|
+
export { verifyPqSeal, verifyDocument, } from "./pq-verify.js";
|
|
27
|
+
export { ensureActivePqKeys, rotatePqKeys, } from "./pq-lifecycle.js";
|
|
28
|
+
export { issueMlDsaCertificate, parseMlDsaCertificate, verifyMlDsaCertificate, certMatchesPqSeal, ID_ML_DSA_65, } from "./pq-cert.js";
|
|
23
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,kEAAkE;AAClE,EAAE;AACF,mFAAmF;AACnF,iFAAiF;AACjF,gFAAgF;AAEhF,8BAA8B;AAC9B,OAAO,EAAE,SAAS,EAAuB,MAAM,iBAAiB,CAAC;AACjE,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,aAAa,GAGd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAA+B,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAyC,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAqB,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAmBxB,4DAA4D;AAC5D,OAAO,EAAE,gBAAgB,EAAyB,MAAM,qBAAqB,CAAC;AAE9E,6DAA6D;AAC7D,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,aAAa,GASd,MAAM,eAAe,CAAC;AAEvB,oCAAoC;AACpC,OAAO,EACL,YAAY,GAGb,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,kEAAkE;AAClE,EAAE;AACF,mFAAmF;AACnF,iFAAiF;AACjF,gFAAgF;AAEhF,8BAA8B;AAC9B,OAAO,EAAE,SAAS,EAAuB,MAAM,iBAAiB,CAAC;AACjE,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,aAAa,GAGd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAA+B,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAyC,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAqB,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAmBxB,4DAA4D;AAC5D,OAAO,EAAE,gBAAgB,EAAyB,MAAM,qBAAqB,CAAC;AAE9E,6DAA6D;AAC7D,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,aAAa,GASd,MAAM,eAAe,CAAC;AAEvB,oCAAoC;AACpC,OAAO,EACL,YAAY,GAGb,MAAM,oBAAoB,CAAC;AAE5B,qEAAqE;AACrE,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,WAAW,GAOZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EACL,YAAY,EACZ,cAAc,GAKf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,YAAY,GAIb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,YAAY,GAKb,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { PqSigningKeys, PqSeal } from "./pq-seal.js";
|
|
2
|
+
/** RFC 9881 / NIST: id-ml-dsa-65. Used for BOTH the SPKI alg and the cert signature alg. */
|
|
3
|
+
export declare const ID_ML_DSA_65: "2.16.840.1.101.3.4.3.18";
|
|
4
|
+
export interface IssueMlDsaCertOptions {
|
|
5
|
+
/** The signing keys — the ML-DSA-65 keypair is both subject and issuer (self-signed). */
|
|
6
|
+
keys: PqSigningKeys;
|
|
7
|
+
/** Subject CN / O seed. ASCII-only (matches cert-issuer's node-forge round-trip guard). */
|
|
8
|
+
subjectName: string;
|
|
9
|
+
/** Validity window in days. Default 365. */
|
|
10
|
+
validityDays?: number;
|
|
11
|
+
/** Optional 16-byte serial; a CSPRNG positive serial is generated otherwise. */
|
|
12
|
+
serial?: Uint8Array;
|
|
13
|
+
/** Backdate notBefore by this many seconds to tolerate clock skew. Default 0. */
|
|
14
|
+
notBefore?: Date;
|
|
15
|
+
}
|
|
16
|
+
export interface MlDsaCertificate {
|
|
17
|
+
certPem: string;
|
|
18
|
+
certDer: Uint8Array;
|
|
19
|
+
/** SHA-256 hex of the DER-encoded certificate. */
|
|
20
|
+
fingerprint: string;
|
|
21
|
+
/** SHA-256 hex of the raw ML-DSA-65 public key — equals a seal's `keys.mldsa65Fpr`. */
|
|
22
|
+
publicKeyFingerprint: string;
|
|
23
|
+
notBefore: Date;
|
|
24
|
+
notAfter: Date;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Issue a self-signed ML-DSA-65 X.509 certificate for the given signing keys.
|
|
28
|
+
* The certificate's public key is the raw 1952-byte ML-DSA-65 key, and its own
|
|
29
|
+
* signature is ML-DSA-65 over the TBSCertificate.
|
|
30
|
+
*/
|
|
31
|
+
export declare function issueMlDsaCertificate(opts: IssueMlDsaCertOptions): MlDsaCertificate;
|
|
32
|
+
export interface ParsedMlDsaCertificate {
|
|
33
|
+
subjectCommonName: string;
|
|
34
|
+
notBefore: Date;
|
|
35
|
+
notAfter: Date;
|
|
36
|
+
/** Raw ML-DSA-65 public key (1952 bytes). */
|
|
37
|
+
publicKey: Uint8Array;
|
|
38
|
+
/** SHA-256 hex of `publicKey` — compare to a seal's `keys.mldsa65Fpr`. */
|
|
39
|
+
publicKeyFingerprint: string;
|
|
40
|
+
/** signatureAlgorithm OID (must be ID_ML_DSA_65). */
|
|
41
|
+
signatureAlgOid: string;
|
|
42
|
+
/** Exact DER of the TBSCertificate (byte-sliced, not re-encoded) — what the signature covers. */
|
|
43
|
+
tbsDer: Uint8Array;
|
|
44
|
+
/** Raw ML-DSA-65 signature (3309 bytes). */
|
|
45
|
+
signature: Uint8Array;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse a self-signed ML-DSA-65 certificate. The TBS bytes are recovered by a
|
|
49
|
+
* raw TLV slice of the original DER (never re-encoded), so signature
|
|
50
|
+
* verification is over the exact signed bytes.
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseMlDsaCertificate(cert: string | Uint8Array): ParsedMlDsaCertificate;
|
|
53
|
+
export interface MlDsaCertVerdict {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
/** signatureAlgorithm is id-ml-dsa-65. */
|
|
56
|
+
algOk: boolean;
|
|
57
|
+
/** The ML-DSA-65 self-signature over the TBS verifies against the cert's own public key. */
|
|
58
|
+
selfSignatureOk: boolean;
|
|
59
|
+
/** `now` is within [notBefore, notAfter]. */
|
|
60
|
+
timeValid: boolean;
|
|
61
|
+
failures: string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Verify a self-signed ML-DSA-65 certificate: correct algorithm, the self
|
|
65
|
+
* signature validates against the embedded public key, and it is within its
|
|
66
|
+
* validity window. Never throws.
|
|
67
|
+
*/
|
|
68
|
+
export declare function verifyMlDsaCertificate(cert: string | Uint8Array, now?: Date): MlDsaCertVerdict;
|
|
69
|
+
/**
|
|
70
|
+
* Bind a certificate to a post-quantum seal: the cert must be valid AND its
|
|
71
|
+
* public key must be the one that produced the seal (fingerprint match). This
|
|
72
|
+
* is how a relying party upgrades from raw-fingerprint pinning to an X.509
|
|
73
|
+
* identity without changing the seal format.
|
|
74
|
+
*/
|
|
75
|
+
export declare function certMatchesPqSeal(cert: string | Uint8Array, seal: PqSeal, now?: Date): boolean;
|
|
76
|
+
//# sourceMappingURL=pq-cert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pq-cert.d.ts","sourceRoot":"","sources":["../src/pq-cert.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE1D,4FAA4F;AAC5F,eAAO,MAAM,YAAY,EAAG,yBAAkC,CAAC;AAoF/D,MAAM,WAAW,qBAAqB;IACpC,yFAAyF;IACzF,IAAI,EAAE,aAAa,CAAC;IACpB,2FAA2F;IAC3F,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,iFAAiF;IACjF,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,IAAI,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,gBAAgB,CA8EnF;AAwCD,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,IAAI,CAAC;IACf,6CAA6C;IAC7C,SAAS,EAAE,UAAU,CAAC;IACtB,0EAA0E;IAC1E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,iGAAiG;IACjG,MAAM,EAAE,UAAU,CAAC;IACnB,4CAA4C;IAC5C,SAAS,EAAE,UAAU,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,sBAAsB,CA2CvF;AAcD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,OAAO,CAAC;IACf,4FAA4F;IAC5F,eAAe,EAAE,OAAO,CAAC;IACzB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,GAAE,IAAiB,GAAG,gBAAgB,CAmC1G;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAK1G"}
|
package/dist/pq-cert.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// src/pq-cert.ts
|
|
2
|
+
//
|
|
3
|
+
// Self-signed ML-DSA-65 X.509 certificate (RFC 9881).
|
|
4
|
+
//
|
|
5
|
+
// The raw-key TOFU identity (pq-seal) is enough to pin a signer, but enterprise
|
|
6
|
+
// relying parties expect an X.509 container: a human-readable subject bound to a
|
|
7
|
+
// public key by a signature. This issues a self-signed certificate whose
|
|
8
|
+
// SubjectPublicKeyInfo AND signatureAlgorithm are id-ml-dsa-65
|
|
9
|
+
// (2.16.840.1.101.3.4.3.18), per RFC 9881 — parseable by OpenSSL 3.5+ and
|
|
10
|
+
// self-verifying with the same ML-DSA-65 primitive the seal uses.
|
|
11
|
+
//
|
|
12
|
+
// The cert does NOT add a root of trust (it is self-signed — still TOFU on the
|
|
13
|
+
// key), but it upgrades the identity from an opaque fingerprint to a portable,
|
|
14
|
+
// standards-shaped assertion "CN=<subject> controls this ML-DSA-65 key", which
|
|
15
|
+
// `certMatchesPqSeal` ties back to a seal by public-key fingerprint.
|
|
16
|
+
import forge from "node-forge";
|
|
17
|
+
import crypto from "node:crypto";
|
|
18
|
+
import { ml_dsa65 } from "@noble/post-quantum/ml-dsa.js";
|
|
19
|
+
/** RFC 9881 / NIST: id-ml-dsa-65. Used for BOTH the SPKI alg and the cert signature alg. */
|
|
20
|
+
export const ID_ML_DSA_65 = "2.16.840.1.101.3.4.3.18";
|
|
21
|
+
const MLDSA65_PUBLIC_LEN = 1952;
|
|
22
|
+
const MLDSA65_SIGNATURE_LEN = 3309;
|
|
23
|
+
const OID_COMMON_NAME = "2.5.4.3";
|
|
24
|
+
const OID_ORG_NAME = "2.5.4.10";
|
|
25
|
+
const OID_BASIC_CONSTRAINTS = "2.5.29.19";
|
|
26
|
+
const OID_KEY_USAGE = "2.5.29.15";
|
|
27
|
+
const DEFAULT_VALIDITY_DAYS = 365;
|
|
28
|
+
const asn1 = forge.asn1;
|
|
29
|
+
const { Class, Type } = asn1;
|
|
30
|
+
// ---------- binary <-> Uint8Array (node-forge speaks binary strings) ----------
|
|
31
|
+
function u8ToBin(u8) {
|
|
32
|
+
let s = "";
|
|
33
|
+
for (let i = 0; i < u8.length; i++)
|
|
34
|
+
s += String.fromCharCode(u8[i]);
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
function binToU8(bin) {
|
|
38
|
+
const u8 = new Uint8Array(bin.length);
|
|
39
|
+
for (let i = 0; i < bin.length; i++)
|
|
40
|
+
u8[i] = bin.charCodeAt(i) & 0xff;
|
|
41
|
+
return u8;
|
|
42
|
+
}
|
|
43
|
+
function sha256Hex(bytes) {
|
|
44
|
+
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
45
|
+
}
|
|
46
|
+
// ---------- ASN.1 builders ----------
|
|
47
|
+
function oidNode(oid) {
|
|
48
|
+
return asn1.create(Class.UNIVERSAL, Type.OID, false, asn1.oidToDer(oid).getBytes());
|
|
49
|
+
}
|
|
50
|
+
/** AlgorithmIdentifier for ML-DSA: SEQUENCE { OID } — parameters field ABSENT (RFC 9881 §4). */
|
|
51
|
+
function mlDsaAlgId() {
|
|
52
|
+
return asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [oidNode(ID_ML_DSA_65)]);
|
|
53
|
+
}
|
|
54
|
+
/** DER BIT STRING with 0 unused bits wrapping raw bytes (keys, signatures). */
|
|
55
|
+
function bitStringNode(bytes) {
|
|
56
|
+
return asn1.create(Class.UNIVERSAL, Type.BITSTRING, false, String.fromCharCode(0x00) + u8ToBin(bytes));
|
|
57
|
+
}
|
|
58
|
+
function utf8Node(value) {
|
|
59
|
+
return asn1.create(Class.UNIVERSAL, Type.UTF8, false, forge.util.encodeUtf8(value));
|
|
60
|
+
}
|
|
61
|
+
/** RDNSequence for a CN + O subject. */
|
|
62
|
+
function nameNode(commonName, org) {
|
|
63
|
+
const rdn = (oid, value) => asn1.create(Class.UNIVERSAL, Type.SET, true, [
|
|
64
|
+
asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [oidNode(oid), utf8Node(value)]),
|
|
65
|
+
]);
|
|
66
|
+
return asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [
|
|
67
|
+
rdn(OID_COMMON_NAME, commonName),
|
|
68
|
+
rdn(OID_ORG_NAME, org),
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
/** SubjectPublicKeyInfo: SEQUENCE { AlgorithmIdentifier, BIT STRING(raw pubkey) } (RFC 9881). */
|
|
72
|
+
function spkiNode(pub) {
|
|
73
|
+
return asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [mlDsaAlgId(), bitStringNode(pub)]);
|
|
74
|
+
}
|
|
75
|
+
function timeNode(d) {
|
|
76
|
+
// UTCTime for years < 2050 (RFC 5280 §4.1.2.5), GeneralizedTime beyond.
|
|
77
|
+
if (d.getUTCFullYear() < 2050) {
|
|
78
|
+
return asn1.create(Class.UNIVERSAL, Type.UTCTIME, false, asn1.dateToUtcTime(d));
|
|
79
|
+
}
|
|
80
|
+
return asn1.create(Class.UNIVERSAL, Type.GENERALIZEDTIME, false, asn1.dateToGeneralizedTime(d));
|
|
81
|
+
}
|
|
82
|
+
/** Extension: SEQUENCE { OID, [critical BOOLEAN], OCTET STRING(DER of value) }. */
|
|
83
|
+
function extensionNode(oid, critical, value) {
|
|
84
|
+
const children = [oidNode(oid)];
|
|
85
|
+
if (critical)
|
|
86
|
+
children.push(asn1.create(Class.UNIVERSAL, Type.BOOLEAN, false, String.fromCharCode(0xff)));
|
|
87
|
+
children.push(asn1.create(Class.UNIVERSAL, Type.OCTETSTRING, false, asn1.toDer(value).getBytes()));
|
|
88
|
+
return asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, children);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Issue a self-signed ML-DSA-65 X.509 certificate for the given signing keys.
|
|
92
|
+
* The certificate's public key is the raw 1952-byte ML-DSA-65 key, and its own
|
|
93
|
+
* signature is ML-DSA-65 over the TBSCertificate.
|
|
94
|
+
*/
|
|
95
|
+
export function issueMlDsaCertificate(opts) {
|
|
96
|
+
const { keys, subjectName, validityDays = DEFAULT_VALIDITY_DAYS } = opts;
|
|
97
|
+
if (!/^[\x20-\x7e]+$/.test(subjectName)) {
|
|
98
|
+
throw new Error(`subjectName "${subjectName}" contains non-ASCII characters`);
|
|
99
|
+
}
|
|
100
|
+
const pub = keys.mldsa65PublicKey;
|
|
101
|
+
if (pub.length !== MLDSA65_PUBLIC_LEN) {
|
|
102
|
+
throw new Error(`ML-DSA-65 public key must be ${MLDSA65_PUBLIC_LEN} bytes, got ${pub.length}`);
|
|
103
|
+
}
|
|
104
|
+
// Serial: 128 bits CSPRNG, top bit cleared (positive DER INTEGER), nonzero lead.
|
|
105
|
+
let serial;
|
|
106
|
+
if (opts.serial) {
|
|
107
|
+
serial = opts.serial;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const s = crypto.randomBytes(16);
|
|
111
|
+
s[0] = (s[0] & 0x7f) | 0x40;
|
|
112
|
+
serial = s;
|
|
113
|
+
}
|
|
114
|
+
const notBefore = opts.notBefore ?? new Date();
|
|
115
|
+
const notAfter = new Date(notBefore.getTime() + validityDays * 86_400_000);
|
|
116
|
+
const cn = `E-sig PQ (${subjectName})`;
|
|
117
|
+
const name = () => nameNode(cn, subjectName);
|
|
118
|
+
const version = asn1.create(Class.CONTEXT_SPECIFIC, 0, true, [
|
|
119
|
+
asn1.create(Class.UNIVERSAL, Type.INTEGER, false, String.fromCharCode(0x02)), // v3
|
|
120
|
+
]);
|
|
121
|
+
const serialNode = asn1.create(Class.UNIVERSAL, Type.INTEGER, false, u8ToBin(serial));
|
|
122
|
+
const validity = asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [timeNode(notBefore), timeNode(notAfter)]);
|
|
123
|
+
// basicConstraints (CA:FALSE = empty SEQUENCE) + keyUsage (digitalSignature + nonRepudiation).
|
|
124
|
+
const basicConstraints = extensionNode(OID_BASIC_CONSTRAINTS, true, asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, []));
|
|
125
|
+
const keyUsage = extensionNode(OID_KEY_USAGE, true,
|
|
126
|
+
// bits 0 (digitalSignature) + 1 (nonRepudiation) set → 0xC0, 6 unused bits.
|
|
127
|
+
asn1.create(Class.UNIVERSAL, Type.BITSTRING, false, String.fromCharCode(0x06) + String.fromCharCode(0xc0)));
|
|
128
|
+
const extensions = asn1.create(Class.CONTEXT_SPECIFIC, 3, true, [
|
|
129
|
+
asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [basicConstraints, keyUsage]),
|
|
130
|
+
]);
|
|
131
|
+
const tbs = asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [
|
|
132
|
+
version,
|
|
133
|
+
serialNode,
|
|
134
|
+
mlDsaAlgId(), // signature algorithm
|
|
135
|
+
name(), // issuer
|
|
136
|
+
validity,
|
|
137
|
+
name(), // subject (self-signed → issuer == subject)
|
|
138
|
+
spkiNode(pub),
|
|
139
|
+
extensions,
|
|
140
|
+
]);
|
|
141
|
+
const tbsDer = binToU8(asn1.toDer(tbs).getBytes());
|
|
142
|
+
const signature = ml_dsa65.sign(tbsDer, keys.mldsa65SecretKey);
|
|
143
|
+
const certAsn1 = asn1.create(Class.UNIVERSAL, Type.SEQUENCE, true, [
|
|
144
|
+
tbs,
|
|
145
|
+
mlDsaAlgId(),
|
|
146
|
+
bitStringNode(signature),
|
|
147
|
+
]);
|
|
148
|
+
const certDer = binToU8(asn1.toDer(certAsn1).getBytes());
|
|
149
|
+
const certPem = derToPem(certDer);
|
|
150
|
+
return {
|
|
151
|
+
certPem,
|
|
152
|
+
certDer,
|
|
153
|
+
fingerprint: sha256Hex(certDer),
|
|
154
|
+
publicKeyFingerprint: sha256Hex(pub),
|
|
155
|
+
notBefore,
|
|
156
|
+
notAfter,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function derToPem(der) {
|
|
160
|
+
const b64 = Buffer.from(der).toString("base64");
|
|
161
|
+
const lines = b64.match(/.{1,64}/g) ?? [];
|
|
162
|
+
return `-----BEGIN CERTIFICATE-----\n${lines.join("\n")}\n-----END CERTIFICATE-----\n`;
|
|
163
|
+
}
|
|
164
|
+
function pemToDer(pem) {
|
|
165
|
+
const b64 = pem.replace(/-----(BEGIN|END) CERTIFICATE-----/g, "").replace(/\s+/g, "");
|
|
166
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
167
|
+
}
|
|
168
|
+
/** Read one definite-length DER TLV starting at `offset`. */
|
|
169
|
+
function readTlv(der, offset) {
|
|
170
|
+
if (offset + 1 >= der.length)
|
|
171
|
+
throw new Error("DER: truncated TLV");
|
|
172
|
+
const tag = der[offset];
|
|
173
|
+
let i = offset + 1;
|
|
174
|
+
let len = der[i++];
|
|
175
|
+
if (len & 0x80) {
|
|
176
|
+
const n = len & 0x7f;
|
|
177
|
+
if (n === 0 || n > 4)
|
|
178
|
+
throw new Error("DER: unsupported length encoding");
|
|
179
|
+
len = 0;
|
|
180
|
+
for (let k = 0; k < n; k++)
|
|
181
|
+
len = (len << 8) | der[i++];
|
|
182
|
+
}
|
|
183
|
+
const contentStart = i;
|
|
184
|
+
const end = contentStart + len;
|
|
185
|
+
if (end > der.length)
|
|
186
|
+
throw new Error("DER: length exceeds buffer");
|
|
187
|
+
return { tag, start: offset, contentStart, end };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Parse a self-signed ML-DSA-65 certificate. The TBS bytes are recovered by a
|
|
191
|
+
* raw TLV slice of the original DER (never re-encoded), so signature
|
|
192
|
+
* verification is over the exact signed bytes.
|
|
193
|
+
*/
|
|
194
|
+
export function parseMlDsaCertificate(cert) {
|
|
195
|
+
const der = typeof cert === "string" ? pemToDer(cert) : cert;
|
|
196
|
+
// Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, signatureValue }
|
|
197
|
+
const outer = readTlv(der, 0);
|
|
198
|
+
const tbs = readTlv(der, outer.contentStart);
|
|
199
|
+
const sigAlg = readTlv(der, tbs.end);
|
|
200
|
+
const sigVal = readTlv(der, sigAlg.end);
|
|
201
|
+
const tbsDer = der.slice(tbs.start, tbs.end);
|
|
202
|
+
// signatureValue is a BIT STRING: first content byte = unused-bits count (0).
|
|
203
|
+
const signature = der.slice(sigVal.contentStart + 1, sigVal.end);
|
|
204
|
+
// Human-readable + key fields via node-forge (structure is standard X.509).
|
|
205
|
+
const parsed = asn1.fromDer(forge.util.createBuffer(u8ToBin(der)));
|
|
206
|
+
const tbsNode = parsed.value[0];
|
|
207
|
+
const kids = tbsNode.value;
|
|
208
|
+
// With explicit version [0], fields are: [0]=version [1]=serial [2]=sigAlg
|
|
209
|
+
// [3]=issuer [4]=validity [5]=subject [6]=spki [7]=extensions.
|
|
210
|
+
const validityNode = kids[4];
|
|
211
|
+
const subjectNode = kids[5];
|
|
212
|
+
const spkiNode = kids[6];
|
|
213
|
+
const notBefore = asn1.utcTimeToDate(validityNode.value[0].value);
|
|
214
|
+
const notAfter = asn1.utcTimeToDate(validityNode.value[1].value);
|
|
215
|
+
const subjectCommonName = extractCommonName(subjectNode);
|
|
216
|
+
const spkiKids = spkiNode.value;
|
|
217
|
+
const algOid = asn1.derToOid(spkiKids[0].value[0].value);
|
|
218
|
+
const bitStr = spkiKids[1].value; // leading unused-bits byte
|
|
219
|
+
const publicKey = binToU8(bitStr.slice(1));
|
|
220
|
+
return {
|
|
221
|
+
subjectCommonName,
|
|
222
|
+
notBefore,
|
|
223
|
+
notAfter,
|
|
224
|
+
publicKey,
|
|
225
|
+
publicKeyFingerprint: sha256Hex(publicKey),
|
|
226
|
+
signatureAlgOid: algOid,
|
|
227
|
+
tbsDer,
|
|
228
|
+
signature,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function extractCommonName(nameNode) {
|
|
232
|
+
for (const rdn of nameNode.value) {
|
|
233
|
+
for (const atv of rdn.value) {
|
|
234
|
+
const [oidNode, valNode] = atv.value;
|
|
235
|
+
if (asn1.derToOid(oidNode.value) === OID_COMMON_NAME) {
|
|
236
|
+
return forge.util.decodeUtf8(valNode.value);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return "";
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Verify a self-signed ML-DSA-65 certificate: correct algorithm, the self
|
|
244
|
+
* signature validates against the embedded public key, and it is within its
|
|
245
|
+
* validity window. Never throws.
|
|
246
|
+
*/
|
|
247
|
+
export function verifyMlDsaCertificate(cert, now = new Date()) {
|
|
248
|
+
const failures = [];
|
|
249
|
+
try {
|
|
250
|
+
const p = parseMlDsaCertificate(cert);
|
|
251
|
+
const algOk = p.signatureAlgOid === ID_ML_DSA_65;
|
|
252
|
+
if (!algOk)
|
|
253
|
+
failures.push(`unexpected signature algorithm ${p.signatureAlgOid}`);
|
|
254
|
+
let selfSignatureOk = false;
|
|
255
|
+
if (p.publicKey.length === MLDSA65_PUBLIC_LEN &&
|
|
256
|
+
p.signature.length === MLDSA65_SIGNATURE_LEN) {
|
|
257
|
+
try {
|
|
258
|
+
selfSignatureOk = ml_dsa65.verify(p.signature, p.tbsDer, p.publicKey);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
selfSignatureOk = false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (!selfSignatureOk)
|
|
265
|
+
failures.push("ML-DSA-65 self-signature invalid");
|
|
266
|
+
const t = now.getTime();
|
|
267
|
+
const timeValid = t >= p.notBefore.getTime() && t <= p.notAfter.getTime();
|
|
268
|
+
if (!timeValid)
|
|
269
|
+
failures.push("certificate is outside its validity window");
|
|
270
|
+
return { ok: algOk && selfSignatureOk && timeValid, algOk, selfSignatureOk, timeValid, failures };
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
return {
|
|
274
|
+
ok: false,
|
|
275
|
+
algOk: false,
|
|
276
|
+
selfSignatureOk: false,
|
|
277
|
+
timeValid: false,
|
|
278
|
+
failures: [`certificate parse error: ${e.message}`],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Bind a certificate to a post-quantum seal: the cert must be valid AND its
|
|
284
|
+
* public key must be the one that produced the seal (fingerprint match). This
|
|
285
|
+
* is how a relying party upgrades from raw-fingerprint pinning to an X.509
|
|
286
|
+
* identity without changing the seal format.
|
|
287
|
+
*/
|
|
288
|
+
export function certMatchesPqSeal(cert, seal, now = new Date()) {
|
|
289
|
+
const verdict = verifyMlDsaCertificate(cert, now);
|
|
290
|
+
if (!verdict.ok)
|
|
291
|
+
return false;
|
|
292
|
+
const p = parseMlDsaCertificate(cert);
|
|
293
|
+
return p.publicKeyFingerprint === seal.keys.mldsa65Fpr;
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=pq-cert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pq-cert.js","sourceRoot":"","sources":["../src/pq-cert.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PqSeal } from "./pq-seal.js";
|
|
2
|
+
/**
|
|
3
|
+
* Append `seal` to `pdf` as an incremental update. Returns the new PDF; the first
|
|
4
|
+
* `pdf.length` bytes are preserved verbatim, so `seal.coveredBytes` (set by the
|
|
5
|
+
* caller to `pdf.length`) still points at the original document in the final file.
|
|
6
|
+
*/
|
|
7
|
+
export declare function embedPqSeal(pdf: Buffer, seal: PqSeal): Buffer;
|
|
8
|
+
/**
|
|
9
|
+
* Extract the embedded seal from a (possibly later-signed) PDF, or null if none
|
|
10
|
+
* is present / it is not valid JSON. Returns the FIRST seal if several exist.
|
|
11
|
+
*
|
|
12
|
+
* FIRST, deliberately: the genuine seal is embedded BEFORE the RSA PAdES
|
|
13
|
+
* signature, so it sits at the lowest file offset and is covered by the classical
|
|
14
|
+
* /ByteRange. A seal an attacker appends AFTER signing lands at a higher offset
|
|
15
|
+
* and is NOT RSA-covered — taking the first match ignores it, so even a standalone
|
|
16
|
+
* `verifyPqSeal` reports the authentic signer rather than the appended identity.
|
|
17
|
+
* Never throws — a malformed seal is treated as absent (fail-closed at verify).
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractPqSeal(pdf: Buffer): PqSeal | null;
|
|
20
|
+
/** True if the PDF carries an embedded post-quantum seal. */
|
|
21
|
+
export declare function hasPqSeal(pdf: Buffer): boolean;
|
|
22
|
+
//# sourceMappingURL=pq-embed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pq-embed.d.ts","sourceRoot":"","sources":["../src/pq-embed.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA+C3C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAiC7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASxD;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG9C"}
|