ICP-Brasil digital signature — Koder Signer contract
signing specs/signing/icp-brasil.kmd
Normative contract for the Koder Signer service (`services/crypto/signer/`) covering ICP-Brasil digital signature: supported formats (PAdES, CAdES, XAdES), signature policies (AD-RB, AD-RT, AD-RV), hardware token integration (A3 via PKCS#11), file certificate (A1 PFX) loading, certificate chain validation, timestamp authority (TSA) interaction, and revocation checking (CRL + OCSP). Applies to every Koder component that needs digital signature with legal validity in Brazil (per MP 2.200-2/2001 art. 10 §1º). Other Koder components consume Signer via REST/gRPC, never reimplementing PKI primitives locally (per `policies/reuse-first.kmd`).
When this spec applies
Primary triggers
- Bootstrap services/crypto/signer/
- Refactor products/horizontal/sign to consume Signer
All triggers
- Implement digital signature with ICP-Brasil legal validity
- Add A3 hardware token (smartcard) support to a Koder component
- Generate PAdES/CAdES/XAdES with policy AD-RB/AD-RT/AD-RV
- Validate ICP-Brasil signed document
Specification body
Spec — ICP-Brasil digital signature (Koder Signer contract)
Version: 0.1.0 — Draft Status: Proposed (2026-05-13)
Position in multi-jurisdiction architecture (2026-05-20). Per
rfcs/signing-RFC-001-multi-jurisdiction.kmd(draft), Koder Signer is designed as a single service issuing signatures under three jurisdictions (BR, EU, US). This spec is the BR profile of that design — it stays normative for everything ICP-Brasil related; sibling profiles (eidas.kmd,esign.kmd) open when their waves begin. Consumers select the jurisdiction per request via?jurisdiction=br|eu|us; this spec applies whenbris selected.
Scope. This spec defines the contract Koder Signer (
services/crypto/signer/) exposes for digital signature with legal validity in Brazil. It governs both the internal implementation of Signer and the consumer contract that every other Koder component (Koder Sign, Flow, custom integrations) follows when requesting a signature. The provider side (key generation, HSM integration, root CA store) is covered separately when those sub-components mature.Legal anchor. MP 2.200-2/2001 (still in force) distinguishes two types of electronic signature in Brazil:
- Art. 10 §1º — Signatures via ICP-Brasil PKI carry legal validity equivalent to handwritten signature by presumption.
- Art. 10 §2º — Other forms (drawn, typed, OTP-based) are valid when both parties agree.
Koder Sign (
products/horizontal/sign/) currently implements only §2º (drawn/typed + email OTP). This spec covers what is needed for §1º — strict ICP-Brasil compliance.
R1 — Supported signature formats
Signer MUST support these output formats:
| Format | Carrier | Use case |
|---|---|---|
| PAdES (PDF Advanced Electronic Signature) | PDF document | Contracts, certificates, official documents |
| CAdES (CMS Advanced Electronic Signature) | .p7s file (detached) or embedded | Generic binary documents, XML, archives |
| XAdES (XML Advanced Electronic Signature) | XML element | NFe, eSocial, structured government docs |
ETSI TS 103 171 / TS 103 173 / TS 103 172 normative reference for PAdES/CAdES/XAdES respectively. ICP-Brasil profile DOC-ICP-15 adds Brazilian-specific OIDs and policy URLs.
PDF Signature Visual representation: required when signature should
appear graphically in the document; SHOULD respect specs/koder-app/
visual conventions when rendered by Koder Sign.
R2 — Signature policies
Signer MUST support these ICP-Brasil signature policies (in order of cryptographic strength):
| Policy | Name | Includes |
|---|---|---|
| AD-RB | Assinatura Digital com Referência Básica | Signer cert + chain |
| AD-RT | Assinatura Digital com Referência de Tempo | AD-RB + qualified timestamp (RFC 3161 TSA) |
| AD-RV | Assinatura Digital com Referência para Validação | AD-RT + complete CRL/OCSP responses for chain |
The policy is encoded as an OID + URL in the signed attributes per DOC-ICP-15.
Default: AD-RT (timestamp gives non-repudiation across cert expiry). Caller MAY request AD-RB (lightweight) or AD-RV (long-term archival).
R3 — Key material sources
Signer MUST accept two key sources:
R3.1 — A1 certificate (file-based)
PKCS#12 (.pfx/.p12) file containing the private key encrypted with
a passphrase. Loaded via the API:
POST /v1/sign/pades
Content-Type: multipart/form-data
document: <PDF>
cert: <PFX>
passphrase: <string>
policy: AD-RT
The passphrase MUST NOT be persisted server-side after the request
completes. The PFX MUST be wiped from memory using
crypto/subtle-style constant-time zeroing.
R3.2 — A3 hardware token (PKCS#11)
Smartcard or USB token (SafeNet eToken, Watchdata Proxkey, Gemalto IDPrime, Morpho, etc.) accessed via PKCS#11 driver. Two deployment modes:
- Server-side token: Signer host has the token attached + driver
installed. API receives PIN, calls
C_Signvia the driver. - Client-side token: end user has the token at their machine.
Signer generates the hash-to-sign, returns it; client signs with
the token; signed hash is returned to Signer for finalization.
REQUIRES
koder_kitPKCS#11 binding (orkoder_web_kitWebAuthn bridge for browser flow).
The PIN MUST NOT be persisted. Failed PIN attempts MUST be rate-limited (token has hardware lockout typically at 3 attempts; Signer should also enforce its own backoff to avoid lockout).
R4 — Certificate chain validation
Signer MUST validate every certificate against the ICP-Brasil chain rooted at the official AC-Raiz (current generation as of 2026: AC-Raiz v5; older v2/v3/v4 trusted for legacy verify-only).
The full chain set is published by ITI; Signer MUST ship with a
bundled icp-brasil-chains.pem and refresh it on a schedule (default:
daily check + reload). Bundle versioning MUST be recorded in
audit logs (which chain version validated which signature).
Required checks per cert in chain:
notBefore/notAftervalid for signing time- KeyUsage contains
digitalSignature(signer cert) orkeyCertSign(CAs) - ExtendedKeyUsage compatible with intended action
- BasicConstraints CA-flag correct for chain position
- Subject DN includes
OU=ICP-Brasilfor ICP certs - Cert is not revoked (see R5)
R5 — Revocation checking
Signer MUST check certificate revocation status before producing a signature, in this order:
- OCSP — if cert carries an
AuthorityInformationAccessOCSP responder URL, query it. Response cached per ICP-Brasil policy (max 7 days, but typically 1-24h). - CRL — if OCSP unavailable, fall back to CRL listed in
CRLDistributionPoints. CRL freshness: max 24h.
Failure modes:
- Revocation status =
revoked→ reject; emitKSIGNER-SIGN-3001 - OCSP+CRL unreachable → soft-fail OR hard-fail based on policy (default: soft-fail for AD-RB, hard-fail for AD-RT/AD-RV)
- Cert revoked before signing time but signing requested retroactively → reject
For AD-RV, the CRL/OCSP responses MUST be embedded in the signature container for offline verification later.
R6 — Timestamp authority (TSA)
For AD-RT and AD-RV, Signer MUST obtain a qualified timestamp from an ICP-Brasil accredited TSA. Default: ICP-Brasil public TSA at ITI. Custom TSA configurable per deployment.
TSA protocol: RFC 3161 over HTTPS. Signer MUST verify the TSA response signature against the TSA cert (also validated per R4-R5).
Time-stamp policy OIDs are encoded in the signature container.
R7 — Error map
User-facing errors follow specs/errors/user-facing-messages.kmd with
the KSIGNER product prefix:
| Code | Category | Meaning |
|---|---|---|
KSIGNER-CERT-1001 | cert | Invalid PFX / passphrase |
KSIGNER-CERT-1002 | cert | Cert expired |
KSIGNER-CERT-1003 | cert | Cert not in ICP-Brasil chain |
KSIGNER-CERT-1004 | cert | Cert KeyUsage incompatible |
KSIGNER-TOKEN-2001 | token | PKCS#11 driver not found |
KSIGNER-TOKEN-2002 | token | Token not present |
KSIGNER-TOKEN-2003 | token | PIN incorrect |
KSIGNER-TOKEN-2004 | token | Token locked out (hardware) |
KSIGNER-REV-3001 | revocation | Cert revoked |
KSIGNER-REV-3002 | revocation | OCSP+CRL both unreachable (hard-fail) |
KSIGNER-TSA-4001 | timestamp | TSA unreachable |
KSIGNER-TSA-4002 | timestamp | TSA response invalid |
KSIGNER-FMT-5001 | format | Input document corrupted |
KSIGNER-FMT-5002 | format | Signature placement conflict (PDF) |
KSIGNER-JURIS-6000 | jurisdiction | Unsupported jurisdiction name (400) — added in signer#013 wave C |
KSIGNER-JURIS-6001 | jurisdiction | Jurisdiction not implemented (501) — wave D promoted EU/US to implemented; reserved for future profiles |
KSIGNER-JURIS-6099 | jurisdiction | Internal resolver error (500, defensive) |
KSIGNER-EIDAS-1000 | level | ?level= slug not in profile's RequiredLevels() (400) — added in signer#014 wave D stage 1; also fires for br?level=anything |
KSIGNER-EIDAS-1001 | level | Level valid but not yet implemented at format layer (501) — current state for eu/us all levels until stages 2-4 |
All error codes localized en-US + pt-BR per policies/language.kmd.
R8 — Multi-tenancy
Signer MUST comply with policies/multi-tenant-by-default.kmd:
- Every signature request carries
koder_user_id(and optionalworkspace_id). - Audit logs include tenant scope (who signed, on whose behalf, for which workspace).
- Cross-tenant access returns 404, not 403.
Tenant isolation does not apply to ICP-Brasil chain bundles (global, read-only) or TSA cache (global but keyed by hash, no PII).
T1-T6 — Test contract
Every Signer implementation MUST pass:
- T1 — Valid A1 sign: PFX + correct passphrase → valid PAdES with policy AD-RT; ITI Verificador validates green.
- T2 — Valid A3 sign (server-side): mocked PKCS#11 with valid cert + PIN → valid CAdES; verifies against bundled chain.
- T3 — Reject expired cert: PFX with
notAfter< now → errorKSIGNER-CERT-1002; no partial output written. - T4 — Reject revoked cert: cert in test CRL → error
KSIGNER-REV-3001. - T5 — Cross-validate with reference signer: same input signed by
signer-cli SERPROand by Koder Signer produces semantically equivalent containers (byte-identical not required; both verify green viaopenssl cms -verify -policy …and ITI Verificador). - T6 — Round-trip: sign → embed in PDF → re-open → extract signature → verify signature is intact, policy is preserved, TSA proof present (for AD-RT).
Negative-path tests:
- N1 — Tampered document: modify PDF byte after sign → verify detects tamper.
- N2 — Wrong passphrase: error
KSIGNER-CERT-1001; rate-limit applies after 3 attempts in 60s window. - N3 — TSA unreachable: AD-RT requested but TSA down → hard-fail
with
KSIGNER-TSA-4001; no partial signature output.
Out of scope (v0.1.0)
- Signature visual templates (graphic representation in PDF). Tracked separately when Signer reaches consumer UI integration.
- ICP-Brasil cross-border interoperability (eIDAS bridge).
- Long-term archival format LTV / PAdES-LTA (planned for v0.3).
- Signer-as-HSM (Signer hosting keys directly, not just orchestrating
signature against external token/file). Out of scope until KMS
Sector (
services/crypto/kms/) ships.
Roadmap
| Phase | Deliverable | Tickets |
|---|---|---|
| 0.1.0 | This spec; CLI prototype ksigner sign --a1 cert.pfx --policy AD-RB doc.pdf | services/crypto/signer#001-003 |
| 0.2.0 | A3 server-side via PKCS#11; AD-RT timestamp; CRL/OCSP | #004-006 |
| 0.3.0 | A3 client-side bridge (koder_kit PKCS#11 binding) | #007-008 |
| 0.4.0 | Koder Sign integration (refactor internal/crypto/ to consume Signer) | sign#XXX |
| 0.5.0 | XAdES + NFe profile; PAdES-LTA archival | #009+ |
Open questions
- Implementation language: Go (consistent with foundation/) vs.
reuse JVM lib
dss-euvia JNI bridge (mature ICP-Brasil support but adds JVM dependency). Decision deferred to ticket #003. - TSA failover: ICP-Brasil has only ~3 public TSAs. Should Signer ship with a configurable fallback list, or fail-open if primary is down? (Affects R6.)
- Cert chain bundle distribution: ship inside the Signer binary (small, no fetch) vs. fetch on startup from ITI (always fresh, but bootstrap dependency)? Default proposal: ship + daily refresh.
Audit hooks
(Reserved for koder-spec-audit signing once a T1-T6 implementation
template exists. Workflow path: .gitea/workflows/audit-signing.yml.)
References
meta/docs/stack/specs/errors/user-facing-messages.kmdmeta/docs/stack/policies/self-hosted-first.kmdmeta/docs/stack/policies/reuse-first.kmdmeta/docs/stack/specs/multi-tenancy/contract.kmdmeta/docs/cryptography/compendium/05-assimetrica.mdmeta/docs/cryptography/compendium/14-koder-aplicada.mdhttps://www.iti.gov.br (ICP-Brasil authority)https://www.gov.br/iti/pt-br/assuntos/certificado-digital (technical norms)