Expired-cert signing: WORKS with warning (old-cert.p12 is expired 2026-03-29) #40
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
EduarD3V/AutoFirme#40
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?
Goal
The real legacy keystore
work/certs/old-cert.p12holds an FNMT cert that EXPIRED 2026-03-29. AutoFirma-equivalent behavior: signing must still SUCCEED but the app must clearly WARN.Secrets (HARD RULES)
Same as standing rules:
work/certs/*.p12gitignored, PIN in.p12passruntime-read, never printed/committed.Tasks
expired: true; UI shows amber warning banner (cert expired on ) but does NOT block signing.valid-but-expireddistinct state (signature cryptographically valid, cert outside validity window) with visible warning.scripts/smoke-real.sh: default cert = old-cert.p12 must still exit 0 with the warning surfaced in output.Gates
pnpm test green, build clean, smoke-real.sh exit 0 (warning shown, signature produced+verified).
Ownership
Owns certstore/certpicker warning UI + verify result state. Do NOT touch pades signing internals (separate issues own those).
bullet: coder launched on branch
issue/40-expired-cert-signing-works-wit(modelnan/deepseek-v4-flash).Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: verify report gets a distinctvalid-but-expiredstate (warn, not fail), amber expiry banner in the cert picker, synthetic expired fixture + real old-cert.p12 smoke script. Note: theBULLET_TOKENI was handed is scoped to other repos (404 on this one), so comments go throughBULLET_BASIC.Issue #40 — Expired-cert signing: works, with a warning
Restated goal
Signing with an expired certificate must behave like AutoFirma: it succeeds, but the app says so
loudly. Two surfaces carry that:
work/certs/old-cert.p12(FNMT leaf,notAfter2026-03-29, already past)must mark the certificate entry
expired: trueand surface an amber banner naming the expirydate instead of the current red text, without disabling the sign button.
must yield a distinct
valid-but-expiredreport state: the cryptographic checks pass, thecertValiditycheck warns instead of failing, and the report UI shows a visible amber warning.The report must not be reported as invalid (
okstays true) when the only problem is expiry."Done" =
pnpm testgreen (including a synthetic expired-cert fixture generated at test time and areal-clock expiry end-to-end test),
pnpm buildclean, andscripts/smoke-real.shexiting 0 withthe warning printed and a signature produced + verified (openssl
cms -verifycross-check plus ourown verifier reporting
valid-but-expired).Starting state (already in the branch, verified by reading)
SignerCert.expired/.notYetValidalready set insrc/lib/keystore.ts:164-165; the pickeralready prints a red one-liner for
cert?.expired(no date, destructive colour).src/lib/verify/{cms,xades}.tscurrently pushcertValidityasfailwhen out of window,which flips
report.okto false — the bug this issue fixes.scripts/smoke-real.shdoes not exist;work/is absent from this worktree.Scope
Will change
src/lib/verify/types.tsCheckStatus+'warn'; addVerificationStatus = 'valid' | 'valid-but-expired' | 'invalid',VerificationReport.status, and asummarize(checks, expired)helpersrc/lib/verify/cms.tswarnwhen outside the window, reportstatusfromsummarizesrc/lib/verify/xades.tssrc/lib/verify/index.tsstatus: 'invalid'src/lib/components/VerifyReport.sveltevalid-but-expiredamber / valid green) + warn card stylesrc/lib/components/CertPicker.sveltesrc/lib/i18n.tsverifyExpired(ES/EN); expiry-date wording for the picker bannersrc/test/fixtures.tsexpiredPKCS#12 fixture generated withopenssl req -x509 -not_after <past>(no committed binary)src/lib/pkcs12.test.tsexpired: true, key present, signing still workssrc/lib/verify.test.tsstatus === 'valid-but-expired',certValidity === 'warn',signature === 'pass',ok === true; CAdES + XAdESsrc/lib/real-cert.test.tsold-cert.p12end-to-end (skip when cert/PIN absent), target of the smoke scriptscripts/smoke-real.shwork/certs/old-cert.p12, exit 0 with the warning surfacedWill not touch —
src/lib/pades.ts,src/lib/pkcs12.tsinternals, the PAdES/CAdES/XAdESbuilders apart from using them,
src/lib/certstore.tssealing,mcp/,docs/MCP-DESIGN.md,Containerfile, the trust store undersrc/lib/verify/roots.Ordered tasks (each with its verifying command)
types.ts,verify/cms.ts,verify/xades.ts,verify/index.ts.Verify:
pnpm vitest run src/lib/verify.test.ts(existing tests unchanged and green).VerifyReport.svelte,CertPicker.svelte,i18n.ts.Verify:
pnpm vitest run src/lib/components/CertPicker.test.ts src/lib/App.test.ts.src/test/fixtures.ts,pkcs12.test.ts,verify.test.ts.Verify:
pnpm vitest run src/lib/pkcs12.test.ts src/lib/verify.test.ts.src/lib/real-cert.test.ts,scripts/smoke-real.sh.Verify:
bash scripts/smoke-real.sh→ exit 0, warning line printed (with the cert copied into theworktree's gitignored
work/certs/; script skips cleanly when absent).pnpm test,pnpm build,git diff --check,bash scripts/smoke-real.sh.Commits after each verified task:
<area>: <what changed> (Refs #40).Open questions (assumption taken, no human to ask)
okremain true for an expired signer? Assumed yes: the signature is cryptographicallyvalid and AutoFirma does not reject it; the distinct
statusfield carries the nuance.okisderived from
status(ok === status !== 'invalid') so the two can never disagree.certValiditycheck warns foreither side of the window, but
status: 'valid-but-expired'is only produced whennotAfteris inthe past; a not-yet-valid signer stays
validwith the warn check visible.work/certs/*.p12is gitignored and absent from this worktree.The smoke script and the integration test resolve it relative to the repo root and skip with exit
0 when missing, as the issue asks. To run the gate here, the operator's read-only
work/certs/old-cert.p12+.p12passare copied into this worktree's gitignoredwork/— nothingoutside this worktree is modified.
.p12passis read at runtime into an env var and never echoed, committed orprinted — including in failure output.
Risk and boring fallback
warntoCheckStatusbreaks exhaustive maps (VerifyReport.sveltestyles) orthe report type. Fallback: keep
CheckStatusuntouched and express the state only throughreport.status+ aninfocheck — worse UI, same behaviour.openssl -not_afterunavailable on another machine's OpenSSL. Fallback: the existing{ now }clock override inloadSignerSessionstill expresses the expired case; the fixture helperkeeps working on OpenSSL 3.4+ only, and the test says so in its failure message.
skip path is verified by running it once without the cert, and the report says exactly which gate
was proven against the real container and which was only exercised through fixtures.
DONE — branch
issue/40-expired-cert-signing-works-wit, commits989f6978ec631ada13dc5217f0e785bf163c5d69.Gates (all on HEAD
163c5d69, tree clean):pnpm testexit 0 — 17 files, 146 tests, including the realold-cert.p12suite.pnpm buildexit 0 — static site written tobuild/.git diff --checkclean.bash scripts/smoke-real.shexit 0 —WARNING: GARCIA APARICIO EDUARDO - 05718264G expired 2026-03-29T06:34:25.000Z — signing anyway,openssl: CMS Verification successful, signature verified by our verifier asvalid-but-expired; skips with exit 0 without the gitignored cert.… Caduca: Mar 29, 2026,border-amber-600/50) with the sign button enabled; verify panel of a real XAdES signed by the expired FNMT cert shows the amber bannerFirma válida, pero el certificado del firmante ha caducado. XAdESwithcertValidity=warn, every other checkpass.What changed:
CheckStatusgainswarn; the report carriesstatus: valid|valid-but-expired|invalidderived once insummarize()so it can never disagree withok;certValiditywarns (never fails) outside the window; the verify banner is three-way; the picker warns in amber with the expiry date and does not block signing; expired PKCS#12 fixtures are generated at test time;scripts/smoke-real.sh+src/lib/real-cert.test.tsexercise the real container; README documents the verdict.Two things for the human: (1)
src/test/fixtures.tshad to be fixed to read this box's xmlsec1 1.3.12 verdict wording (Verification status: OK) —App.test.tsfailed with "xmlsec1 printed no verdict" before any of my changes, one hunk, split it out if you prefer; (2) the suite is load-sensitive: during a load-13 spike two runs failed with 5 s openssl-keygen timeouts in untouched tests, all three failing files pass in isolation and the suite is green at lower load.bullet: coder session died — relaunched from committed state (branch
issue/40-expired-cert-signing-works-wit).bullet: implementation finished (DONE — branch
issue/40-expired-cert-signing-works-wit, commits989f6978ec631ada13dc5217f0e785bf163c5d69.) — moving to review.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: five #40 commits already sit on this branch from an interrupted session; no plan file and the gates were never run (pnpm testred). Diagnosing the red gate, then re-verifying test/build/smoke.Plan — issue #40: expired-cert signing (works, with warning)
Restated goal
work/certs/old-cert.p12is a real legacy FNMT keystore whose certificate expired 2026-03-29.Signing with it must still SUCCEED, while the app says the expiry out loud in both flows:
notAfteris in the past is flagged (no keywithheld, signing not blocked), and the picker shows an amber banner with the expiry date.
distinct
valid-but-expiredverdict (signature sound, certificate pastnotAfter) instead ofinvalid, with a visible amber warning.real container exercised by a smoke script when present (skipped otherwise).
scripts/smoke-real.sh— defaulting toold-cert.p12, exits 0 with the warning surfaced inthe output and the signature produced + verified.
Done = the four points above,
pnpm testgreen,pnpm buildclean,scripts/smoke-real.shexit 0, and the diff confined to the files below.
State found on arrival (important)
The branch already carries five commits from an earlier, interrupted session of this same issue —
the implementation, tests and docs of all four tasks are written and committed:
989f6978feat(verify):valid-but-expiredverdict +warncheck statusec631adafeat(ui): amber expiry warning in CertPicker + VerifyReport13dc5217test: synthetic expired fixture (openssl-generated), verify coveragef0e785bftest(smoke):src/lib/real-cert.test.ts+scripts/smoke-real.sh163c5d69docs: README verify-flow and script tableNo plan or report file was written, and the gates were never run —
pnpm testis red onarrival (7 failures / 4 files). So this session's work is: diagnose the red gate, fix what belongs
to #40, re-verify every gate, and write the report. No re-implementation.
Scope
Files this plan may touch
src/lib/verify.test.ts,src/lib/pkcs12.test.ts,src/lib/components/CertPicker.test.ts,src/lib/App.test.tssrc/test/fixtures.tsxmlsec1Verifyhunk collides with branchissue/45-…; align byte-for-byte so the integration merge is conflict-freescripts/smoke-real.shsrc/lib/verify/{types,cms,xades}.ts,src/lib/components/{CertPicker,VerifyReport}.svelte,src/lib/i18n.tsFiles deliberately NOT touched
src/lib/pades.ts,src/lib/pades.test.ts) — owned by other issues, and theissue's ownership clause says so explicitly.
src/lib/cms.ts(the CAdES builder),src/lib/keystore.ts's crypto,src/lib/x509.tsparsing:the
expired/notYetValidflags already exist onSignerCert(src/lib/sign.ts), which iswhat task 1 asks for. Re-adding them would be a second convention.
work/,original/,decompiled*/— gitignored inputs; the container and.p12passare read atruntime, never printed, never committed.
docs/beyond the README rows already updated.Ordered tasks
pnpm test(full output, failures by file) →compare each failing test against
git show 163c5d69 --statto decide pre-existing vs.caused-by-#40. Verify: the failure list with a cause per failure recorded in the report.
Verify:
pnpm exec vitest run <file>for each file touched, green.xmlsec1Verifyhunk withissue/45so the shared file cannot conflict atintegration. Verify:
git diff issue/45-… issue/40-… -- src/test/fixtures.tsshows no hunktouching
xmlsec1Verify, andpnpm exec vitest run src/lib/xades.test.tsstays green.pnpm test→ green;pnpm build→ clean;git diff --check→ clean.bash scripts/smoke-real.shwith the defaultwork/certs/old-cert.p12and the PINfrom
.p12pass; must exit 0 withWARNING: … expired …and the openssl re-verification in theoutput, and the report must assert
valid-but-expired. Verify:echo $?= 0..bullet/meta/issue-40.report.mdwith the outcome, commit hashes, one line per gate.Open questions (assumption stated, moving on)
mainor caused by #40? Assumed mixed until task 1 shows it.Hard-law forbids checking out
mainin this worktree, so attribution is done by reading thefailing assertions against the diff, not by a baseline run.
xmlsec1Verifyregex part of #40? #45 owns that change and already committedit. Assumption: the earlier session needed it because this box runs
xmlsec1 1.3.12(which printsVerification status: OK, notstatus: OK) and the suite is otherwise red. Keep it, but align itwith #45's exact text so the two branches do not conflict.
VerificationReport? Assumption: onlyverify/cms.ts,verify/xades.tsand the error paths inverify/index.ts;statuswas added to all of them.A missed producer would be a
pnpm buildtype error, which task 4 catches.Risk and boring fallback
re-run the single file in isolation; if it passes alone and fails only in the full run, report it
as a pre-existing suite-level flake with the evidence and leave it untouched.
openssl req -not_before/-not_afterneeds OpenSSL ≥ 3.4. This box has 4.0.3, so thefixture works here; a contributor on 3.0 would see a fixture error. Fallback: that is already
documented in the fixture's comment; no code change.
current hunk (the reconciler resolves the one-line conflict) and note it in the report.
with the exact failing command and its first error line.
DONE — branch
issue/40-expired-cert-signing-works-wit.Commits:
989f6978(verify verdict),ec631ada(amber UI),13dc5217(fixtures+tests),f0e785bf(real-cert smoke + scripts/smoke-real.sh),163c5d69(docs),05799d83(xmlsec1 hunk aligned with #45),0b87fc36(App: banner + signing not blocked),64d4be2b(VerifyReport banner test).Gates:
pnpm test— exit 0, 18 files / 150 tests. Red on arrival for load reasons only: while the rest of the fleet ran its suites (load 12.5 on 4 CPUs) 7–9 tests across 4 untouched files tripped their 1swaitFortimeouts, failing set different every run;CertPicker.test.tspasses 3/3 alone, and at load ~9.5 the plain suite ran green twice (also--maxWorkers=2: 150/150).pnpm build— exit 0.bash scripts/smoke-real.sh— exit 0:WARNING: … expired 2026-03-29T06:34:25.000Z — signing anyway,openssl: CMS Verification successful, our verifier reportsvalid-but-expired.git diff --check— exit 0.pnpm dev: amberrole=alertbanner with the expiry date,Firmarenabled.Full detail in
.bullet/meta/issue-40.report.md; decisions for a human are vitest worker cap, the sharedxmlsec1Verifyhunk with #45, the picker date locale, and the smoke testbullet: reviewer launched (model
9router/reviewer, independent of coder).REVIEW: CLEAN — commit
64d4be2b: findings: none; UNFIXED: nonebullet: review CLEAN — ready to publish.
bullet: branch
issue/40-expired-cert-signing-works-witnot on origin — cannot integrate.bullet: PR #52 open →
bullet-develop(auto-merge follows).bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: re-applying #40 (expired-cert signing warning + valid-but-expired verify state) on top of bullet-develop and reconciling the 3 conflicted files (README.md, scripts/smoke-real.sh, src/test/fixtures.ts).bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: worktree gone and unrecoverable — cannot publish.
bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: re-applying #40 (expired-cert warning + valid-but-expired verify) onto bullet-develop and reconciling conflicts.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: re-applying expired-cert warning work onto bullet-develop and resolving conflicts.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: re-applying expired-cert signing onto bullet-develop and resolving conflicts.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — origin/main
abda84d9already an ancestor via merge58bdf0f7; PR #48 mergeable=true)bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: re-applying expired-cert signing + verify warning onto bullet-develop, resolving README/smoke-real/fixtures conflicts.Claimed — branch
bullet-develop. Doing: finish the interrupted issue/40 merge, resolve README.md, scripts/smoke-real.sh, src/test/fixtures.ts conflicts.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: re-applying expired-cert signing warning + verify valid-but-expired state on top of bullet-develop, resolving conflicts.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
bullet-develop. Doing: reconcile trunk + integrate issue #40 (3 conflicting files) into bullet-develop.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.Claimed — branch
issue/40-expired-cert-signing-works-wit. Doing: re-apply expired-cert warning work onto bullet-develop, resolve conflicts with #45/#44 changes, keep gates green.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merge conflicts with
bullet-develop— conflict-solver agent owns the worktree until it pushes.bullet: merge blocked (HTTP 405) — conciliation agent merges trunk work into
bullet-develop; PR auto-merges next tick.bullet: merged into
bullet-develop— integration gates green.bullet: PR #52 closed — work already merged into
bullet-develop.