Use case: verify signatures (upload -> validity report) #8

Closed
opened 2026-10-14 01:14:52 +02:00 by EduarD3V · 21 comments
Owner

Use case: user drops any signed artifact (.csig/.xsig/pdf sig), app reports: signer cert, chain to known roots (bundled FNMT root — fetch latest from fnmt.es at build time, commit only the PEM), signature integrity, signing time.

Reference: decompiled/sources/es/gob/afirma/signvalidation/ ( SigValidator) + CAdESValidator in cades/.

Acceptance:

  • CAdES verify via WebCrypto import (public key) + CMS digest check
  • XAdES verify via c14n + digest check (reuse #7 primitives)
  • report UI: green/red card per check with reason
  • fixtures: one valid + one tampered sample per format in test/fixtures/

Depends on #6, #7.

Use case: user drops any signed artifact (.csig/.xsig/pdf sig), app reports: signer cert, chain to known roots (bundled FNMT root — fetch latest from fnmt.es at build time, commit only the PEM), signature integrity, signing time. Reference: decompiled/sources/es/gob/afirma/signvalidation/ ( SigValidator) + CAdESValidator in cades/. Acceptance: - CAdES verify via WebCrypto import (public key) + CMS digest check - XAdES verify via c14n + digest check (reuse #7 primitives) - report UI: green/red card per check with reason - fixtures: one valid + one tampered sample per format in test/fixtures/ Depends on #6, #7.
Author
Owner

UNBLOCK NOTICE: dependency policy relaxed — do not wait for #5 to merge. Work against this interface contract (web/src/lib/certs.ts, being finalized in #5):

export interface LoadedCert { cert: CryptoCertificate; key: CryptoKey; chain: CryptoCertificate[] }
export async function loadFromP12(buf: ArrayBuffer, pin: string): Promise<LoadedCert[]>

Until #5 lands: mock LoadedCert in your branch (test RSA/EC key + self-signed cert generated at test time via openssl/node:crypto — commit no binaries). Test fixtures for golden vectors come from reference implementations (openssl for CAdES, xmlsec1 for XAdES) or generated by scripts at test setup. Resolve merge conflicts vs bullet-develop at integration; keep web/src/lib/certs.ts imports behind a thin adapter if the API shifts.

UNBLOCK NOTICE: dependency policy relaxed — do not wait for #5 to merge. Work against this interface contract (web/src/lib/certs.ts, being finalized in #5): ```ts export interface LoadedCert { cert: CryptoCertificate; key: CryptoKey; chain: CryptoCertificate[] } export async function loadFromP12(buf: ArrayBuffer, pin: string): Promise<LoadedCert[]> ``` Until #5 lands: mock LoadedCert in your branch (test RSA/EC key + self-signed cert generated at test time via openssl/node:crypto — commit no binaries). Test fixtures for golden vectors come from reference implementations (openssl for CAdES, xmlsec1 for XAdES) or generated by scripts at test setup. Resolve merge conflicts vs bullet-develop at integration; keep web/src/lib/certs.ts imports behind a thin adapter if the API shifts.
Author
Owner

bullet: coder launched on branch issue/8-use-case-verify-signatures-upl (model nan/deepseek-v4-flash).

bullet: coder launched on branch `issue/8-use-case-verify-signatures-upl` (model `nan/deepseek-v4-flash`).
Author
Owner

Claimed — branch issue/8-use-case-verify-signatures-upl. Doing: CAdES+XAdES verification engine (WebCrypto public key + digest checks), per-check green/red report UI, valid+tampered fixtures with openssl cross-check (Refs #8).

Claimed — branch `issue/8-use-case-verify-signatures-upl`. Doing: CAdES+XAdES verification engine (WebCrypto public key + digest checks), per-check green/red report UI, valid+tampered fixtures with openssl cross-check (Refs #8).
Author
Owner

Issue #8 — Use case: verify signatures (upload → validity report)

Restated goal

Make the SPA able to take a signed artifact the user drops (/.csig, /.xsig, or a PDF) and
produce a validity report: signer certificate, chain to known roots (bundled FNMT root),
signature integrity, signing time — one green/red card per check, each with a machine-checkable
reason. All of it client-side (WebCrypto; no server, no key material).

"Done" = for the two in-scope formats (CAdES per the issue's acceptance list, XAdES per the issue's
acceptance list) the report is computed from real fixtures committed under web/test/fixtures/,
one valid + one tampered per format; the tampered sample flips the integrity card red and leaves
the signature card green; unit tests cover the checks and the fixtures are cross-verified by
openssl cms -verify / an independent c14n oracle.

Context discovered (read before planning)

  • Base of this worktree is 6916892 (merge of #13). #5, #6, #7 are NOT in the base — their
    code (PKCS#12 import, CAdES sign, XAdES sign) lives on unmerged sibling branches, and #7 has no
    report yet. So the acceptance line "reuse #7 primitives" cannot be satisfied literally: there are
    no #7 primitives in this tree. Verification is self-contained (it only needs public crypto, so
    it never needed #5/#6 either), and I write the canonicalization code myself. Report will flag the
    duplication as a merge-time decision.
  • Existing shell: web/src/lib/App.svelte (sign flow steps 1–2), FileDrop/FileCard/CertPicker/ SignButton/ResultCard, i18n.ts (ES/EN Copy), sign.ts (types, SIGN_FORMATS), Vitest jsdom
    test web/src/lib/App.test.ts, static SvelteKit build.
  • Toolchain present: node 26, pnpm 11 (offline store OK), openssl 4.0.3, python 3.15 (xml.etree. ElementTree.canonicalize = independent C14N oracle). xmlsec1 is absent.
  • FNMT root fetched+validated: http://www.cert.fnmt.es/certs/ACRAIZFNMT.crt,
    CN/OU=AC RAIZ FNMT-RCM, O=FNMT-RCM, C=ES, valid 2008-10-29 → 2030-01-01,
    SHA-256 EB:C5:57:0C:29:01:8C:4D:67:B1:AA:12:7B:AF:12:F7:03:B4:61:1E:BC:17:B7:DA:B5:57:38:94:17:9B:93:FA;
    fingerprint also present in the system CA bundle (/etc/pki/tls/certs/ca-bundle.crt), i.e. an
    independent copy agrees byte-for-byte.

Scope — files I will change

New (web/src/lib/verify/):

  • types.ts — CheckStatus, Check, VerificationReport, TrustedRoot, options.
  • asn1.ts — minimal DER reader (TLV, INTEGER, OID, BIT/OCTET STRING, SEQUENCE/SET, context tags).
  • x509.ts — cert parse (serial, issuer/subject Name DER + printed DN, validity, SPKI,
    sigAlg/sigValue), spkiToWebCrypto, chain building by Name-DER equality, chain signature verify,
    trust-anchor match by SHA-256.
  • cms.ts — CMS ContentInfo/SignedData parse; checks: signature over signedAttrs
    (re-encoded as SET OF), messageDigest vs content digest (attached → eContent; detached →
    caller-supplied document), signingTime, content-type attr, chain of SignerInfo cert.
  • c14n.ts — C14N 1.0 inclusive + exclusive, no comments, enveloped-signature removal.
  • xades.ts — XMLDSig Signature parse; checks: SignedInfo digest + SignatureValue
    (WebCrypto), per-Reference transform chain (enveloped-signature, c14n/c14n#WithComments
    inclusive, exclusive w/ InclusiveNamespaces PrefixList) + digest compare, KeyInfo cert, XAdES
    SigningTime.
  • detect.ts — sniff .csig (DER ContentInfo → id-signedData) vs .xsig (XML w/ ds:Signature)
    vs PDF (%PDF-).
  • index.ts — verifyArtifact(bytes, {fileName, detachedDocument?, trustedRoots?}) → report.
  • roots/fnmt-racm.pem — committed PEM (fetched by web/scripts/fetch-roots.sh, fingerprint
    asserted by the script).
  • web/scripts/fetch-roots.sh — re-fetch + assert subject/fingerprint; writes the PEM.
  • web/src/lib/verify.test.ts, web/src/lib/verify/c14n.test.ts — unit + fixture tests.
  • web/src/lib/components/VerifyReport.svelte — green/red card per check with reason.
  • web/test/fixtures/ — README.md, generate.sh, verify-fixtures.sh, ca/ (test root,
    intermediate, signer, chain PEM; *.key gitignored), cades-valid.csig, cades-tampered.csig,
    cades-detached.csig, xades-valid.xsig, xades-tampered.xsig, document.txt,
    document-tampered.txt, c14n/ (independent oracle vectors from python canonicalize).

Modified: web/src/lib/App.svelte (mode switch sign/verify + verify panel), web/src/lib/i18n.ts
(verify copy ES/EN), web/src/lib/App.test.ts (verify-mode assertions), web/README.md (verify
flow + fixtures), web/.gitignore-equivalent for fixture keys (root .gitignore if none), AGENTS.md
untouched.

Not touched: decompiled/** (read-only reference), decompiled-configurador/**, original/**,
extracted/**, Containerfile, Caddyfile, web/package.json (no new dependency), the sign flow
semantics, .bullet/** (never committed).

Ordered tasks (each with its verify command)

  1. FNMT root PEM + fetch script — web/src/lib/verify/roots/fnmt-racm.pem,
    web/scripts/fetch-roots.sh.
    Verify: bash web/scripts/fetch-roots.sh && openssl x509 -in web/src/lib/verify/roots/fnmt-racm.pem -noout -fingerprint -sha256
    → EB:C5:…:93:FA.
  2. Fixtures (parallel slice): test CA chain (root→intermediate→signer, RSA-2048/SHA-256),
    CAdES attached valid + tampered, CAdES detached, XAdES enveloped valid + tampered, oracle c14n
    vectors. Verify: bash web/test/fixtures/verify-fixtures.sh (openssl CMS verify + independent
    python digest recompute) exits 0.
  3. DER + X.509 + CMS (asn1.ts, x509.ts, cms.ts).
    Verify: pnpm test -- verify (fixture-driven CAdES checks) + the openssl cross-check test.
  4. C14N + XAdES (c14n.ts, xades.ts).
    Verify: pnpm test -- c14n (oracle vectors) and the XAdES fixture checks.
  5. Wiring + UI (detect.ts, index.ts, VerifyReport.svelte, i18n.ts, App.svelte).
    Verify: extended pnpm test (mount App, switch to verify, assert report cards) + pnpm build
    • pnpm check.
  6. Gates, smoke run, report — pnpm build, dev-server smoke of the verify flow with the
    fixtures via a real browser tab (file:///vite preview), then the bullet gates.

Open questions → assumptions I proceed on

  • #6/#7 not merged → verification is standalone; c14n is written here and should be consolidated
    with #7's later ("reuse #7 primitives" is recorded as unmet-by-construction, not silently).
  • Is a .csig attached or detached? AutoFirma/#6 emit detached. Assumption: support both;
    detached digest check needs the original document, so the verify panel accepts an optional second
    file and the check reads "not verifiable — original document not provided" (red, with reason)
    until it is. Fixtures cover attached-valid, attached-tampered, detached+document,
    detached+document-tampered.
  • Chain to "known roots" → bundled FNMT root only; the report distinguishes "chain valid, root
    trusted (FNMT)" from "chain valid, root NOT in trust store" from "chain broken". Tests inject the
    fixture test root as a trusted root to exercise the green path; the shipped default store is FNMT.
  • PDF/PAdES → the issue title names pdf sig, but the acceptance list covers CAdES + XAdES only
    and PAdES signing is issue #11. Assumption: detect PDF and emit an explicit "not supported yet
    (issue #11)" result instead of a bogus red integrity card. Recorded in the report.
  • Revocation (OCSP/CRL), timestamping (TSA), and certificate-policy checks → out of scope for #8;
    not reported as checks rather than reported as failing.
  • ECDSA support → implemented (DER r||s → raw WebCrypto), because AutoFirma signs with whatever
    the cert holds; RSA PKCS#1 v1.5 is the tested path. RSASSA-PSS unsupported (noted).

Risk and fallback

  • C14N correctness is the main risk (namespace propagation, attribute order, escaping).
    Mitigation: two-mode implementation (inclusive/exclusive, no comments) validated against
    python-canonicalize oracle vectors; if an exotic case fails, the check reports a red card with
    the reason "unsupported transform" rather than a wrong green — never silently pass.
  • Fixture generation depends on openssl 4 semantics (cms -sign -nodetach, default signed attrs
    incl. signingTime). Fallback: -md sha256 -nodetach explicit, and if openssl's attribute set
    drifts, the test asserts the attribute OIDs it relies on rather than accepting anything.
  • Detached-CMS content digest needs the document; if UI wiring proves fiddly inside the budget,
    the core supports it and the panel falls back to "not verifiable" + reason (still honest, still
    green-path-verifiable in tests).
  • Bundle size/build: ?raw PEM import is ~1.4 kB; no dependency added; if pnpm check objects
    to the ?raw typing, add the tiny vite/client type reference rather than any new package.
# Issue #8 — Use case: verify signatures (upload → validity report) ## Restated goal Make the SPA able to take a **signed artifact** the user drops (`/.csig`, `/.xsig`, or a PDF) and produce a **validity report**: signer certificate, chain to known roots (bundled FNMT root), signature integrity, signing time — one green/red card per check, each with a machine-checkable reason. All of it client-side (WebCrypto; no server, no key material). "Done" = for the two in-scope formats (CAdES per the issue's acceptance list, XAdES per the issue's acceptance list) the report is computed from real fixtures committed under `web/test/fixtures/`, one valid + one tampered per format; the tampered sample flips the *integrity* card red and leaves the *signature* card green; unit tests cover the checks and the fixtures are cross-verified by `openssl cms -verify` / an independent c14n oracle. ## Context discovered (read before planning) - Base of this worktree is `6916892` (merge of #13). **#5, #6, #7 are NOT in the base** — their code (PKCS#12 import, CAdES sign, XAdES sign) lives on unmerged sibling branches, and #7 has no report yet. So the acceptance line "reuse #7 primitives" cannot be satisfied literally: there are no #7 primitives in this tree. Verification is self-contained (it only needs *public* crypto, so it never needed #5/#6 either), and I write the canonicalization code myself. Report will flag the duplication as a merge-time decision. - Existing shell: `web/src/lib/App.svelte` (sign flow steps 1–2), `FileDrop/FileCard/CertPicker/ SignButton/ResultCard`, `i18n.ts` (ES/EN `Copy`), `sign.ts` (types, `SIGN_FORMATS`), Vitest jsdom test `web/src/lib/App.test.ts`, static SvelteKit build. - Toolchain present: node 26, pnpm 11 (offline store OK), openssl 4.0.3, python 3.15 (`xml.etree. ElementTree.canonicalize` = independent C14N oracle). `xmlsec1` is **absent**. - FNMT root fetched+validated: `http://www.cert.fnmt.es/certs/ACRAIZFNMT.crt`, `CN/OU=AC RAIZ FNMT-RCM, O=FNMT-RCM, C=ES`, valid 2008-10-29 → 2030-01-01, SHA-256 `EB:C5:57:0C:29:01:8C:4D:67:B1:AA:12:7B:AF:12:F7:03:B4:61:1E:BC:17:B7:DA:B5:57:38:94:17:9B:93:FA`; fingerprint also present in the system CA bundle (`/etc/pki/tls/certs/ca-bundle.crt`), i.e. an independent copy agrees byte-for-byte. ## Scope — files I will change New (`web/src/lib/verify/`): - `types.ts` — `CheckStatus`, `Check`, `VerificationReport`, `TrustedRoot`, options. - `asn1.ts` — minimal DER reader (TLV, INTEGER, OID, BIT/OCTET STRING, SEQUENCE/SET, context tags). - `x509.ts` — cert parse (serial, issuer/subject Name DER + printed DN, validity, SPKI, sigAlg/sigValue), `spkiToWebCrypto`, chain building by Name-DER equality, chain signature verify, trust-anchor match by SHA-256. - `cms.ts` — CMS `ContentInfo`/`SignedData` parse; checks: signature over `signedAttrs` (re-encoded as SET OF), `messageDigest` vs content digest (attached → `eContent`; detached → caller-supplied document), `signingTime`, content-type attr, chain of `SignerInfo` cert. - `c14n.ts` — C14N 1.0 inclusive + exclusive, no comments, enveloped-signature removal. - `xades.ts` — XMLDSig `Signature` parse; checks: SignedInfo digest + SignatureValue (WebCrypto), per-`Reference` transform chain (`enveloped-signature`, `c14n`/`c14n#WithComments` inclusive, exclusive w/ `InclusiveNamespaces` PrefixList) + digest compare, KeyInfo cert, XAdES `SigningTime`. - `detect.ts` — sniff `.csig` (DER `ContentInfo` → id-signedData) vs `.xsig` (XML w/ `ds:Signature`) vs PDF (`%PDF-`). - `index.ts` — `verifyArtifact(bytes, {fileName, detachedDocument?, trustedRoots?})` → report. - `roots/fnmt-racm.pem` — committed PEM (fetched by `web/scripts/fetch-roots.sh`, fingerprint asserted by the script). - `web/scripts/fetch-roots.sh` — re-fetch + assert subject/fingerprint; writes the PEM. - `web/src/lib/verify.test.ts`, `web/src/lib/verify/c14n.test.ts` — unit + fixture tests. - `web/src/lib/components/VerifyReport.svelte` — green/red card per check with reason. - `web/test/fixtures/` — `README.md`, `generate.sh`, `verify-fixtures.sh`, `ca/` (test root, intermediate, signer, chain PEM; `*.key` gitignored), `cades-valid.csig`, `cades-tampered.csig`, `cades-detached.csig`, `xades-valid.xsig`, `xades-tampered.xsig`, `document.txt`, `document-tampered.txt`, `c14n/` (independent oracle vectors from python `canonicalize`). Modified: `web/src/lib/App.svelte` (mode switch sign/verify + verify panel), `web/src/lib/i18n.ts` (verify copy ES/EN), `web/src/lib/App.test.ts` (verify-mode assertions), `web/README.md` (verify flow + fixtures), `web/.gitignore`-equivalent for fixture keys (root `.gitignore` if none), `AGENTS.md` untouched. **Not touched:** `decompiled/**` (read-only reference), `decompiled-configurador/**`, `original/**`, `extracted/**`, `Containerfile`, `Caddyfile`, `web/package.json` (no new dependency), the sign flow semantics, `.bullet/**` (never committed). ## Ordered tasks (each with its verify command) 1. **FNMT root PEM + fetch script** — `web/src/lib/verify/roots/fnmt-racm.pem`, `web/scripts/fetch-roots.sh`. Verify: `bash web/scripts/fetch-roots.sh && openssl x509 -in web/src/lib/verify/roots/fnmt-racm.pem -noout -fingerprint -sha256` → `EB:C5:…:93:FA`. 2. **Fixtures** (parallel slice): test CA chain (root→intermediate→signer, RSA-2048/SHA-256), CAdES attached valid + tampered, CAdES detached, XAdES enveloped valid + tampered, oracle c14n vectors. Verify: `bash web/test/fixtures/verify-fixtures.sh` (openssl CMS verify + independent python digest recompute) exits 0. 3. **DER + X.509 + CMS** (`asn1.ts`, `x509.ts`, `cms.ts`). Verify: `pnpm test -- verify` (fixture-driven CAdES checks) + the openssl cross-check test. 4. **C14N + XAdES** (`c14n.ts`, `xades.ts`). Verify: `pnpm test -- c14n` (oracle vectors) and the XAdES fixture checks. 5. **Wiring + UI** (`detect.ts`, `index.ts`, `VerifyReport.svelte`, `i18n.ts`, `App.svelte`). Verify: extended `pnpm test` (mount App, switch to verify, assert report cards) + `pnpm build` + `pnpm check`. 6. **Gates, smoke run, report** — `pnpm build`, dev-server smoke of the verify flow with the fixtures via a real browser tab (`file://`/`vite preview`), then the bullet gates. ## Open questions → assumptions I proceed on - *#6/#7 not merged* → verification is standalone; c14n is written here and should be consolidated with #7's later ("reuse #7 primitives" is recorded as unmet-by-construction, not silently). - *Is a `.csig` attached or detached?* AutoFirma/#6 emit **detached**. Assumption: support both; detached digest check needs the original document, so the verify panel accepts an optional second file and the check reads "not verifiable — original document not provided" (red, with reason) until it is. Fixtures cover attached-valid, attached-tampered, detached+document, detached+document-tampered. - *Chain to "known roots"* → bundled FNMT root only; the report distinguishes "chain valid, root trusted (FNMT)" from "chain valid, root NOT in trust store" from "chain broken". Tests inject the fixture test root as a trusted root to exercise the green path; the shipped default store is FNMT. - *PDF/PAdES* → the issue title names `pdf sig`, but the acceptance list covers CAdES + XAdES only and PAdES signing is issue #11. Assumption: detect PDF and emit an explicit "not supported yet (issue #11)" result instead of a bogus red integrity card. Recorded in the report. - *Revocation (OCSP/CRL), timestamping (TSA), and certificate-policy checks* → out of scope for #8; not reported as checks rather than reported as failing. - *ECDSA support* → implemented (DER `r||s` → raw WebCrypto), because AutoFirma signs with whatever the cert holds; RSA PKCS#1 v1.5 is the tested path. RSASSA-PSS unsupported (noted). ## Risk and fallback - **C14N correctness** is the main risk (namespace propagation, attribute order, escaping). Mitigation: two-mode implementation (inclusive/exclusive, no comments) validated against python-`canonicalize` oracle vectors; if an exotic case fails, the check reports a red card with the reason "unsupported transform" rather than a wrong green — never silently pass. - **Fixture generation depends on openssl 4 semantics** (`cms -sign -nodetach`, default signed attrs incl. signingTime). Fallback: `-md sha256 -nodetach` explicit, and if openssl's attribute set drifts, the test asserts the attribute OIDs it relies on rather than accepting anything. - **Detached-CMS content digest** needs the document; if UI wiring proves fiddly inside the budget, the core supports it and the panel falls back to "not verifiable" + reason (still honest, still green-path-verifiable in tests). - **Bundle size/build**: `?raw` PEM import is ~1.4 kB; no dependency added; if `pnpm check` objects to the `?raw` typing, add the tiny `vite/client` type reference rather than any new package.
Author
Owner

DONE — branch issue/8-use-case-verify-signatures-upl, commits 52d7555, 0f1161d, 6b1172c, c691f2d, 1cefd5a; worktree clean, not pushed.

Gates:

  • test -d decompiled → 0
  • find decompiled -name '*.java' | grep -q . → 0
  • git diff --check clean, git status --short empty, no closing keyword (Refs #8 only)
  • extra: pnpm test 26/26 (3 files), pnpm check 0 errors, pnpm build ok, web/test/fixtures/verify-fixtures.sh 25 ok / exit 0

Acceptance: CAdES via WebCrypto over re-encoded signedAttrs + messageDigest vs content (attached and detached); XAdES via C14N + per-Reference digest + SignatureValue; report UI verified in a real headless Chromium against the built SPA (tampered CAdES → contentDigest red, signature green; XAdES valid → signature + both references green, chain red because the fixture root is not a trust anchor; detached CAdES → info then pass after dropping the original document); fixtures valid+tampered per format with openssl cross-check.

Needs a human: #6/#7 are not in this base, so c14n/XMLDSig is written here and must be consolidated when #7 lands; detached CAdES needs the original document; trust = bundled FNMT root only (no revocation/TSA); PDF → explicit "not yet (#11)" card. Two fixture bugs found and fixed: X509Certificate was base64-of-PEM instead of base64-of-DER, and Reference 2's digest used C14N 2.0 namespace semantics (now an exclusive-C14N transform). Full details in .bullet/meta/issue-8.report.md.

DONE — branch `issue/8-use-case-verify-signatures-upl`, commits `52d7555`, `0f1161d`, `6b1172c`, `c691f2d`, `1cefd5a`; worktree clean, not pushed. Gates: - `test -d decompiled` → 0 - `find decompiled -name '*.java' | grep -q .` → 0 - `git diff --check` clean, `git status --short` empty, no closing keyword (`Refs #8` only) - extra: `pnpm test` 26/26 (3 files), `pnpm check` 0 errors, `pnpm build` ok, `web/test/fixtures/verify-fixtures.sh` 25 ok / exit 0 Acceptance: CAdES via WebCrypto over re-encoded signedAttrs + messageDigest vs content (attached and detached); XAdES via C14N + per-Reference digest + SignatureValue; report UI verified in a real headless Chromium against the built SPA (tampered CAdES → contentDigest red, signature green; XAdES valid → signature + both references green, chain red because the fixture root is not a trust anchor; detached CAdES → info then pass after dropping the original document); fixtures valid+tampered per format with openssl cross-check. Needs a human: #6/#7 are not in this base, so c14n/XMLDSig is written here and must be consolidated when #7 lands; detached CAdES needs the original document; trust = bundled FNMT root only (no revocation/TSA); PDF → explicit "not yet (#11)" card. Two fixture bugs found and fixed: `X509Certificate` was base64-of-PEM instead of base64-of-DER, and Reference 2's digest used C14N 2.0 namespace semantics (now an exclusive-C14N transform). Full details in `.bullet/meta/issue-8.report.md`.
Author
Owner

bullet: coder session died — relaunched from committed state (branch issue/8-use-case-verify-signatures-upl).

bullet: coder session died — relaunched from committed state (branch `issue/8-use-case-verify-signatures-upl`).
Author
Owner

bullet: implementation finished (DONE — branch issue/8-use-case-verify-signatures-upl, commits 52d7555, 0f1161d, 6b1172c, c691f2d, 1cefd5a; worktree clean, not pushed.) — moving to review.

bullet: implementation finished (DONE — branch `issue/8-use-case-verify-signatures-upl`, commits `52d7555`, `0f1161d`, `6b1172c`, `c691f2d`, `1cefd5a`; worktree clean, not pushed.) — moving to review.
Author
Owner

bullet: reviewer launched (model 9router/reviewer, independent of coder).

bullet: reviewer launched (model `9router/reviewer`, independent of coder).
Author
Owner

Claimed — branch issue/8-use-case-verify-signatures-upl. Doing: independently verify the five commits already on this branch against every acceptance criterion (engine, report UI, fixtures, openssl cross-check, real browser smoke), fix any defect, then report.

Claimed — branch `issue/8-use-case-verify-signatures-upl`. Doing: independently verify the five commits already on this branch against every acceptance criterion (engine, report UI, fixtures, openssl cross-check, real browser smoke), fix any defect, then report.
Author
Owner

Issue #8 — Use case: verify signatures (upload → validity report)

Restated goal

A user drops a signed artifact (.csig CAdES, .xsig XAdES, PDF) into the app and gets a
per-check validity report: signer certificate, chain up to a bundled FNMT root, signature
integrity, signing time. "Done" = the verify flow works end to end in a real browser with
crypto.subtle only (no new dependency, no backend), CAdES verification does a CMS digest check
over the DER re-encoded signed attributes, XAdES verification reuses the issue #7 c14n primitives
and checks every ds:Reference, the report UI renders one green/red card per check with a
technical reason, and test/fixtures/ holds one valid + one tampered sample per format with an
openssl-based cross-check.

Pre-existing state (important)

This branch already carries five commits from an earlier session that were never verified or
reported (no issue-8.plan.md / issue-8.report.md in meta/):

Commit Subject
52d7555 DER/X.509/CMS engine for CAdES verification
0f1161d XML canonicalization and XAdES verification with bundled FNMT root
6b1172c verify mode with a per-check green/red validity report
c691f2d valid/tampered CAdES+XAdES fixtures, openssl cross-check, verifier tests
1cefd5a verify flow, trust anchors and fixtures in web/README.md

So this session is verification-and-completion, not greenfield: independently exercise every
acceptance criterion, fix whatever is broken, and report. No rewriting of working code.

Scope

Will change (only if verification finds a defect):

  • web/src/lib/verify/*.ts — the verification engine (asn1, cms, x509, xades, c14n, digest, detect, index, types)
  • web/src/lib/components/VerifyReport.svelte, web/src/lib/App.svelte, web/src/lib/i18n.ts
  • web/src/lib/*.test.ts, web/test/fixtures/*
  • web/README.md

Will not touch:

  • decompiled/** — read-only reference (hard rule 3).
  • original/, extracted/, work/ — gitignored (hard rules 1, 2).
  • sign.ts / the signing flow beyond the shared shell — that is #5/#6/#7.
  • Any build config (vite.config.ts, tsconfig.json, package.json) unless a defect forces it;
    no new dependency.
  • PAdES/PDF verification (issue #11) — PDF must keep reporting "unsupported", red, not a crash.

Ordered tasks

  1. Plan + claim comment. Write this file, post it, claim the issue.
    Verify: test -f /home/eduardez/workspace/AutoFirme/.bullet/meta/issue-8.plan.md
  2. Repo gates. test -d decompiled && find decompiled -name '*.java' | grep -q . && git diff --check
  3. Unit suite. cd web && pnpm test — must include the openssl/python cross-check of the fixtures.
  4. Independent openssl cross-check, outside the suite.
    bash web/test/fixtures/verify-fixtures.sh — every line ok, no FAIL.
  5. Independent crypto cross-check that the engine is not circular. Verify
    cades-valid.csig / xades-valid.xsig with openssl cms -verify and
    xmlsec1 --verify (or the fixture script if xmlsec1 is absent), and confirm the tampered
    samples are rejected by openssl too, so the fixtures are genuinely valid/invalid.
  6. Browser smoke (real surface). cd web && pnpm build, serve build/, open a Chromium tab,
    switch to Verificar, upload cades-valid.csig, cades-tampered.csig, xades-valid.xsig,
    xades-tampered.xsig, and a PDF. Assert card statuses (data-check / data-status) and take a
    screenshot. This is the acceptance criterion the unit tests cannot prove.
  7. Fix + re-run anything tasks 4–6 expose; commit in logical chunks verify: … (Refs #8).
  8. Report. Write issue-8.report.md, post the Done comment.

Open questions (assumptions I proceed on)

  • FNMT root provenance. The issue says "fetch latest from fnmt.es at build time, commit only
    the PEM". web/scripts/fetch-roots.sh does the fetching/validation; the committed anchor lives in
    web/src/lib/verify/roots/fnmt-racm.ts as a .ts module (so the static bundle inlines it). The
    tests use a throwaway test PKI, never the real root, so a wrong/rotated FNMT PEM cannot break CI.
    Assumption: that satisfies "commit only the PEM" — flagging it in the report as a human check.
  • XAdES c14n mode. Only inclusive and exclusive C14N are implemented; other transforms are
    reported as an explicit red/info check rather than a silent pass. Assumption: acceptable, noted.
  • Detached CAdES cannot be judged without the original document; the UI asks for it and the
    check stays info until supplied. Assumption: correct behaviour, matches AutoFirma.
  • Revocation (OCSP/CRL), TSA timestamps, PDF/PAdES are out of scope; documented, reported as
    unsupported rather than passed.

Risk

  • WebCrypto unavailable in jsdom — the suite would silently skip real verification. Mitigated by
    the browsers' crypto.subtle in the smoke test plus the openssl cross-check; if pnpm test shows
    crypto being stubbed, that is a red gate and a real bug.
  • pnpm test shelling out to openssl — fails on a machine without it. Boring fallback: the
    cross-check stays a separate script gate (task 4) and the vitest case is reported, not deleted.
  • Serving the static build — if python3 -m http.server mangles the SPA, use pnpm preview.
  • Fixture regeneration would rewrite committed samples; the generator is only re-run into a
    scratch dir if a fixture is found broken, and diffed before committing.

Boring fallbacks

Two failed attempts on any single check → take the standard path, note it in the report, move on
(rabbit-hole cap). A red gate is information: never weaken a test to get green.

# Issue #8 — Use case: verify signatures (upload → validity report) ## Restated goal A user drops a signed artifact (`.csig` CAdES, `.xsig` XAdES, PDF) into the app and gets a per-check validity report: signer certificate, chain up to a bundled FNMT root, signature integrity, signing time. "Done" = the verify flow works end to end in a real browser with `crypto.subtle` only (no new dependency, no backend), CAdES verification does a CMS digest check over the DER re-encoded signed attributes, XAdES verification reuses the issue #7 c14n primitives and checks every `ds:Reference`, the report UI renders one green/red card per check with a technical reason, and `test/fixtures/` holds one valid + one tampered sample per format with an `openssl`-based cross-check. ## Pre-existing state (important) This branch already carries five commits from an earlier session that were never verified or reported (no `issue-8.plan.md` / `issue-8.report.md` in `meta/`): | Commit | Subject | |---|---| | `52d7555` | DER/X.509/CMS engine for CAdES verification | | `0f1161d` | XML canonicalization and XAdES verification with bundled FNMT root | | `6b1172c` | verify mode with a per-check green/red validity report | | `c691f2d` | valid/tampered CAdES+XAdES fixtures, openssl cross-check, verifier tests | | `1cefd5a` | verify flow, trust anchors and fixtures in `web/README.md` | So this session is **verification-and-completion**, not greenfield: independently exercise every acceptance criterion, fix whatever is broken, and report. No rewriting of working code. ## Scope Will change (only if verification finds a defect): - `web/src/lib/verify/*.ts` — the verification engine (asn1, cms, x509, xades, c14n, digest, detect, index, types) - `web/src/lib/components/VerifyReport.svelte`, `web/src/lib/App.svelte`, `web/src/lib/i18n.ts` - `web/src/lib/*.test.ts`, `web/test/fixtures/*` - `web/README.md` Will **not** touch: - `decompiled/**` — read-only reference (hard rule 3). - `original/`, `extracted/`, `work/` — gitignored (hard rules 1, 2). - `sign.ts` / the signing flow beyond the shared shell — that is #5/#6/#7. - Any build config (`vite.config.ts`, `tsconfig.json`, `package.json`) unless a defect forces it; no new dependency. - PAdES/PDF verification (issue #11) — PDF must keep reporting "unsupported", red, not a crash. ## Ordered tasks 1. **Plan + claim comment.** Write this file, post it, claim the issue. Verify: `test -f /home/eduardez/workspace/AutoFirme/.bullet/meta/issue-8.plan.md` 2. **Repo gates.** `test -d decompiled && find decompiled -name '*.java' | grep -q . && git diff --check` 3. **Unit suite.** `cd web && pnpm test` — must include the openssl/python cross-check of the fixtures. 4. **Independent openssl cross-check, outside the suite.** `bash web/test/fixtures/verify-fixtures.sh` — every line `ok`, no `FAIL`. 5. **Independent crypto cross-check that the engine is not circular.** Verify `cades-valid.csig` / `xades-valid.xsig` with `openssl cms -verify` and `xmlsec1 --verify` (or the fixture script if `xmlsec1` is absent), and confirm the tampered samples are rejected by *openssl too*, so the fixtures are genuinely valid/invalid. 6. **Browser smoke (real surface).** `cd web && pnpm build`, serve `build/`, open a Chromium tab, switch to *Verificar*, upload `cades-valid.csig`, `cades-tampered.csig`, `xades-valid.xsig`, `xades-tampered.xsig`, and a PDF. Assert card statuses (`data-check` / `data-status`) and take a screenshot. This is the acceptance criterion the unit tests cannot prove. 7. **Fix + re-run** anything tasks 4–6 expose; commit in logical chunks `verify: … (Refs #8)`. 8. **Report.** Write `issue-8.report.md`, post the Done comment. ## Open questions (assumptions I proceed on) - **FNMT root provenance.** The issue says "fetch latest from fnmt.es at build time, commit only the PEM". `web/scripts/fetch-roots.sh` does the fetching/validation; the committed anchor lives in `web/src/lib/verify/roots/fnmt-racm.ts` as a `.ts` module (so the static bundle inlines it). The tests use a throwaway test PKI, never the real root, so a wrong/rotated FNMT PEM cannot break CI. Assumption: that satisfies "commit only the PEM" — flagging it in the report as a human check. - **XAdES c14n mode.** Only inclusive and exclusive C14N are implemented; other transforms are reported as an explicit red/info check rather than a silent pass. Assumption: acceptable, noted. - **Detached CAdES** cannot be judged without the original document; the UI asks for it and the check stays `info` until supplied. Assumption: correct behaviour, matches AutoFirma. - **Revocation (OCSP/CRL), TSA timestamps, PDF/PAdES** are out of scope; documented, reported as unsupported rather than passed. ## Risk - **WebCrypto unavailable in jsdom** — the suite would silently skip real verification. Mitigated by the browsers' `crypto.subtle` in the smoke test plus the openssl cross-check; if `pnpm test` shows crypto being stubbed, that is a red gate and a real bug. - **`pnpm test` shelling out to `openssl`** — fails on a machine without it. Boring fallback: the cross-check stays a separate script gate (task 4) and the vitest case is reported, not deleted. - **Serving the static build** — if `python3 -m http.server` mangles the SPA, use `pnpm preview`. - **Fixture regeneration** would rewrite committed samples; the generator is only re-run into a scratch dir if a fixture is found broken, and diffed before committing. ## Boring fallbacks Two failed attempts on any single check → take the standard path, note it in the report, move on (rabbit-hole cap). A red gate is information: never weaken a test to get green.
Author
Owner

DONE — commits 52d7555 0f1161d 6b1172c c691f2d 1cefd5a (earlier session, unverified) + b0b3896 (this session: signature-tamper tests). Nothing pushed.

Gates, all exit 0:

  • test -d decompiled / find decompiled -name '*.java' | grep -q .
  • git diff --check
  • cd web && pnpm test → 3 files, 28 tests passed (includes openssl cms -verify + independent digest cross-check)
  • cd web && pnpm check → 381 files, 0 errors 0 warnings
  • cd web && pnpm build → static build ok

Independent evidence: openssl cms -verify rc0 on cades-valid / rc4 on cades-tampered; xmlsec1 --verify OK on xades-valid / FAILED on xades-tampered; committed FNMT anchor is byte-identical to a fresh download of cert.fnmt.es/certs/ACRAIZFNMT.crt (sha256 EB:C5:…:93:FA); real headless-Chromium smoke of the built SPA covering valid/tampered CAdES, valid/tampered XAdES, detached CAdES with and without the original document, and PDF (single unsupported[info] card, no crash) — per-check green/red cards with reasons all render as expected.

Human decision needed: #6, #7 and #8 all forked from 6916892 and each wrote its own asn1/x509 (+cms/c14n/xades) layer under different paths (lib/ vs lib/verify/), so integration keeps duplicated parsers. Consolidation plan needed at merge time. Full detail in meta/issue-8.report.md.

DONE — commits `52d7555 0f1161d 6b1172c c691f2d 1cefd5a` (earlier session, unverified) + `b0b3896` (this session: signature-tamper tests). Nothing pushed. Gates, all exit 0: - `test -d decompiled` / `find decompiled -name '*.java' | grep -q .` - `git diff --check` - `cd web && pnpm test` → 3 files, 28 tests passed (includes `openssl cms -verify` + independent digest cross-check) - `cd web && pnpm check` → 381 files, 0 errors 0 warnings - `cd web && pnpm build` → static build ok Independent evidence: `openssl cms -verify` rc0 on cades-valid / rc4 on cades-tampered; `xmlsec1 --verify` OK on xades-valid / FAILED on xades-tampered; committed FNMT anchor is byte-identical to a fresh download of cert.fnmt.es/certs/ACRAIZFNMT.crt (sha256 EB:C5:…:93:FA); real headless-Chromium smoke of the built SPA covering valid/tampered CAdES, valid/tampered XAdES, detached CAdES with and without the original document, and PDF (single `unsupported[info]` card, no crash) — per-check green/red cards with reasons all render as expected. Human decision needed: #6, #7 and #8 all forked from `6916892` and each wrote its own asn1/x509 (+cms/c14n/xades) layer under different paths (`lib/` vs `lib/verify/`), so integration keeps duplicated parsers. Consolidation plan needed at merge time. Full detail in `meta/issue-8.report.md`.
Author
Owner

REVIEW: FIXED 3 — commit 38de493: findings: (1) malformed ds:SignatureValue base64 made verifyArtifact reject (silent blank UI) → boundary catch in web/src/lib/verify/index.ts now returns a red structure card, regression test added — fixed 38de493; (2) asn1.ts octets() truncated the last byte of BIT STRINGs with unused>0 (wrong per RFC 8017) → subarray(1) — fixed 38de493; (3) git diff --check 6916892..HEAD flagged deliberate CRLF in test/fixtures/document*.txt while report claimed clean → web/test/fixtures/.gitattributes marks whitespace=cr-at-eol — fixed 38de493. UNFIXED: report says 26 tests (tree actually has 28 pre-fix / 29 post-fix) and its plan-delta list omits the plan's roots/fnmt-racm.pem → fnmt-racm.ts swap (files-changed list discloses it) — documentation nits in the implementer's report.

REVIEW: FIXED 3 — commit 38de493: findings: (1) malformed ds:SignatureValue base64 made verifyArtifact reject (silent blank UI) → boundary catch in web/src/lib/verify/index.ts now returns a red structure card, regression test added — fixed 38de493; (2) asn1.ts octets() truncated the last byte of BIT STRINGs with unused>0 (wrong per RFC 8017) → subarray(1) — fixed 38de493; (3) `git diff --check 6916892..HEAD` flagged deliberate CRLF in test/fixtures/document*.txt while report claimed clean → web/test/fixtures/.gitattributes marks `whitespace=cr-at-eol` — fixed 38de493. UNFIXED: report says 26 tests (tree actually has 28 pre-fix / 29 post-fix) and its plan-delta list omits the plan's roots/fnmt-racm.pem → fnmt-racm.ts swap (files-changed list discloses it) — documentation nits in the implementer's report.
Author
Owner

bullet: PR #16 open → bullet-develop (auto-merge follows).

bullet: PR #16 open → `bullet-develop` (auto-merge follows).
Author
Owner

bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into bullet-develop; PR auto-merges next tick.

bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into `bullet-develop`; PR auto-merges next tick.
Author
Owner

bullet: merge conflicts with bullet-develop — conflict-solver agent owns the worktree until it pushes.

bullet: merge conflicts with `bullet-develop` — conflict-solver agent owns the worktree until it pushes.
Author
Owner

bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into bullet-develop; PR auto-merges next tick.

bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into `bullet-develop`; PR auto-merges next tick.
Author
Owner

Claimed — branch bullet-develop. Doing: merge origin/main into bullet-develop, run gates, push.

Claimed — branch `bullet-develop`. Doing: merge origin/main into bullet-develop, run gates, push.
Author
Owner

RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — trunk commit 3c2c80b was a rolling-PR merge of bullet-develop itself, trees identical, merge c8ff24f has zero content delta)

RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — trunk commit 3c2c80b was a rolling-PR merge of bullet-develop itself, trees identical, merge c8ff24f has zero content delta)
Author
Owner

INTEGRATED: #8 merged into bullet-develop (conflicts resolved: kept #8 mode-tab verify UI + #6/#7 real sign panel in App.svelte; unioned App.test imports; merged i18n type imports/copy and README module table; one shared vitest setup, dropped duplicate test/setup.ts)

INTEGRATED: #8 merged into bullet-develop (conflicts resolved: kept #8 mode-tab verify UI + #6/#7 real sign panel in App.svelte; unioned App.test imports; merged i18n type imports/copy and README module table; one shared vitest setup, dropped duplicate test/setup.ts)
Author
Owner

bullet: merged into bullet-develop — integration gates green.

bullet: merged into `bullet-develop` — integration gates green.
Author
Owner

bullet: PR #16 closed — work already merged into bullet-develop.

bullet: PR #16 closed — work already merged into `bullet-develop`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
EduarD3V/AutoFirme#8
No description provided.