Koder Signer — multi-jurisdiction architecture (BR / EU / US)

accepted

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:

  1. *box#114 surfaced the API contract for OCI signing* but the underlying signer is BRonly 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 (defeats policies/reuse-first.kmd).
  1. *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.
  1. *IDAS 2.0*(Reg. 2024/1183) introduces the European Digital Identity Wallet (EUDI Wallet) and mandates MemberState 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 (BB / 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 DOCICP15 (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

  1. Client POST /v1/sign/pades?jurisdiction=eu with {document, policy?, key_source?, key_id?} (or POST /v1/sign/oci?jurisdiction=br for image manifests).
  2. jurisdictions/<juris>.go validates the request against the regime's required fields (e.g. EU QES requires key_source=qscd and a qualified_cert claim resolvable in LOTL).
  3. trust/resolver.go returns the trust bundle for the jurisdiction.
  4. pkcs11/ or pfx/ produces the signature using the bundle's chain.
  5. tsa/ requests a timestamp from the jurisdiction's configured TSA endpoint (ITI-credentialed for BR; qualified TSA from TL for EU; DigiCertIdenTrustSectigo for US).
  6. revocation/ checks OCSP/CRL against the chain.
  7. The format layer (pades/`cades/xades`ocisign/) packages everything into the appropriate envelope.
  8. 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 ICPBrasil) + 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 required
    • ades → advanced signature, certificate must chain to an LOTL TSP
    • qes → 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 BLT, #009 crossvalidation ~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

Resolved decisions

Open during the draft phase; ratified 20260520 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 profile
  • dev.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, 20260520) — 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 20260523 — 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 20260523 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 20260523 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 20260523 — services/crypto/signer/backlog/pending/014-eidas-lotl-fetcher-skeleton.md
Open tracking ticket for kboxside `-jurisdiction` flag (wave G consumer) claude When wave C ships ✅ done 20260523 — infra/net/box/backlog/pending/133-cli-jurisdiction-flag.md

Source: ../home/koder/dev/koder/meta/docs/stack/rfcs/signing-RFC-001-multi-jurisdiction.kmd