Koder Signer — multi-jurisdiction architecture (BR / EU / US)
Status
accepted — ratificada 2026-05-20 (mesmo dia da abertura). Owner ratificou o desenho de signer único com 3 profiles, o phasing A-G, e as recomendações das 4 open questions sem alterações. Implementação das waves C-G destravada — tickets podem ser abertos sob services/crypto/signer/backlog/pending/ conforme o phasing.
Summary
Koder Signer should ship as a single Go service that issues digitally signed envelopes valid under three jurisdictions: Brazil (ICP-Brasil), the European Union (eIDAS / Reg. 9102014 + 20241183), and the United States (ESIGN Act + UETA). The three regimes converge on the ETSI PAdES / CAdES / XAdES format family, so a single codebase covers all three at the format layer; what changes between them is the trust anchor, the timestamp authority, the qualification level, and the legal acceptance procedure. This RFC decomposes the design accordingly.
The existing meta/docs/stack/specs/signing/icp-brasil.kmd becomes the BR profile; this RFC frames it as one of three profiles and specifies the EU/US profiles in parallel.
Motivation
Three forces push the multi-jurisdiction direction now:
- kbox#114 surfaced the API contract for OCI signing, but the underlying signer is BR-only by current spec. As soon as a Koder customer deploys to a regulated EU market (e.g. a German hospital with patient records, an Italian fintech under MiCA), the BR-only signature is legally inert there — the operator either ships an unsigned image (regression) or chains in an external eIDAS signer (defeats
policies/reuse-first.kmd).
- US uptake of Koder Stack is already underway (kbox, Koder Flow, Koder Cloud). ESIGN's permissive default ("any electronic signature with intent") makes the US the easiest target, but the AATL (Adobe Approved Trust List) chain for legally robust PDF signatures and the NIST 800-63 IAL/AAL framework for federal interactions are not free — they need design now to avoid a rewrite later.
- eIDAS 2.0 (Reg. 2024/1183) introduces the European Digital Identity Wallet (EUDI Wallet) and mandates Member-State acceptance of QES from any Trusted Service Provider (TSP) in the EU LOTL. This is a once-per-decade architectural pivot — designing the signer with eIDAS as a first-class profile costs almost nothing now, while retrofitting later means migrating live signatures.
The technical work (formats, TSA client, OCSP/CRL, PKCS#11) is identical across the three regimes. Recognising that early means the signer ships as a multi-jurisdiction service from v0.1.0.
Scope
In
- Three jurisdiction profiles: BR (ICP-Brasil), EU (eIDAS — SESAdESQES), US (ESIGN + AATL).
- Shared format layer: PAdES (B-B / B-T / B-LT / B-LTA), CAdES, XAdES, OCI signature envelopes.
- Shared infra layer: PKCS#11 key access, TSA RFC 3161 client, OCSP/CRL revocation, certificate chain validation.
- Per-jurisdiction trust bundles loaded at runtime, switchable per request.
- Per-jurisdiction TSA endpoint selection.
- API:
POST /v1/sign/<format>?jurisdiction=br|eu|us(and per-format aliases for backward compat with #010).
Out (this RFC)
- Hardware/operational qualification — getting the actual QSCD device, the qualified certificate from a listed TSP, the ICP-Brasil A1A3 certificate from a credentialed AC. These are procurementlegal items, not code.
- EU LOTL signature verification implementation details (separate RFC if the implementation surface warrants it).
- eIDAS 2.0 EUDI Wallet integration — separate, larger project; signer is not the right surface.
- CRL caching / OCSP stapling performance work — covered by signer's own RFC-002 when those tickets materialise.
Background — three regimes side by side
| Aspect | 🇧🇷 ICP-Brasil | 🇪🇺 eIDAS | 🇺🇸 ESIGN |
|---|---|---|---|
| Legal basis | MP 2.200-2/2001 art. 10 §1º | Reg. UE 9102014 + 20241183 | ESIGN Act 2000 + UETA per state |
| Format spec | DOC-ICP-15 (wraps ETSI) | ETSI EN 319 142 / 122 / 132 | None mandated; PAdES is convention |
| Qualification levels | Implicit single level (the "Assinatura Digital ICP-Brasil") | SES (simple) / AdES (advanced) / QES (qualified) | Implicit single level (intent + retainable record) |
| Trust anchor | Single AC Raiz da ICP-Brasil (ITI, federal) | EU LOTL — 27 national Trust Lists aggregated | Adobe AATL + commercial CAs |
| Hardware requirement | A1 (soft PFX) or A3 (token PKCS#11) | QES requires QSCD (HSM/smartcard CC EAL4+) | None at federal level |
| TSA | ACTs credentialed by ITI | Qualified TSAs in TL | RFC 3161 from any trusted CA |
| Cross-border | Limited; no formal BR↔EU equivalence agreement yet | LOTL covers all 27 + EEA | Recognized via reciprocal trust |
Design
Architecture
services/crypto/signer/backend/
├── cmd/ksigner/main.go — chi router + jurisdiction-aware routes
├── internal/
│ ├── pades/ — PAdES B-B / B-T / B-LT / B-LTA (shared)
│ ├── cades/ — CAdES envelopes (shared)
│ ├── xades/ — XAdES enveloped/enveloping/detached (shared)
│ ├── ocisign/ — OCI image descriptors (shared; signer#010)
│ ├── pkcs11/ — A3/QSCD driver (shared)
│ ├── pfx/ — A1 soft-key (shared)
│ ├── tsa/ — RFC 3161 client, endpoint per jurisdiction
│ ├── revocation/ — OCSP + CRL (shared)
│ ├── trust/
│ │ ├── bundles/
│ │ │ ├── icp-brasil/ — AC Raiz BR + intermediárias
│ │ │ ├── eu-lotl/ — LOTL fetcher + verifier + cache
│ │ │ └── us-aatl/ — Adobe AATL snapshot
│ │ └── resolver.go — pick bundle by request jurisdiction
│ └── jurisdictions/
│ ├── br.go — ICP-Brasil profile (policies, AD-RB/RT/RV)
│ ├── eu.go — eIDAS profile (qualifier checks, LOTL freshness)
│ └── us.go — ESIGN profile (intent metadata, audit trail)
└── pkg/sdk/ — public Go client for consumers (kbox, Sign, Hub)Request flow
- Client
POST /v1/sign/pades?jurisdiction=euwith{document, policy?, key_source?, key_id?}(orPOST /v1/sign/oci?jurisdiction=brfor image manifests). jurisdictions/<juris>.govalidates the request against the regime's required fields (e.g. EU QES requireskey_source=qscdand aqualified_certclaim resolvable in LOTL).trust/resolver.goreturns the trust bundle for the jurisdiction.pkcs11/orpfx/produces the signature using the bundle's chain.tsa/requests a timestamp from the jurisdiction's configured TSA endpoint (ITI-credentialed for BR; qualified TSA from TL for EU; DigiCertIdenTrustSectigo for US).revocation/checks OCSP/CRL against the chain.- The format layer (
pades/`cades/xades`ocisign/) packages everything into the appropriate envelope. - Response carries the envelope + chain + timestamp +
mode(placeholder while the chain isn't yet production-grade).
Jurisdiction profiles
BR — ICP-Brasil
- Existing spec:
meta/docs/stack/specs/signing/icp-brasil.kmd(R1-R8). - This RFC frames it as the BR profile; the spec stays normative for that profile.
- Bundle: single root (AC Raiz da ICP-Brasil) + 4-5 intermediárias active. Refreshed manually or via ITI's published bundle when one materialises.
EU — eIDAS
- New spec:
meta/docs/stack/specs/signing/eidas.kmd(to be opened as part of EU profile rollout). - 3 levels mapped onto request
level=param:ses→ simple signature, no trust chain requiredades→ advanced signature, certificate must chain to an LOTL TSPqes→ qualified signature, requires QSCD + qualified cert + qualified TSA
- Bundle: EU LOTL — XML signed by the Commission, contains pointers to 27 national TLs. Fetcher checks signature, downloads referenced TLs, builds the union trust store. Refreshed daily; failure to refresh stops issuing
ades/qes(logged warning). - The LOTL URL is stable:
https://ec.europa.eu/tools/lotl/eu-lotl.xml.
US — ESIGN
- New spec:
meta/docs/stack/specs/signing/esign.kmd(to be opened as part of US profile rollout). - 2 levels:
simple→ any certificate; signature carries an "intent" metadata block (per ESIGN's requirement of demonstrating intent to sign)aatl→ certificate must chain to Adobe AATL trust list
- Bundle: AATL snapshot, distributed as a CSV by Adobe. Refreshed quarterly (Adobe's cadence).
- For federal interactions: cert must additionally satisfy NIST 800-63 IAL/AAL levels declared in request.
Operational responsibilities
| Layer | Owned by signer (code) | Owned by operator (procurement) |
|---|---|---|
| Format layer | ✓ | — |
| TSA RFC 3161 client | ✓ | TSA endpoint URL + auth credentials |
| Revocation (OCSP/CRL) | ✓ | Network reachability to TSP infra |
| PKCS#11 driver | ✓ | HSMQSCDsmartcard purchase + firmware |
| Trust bundle storage | ✓ | Cert procurement (A1 PFX for BR, QSCD for EU QES) |
| LOTL fetcher | ✓ | Outbound internet to ec.europa.eu |
| Per-tenant key isolation | ✓ (multi-tenancy contract) | — |
| Legal acceptance / archival | — | Customer's compliance team |
Multi-tenancy
Per policies/multi-tenant-by-default.kmd, every signing operation carries a koder_user_id (resolved by services/foundation/id/engine). Tenants register one or more signing identities (= a (key_source, key_id, jurisdiction) triple); the request selects which identity to use. Cross-tenant key access returns 404 (not 403) per the spec.
Phasing
Implementation rolls out as profile waves, each shippable independently.
| Wave | Scope | Estimated effort | Depends on |
|---|---|---|---|
| A — BR foundation | signer #002 chain bundle, #003 A1 PFX, #005 TSA, #007 PAdES B-LT, #009 cross-validation | ~4 weeks | — |
| B — BR completeness | #004 PKCS#11 A3, #006 OCSPCRL, #008 CAdESXAdES CLI | ~3 weeks | A |
| C — Multi-jurisdiction skeleton | jurisdictions/ package, trust/resolver.go, request-path ?jurisdiction= flag, EU profile spec opened |
~2 weeks | A |
| D — EU AdES | EU LOTL fetcher + verifier, AdES level support, EU TSA integration | ~4 weeks | C + B |
| E — EU QES | QSCD validation, qualified-cert checks, eIDAS conformance test suite | ~4 weeks | D |
| F — US ESIGN | AATL bundle, intent metadata, NIST 800-63 declaration handling | ~2 weeks | C |
| G — OCI sign cross-jurisdiction | Extend kbox#114 path to accept --jurisdiction and route accordingly |
~1 week | C |
Wave A is in progress (signer #001-#009 backlog). This RFC unblocks waves C onward by setting the architectural direction.
Cross-references
- signing/icp-brasil.kmd — BR profile (existing normative spec)
- signing/eidas.kmd — EU profile (to be opened with wave D)
- signing/esign.kmd — US profile (to be opened with wave F)
- policies/reuse-first.kmd — Signer is the canonical consumer; no component reimplements PKI
- policies/self-hosted-first.kmd — Signer replaces externals (DocuSign, Adobe Sign, ITI-only signers) where the gate passes
Resolved decisions
Open during the draft phase; ratified 2026-05-20 by the owner.
Q1 — EU LOTL freshness policy when fetch fails (ratified)
If the Commission's endpoint is unreachable, the signer degrades by level:
| Level | Behaviour on stale LOTL |
|---|---|
qes |
Refuse new signatures, return KSIGNER-3001 lotl_unreachable |
ades |
Continue against cached LOTL up to 24 h staleness window; after 24 h same as qes |
ses |
Continue unconditionally (no LOTL dependency) |
Spec'd in wave D. The 24 h window is the trade-off between availability (LOTL endpoints have occasional outages) and revocation latency (revoked TSPs need to stop signing within a business day).
Q2 — Multi-jurisdiction OCI signatures (ratified)
A single image gets three independent envelopes as OCI annotations:
dev.koder.signature.icp-brasil— BR profiledev.koder.signature.eidas— EU profile (with sub-annotations for level:eidas-level=ses|ades|qes)dev.koder.signature.esign— US profile
Plus the three modepolicytimestamp companions already standardised in BOX-120. Verifiers pick the annotation matching their jurisdiction. Cross-jurisdiction single-envelope (multiple validators clauses in one ETSI envelope) is technically possible but no jurisdiction's spec endorses it today — revisit only if/when one of them does.
Q3 — Signer-as-qualified-TSP (ratified out of scope)
Listing Koder Signer itself as a qualified TSP in any Member State's Trust List is out of scope for this RFC and all dependent waves. Cost: multi-year ETSI EN 319 401 conformance assessment, CC EAL4+ certification for the HSM, national supervisory body audit. Business value: only relevant if Koder operates as a commercial TSP for external customers — which is a separate strategic question, not an implementation detail.
For now, the signer acts as a client of an external qualified TSP. Customers operating under QES bring their own TSP credentials (qualified certificate + QSCD reference). Track signer-as-TSP under its own RFC if/when it becomes a business priority.
Q4 — BR ↔ EU mutual recognition (ratified deferred)
No formal equivalence decision exists between ICP-Brasil and eIDAS as of 2026. This RFC does not attempt to bridge the two — multi-envelope (Q2) covers cross-border deployments cleanly without invoking equivalence.
If/when Brazil ratifies a treaty or the EU publishes a "third-country trusted list" decision for ICP-Brasil, wave G is extended with a cross_recognition flag and a follow-up RFC documents the bridging mechanics. Until then: out of scope.
Acceptance criteria for this RFC
- ✅ Owner ratificou (status: accepted, 2026-05-20) — waves C-G destravadas.
- ✅ BR profile spec (
icp-brasil.kmd) ganhou subsection "Position in multi-jurisdiction architecture" apontando pra esta RFC. - 🔲 Signer's
koder.toml[self_hosted]block (when added) records the multi-jurisdiction direction so the registry shows it — pendente até o block existir.
Next actions (post-ratification)
| Action | Owner | Trigger | Status |
|---|---|---|---|
Open implementation tickets for wave C in services/crypto/signer/backlog/pending/ (skeleton: jurisdictions/ pkg + trust/resolver.go + request-path ?jurisdiction= flag) |
claude | Anytime after wave A is far enough (#002+#003 done) | ✅ done 2026-05-23 — services/crypto/signer/backlog/pending/013-multi-jurisdiction-skeleton.md; shipped same day commit 5064c82d7f |
Open EU profile spec meta/docs/stack/specs/signing/eidas.kmd |
claude/owner | Start of wave D | ✅ stub opened 2026-05-23 as part of #013; promoted to v1.0.0 normative inside #014 |
Open US profile spec meta/docs/stack/specs/signing/esign.kmd |
claude/owner | Start of wave F | ✅ stub opened 2026-05-23 as part of #013; promoted to v1.0.0 normative when wave F begins |
| Open implementation ticket for wave D (eIDAS — LOTL fetcher + AdES levels) | claude | After wave C ships | ✅ done 2026-05-23 — services/crypto/signer/backlog/pending/014-eidas-lotl-fetcher-skeleton.md |
Open tracking ticket for kbox-side --jurisdiction flag (wave G consumer) |
claude | When wave C ships | ✅ done 2026-05-23 — infra/net/kbox/backlog/pending/133-cli-jurisdiction-flag.md |