Koder Signer — multi-jurisdiction architecture (BR / EU / US)
signingRFC001 — Multi-jurisdiction architecture for Koder Signer
Status
*ccepted*— ratificada 20260520 (mesmo dia da abertura). Owner ratificou o desenho de signer único com 3 profiles, o phasing AG, e as recomendações das 4 open questions sem alterações. Implementação das waves CG 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 *hree jurisdictions* Brazil (ICP-Brasil), the European Union (eIDAS / Reg. 9102014 + 20241183), and the United States (ESIGN Act + UETA). The three regimes converge on the *TSI PAdES / CAdES / XAdES*format family, so a single codebase covers all three at the format layer; what changes between them is the *rust anchor* the *imestamp authority* the *ualification level* and the *egal acceptance procedure* This RFC decomposes the design accordingly.
The existing meta/docs/stack/specs/signing/icp-brasil.kmd becomes the *R 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:
- *box#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 BRonly signature is legally inert there — the operator either ships an unsigned image (regression) or chains in an external eIDAS signer (defeatspolicies/reuse-first.kmd).
- *S 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.
- *IDAS 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 onceperdecade architectural pivot — designing the signer with eIDAS as a firstclass profile costs almost nothing now, while retrofitting later means migrating live signatures.
The technical work (formats, TSA client, OCSP/CRL, PKCS#11) is *dentical*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: *R*(ICP-Brasil), *U*(eIDAS — SESAdESQES), *S*(ESIGN + AATL).
- Shared format layer: PAdES (B
B / BT / BLT / BLTA), 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)
- *ardwareoperational qualification*— getting the actual QSCD device, the qualified certificate from a listed TSP, the ICP-Brasil A1A3 certificate from a credentialed AC. These are procurement/legal items, not code.
- *U LOTL signature verification implementation details*(separate RFC if the implementation surface warrants it).
- *IDAS 2.0 EUDI Wallet integration*— separate, larger project; signer is not the right surface.
- *RL 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 |
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 *R profile* the spec stays normative for that profile.
- Bundle: single root (AC Raiz da ICP
Brasil) + 45 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 *igning 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 |
|---|---|---|---|
| * — BR foundation* | signer #002 chain bundle, #003 A1 PFX, #005 TSA, #007 PAdES B |
~4 weeks | — |
| * — BR completeness* | #004 PKCS#11 A3, #006 OCSPCRL, #008 CAdESXAdES CLI | ~3 weeks | A |
| * — Multi-jurisdiction skeleton* | jurisdictions/ package, trust/resolver.go, request-path ?jurisdiction= flag, EU profile spec opened |
~2 weeks | A |
| * — EU AdES* | EU LOTL fetcher + verifier, AdES level support, EU TSA integration | ~4 weeks | C + B |
| * — EU QES* | QSCD validation, qualified-cert checks, eIDAS conformance test suite | ~4 weeks | D |
| * — US ESIGN* | AATL bundle, intent metadata, NIST 800-63 declaration handling | ~2 weeks | C |
| * — 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
hostedfirst.kmd — Signer replaces externals (DocuSign, Adobe Sign, ITI-only signers) where the gate passes
Resolved decisions
Open during the draft phase; ratified 2026
0520 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 |
*efuse*new signatures, return KSIGNER-3001 lotl_unreachable |
ades |
*ontinue*against cached LOTL up to *4 h*staleness window; after 24 h same as qes |
ses |
*ontinue*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 *hree independent envelopes*as OCI annotations:
dev.koder.signature.icp-brasil— BR profiledev.koder.signature.eidas— EU profile (with subannotations for level: `eidaslevel=ses|ades|qes`)dev.koder.signature.esign— US profile
Plus the three modepolicytimestamp companions already standardised in BOX120. Verifiers pick the annotation matching their jurisdiction. Crossjurisdiction 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 — Signerasqualified-TSP (ratified out of scope)
Listing Koder Signer itself as a qualified TSP in any Member State's Trust List is *ut 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 *lient*of an external qualified TSP. Customers operating under QES bring their own TSP credentials (qualified certificate + QSCD reference). Track signerasTSP under its own RFC if/when it becomes a business priority.
Q4 — BR ↔ EU mutual recognition (ratified deferred)
No formal equivalence decision exists between ICPBrasil and eIDAS as of 2026. This RFC does *ot*attempt to bridge the two — multienvelope (Q2) covers cross-border deployments cleanly without invoking equivalence.
If/when Brazil ratifies a treaty or the EU publishes a "thirdcountry trusted list" decision for ICPBrasil, 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
0520) — 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 2026services/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 |
Open US profile spec meta/docs/stack/specs/signing/esign.kmd |
claude/owner | Start of wave F | ✅ stub opened 2026 |
| Open implementation ticket for wave D (eIDAS — LOTL fetcher + AdES levels) | claude | After wave C ships | ✅ done 2026services/crypto/signer/backlog/pending/014-eidas-lotl-fetcher-skeleton.md |
| Open tracking ticket for kbox |
claude | When wave C ships | ✅ done 2026infra/net/box/backlog/pending/133-cli-jurisdiction-flag.md |