@val-protocol/qes-validator 0.1.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/CHANGELOG.md +16 -0
- package/LICENSE +201 -0
- package/NOTICE +13 -0
- package/README.md +78 -0
- package/dist/cjs/index.js +805 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/index.d.ts +127 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +802 -0
- package/dist/esm/index.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @val-protocol/qes-validator — eIDAS QES validation for VAL Profile C (ADR 0063).
|
|
3
|
+
*
|
|
4
|
+
* The `validateQes` entry point a relying party runs THEMSELVES (so verification stays trustless), plus
|
|
5
|
+
* the `QesValidationReport` contract the zero-dep `@val-protocol/chain-verifier` consumes
|
|
6
|
+
* (`options.qesValidation.reports`). Structurally a superset of the core's `QesVerdict`
|
|
7
|
+
* ({ qualified, signerIdentity?, reportRef? }) so it drops into the seam unchanged, PLUS `signatureRef`
|
|
8
|
+
* (per-signature keying the WS6 verifier PR / §11.4 item 5 will match on) and `status`/`reason`
|
|
9
|
+
* (conclusive-vs-indeterminate, never collapsed into a bare boolean for the operator).
|
|
10
|
+
*
|
|
11
|
+
* ── BACKEND DECISION (supersedes the scaffold's "S1: wrap DSS") ────────────────────────────────────
|
|
12
|
+
* The closeout build prompt (operator-ratified, 2026-06-29) makes this a **no-DSS, no-server, pure-JS**
|
|
13
|
+
* validator — that is the entire point of "the auditor's trustless tool": a relying party verifies a VAL
|
|
14
|
+
* grant's qualified signature WITHOUT standing up a Java DSS. The earlier scaffold wrapped a self-hosted
|
|
15
|
+
* DSS over REST; that defeated trustlessness and is replaced here. (`@val-protocol/chain-verifier` stays
|
|
16
|
+
* zero-dep; this package carries the X.509/LOTL logic + reuses `@val-protocol/anchor-lotl-resolver`.)
|
|
17
|
+
*
|
|
18
|
+
* ── DOCUMENTED SUBSET (honest scope, ETSI) ─────────────────────────────────────────────────────────
|
|
19
|
+
* Implements a SUBSET of the ETSI validation chain, labelled per document:
|
|
20
|
+
* (1) JWS/JAdES parse (ETSI TS 119 182-1);
|
|
21
|
+
* (2) signature-value verification over the JWS signing input via the embedded x5c — ETSI TS 119 102-1;
|
|
22
|
+
* (3) certificate-path build + verification leaf→issuer→trust-anchor — ETSI TS 119 102-1;
|
|
23
|
+
* (4) qualification-status determination — ETSI TS 119 615: QcStatements in the signing cert
|
|
24
|
+
* (QcCompliance 0.4.0.1862.1.1 AND QcType-eSign 0.4.0.1862.1.6.1) AND the issuer resolves to a
|
|
25
|
+
* Trusted-List service of type CA/QC, granted-for-eSignatures, granted at signing time.
|
|
26
|
+
* NOT in scope (this is DSS's job, not reimplemented here): per-certificate OCSP/CRL revocation, full
|
|
27
|
+
* AdES-LTA/archive-timestamp LTV. Revocation here is at the Trusted-List SERVICE-STATUS granularity
|
|
28
|
+
* (granted/withdrawn at signing time), NOT per-cert OCSP. Anything this subset cannot conclude returns
|
|
29
|
+
* `indeterminate` — never a silent `qualified:true`.
|
|
30
|
+
*
|
|
31
|
+
* PRECONDITION (cert path): the full chain leaf → … → TL-listed granted CA/QC service MUST be present in
|
|
32
|
+
* `x5c` or `trust.intermediateHintsDer`. There is NO AIA/caIssuers chasing (it would need network + break
|
|
33
|
+
* the offline model). A partial `x5c` (leaf only) ⇒ `not_qualified`/`CHAIN_INCOMPLETE` (supply the chain),
|
|
34
|
+
* NOT a false `qualified`, and distinct from `ANCHOR_NOT_ON_TRUSTED_LIST` (complete chain, top not on the TL).
|
|
35
|
+
*
|
|
36
|
+
* Honesty: this validator — not the core — is the authority for "qualified". `qualified` is TRUE only on
|
|
37
|
+
* a conclusive positive determination; anything else is `not_qualified` (conclusive negative) or
|
|
38
|
+
* `indeterminate` (could not conclude). The core treats only `qualified` as the gate, so neither a null
|
|
39
|
+
* identity nor an indeterminate verdict can fake a green.
|
|
40
|
+
*/
|
|
41
|
+
/** eIDAS natural-person minimum dataset, surfaced verbatim from the qualified certificate. */
|
|
42
|
+
export interface QesSignerIdentity {
|
|
43
|
+
given_name: string;
|
|
44
|
+
family_name: string;
|
|
45
|
+
date_of_birth: string | null;
|
|
46
|
+
persistent_id: string | null;
|
|
47
|
+
country: string | null;
|
|
48
|
+
}
|
|
49
|
+
/** Conclusive-vs-indeterminate (refinement 2). `qualified` (the core gate) is TRUE only iff `'qualified'`. */
|
|
50
|
+
export type QesStatus = 'qualified' | 'not_qualified' | 'indeterminate';
|
|
51
|
+
/** A reproducible QES validation verdict over one qualified delegation signature. */
|
|
52
|
+
export interface QesValidationReport {
|
|
53
|
+
/** The ONLY field the core verifier treats as the qualified gate. TRUE only iff `status==='qualified'`. */
|
|
54
|
+
qualified: boolean;
|
|
55
|
+
/** Conclusive positive / conclusive negative / could-not-conclude (refinement 2). */
|
|
56
|
+
status: QesStatus;
|
|
57
|
+
/** Human-readable reason — distinguishes "off the EU Trusted List" from "LOTL unreachable". */
|
|
58
|
+
reason: string;
|
|
59
|
+
/** Per-signature key (refinement 1 / WS6): sha256-hex of the delegation `signature.signature` bytes,
|
|
60
|
+
* so §11.4 item 5 can match THIS report to THIS signature instead of "first qualified report". */
|
|
61
|
+
signatureRef: string | null;
|
|
62
|
+
/** Proven natural-person identity (minimum dataset), present iff `qualified`. */
|
|
63
|
+
signerIdentity: QesSignerIdentity | null;
|
|
64
|
+
/** ETSI EN 319 102-1 main indication: 'TOTAL-PASSED' | 'TOTAL-FAILED' | 'INDETERMINATE'. */
|
|
65
|
+
indication: string;
|
|
66
|
+
/** Sub-indication when not passed (e.g. 'SIG_CRYPTO_FAILURE', 'NO_CERTIFICATE_CHAIN_FOUND'), else null. */
|
|
67
|
+
subIndication: string | null;
|
|
68
|
+
/** AdES level proven (best-effort from the JAdES header), for LTV/retention reasoning. */
|
|
69
|
+
adesLevel: string | null;
|
|
70
|
+
/** Opaque reference to this verdict's reproducible inputs (the relying party can re-run). */
|
|
71
|
+
reportRef: string | null;
|
|
72
|
+
/** Which backend produced this report. */
|
|
73
|
+
backend: 'offline-js';
|
|
74
|
+
/** sha256-hex of the TL-granted CA/QC cert the path anchored at (present iff `qualified`). Ruling 2:
|
|
75
|
+
* feeding this back through `matchGrantedCaQc` MUST return granted — the walker cannot drift from the
|
|
76
|
+
* DSS-cross-checked resolver. */
|
|
77
|
+
anchorFingerprint?: string | null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Trust inputs for the offline determination.
|
|
81
|
+
*
|
|
82
|
+
* INVARIANT (ADR 0063, ruling 1): **the trust anchor is ONLY derived from the EU Trusted List.** No caller
|
|
83
|
+
* can inject a trust anchor — `x5c`-supplied and `intermediateHintsDer` certificates are untrusted path-
|
|
84
|
+
* building HINTS, validated per RFC 5280 §6 but NEVER trusted as a root. A path is anchored only when it
|
|
85
|
+
* reaches a cert the LOTL resolver (`matchGrantedCaQc`) calls a granted CA/QC-for-eSignatures service at
|
|
86
|
+
* signing time. (Replaces the prior `trustAnchorsDer`, which let the x5c top become the anchor — a forgeable
|
|
87
|
+
* trust root.)
|
|
88
|
+
*/
|
|
89
|
+
export interface QesTrustInput {
|
|
90
|
+
/** Pre-fetched member-state Trusted List XML (ETSI TS 119 612). Offline/test path. */
|
|
91
|
+
tslXml?: string;
|
|
92
|
+
/** Fetch the EU LOTL → member-state TSL live (network). */
|
|
93
|
+
fetchLive?: boolean;
|
|
94
|
+
lotlUrl?: string;
|
|
95
|
+
fetchImpl?: typeof fetch;
|
|
96
|
+
/** Extra intermediate CA certs (base64 DER) to help BUILD the path when `x5c` omits them. HINTS ONLY —
|
|
97
|
+
* validated as path links, never a trust source. The anchor is always the TL-matched cert. */
|
|
98
|
+
intermediateHintsDer?: string[];
|
|
99
|
+
}
|
|
100
|
+
/** Input to validate one qualified delegation signature carried on a VAL root ASSIGNMENT. */
|
|
101
|
+
export interface QesValidationInput {
|
|
102
|
+
/** The canonical bytes the QES was computed over (the root ASSIGNMENT canonical_details). */
|
|
103
|
+
signedCanonical: string;
|
|
104
|
+
/** The qualified signature carried in `human_attestation.delegator_authority.signature`
|
|
105
|
+
* (a ValQesSignature { alg, signature } or a bare base64 JAdES string). */
|
|
106
|
+
signature: unknown;
|
|
107
|
+
/** Validation/signing time (defaults to now). For LTV, pass a chain anchor genTime (ADR 0062). */
|
|
108
|
+
validationTime?: string;
|
|
109
|
+
/** Trust material for the offline determination. */
|
|
110
|
+
trust: QesTrustInput;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Validate a QES → a reproducible `QesValidationReport`, with NO DSS and NO server (pure JS). The relying
|
|
114
|
+
* party runs this THEMSELVES; its output feeds `verifyValChain({ qesValidation: { reports: [...] } })`,
|
|
115
|
+
* so the core never trusts RIGA for "qualified" (Charter §II claim #12). See the file header for the
|
|
116
|
+
* documented ETSI subset and the never-silently-upgrade discipline.
|
|
117
|
+
*/
|
|
118
|
+
export declare function validateQes(input: QesValidationInput): Promise<QesValidationReport>;
|
|
119
|
+
interface CaQcMatch {
|
|
120
|
+
matched: boolean;
|
|
121
|
+
serviceName?: string;
|
|
122
|
+
statusStartingTimeMs?: number;
|
|
123
|
+
reason?: string;
|
|
124
|
+
}
|
|
125
|
+
export declare function matchGrantedCaQc(tslXml: string, issuerCaFingerprintHex: string, signingTimeMs: number): CaQcMatch;
|
|
126
|
+
export {};
|
|
127
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAkDH,8FAA8F;AAC9F,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,8GAA8G;AAC9G,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC;AAExE,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAClC,2GAA2G;IAC3G,SAAS,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,MAAM,EAAE,SAAS,CAAC;IAClB,+FAA+F;IAC/F,MAAM,EAAE,MAAM,CAAC;IACf;uGACmG;IACnG,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iFAAiF;IACjF,cAAc,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACzC,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAC;IACnB,2GAA2G;IAC3G,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0FAA0F;IAC1F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,6FAA6F;IAC7F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,0CAA0C;IAC1C,OAAO,EAAE,YAAY,CAAC;IACtB;;sCAEkC;IAClC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;mGAC+F;IAC/F,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,6FAA6F;IAC7F,eAAe,EAAE,MAAM,CAAC;IACxB;gFAC4E;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB,kGAAkG;IAClG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,KAAK,EAAE,aAAa,CAAC;CACtB;AAuCD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAezF;AA+iBD,UAAU,SAAS;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,CA+BjH"}
|