@e-sig/core 0.5.1 → 0.7.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/LICENSE +1 -1
- package/README.md +92 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/pem-signer.d.ts +26 -5
- package/dist/pem-signer.d.ts.map +1 -1
- package/dist/pem-signer.js +84 -13
- package/dist/pem-signer.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 +183 -0
- package/dist/pq-seal.d.ts.map +1 -0
- package/dist/pq-seal.js +345 -0
- package/dist/pq-seal.js.map +1 -0
- package/dist/pq-verify.d.ts +99 -0
- package/dist/pq-verify.d.ts.map +1 -0
- package/dist/pq-verify.js +135 -0
- package/dist/pq-verify.js.map +1 -0
- package/dist/render-pdf.d.ts +5 -5
- package/dist/render-pdf.d.ts.map +1 -1
- package/dist/render-pdf.js +51 -17
- package/dist/render-pdf.js.map +1 -1
- 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 +66 -4
- package/dist/sign-pdf.d.ts.map +1 -1
- package/dist/sign-pdf.js +51 -2
- package/dist/sign-pdf.js.map +1 -1
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vendor/placeholder-plain/LICENSE-signpdf.md +47 -0
- package/dist/vendor/placeholder-plain/createBufferPageWithAnnotation.d.ts +6 -0
- package/dist/vendor/placeholder-plain/createBufferPageWithAnnotation.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/createBufferPageWithAnnotation.js +38 -0
- package/dist/vendor/placeholder-plain/createBufferPageWithAnnotation.js.map +1 -0
- package/dist/vendor/placeholder-plain/createBufferRootWithAcroform.d.ts +6 -0
- package/dist/vendor/placeholder-plain/createBufferRootWithAcroform.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/createBufferRootWithAcroform.js +15 -0
- package/dist/vendor/placeholder-plain/createBufferRootWithAcroform.js.map +1 -0
- package/dist/vendor/placeholder-plain/createBufferTrailer.d.ts +4 -0
- package/dist/vendor/placeholder-plain/createBufferTrailer.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/createBufferTrailer.js +26 -0
- package/dist/vendor/placeholder-plain/createBufferTrailer.js.map +1 -0
- package/dist/vendor/placeholder-plain/findObject.d.ts +4 -0
- package/dist/vendor/placeholder-plain/findObject.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/findObject.js +18 -0
- package/dist/vendor/placeholder-plain/findObject.js.map +1 -0
- package/dist/vendor/placeholder-plain/getIndexFromRef.d.ts +4 -0
- package/dist/vendor/placeholder-plain/getIndexFromRef.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/getIndexFromRef.js +13 -0
- package/dist/vendor/placeholder-plain/getIndexFromRef.js.map +1 -0
- package/dist/vendor/placeholder-plain/getPageRef.d.ts +6 -0
- package/dist/vendor/placeholder-plain/getPageRef.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/getPageRef.js +18 -0
- package/dist/vendor/placeholder-plain/getPageRef.js.map +1 -0
- package/dist/vendor/placeholder-plain/getPagesDictionaryRef.d.ts +3 -0
- package/dist/vendor/placeholder-plain/getPagesDictionaryRef.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/getPagesDictionaryRef.js +12 -0
- package/dist/vendor/placeholder-plain/getPagesDictionaryRef.js.map +1 -0
- package/dist/vendor/placeholder-plain/index.d.ts +2 -0
- package/dist/vendor/placeholder-plain/index.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/index.js +4 -0
- package/dist/vendor/placeholder-plain/index.js.map +1 -0
- package/dist/vendor/placeholder-plain/pdfObject.d.ts +13 -0
- package/dist/vendor/placeholder-plain/pdfObject.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/pdfObject.js +142 -0
- package/dist/vendor/placeholder-plain/pdfObject.js.map +1 -0
- package/dist/vendor/placeholder-plain/pdfkitAddPlaceholder.d.ts +49 -0
- package/dist/vendor/placeholder-plain/pdfkitAddPlaceholder.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/pdfkitAddPlaceholder.js +115 -0
- package/dist/vendor/placeholder-plain/pdfkitAddPlaceholder.js.map +1 -0
- package/dist/vendor/placeholder-plain/plainAddPlaceholder.d.ts +41 -0
- package/dist/vendor/placeholder-plain/plainAddPlaceholder.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/plainAddPlaceholder.js +95 -0
- package/dist/vendor/placeholder-plain/plainAddPlaceholder.js.map +1 -0
- package/dist/vendor/placeholder-plain/readPdf.d.ts +20 -0
- package/dist/vendor/placeholder-plain/readPdf.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/readPdf.js +47 -0
- package/dist/vendor/placeholder-plain/readPdf.js.map +1 -0
- package/dist/vendor/placeholder-plain/readRefTable.d.ts +17 -0
- package/dist/vendor/placeholder-plain/readRefTable.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/readRefTable.js +84 -0
- package/dist/vendor/placeholder-plain/readRefTable.js.map +1 -0
- package/dist/vendor/placeholder-plain/xrefToRefMap.d.ts +3 -0
- package/dist/vendor/placeholder-plain/xrefToRefMap.d.ts.map +1 -0
- package/dist/vendor/placeholder-plain/xrefToRefMap.js +38 -0
- package/dist/vendor/placeholder-plain/xrefToRefMap.js.map +1 -0
- package/package.json +10 -8
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
/** Seal schema version. Bump on any breaking change to the signed payload shape. */
|
|
3
|
+
export declare const PQ_SEAL_VERSION: 1;
|
|
4
|
+
/** Hybrid algorithm identifier embedded in (and bound by) every seal. */
|
|
5
|
+
export declare const PQ_SEAL_ALG: "hybrid-ed25519-ml-dsa-65";
|
|
6
|
+
/** Bundle schema version for the wrapped at-rest key material. */
|
|
7
|
+
declare const PQ_BUNDLE_VERSION: 1;
|
|
8
|
+
/**
|
|
9
|
+
* True if `value` is a structurally well-formed UUAID assertion — the exact rule
|
|
10
|
+
* `buildPqSeal` enforces on input and `verifyPqSealSignatures` enforces on a
|
|
11
|
+
* seal that carries one. This is an envelope check, NOT proof that the
|
|
12
|
+
* identifier resolves, exists, or belongs to the signer.
|
|
13
|
+
*
|
|
14
|
+
* Accepted here does NOT imply accepted by the UUAID registry: the registry
|
|
15
|
+
* additionally enforces semantics this package deliberately does not know
|
|
16
|
+
* (registered subject classes and object types, canonical UUID form). Expect a
|
|
17
|
+
* band of identifiers this will sign and a resolver will still reject.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isWellFormedUuaidAssertion(value: unknown): value is string;
|
|
20
|
+
/**
|
|
21
|
+
* The wrappable at-rest key bundle. Persist `wrapPqKeyBundle(bundle, passphrase)`;
|
|
22
|
+
* never store the raw bundle. Compact by design: an Ed25519 PKCS#8 key plus a
|
|
23
|
+
* 32-byte ML-DSA seed (the full 4032-byte ML-DSA secret key is re-derived
|
|
24
|
+
* deterministically via `ml_dsa65.keygen(seed)`).
|
|
25
|
+
*/
|
|
26
|
+
export interface PqKeyBundle {
|
|
27
|
+
v: typeof PQ_BUNDLE_VERSION;
|
|
28
|
+
/** base64 PKCS#8 DER of the Ed25519 private key. */
|
|
29
|
+
ed25519Pkcs8: string;
|
|
30
|
+
/** base64 32-byte ML-DSA-65 seed. */
|
|
31
|
+
mldsa65Seed: string;
|
|
32
|
+
}
|
|
33
|
+
/** Public key material derived from a bundle — safe to publish/pin as the signer identity. */
|
|
34
|
+
export interface PqPublicMaterial {
|
|
35
|
+
/** base64 raw 32-byte Ed25519 public key. */
|
|
36
|
+
ed25519: string;
|
|
37
|
+
/** base64 raw 1952-byte ML-DSA-65 public key. */
|
|
38
|
+
mldsa65: string;
|
|
39
|
+
/** SHA-256 hex of the raw ML-DSA-65 public key — the post-quantum identity fingerprint. */
|
|
40
|
+
mldsa65Fpr: string;
|
|
41
|
+
/** Stable 128-bit hex id over both public keys (bound into every seal). */
|
|
42
|
+
keyId: string;
|
|
43
|
+
}
|
|
44
|
+
/** In-memory signing keys, ready to produce seals. Never persisted directly. */
|
|
45
|
+
export interface PqSigningKeys {
|
|
46
|
+
ed25519PrivateKey: crypto.KeyObject;
|
|
47
|
+
ed25519PublicRaw: Uint8Array;
|
|
48
|
+
mldsa65SecretKey: Uint8Array;
|
|
49
|
+
mldsa65PublicKey: Uint8Array;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A hybrid post-quantum seal. The signed payload is every field EXCEPT `sig`,
|
|
53
|
+
* serialized canonically (see `canonicalJson`). `digest` is the SHA-256 (hex) of
|
|
54
|
+
* the first `coveredBytes` bytes of the final document — the PDF-verify layer
|
|
55
|
+
* checks that binding; `verifyPqSealSignatures` only checks the two signatures.
|
|
56
|
+
*/
|
|
57
|
+
export interface PqSeal {
|
|
58
|
+
v: typeof PQ_SEAL_VERSION;
|
|
59
|
+
alg: typeof PQ_SEAL_ALG;
|
|
60
|
+
/** Digest algorithm over the covered document bytes. */
|
|
61
|
+
over: "sha256";
|
|
62
|
+
/** SHA-256 hex of the covered document bytes (the first `coveredBytes` bytes). */
|
|
63
|
+
digest: string;
|
|
64
|
+
/** Byte length of the document prefix the digest covers (P0 = rendered PDF pre-seal). */
|
|
65
|
+
coveredBytes: number;
|
|
66
|
+
/** ISO 8601 seal creation time. */
|
|
67
|
+
signedAt: string;
|
|
68
|
+
/** 128-bit hex id over both public keys. */
|
|
69
|
+
keyId: string;
|
|
70
|
+
/**
|
|
71
|
+
* OPTIONAL signer-asserted UUAID (IAASO-0004 attribution). Omitted entirely
|
|
72
|
+
* unless the signer supplies one. Signed like every other payload field, so it
|
|
73
|
+
* is immutable once sealed — but it is a CLAIM by the seal key, not proof of
|
|
74
|
+
* identity: it binds key -> uuaid only. See the identity note at the top of
|
|
75
|
+
* this file before relying on it for attribution.
|
|
76
|
+
*/
|
|
77
|
+
uuaid?: string;
|
|
78
|
+
keys: {
|
|
79
|
+
ed25519: string;
|
|
80
|
+
mldsa65: string;
|
|
81
|
+
mldsa65Fpr: string;
|
|
82
|
+
};
|
|
83
|
+
sig: {
|
|
84
|
+
/** base64 Ed25519 signature over canonicalJson(payload). */
|
|
85
|
+
ed25519: string;
|
|
86
|
+
/** base64 ML-DSA-65 signature over canonicalJson(payload). */
|
|
87
|
+
mldsa65: string;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Result of verifying a seal's two signatures (does NOT check document binding). */
|
|
91
|
+
export interface PqSealVerification {
|
|
92
|
+
/** Classical Ed25519 signature valid. */
|
|
93
|
+
ed25519: boolean;
|
|
94
|
+
/** Post-quantum ML-DSA-65 signature valid. */
|
|
95
|
+
mldsa65: boolean;
|
|
96
|
+
/** The embedded `mldsa65Fpr` matches SHA-256(mldsa65 public key). */
|
|
97
|
+
fingerprintOk: boolean;
|
|
98
|
+
/** The embedded `keyId` matches the digest of the two public keys it claims. */
|
|
99
|
+
keyIdOk: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* The seal either asserts no UUAID, or asserts a structurally well-formed one.
|
|
102
|
+
* A seal carrying a malformed `uuaid` is rejected (fail-closed) even though its
|
|
103
|
+
* signatures are intact — a hand-crafted identity field is not something to
|
|
104
|
+
* pass downstream. True does NOT mean the assertion was verified.
|
|
105
|
+
*/
|
|
106
|
+
uuaidOk: boolean;
|
|
107
|
+
/** Hybrid verdict: every check passed (both signatures + fingerprint + keyId + uuaid shape). */
|
|
108
|
+
ok: boolean;
|
|
109
|
+
}
|
|
110
|
+
export interface GeneratePqKeyBundleOptions {
|
|
111
|
+
/**
|
|
112
|
+
* 32-byte ML-DSA-65 keygen seed. The same seed always derives the same
|
|
113
|
+
* keypair — supply one for deterministic key provisioning (e.g. from a KMS
|
|
114
|
+
* or sealed secret). Omit for a fresh random seed.
|
|
115
|
+
*/
|
|
116
|
+
mldsa65Seed?: Uint8Array;
|
|
117
|
+
/**
|
|
118
|
+
* PKCS#8 DER Ed25519 private key to use instead of generating a fresh one.
|
|
119
|
+
* Together with `mldsa65Seed` this makes the whole bundle deterministic.
|
|
120
|
+
*/
|
|
121
|
+
ed25519Pkcs8?: Uint8Array;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Generate a fresh hybrid key bundle plus its derived public material. The bundle
|
|
125
|
+
* is what you wrap + persist; the public material is what you publish/pin as the
|
|
126
|
+
* signer's post-quantum identity. Pass `opts` to provision deterministically
|
|
127
|
+
* from existing key material instead of random generation.
|
|
128
|
+
*/
|
|
129
|
+
export declare function generatePqKeyBundle(opts?: GeneratePqKeyBundleOptions): {
|
|
130
|
+
bundle: PqKeyBundle;
|
|
131
|
+
public: PqPublicMaterial;
|
|
132
|
+
};
|
|
133
|
+
/** Rehydrate in-memory signing keys from a bundle (deterministically re-derives the ML-DSA keypair). */
|
|
134
|
+
export declare function loadPqSigningKeys(bundle: PqKeyBundle): PqSigningKeys;
|
|
135
|
+
/** Derive the publishable public material for a set of signing keys. */
|
|
136
|
+
export declare function publicMaterialForKeys(keys: PqSigningKeys): PqPublicMaterial;
|
|
137
|
+
/** AES-256-GCM-wrap a key bundle for persistence. `passphrase` ≥ 24 chars. */
|
|
138
|
+
export declare function wrapPqKeyBundle(bundle: PqKeyBundle, passphrase: string): Uint8Array;
|
|
139
|
+
/** Inverse of `wrapPqKeyBundle`. Throws on wrong passphrase / tampering (auth-tag mismatch). */
|
|
140
|
+
export declare function unwrapPqKeyBundle(blob: Uint8Array, passphrase: string): PqKeyBundle;
|
|
141
|
+
export interface BuildPqSealInput {
|
|
142
|
+
/** SHA-256 hex of the covered document bytes. */
|
|
143
|
+
digestHex: string;
|
|
144
|
+
/** Byte length the digest covers (the document prefix protected by the seal). */
|
|
145
|
+
coveredBytes: number;
|
|
146
|
+
keys: PqSigningKeys;
|
|
147
|
+
/** Seal timestamp. Defaults to now. */
|
|
148
|
+
signedAt?: Date;
|
|
149
|
+
/**
|
|
150
|
+
* OPTIONAL UUAID to assert as the signer identity (IAASO-0004 attribution).
|
|
151
|
+
* Omit — the default — to produce a seal byte-identical to pre-`uuaid` output.
|
|
152
|
+
* Rejected at build time unless structurally well-formed, so a malformed
|
|
153
|
+
* identifier can never be signed into a document. That check is structural
|
|
154
|
+
* only: passing it does not mean the UUAID registry will resolve or accept the
|
|
155
|
+
* identifier (see `isWellFormedUuaidAssertion`).
|
|
156
|
+
*/
|
|
157
|
+
uuaid?: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Produce a hybrid seal over `digestHex`. Both signatures are computed over the
|
|
161
|
+
* exact same canonical bytes (the seal minus `sig`), binding digest, covered
|
|
162
|
+
* length, public keys, timestamp and keyId under BOTH schemes.
|
|
163
|
+
*/
|
|
164
|
+
export declare function buildPqSeal(input: BuildPqSealInput): PqSeal;
|
|
165
|
+
/**
|
|
166
|
+
* Verify a seal's two signatures over its own payload (does NOT bind the seal to a
|
|
167
|
+
* document — the PDF-verify layer checks `digest` against the covered bytes).
|
|
168
|
+
*
|
|
169
|
+
* Fails CLOSED: any malformed field, wrong-length key/signature, or thrown error
|
|
170
|
+
* yields `ok:false` rather than propagating. The hybrid verdict requires BOTH
|
|
171
|
+
* signatures valid AND the ML-DSA fingerprint self-consistent.
|
|
172
|
+
*/
|
|
173
|
+
export declare function verifyPqSealSignatures(seal: PqSeal): PqSealVerification;
|
|
174
|
+
/**
|
|
175
|
+
* Deterministic JSON serialization (recursively key-sorted, no insignificant
|
|
176
|
+
* whitespace) used as the signing input. The seal payload contains only strings
|
|
177
|
+
* and safe integers, so this is unambiguous without full RFC 8785 number
|
|
178
|
+
* canonicalization. Both signing and verification serialize the identical field
|
|
179
|
+
* set (payload = seal minus `sig`), so the two sides always agree.
|
|
180
|
+
*/
|
|
181
|
+
export declare function canonicalJson(value: unknown): string;
|
|
182
|
+
export {};
|
|
183
|
+
//# sourceMappingURL=pq-seal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pq-seal.d.ts","sourceRoot":"","sources":["../src/pq-seal.ts"],"names":[],"mappings":"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"}
|
package/dist/pq-seal.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// pq-seal.ts
|
|
2
|
+
//
|
|
3
|
+
// Hybrid post-quantum document seal — Ed25519 (classical) + ML-DSA-65 (FIPS 204,
|
|
4
|
+
// module-lattice, quantum-resistant). This is the cryptographic core of e-sig's
|
|
5
|
+
// post-quantum wedge: every sealed document carries TWO independent signatures
|
|
6
|
+
// over the same payload, and a verifier requires BOTH to pass. If either scheme
|
|
7
|
+
// is ever broken (a CRQC breaks Ed25519, or an unforeseen lattice attack dents
|
|
8
|
+
// ML-DSA), the seal still stands on the other — the belt-and-suspenders
|
|
9
|
+
// migration path NIST/CNSA 2.0 recommends over a hard cutover.
|
|
10
|
+
//
|
|
11
|
+
// The seal is a small canonical-JSON object signed over a SHA-256 digest of the
|
|
12
|
+
// document bytes it covers. It does NOT replace the PDF's PKCS#7/PAdES RSA
|
|
13
|
+
// signature (which stays valid in every PDF reader, including Adobe Acrobat —
|
|
14
|
+
// no mainstream reader validates ML-DSA in PAdES yet, 2026). Instead the seal is
|
|
15
|
+
// embedded in the PDF and the RSA /ByteRange signature is applied on top, so the
|
|
16
|
+
// classical signature cryptographically covers the seal (see sign-pdf.ts).
|
|
17
|
+
//
|
|
18
|
+
// Identity model (v1): raw ML-DSA-65 public key + its SHA-256 fingerprint carried
|
|
19
|
+
// in the seal (the `keyId` / `mldsa65Fpr`), verified TOFU / against-published-key.
|
|
20
|
+
// A self-signed ML-DSA-65 X.509 certificate (RFC 9881 OID 2.16.840.1.101.3.4.3.18)
|
|
21
|
+
// is a deliberate fast-follow, not required to ship the seal.
|
|
22
|
+
//
|
|
23
|
+
// Optional signer-asserted UUAID (`uuaid`): a signer MAY additionally assert the
|
|
24
|
+
// UUAID identity it signs as, for IAASO-0004 (Media Provenance & Attribution)
|
|
25
|
+
// attribution. The field is part of the signed payload, so it is bound under BOTH
|
|
26
|
+
// schemes and cannot be added, removed or swapped on an existing seal.
|
|
27
|
+
//
|
|
28
|
+
// What it proves: the holder of THIS seal key claims to be THAT UUAID.
|
|
29
|
+
// What it does NOT: that the UUAID's owner authorised this document.
|
|
30
|
+
//
|
|
31
|
+
// It is one half of a mutual binding (key -> uuaid). The other half (uuaid -> key,
|
|
32
|
+
// i.e. the UUAID owner authenticating this seal key as theirs) lives in the UUAID
|
|
33
|
+
// registry and is NOT established here. A relying party MUST NOT treat a bare
|
|
34
|
+
// `uuaid` assertion as attribution: unmatched, it is worth exactly the TOFU
|
|
35
|
+
// fingerprint next to it. Absent the second direction, an attacker who can mint
|
|
36
|
+
// any seal can assert any UUAID.
|
|
37
|
+
//
|
|
38
|
+
// Backward compatibility: the field is omitted entirely when not supplied, so
|
|
39
|
+
// seals from callers that never set it are byte-identical to pre-`uuaid` output,
|
|
40
|
+
// and verifiers that predate the field still verify uuaid-bearing seals (the
|
|
41
|
+
// signed payload is reconstructed generically as "the seal minus `sig`"). This is
|
|
42
|
+
// why the field is additive at v1 rather than a version bump: bumping
|
|
43
|
+
// PQ_SEAL_VERSION would break every already-deployed verifier, the added field
|
|
44
|
+
// does not.
|
|
45
|
+
//
|
|
46
|
+
// Algorithm binding: both signatures are computed over the SAME canonical bytes —
|
|
47
|
+
// the seal object minus its `sig` field — so digest, covered length, public keys,
|
|
48
|
+
// timestamp and keyId are all bound together and cannot be swapped independently.
|
|
49
|
+
import crypto from "node:crypto";
|
|
50
|
+
import { ml_dsa65 } from "@noble/post-quantum/ml-dsa.js";
|
|
51
|
+
import { encryptKeyPem, decryptKeyPem } from "./cert-issuer.js";
|
|
52
|
+
// ---------- Constants ----------
|
|
53
|
+
/** Seal schema version. Bump on any breaking change to the signed payload shape. */
|
|
54
|
+
export const PQ_SEAL_VERSION = 1;
|
|
55
|
+
/** Hybrid algorithm identifier embedded in (and bound by) every seal. */
|
|
56
|
+
export const PQ_SEAL_ALG = "hybrid-ed25519-ml-dsa-65";
|
|
57
|
+
/** Bundle schema version for the wrapped at-rest key material. */
|
|
58
|
+
const PQ_BUNDLE_VERSION = 1;
|
|
59
|
+
/**
|
|
60
|
+
* DER SubjectPublicKeyInfo prefix for an Ed25519 key (RFC 8410 §4). A raw 32-byte
|
|
61
|
+
* Ed25519 public key is exactly this 12-byte prefix followed by the key, so we
|
|
62
|
+
* can round-trip raw keys ↔ node KeyObjects without a heavier ASN.1 dependency.
|
|
63
|
+
*/
|
|
64
|
+
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
65
|
+
// FIPS 204 ML-DSA-65 sizes (raw, unencoded) — asserted defensively at load time.
|
|
66
|
+
const MLDSA65_PUBLIC_LEN = 1952;
|
|
67
|
+
const MLDSA65_SIGNATURE_LEN = 3309;
|
|
68
|
+
const MLDSA65_SEED_LEN = 32;
|
|
69
|
+
const ED25519_RAW_PUBLIC_LEN = 32;
|
|
70
|
+
const ED25519_SIGNATURE_LEN = 64;
|
|
71
|
+
/** Upper bound on an asserted UUAID string — bounds the seal, not the namespace. */
|
|
72
|
+
const UUAID_MAX_LEN = 255;
|
|
73
|
+
/**
|
|
74
|
+
* Structural shape of a UUAID: `uuaid:` followed by three or four
|
|
75
|
+
* `[A-Za-z0-9_-]` segments — covering both the compact profile
|
|
76
|
+
* (`uuaid:foundation:<objectType>:<uuid>`) and the federated profile
|
|
77
|
+
* (`uuaid:<subjectClass>:<jurisdiction>:<authority>:<localId>`).
|
|
78
|
+
*
|
|
79
|
+
* Deliberately structural only. Which subject classes and object types exist is
|
|
80
|
+
* a registry that evolves in the UUAID lane; hard-coding that enum here would
|
|
81
|
+
* make this package reject newly-registered-but-valid identifiers until it is
|
|
82
|
+
* republished, and it would bake another system's policy into a signed wire
|
|
83
|
+
* format. Semantic validation and resolution belong to the UUAID verifier.
|
|
84
|
+
*/
|
|
85
|
+
const UUAID_ASSERTION_RE = /^uuaid:[A-Za-z0-9_-]+:[A-Za-z0-9_-]+:[A-Za-z0-9_-]+(?::[A-Za-z0-9_-]+)?$/;
|
|
86
|
+
/**
|
|
87
|
+
* True if `value` is a structurally well-formed UUAID assertion — the exact rule
|
|
88
|
+
* `buildPqSeal` enforces on input and `verifyPqSealSignatures` enforces on a
|
|
89
|
+
* seal that carries one. This is an envelope check, NOT proof that the
|
|
90
|
+
* identifier resolves, exists, or belongs to the signer.
|
|
91
|
+
*
|
|
92
|
+
* Accepted here does NOT imply accepted by the UUAID registry: the registry
|
|
93
|
+
* additionally enforces semantics this package deliberately does not know
|
|
94
|
+
* (registered subject classes and object types, canonical UUID form). Expect a
|
|
95
|
+
* band of identifiers this will sign and a resolver will still reject.
|
|
96
|
+
*/
|
|
97
|
+
export function isWellFormedUuaidAssertion(value) {
|
|
98
|
+
return typeof value === "string" && value.length <= UUAID_MAX_LEN && UUAID_ASSERTION_RE.test(value);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Generate a fresh hybrid key bundle plus its derived public material. The bundle
|
|
102
|
+
* is what you wrap + persist; the public material is what you publish/pin as the
|
|
103
|
+
* signer's post-quantum identity. Pass `opts` to provision deterministically
|
|
104
|
+
* from existing key material instead of random generation.
|
|
105
|
+
*/
|
|
106
|
+
export function generatePqKeyBundle(opts = {}) {
|
|
107
|
+
let ed25519Pkcs8;
|
|
108
|
+
let ed25519PublicRaw;
|
|
109
|
+
if (opts.ed25519Pkcs8) {
|
|
110
|
+
const privateKey = crypto.createPrivateKey({
|
|
111
|
+
key: Buffer.from(opts.ed25519Pkcs8),
|
|
112
|
+
format: "der",
|
|
113
|
+
type: "pkcs8",
|
|
114
|
+
});
|
|
115
|
+
if (privateKey.asymmetricKeyType !== "ed25519") {
|
|
116
|
+
throw new Error(`generatePqKeyBundle: ed25519Pkcs8 must be an Ed25519 key, got ${privateKey.asymmetricKeyType}`);
|
|
117
|
+
}
|
|
118
|
+
ed25519Pkcs8 = Buffer.from(opts.ed25519Pkcs8);
|
|
119
|
+
ed25519PublicRaw = rawEd25519FromKeyObject(crypto.createPublicKey(privateKey));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
123
|
+
ed25519Pkcs8 = privateKey.export({ type: "pkcs8", format: "der" });
|
|
124
|
+
ed25519PublicRaw = rawEd25519FromKeyObject(publicKey);
|
|
125
|
+
}
|
|
126
|
+
if (opts.mldsa65Seed && opts.mldsa65Seed.length !== MLDSA65_SEED_LEN) {
|
|
127
|
+
throw new Error(`generatePqKeyBundle: mldsa65Seed must be ${MLDSA65_SEED_LEN} bytes, got ${opts.mldsa65Seed.length}`);
|
|
128
|
+
}
|
|
129
|
+
const seed = opts.mldsa65Seed ? Buffer.from(opts.mldsa65Seed) : crypto.randomBytes(MLDSA65_SEED_LEN);
|
|
130
|
+
const { publicKey: mldsa65PublicKey } = ml_dsa65.keygen(seed);
|
|
131
|
+
const bundle = {
|
|
132
|
+
v: PQ_BUNDLE_VERSION,
|
|
133
|
+
ed25519Pkcs8: b64(ed25519Pkcs8),
|
|
134
|
+
mldsa65Seed: b64(seed),
|
|
135
|
+
};
|
|
136
|
+
return { bundle, public: publicMaterial(ed25519PublicRaw, mldsa65PublicKey) };
|
|
137
|
+
}
|
|
138
|
+
/** Rehydrate in-memory signing keys from a bundle (deterministically re-derives the ML-DSA keypair). */
|
|
139
|
+
export function loadPqSigningKeys(bundle) {
|
|
140
|
+
if (bundle.v !== PQ_BUNDLE_VERSION) {
|
|
141
|
+
throw new Error(`loadPqSigningKeys: unknown bundle version ${bundle.v}`);
|
|
142
|
+
}
|
|
143
|
+
const ed25519PrivateKey = crypto.createPrivateKey({
|
|
144
|
+
key: Buffer.from(bundle.ed25519Pkcs8, "base64"),
|
|
145
|
+
format: "der",
|
|
146
|
+
type: "pkcs8",
|
|
147
|
+
});
|
|
148
|
+
const ed25519PublicRaw = rawEd25519FromKeyObject(crypto.createPublicKey(ed25519PrivateKey));
|
|
149
|
+
const seed = Buffer.from(bundle.mldsa65Seed, "base64");
|
|
150
|
+
if (seed.length !== MLDSA65_SEED_LEN) {
|
|
151
|
+
throw new Error(`loadPqSigningKeys: ML-DSA seed must be ${MLDSA65_SEED_LEN} bytes, got ${seed.length}`);
|
|
152
|
+
}
|
|
153
|
+
const { publicKey, secretKey } = ml_dsa65.keygen(seed);
|
|
154
|
+
return {
|
|
155
|
+
ed25519PrivateKey,
|
|
156
|
+
ed25519PublicRaw,
|
|
157
|
+
mldsa65SecretKey: secretKey,
|
|
158
|
+
mldsa65PublicKey: publicKey,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Derive the publishable public material for a set of signing keys. */
|
|
162
|
+
export function publicMaterialForKeys(keys) {
|
|
163
|
+
return publicMaterial(keys.ed25519PublicRaw, keys.mldsa65PublicKey);
|
|
164
|
+
}
|
|
165
|
+
// ---------- At-rest wrapping (reuses cert-issuer AES-256-GCM) ----------
|
|
166
|
+
/** AES-256-GCM-wrap a key bundle for persistence. `passphrase` ≥ 24 chars. */
|
|
167
|
+
export function wrapPqKeyBundle(bundle, passphrase) {
|
|
168
|
+
return encryptKeyPem(JSON.stringify(bundle), passphrase);
|
|
169
|
+
}
|
|
170
|
+
/** Inverse of `wrapPqKeyBundle`. Throws on wrong passphrase / tampering (auth-tag mismatch). */
|
|
171
|
+
export function unwrapPqKeyBundle(blob, passphrase) {
|
|
172
|
+
const bundle = JSON.parse(decryptKeyPem(blob, passphrase));
|
|
173
|
+
if (bundle.v !== PQ_BUNDLE_VERSION) {
|
|
174
|
+
throw new Error(`unwrapPqKeyBundle: unknown bundle version ${bundle.v}`);
|
|
175
|
+
}
|
|
176
|
+
return bundle;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Produce a hybrid seal over `digestHex`. Both signatures are computed over the
|
|
180
|
+
* exact same canonical bytes (the seal minus `sig`), binding digest, covered
|
|
181
|
+
* length, public keys, timestamp and keyId under BOTH schemes.
|
|
182
|
+
*/
|
|
183
|
+
export function buildPqSeal(input) {
|
|
184
|
+
if (!/^[0-9a-f]{64}$/.test(input.digestHex)) {
|
|
185
|
+
throw new Error("buildPqSeal: digestHex must be 64 lowercase hex chars (SHA-256)");
|
|
186
|
+
}
|
|
187
|
+
if (!Number.isInteger(input.coveredBytes) || input.coveredBytes <= 0) {
|
|
188
|
+
throw new Error("buildPqSeal: coveredBytes must be a positive integer");
|
|
189
|
+
}
|
|
190
|
+
if (input.uuaid !== undefined && !isWellFormedUuaidAssertion(input.uuaid)) {
|
|
191
|
+
throw new Error("buildPqSeal: uuaid must be a well-formed UUAID (uuaid:<3-or-4 segments>) — refusing to sign a malformed identity claim");
|
|
192
|
+
}
|
|
193
|
+
const pub = publicMaterialForKeys(input.keys);
|
|
194
|
+
const payload = {
|
|
195
|
+
v: PQ_SEAL_VERSION,
|
|
196
|
+
alg: PQ_SEAL_ALG,
|
|
197
|
+
over: "sha256",
|
|
198
|
+
digest: input.digestHex,
|
|
199
|
+
coveredBytes: input.coveredBytes,
|
|
200
|
+
signedAt: (input.signedAt ?? new Date()).toISOString(),
|
|
201
|
+
keyId: pub.keyId,
|
|
202
|
+
// Spread-in rather than `uuaid: input.uuaid` so the key is ABSENT (not
|
|
203
|
+
// `undefined`) when unused — canonicalJson would otherwise serialize it and
|
|
204
|
+
// change the signed bytes of every seal that never asked for attribution.
|
|
205
|
+
...(input.uuaid !== undefined ? { uuaid: input.uuaid } : {}),
|
|
206
|
+
keys: { ed25519: pub.ed25519, mldsa65: pub.mldsa65, mldsa65Fpr: pub.mldsa65Fpr },
|
|
207
|
+
};
|
|
208
|
+
const signingInput = Buffer.from(canonicalJson(payload), "utf8");
|
|
209
|
+
const ed25519Sig = crypto.sign(null, signingInput, input.keys.ed25519PrivateKey);
|
|
210
|
+
const mldsa65Sig = ml_dsa65.sign(signingInput, input.keys.mldsa65SecretKey);
|
|
211
|
+
return { ...payload, sig: { ed25519: b64(ed25519Sig), mldsa65: b64(mldsa65Sig) } };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Verify a seal's two signatures over its own payload (does NOT bind the seal to a
|
|
215
|
+
* document — the PDF-verify layer checks `digest` against the covered bytes).
|
|
216
|
+
*
|
|
217
|
+
* Fails CLOSED: any malformed field, wrong-length key/signature, or thrown error
|
|
218
|
+
* yields `ok:false` rather than propagating. The hybrid verdict requires BOTH
|
|
219
|
+
* signatures valid AND the ML-DSA fingerprint self-consistent.
|
|
220
|
+
*/
|
|
221
|
+
export function verifyPqSealSignatures(seal) {
|
|
222
|
+
const fail = {
|
|
223
|
+
ed25519: false,
|
|
224
|
+
mldsa65: false,
|
|
225
|
+
fingerprintOk: false,
|
|
226
|
+
keyIdOk: false,
|
|
227
|
+
uuaidOk: false,
|
|
228
|
+
ok: false,
|
|
229
|
+
};
|
|
230
|
+
try {
|
|
231
|
+
if (seal.v !== PQ_SEAL_VERSION || seal.alg !== PQ_SEAL_ALG || seal.over !== "sha256")
|
|
232
|
+
return fail;
|
|
233
|
+
const ed25519Pub = Buffer.from(seal.keys.ed25519, "base64");
|
|
234
|
+
const mldsa65Pub = Buffer.from(seal.keys.mldsa65, "base64");
|
|
235
|
+
const ed25519Sig = Buffer.from(seal.sig.ed25519, "base64");
|
|
236
|
+
const mldsa65Sig = Buffer.from(seal.sig.mldsa65, "base64");
|
|
237
|
+
if (ed25519Pub.length !== ED25519_RAW_PUBLIC_LEN)
|
|
238
|
+
return fail;
|
|
239
|
+
if (mldsa65Pub.length !== MLDSA65_PUBLIC_LEN)
|
|
240
|
+
return fail;
|
|
241
|
+
if (ed25519Sig.length !== ED25519_SIGNATURE_LEN)
|
|
242
|
+
return fail;
|
|
243
|
+
if (mldsa65Sig.length !== MLDSA65_SIGNATURE_LEN)
|
|
244
|
+
return fail;
|
|
245
|
+
// Reconstruct the exact signed payload: the seal minus `sig`.
|
|
246
|
+
const { sig: _sig, ...payload } = seal;
|
|
247
|
+
void _sig;
|
|
248
|
+
const signingInput = Buffer.from(canonicalJson(payload), "utf8");
|
|
249
|
+
// Self-consistency of the identity fields carried in the (signed) payload:
|
|
250
|
+
// fingerprint over the ML-DSA key, and keyId over BOTH public keys. These
|
|
251
|
+
// don't add trust (the keys are the seal's own), but they make the identity
|
|
252
|
+
// fields the signature commits to actually correspond to the keys present.
|
|
253
|
+
const fingerprintOk = sha256Hex(mldsa65Pub) === seal.keys.mldsa65Fpr;
|
|
254
|
+
const expectedKeyId = crypto
|
|
255
|
+
.createHash("sha256")
|
|
256
|
+
.update(ed25519Pub)
|
|
257
|
+
.update(mldsa65Pub)
|
|
258
|
+
.digest("hex")
|
|
259
|
+
.slice(0, 32);
|
|
260
|
+
const keyIdOk = seal.keyId === expectedKeyId;
|
|
261
|
+
// An absent `uuaid` asserts nothing and is fine; a present one must be
|
|
262
|
+
// structurally sound. Both cases are already covered by the signatures —
|
|
263
|
+
// this only rejects identity fields no honest signer could have produced.
|
|
264
|
+
const uuaidOk = seal.uuaid === undefined || isWellFormedUuaidAssertion(seal.uuaid);
|
|
265
|
+
let ed25519 = false;
|
|
266
|
+
try {
|
|
267
|
+
ed25519 = crypto.verify(null, signingInput, ed25519RawToPublicKey(ed25519Pub), ed25519Sig);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
ed25519 = false;
|
|
271
|
+
}
|
|
272
|
+
let mldsa65 = false;
|
|
273
|
+
try {
|
|
274
|
+
mldsa65 = ml_dsa65.verify(mldsa65Sig, signingInput, mldsa65Pub);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
mldsa65 = false;
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
ed25519,
|
|
281
|
+
mldsa65,
|
|
282
|
+
fingerprintOk,
|
|
283
|
+
keyIdOk,
|
|
284
|
+
uuaidOk,
|
|
285
|
+
ok: ed25519 && mldsa65 && fingerprintOk && keyIdOk && uuaidOk,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return fail;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// ---------- Internals ----------
|
|
293
|
+
function publicMaterial(ed25519PublicRaw, mldsa65PublicKey) {
|
|
294
|
+
const ed25519 = b64(ed25519PublicRaw);
|
|
295
|
+
const mldsa65 = b64(mldsa65PublicKey);
|
|
296
|
+
const mldsa65Fpr = sha256Hex(mldsa65PublicKey);
|
|
297
|
+
const keyId = crypto
|
|
298
|
+
.createHash("sha256")
|
|
299
|
+
.update(ed25519PublicRaw)
|
|
300
|
+
.update(mldsa65PublicKey)
|
|
301
|
+
.digest("hex")
|
|
302
|
+
.slice(0, 32);
|
|
303
|
+
return { ed25519, mldsa65, mldsa65Fpr, keyId };
|
|
304
|
+
}
|
|
305
|
+
/** Extract the raw 32-byte Ed25519 public key from a node KeyObject (SPKI tail). */
|
|
306
|
+
function rawEd25519FromKeyObject(pub) {
|
|
307
|
+
const spki = pub.export({ type: "spki", format: "der" });
|
|
308
|
+
return Uint8Array.prototype.slice.call(spki, spki.length - ED25519_RAW_PUBLIC_LEN);
|
|
309
|
+
}
|
|
310
|
+
/** Rebuild a node public KeyObject from a raw 32-byte Ed25519 key. */
|
|
311
|
+
function ed25519RawToPublicKey(raw) {
|
|
312
|
+
if (raw.length !== ED25519_RAW_PUBLIC_LEN) {
|
|
313
|
+
throw new Error(`ed25519 raw public key must be ${ED25519_RAW_PUBLIC_LEN} bytes`);
|
|
314
|
+
}
|
|
315
|
+
return crypto.createPublicKey({
|
|
316
|
+
key: Buffer.concat([ED25519_SPKI_PREFIX, Buffer.from(raw)]),
|
|
317
|
+
format: "der",
|
|
318
|
+
type: "spki",
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function sha256Hex(bytes) {
|
|
322
|
+
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
323
|
+
}
|
|
324
|
+
function b64(bytes) {
|
|
325
|
+
return Buffer.from(bytes).toString("base64");
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Deterministic JSON serialization (recursively key-sorted, no insignificant
|
|
329
|
+
* whitespace) used as the signing input. The seal payload contains only strings
|
|
330
|
+
* and safe integers, so this is unambiguous without full RFC 8785 number
|
|
331
|
+
* canonicalization. Both signing and verification serialize the identical field
|
|
332
|
+
* set (payload = seal minus `sig`), so the two sides always agree.
|
|
333
|
+
*/
|
|
334
|
+
export function canonicalJson(value) {
|
|
335
|
+
if (value === null || typeof value !== "object") {
|
|
336
|
+
return JSON.stringify(value);
|
|
337
|
+
}
|
|
338
|
+
if (Array.isArray(value)) {
|
|
339
|
+
return `[${value.map((v) => canonicalJson(v)).join(",")}]`;
|
|
340
|
+
}
|
|
341
|
+
const obj = value;
|
|
342
|
+
const keys = Object.keys(obj).sort();
|
|
343
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=pq-seal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pq-seal.js","sourceRoot":"","sources":["../src/pq-seal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type VerifyResult } from "./verify-pdf.js";
|
|
2
|
+
export interface PqSealVerdict {
|
|
3
|
+
/** A seal was found and parsed. */
|
|
4
|
+
present: boolean;
|
|
5
|
+
/** Hybrid verdict: digest binds to the document AND both signatures + fingerprint valid. */
|
|
6
|
+
ok: boolean;
|
|
7
|
+
/** SHA-256 over the covered document prefix equals the seal's `digest`. */
|
|
8
|
+
digestBinds?: boolean;
|
|
9
|
+
/** Classical Ed25519 signature valid. */
|
|
10
|
+
ed25519?: boolean;
|
|
11
|
+
/** Post-quantum ML-DSA-65 signature valid. */
|
|
12
|
+
mldsa65?: boolean;
|
|
13
|
+
/** Embedded ML-DSA fingerprint matches its public key. */
|
|
14
|
+
fingerprintOk?: boolean;
|
|
15
|
+
/** A `signerCert` was supplied AND it is a valid ML-DSA-65 cert for this seal's key. */
|
|
16
|
+
certIdentityOk?: boolean;
|
|
17
|
+
alg?: string;
|
|
18
|
+
keyId?: string;
|
|
19
|
+
/** ML-DSA-65 public-key fingerprint — the post-quantum signer identity to pin. */
|
|
20
|
+
mldsa65Fpr?: string;
|
|
21
|
+
signedAt?: string;
|
|
22
|
+
coveredBytes?: number;
|
|
23
|
+
/**
|
|
24
|
+
* The signer-asserted UUAID carried in the seal, when present (IAASO-0004).
|
|
25
|
+
* ASSERTION, NOT ATTRIBUTION: a valid seal proves the seal key claimed this
|
|
26
|
+
* identity, never that the identity's owner authorised the document. Treat it
|
|
27
|
+
* as attribution only once an independent uuaid -> key direction has been
|
|
28
|
+
* checked (a UUAID-side registration of this seal key). Unmatched, it carries
|
|
29
|
+
* no more weight than `mldsa65Fpr`.
|
|
30
|
+
*/
|
|
31
|
+
uuaid?: string;
|
|
32
|
+
/** A `expectedUuaid` was supplied AND the seal asserts exactly that UUAID. */
|
|
33
|
+
uuaidMatches?: boolean;
|
|
34
|
+
failures: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface VerifyPqSealOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Pin the expected post-quantum signer. When set, the seal's ML-DSA-65
|
|
39
|
+
* fingerprint MUST equal this value (case-insensitive hex) or verification
|
|
40
|
+
* fails — this is how a relying party asserts "signed by THIS party" in-band
|
|
41
|
+
* (the raw-key TOFU trust model). Compare against a fingerprint you published
|
|
42
|
+
* or received out-of-band.
|
|
43
|
+
*/
|
|
44
|
+
expectedMldsa65Fpr?: string;
|
|
45
|
+
/**
|
|
46
|
+
* A self-signed ML-DSA-65 X.509 certificate (PEM or DER) asserting the signer
|
|
47
|
+
* identity. When set, the cert must be valid AND its public key must be the one
|
|
48
|
+
* that produced the seal — the X.509 upgrade of `expectedMldsa65Fpr`.
|
|
49
|
+
*/
|
|
50
|
+
signerCert?: string | Uint8Array;
|
|
51
|
+
/**
|
|
52
|
+
* Pin the expected signer-asserted UUAID. When set, the seal MUST carry
|
|
53
|
+
* exactly this `uuaid` (case-sensitive — UUAID segments are case-preserving)
|
|
54
|
+
* or verification fails.
|
|
55
|
+
*
|
|
56
|
+
* This pins an ASSERTION. It answers "does this seal claim the identity I
|
|
57
|
+
* expected?", not "does that identity vouch for this key?" — the relying party
|
|
58
|
+
* still supplies the trust, exactly as with `expectedMldsa65Fpr`. Pinning a
|
|
59
|
+
* UUAID you obtained from the document itself proves nothing.
|
|
60
|
+
*/
|
|
61
|
+
expectedUuaid?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Verify the embedded post-quantum seal against the document bytes. Never throws;
|
|
65
|
+
* a missing or malformed seal returns `{ present:false, ok:false }`. Pass
|
|
66
|
+
* `expectedMldsa65Fpr` to also pin the signer identity.
|
|
67
|
+
*/
|
|
68
|
+
export declare function verifyPqSeal(pdf: Buffer, opts?: VerifyPqSealOptions): PqSealVerdict;
|
|
69
|
+
export interface DocumentVerification {
|
|
70
|
+
/**
|
|
71
|
+
* Overall: the classical signature is valid AND, if a post-quantum seal is
|
|
72
|
+
* present, it too is valid and lies within the classically-signed region. A
|
|
73
|
+
* document with no seal is judged on the classical signature alone (backward
|
|
74
|
+
* compatible); callers that REQUIRE post-quantum protection should also assert
|
|
75
|
+
* `postQuantum.present`.
|
|
76
|
+
*/
|
|
77
|
+
ok: boolean;
|
|
78
|
+
/** Classical PAdES / PKCS#7 (RSA) verification. */
|
|
79
|
+
classical: VerifyResult;
|
|
80
|
+
/** Post-quantum hybrid seal verification. */
|
|
81
|
+
postQuantum: PqSealVerdict;
|
|
82
|
+
}
|
|
83
|
+
export interface VerifyDocumentOptions extends VerifyPqSealOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Require a valid post-quantum seal. When true, a document without a seal (or
|
|
86
|
+
* with an invalid one) fails overall — use this where post-quantum protection
|
|
87
|
+
* is mandatory, to prevent a silent downgrade to classical-only.
|
|
88
|
+
*/
|
|
89
|
+
requirePq?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Full document verification: classical PAdES signature + post-quantum hybrid
|
|
93
|
+
* seal. Also asserts that the seal-covered region falls within the RSA-signed
|
|
94
|
+
* prefix, so the classical signature genuinely protects the seal. Pass
|
|
95
|
+
* `requirePq` to reject unsealed documents and `expectedMldsa65Fpr` to pin the
|
|
96
|
+
* post-quantum signer.
|
|
97
|
+
*/
|
|
98
|
+
export declare function verifyDocument(pdf: Buffer, opts?: VerifyDocumentOptions): DocumentVerification;
|
|
99
|
+
//# sourceMappingURL=pq-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pq-verify.d.ts","sourceRoot":"","sources":["../src/pq-verify.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKxE,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,4FAA4F;IAC5F,EAAE,EAAE,OAAO,CAAC;IACZ,2EAA2E;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yCAAyC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wFAAwF;IACxF,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IACjC;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,mBAAwB,GAAG,aAAa,CAgFvF;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,EAAE,EAAE,OAAO,CAAC;IACZ,mDAAmD;IACnD,SAAS,EAAE,YAAY,CAAC;IACxB,6CAA6C;IAC7C,WAAW,EAAE,aAAa,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,qBAA0B,GAAG,oBAAoB,CA+BlG"}
|