Gerege eID — Onboarding to the national CA (L1 CSR → chain)¶
By law, Gerege must chain to the national root CA — a subordinate CA, not a standalone root. The final hierarchy:
National Root CA (Government HSM) └── Gerege CA (L1) — the national root signs ONLY this; pathlen:1 ├── Gerege Personal Issuing CA (L2, pathlen:0) → Natural Person cert └── Gerege Organization Issuing CA (L2, pathlen:0) → Legal Person certeIDAS naming: Personal = Natural Person, Organization = Legal Person. Two separate L2 Issuing CAs under L1 (separated by purpose/policy) — validated on a real HSM (
TestHSMProxyDualIssuingCA).
1. L1 CSR — contents (validated on a real HSM)¶
TestPrepareL1CSR in internal/crypto produces the L1 CSR with the HSM key (PoP):
Subject: C=MN, O=Gerege Systems LLC, CN=Gerege eID CA
Public-Key: RSA-2048 (ceremony July 2048; 4096 ~1 year later)
Requested Extensions:
Basic Constraints: critical, CA:TRUE, pathlen:1 ← REQUIRED for 2-tier (L1→L2)
Key Usage: critical, Certificate Sign, CRL Sign
Signature: sha256WithRSAEncryption (proof-of-possession verify OK)
Match the Subject DN to the national CA's naming rules (they may require a specific O/CN/ organizationIdentifier). The above is a recommendation.
2. (!) What to obtain IN WRITING from the national CA¶
| Item | Why |
|---|---|
| pathLenConstraint ≥ 1 | The national root decides the pathlen of the final L1 cert (the CSR only requests). If 0, Gerege cannot issue L2 → the 2-tier structure FAILS. Include this in the subordinate CA agreement. |
| Algorithm / key size | RSA-2048 (currently) — must be compatible with their ceremony/CP-CPS |
| Validity | L1 is shorter than the root |
| DN / naming rules | O/CN/organizationIdentifier |
| CRL/OCSP rules | Where AIA/CDP URLs point |
3. WHEN to generate the real CSR¶
The real L1 key is generated at the official key ceremony (July, RSA-2048) with M-of-N
custodians, tamper bags, and witnesses — hsm-thales-gerege/docs/eid-issuing-ca-key-ceremony-script.md.
Do not use the demo key. During the ceremony, generate the CSR by one of two methods:
A. Go (hsm-proxy, this repo): given the ceremony's key label,
HSMPROXY_E2E_URL=https://<proxy>:8443 HSMPROXY_E2E_CERT=admin.crt HSMPROXY_E2E_KEY=admin.key \
CSR_OUT=gerege-l1.csr go test ./internal/crypto -run TestPrepareL1CSR -v
# (change the test to match the label to the ceremony key; NOTE that the demo deletes the key)
B. Luna native (cmu, compatible with the ceremony script):
cmu requestcertificate -slot <issuing> -lco \
-publichandle <ph> -privatehandle <prh> \
-sha256withrsa -outputfile gerege-l1.csr \
-cn "Gerege eID CA" -o "Gerege Systems LLC" -c MN
cmudoes not put extensions in the CSR — the national root sets pathlen/keyUsage by agreement.
4. After the L1 cert arrives¶
- Import the L1 cert signed by the national root into the Gerege HSM (the cert is public, safe).
- Within Gerege, create two separate L2 issuing CAs (each signed by L1):
- Personal (Natural Person) — key label e.g.
gerege-personal-issuing-ca - Organization (Legal Person) — key label e.g.
gerege-org-issuing-caEach withpathlen:0. On the organization leaf,organizationIdentifier(OID 2.5.4.97, e.g. NTRMN-). - Go server configuration (one server loads both issuing CAs at once):
SMARTID_PKI_CA_PROVIDER=hsmproxy- Personal:
SMARTID_HSMPROXY_KEY_LABEL=<Personal L2 key>,SMARTID_HSMPROXY_ISSUING_CERT=<Personal L2 cert> - Organization (optional):
SMARTID_HSMPROXY_ORG_KEY_LABEL=<Org L2 key>,SMARTID_HSMPROXY_ORG_ISSUING_CERT=<Org L2 cert> - Automatic selection: by ETSI prefix —
PNOMN-…→ Personal CA,NTRMN-…→ Organization CA (domain.SubjectTypeForEtsi+store.caFor). If Org is not configured, it falls back to Personal. - the full chain (L2+L1) is attached to the leaf; trust anchor = national root.
Validated on a real HSM: -
TestHSMProxy3LayerChain— National Root → L1 (pathlen:1) → L2 (pathlen:0) → leaf. -TestHSMProxyDualIssuingCA— L1 → {Personal, Organization} issuing CA → two leaves, two chains verify OK.
Cloud HSM failover (automatic switchover when on-prem Luna goes down)¶
To prevent enrollment/seal from stopping when the on-prem Luna session drops
(slot ... not found / CKR_SESSION_HANDLE_INVALID), you can configure a fallback hsm-proxy
(behind a Thales DPoD Luna Cloud HSM). The same CA key (same label) must have been cloned into the
DPoD partition via backup/restore (the partition config must be identical to prodpart1 —
hsm-thales-gerege/docs/ceremony-rehearsal-results.md).
SMARTID_HSMPROXY_FALLBACK_URL=https://<dpod-proxy>:8443 # Cloud HSM-backed hsm-proxy
SMARTID_HSMPROXY_FALLBACK_CLIENT_CERT=/app/hsmproxy/dpod-client.crt # if empty, uses the primary's
SMARTID_HSMPROXY_FALLBACK_CLIENT_KEY=/app/hsmproxy/dpod-client.key
SMARTID_HSMPROXY_FALLBACK_SERVER_CA=/app/hsmproxy/dpod-proxy-ca.crt
SMARTID_HSMPROXY_PREFER=primary # "fallback" puts Cloud FIRST (choose on-prem as failover manually)
- Behavior: if primary is unreachable or returns 5xx (slot/session) → automatically to fallback; 4xx (wrong label) → no failover. The signature is verified with the issuing CA cert's pubkey (a fallback with the wrong key → fail-closed). It applies to Personal + Organization issuing CA and the e-Seal client alike.
- Manual switchover:
SMARTID_HSMPROXY_PREFER=fallback(restart) — makes Cloud the primary. - Tests:
TestHSMProxyFailover_*(IssuingCA/PreferFallback/BothDown/4xxNoFailover/SealClientEC).
Enabling the Organization issuing CA in production (in the stage WITHOUT a national root)¶
The current production Personal CA (eidmongol-issuing-ca-v1) is self-signed (the national L1 does
not exist yet). Set up the Organization CA the same way — a new key + self-signed cert in the HSM:
- On the HSM host (ceremony — hsm-thales-gerege/docs/eid-issuing-ca-key-ceremony-script.md): (public/private label SAME — hsm-proxy searches by label; match the Personal key's convention.)
- Mint the cert remotely from the app host (no need to return to the HSM host) —
cmd/hsmca-selfsigncreates a self-signed CA cert via the proxy's/pubkey+/sign-digest: - .env (app host):
SMARTID_HSMPROXY_ORG_KEY_LABEL=eidmongol-org-issuing-ca-v1,SMARTID_HSMPROXY_ORG_ISSUING_CERT=/app/hsmproxy/org-issuing-ca.crt→docker compose up -d app. - Verification: startup log "HSM-proxy Organization CA enabled",
GET /crl/org200, and when issuing a seal cert from admin, the issuer is the Organization CA.
Later, once the national root is ready, both CAs are re-signed by L1 (see §2 above).
Making the e-Seal a QSCD (seal leaf key in the HSM)¶
By default the e-Seal leaf key is generated on the server (software HSM, DB AES-GCM), so its level is
QUALIFIED. To make it a real QSCD (QcSSCD + QCP-l-qscd policy), every seal key must stay
inside the HSM and the private key must never come to the server. The model is the same as the
issuing CA: generate the key on the HSM via ceremony, and the server operates only via /pubkey +
/sign-digest.
Modeling: the e-Seal is a contracted (managed) service like SK — the operator performs a separate HSM key ceremony for each organization (the organization does not generate the seal key itself).
- On the HSM host (per org, EC P-256): label =
<prefix><register code>(default prefixeseal-; e.g. register 1234567 →eseal-1234567):(public/private label SAME; hsm-proxy searches by label. Change the prefix viacmu generatekeypair -slot <issuing> -lco \ -keytype ec -curve prime256v1 \ -labelpublic "eseal-1234567" -labelprivate "eseal-1234567" \ -sign 1 -verify 1 -encrypt 0 -decrypt 0 -wrap 0 -unwrap 0 -derive 0SMARTID_SEAL_KEY_PREFIX.) - .env (app host):
SMARTID_SEAL_QSCD=true(the hsmproxy CA provider + HSMPROXY creds must already be present — otherwise the server fail-fasts to stop, preventing a false QSCD declaration). If the prefix differs,SMARTID_SEAL_KEY_PREFIX=eseal-. - Issue the seal cert (admin or RP): the server gets the pubkey of that org's
eseal-<code>label from/pubkeyand issues the cert via the Organization CA. The private key stays in the HSM. - Sealing:
POST /v3/seal/{orgEtsi}— the server signs the digest via/sign-digest(HSM key) and returns it, verified with the cert's pubkey. - Verification: the seal cert's
certificateLevel=QSCD; on the admin "Organization CA" screen, "QSCD (QcSSCD + QCP-l-qscd)"; inopenssl x509, a QcSSCD statement + policy 0.4.0.194112.1.3.
If you try to issue a seal cert for an org that has NOT had the ceremony, the server gets a
/pubkey 404 and returns an error (fail-closed) — it will not silently fall back to a software key
while a QSCD is declared.