Use case: load signing cert from P12/PFX in browser #5

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

Use case: user uploads .p12/.pfx + PIN, app imports key via WebCrypto as non-extractable, shows cert list (CN, issuer, valid dates) and lets user pick one. Mirrors AutoFirma keystore selection (decompiled/sources/es/gob/afirma/keystores/, filters/).

Constraints: key material never leaves browser memory; no server-side key ops.

Acceptance:

  • parse P12 (ASN.1 or node-forge free approach: use WebCrypto importKey on PKCS12 only if supported; else nativecrypto fallback documented)
  • expose selected cert + CryptoKey to sign flow
  • error paths: wrong PIN, expired cert (warn, allow)
  • unit test with generated test P12 (self-signed, created in test setup, not committed binary)

Depends on #4 (web/ scaffold).

Use case: user uploads .p12/.pfx + PIN, app imports key via WebCrypto as non-extractable, shows cert list (CN, issuer, valid dates) and lets user pick one. Mirrors AutoFirma keystore selection (decompiled/sources/es/gob/afirma/keystores/, filters/). Constraints: key material never leaves browser memory; no server-side key ops. Acceptance: - parse P12 (ASN.1 or node-forge free approach: use WebCrypto importKey on PKCS12 only if supported; else nativecrypto fallback documented) - expose selected cert + CryptoKey to sign flow - error paths: wrong PIN, expired cert (warn, allow) - unit test with generated test P12 (self-signed, created in test setup, not committed binary) Depends on #4 (web/ scaffold).
Author
Owner

bullet: coder launched on branch issue/5-use-case-load-signing-cert-fro (model nan/deepseek-v4-flash).

bullet: coder launched on branch `issue/5-use-case-load-signing-cert-fro` (model `nan/deepseek-v4-flash`).
Author
Owner

Claimed — branch issue/5-use-case-load-signing-cert-fro. Doing: hand-rolled PKCS#12 parser (DER + MAC + PBES2/PBKDF2+AES-CBC) with non-extractable WebCrypto key import, cert list UI, openssl-generated test fixtures.

Claimed — branch `issue/5-use-case-load-signing-cert-fro`. Doing: hand-rolled PKCS#12 parser (DER + MAC + PBES2/PBKDF2+AES-CBC) with non-extractable WebCrypto key import, cert list UI, openssl-generated test fixtures.
Author
Owner

Plan — Issue #5: load signing cert from P12/PFX in browser

Restated goal

