eID 2-cert — production-environment validation (2026-06-18)¶
All core flows of the Smart-ID / eIDAS model have been verified running end-to-end on a real iPhone + staging backend (rp-api.eidmongolia.mn).
Validated flows¶
| Flow | Key / PIN | Cert | Evidence |
|---|---|---|---|
| Enrollment (DAN KYC → liveness → distributed keygen → 2 cert) | sign + auth | SIGN + AUTH | Real phone: users (civil_id), 2 ACTIVE cert, device (has_sign+has_auth) |
| Login (web login, QR) | PIN1 · authentication | clientAuth | Web "Successfully logged in" + identity |
| Contract signing (web sign, QR) | PIN2 · signing · non-repudiation | QUALIFIED | Web "Signed the contract", COMPLETE/OK |
Key architectural decisions¶
- Dual-key 2-cert. Every citizen has TWO independent threshold ECDSA keys: Authentication
(PIN1,
clientAuth, login) and Signing (PIN2,contentCommitmentnon-repudiation, legally binding signature). One phone, onedocumentNumber, two Keychain slots. Enroll: 2 keygen (fast via prewarm), 2 cert; the signing/auth flow loads its respective key (credentialFor(flow)backend,approve(authentication:)iOS). - Identity = civil_id. Cert/etsi is
PNOMN-<civil_id>(digits only). reg_no (РД, Cyrillic "МА...") does NOT go into the cert — it is a separate attribute. The DAN callback sends civil_id + reg_no + image + identity. DOB/gender go into the SDA extension (RFC 3739). - DAN secure binding. The dan.gerege.mn callback does not send
state(only reg_no), so the state is embedded in the callback_url and the callback is reliably tied to the specific session (reg_no is only a cross-check) — preventing the identity-takeover race. - Liveness. The DAN reference photo vs. selfie is compared via the InsightFace ArcFace (buffalo_l) service
(
/v1/compare, cosine ≥ 0.6). iOS uses the full camera (Vision, with DAN framing).
Components¶
- Backend (Go):
service/dan(KYC + liveness + photo),crypto/ca(Profile + SDA),service/memory/crypto_wired(dual-key enroll, FK-correct ordering),domain(User/KYC FSM). - iOS (Swift SDK + SwiftUI): reference DAN screens (phone→DAN→liveness→PIN×2→keygen),
dual-key
enroll, FaceVerify (ArcFace, %), RoundTripOverlay, Main Dashboard. - Web (Next.js RP demo): login (auth) + sign (PIN2) flow.
- Staging deploy + operational facts:
deploy/,memory/staging-ops-gotchas.
Remaining for production (TODO)¶
- HSM-backed CA (currently staging ephemeral self-signed) —
docs/PKI_CA_ONBOARDING.md. - Org / legal-person (NTRMN-) enrollment.
- Postgres: dan_replay_cache, PII purge sweep (currently in-memory session).
- Enrich the dashboard (pending-sign inbox, settings).