Use case: XAdES sign in browser (enveloped XML) #7
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
EduarD3V/AutoFirme#7
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Use case: sign XML documents (e.g. facturae) producing XAdES-EPES enveloped signature, downloadable .xsig.
Reference: decompiled/sources/es/gob/afirma/signers/xades/ (23 classes) + xmldsig/. Java used Apache Santuario; browser: implement canonicalization (c14n11) + dsig transform chain in TS — hardest part, keep scope: c14n 20010315 + exclusive c14n only.
Acceptance:
Depends on #5.
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):
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.
bullet: coder launched on branch
issue/7-use-case-xades-sign-in-browser(modelnan/deepseek-v4-flash).Claimed — branch
issue/7-use-case-xades-sign-in-browser. Doing: c14n (C14N 1.0 + exclusive) in TS + XAdES-EPES enveloped builder, verified with xmlsec1 --verify.Plan — Issue #7: Use case: XAdES sign in browser (enveloped XML)
Restated goal
A user picks an XML document (e.g. a Facturae invoice), picks a signing certificate loaded
from a PKCS#12 (#5), picks the
XAdES-EPESformat, and gets a.xsigfile: the original XMLwith an enveloped
ds:Signatureinserted at the document root, carrying XAdES signedproperties, a
KeyInfoholding the certificate chain from #5, and aSignatureValuecomputedwith WebCrypto over the canonicalized
ds:SignedInfo."Done" = the emitted
.xsigverifies withxmlsec1 --verify(exit 0) against the fixture CA,the signature is a real WebCrypto signature over our own canonicalization of
SignedInfo, theKeyInfocarries the DER chain of the loaded P12, and the document structure (element order,URIs, XAdES version) matches the reference AutoFirma output shape. Canonicalization scope is
C14N 1.0 (20010315) + Exclusive C14N only, as the issue says.
Scope
Files I will change (all under
web/):web/src/lib/c14n.ts— canonical XML serializer (C14N 1.0 + exclusive, commentsexcluded), over DOM nodes: namespace propagation, attribute ordering, escaping, CR handling.
web/src/lib/xades.ts— XAdES-EPES enveloped signature builder:ds:Signaturetemplate, digest references, WebCrypto digests +
signBytes(#5), Base64,.xsigbytes.web/src/lib/c14n.test.ts,web/src/lib/xades.test.ts— c14n golden vectors and thexmlsec1 --verifyend-to-end check (plus an openssl cross-check of the signature value).web/src/lib/sign.ts— format/extension plumbing for the XAdES engine (seam only).web/src/lib/App.svelte,web/src/lib/i18n.ts,web/src/lib/components/SignButton.svelte— wire the "Firmar" button for XAdES: sign and download the
.xsig. Kept minimal and sharedwith #6/#8 (the button had no handler yet).
web/README.md— status row for XAdES.Files I will not touch:
decompiled/**(read-only reference),original/,extracted/,docs/**,.bullet/**,Containerfile, anything outsideweb/. No new runtime dependency(the canonicalizer is written in TS on purpose — that is the point of the issue).
Base-branch note (dependency on #5)
My worktree branched from
mainat6916892, which does not contain #5 even though #7depends on it (#5 is DONE on its own branch, unmerged). I fast-forwarded my branch onto
issue/5-use-case-load-signing-cert-fro(27d86a5) before the first edit: no merge commit,my branch is exactly
main + #5's 4 commits + my commits. Consequence for the reviewer: thePR diff will also show #5's files (
asn1.ts,pkcs12.ts,x509.ts,keystore.ts, …), whichare byte-identical to the #5 branch. Without #5 there is no certificate, no
CryptoKeyand noKeyInfochain, so the issue cannot be implemented or verified at all.Ordered tasks (each with its verifying command)
c14n.ts(exclusive + inclusive, DOM walk, spec ordering/escaping).Verify:
cd web && pnpm vitest run src/lib/c14n.test.ts— golden vectors that were producedindependently by
lxml(method="c14n", exclusive variant) and pasted into the test.xades.ts(template, 2 references, digests via WebCrypto, signaturevia
signBytes, Base64,KeyInfo/X509Datachain).Verify:
cd web && pnpm vitest run src/lib/xades.test.ts.xmlsec1 --verify --trusted-pem ca.pem --enabled-key-data x509 out.xsig→ exit 0,OK.Plus
openssl dgst -sha256 -verify leaf.pub.pemover our canonicalSignedInfobytes.Verify: same test file,
pnpm vitest run src/lib/xades.test.ts.xadessigns the selected file and downloads<name>.xsig; ES/EN copy.Verify:
cd web && pnpm check && pnpm test && pnpm build.test -d decompiled,find decompiled -name '*.java' | grep -q .,git diff --check; then the report file.Commit after each task (
xades: … (Refs #7)).Open questions (assumptions I am proceeding on)
SignaturePolicyIdentifier. A real policy would meaninventing a Spanish policy URN + hash. I ship
<SignaturePolicyIdentifier><SignaturePolicyImplied/>by default and accept an optional explicit policy (
{identifier, description?, digest}) fromthe caller — nothing fabricated, no invented URN. Recorded in the report for a human decision.
URI=""+enveloped-signaturetransform, as AutoFirma does(
AOXMLAdvancedSignature/XAdESSignerusenewReference("", …, [enveloped-signature], …)).sign.tsSigningAlgorithm.hashisSHA-256).xmlsec1is the acceptance oracle. A browser-side verifier isa separate issue (not in this scope).
xmlsec1is not a repo dependency: it is only used by the test, exactly likeopensslin #5. Installed via
brew install xmlsec1for this session.Risk / boring fallback
xmlsec1 --verifyreports a digest mismatch, the boring fallback is tocanonicalize with Exclusive C14N for every transform/reference (deterministic, no inherited
namespace propagation) and only support that; inclusive C14N stays unit-tested by the lxml
vectors. A structural mismatch in
SignedPropertiesordering → reorder to the referenceXAdESConstantstag order.xmlsec1install fails: fallback oracle for the signature value isopenssl dgst -sha256 -verifyover our canonicalizedSignedInfo(proves RSA/ECDSAcorrectness and canonical bytes), with the schema/structure asserts kept; the report says
plainly that
xmlsec1could not be run.green by the budget, I commit the engine and report the wiring as the open item rather than
shipping a half-wired button.
DONE — branch
issue/7-use-case-xades-sign-in-browser, worktree clean, nothing pushed.Commits:
e200473c14n (C14N 1.0 + exclusive) ·5d80a63XAdES-EPES engine ·1408752test types ·2a8f672UI signs and downloads.xsig·b21152adocs. (Branch ismain + #5 + these 5; #5 was fast-forwarded in — see the report.)Gates (
.bullet/bullet.yaml):test -d decompiled→ 0 ·find decompiled -name '*.java' | grep -q .→ 0 ·git diff --check→ 0.Acceptance:
xmlsec1 --verify→status: OKfor the RSA and the EC fixture, and for the file the real UI produced ·SignatureValue= WebCrypto signature over our canonicalSignedInfo(opensslVerified OK, xmlsec1 agrees) ·KeyInfo/X509Dataembeds the whole container chain from #5 · structure/order/URI assertions + digests recomputed by xmlsec1 (c14n pinned to lxml vectors) · tampered document →Failure reason: REFERENCE.Extra:
pnpm test31/31 ·pnpm check0 errors ·pnpm buildok · Chromium smoke: upload XML + P12 → Firmar →facturae.xml.xsig→xmlsec1 --verifyOK, 0 external requests.Human decisions (in the report): EPES policy defaults to
SignaturePolicyImplied(no invented URN) · no XSD validation (xmlsec1 strict parsing + structure asserts instead) ·#5must land with this branch (PR diff vs main includes #5's commits) · tests needopensslandxmlsec1on PATH (not app dependencies).bullet: coder session died — relaunched from committed state (branch
issue/7-use-case-xades-sign-in-browser).bullet: implementation finished (DONE — branch
issue/7-use-case-xades-sign-in-browser, worktree clean, nothing pushed.) — moving to review.bullet: reviewer launched (model
9router/reviewer, independent of coder).DONE — branch
issue/7-use-case-xades-sign-in-browser, worktree clean, nothing pushed.Commits:
e200473c14n (C14N 1.0 + exclusive) ·5d80a63XAdES-EPES engine ·1408752test typing ·2a8f672UI wiring (download.xsig) ·b21152adocs ·07be7edxmlsec1 check for the UI-produced file.This session (relaunched coder) verified the committed state and closed one gap: the UI download path had no test, only a browser smoke. Now
App.test.tsdrives the real UI, captures the downloaded Blob and runsxmlsec1 --verifyover it.Evidence:
cd web && pnpm test→ exit 0, 32/32 (RSA + EC xmlsec1status: OK, tamper →REFERENCE, opensslVerified OKover our canonicalSignedInfo, KeyInfo = container chain, UI download verified by xmlsec1).cd web && npx svelte-check→ exit 0, 377 files, 0 errors/0 warnings.npx vite build→ exit 0.c14n.test.tsreproduce byte-for-byte underxmllint --c14n/--exc-c14n(libxml2).Gates (
.bullet/bullet.yaml):test -d decompiled→ 0find decompiled -name '*.java' | grep -q .→ 0git diff --check→ 0Human decisions: EPES policy defaults to
SignaturePolicyImplied(no invented URN); no XSD validation (structure asserts + xmlsec1 instead); the branch ismain + #5's commits + 6, so #5 must land before or with it; tests needxmlsec1+opensslon PATH (not app dependencies).Refs #7
REVIEW: FIXED 2 — commit
13e6c4e: findings: .xsig dropped top-level PIs/comments and the document digest ignored document-level node-sets — such documents failed xmlsec1 with REFERENCE (fixed13e6c4e, regression tests against xmlsec1); canonicalize(document) skipped document-level nodes and libxml2 newline separators (fixed13e6c4e); UNFIXED: implementer report still reflects the 5-commit state —07be7edlanded after it was written and is not in its commit tablebullet: merged into
bullet-develop— integration gates green.bullet: work already merged into
bullet-develop.