A user picks a .p12/.pfx file plus its PIN in the AutoFirma Web SPA. The app parses the
PKCS#12 container entirely in the browser (no network, no server key ops), decrypts the
shrouded private key, imports it into WebCrypto as non-extractable (extractable=false,
usages ['sign']), lists the certificates in the file (subject CN, issuer, validity dates) and
lets the user select one. The selected certificate + CryptoKey are exposed to the sign flow
(#6/#7/#11) through a typed seam in web/src/lib/sign.ts. Error paths: wrong PIN, corrupt
file, unsupported PBE algorithm, expired certificate (warn, still allow selection).

"Done" = pnpm test green with a test that generates its own self-signed P12 in test setup
(nothing binary committed), imports the key, and proves the imported key is the real one by
signing with WebCrypto and verifying that signature with openssl dgst -verify. Plus the UI
actually loading a P12 end to end in the SPA.

Reference only (read, not ported): decompiled/sources/es/gob/afirma/keystores/ — AutoFirma
shows alias/CN + validity and asks for the PIN; same shape here.

Key technical decision (recorded, not negotiable mid-flight)

WebCrypto has no PKCS#12 support, and the browser has no other native P12 API, so the
container must be parsed by hand. We implement:

  • DER/BER reader (asn1.ts), PKCS#12 structure walk (RFC 7292), MAC check with the PKCS#12 KDF
    (HMAC-SHA1 / HMAC-SHA256) → this is what detects a wrong PIN.
  • Key decryption for PBES2: PBKDF2 (HMAC-SHA1/SHA256, via crypto.subtle.deriveBits) +
    AES-128/192/256-CBC (crypto.subtle.decrypt). This is what OpenSSL 3/4 pkcs12 -export
    writes by default and what Java 9+ KeyStore.store(...,"PKCS12") writes.
  • PBES1 (3DES / RC2, "legacy") is not implemented: WebCrypto has no DES/RC2 primitive, and
    hand-rolling DES/RC2 from memory is a correctness hazard for crypto. It raises a typed,
    actionable UnsupportedPbeError telling the user to re-export the container as modern
    PKCS#12 (or use a hardware/OS keystore). Documented in web/README.md.

Scope

Files I will change:

  • web/src/lib/asn1.ts (new) — minimal DER reader + OID decode.
  • web/src/lib/pkcs12.ts (new) — container parse, MAC verify, PBES2 key decryption.
  • web/src/lib/x509.ts (new) — certificate summary (subject/issuer/validity/serial/SPKI) + the
    public-key material needed to match a cert to its private key.
  • web/src/lib/keystore.ts (new) — loadPkcs12(), typed errors, SignerSession.
  • web/src/lib/sign.ts — extend SignerCert, add the key/cert pairing types, signWithKey seam.
  • web/src/lib/components/CertPicker.svelte — real control: file, PIN, load, error, cert select,
    expiry warning.
  • web/src/lib/App.svelte — hold the loaded session, enable signing when file+cert exist.
  • web/src/lib/i18n.ts — ES/EN copy for the new control.
  • web/src/lib/App.test.ts — end-to-end SPA flow against a generated P12.
  • web/src/lib/pkcs12.test.ts (new) — container/error/openssl cross-check tests.
  • web/src/test/fixtures.ts (new) — test-only P12 generation via the openssl CLI.
  • web/vite.config.ts — test.setupFiles (jsdom's crypto has no subtle).
  • web/src/test-setup.ts (new) — 3 lines installing Node's webcrypto.
  • web/README.md — status + the PBES1/PBES2 limitation.

Not touching: decompiled/** (read-only reference), original/, extracted/, docs/**
(owned by #3), .bullet/** (never committed), everything outside web/ except nothing.

Ordered tasks (each with its verifying command)

  1. Plan + claim + plan comment on #5. Verify: test -f /home/eduardez/workspace/AutoFirme/.bullet/meta/issue-5.plan.md.
  2. asn1.ts + x509.ts (parse DER, cert summary). Verify: cd web && pnpm test after the
    first test lands (see 4) — interim check npx tsc --noEmit -p tsconfig.json exit 0.
  3. pkcs12.ts + keystore.ts (MAC, PBES2 decrypt, import as non-extractable, cert↔key
    match). Verify: cd web && pnpm test.
  4. web/src/test/fixtures.ts + web/src/lib/pkcs12.test.ts — openssl-generated fixtures
    (modern AES-256/PBKDF2-SHA256; 2-cert file; legacy PBES1 file for the error path), wrong PIN,
    corrupt file, expiry via injected now, and a real sign→openssl dgst -verify cross-check.
    Verify: cd web && pnpm test.
  5. UI wiring (CertPicker.svelte, App.svelte, i18n.ts) + App.test.ts flow.
    Verify: cd web && pnpm test && pnpm check.
  6. Docs + gates + build smoke: pnpm build exit 0, git diff --check exit 0, the two
    .bullet/bullet.yaml gates exit 0, then commit each step as web: … (Refs #5).

Open questions (assumption I proceed on)

  1. Does the browser need legacy (PBES1) containers? Spanish FNMT exports are often legacy.
    Assumption: out of scope for a correct-diff budget today; ship PBES2 + a typed, actionable
    error and document it. Follow-up issue material, not silently faked.
  2. WebCrypto can't export a non-extractable key, so how do we know it matches the cert?
    Assumption: compare key material — RSA modulus/exponent from PKCS#8 vs the certificate SPKI;
    EC public point from PKCS#8 [1] vs SPKI. A cert whose key is not in the container is shown
    disabled rather than silently producing a broken signature.
  3. Where do the fixtures live? Assumption: generated into os.tmpdir() per test run by the
    openssl CLI (dev tool, not a dependency, not a committed binary).
  4. Test environment: jsdom lacks crypto.subtle. Assumption: test.setupFiles installs Node's
    webcrypto; no production code depends on it.

Risk

  • jsdom/Node subtle differences (deriveBits PBKDF2 params) → fallback: run the crypto
    tests under // @vitest-environment node and keep the jsdom test to the UI.
  • openssl 4 CLI differences (-legacy, digest names) → fallback: keep the modern fixture
    (verified working) and drop the legacy fixture, keeping the error path unit-tested with a
    hand-built PBES1 header.
  • Cert parsing corner cases (UTCTime vs GeneralizedTime, odd string types) → fallback: decode
    the common types explicitly and fail with a clear error for anything else rather than guessing.
  • Time: crypto core first, UI second. If the budget runs out, the UI keeps the placeholder
    and the report says PARTIAL with the core landed.
# Plan — Issue #5: load signing cert from P12/PFX in browser ## Restated goal A user picks a `.p12`/`.pfx` file plus its PIN in the AutoFirma Web SPA. The app parses the PKCS#12 container **entirely in the browser** (no network, no server key ops), decrypts the shrouded private key, imports it into WebCrypto as **non-extractable** (`extractable=false`, usages `['sign']`), lists the certificates in the file (subject CN, issuer, validity dates) and lets the user select one. The selected certificate + `CryptoKey` are exposed to the sign flow (`#6`/`#7`/`#11`) through a typed seam in `web/src/lib/sign.ts`. Error paths: wrong PIN, corrupt file, unsupported PBE algorithm, expired certificate (warn, still allow selection). "Done" = `pnpm test` green with a test that generates its own self-signed P12 in test setup (nothing binary committed), imports the key, and proves the imported key is the real one by signing with WebCrypto and verifying that signature with `openssl dgst -verify`. Plus the UI actually loading a P12 end to end in the SPA. Reference only (read, not ported): `decompiled/sources/es/gob/afirma/keystores/` — AutoFirma shows alias/CN + validity and asks for the PIN; same shape here. ## Key technical decision (recorded, not negotiable mid-flight) WebCrypto has **no PKCS#12 support**, and the browser has no other native P12 API, so the container must be parsed by hand. We implement: - DER/BER reader (`asn1.ts`), PKCS#12 structure walk (RFC 7292), MAC check with the PKCS#12 KDF (HMAC-SHA1 / HMAC-SHA256) → this is what detects a **wrong PIN**. - Key decryption for **PBES2**: PBKDF2 (HMAC-SHA1/SHA256, via `crypto.subtle.deriveBits`) + AES-128/192/256-CBC (`crypto.subtle.decrypt`). This is what OpenSSL 3/4 `pkcs12 -export` writes by default and what Java 9+ `KeyStore.store(...,"PKCS12")` writes. - **PBES1 (3DES / RC2, "legacy") is not implemented**: WebCrypto has no DES/RC2 primitive, and hand-rolling DES/RC2 from memory is a correctness hazard for crypto. It raises a typed, actionable `UnsupportedPbeError` telling the user to re-export the container as modern PKCS#12 (or use a hardware/OS keystore). Documented in `web/README.md`. ## Scope Files I will change: - `web/src/lib/asn1.ts` (new) — minimal DER reader + OID decode. - `web/src/lib/pkcs12.ts` (new) — container parse, MAC verify, PBES2 key decryption. - `web/src/lib/x509.ts` (new) — certificate summary (subject/issuer/validity/serial/SPKI) + the public-key material needed to match a cert to its private key. - `web/src/lib/keystore.ts` (new) — `loadPkcs12()`, typed errors, `SignerSession`. - `web/src/lib/sign.ts` — extend `SignerCert`, add the key/cert pairing types, `signWithKey` seam. - `web/src/lib/components/CertPicker.svelte` — real control: file, PIN, load, error, cert select, expiry warning. - `web/src/lib/App.svelte` — hold the loaded session, enable signing when file+cert exist. - `web/src/lib/i18n.ts` — ES/EN copy for the new control. - `web/src/lib/App.test.ts` — end-to-end SPA flow against a generated P12. - `web/src/lib/pkcs12.test.ts` (new) — container/error/openssl cross-check tests. - `web/src/test/fixtures.ts` (new) — test-only P12 generation via the `openssl` CLI. - `web/vite.config.ts` — `test.setupFiles` (jsdom's `crypto` has no `subtle`). - `web/src/test-setup.ts` (new) — 3 lines installing Node's `webcrypto`. - `web/README.md` — status + the PBES1/PBES2 limitation. **Not touching**: `decompiled/**` (read-only reference), `original/`, `extracted/`, `docs/**` (owned by #3), `.bullet/**` (never committed), everything outside `web/` except nothing. ## Ordered tasks (each with its verifying command) 1. **Plan + claim + plan comment on #5.** Verify: `test -f /home/eduardez/workspace/AutoFirme/.bullet/meta/issue-5.plan.md`. 2. **`asn1.ts` + `x509.ts`** (parse DER, cert summary). Verify: `cd web && pnpm test` after the first test lands (see 4) — interim check `npx tsc --noEmit -p tsconfig.json` exit 0. 3. **`pkcs12.ts` + `keystore.ts`** (MAC, PBES2 decrypt, import as non-extractable, cert↔key match). Verify: `cd web && pnpm test`. 4. **`web/src/test/fixtures.ts` + `web/src/lib/pkcs12.test.ts`** — openssl-generated fixtures (modern AES-256/PBKDF2-SHA256; 2-cert file; legacy PBES1 file for the error path), wrong PIN, corrupt file, expiry via injected `now`, and a real sign→`openssl dgst -verify` cross-check. Verify: `cd web && pnpm test`. 5. **UI wiring** (`CertPicker.svelte`, `App.svelte`, `i18n.ts`) + `App.test.ts` flow. Verify: `cd web && pnpm test && pnpm check`. 6. **Docs + gates + build smoke**: `pnpm build` exit 0, `git diff --check` exit 0, the two `.bullet/bullet.yaml` gates exit 0, then commit each step as `web: … (Refs #5)`. ## Open questions (assumption I proceed on) 1. *Does the browser need legacy (PBES1) containers?* Spanish FNMT exports are often legacy. Assumption: out of scope for a correct-diff budget today; ship PBES2 + a typed, actionable error and document it. Follow-up issue material, not silently faked. 2. *WebCrypto can't export a non-extractable key, so how do we know it matches the cert?* Assumption: compare key material — RSA modulus/exponent from PKCS#8 vs the certificate SPKI; EC public point from PKCS#8 `[1]` vs SPKI. A cert whose key is not in the container is shown disabled rather than silently producing a broken signature. 3. *Where do the fixtures live?* Assumption: generated into `os.tmpdir()` per test run by the `openssl` CLI (dev tool, not a dependency, not a committed binary). 4. *Test environment*: jsdom lacks `crypto.subtle`. Assumption: `test.setupFiles` installs Node's `webcrypto`; no production code depends on it. ## Risk - **jsdom/Node subtle differences** (`deriveBits` PBKDF2 params) → fallback: run the crypto tests under `// @vitest-environment node` and keep the jsdom test to the UI. - **openssl 4 CLI differences** (`-legacy`, digest names) → fallback: keep the modern fixture (verified working) and drop the legacy fixture, keeping the error path unit-tested with a hand-built PBES1 header. - **Cert parsing corner cases** (UTCTime vs GeneralizedTime, odd string types) → fallback: decode the common types explicitly and fail with a clear error for anything else rather than guessing. - **Time**: crypto core first, UI second. If the budget runs out, the UI keeps the placeholder and the report says PARTIAL with the core landed.
Author
Owner

DONE — branch issue/5-use-case-load-signing-cert-fro, 3 commits, worktree clean.

Commits: db4a0ac (PKCS#12 core: DER + RFC 7292 walk, PBES2 AES-CBC decryption, non-extractable WebCrypto key import, cert↔key pairing), ac5afe2 (picker UI + App wiring + ES/EN copy), 9e1dbbb (ArrayBuffer-backed byte types for BufferSource).

Gates: test -d decompiled 0 | find decompiled -name "*.java" | grep -q . 0 | git diff --check 0.

Verification: pnpm test 14/14 (incl. signature produced by the imported key verified by openssl dgst -sha256 -verify, and a tampered message rejected) | pnpm check 0 errors | pnpm build 0 | real Chromium smoke on pnpm preview: real keystore.p12 + PIN -> cert list ["Ñandú Prueba"], selected, Sign enabled, no console errors.

Two deviations, both documented in web/README.md and the report, each needing a follow-up issue: (1) legacy PBES1 containers (3DES/RC2) are rejected with an actionable message — WebCrypto has no DES/RC2 primitive, and aged FNMT exports are legacy; (2) the PKCS#12 MAC is not verified (RFC 7292 B.2 KDF not reproduced against openssl kdf after 3 attempts) — a wrong PIN is still detected via the key-bag decryption failure. Also: the tests shell out to the openssl binary; add it to CI if the image is bare.
Refs #5

DONE — branch `issue/5-use-case-load-signing-cert-fro`, 3 commits, worktree clean. Commits: `db4a0ac` (PKCS#12 core: DER + RFC 7292 walk, PBES2 AES-CBC decryption, non-extractable WebCrypto key import, cert↔key pairing), `ac5afe2` (picker UI + App wiring + ES/EN copy), `9e1dbbb` (ArrayBuffer-backed byte types for BufferSource). Gates: `test -d decompiled` 0 | `find decompiled -name "*.java" | grep -q .` 0 | `git diff --check` 0. Verification: `pnpm test` 14/14 (incl. signature produced by the imported key verified by `openssl dgst -sha256 -verify`, and a tampered message rejected) | `pnpm check` 0 errors | `pnpm build` 0 | real Chromium smoke on `pnpm preview`: real `keystore.p12` + PIN -> cert list ["Ñandú Prueba"], selected, Sign enabled, no console errors. Two deviations, both documented in `web/README.md` and the report, each needing a follow-up issue: (1) legacy PBES1 containers (3DES/RC2) are rejected with an actionable message — WebCrypto has no DES/RC2 primitive, and aged FNMT exports are legacy; (2) the PKCS#12 MAC is not verified (RFC 7292 B.2 KDF not reproduced against `openssl kdf` after 3 attempts) — a wrong PIN is still detected via the key-bag decryption failure. Also: the tests shell out to the `openssl` binary; add it to CI if the image is bare. Refs #5
Author
Owner

bullet: coder session died — relaunched from committed state (branch issue/5-use-case-load-signing-cert-fro).

bullet: coder session died — relaunched from committed state (branch `issue/5-use-case-load-signing-cert-fro`).
Author
Owner

Re-verified (relaunched coder session, no new commits, branch still at 9e1dbbb).

From scratch in this session: gates test -d decompiled 0 | find decompiled -name "*.java" | grep -q . 0 | git diff --check 0. pnpm test 14/14 | pnpm check 0 errors | pnpm build 0 | git ls-files -- original extracted work 0 paths.

Fresh real-Chromium smoke with a newly generated container (openssl pkcs12 -export, leaf + CA, PBES2/PBKDF2/AES-256-CBC, MAC sha256, PIN 4321): wrong PIN -> PIN incorrecto. + Firmar disabled; correct PIN -> 2 certs, signer auto-selected, CA disabled (no key), Firmar enabled, PIN cleared; tab.requests() 27 requests / 0 external.

No code changed, so the two documented deviations (legacy PBES1 rejected; PKCS#12 MAC not verified) still stand and remain follow-up material. Nothing pushed. Refs #5

Re-verified (relaunched coder session, no new commits, branch still at `9e1dbbb`). From scratch in this session: gates `test -d decompiled` 0 | `find decompiled -name "*.java" | grep -q .` 0 | `git diff --check` 0. `pnpm test` 14/14 | `pnpm check` 0 errors | `pnpm build` 0 | `git ls-files -- original extracted work` 0 paths. Fresh real-Chromium smoke with a newly generated container (`openssl pkcs12 -export`, leaf + CA, PBES2/PBKDF2/AES-256-CBC, MAC sha256, PIN 4321): wrong PIN -> `PIN incorrecto.` + Firmar disabled; correct PIN -> 2 certs, signer auto-selected, CA disabled (no key), Firmar enabled, PIN cleared; `tab.requests()` 27 requests / 0 external. No code changed, so the two documented deviations (legacy PBES1 rejected; PKCS#12 MAC not verified) still stand and remain follow-up material. Nothing pushed. Refs #5
Author
Owner

bullet: coder session died — relaunched from committed state (branch issue/5-use-case-load-signing-cert-fro).

bullet: coder session died — relaunched from committed state (branch `issue/5-use-case-load-signing-cert-fro`).
Author
Owner

bullet: implementation finished (DONE — branch issue/5-use-case-load-signing-cert-fro, 3 commits, worktree clean.) — moving to review.

bullet: implementation finished (DONE — branch `issue/5-use-case-load-signing-cert-fro`, 3 commits, worktree clean.) — 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

Re-verified again (third session, branch still at 9e1dbbb, nothing new committed, tree clean). Gates 0/0/0, pnpm test 14/14, pnpm check 0 errors, pnpm build exit 0.

Fresh Chromium run against pnpm preview with two containers generated this session by openssl 4.0.3 (PBES2/PBKDF2/AES-256-CBC):

  • wrong PIN 9999 → PIN incorrecto., empty cert list, Firmar disabled;
  • correct PIN 2468 → CN=Firma Humo listed with issuer and Válido hasta: Oct 14, 2027, auto-selected, Firmar enabled;
  • expired container (notAfter 2021-01-01) → Este certificado está caducado; puedes firmar, pero puede ser rechazado. and Firmar still enabled (warn + allow, first time this path is proven in a real browser);
  • 13 requests, all localhost:4173, 0 external — key material never leaves the page.

Code re-read, not trusted: importKey(..., false, ["sign"]), cert↔key pairing by public-key material, canSign = file && cert?.key excludes expiry deliberately. Outcome stays DONE; the three documented deviations (legacy PBES1 rejected, MAC not verified, tests need openssl) remain follow-up-issue material. Refs #5.

Re-verified again (third session, branch still at `9e1dbbb`, nothing new committed, tree clean). Gates 0/0/0, `pnpm test` 14/14, `pnpm check` 0 errors, `pnpm build` exit 0. Fresh Chromium run against `pnpm preview` with two containers generated this session by openssl 4.0.3 (PBES2/PBKDF2/AES-256-CBC): - wrong PIN `9999` → `PIN incorrecto.`, empty cert list, **Firmar disabled**; - correct PIN `2468` → `CN=Firma Humo` listed with issuer and `Válido hasta: Oct 14, 2027`, auto-selected, **Firmar enabled**; - expired container (`notAfter 2021-01-01`) → `Este certificado está caducado; puedes firmar, pero puede ser rechazado.` and **Firmar still enabled** (warn + allow, first time this path is proven in a real browser); - 13 requests, all `localhost:4173`, 0 external — key material never leaves the page. Code re-read, not trusted: `importKey(..., false, ["sign"])`, cert↔key pairing by public-key material, `canSign = file && cert?.key` excludes expiry deliberately. Outcome stays **DONE**; the three documented deviations (legacy PBES1 rejected, MAC not verified, tests need `openssl`) remain follow-up-issue material. Refs #5.
Author
Owner

REVIEW: FIXED 1 — commit 27d86a5: findings: CertPicker kept the previous file's certificate list (and enabled Firmar) after a different keystore file was picked, so the displayed signer did not match the selected file — fixed by clearing certs/error/selection on file change + regression test (fix commit 27d86a5); UNFIXED: none

REVIEW: FIXED 1 — commit 27d86a5: findings: CertPicker kept the previous file's certificate list (and enabled Firmar) after a different keystore file was picked, so the displayed signer did not match the selected file — fixed by clearing certs/error/selection on file change + regression test (fix commit 27d86a5); UNFIXED: none
Author
Owner

bullet: fixer launched (round 1/2).

bullet: fixer launched (round 1/2).
Author
Owner

FIXED: applied 1 — d8d191a: the reviewed finding (CertPicker kept the previous file's certificate list, and enabled Firmar, after a different keystore file was picked) held only for a settled load; a load still in flight when the file changed repopulated the list from the superseded file. CertPicker now discards a superseded load generation (certs/error/loading), regression test App.test.ts:123 — red without the fix, green with it; 16/16 tests, pnpm check and build exit 0. Refs #5; UNFIXED: none

FIXED: applied 1 — d8d191a: the reviewed finding (CertPicker kept the previous file's certificate list, and enabled Firmar, after a different keystore file was picked) held only for a settled load; a load still in flight when the file changed repopulated the list from the superseded file. CertPicker now discards a superseded load generation (certs/error/loading), regression test App.test.ts:123 — red without the fix, green with it; 16/16 tests, pnpm check and build exit 0. Refs #5; UNFIXED: none
Author
Owner

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

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

REVIEW: FIXED 1 — findings: commit d8d191a: clearFile() in web/src/lib/App.svelte reset only file, so removing the document and picking another re-enabled Firmar with the previously loaded signer while the picker showed "Sin certificado" — fixed in f873e60 (clears cert, regression test in App.test.ts); UNFIXED: none

REVIEW: FIXED 1 — findings: commit d8d191a: `clearFile()` in `web/src/lib/App.svelte` reset only `file`, so removing the document and picking another re-enabled *Firmar* with the previously loaded signer while the picker showed "Sin certificado" — fixed in f873e60 (clears `cert`, regression test in `App.test.ts`); UNFIXED: none
Author
Owner

bullet: fixer round done — fresh reviewer re-checking.

bullet: fixer round done — fresh reviewer re-checking.
Author
Owner

REVIEW: FIXED 2 — commit f873e60: findings: PKCS#12 with two certificates sharing one serial crashed the cert picker (Svelte keyed-each throws each_key_duplicate) and made selection resolve to the first match — fixed ad361cb; picker showed only the expiry date although the issue asks for valid dates (CN, issuer, valid dates) — fixed a19259f; UNFIXED: none

REVIEW: FIXED 2 — commit f873e60: findings: PKCS#12 with two certificates sharing one serial crashed the cert picker (Svelte keyed-each throws each_key_duplicate) and made selection resolve to the first match — fixed ad361cb; picker showed only the expiry date although the issue asks for valid dates (CN, issuer, valid dates) — fixed a19259f; UNFIXED: none
Author
Owner

bullet: fixer round done — fresh reviewer re-checking.

bullet: fixer round done — fresh reviewer re-checking.
Author
Owner

REVIEW: FIXED 1 — commit a19259f: findings: removing the document while a keystore load was still in flight left the late onpick(session.signer) repopulating App.cert after clearFile(), so the next document enabled signing with a certificate never loaded for it (reproduced red) — fixed 3918525; UNFIXED: none

Evidence (this round, head a19259f → fix 3918525):

  • Regression test web/src/lib/App.test.ts "does not resurrect the signer when the document is removed while a load runs": red before the fix (expected false to be true — Firmar enabled), green after; fix is onDestroy bumping the load generation in web/src/lib/components/CertPicker.svelte.
  • cd web && pnpm test exit 0 — 19 passed (10 pkcs12 + 9 App); pnpm check 0 errors/373 files; pnpm build wrote build.
  • Gates: test -d decompiled / find decompiled -name '*.java' | grep -q . / git diff --check — 0 / 0 / 0.
  • No test skipped or weakened, no new dependency (web/package.json untouched), Refs #5 only.

Plan-scope and prior-round fixes (27d86a5, d8d191a, f873e60, ad361cb, a19259f) re-verified green at this head. Full table: .bullet/meta/issue-5.review.md.

REVIEW: FIXED 1 — commit a19259f: findings: removing the document while a keystore load was still in flight left the late onpick(session.signer) repopulating App.cert after clearFile(), so the next document enabled signing with a certificate never loaded for it (reproduced red) — fixed 3918525; UNFIXED: none Evidence (this round, head `a19259f` → fix `3918525`): - Regression test `web/src/lib/App.test.ts` "does not resurrect the signer when the document is removed while a load runs": red before the fix (`expected false to be true` — Firmar enabled), green after; fix is `onDestroy` bumping the load generation in `web/src/lib/components/CertPicker.svelte`. - `cd web && pnpm test` exit 0 — 19 passed (10 pkcs12 + 9 App); `pnpm check` 0 errors/373 files; `pnpm build` wrote `build`. - Gates: `test -d decompiled` / `find decompiled -name '*.java' | grep -q .` / `git diff --check` — 0 / 0 / 0. - No test skipped or weakened, no new dependency (`web/package.json` untouched), `Refs #5` only. Plan-scope and prior-round fixes (27d86a5, d8d191a, f873e60, ad361cb, a19259f) re-verified green at this head. Full table: `.bullet/meta/issue-5.review.md`.
Author
Owner

bullet: fixer round done — fresh reviewer re-checking.

bullet: fixer round done — fresh reviewer re-checking.
Author
Owner

REVIEW: CLEAN — commit 3918525: findings none

All issue #5 acceptance criteria met (parse P12 without node-forge, non-extractable CryptoKey exposed to sign flow, wrong-PIN + expired-cert error paths, generated-P12 unit tests); plan Scope = diff (14 files, all under web/, none out of scope). Re-ran this session: pnpm test 19/19, pnpm check 0 errors, pnpm build exit 0, bullet gates 0/0, git diff --check clean, no test skipped/loosened, no closing keywords, worktree clean. The head commit 3918525 (in-flight load discarded on picker unmount) verified red-without-fix/green-with-fix. UNFIXED: none. Disclosed deviations (legacy PBES1 rejected, PKCS#12 MAC not verified, openssl as test tool) unchanged follow-up material.

REVIEW: CLEAN — commit 3918525: findings none All issue #5 acceptance criteria met (parse P12 without node-forge, non-extractable CryptoKey exposed to sign flow, wrong-PIN + expired-cert error paths, generated-P12 unit tests); plan Scope = diff (14 files, all under web/, none out of scope). Re-ran this session: pnpm test 19/19, pnpm check 0 errors, pnpm build exit 0, bullet gates 0/0, git diff --check clean, no test skipped/loosened, no closing keywords, worktree clean. The head commit 3918525 (in-flight load discarded on picker unmount) verified red-without-fix/green-with-fix. UNFIXED: none. Disclosed deviations (legacy PBES1 rejected, PKCS#12 MAC not verified, openssl as test tool) unchanged follow-up material.
Author
Owner

bullet: review CLEAN — ready to publish.

bullet: review CLEAN — ready to publish.
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: PR #24 open → bullet-develop (auto-merge follows).

bullet: PR #24 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

Claimed — branch issue/5-use-case-load-signing-cert-fro. Doing: merge into bullet-develop, resolve App.svelte/App.test.ts conflicts.

Claimed — branch `issue/5-use-case-load-signing-cert-fro`. Doing: merge into bullet-develop, resolve App.svelte/App.test.ts conflicts.
Author
Owner

Claimed — branch bullet-develop. Doing: trunk→bullet-develop reconciliation (trunk work already merged at 329550a; clearing the dead conflict-solver's in-flight issue/5 merge from this worktree, then gates + report). Refs #5

Claimed — branch `bullet-develop`. Doing: trunk→bullet-develop reconciliation (trunk work already merged at 329550a; clearing the dead conflict-solver's in-flight issue/5 merge from this worktree, then gates + report). Refs #5
Author
Owner

RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — origin/main 9c2af1a is already an ancestor of origin/bullet-develop 329550a, git merge origin/main = "Already up to date.", no commit; the only blockage in this worktree was a dead agent's uncommitted merge of issue/5-..., saved as .bullet/meta/issue-5.inflight-issue-merge.patch then aborted — PR #24 is red for issue-branch vs integration-branch, not trunk; gates test -d decompiled 0 / find decompiled -name "*.java" | grep -q . 0, pnpm test 92/92, pnpm check 0 errors). Refs #5

RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — `origin/main` 9c2af1a is already an ancestor of `origin/bullet-develop` 329550a, `git merge origin/main` = "Already up to date.", no commit; the only blockage in this worktree was a dead agent's uncommitted merge of `issue/5-...`, saved as `.bullet/meta/issue-5.inflight-issue-merge.patch` then aborted — PR #24 is red for issue-branch vs integration-branch, not trunk; gates `test -d decompiled` 0 / `find decompiled -name "*.java" | grep -q .` 0, `pnpm test` 92/92, `pnpm check` 0 errors). Refs #5
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: trunk→bullet-develop reconciliation (trunk advanced to c296b96 = merge of PR #25; its tree is byte-identical to bullet-develop 329550a, so trunk gained no content). Waiting on the live conflict-solver (-i-5, in-progress issue/5 merge in this worktree) before touching refs. Refs #5

Claimed — branch `bullet-develop`. Doing: trunk→bullet-develop reconciliation (trunk advanced to `c296b96` = merge of PR #25; its tree is byte-identical to `bullet-develop` 329550a, so trunk gained no content). Waiting on the live conflict-solver (`-i-5`, in-progress issue/5 merge in this worktree) before touching refs. Refs #5
Author
Owner

INTEGRATED: #5 merged into bullet-develop (conflicts resolved: App.svelte kept the verify-mode block and reinstated clearing the selection when the last document is removed; App.test.ts kept both sides tests plus the keystore import; CertPicker/keystore/i18n/fixtures auto-merged)

INTEGRATED: #5 merged into bullet-develop (conflicts resolved: App.svelte kept the verify-mode block and reinstated clearing the selection when the last document is removed; App.test.ts kept both sides tests plus the keystore import; CertPicker/keystore/i18n/fixtures auto-merged)
Author
Owner

RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — the trunk only advanced with merge commits whose trees are byte-identical to bullet-develop (c296b96 = PR #25, then ff55d2f), so git merge --no-ff origin/main merged clean twice and added zero content: a090a08 (carries the conflict-solver merge 1ae7480, Refs #5) and c9257b7 (pushed; origin/bullet-develop = c9257b7, origin/main = ff55d2f, ahead by 1); issue branches untouched; gates test -d decompiled 0 / find decompiled -name "*.java" | grep -q . 0 (11487 files) / git diff --check 0; smoke pnpm test 96/96, pnpm check 0 errors on the merged tree). Refs #5

RECONCILED: trunk work merged into bullet-develop (conflicts resolved: none — the trunk only advanced with merge commits whose trees are byte-identical to bullet-develop (`c296b96` = PR #25, then `ff55d2f`), so `git merge --no-ff origin/main` merged clean twice and added zero content: `a090a08` (carries the conflict-solver merge `1ae7480`, Refs #5) and `c9257b7` (pushed; `origin/bullet-develop` = c9257b7, `origin/main` = ff55d2f, ahead by 1); issue branches untouched; gates `test -d decompiled` 0 / `find decompiled -name "*.java" | grep -q .` 0 (11487 files) / `git diff --check` 0; smoke `pnpm test` 96/96, `pnpm check` 0 errors on the merged tree). Refs #5
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#5
No description provided.