Цахим үнэмлэхийг
системдээ холбо.
Нэвтрэлт, хууль ёсны гарын үсэг, байгууллагын тамга — нэг RP-API-аар. TypeScript, Go SDK нь QR, push, Web2App, OCSP, rate limit-ийг бэлэн шийдэж өгнө.
POST https://rp.eidmongolia.mn/authentication /notification/etsi/PNOMN-… Authorization: Bearer rp_sk_… { "relyingPartyUUID": "…", "relyingPartyName": "Таны үйлчилгээ", "certificateLevel": "QUALIFIED", "signatureProtocol": "ACSP_V2" }
{
"sessionID": "…",
"vc": { "type": "alphaNumeric5", "value": "48271" },
"expiresAt": "2026-10-26T04:05:00Z"
}
Нэг API. Итгэлцлийн бүх үйлдэл.
Иргэнийг таних, гарын үсэг авах, байгууллагын нэрийн өмнөөс батлах — бүгд ижил session загвараар ажиллана.
Нэвтрэлт
Регистрээр push илгээх, эсвэл QR-аар нэргүй нэвтрүүлэх. ACSP_V2 протокол.
/authentication/notification/… §5Гарын үсэг
Digest болон PDF (PAdES B-LT) гарын үсэг, имэйлээр илгээх. Баримт бус, зөвхөн hash.
RAW_DIGEST_SIGNATURE §6QR · Web2App · App2App
Device link: компьютер дээр QR, утсан дээр шууд апп руу шилжих. v3 гарын үсэгтэй холбоос.
/device-link/anonymous §4.5«Миний QR»
Иргэн өөрийн мэдээллийг чанартай гарын үсэгтэй JWS хэлбэрээр хуваалцана.
/authentication/qr §9–10Байгууллага · Тамга
Байгууллагын нэрийн өмнөөс гарын үсэг (onBehalfOf), SEAL тамга, PKI унших эрх.
onBehalfOf: NTRMN-… §8Баталгаажуулалт
Гэрчилгээний гинж, OCSP/CRL, нийтийн /v3/verify — хариуг өөрийн талд заавал шалгана.
ca.eidmongolia.mn/v3/verifySession нээ. Код харуул. Хариугаа шалга.
Найман алхам. Эсвэл SDK-аар хоёр.
SDK-ийн createEidLogin (TS) эсвэл eidlogin.New (Go) нь 5–8-р алхмыг бүхэлд нь хийнэ. Хөтөчид <eid-login-button> тавихад л болно.
- 1БүртгүүлэхorgName, orgRegister, contact, purpose-оо илгээ → relyingPartyUUID + rp_sk_… авна.
- 2Secret-ээ хадгалахНэг л удаа харагдана. Хөтөч, мобайл апп руу хэзээ ч бүү дамжуул.
- 3Discovery уншихGET ca.eidmongolia.mn/.well-known/eid
- 4mTLS · сонголтоорEC P-256 түлхүүр, ASCII subject-тэй CSR илгээж rp.crt авна.
- 5Session эхлүүлэхPush: …/notification/etsi/{id} · QR: …/device-link/anonymous
- 6VC харуулахХариуны vc.value-г иргэнд харуул.
- 7Poll хийхGET /session/{sessionID} → COMPLETE · OK
- 8БаталгаажуулахГинж, хугацаа, түвшин, гарын үсэг, userChallenge-ийг өөрөө шалга. endResult-д ганцаар бүү итгэ.
Банк, төрийн байгууллага, цахим үйлчилгээ (Relying Party) eID Mongolia-д холбогдож иргэнээ утсаар нь нэвтрүүлж, баримтад хууль ёсны гарын үсэг зуруулна. Энэ хуудас бүртгэлээс эхлээд endpoint бүр, хариуг хэрхэн шалгах хүртэл бүх зүйлийг агуулна.
Хэрхэн ажилладаг
Иргэн бүр eID Mongolia аппад хоёр гэрчилгээтэй: нэвтрэлтийн (PIN1) ба гарын үсгийн (PIN2). Хувийн түлхүүрийн хэсгүүд иргэний утас болон серверт тусдаа хадгалагдаж, гарын үсэг зурахад хоёулаа оролцоно (2-of-2 threshold); бүтэн хувийн түлхүүр нэг газар хадгалагдахгүй. Гэрчилгээ нь Монгол Улсын үндэсний Root CA-аас гинжлэгдэнэ. Таны сервер (RP backend) RP-API руу session үүсгэж, иргэн утсаараа баталгаажуулж, та үр дүнг авна.
- Session үүсгэх. Таны backend
POST …/authentication/…эсвэлPOST …/signature/…дуудна. ХариундsessionIDба 5 оронтой VC (баталгаажуулах код) ирнэ — VC-г хэрэглэгчдээ харуулна. - Иргэн батлана. Утсанд push ирнэ (эсвэл QR уншуулна / App2App-аар апп нээгдэнэ). Иргэн дэлгэц дээрх VC-г тулгаад PIN1 (нэвтрэлт) эсвэл PIN2 (гарын үсэг) оруулна.
- Үр дүн авах. Backend
GET …/session/{sessionID}-г long-poll хийнэ.state: COMPLETE,endResult: OKболоход гарын үсэг ба иргэний гэрчилгээ ирнэ. - Хариуг шалгах. Гэрчилгээний гинж, хугацаа, түвшин, гарын үсгийн криптограф — бүгдийг өөрийн талд заавал шалгана.
endResult: OKгэдэгт дангаар нь итгэхгүй.
Хоёр суваг
- Push (notification) — иргэнийг регистрийн дугаар, иргэний дугаар эсвэл ETSI (
PNOMN-…) тодорхойлогчоор нь заана; утсанд нь мэдэгдэл очно. Ширээний компьютер, оператортой үйлчилгээнд. - Device-link (QR / App2App) — иргэнийг урьдчилан мэдэхгүй. Компьютер дээр QR харуулж утсаар уншуулна, эсвэл иргэн утсаараа орж байгаа бол ижил холбоосоор аппыг шууд нээж, батлаад
initialCallbackUrl-аар таны сайт/апп руу буцна (6-р хэсэг).
VC нь PIN биш. 5 оронтой VC-г зөвхөн дэлгэцэнд харуулна; хэрэглэгчээр оруулуулахгүй. PIN1/PIN2-ыг иргэн зөвхөн өөрийн утсан дээр оруулдаг — таны систем PIN-тэй хэзээ ч харьцахгүй.
API түлхүүрээ аваад эхэл.
Байгууллагын нэр, регистр, холбоо барих хүн, зорилго (AUTH · SIGN · CERT · QR_PRESENT), callback host-оо илгээнэ үү. Оператор хянаж баталгаажуулна.
Дэлгэрэнгүй
RP-г eID Mongolia-гийн оператор бүртгэнэ. Нийтийн өргөдлийн API (/v3/rp-applications) түр зогссон (503; discovery registration.paused: true — тэр үед registration.apply, endpoints.public.rp_application зарлагдахгүй) — хүсэлтийг registration.contact руу: eIDsupport@gerege.com эсвэл 7777-3773 утсаар гаргана. Оператор байгууллага ба холбогдох хүнийг шалгаж баталсны дараа relyingPartyUUID ба API secret (rp_sk_…) олгогдоно.
- Хүсэлт гаргах — доорх мэдээллийг оператор руу илгээнэ:
Мэдээлэл Шаардлага Тайлбар orgNameзаавал Байгууллагын нэр — иргэний утсан дээр харагдана (≤ 200 тэмдэгт) orgRegisterзаавал Улсын бүртгэлийн дугаар contactName,contactEmailзаавал Оператор энэ хаягаар холбогдоно purposeзаавал AUTH(нэвтрэлт),SIGN(гарын үсэг),CERT(гэрчилгээ авах),QR_PRESENT(«Миний QR» — иргэний «Миний мэдээлэл»-ийг авах, §4.5) — таслалаар, дараалал чөлөөтэй; батлагдахад эдгээр нь RP-ийн эрх болно.callbackHostsсонголт App2App / Web2App буцах https host-ууд (таслалаар, порт/path/scheme-гүй) — initialCallbackUrlзөвхөн эдгээр host-ийнhttps://хаягт зөвшөөрөгдөнө. Custom scheme хүлээн авахгүй. Бүх host нийлээд ≤ 200 тэмдэгт.certSubjectсонголт mTLS client cert-ийн subject (RFC 2253) — RP-г mTLS заавал болгох бол. Cert нь Bearer secret-ийн нэмэлт, орлохгүй contactPhone,website,noteсонголт note≤ 2000 тэмдэгтnote-оос бусад утга бүр ≤ 200 тэмдэгт. - Батлагдсаны дараа оператор танд
relyingPartyUUIDбаrp_sk_…secret-ийг өгнө. Secret зөвхөн нэг удаа харагдана — backend-ийнхээ нууцын санд хадгална, алдвал оператор шинээр гаргана (хуучин нь хүчингүй болно). IP allowlist-ийг оператор батлахдаа тохируулна; mTLS, тамга (SEAL), PKI_READ зэрэг нэмэлт эрхийг мөн оператор олгоно.
Эрх, secret, callbackHosts болон бусад тохиргооны өөрчлөлтийг оператортой тохирно.
Холболт ба хамгаалалт
Base URL ба танилт
| Хост | Юунд | Танилт |
|---|---|---|
https://rp.eidmongolia.mn | RP-API — нэвтрэлт, гарын үсэг, session, тамга, PKI мэдээлэл (/authentication/…, /signature/…, /session/… г.м.). Бүх RP зөвхөн энэ хостыг дуудна. | Bearer secret заавал + IP allowlist; mTLS заавал гэж бүртгэгдсэн RP-д client cert нэмэлтээр |
https://ca.eidmongolia.mn | Нийтийн: /.well-known/eid, гарын үсэг шалгах /v3/validate/…, /v3/verify/… | auth-гүй |
http(s)://ocsp.eidmongolia.mn/ocsp | OCSP (гэрчилгээний AIA-д бичигдсэн) | нээлттэй |
http(s)://crl.eidmongolia.mn | /crl (иргэн), /crl/org (байгууллага, тамга), /crl/ca (завсрын CA), /ca/gerege-issuing-ca.cer | нээлттэй |
https://tsa.timeserver.mn/ | RFC 3161 цагийн тэмдэг (TSA) — PDF-ийг өөрөө угсрах клиентэд (B-T / B-LT): POST /, application/timestamp-query → application/timestamp-reply; certReq=true + nonce; policy 2.16.496.1.101.1.2.8, SHA-256. TSA cert ← 04 ← 03 ← 02 ← 01 Root. Discovery: endpoints.tsa | нээлттэй (IP тутам 20 r/s, бие ≤ 64 KiB) |
Зөвхөн дээрх нэрсийг ашиглана — өөр нэр дээр ажиллах баталгаагүй. Бүх дуудлага HTTPS, JSON. Секретийг header-ээр илгээнэ:
POST https://rp.eidmongolia.mn/authentication/…
Authorization: Bearer rp_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
- Секретийг зөвхөн backend-д хадгална; браузер, гар утасны апп руу хэзээ ч өгөхгүй. Серверт зөвхөн SHA-256 hash нь хадгалагддаг.
- Body-д
relyingPartyUUID,relyingPartyNameилгээнэ. UUID-г сервер таны бүртгэлээр дардаг (өөр RP-ийн UUID зарлах боломжгүй); иргэнд харагдах нэр нь бүртгэлийн нэр, харинrelyingPartyNameнь дэд системийн нэр (≤ 120 тэмдэгт; урт бол тэмдэгтээр тайрагдана; ж: «Интернэт банк») болж хадгалагдана.
mTLS (Bearer-ийн нэмэлт, production-д зөвлөнө)
Client cert-ийг ижил https://rp.eidmongolia.mn хост руу үзүүлнэ (TLS-ийн үед сонголттой). Cert-ийг eID Mongolia Organization Issuing CA олгоно. RP-д require_mtls асаалттай бол Bearer secret хүчинтэй байсан ч cert-гүй дуудлага 401. Cert нь Bearer-ийг орлохгүй: Authorization: Bearer-гүй дуудлага (хүчинтэй cert-тэй ч) үргэлж 401.
- Түлхүүр ба CSR-ийг өөрийн backend хост дээр үүсгэнэ. Хувийн түлхүүр (
rp.key) тэр хостоос гарахгүй, хэнд ч илгээхгүй. EC P-256-г зөвлөнө. Subject-дCN= таны RP-ийн домэйн (заавал, ≤ 64 тэмдэгт),O= байгууллагын нэр,C=MN. Subject-дC,O,CN(мөн байвалserialNumber,organizationIdentifier) хадгалагдана (OU/L/STг.м. хасагдана);certSubjectнь олгогдсон гэрчилгээтэй таарна.Subject-ийн утга зөвхөн латин (ASCII):
C,O,CN(мөнOUг.м.) — зөвхөн латин үсэг, цифр, зай, ASCII цэг тэмдэг (. , - & ( ) 'г.м.). Кирилл болон бусад ASCII бус тэмдэгт оруулахгүй — байгууллагын нэрийг латинаар бичнэ (Gerege Systems LLC). Яагаад: nginx client cert-ийн subject-ийг OpenSSL RFC 2253 хэлбэрээр дамжуулахдаа ASCII бус UTF-8 байт бүрийг escape хийдэг (O=Гэрэгэ…→O=\D0\93\D1\8D…) тул бүртгэлийнcertSubjectуншигдахгүй, эмзэг болно. ASCII бус утгатай CSR-ийг CA400,{"error":"…","code":"SUBJECT_NOT_ASCII"}-оор буцаана — subject-ийг латинаар засаж CSR-ийг дахин үүсгээд илгээнэ.openssl ecparam -name prime256v1 -genkey -noout -out rp.key chmod 600 rp.key openssl req -new -key rp.key -out rp.csr \ -subj "/C=MN/O=Gerege Systems LLC/CN=accounts.example.mn" # зөвхөн латин (ASCII) openssl req -in rp.csr -noout -verify -subject # гарын үсэг OK, subject зөв эсэх - Зөвхөн
rp.csr-ийг илгээнэ.relyingPartyUUID-тайгаа eIDsupport@gerege.com руу. Гарын үсгийг eID Mongolia-гийн оператор Organization Issuing CA-аар зурна — RP өөрөө гэрчилгээ зурахгүй, өөр CA-ийн гэрчилгээ хүлээн авахгүй. - Хариуд гинжтэй PEM файл ирнэ (
rp-<serial>-chain.pem= таны гэрчилгээ + Organization Issuing CA). Үүнийг бүтнээр нь client cert болгон (rp.crt) тавина. Хүчинтэй хугацаа 3 жил (1095 хоног) — дуусахаас өмнө шинэ CSR илгээнэ.openssl x509 -in rp.crt -noout -subject -issuer -enddate -nameopt RFC2253 # түлхүүр ба гэрчилгээ таарч байгаа эсэх (хоёр мөр ижил байх ёстой) openssl pkey -in rp.key -pubout | openssl sha256 openssl x509 -in rp.crt -pubkey -noout | openssl sha256 - Клиентээ тохируулна. Хаяг хэвээр
https://rp.eidmongolia.mn,Authorization: Bearerхэвээр — нэмж client cert-ийг TLS-ийн үед үзүүлнэ. Серверийн гэрчилгээ нийтийн CA-аас тул trust store өөрчлөх шаардлагагүй.# curl curl --cert rp.crt --key rp.key -H "Authorization: Bearer $RP_SECRET" … // Go cert, err := tls.LoadX509KeyPair("rp.crt", "rp.key") if err != nil { return err } client := &http.Client{Transport: &http.Transport{ TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}, }} // Node (undici / fetch) import { Agent } from "undici"; import { readFileSync } from "node:fs"; const dispatcher = new Agent({ connect: { cert: readFileSync("rp.crt"), key: readFileSync("rp.key") } }); await fetch(url, { dispatcher, method: "POST", headers: { Authorization: `Bearer ${RP_SECRET}`, "Content-Type": "application/json" }, body }); - Шалгаад, дараа нь заавал болгуулна. 4-р хэсгийн нэвтрэлтийн хүсэлтийг client cert-тэй явуулахад 200 +
sessionIDирж байвал cert зөв. Үүний дараа л оператороос RP-дrequire_mtls-ийг асаалгана — оператор таны гэрчилгээний subject-ийг (certSubject) RP-д холбож асаана. Эрт асаавал cert суугаагүй хүсэлт бүр 401.certSubject-ийг өөрөө бөглөх шаардлагагүй: тэр нь гэрчилгээний RFC 2253 хэлбэр (C=MN,O=…,CN=…— CSR-ийн дарааллын урвуу), оператор олгосон гэрчилгээнээс шууд авна. - Асаасны дараа дахин нэг шалгана: cert-тэй хүсэлт 200, cert-гүй ижил хүсэлт 401 байвал mTLS идэвхтэй. 401 гарвал: cert дуусаагүй эсэх,
rp.crt-д гинж бүтэн эсэх, түлхүүр таарч байгааг дээрх командаар шалгаад, хүсэлтийн цагтай нь дэмжлэгт хандана.
IP allowlist
Оператор RP-д IP/CIDR жагсаалт тохируулсан бол бусад IP-ээс 403. Хоосон бол хязгаарлалтгүй. Secret задарсан ч allowlist-ийн гадна ашиглагдахгүй тул тохируулахыг зөвлөнө.
Хязгаар ба хугацаа
- Хүсэлтийн хурд: нэг IP-ээс 20 хүсэлт/сек (burst 40); хэтэрвэл 503. Session poll нэг-нэгээр (зэрэгцээ олон poll хийхгүй).
- Body ≤ 10 MiB. Session-ийн амьдрах хугацаа: нэвтрэлт 5 минут, гарын үсэг ба certificate-choice 10 минут (одоогийн утга — discovery
sessionTtl; тухайн session-ий бодит дуусах мөч — start хариуныexpiresAt) — дараа ньCOMPLETE/TIMEOUT. - Long-poll
timeoutMs: анхдагч 1000, дээд 120000 (их утгыг 120000 болгож тайрна); серверийн холболтын timeout 150 сек. - SDK-ийн
waitForResult/WaitForResult(TS 0.27.0+, Go v0.13.0+) анхдагчаар session дуустал хүлээнэ: start хариуныexpiresAt+ 15 с ({ expiresAt }/WaitOptions.ExpiresAt), алга бол discoverysessionTtl+ 15 с, эс бөгөөс 600 с. Хугацаа дууссан session-ийг CACOMPLETE/TIMEOUTболгоно.maxWaitMs/MaxWaitөгвөл түүгээр таслана. Өмнөх хувилбарууд 150 с-т тасалж, 150 с-ээс хойш баталсан иргэнийг RUNNING гэж үздэг байв.
Discovery
GET https://ca.eidmongolia.mn/.well-known/eid — endpoint, дэмжигдэх алгоритм, гэрчилгээний түвшин, бүртгэлийн зам зэргийг машинд уншигдах хэлбэрээр буцаана. Тохиргоогоо hardcode хийхийн оронд эндээс уншиж болно.
sessionTtl — шинэ session-ий хугацаа (бүхэл секунд): {"auth": 300, "sign": 600} (auth — нэвтрэлт, sign — гарын үсэг ба certificate-choice). CA-ийн админ тохиргооноос амьд уншина — өөрчлөлт ≤ 15 секундэд тусна, хариу Cache-Control: no-cache. Login cookie, хадгалсан төлөв, decoy зэрэг хугацаагаа тогтмол тоогоор биш үүгээр тааруулна; тодорхой session-ий бодит дуусах мөч нь start хариуны expiresAt. SDK: TS client.auth.sessionTtl(), Go c.Auth.SessionTTL(ctx) (TS 0.23.0+ / Go v0.10.0+).
Нэвтрэлт
Санал болгох арга: нэг товч нэвтрэлт (SDK). Ихэнх RP-д хөтөчид <eid-login-button>, backend-д createEidLogin (TypeScript) эсвэл eidlogin (Go) хангалттай. QR, регистрээр push, утсан дээр Web2App, VC, гэрчилгээ ба OCSP шалгалт, decoy, rate limit, аудит бүгд SDK дотор. Дэлгэрэнгүй — §4.1.
<!-- Хөтөч (+ <script type="module" src="…/eid-mongolia-sdk/dist/button.js">, §4.1) -->
<eid-login-button start-url="/auth/eid/start" status-url="/auth/eid/status"></eid-login-button>
// Backend (TS): POST/GET /auth/eid/start · GET /auth/eid/status · GET /auth/eid/callback — нэг handler
const eidLogin = createEidLogin({
client: eid, // EidClient: RP UUID, нэр, API secret, trust anchor
callbackUrl: "https://bank.example.mn/auth/eid/callback",
async onSuccess(id) {
const sid = await sessions.create((await users.findOrCreateByEtsi(id.etsi)).id);
return { redirect: "/dashboard", headers: { "Set-Cookie": `app_session=${sid}; Path=/; HttpOnly; Secure; SameSite=Lax` } };
},
});
app.use("/auth/eid", express.json(), async (req, res) => {
const r = await eidLogin.handleRequest({ method: req.method, url: req.originalUrl, headers: req.headers, body: req.body, ip: req.ip });
res.status(r.status).set(r.headers).send(r.body);
});
// Go: login, err := eidlogin.New(eidlogin.Config{…}); mux.Handle("/auth/eid/", login)
Доорх RP-API урсгал (session эхлүүлэх, poll, device link — §4.2–§4.4) нь UI-гаа өөрөө хийх эсвэл SDK-гүй хэл дээр л хэрэгтэй.
4.1 «eID-ээр нэвтрэх» товч
«Google-ээр нэвтрэх»-тэй адил нэг товч: хөтөчид товч, бүх eID дуудлага таны backend-д таны RP API secret-ээр. Энэ нь тусдаа протокол биш — доорх §4.2–4.4-ын RP-API AUTH (QR, push, Web2App device link v3) болон §8-ын шалгалтыг SDK-д бэлэн урсгал болгосон хэлбэр. SDK: TypeScript 0.14.0+ (createEidLogin + @gerege-systems/eid-mongolia-sdk/button), Go v0.2.0+ (eidlogin багц) — §13. theme, trustProxy / getClientIp, sessionId нь TS 0.14.1+, Go v0.2.1+ (ForwardedClientIP, RemoteAddrIP). Регистрийн oracle-оос хамгаалах decoy + хариуны доод хугацаа, OCSP цуцлалт, аудитын onEvent нь TS 0.15.2+, Go v0.3.2+; товчны label / icon / variant нь TS 0.15.4+.
Хөтөч Таны backend (/auth/eid/…) RP-API (rp.eidmongolia.mn) Иргэний утас
<eid-login-button>
Desktop:
POST start {"mode":"qr"} ─→ device-link/anonymous ─────────────→ session
←─ {qr, vc, expiresIn} + cookie eid_login
GET status (1 с тутам) ──→ GET /session/{id} (long-poll 1 с) ──→ QR уншуулж PIN1
←─ PENDING + шинэ qr … OK ←── validateAuth (flowType=QR) ← COMPLETE
Push (регистрээр):
POST start {"mode":"push","idCode":"УБ12345678"} ─→ notification/etsi/{id} ─→ мэдэгдэл → PIN1
GET status … ←─ OK ←── validateAuth (flowType=Notification)
Утас:
GET start?mode=web2app ──→ device-link/anonymous (initialCallbackUrl) ─→
←─ 302 → https://ca.eidmongolia.mn/dl?deviceLinkType=Web2App&… ─→ eID апп → PIN1
GET callback?sessionId&userChallengeVerifier ←──────────────────────────────────────────── апп буцаана
── GET /session/{id} → validateAuth (Web2App + verifier) → 303 → таны хуудас
OK бүрд: onSuccess(иргэн) → ТАНЫ session (Set-Cookie) + redirect
Backend-ийн замууд. Handler замын сүүлийн хэсгээр (start | status | callback) чиглүүлнэ. eID сервер Web2App буцах замыг үргэлж /auth/eid/callback болгож нормчилдог (§4.4) тул /auth/eid/ дор холбоно.
| Хүсэлт | Бие / оролт | Хариу |
|---|---|---|
POST /auth/eid/start | {"mode":"qr"} эсвэл {"mode":"push","idCode":"УБ12345678"}, нэмэлт "lang":"en". Content-Type: application/json заавал. | 200 {mode, vc, qr?, expiresIn} + Set-Cookie: eid_login=… |
GET /auth/eid/start?mode=web2app | Утасны хөтөч (товч өөрөө шилжинэ); lang | 302 → eID апп (Web2App холбоос) + cookie |
GET /auth/eid/status | eid_login cookie | {"state":"PENDING", qr, vc, expiresIn} · WAIT_CALLBACK · {"state":"OK", redirect?, data?} + таны header · {"state":"FAILED", error} · EXPIRED |
GET /auth/eid/callback | Апп буцаана: ?sessionId=…&userChallengeVerifier=… + cookie | 303 → onSuccess-ийн redirect (эсвэл successRedirect); алдаа бол failureRedirect?eid_error=<код> |
Алдааны хариу {"error":"…"}: 403 cross_site (Sec-Fetch-Site өөр сайт), 405 method_not_allowed, 415 json_required, 400 invalid_body | invalid_mode | invalid_id | web2app_not_configured (callback URL тохируулаагүй), 429 rate_limited (Retry-After: 60), 502 eid_unavailable, 500 internal, 404 not_found (танихгүй зам). Push-д eID дугаарыг татгалзвал (бүртгэлгүй / ашиглах боломжгүй — eID-ийн JSON 4xx) 502 биш decoy session буцна (доор «Аюулгүй байдал»); 502 зөвхөн eID-тэй холбогдож чадаагүй (5xx, сүлжээ, 401/403) үед. FAILED-ийн error нь session-ий endResult (USER_REFUSED…, TIMEOUT, WRONG_VC …) эсвэл VALIDATION (§8-ын шалгалт унасан, гэрчилгээ цуцлагдсан). Push-ийн TIMEOUT нь FAILED биш EXPIRED (decoy-той ижил төгсгөл). Callback-ийн eid_error: expired, unbound (cookie/session таарахгүй, verifier алга), not_complete, eid_unavailable, VALIDATION, internal эсвэл endResult.
Backend — TypeScript (createEidLogin; нэг instance-ийг модулийн түвшинд үүсгэнэ):
import { EidClient, NATIONAL_ROOT_CA_PEM, createEidLogin } from "@gerege-systems/eid-mongolia-sdk";
const eid = new EidClient({
credentials: { rpUUID: process.env.EID_RP_UUID!, rpName: "Интернэт банк", apiSecret: process.env.EID_RP_SECRET! },
// Root (01); завсрын 02 → 03 → 04 нь SDK-д суусан (CITIZEN_INTERMEDIATES_PEM, 0.19.0+ анхдагч)
trust: { trustAnchorsPem: [NATIONAL_ROOT_CA_PEM] },
});
export const eidLogin = createEidLogin({
client: eid,
// Утасны (Web2App) урсгалд заавал: https, host нь таны callbackHosts-д, зам яг /auth/eid/callback.
callbackUrl: "https://bank.example.mn/auth/eid/callback",
async onSuccess(id) {
// id.etsi (PNOMN-…) — хэрэглэгчийг ҮҮГЭЭР таньна; id.givenName / id.surname (латин), id.person (кирилл, байвал)
// id.sessionId, id.documentNumber, id.certificateDerB64 — аудит, дараагийн алхам (гарын үсэг г.м.)
const user = await users.findOrCreateByEtsi(id.etsi);
const sid = await sessions.create(user.id); // ШИНЭ session id (session fixation)
return {
redirect: "/dashboard",
headers: { "Set-Cookie": `app_session=${sid}; Path=/; HttpOnly; Secure; SameSite=Lax` },
};
},
// failureRedirect: "/login", // ?eid_error=<код> нэмэгдэнэ (анхдагч "/")
// store: redisStore, // олон instance / serverless бол (анхдагч — процессын санах ой)
// rateLimit: (key) => limiter.allow(key), // "start:<ip>", "push:<idCode>"
trustProxy: 1, // handle(Request)-д клиент IP: итгэмжит proxy (nginx/LB) нэг hop бол 1 — доор «Клиент IP»
});
// Next.js App Router — app/auth/eid/[action]/route.ts
import { eidLogin } from "@/lib/eid";
export const dynamic = "force-dynamic";
export const GET = (req: Request) => eidLogin.handle(req);
export const POST = (req: Request) => eidLogin.handle(req);
// Express
app.use("/auth/eid", express.json(), async (req, res) => {
const r = await eidLogin.handleRequest({
method: req.method, url: req.originalUrl, headers: req.headers, body: req.body, ip: req.ip,
});
res.status(r.status).set(r.headers).send(r.body);
});
handle(req: Request) — Web-standard (Next.js, Hono, Bun, Deno); handleRequest({method, url, headers, body?, ip?}) → {status, headers, body} — бусад framework.
Клиент IP (start:<ip> хязгаар). Web-standard Request-д IP байдаггүй тул handle(req)-д аль нэгийг өгнө:
trustProxy: n— итгэмжит reverse proxy-ийн ард:X-Forwarded-For-ийн баруунаас n дахь утга (n = таны итгэмжит proxy-ийн тоо; nginx нэг бол 1), XFF байхгүй болX-Real-IP. XFF-д hop дутвал IP-гүй гэж үзнэ (хуурамч header-ээр тойрохоос).true= XFF-ийн хамгийн зүүн утга — зөвхөн proxy нь клиентийн XFF-ийг дарж бичдэг үед. Proxy-гүй үед бүү асаа: header-ийг клиент өөрөө илгээж хязгаарыг тойрно.getClientIp: (req) => …— платформын IP (Bunserver.requestIP(req)?.address, Denoinfo.remoteAddr.hostname, Vercelreq.headers.get("x-real-ip"));trustProxy-оос давуу.handleRequest-дip(Expressreq.ip,app.set("trust proxy", …)-тэй) шууд хэрэглэгдэнэ;ipөгөөгүй болtrustProxy-ийн дүрмээр header-ээс.- Go:
Config.ClientIP— proxy-ийн ардeidlogin.ForwardedClientIP(n)(ижил дүрэм), шууд холбогддог болeidlogin.RemoteAddrIP, эсвэл өөрийн функц.
Аль нь ч үгүй бол IP-ээр хязгаарлахгүй (TS эхний хүсэлтэд onError-оор, Go New-д Logger-оор нэг удаа анхааруулна). Анхдагч rateLimit (TS 0.19.0+ / Go v0.6.0+): start:<ip> — нэг IP-ээс 60 с-д 20, push: — нэг дугаарт 60 с-д 3; өөрийн давхаргад хязгаарладаг бол { start: false } (Go RateLimits["start"] = RateRule{}). Санах ойн хязгаарлагч ба store ≤ 10 000 түлхүүр — дүүрвэл шинэ түлхүүр 429 / start 503 busy (байгаа тоолуур, эхэлсэн нэвтрэлт арилахгүй тул олон түлхүүрээр дүүргэж хязгаарыг тойрох боломжгүй). Олон instance бол Redis.
| Тохиргоо (TS / Go) | Анхдагч | Утга |
|---|---|---|
client / Client | заавал | EidClient (eid.NewClient) — RP UUID, нэр, API secret, trust anchor |
onSuccess / OnSuccess | заавал | Иргэн validateAuth-аар баталгаажсаны дараа нэг session-д нэг л удаа. Энд өөрийн session-ээ үүсгэнэ. |
callbackUrl / CallbackURL | — | Өгөөгүй бол утасны урсгал 400 web2app_not_configured, QR/push ажиллана. Зам /auth/eid/callback биш, https биш бол үүсгэхэд алдаа. |
successRedirect · failureRedirect | / · / | Callback-ийн дараа (onSuccess redirect өгөөгүй бол) · алдаа бол ?eid_error=<код>-тэй |
displayText | «<rpName>-д нэвтрэх» | Утсанд харагдах текст, 60 тэмдэгтээр таслагдана (displayTextAndPIN) |
certificateLevel | — | RP-API-ийн certificateLevel (§4.2) |
ttlMs · pollMs / TTL · Poll | 120 с · 1 с | Нэвтрэлтийн хугацаа · status нэг дуудлагад eID-ийг хүлээх. Хугацаа CA-ийн session-оос урт байхгүй (TS 0.23.0+ / Go v0.10.0+): QR / Web2App-д min(TTL, start хариуны expiresAt − одоо), push-д (бодит ба decoy ижил) min(TTL, discovery sessionTtl.auth) — decoy-д CA хариу байхгүй тул хугацааны зөрүүгээр бүртгэлтэй эсэх ялгагдахгүй |
store / Store | санах ой | EidLoginStore (get / set / delete; Go eidlogin.Store: Get / Set / Delete). Анхдагч нэг процессын санах ой (TS memoryLoginStore(), Go eidlogin.NewMemoryStore()) — олон instance, serverless (Vercel г.м.)-д ажиллахгүй, Redis г.м. хэрэглэнэ. delete атомар байх ёстой (Redis DEL) — onSuccess нэг л удаа дуудагдах баталгаа. |
cookie: {name, path, secure, domain} / CookieName, CookiePath, CookieDomain, InsecureCookie | eid_login, /, Secure | Түр cookie (HttpOnly, SameSite=Lax). Secure-ийг зөвхөн http dev-д унтраана. |
rateLimit / RateLimits, RateLimit | start: нэг IP-ээс 60 с-д 20 (0.19.0+ / v0.6.0+); push: нэг дугаарт 60 с-д 3 | Түлхүүр start:<ip> (IP мэдэгдэж байвал), push:<idCode>; false → 429. Объект {start?, push?} (Go RateLimits map[string]RateRule) — анхдагчтай нэгтгэгдэнэ, утга {limit, windowMs} / функц / false. Функц (Go RateLimit) — анхдагч хязгаар дээр нэмэгдэнэ |
trustProxy · getClientIp / ClientIP | — | Клиент IP (start:<ip>) — дээрх «Клиент IP». Go: eidlogin.ForwardedClientIP(n) · eidlogin.RemoteAddrIP |
onError / Logger | console.error / slog.Default() | Дотоод алдаа (eID API, store) |
decoy / DisableDecoy | асаалттай | eID push-ийг дугаарын шалтгаанаар татгалзвал бодит хариутай ижил decoy session ({mode, vc, expiresIn} + cookie, status PENDING → TTL-ийн дараа EXPIRED). Унтраавал (decoy: false / DisableDecoy: true) start «энэ регистр eID-д бүртгэлтэй эсэх»-ийг хэлдэг oracle болно — зөвхөн тест орчинд |
pushFloorMs / PushFloor, DisablePushFloor | 1000 мс / 1 с | Push start-ийн хариу (бодит, decoy, 502) хамгийн багадаа энэ хугацаанд — decoy-г хугацаагаар ялгахаас. TS 0 / Go DisablePushFloor — унтраана. 429, 400-д хамаарахгүй |
revocation / Revocation, DisableRevocation | AIA OCSP | onSuccess-ийн өмнө гэрчилгээний цуцлалт: анхдагч TS ocspRevocationChecker(), Go eid.NewOCSPChecker(eid.OCSPOptions{}). Цуцлагдсан / олгогч мэдэхгүй → FAILED VALIDATION. revocation: false / DisableRevocation: true — шалгахгүй (цуцалсан гэрчилгээгээр нэвтэрнэ) |
revocationMode / RevocationMode | soft-fail | OCSP хариугүй (timeout, 5xx, AIA алга): soft-fail — зөвшөөрч анхааруулна, revocation = "unchecked"; hard-fail (Go eid.RevocationHardFail) — FAILED VALIDATION |
onEvent / OnEvent | — | Аудит: push_sent (meta decoy), push_limited, refused, expired, unverified (reason: validation | revoked | revocation_unavailable), flow_refused, success (flow, revocation), failed (endResult). Хариу өгөхөөс өмнө дуудагдана — удаан ажлыг өөрөө async болго. meta-д токен, регистрийн дугаар орохгүй |
auditKey / AuditKey | санамсаргүй | Push-ийн pushKey = HMAC-SHA256(auditKey, дугаар)-ын түлхүүр (≥ 32 байт). Instance / restart хооронд харьцуулах бол тогтмол нууц өг |
onSuccess-д ирэх иргэн. sessionId (Go SessionID; RP-API session-ий id — аудит, эсвэл нэвтрэлтийг дараагийн алхамтай холбоход), etsi (PNOMN-…), civilId, givenName, surname (латин, гэрчилгээнээс), country, documentNumber, certificateLevel, гэрчилгээ (TS certificateDerB64, Go *x509.Certificate), flowType (QR | Notification | Web2App), person (кирилл нэр г.м. — гэрчилгээтэй таарсан үед л, эс бөгөөс null), revocation (good | unchecked | skipped — аудитад хадгална; Go Revocation). Хэрэглэгчийг etsi-ээр таньна, нэрээр биш. onSuccess-ээс өмнө tx store-оос атомар устгагдана — onSuccess дотор store-оос бүү унш, хэрэгтэй бүхэн энэ объектод. Буцаах {redirect?, data?, headers?}: data нь QR/push-ийн status хариунд хөтөчид очно — нууц бүү хий.
Backend — Go (eidlogin.Handler нь http.Handler):
import (
eid "github.com/gerege-systems/eid-mongolia-sdk-go"
"github.com/gerege-systems/eid-mongolia-sdk-go/eidlogin"
)
c, err := eid.NewClient(eid.Config{
Credentials: eid.Credentials{RPUUID: uuid, RPName: "Интернэт банк", APISecret: secret},
Trust: eid.TrustConfig{TrustAnchorsPEM: []string{eid.NationalRootCAPEM}}, // завсрын 02–04: eid.CitizenIntermediatesPEM (v0.6.0+ анхдагч)
})
if err != nil {
log.Fatal(err)
}
login, err := eidlogin.New(eidlogin.Config{
Client: c,
CallbackURL: "https://bank.example.mn/auth/eid/callback",
FailureRedirect: "/login",
OnSuccess: func(w http.ResponseWriter, r *http.Request, id *eidlogin.Identity) (*eidlogin.Success, error) {
u, err := users.FindOrCreateByETSI(r.Context(), id.ETSI)
if err != nil {
return nil, err
}
sid, err := sessions.Create(r.Context(), u.ID) // ШИНЭ session id
if err != nil {
return nil, err
}
http.SetCookie(w, &http.Cookie{Name: "app_session", Value: sid, Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode})
return &eidlogin.Success{Redirect: "/dashboard"}, nil
},
ClientIP: eidlogin.ForwardedClientIP(1), // итгэмжит proxy (nginx) нэг hop; proxy-гүй бол eidlogin.RemoteAddrIP
// Store: redisStore, RateLimit: …,
})
if err != nil {
log.Fatal(err)
}
mux.Handle("/auth/eid/", login)
Go-д OnSuccess алдаа буцаавал status → 500 internal, callback → failureRedirect?eid_error=internal. login.Start / Status / Callback аргуудыг router-тоо тусад нь холбож болно.
Redis store (олон instance / serverless; SDK нэмэлт хамааралгүй — жишээ нь ioredis):
import Redis from "ioredis";
import type { EidLoginStore, EidLoginTx } from "@gerege-systems/eid-mongolia-sdk";
const redis = new Redis(process.env.REDIS_URL!);
const key = (sid: string) => `eid:login:${sid}`;
export const redisStore: EidLoginStore = {
async get(sid) {
const v = await redis.get(key(sid));
return v ? (JSON.parse(v) as EidLoginTx) : undefined;
},
async set(sid, tx, ttlMs) {
await redis.set(key(sid), JSON.stringify(tx), "PX", ttlMs);
},
async delete(sid) {
return (await redis.del(key(sid))) === 1; // DEL атомар — зэрэг хоёр status-ийн зөвхөн нэг нь onSuccess дуудна
},
};
Tx-д sessionSecret (QR угсрах материал) хадгалагддаг — Redis-ийг сүлжээнээс хаалттай, TLS/AUTH-тай ашигла. Go-д eidlogin.Store-ийг ижил байдлаар (SET … PX, DEL) хэрэгжүүлнэ.
Хөтөч — <eid-login-button> (Web Component; React, Vue, энгийн HTML аль нь ч; гадны хамааралгүй, QR encoder дотроо). Модулийг импортлоход элемент автоматаар бүртгэгдэнэ:
// 1) Bundler (Vite, Next.js, webpack) — client entry-д:
import "@gerege-systems/eid-mongolia-sdk/button";
<!-- 2) Bundler-гүй: node_modules/@gerege-systems/eid-mongolia-sdk/dist/button.js-ийг өөрийн домэйнд байршуулна
(CSP script-src 'self'-д нийцнэ), эсвэл хувилбар бэхэлсэн CDN -->
<script type="module" src="./static/eid-button.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@gerege-systems/eid-mongolia-sdk@0.38.1/dist/button.js"></script>
<eid-login-button start-url="/auth/eid/start" status-url="/auth/eid/status" lang="mn"></eid-login-button>
const btn = document.querySelector("eid-login-button");
btn.addEventListener("eid-login-success", (e) => {
// e.detail = { state: "OK", redirect?, data? }. preventDefault() → товч өөрөө шилжихгүй.
});
btn.addEventListener("eid-login-error", (e) => {
// e.detail = { error, status, code, retryAfter } (0.20.0+; bubbles + composed — shadow DOM-оос гарна)
// error: "USER_REFUSED…" | "TIMEOUT" | "VALIDATION" | "EXPIRED" | start-ийн код ("eid_unavailable" | "rate_limited" | "busy" | "invalid_id" | "NETWORK" | "HTTP_<статус>")
const { status, code, retryAfter } = e.detail;
if (status === 429 || status === 503) console.warn(`eID завгүй (${code}), ${retryAfter ?? 30} с дараа`);
});
// Эсвэл программаар (framework-ийн ref):
import { mountEidLoginButton } from "@gerege-systems/eid-mongolia-sdk/button";
mountEidLoginButton(el, { startUrl: "/auth/eid/start", statusUrl: "/auth/eid/status", lang: "en",
theme: "auto", successUrl: "/dashboard", label: "Нэвтрэх", icon: "eid", variant: "default",
onSuccess: (s) => {}, onError: (code) => {} });
| Атрибут / үйл явдал | Утга |
|---|---|
start-url, status-url | Таны backend-ийн start, status зам (ижил origin — credentials: "same-origin") |
lang | mn (анхдагч) | en | ru | zh | fr | es | ar (0.36.0+; eID аппын хэлнүүд; zh-CN г.м. → үндсэн хэл, бусад → mn; ar → цонх dir="rtl") — товч «e-ID-ээр нэвтрэх» / «Log in with e-ID» (0.38.1+), цонхны бичвэр ба /dl хуудасны хэл |
mode | auto (анхдагч: утас → Web2App, бусад → QR цонх) | qr | app |
theme | auto (анхдагч) | light | dark. auto: хуудасны html[data-theme="dark|light"] → html.dark / html.light → OS (prefers-color-scheme); хуудас горимоо солиход шууд дагана. light/dark хуудсыг давна. Фонт хуудаснаас өвлөгдөнө (товч, цонх, input). |
success-url | Backend redirect өгөөгүй үед амжилтын дараа шилжих URL |
label | Товчны текст (0.15.4+); анхдагч lang-ийн «e-ID-ээр нэвтрэх» / «Log in with e-ID» (0.38.1+). label="" = variant="icon" |
icon | eid (анхдагч — eID тэмдэг, дөрвөлжин inline SVG; 0.38.0+ light/dark хувилбар нь theme-ийг дагана) | none (0.15.4+). icon="none" + текстгүй бол хоосон товч үүсгэхгүй — анхдагч текст гарна |
variant | default (лого + текст) | icon — зөвхөн лого, 44×44 (0.15.4+). Нэр нь хостын aria-label → label → анхдагч текст |
eid-login-success | detail = status-ийн OK хариу; cancelable — preventDefault() хийвэл шилжихгүй |
eid-login-error | detail.error — FAILED-ийн error (USER_REFUSED…, TIMEOUT, VALIDATION …), EXPIRED, эсвэл start-ийн алдааны код (detail.code-той ижил). 0.20.0+: bubbles, composed; detail.status (start-ийн HTTP статус, сүлжээний алдаанд 0, poll-ийн алдаанд null), detail.code (хариуны error/code, эсвэл HTTP_<статус> / NETWORK), detail.retryAfter (Retry-After секунд эсвэл null). 429/503 (busy) үед товч «Систем завгүй — N сек дараа дахин оролдоно уу» / “Service busy — try again in N s” гэж тоолж, дуустал «Дахин оролдох» идэвхгүй (анхдагч 30 с) |
start(), close() | Товчны үйлдлийг программаар эхлүүлэх, цонхыг хааж poll зогсоох |
mountEidLoginButton(el, opts) | Ижил тохиргоо camelCase-ээр: startUrl, statusUrl, lang, mode, theme, successUrl, label, icon, variant, onSuccess, onError |
Desktop ба утас
- Desktop хөтөч: товч цонх нээж
POST start {"mode":"qr"}хийнэ — QR (секунд тутамstatus-аас шинэ холбоос, §4.3), VC, үлдсэн хугацаа. Доор нь «Регистрийн дугаараар нэвтрэх» товч — дарахад ижил цонхонд push талбар дэлгэгдэнэ (QR, код хэвээр; 0.15.3+):УБ12345678хэлбэр (12 оронтой иргэний дугаар ч болно), латин үсгээр (UB…) бичвэл кириллээр бичихийг сануулна. Сүлжээ 3 удаа тасарвал анхааруулга, QR-ийг бүдгэрүүлнэ. - Утасны хөтөч (Android, iPhone, iPad; touch-тэй Macintosh UA-г ч утас гэж үзнэ): товч
start?mode=web2appруу шилжинэ → backend 302 → eID апп нээгдэнэ → иргэн PIN1 → апп/auth/eid/callback-ийг нээнэ → 303 → таны хуудас. Backend буцах URL-д хөтчийнretScheme(iOS) /retPkg(Android) hint-ийг User-Agent-аас нэмдэг тул callback нэвтрэлтийг эхлүүлсэн хөтөчид буцна. - Web2App session-ийг
statusдуусгахгүй (WAIT_CALLBACK) — нэвтрэлт зөвхөн callback-аар; QR цонх энэ төлөвт «Утсан дээрх хөтчөөр үргэлжилнэ» гэж харуулна.
eID аппын доод хувилбар. eID сервер доод хувилбарыг /mobile/app-config-оор албадан шинэчлүүлдэг — одоогоор iOS ≥ 2.2.8, Android versionCode ≥ 91 (curl https://ca.eidmongolia.mn/mobile/app-config). Нэвтэрч чадах бүх апп flowType бэхлэлт (device link v3) ба Web2App-ийн userChallengeVerifier-ийг дэмжинэ — RP талд нэмэлт шалгалт хэрэггүй.
Аюулгүй байдал (SDK хийдэг — өөрөө давхар хийх шаардлагагүй, гэхдээ эвдэхгүй):
sessionToken,sessionSecret,rpChallengeсерверт (store) үлдэнэ. Хөтөчид зөвхөнeid_login=<sessionId>.<binding>HttpOnly, Secure, SameSite=Lax cookie; store-д binding-ийн SHA-256.statusбаcallbackзөвхөн нэвтрэлтийг эхлүүлсэн хөтөчид хариулна — өөр хөтчид нээгдсэн callback URL (verifier-тэй ч)unbound/expired(login CSRF, §4.4-ын callout).startPOST нь JSON заавал (cross-origin preflight шаардана),Sec-Fetch-Site: cross-site→ 403. Push анхдагчаар нэг дугаарт минутад 3 — иргэний утсыг мэдэгдлээр дарахаас (MFA fatigue). Иргэн бүртгэлтэй эсэхийг задлахгүй (0.15.2+ / v0.3.2+): eID дугаарыг татгалзвал бодит хариутай ижил decoy session (утас руу юу ч очихгүй,statusPENDING →EXPIRED), pushstart-ийн бүх хариу ≥ 1 с; бодит push-ийнTIMEOUTмөнEXPIRED. Хязгаар decoy-д ч үйлчилнэ. Decoy эсэх нь зөвхөн таныonEvent(push_sent,decoy: true)-д.OKзөвхөнvalidateAuth(§8: гинж, ACSP_V2) амжилттай үед, урсгалыг тулгаж: QR →QR, push →Notification, утас →Web2App+ callback-ийнuserChallengeVerifier(§8.3). Дараа нь гэрчилгээний цуцлалт AIA OCSP-оор (0.15.2+ / v0.3.2+, анхдагч асаалттай,soft-fail): цуцлагдсан →FAILEDVALIDATION, OCSP хариугүй бол зөвшөөрчrevocation = "unchecked"(revocationMode: "hard-fail"— татгалзана).onSuccess-д шинэ session id үүсгэнэ (session fixation);data-д нууц бүү хий. Олон instance, serverless (Vercel г.м.) дээр анхдагч санах ойн store ажиллахгүй — Redis зэрэг хуваалцсан store.- Хөтөч дээрх
eid-login-successнь UX-ийн дохио л — нэвтрэлтийг таны backendonSuccess-д тавьсан cookie тодорхойлно.
Товчны харагдах байдал. SDK eID Mongolia тэмдэг (0.38.0+; товчинд 20px, зөвхөн-лого үед 24px, цонхны толгойд 24px — өнгө нь брэндээр тогтмол), «eID-ээр нэвтрэх» бичвэр, light/dark өнгийг нийлүүлнэ (theme). CSS хувьсагч --eid-<нэр> (хуудасны аль ч түвшинд, хоёр горимд) ба --eid-<нэр>-dark (зөвхөн харанхуйд давуу): accent, accent-fg, accent-subtle, accent-subtle-fg, ring, bg, bg-subtle, bg-muted, fg, fg-muted, fg-subtle, border, border-strong, warning-bg, warning-border, warning-fg, danger-fg (17 өнгө) + --eid-radius, --eid-radius-lg. --eid-logo / --eid-logo-dark нь 0.38.0-аас deprecated — алдаагүй үл хэрэгжинэ. Shadow part: ::part(button), ::part(icon), ::part(label) (0.15.4+), ::part(dialog), ::part(backdrop). Анхдагч утгын хүснэгт — SDK README §1a. Цонх нь focus trap, Esc, aria-live төлөвтэй.
eid-login-button {
--eid-accent: var(--brand); /* хоёр горимд */
--eid-accent-dark: var(--brand-400); /* зөвхөн харанхуйд */
--eid-radius: 6px;
}
eid-login-button::part(button) { width: 100%; }
4.2 Push — регистрээр
curl -X POST https://rp.eidmongolia.mn/authentication/notification/etsi/PNOMN-12345678 \
-H "Authorization: Bearer $RP_SECRET" -H "Content-Type: application/json" \
-d '{
"relyingPartyUUID": "'"$RP_UUID"'",
"relyingPartyName": "Интернэт банк",
"certificateLevel": "QUALIFIED",
"signatureProtocol": "ACSP_V2",
"signatureProtocolParameters": {
"rpChallenge": "'"$(openssl rand -base64 64 | tr -d '\n')"'",
"signatureAlgorithm": "rsassa-pss",
"signatureAlgorithmParameters": { "hashAlgorithm": "SHA-512" }
},
"interactions": "'"$(printf '[{"type":"displayTextAndPIN","displayText60":"Интернэт банкинд нэвтрэх"}]' | base64 | tr -d '\n')"'"
}'
# 200 {"sessionID":"3f0c…","vc":{"type":"alphaNumeric5","value":"48213"},"expiresAt":"2026-10-14T14:10:00Z"}
Session эхлэх бүх хариунд expiresAt (RFC 3339 UTC) — session-ий бодит дуусах хугацаа (админ тохируулдаг TTL; одоогийн утга discovery sessionTtl). Хугацааг өөрөө тооцоолохгүй, энийг уншина.
Замын {semanticsIdentifier}-д ETSI (PNOMN-<иргэний дугаар>), регистрийн дугаар эсвэл иргэний дугаарын аль нь ч болно — сервер танина. Иргэн бүртгэлгүй бол session үүссэн ч хэн ч батлахгүй тул хугацаа (expiresAt, discovery sessionTtl.auth) дуусахад TIMEOUT болно. Төхөөрөмжийн дугаараар: …/notification/document/{documentNumber}.
| Талбар | Шаардлага | Утга |
|---|---|---|
certificateLevel | сонголт | QUALIFIED | ADVANCED | QSCD — сервер одоогоор энэ утгаар шүүдэггүй (иргэний бүх гэрчилгээ QUALIFIED). Хариуны cert.certificateLevel-ийг өөрийн талд заавал шалгана. |
signatureProtocol | сонголт | Нэвтрэлтэд зөвхөн ACSP_V2 (анхдагч); гарын үсэгт анхдагч RAW_DIGEST_SIGNATURE. Тодорхой илгээхийг зөвлөнө. |
signatureProtocolParameters.rpChallenge | заавал | Стандарт base64, ≥ 16 байт санамсаргүй (32–64 байт зөвлөнө). Хүсэлт бүрд шинэ; хариуг шалгахад хэрэгтэй тул хадгална. |
…signatureAlgorithm | сонголт | rsassa-pss (анхдагч) | sha256WithRSAEncryption | sha384… | sha512… (hash-тайгаа таарсан байх) |
…signatureAlgorithmParameters.hashAlgorithm | сонголт | SHA-256 (анхдагч) | SHA-384 | SHA-512 |
interactions | сонголт | base64(JSON массив) — илгээхийг зөвлөнө; хоосон бол апп анхдагч бичвэр үзүүлнэ. Төрөл: displayTextAndPIN (displayText60 ≤ 60 тэмдэгт), confirmationMessage (displayText200 ≤ 200 тэмдэгт), confirmationMessageAndVerificationCodeChoice. Сервер уртыг шалгадаггүй — хязгаарыг өөрөө барина. Апп эхний дэмжигдэх төрлийг хэрэглэнэ. Илгээсэн base64 мөрөө яг хэвээр хадгална — ACSP_V2 шалгалтад хэрэгтэй. |
initialCallbackUrl | сонголт | App2App: https://<callbackHosts-ийн host>/…?state=…. Host allowlist-д байхгүй бол сервер чимээгүй хасна. Сервер path-ыг /auth/eid/callback болгож query-г хэвээр үлдээнэ — буцах хуудсаа энэ замд байршуулна. Зөвхөн https:// — гар утасны апп руу буцаахдаа Universal Link / App Link ашиглана; custom scheme (myapp://…) хүлээн авахгүй. |
onBehalfOf | сонголт | NTRMN-<байгууллагын регистр> — иргэн байгууллагын төлөөлөгчөөр нэвтрэх/гарын үсэг зурах; эрхгүй бол 403 (code: REPRESENTATION_*, ORG_NOT_ACTIVE, SIGNER_UNIDENTIFIED), байгууллага бүртгэлгүй бол 404 ORG_NOT_FOUND. Зөвхөн иргэнийг нэрлэсэн хүсэлтэд (…/etsi/…, …/document/…); device-link/anonymous-д үргэлж 403. |
SDK — TS eid.auth.notificationByEtsi(id, interactions) / notificationByDocument(documentNumber, interactions); Go c.Auth.NotificationByEtsi / NotificationByDocument. .NET-д RP-API клиент байхгүй (зөвхөн Pades, Qr).
4.3 QR / Web2App / App2App — device link
POST /authentication/device-link/anonymous
# body: дээрхтэй ижил (rpChallenge, interactions — base64 МӨР, …), нэмээд товч (Web2App/App2App) харуулах бол:
# "initialCallbackUrl": "https://khanbank.mn/auth/eid/callback?state=<csrf>"
# 200 {"sessionID":"…","sessionToken":"…","sessionSecret":"…",
# "deviceLinkBase":"https://ca.eidmongolia.mn/dl","vc":"48213","expiresAt":"2026-10-14T14:05:00Z"}
| Талбар | Утга |
|---|---|
sessionID | UUID — GET /session/{sessionID} poll-д |
sessionToken | Холбоосонд яг хэвээр орно (URL-safe, 32 тэмдэгт [A-Za-z0-9_-]) |
sessionSecret | Стандарт Base64 (padding-тэй, 32 байт → 44 тэмдэгт) — authCode-ийн HMAC түлхүүр (BASE64-DECODE(sessionSecret)). Зөвхөн таны backend-д — хөтөч, апп руу хэзээ ч бүү гарга |
deviceLinkBase | https://ca.eidmongolia.mn/dl — холбоосын угтвар |
vc | 5 оронтой баталгаажуулах код — дэлгэцэндээ харуулж иргэн утсан дээрхтэй нүдээр тулгана (ПИН биш) |
expiresAt | Session-ий бодит дуусах хугацаа (RFC 3339 UTC) — sessionTtl.auth-аас; хугацааг өөрөө тооцоолохгүй |
receivedAt — таны backend энэ хариуг хүлээн авсан мөч (өөрийн цагаар). elapsedSeconds-ийг үүнээс тооцно (expiresAt-аас биш); таны цаг серверийнхтэй синк байх шаардлагагүй.
Холбоос (Smart-ID RP-API v3 device link). Бүх утга том/жижиг үсэг ялгана, URL encoding байхгүй, # байхгүй, параметрийн дараалал яг ийм:
QR: {deviceLinkBase}?deviceLinkType=QR&elapsedSeconds={E}&sessionToken={T}&sessionType={S}&version=1.0&lang={L}&authCode={A}
Web2App: {deviceLinkBase}?deviceLinkType=Web2App&sessionToken={T}&sessionType={S}&version=1.0&lang={L}&authCode={A}
App2App: {deviceLinkBase}?deviceLinkType=App2App&sessionToken={T}&sessionType={S}&version=1.0&lang={L}&authCode={A}
| Параметр | Утга |
|---|---|
deviceLinkType | QR — өөр төхөөрөмжийн (компьютер) дэлгэц дээрх QR · Web2App — утасны хөтөч дэх товч · App2App — таны апп дахь товч |
elapsedSeconds | Зөвхөн QR: floor(now − receivedAt) секунд, тэргүүлэх 0-гүй бүхэл тоо (0, 7, 22) |
sessionToken | Хариуны sessionToken |
sessionType | auth (нэвтрэлт) · sign (гарын үсэг) · cert (гэрчилгээ сонгох) |
version | 1.0 |
lang | ISO 639-2 гурван жижиг үсэг (mon, eng) — зөвхөн /dl нөөц хуудасны хэл |
authCode | Доор |
unprotectedDeviceLink = холбоос authCode-гүйгээр (…&lang={L} хүртэл)
payload = "smart-id" | signatureProtocol | rpChallenge эсвэл digest
| B64(relyingPartyName) | B64(brokeredRpName) | interactions
| initialCallbackUrl | unprotectedDeviceLink
authCode = BASE64URL-NOPAD( HMAC-SHA256( BASE64-DECODE(sessionSecret), UTF-8(payload) ) )
| Талбар | auth | sign | cert |
|---|---|---|---|
signatureProtocol | ACSP_V2 | RAW_DIGEST_SIGNATURE | хоосон |
| challenge | илгээсэн rpChallenge (base64 текстээрээ) | илгээсэн digest (base64 текст) | хоосон |
relyingPartyName | хүсэлтэд илгээсэн яг тэр мөр (UTF-8, Кирилл хэвээр), стандарт base64-аар | ||
brokeredRpName | үргэлж хоосон (B64("") = хоосон мөр) | ||
interactions | илгээсэн base64 мөр яг хэвээр (hash хийхгүй) | хоосон | |
initialCallbackUrl | QR ба push (Notification): хоосон · Web2App/App2App: хүсэлтэд илгээсэн түүхий URL | ||
Хоосон талбарт ч | тусгаарлагч үлдэнэ (…|REVNTw==||…). Жишээ вектор (sessionSecret = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=, relyingPartyName = «Дэмо банк», QR, elapsedSeconds=0) болон SK ID Solutions-ийн баримтын векторууд: SDK репогийн test/fixtures/devicelink_golden.json — өөрийн хэрэгжүүлэлтээ байт-ижил эсэхийг тулга.
- Холбоосыг зөвхөн backend угсарна.
sessionSecretзөвхөн backend-д үлдэнэ; хөтөч бэлэн холбоосыг авна.sessionTokenнь тэр холбоосын параметр тул хөтөч/аппад харагдана — холбоосыг бусдад дамжуулахгүй, лог болон analytics-д бүтнээр нь бичихгүй. - QR-ийг секунд тутам шинэчилнэ. Хөтөч таны backend-ээс 1 секунд тутам шинэ холбоос (шинэ
elapsedSeconds, шинэauthCode) авч QR-ийг дахин зурна. Сервер ~20 секундээс хуучин QR-ийг татгалздаг тул хуучирсан зураг, холбоос дахин ашиглах боломжийг багасгана. Хугацааны цонх дотор хурдан дамжуулсан холбоосыг дангаараа бүрэн хаахгүй — VC, урсгалын төрөл болон ижил төхөөрөмжийн verifier-ийг мөн шалгана. Хажууд нь VC-г харуулна. - Web2App / App2App холбоосыг нэг удаа угсарна (
elapsedSeconds-гүй, шинэчлэхгүй). ЗөвхөнinitialCallbackUrl-тай (callbackHosts-д бүртгэлтэй) session-д хүчинтэй — дүрэм §4.4. - QR ба товч хоёуланг нэг хуудсанд харуулах бол нэг session (
initialCallbackUrl-тай) эхлүүлж хоёр холбоос угсарна — хоёр session бүү эхлүүл. interactions-ийг base64 мөрөөр илгээнэ (массиваар илгээсэн session-д v3 холбоос угсрах боломжгүй).- Иргэн тодорхой бол
…/device-link/etsi/{id},…/device-link/document/{documentNumber}— холбоосыг зөвхөн тэр иргэн батална. - Урсгалын төрөл хамгаалагдсан.
deviceLinkTypeнь authCode-д ордог тул хуурамчлах боломжгүй: сервер нээгдсэн холбоосын төрлийг session-д бэхэлж, хариуныsignature.flowType= тэр төрөл. Та харуулсан төрлөө тулгана — §8.3.
Иргэний аппад гарах алдаа (холбоос нээхэд; таны backend-д ирэхгүй — session RUNNING хэвээр үлдэж, эцэст нь TIMEOUT):
| Код | Шалтгаан | Иргэнд |
|---|---|---|
DEVICE_LINK_INVALID | Хэлбэр, authCode, token, sessionType буруу; өөр домэйн; ирээдүйн elapsedSeconds | «Холбоос хүчингүй» |
DEVICE_LINK_EXPIRED | QR хуучирсан (authCode зөв) — ихэнхдээ QR секунд тутам шинэчлэгдээгүй | «Дэлгэц дээрх шинэ QR-ийг уншуулна уу» |
DEVICE_LINK_NO_CALLBACK | Web2App/App2App холбоос боловч session-д зөвшөөрөгдсөн initialCallbackUrl алга | Холбоос хүчингүй |
DEVICE_LINK_TYPE_CONFLICT | Session өөр төрлөөр эсвэл өөр төхөөрөмжөөр аль хэдийн нээгдсэн | Холбоос хүчингүй |
SESSION_NOT_ACTIVE | Session дууссан эсвэл хугацаа хэтэрсэн | Дахин эхлүүлэх |
FLOW_TYPE_MISMATCH | Гарын үсгийн flowType ≠ нээгдсэн холбоосын төрөл | Алдаа |
DEVICE_LINK_REQUIRED | Device-link session v3 холбоосоор (authCode-той) нээгдээгүй — текст QR, зөвхөн sessionId г.м. | Алдаа |
Device-link session-ийг зөвхөн дээрх v3 холбоосоор (authCode-той) нээнэ. Текст QR, зөвхөн sessionId-аар эсвэл v3 холбоосонд нэмэлт параметр (sessionId г.м.) залгаж нээвэл апп session-ийг харах, батлах, татгалзах боломжгүй — сервер DEVICE_LINK_REQUIRED / DEVICE_LINK_INVALID буцааж, иргэний апп алдаа харуулна; таны талд session RUNNING хэвээр үлдээд хугацаа дуусахад TIMEOUT болно. Push (notification) урсгал холбоос ашигладаггүй; түүний гарын үсгийн payload-ийн initialCallbackUrl хоосон (§8.3).
SDK — TS eid.auth.deviceLinkAnonymous(interactions) / deviceLinkByEtsi(id, interactions), холбоос угсрах buildDeviceLink({…}), QR-ийг секунд тутам шинэчлэх qrDeviceLinkTicker; Go c.Auth.DeviceLinkAnonymous / DeviceLinkByEtsi, eid.BuildDeviceLink, eid.TickQRDeviceLink.
4.4 Callback — ижил төхөөрөмжөөс буцах
Иргэн таны сайт эсвэл аппыг eID Mongolia апп суусан тэр утсаараа ашиглаж байгаа үед (§6) батласны дараа апп иргэнийг initialCallbackUrl руу буцаана. Энэ бол зөвхөн «иргэнийг буцаах» навигаци — үр дүн биш. Үр дүнг үргэлж өөрийн backend-ээс GET /session/{sessionID}-аар авна.
Хаана өгнө. initialCallbackUrl талбарыг /authentication/device-link/…, /signature/device-link/… болон …/notification/… хүсэлтүүд хүлээн авна; /signature/certificate…, certificate-choice хүлээн авахгүй. Device-link session-ийн төрөл = нээгдсэн v3 холбоос (§4.3) — push (notification) хүсэлтэд ч callback өгвөл иргэн утсан дээрээ батлаад тэр хаяг руу шилжинэ. Тиймээс callback-ийг зөвхөн иргэн тэр утсаараа орж байгаа үед өгнө; компьютер дээрх QR урсгалд өгөхгүй.
| Таны илгээсэн | callbackHosts-д | Апп руу очих хаяг |
|---|---|---|
https://example.mn/login/done?state=a1b2#x | example.mn — host яг таарна (том жижиг үсэг, төгсгөлийн цэг хамаагүй). *.example.mn мэт wildcard байхгүй — www.example.mn-ийг тусад нь бүртгүүлнэ. | https://example.mn/auth/eid/callback?state=a1b2 — path үргэлж /auth/eid/callback болно, query хэвээр, fragment хасагдана. Порт URL-д байвал хадгалагдана. |
Custom scheme (myapp://…), http://, бүртгэлгүй host, user:pass@-тэй, host-гүй https | — | Хаягдана. Хүсэлт алдаа өгөхгүй: session энгийн QR (cross-device) болж, апп батласны дараа буцахгүй. Ийм session-ий Web2App/App2App v3 холбоосыг апп DEVICE_LINK_NO_CALLBACK-оор татгалзана. |
Сервер callback-т өөрөө юу ч нэмэхгүй. ACSP_V2 гарын үсгийн payload-д Web2App / App2App холбоосоор нээгдсэн session-д таны илгээсэн түүхий initialCallbackUrl орно (нормчилсон хувилбар биш); QR ба push (notification) session-д callback өгсөн ч payload-д хоосон — §8.3.
Апп юу хийдэг вэ (iOS ба Android, v3 дэмждэг 2.2.3+):
| Иргэний үйлдэл | Апп |
|---|---|
Баталсан (OK) | Callback-ийг нээнэ. Query-д sessionId=<session> нэмнэ (ижил нэртэй параметр байсан бол солино); нэвтрэлтэд userChallengeVerifier=<43 тэмдэгт> нэмнэ; retScheme, retPkg hint-ийг хасна. Бусад query (state г.м.) хэвээр үлдэнэ. |
| Татгалзсан | Серверт татгалзлыг мэдэгдэнэ (USER_REFUSED), callback нээхгүй — иргэн eID Mongolia апп дээр үлдэнэ. |
| PIN буруу, блоклогдсон, хугацаа дууссан, сүлжээний алдаа | Аппад алдаа харуулна, callback нээхгүй. |
| Юу ч хийгээгүй | Session TIMEOUT-оор дуусна, callback нээхгүй. |
Callback зөвхөн амжилттай замд ирэх тул иргэний эхлүүлсэн хуудас session-ээ poll хийсээр байж татгалзсан, хугацаа дууссан төлвийг өөрөө харуулна.
- https callback хөтчийн гадаад навигациар нээгдэнэ: iOS
UIApplication.open, AndroidACTION_VIEW.retScheme(iOS:googlechromes,microsoft-edge-https,opera-https,touch-https,ddgQuickLink,firefox) эсвэлretPkg(Android хөтчийн package) өгвөл тэр хөтчөөр, үгүй бол системийн default-оор нээнэ. Таны домэйнд Universal Link / App Link-тэй апп суусан бол систем callback-ийг тэр апп руу өгнө — hint өгвөл хөтөч рүү албадна.
Аль хэлбэрийг сонгох вэ
- Вэб (гар утасны хөтөч): өөрийн домэйн дээрх https хуудас —
https://<таны host>/auth/eid/callback?state=…. Chrome/Firefox зэрэг default биш хөтчөөс эхэлсэн болretScheme/retPkg-ийг query-д нэмнэ. - Native апп: өөрийн Universal Link (iOS) / App Link (Android) — таны домэйны
apple-app-site-associationбаassetlinks.json/auth/eid/callbackзамыг хамарсан байна. eID Mongolia-гийн AASA зөвхөнca.eidmongolia.mn/dl-ийг хамардаг; таны callback домэйныг хамарахгүй. Энэ үедretScheme/retPkgөгөхгүй. - Custom scheme (
myapp://…) хүлээн авахгүй. Ямар ч апп ижил scheme зарлаж, буцах хаягийнuserChallengeVerifier-ийг хулгайлж чадна. Буцах хаяг үргэлжhttps://— баталгаажсан Universal Link / App Link эсвэл таны вэб. callbackHosts-ийг бүртгүүлэх хүсэлтдээ (§2) өгнө; дараа нь нэмэх, өөрчлөхийг оператороор хийлгэнэ. Зөвхөн https host (порт, path, scheme-гүй) — таслалаар, нийлээд ≤ 200 байт. Өөр хэлбэр (myapp://,https://…,*)400CALLBACK_HOST_INVALID.
Callback-ийн параметр нотолгоо биш. URL-ийг хэн ч гараар нээж чадна. Иргэнийг нэвтрүүлэхээс өмнө: (1) session эхлүүлэхдээ sessionID-ийг хэрэглэгчийн хөтчийн session (cookie)-той холбож серверт хадгалаад, callback-ийн state-ийг тулгана; (2) query-ийн sessionId-д итгэхгүй — хадгалсан sessionID-тай таарахгүй бол зогсооно (session fixation); (3) backend-ээрээ GET /session/{sessionID} хийж хариуг §8-ын дагуу бүрэн шалгана. Нэвтрэлтэд signature.flowType-ийг эхлүүлсэн урсгалтай тулгаж, BASE64URL(SHA-256(userChallengeVerifier)) == signature.userChallenge гэж заавал шалгана (§8.3). Callback URL-д secret, токен, хувийн мэдээлэл бүү хий — state нь санамсаргүй, нэг удаагийн утга.
Урсгал
Ижил төхөөрөмж (утасны хөтөч эсвэл таны апп):
1. Таны хуудас → таны backend: POST /authentication/device-link/anonymous
{ …, "initialCallbackUrl": "https://example.mn/auth/eid/callback?state=S" }
backend: sessionID + state S-ийг хэрэглэгчийн session-д хадгална
2. Таны backend вэбэд Web2App, native аппад App2App холбоос угсарна (§4.3) → холбоосыг нээнэ (VC-г харуулсаар)
https://ca.eidmongolia.mn/dl?deviceLinkType=Web2App&sessionToken=…&…&authCode=…
3. Universal Link → eID Mongolia апп (эсвэл /dl хуудас → «Аппаар нээх»)
4. Иргэн PIN → батална
5. Апп → https://example.mn/auth/eid/callback?state=S&sessionId=…&userChallengeVerifier=…
6. Таны backend: state S + хадгалсан sessionID тулгана → GET /session/{sessionID} → §8 шалгалт → нэвтрүүлнэ
QR (компьютер + утас):
1. Таны backend: POST /authentication/device-link/anonymous (initialCallbackUrl-гүй)
2. Компьютер: QR (deviceLinkType=QR) + VC — хөтөч таны backend-ээс 1 секунд тутам шинэ холбоос авч QR-ийг дахин зурна
3. Иргэн утсаараа уншуулж батална — апп хаашаа ч буцахгүй
4. Компьютер дээрх хуудас: backend GET /session/{sessionID} poll → §8 шалгалт → нэвтрүүлнэ
Аппын хувилбар
- Доод хувилбар: eID сервер аппын доод хувилбарыг
/mobile/app-config-оор албадан шинэчлүүлдэг (одоогоор iOS ≥ 2.2.8, Android versionCode ≥ 91) — ажиллаж буй бүх апп v3 холбоосыг (authCode-той) дэмжинэ. - Шууд нээх: iOS Universal Link болон Android App Link
ca.eidmongolia.mn/dl-ийг хамардаг. Android-ийнassetlinks.jsonнийтлэгдсэн. Суусан аппын домэйн баталгаажуулалт, гарын үсэг, систем болон хэрэглэгчийн тохиргооноос шууд нээгдэх эсэх хамаарна. - Нөөц зам: шууд апп нээгдээгүй бол
/dlхуудас хөтөчид нээгдэж аппыг нээхийг оролдоно; «Аппаар нээх» болон суулгах холбоос харуулна. RP зөвхөн HTTPS v3 холбоос ашиглана.
4.5 «Миний QR» — биечлэн шалгах
Касс, POS, хүлээн авах ширээ зэрэг иргэн биечлэн ирсэн газарт: иргэн eID Mongolia апп дээрээ «Миний QR»-ийг харуулна, та уншуулна, иргэн утсан дээрээ PIN2-оор баталгаажуулна, та иргэний аппын «Миний мэдээлэл» хэсгийн бүх мэдээлэл (+ нүүрний зураг)-ийг иргэний өөрийн QUALIFIED гарын үсэгтэй JWS хэлбэрээр авна. Регистр бичүүлэх, үнэмлэх хуулах шаардлагагүй. Аттрибут сонгохгүй — иргэн бүгдийг хуваалцана эсвэл татгалзана.
- Уншина — QR-ийн агуулга
EIDMN-QR1:<token>(token = 32 санамсаргүй байтын base64url, 43 тэмдэгт). Токен нэг удаагийн, 120 секунд хүчинтэй, хувийн мэдээлэл агуулахгүй; иргэн шинэ QR гаргахад өмнөх нь хүчингүй болно. SDK:qr.parse(scanned)→{token, qr}; өөр агуулга болQR_FORMAT_INVALID. - Хүсэлт — backend-ээсээ
POST /authentication/qr(qr.start): уншсанqr+nonce. CA токеныг хэрэглэж (дахин ашиглагдахгүй) иргэний утас руу хүсэлт илгээнэ; хариунд зөвхөнsessionID(VC байхгүй). - Иргэн баталгаажуулна — утсанд ганц өгүүлбэр: «Та <таны RP-ийн бүртгэлийн нэр>-д «Миний мэдээлэл» хэсгийн бүх мэдээллийг хуваалцах гэж байна» ба PIN2. PIN2 оруулах нь зөвшөөрөл; апп мэдээллээ илгээж, иргэний SIGN түлхүүрээр (SplitKey) гарын үсэг зурна.
- Үр дүн —
GET /session/{sessionID}(qr.result/qr.check) →presentation: {jws}. SDK JWS-ийг шалгаж (гинж, OCSP, PS256, claim — доор) мэдээллийг буцаана. Үр дүн ганц удаа уншигдана.
Иргэний апп eID (rp.eidmongolia.mn) Таны backend (касс)
QR: EIDMN-QR1:… ─────────────────────────────────────────────────────▶ уншина
◀── POST /authentication/qr {qr, relyingPartyUUID, relyingPartyName, nonce}
эрх QR_PRESENT → токен → хэрэглэнэ → SIGNATURE session + push
──▶ {sessionID}
push ◀──────────────────────
өгүүлбэр + PIN2 ─ «Миний мэдээлэл» + гарын үсэг (PS256) ─▶
◀── GET /session/{sessionID} (long-poll)
──▶ {state: COMPLETE, result: {endResult: OK}, presentation: {jws}}
SDK шалгалт → data, photo, signer
Эрх: зөвхөн QR_PRESENT (§2-ын эрхийн жагсаалтад нэмүүлнэ). AUTH, SIGN шаардлагагүй. Үр дүнг унших бүрд эрх дахин шалгагдана.
Нууцлал. Та иргэний «Миний мэдээлэл»-ийг бүхэлд нь (регистр, төрсөн огноо, хаяг зэрэг аппад хадгалагдсан бүх талбар, нүүрний зураг) авна — иргэн үүнийг PIN2-оор, хууль ёсны гарын үсгээр баталсан. eID утгуудын хуулбарыг хадгалахгүй: өгөгдлийн санд, лог, аудитад бичигдэхгүй; гарын үсэг хүлээх хооронд шифрлэгдсэн түр төлөвт л байж, таны анхны амжилттай уншилт, session дуусах эсвэл хугацаа дуусахад устгагдана. Session-ийг нийтийн /verify-аар харах боломжгүй. Тиймээс хүлээн авсан мэдээллийн хадгалалт, хамгаалалт бүрэн таны хариуцлага — зөвхөн хэрэгтэй талбараа хадгална, JWS-ийг лог, алдааны мессежид бүү бич.
Хүсэлт
curl -X POST https://rp.eidmongolia.mn/authentication/qr \
-H "Authorization: Bearer $RP_SECRET" -H "Content-Type: application/json" \
-d '{
"qr": "EIDMN-QR1:q9Xk3vQh2m9cTqL8rW1xYb5nPdA0sE6uJgHtKoVfRiM",
"relyingPartyUUID": "'"$RP_UUID"'",
"relyingPartyName": "Касс 3",
"nonce": "order-2026-000142"
}'
# 200 {"sessionID":"3f0d4a52-8e1b-4f6a-b0c7-2d9e1a5b7c33"}
| Талбар | Шаардлага | Утга |
|---|---|---|
qr | заавал | Уншсан агуулга яг: EIDMN-QR1:<token> |
relyingPartyUUID | заавал | Таны RP UUID (бусад session-тэй ижил) |
relyingPartyName | заавал | Дэд системийн шошго (касс, салбар) — зөвхөн лог. Иргэнд болон JWS-ийн rp_name-д таны бүртгэлийн нэр орно |
nonce | заавал | 16–128 байт, хэвлэгдэх ASCII (0x21–0x7E, зайгүй) — хүсэлт бүрд шинэ (захиалгын дугаар + санамсаргүй хэсэг г.м.). JWS-ийн nonce-д яг буцна. SDK өгөөгүй бол 16 санамсаргүй байтын base64url (22 тэмдэгт) үүсгэнэ |
JSON хатуу: дээрх 4 талбараас өөр талбар (token, attributes, signatureProtocol…) → 400 REQUEST_INVALID; бие ≤ 8 KiB. Шалгалтын дараалал: хэлбэр → эрх → токен. Тиймээс буруу эсвэл эрхгүй хүсэлт иргэний QR-ийг шатаахгүй.
HTTP · code | Утга · үйлдэл |
|---|---|
400 REQUEST_INVALID | JSON буруу, танихгүй талбар, nonce-ийн урт/тэмдэгт буруу |
403 RP_REQUIRED | Бүртгэлтэй RP биш эсвэл relyingPartyUUID канон хэлбэрийн биш |
403 PERMISSION_DENIED | RP-д QR_PRESENT алга |
404 QR_TOKEN_INVALID | Токен танигдахгүй, 120 секунд өнгөрсөн, аль хэдийн ашиглагдсан, шинэ QR-ээр солигдсон эсвэл төхөөрөмж өөрчлөгдсөн — иргэнээс шинэ QR авч уншуулна |
409 NO_SIGNING_CERTIFICATE | Иргэнд идэвхтэй QUALIFIED SIGN гэрчилгээ алга (токен хэрэглэгдсэн) |
429 RATE_LIMITED | RP-ийн хязгаар (300/мин) — Retry-After |
Үр дүн — GET /session/{sessionID}?timeoutMs= (бусад session-тэй ижил long-poll). Иргэн хариулаагүй бол {"state":"RUNNING"}; татгалзсан / хугацаа дууссан бол {"state":"COMPLETE","result":{"endResult":"USER_REFUSED"}} (TIMEOUT, DOCUMENT_UNUSABLE…). Амжилттай бол:
{
"state": "COMPLETE",
"result": { "endResult": "OK" },
"signatureProtocol": "RAW_DIGEST_SIGNATURE",
"interactionTypeUsed": "displayTextAndPIN",
"presentation": {
"jws": "eyJhbGciOiJQUzI1NiIsInR5cCI6ImVpZG1uLW15cXIrand0IiwieDVjIjpb…" // compact JWS — бүх мэдээлэл үүн дотор
}
}
cert, signature, person, onBehalfOf, result.documentNumber ирэхгүй, задалсан claims хуулбар ч үгүй — бүгд JWS дотор (payload ≈ 270 KB хүрч болно). Анхны амжилттай уншилт мэдээллийг устгана: дахин уншвал, эсвэл түр төлөвийн хугацаа (session-ий үлдсэн хугацаа + 120 с) дууссан бол 410 QR_PRESENTATION_GONE — дамжуулалтад алдагдсан хариуг сэргээхгүй, шинэ QR уншуулж дахин эхэлнэ. Уншилт бүрд QR_PRESENT дахин шалгагдана (хураагдсан бол 403 PERMISSION_DENIED). Session-ий хугацаа = гарын үсгийн session-ийх (sessionTtl.sign).
Танд юу ирэх вэ — JWS (RFC 7515, compact): B64U(header) "." B64U(payload) "." B64U(signature).
// header
{"alg":"PS256","typ":"eidmn-myqr+jwt","x5c":["MIIF…(иргэний SIGN гэрчилгээ)","MIIG…(олгосон CA)"]}
// payload (уншихад зориулж мөр задалсан; бодит payload зайгүй)
{
"iss": "https://ca.eidmongolia.mn",
"sub": "PNOMN-123456789012",
"aud": "6f1c2a9e-1b7d-4c2e-9a51-3d8e5b7c9f01",
"rp_name": "Demo Bank",
"sid": "3f0d4a52-8e1b-4f6a-b0c7-2d9e1a5b7c33",
"nonce": "order-2026-000142",
"iat": 1793000000,
"exp": 1793000300,
"statement": "Та Demo Bank-д «Миний мэдээлэл» хэсгийн бүх мэдээллийг хуваалцах гэж байна",
"data": {
"source": "citizenid",
"fields": { "birth_date": "1990-01-01", "given_name": "Бат", "reg_no": "УБ90010112", "surname": "Дорж" },
"photo": "/9j/4AAQSkZJRgABAQ…"
}
}
| Гишүүн | Утга |
|---|---|
alg · typ | PS256 (RSASSA-PSS, SHA-256, MGF1-SHA-256, salt 32) · eidmn-myqr+jwt. Өөр header гишүүн (kid, crit, jku…) байхгүй |
x5c | Яг 2 стандарт base64 DER: [0] — иргэний одоогийн QUALIFIED SIGN гэрчилгээ (гарын үсэг зурсан), [1] — түүнийг олгосон CA. Root ордохгүй — итгэлийн зангуу таны тохиргооноос (SDK-д багцлагдсан) |
iss | https://ca.eidmongolia.mn |
sub | Иргэний ETSI (PNOMN-… / PAS<CC>-…) — x5c[0]-ийн subject serialNumber-тэй ижил |
aud · rp_name | Таны RP UUID (жижиг үсгээр) · таны бүртгэлийн нэр |
sid · nonce | Таны sessionID · таны илгээсэн nonce яг |
iat · exp | Иргэний апп мэдээллээ илгээсэн мөч (unix секунд) · iat + 300 |
statement | Иргэнд харуулж гарын үсэг зуруулсан өгүүлбэр яг: "Та " + rp_name + "-д «Миний мэдээлэл» хэсгийн бүх мэдээллийг хуваалцах гэж байна" (цэггүй, үргэлж монголоор) |
data.source | Аппын мэдээллийн эх сурвалж (^[a-z0-9_-]{1,32}$): dan, passport, citizenid, gsign, guardian; шинэ утга нэмэгдэж болно; аппад хадгалагдаагүй бол unknown |
data.fields | Аппад хадгалагдсан «Миний мэдээлэл» — string → string, яг хадгалснаараа: түлхүүрийн нэр, утгын хэлбэрийг хэвшүүлэхгүй, UI-д нуусан талбарыг ч хасахгүй. Түлхүүрийн нэр эх сурвалжаас хамаарч өөр байна — тогтмол багц гэж бүү тооц; ETSI хэрэгтэй бол sub / signer.etsi-ийг ашиглана |
data.photo | Сонголттой. Нүүрний зураг — JPEG-ийн стандарт base64 (padding-тэй, мөр таслалгүй). Паспорт / үнэмлэхний скан зураг хэзээ ч ирэхгүй. Зураггүй бол гишүүн байхгүй |
| Хязгаар | Утга |
|---|---|
fields-ийн түлхүүрийн тоо | 1–128 (зураг байвал fields хоосон байж болно; хоёул хоосон бол хүлээн авахгүй) |
| Түлхүүр | 1–64 Unicode тэмдэгт, UTF-8, хяналтын тэмдэгтгүй (U+0000–U+001F, U+007F–U+009F) |
| Утга | Зөвхөн JSON string, ≤ 2048 байт UTF-8, U+0000-гүй; хоосон string зөвшөөрнө |
photo | Задалсан нь ≤ 153600 байт (150 KiB), JPEG (FF D8 FF-ээр эхэлнэ) |
| JWS | ≤ 524288 байт |
Хязгаар хэтэрвэл апп мэдээллийг тайрахгүй — хуваалцалт амжилтгүй болж session USER_REFUSED-ээр дуусна. Бодит профайл (≈ 25 талбар) хязгаараас хол доор.
Presentation-ийг шалгах (заавал; SDK өөрөө хийнэ) — эхний алдаан дээр зогсоно. Хүлээн авсан байт дээр шалгана, payload-ийг дахин serialize хийхгүй.
- JWS ≤ 524288 байт, яг 3 хэсэг, хэсэг бүр
^[A-Za-z0-9_-]+$→ эс бөгөөсQR_JWS_MALFORMED. - Header: яг
alg,typ,x5cгишүүнтэй;alg=PS256,typ=eidmn-myqr+jwt;x5c= хоосон биш 2 string; тус бүр X.509 DER болж задарна → эс бөгөөсQR_HEADER_INVALID.x5c[1]зөвхөн завсрын CA-ийн нэр дэвшигч — хэзээ ч итгэлийн зангуу биш. - Гинж:
x5c[0]→ (x5c[1]∪ таны завсрын CA-ууд) → таны итгэлийн зангуу (Үндэсний root CA), одоо ± 60 с. Leaf: CA биш; KUcontentCommitment; түвшинQUALIFIED(QcStatements); олгогчийн SKI нь иргэний issuing CA-ийнх; RSA ≥ 2048 бит → эс бөгөөсQR_CHAIN_INVALID. - Хүчингүй эсэх (OCSP, §8):
revoked→QR_CERT_REVOKED;unknown/ алдаа (hard-fail, анхдагч) →QR_REVOCATION_UNKNOWN. - Гарын үсэг: RSASSA-PSS (SHA-256, MGF1-SHA-256, salt 32) —
seg0 "." seg1-ийн ASCII байт дээр, leaf-ийн түлхүүрээр → эс бөгөөсQR_SIGNATURE_INVALID. - Payload (JSON объект):
iss=https://ca.eidmongolia.mn;sub= leaf-ийн subjectserialNumber;rp_name,statement,nonce,sid,audstring;iat,expбүхэл тоо;statement= дээрх томьёогоор яг → эс бөгөөсQR_CLAIMS_INVALID. aud= таны RP UUID (том/жижиг үсэг ялгахгүй) →QR_AUDIENCE_MISMATCH;sid= таныsessionID→QR_SESSION_MISMATCH;nonce= таныnonceяг →QR_NONCE_MISMATCH.- Хугацаа:
0 < exp − iat ≤ 300→ эс бөгөөсQR_CLAIMS_INVALID;iat ≤ одоо + 60→QR_NOT_YET_VALID;одоо < exp + 60→QR_EXPIRED. data: зөвхөнsource,fields, (сонголттой)photo; дээрх хязгаарт багтана → эс бөгөөсQR_DATA_INVALID.
SDK-ийн үр дүн: jws, claims (задалсан payload), data (fields payload-ийн дарааллаар), source, photo (JPEG байт эсвэл байхгүй), signer (certificate, etsi = sub, givenName, surname, commonName — гэрчилгээний subject-ээс), signedAt (iat), sha256 (SHA-256(seg0 "." seg1)-ийн жижиг hex — маргаантай үед eID тусламжид өгвөл бүртгэлтэй тулгана). sub / гэрчилгээг fields-ийн регистртэй SDK тулгахгүй (түлхүүрийн нэр эх сурвалжаас хамаарна) — хэрэгтэй бол өөрөө тулгана. Биечлэн ирсэн хүн мөн эсэхийг photo-оор шаардлагатай бол та тулгана.
Алдааны кодууд (SDK-д ижил; серверийн код өөрчлөгдөхгүй дамжина):
| Код | Хаанаас | Утга · үйлдэл |
|---|---|---|
QR_FORMAT_INVALID | SDK (parse) | Уншсан агуулга «Миний QR» биш — сервер рүү явахгүй |
QR_TOKEN_INVALID | 404 | Шинэ QR уншуулна |
NO_SIGNING_CERTIFICATE | 409 | Иргэнд QUALIFIED SIGN гэрчилгээ алга |
PERMISSION_DENIED · RP_REQUIRED | 403 | RP-ийн эрх / бүртгэл |
QR_PRESENTATION_GONE | 410 | Үр дүн аль хэдийн уншигдсан эсвэл хугацаа дууссан — шинэ QR-ээр дахин эхэлнэ |
QR_JWS_MALFORMED, QR_HEADER_INVALID, QR_CHAIN_INVALID, QR_CERT_REVOKED, QR_SIGNATURE_INVALID, QR_CLAIMS_INVALID, QR_AUDIENCE_MISMATCH, QR_SESSION_MISMATCH, QR_NONCE_MISMATCH, QR_NOT_YET_VALID, QR_EXPIRED, QR_DATA_INVALID | SDK (verify) | Шалгалт унасан — мэдээлэлд итгэхгүй («Гарын үсэг баталгаажсангүй») |
QR_REVOCATION_UNKNOWN | SDK (verify) | OCSP хариу өгсөнгүй (түр) — итгэхгүй; JWS-ийг verify-аар дахин шалгаж болно |
QR_PENDING | SDK (result) | maxWaitMs дууссан ч иргэн хараахан хариулаагүй — session идэвхтэй, дахин result / check |
| татгалзал / CA-ийн хугацаа | endResult | TS/Go SessionFailedError, .NET SESSION_FAILED (EndResult: USER_REFUSED, TIMEOUT…) |
SDK — TS 0.37.0+ (eid.qr), Go v0.21.0+ (c.QR), .NET 0.13.0+ (EidQrClient, namespace GeregeSystems.EidMongolia.Qr); гурвуулаа CA-ийн ижил test vector давдаг. Итгэлийн тохиргоо анхдагчаар клиентийнх: багцалсан Үндэсний root + иргэний завсрын CA, QUALIFIED, KU шалгалт, OCSP hard-fail. Кассын хуудас өөрөө polling хийдэг бол result-ийн оронд нэг удаагийн check (TS eid.qr.check(s, {serverPollMs}) → {complete: false} | {complete: true, result}; Go c.QR.Check(ctx, sess, serverPoll) → {Complete, Presentation}; .NET CheckAsync → QrCheck(Complete, Result)). start-ийн хариу (sessionId, nonce)-г JSON-оор хадгалж болно — шалгалтад хэрэгтэй. Аль хэдийн авсан JWS-ийг дахин шалгах (OCSP-оос бусад нь офлайн): TS verifyQrPresentation(jws, {rpUUID, sessionId, nonce}, trust) / eid.qr.verify, Go eid.VerifyQRPresentation(ctx, jws, eid.QRExpect{…}, trust) / c.QR.Verify, .NET EidQr.VerifyAsync(jws, QrExpect, QrTrust?) / EidQrClient.VerifyAsync.
// TypeScript — @gerege-systems/eid-mongolia-sdk 0.37.0+
import { QrError, QrTokenInvalidError, SessionFailedError } from "@gerege-systems/eid-mongolia-sdk";
const { qr } = eid.qr.parse(scanned); // {token, qr}; өөр агуулга → QrError QR_FORMAT_INVALID
const s = await eid.qr.start({ qr }); // {sessionId, nonce} — nonce-г SDK үүсгэнэ (эсвэл өөрөө өгнө)
try {
const p = await eid.qr.result(s); // long-poll + JWS шалгалт (гинж, OCSP, PS256, claim)
showPerson(p.signer.givenName, p.signer.surname, p.signer.etsi, p.photo);
for (const [k, v] of Object.entries(p.data)) render(k, v); // payload-ийн дараалал, түлхүүр яг байгаагаар
audit(p.sha256, p.signedAt);
} catch (e) {
if (e instanceof QrTokenInvalidError) { /* 404 — шинэ QR уншуулна */ }
else if (e instanceof SessionFailedError) { /* e.endResult: USER_REFUSED, TIMEOUT… */ }
else if (e instanceof QrError && e.code === "QR_PRESENTATION_GONE") { /* аль хэдийн уншигдсан → шинэ QR */ }
else if (e instanceof QrError && e.code === "QR_PENDING") { /* maxWaitMs дууссан, иргэн хараахан хариулаагүй → eid.qr.result(s)-ийг дахин дуудна (итгэхгүй гэсэн үг биш) */ }
else if (e instanceof QrError) { /* QR_* шалгалт унасан → итгэхгүй */ }
else throw e;
}
// Go — github.com/gerege-systems/eid-mongolia-sdk-go v0.21.0+
if _, err := eid.ParseQR(scanned); err != nil { // errors.Is(err, eid.ErrQRFormatInvalid)
return err
}
sess, err := c.QR.Start(ctx, eid.QRStartRequest{QR: scanned, RelyingPartyName: "Касс 3"}) // Nonce "" → SDK үүсгэнэ
if err != nil {
if errors.Is(err, eid.ErrQRTokenInvalid) { /* 404 — шинэ QR уншуулна */ }
return err
}
p, err := c.QR.Result(ctx, sess, nil)
var sf *eid.SessionFailedError
var qe *eid.QRError
switch {
case errors.As(err, &sf): // sf.EndResult
case errors.Is(err, eid.ErrQRPresentationGone): // 410 — шинэ QR
case errors.Is(err, eid.ErrQRPending): // хүлээх хугацаа дууссан, иргэн хараахан хариулаагүй → Result-ийг дахин дуудна
case errors.As(err, &qe): // qe.Code — QR_* шалгалт унасан → итгэхгүй
case err == nil:
for _, k := range p.Keys { // payload-ийн дараалал (Go map дараалалгүй)
render(k, p.Data[k])
}
register(p.Signer.ETSI, p.Signer.GivenName, p.Signer.Surname, p.Photo, p.SHA256)
}
// .NET — GeregeSystems.EidMongolia.Pades 0.13.0+ (namespace GeregeSystems.EidMongolia.Qr)
var (_, qr) = EidQr.Parse(scanned); // өөр агуулга → EidQrException QR_FORMAT_INVALID
using var client = new EidQrClient(new EidQrOptions { RpUuid = rpUuid, RpName = "Касс 3", ApiSecret = secret });
try
{
QrSession s = await client.StartAsync(new QrStartRequest(qr));
QrResult r = await client.ResultAsync(s);
foreach (var (key, value) in r.Data) Render(key, value); // payload-ийн дараалал
Register(r.Signer.Etsi, r.Signer.GivenName, r.Signer.Surname, r.Photo, r.Sha256);
}
catch (EidQrException e) when (e.Code == EidQrErrorCodes.TokenInvalid) { /* шинэ QR уншуулна */ }
catch (EidQrException e) when (e.Code == EidQrErrorCodes.SessionFailed) { /* e.EndResult */ }
catch (EidQrException e) when (e.Code == EidQrErrorCodes.PresentationGone) { /* шинэ QR */ }
catch (EidQrException e) when (e.Code == EidQrErrorCodes.Pending) { /* maxWait дууссан, иргэн хараахан хариулаагүй → ResultAsync-ийг дахин дуудна */ }
catch (EidQrException e) { /* e.Code: QR_* шалгалт унасан → итгэхгүй */ }
Гарын үсэг
Иргэн PIN2-оор хууль ёсны (non-repudiation) гарын үсэг зурна. eID руу баримт хэзээ ч очихгүй — зөвхөн hash (digest): §5.1 — дурын контейнерт (CMS, XAdES …) digest-д гарын үсэг; §5.2 — PDF (PAdES): PDF-ийг eID-ийн PAdES SDK таны backend эсвэл клиент дээр бэлтгэж, гүйцээнэ. Байгууллагын нэрийн өмнөөс зурахад аль алинд onBehalfOf нэмнэ (§10).
5.1 Digest-д гарын үсэг зуруулах
DIGEST=$(openssl dgst -sha256 -binary geree.pdf | base64)
curl -X POST https://rp.eidmongolia.mn/signature/notification/etsi/PNOMN-12345678 \
-H "Authorization: Bearer $RP_SECRET" -H "Content-Type: application/json" \
-d '{
"relyingPartyUUID": "'"$RP_UUID"'",
"relyingPartyName": "Зээлийн гэрээ",
"certificateLevel": "QUALIFIED",
"signatureProtocol": "RAW_DIGEST_SIGNATURE",
"signatureProtocolParameters": {
"digest": "'"$DIGEST"'",
"signatureAlgorithm": "rsassa-pss",
"signatureAlgorithmParameters": { "hashAlgorithm": "SHA-256" }
},
"fileName": "Зээлийн гэрээ №42.pdf",
"interactions": "'"$(printf '[{"type":"displayTextAndPIN","displayText60":"Зээлийн гэрээ №42-т гарын үсэг зурах"}]' | base64 | tr -d '\n')"'"
}'
# 200 {"sessionID":"…","vc":{"type":"alphaNumeric5","value":"90177"}}
digest— стандарт base64; урт ньhashAlgorithm-ийнхтай яг тэнцүү (SHA-256 → 32 байт). Буруу бол 400.fileName— /verify хуудас болон иргэний түүхэнд харагдана; утасны батлах дэлгэцэд харагдахгүй (тэндinteractions-ийн бичвэр гарна).vcнь digest-ээс тооцогддог (SHA-256/384/512 аль алинд) тул нэг баримтад үргэлж ижил.- QR/App2App-аар:
POST /signature/device-link/etsi/{id}эсвэл…/device-link/document/{documentNumber}— body ижил; холбоос §4.3-тэй ижил,sessionType=sign, authCode-дRAW_DIGEST_SIGNATUREба илгээсэнdigest. - Гэрчилгээ сонгуулсны дараа шууд зуруулах:
POST /signature/notification/linked/{documentNumber}+linkedSessionID(заавал) — таны RP-ийнCERTIFICATE_CHOICEsession: COMPLETE/OK, ижилdocumentNumber, дууссанаас ≤ 5 мин, гэрчилгээ нь одоогийн SIGN гэрчилгээ, нэг л удаа. Өөр RP-ийнх эсвэл олдоогүй → 404, бусад зөрчил → 409CERT_CHOICE_INVALID. - SDK — TS
eid.sign.digestByEtsi/digestByDocument/textByEtsi(текстийн SHA-256-г SDK тооцно) /deviceLinkByEtsi/deviceLinkByDocument; Goc.Sign.DigestByEtsi/DigestByDocument/TextByEtsi/DeviceLinkByEtsi/DeviceLinkByDocument. PDF-ийн hash session: TSeid.sign.pdfDigest(input)(=eid.signPdfDigest), Goc.Sign.DigestForSigner(§5.2.2).
5.2 PDF гарын үсэг (PAdES) — баримт таны талд үлдэнэ
PDF файл eID Mongolia-гийн аль ч серверт очихгүй. Эстонийн Smart-ID / DigiDoc-ийн загвартай ижил: RP-API зөвхөн hash-д гарын үсэг зуруулна (§5.1-ийн session), харин PDF-ийг бэлтгэх, гарын үсгийг суулгах, цагийн тэмдэг (TSA), OCSP/CRL, LTV (DSS), өөрийн шалгалтыг PAdES SDK таны клиент (хөтөч, апп) эсвэл backend дээр хийнэ. Гаралт нь PAdES-BASELINE-LT — Adobe Reader, EU DSS, ca.eidmongolia.mn/verify eID-гүйгээр шалгана. Таван платформын SDK нэг профайлаар (pades-client/1) байт-ижил гаралт өгнө (§5.2.10).
SDK: TypeScript 0.15.0+, Go v0.3.0+, Swift 0.1.0+, Android 0.1.0+, .NET 0.1.0+ — бүгд pades-client/1. Харагдах гарын үсэг (appearance, §5.2.4) — TS 0.16.0+, Go v0.4.0+, Swift / Android / .NET 0.2.0+; эдгээр нь golden pades-golden-v3-ийн 17 тохиолдлыг байт-ижил давна. TS 0.15.2+ / Go v0.3.2+-ийг ашиглана: CA-аар угсарсан гарын үсэгтэй баримтад нэмж зурахад бүртгэлийн priorRevisionSha256-ийг SDK өөрөө тааруулна, хариунд created (§5.2.6). TS 0.30.1+ / Go v0.15.2+ / .NET 0.8.1+-т шинэчилнэ үү: эвдэрсэн эсвэл зориуд бүтээсэн PDF (хаагдаагүй массив/мөр, ObjStm /N хэтэрсэн) дээр parser гацаж CPU/санах ой зарцуулдаг байсныг засав — одоо PDF_INVALID; Go-д дээрх replace мөр заавал. Swift / Android-д нөлөөгүй.
RP-ийн эрх: SIGN (hash session, POST /pdf/signatures) ба CERT (prepare-ээс өмнөх certificate-choice session) — CERT-гүй бол certificate-choice 403 («RP-д 'CERT' эрх алга»). CERT-ийн оронд PKI_READ-аар гэрчилгээг уншиж болно (§5.2.1, 1-р алхам) — тэр үед linked биш …/document/{documentNumber} замаар зуруулна (§5.2.5). Эрхийг оператор олгоно (§2).
5.2.1 Урсгал
Клиент (PAdES SDK) RP backend (eID SDK) eID RP-API (rp.) Утас / USB токен
────────────────── ──────────────────── ──────────────── ────────────────
1. гэрчилгээ сонгуулах ──────────▶ certificate-choice ───────▶ гэрчилгээ сонгоно
◀── signerCert ──── ◀── cert.value, documentNumber ─── GET /session/{id}
2. (onBehalfOf) claimedRole ◀───── GET /organization/representations/…
3. prepare(pdf, options)
→ dtbs (32 байт), state [сүлжээгүй; state-ийг хадгалж болно]
── dtbs ─────────────────▶ 4a. утас: hash session ─────────▶ /signature/notification/ ─▶ «Баримтад гарын үсэг
digest = dtbs linked/{documentNumber} зурах: <fileName>»
эсвэл …/device-link/… + RP нэр → PIN2
◀── signature.value ───────────── GET /session/{id}
4b. токен (desktop): PKCS#11 C_Sign(dtbs) → r‖s ─────────────────────── eID оролцохгүй ──▶ токен PIN
5. complete(state, signature)
→ TSA, OCSP/CRL, DSS, өөрийн шалгалт → гарын үсэгтэй PDF
── cms + hash-ууд ───────▶ 6. ──────────────────────────────▶ POST /pdf/signatures
◀── 201 {docID, revIndex, registeredAt, verifyUrl}
eID руу зөвхөн: гэрчилгээний сонголт, 32 байт dtbs, CMS (≤ placeholderBytes), SHA-256 hash-ууд. PDF — клиент / RP backend.
- Гэрчилгээ — signedAttrs-д (
signingCertificateV2) зурагчийн SIGN гэрчилгээ hash-аас өмнө хэрэгтэй. Иргэнээр сонгуулна:POST /signature/certificate-choice/device-link/anonymous(QR / Web2App / App2App) эсвэл…/certificate-choice/notification/etsi/{id}(push) →GET /session/{sessionID}:cert.value(base64 DER),result.documentNumber(§5.3).CERTэрх шаардана. Хувилбар:PKI_READэрхтэй бөгөөд иргэн 24 цагт таны RP-ээр нэвтэрсэн болGET /certificates/etsi/{personEtsi}-ийнtype: SIGN,status: VALID,keyMedium: MOBILEмөрийнcertValue,documentNumber-ийг авна — certificate-choice session байхгүй тул 4-р алхамд linked биш…/notification/document/{documentNumber}эсвэл…/device-link/document/{documentNumber}. - Байгууллагын нэрийн өмнөөс (сонголт) —
representations[]-ийн сонгосон мөрийнclaimedRole-ийг яг хэвээрprepare-д (§5.2.7). - prepare — SDK PDF-ийг (эхний гарын үсэгт QR-тэй баталгаажуулалтын хуудас + гарын үсгийн талбар) бэлтгэж
dtbs= SHA-256(DER(signedAttrs)) баstateбуцаана. Сүлжээ ашиглахгүй.stateнь бэлтгэсэн PDF-ийг агуулна — PDF-тэй ижил нууцлалтай хадгална (хуудас дахин ачаалагдах, апп унтрахад). - Гарын үсэг — утас: RP backend
dtbs-ийгdigestболгож hash session эхлүүлнэ (§5.2.5), иргэн утсандаа файлын нэр ба таны RP-ийн нэрийг хараад PIN2 оруулна. Токен: клиент PKCS#11-ээр локал зурна (§9.3). - complete — SDK гарын үсгийг гэрчилгээгээр шалгаж (буруу бол
SIGNATURE_INVALID,stateхэвээр), TSA, CMS, OCSP/CRL, DSS-ийг нэмээд гаралтаа өөрөө шалгана. Аль нэг алхам унавал файл буцаахгүй (§5.2.4). - Бүртгэл — RP backend
POST /pdf/signatures(§5.2.6). Энэ нь/verify/{docID}-ийн эх, урсгалд заавал. Бүртгэлгүй файл Adobe/EU DSS-д хүчинтэй хэвээр, зөвхөн/verify«бүртгэлгүй» гэнэ.
Утсанд юу харагдах вэ (WYSIWYS). Иргэн файлын нэр (fileName) ба таны RP-ийн бүртгэлийн нэрийг харна — PDF-ийн агуулгыг биш. Иргэнд харуулсан баримт яг гарын үсэг зурж буй баримт гэдэг нь RP-ийн хариуцлага.
5.2.2 SDK
| Платформ | Багц | Доод хувилбар | Хэмжээ |
|---|---|---|---|
| Хөтөч + Node.js | @gerege-systems/eid-mongolia-sdk 0.15.0+ (санал 0.35.0+), subpath /pades (native TypeScript)npm install @gerege-systems/eid-mongolia-sdk | Node.js ≥ 18; ES2022 + WebCrypto-той хөтөч | ≈ 123 KB gzip (/pades, хөтчийн bundle) |
| Go backend | github.com/gerege-systems/eid-mongolia-sdk-go v0.3.0+ (санал v0.20.0+), багц pades (reference + бүрэн Verify)go get github.com/gerege-systems/eid-mongolia-sdk-go/pades+ таны go.mod-д: replace github.com/digitorus/pdf => github.com/craftzbay/pdf v0.1.3-eid.2 (Go replace-ийг дамжуулдаггүй) | Go ≥ 1.25 | — |
| iOS + macOS | eid-mongolia-sdk-swift 0.10.0, product GeregeEIDPades (SwiftPM) | iOS 14, macOS 13, Swift 5.9 | фонт ≈ 108 KB |
| Android | eid-mongolia-sdk-android 0.10.0 (JitPack), mn.eidmongolia.eid.pades | minSdk 24, JDK 17 | AAR ≈ 330 KB |
| Windows / .NET | GeregeSystems.EidMongolia.Pades 0.13.0 (native C#; nuget.org) | .NET 8 | nupkg ≈ 186 KB |
Бүгд native, гадны binary үгүй. RP-API-ийн дуудлага (hash session, бүртгэл) нь backend SDK-д: TypeScript EidClient — signPdfDigest(), registerSignature(), registrationFromCompleted(); Go — Sign.DigestForSigner / DeviceLinkForSigner, pades.Registration(), PDF.RegisterSignature(). Бүртгэлийн бие бөглөх туслах: .NET EidPades.Registration() (0.3.0+), Swift completed.registration(medium:…) — илгээхийг таны backend хийнэ. Бүх SDK-д (TS 0.27.0, Go v0.13.0, Swift / Android / .NET 0.6.0): medium хоосон бол sessionID-аас (phone ⇔ sessionID); phone + sessionID-гүй, token + sessionID, эсвэл claimedRole-той гарын үсэгт onBehalfOf алга бол CA-д илгээхээс өмнө татгалзана (REQUEST_INVALID; TS ValidationError). Бүрэн шалгагч (pades.Verify, сүлжээгүй) зөвхөн Go-д. Нарийвчилсан профайл: Go репогийн pades/PROFILE.md, PAGE_SPEC.md, PRECHECKS.md, WIRE.md.
// Swift — Package.swift
.package(url: "https://github.com/gerege-systems/eid-mongolia-sdk-swift.git", from: "0.10.0")
// target dependencies: .product(name: "GeregeEIDPades", package: "eid-mongolia-sdk-swift")
// Android — settings.gradle.kts
dependencyResolutionManagement { repositories { google(); mavenCentral(); maven("https://jitpack.io") } }
// app/build.gradle.kts
implementation("com.github.gerege-systems:eid-mongolia-sdk-android:0.10.0")
# .NET (nuget.org)
dotnet add package GeregeSystems.EidMongolia.Pades --version 0.13.0
5.2.3 Жишээ
Хөтөч + Node.js backend (Express). PDF хэрэглэгчийн хөтчөөс гарахгүй; хөтөч ↔ backend хооронд зөвхөн гэрчилгээ, dtbs, гарын үсэг, бүртгэлийн мэдээлэл. RP secret, sessionSecret, бүртгэл — backend-д. Next.js-ийн route handler-т ижил дуудлага.
// хөтөч — npm install @gerege-systems/eid-mongolia-sdk (0.16.0+, санал 0.35.0+)
import { prepare, complete, PadesError } from "@gerege-systems/eid-mongolia-sdk/pades";
const { signerCert, documentNumber } = await api("/sign/pdf/signer"); // backend: гэрчилгээ (§5.2.1, 1)
const p = await prepare(pdfBytes, {
signerCert: b64decode(signerCert), verifyPage: true, fileName: file.name, lang: "mn",
// claimedRole, // байгууллагаар бол getRepresentations()-ийн claimedRole ЯГ ХЭВЭЭР
appearance: { page: 0, x: 340, y: 36, width: 220, height: 64 }, // 0.16.0+, сонголт: баталгаажуулалтын биш сүүлийн хуудасны баруун доод; байхгүй бол үл харагдах
});
await idb.put("pades-state", p.state); // хуудас дахин ачаалагдаж болно
const s = await api("/sign/pdf/start", { dtbs: b64(p.dtbs), documentNumber, fileName: file.name });
showVc(s.vc);
const { signature } = await api(`/sign/pdf/${s.sessionId}/result`); // backend: poll + validateSign
let done;
for (let i = 0; ; i++) {
try { done = await complete(p.state, signature); break; } // TSA + OCSP/CRL + DSS
catch (e) { if (e instanceof PadesError && e.retryable && i < 3) continue; throw e; }
}
await api(`/sign/pdf/${s.sessionId}/register`, { // backend → POST /pdf/signatures
docId: done.docId, revIndex: done.revIndex, cms: b64(done.cms), inSha256: done.inSha256,
hasPage: done.hasPage, pageSha256: done.pageSha256, sigRevSha256: done.sigRevSha256,
outSha256: done.outSha256, priorCmsSha256: done.priorCmsSha256,
priorRevisionSha256: done.priorRevisionSha256, // 0.15.2+: CA угсарсан өмнөх гарын үсгийг SDK тааруулна
});
download(done.pdf, file.name);
// backend (Express) — EidClient: RP secret зөвхөн энд
import { EidClient, registrationFromCompleted } from "@gerege-systems/eid-mongolia-sdk";
app.post("/sign/pdf/start", async (req, res) => {
const s = await eid.signPdfDigest({
dtbs: req.body.dtbs, fileName: req.body.fileName,
signer: { documentNumber: req.body.documentNumber }, // эсвэл { etsi }
// flow: "device-link", onBehalfOf: "NTRMN-…",
});
store.set(s.sessionId, { digest: s.digest, fileName: req.body.fileName, etsi: req.user.etsi });
res.json({ sessionId: s.sessionId, vc: s.vc }); // sessionSecret (device-link) backend-ээс гарахгүй
});
app.get("/sign/pdf/:id/result", async (req, res) => {
const r = await eid.session.waitForResult(req.params.id);
const sig = eid.validator.validateSign(r, store.get(req.params.id).digest, { expectedFlowType: "Notification" });
res.json({ signature: sig.signatureValueB64 });
});
app.post("/sign/pdf/:id/register", async (req, res) => {
const tx = store.get(req.params.id);
const reg = await eid.registerSignature(registrationFromCompleted(req.body, {
fileName: tx.fileName, sessionID: req.params.id, medium: "phone",
signerEtsi: tx.etsi, // CA CMS-ийн SERIALNUMBER-тэй тулгана → 409 SIGNER_MISMATCH
// onBehalfOf: "NTRMN-…",
}));
res.json({ verifyUrl: reg.verifyUrl }); // reg.created: 201 → true, давталт 200 → false
});
Go backend (PDF таны серверт). DigestForSigner / DeviceLinkForSigner гэрчилгээ сонгосон яг тэр төхөөрөмж рүү sha256WithRSAEncryption-оор зуруулна (DigestByEtsi-ийн анхдагч rsassa-pss-ийг Complete SIGNATURE_INVALID-аар татгалзана). Бүтэн, compile-оор шалгагддаг жишээ: pades/example_test.go.
// go get github.com/gerege-systems/eid-mongolia-sdk-go/pades@v0.21.0
// go.mod: replace github.com/digitorus/pdf => github.com/craftzbay/pdf v0.1.3-eid.2
import (
eid "github.com/gerege-systems/eid-mongolia-sdk-go"
"github.com/gerege-systems/eid-mongolia-sdk-go/pades"
)
// 1. Гэрчилгээ (certificate-choice)
cc, err := c.Sign.CertificateChoiceByEtsi(ctx, "PNOMN-12345678", nil)
r, err := c.Session.WaitForResult(ctx, cc.SessionID, nil)
signer, err := c.Validator.ValidateCertificateChoice(r, cc.SessionID)
// 2. Prepare — эхний гарын үсэгт QR хуудас (бүртгэлд заавал)
p, err := pades.Prepare(pdfBytes, pades.PrepareOptions{SignerCert: signer.Certificate.Raw,
VerifyPage: true, FileName: "Гэрээ №12.pdf", Lang: "mn",
Appearance: &pades.Appearance{X: 340, Y: 36, Width: 220, Height: 64}}) // v0.4.0+, сонголт; Page 0 = eID баталгаажуулалтын хуудас биш сүүлийн хуудас (v0.12.0+)
// 3. DTBS-ийг яг тэр төхөөрөмжөөр (linked, sha256WithRSAEncryption)
s, err := c.Sign.DigestForSigner(ctx, signer, base64.StdEncoding.EncodeToString(p.DTBS), interactions, nil)
res, err := c.Session.WaitForResult(ctx, s.SessionID, nil)
_, err = c.Validator.ValidateSign(res, s.Sign.Digest, s.Sign.HashAlgorithm, eid.ValidateSignOptions{
ExpectedAlgorithm: s.Sign.SignatureAlgorithm, ExpectedCertificate: signer.Certificate, ExpectedFlowType: eid.FlowNotification})
sig, _ := base64.StdEncoding.DecodeString(res.SignatureValueB64)
// 4. Complete — TSA, OCSP/CRL, DSS, өөрийн шалгалт
done, err := pades.Complete(ctx, p.State, sig, pades.CompleteOptions{})
if pades.ErrorCode(err) == pades.CodeTSAUnavailable { /* p.State хэвээр — дахин оролдоно */ }
// 5. Бүртгэл (signerEtsi = гэрчилгээний SERIALNUMBER — автоматаар)
reg, err := pades.Registration(done, pades.RegistrationOptions{Medium: eid.PDFMediumPhone, SessionID: s.SessionID})
_, err = c.PDF.RegisterSignature(ctx, reg) // POST /pdf/signatures
// done.PDF — PAdES-BASELINE-LT; pades.Verify(done.PDF, pades.VerifyOptions{}) — сүлжээгүй тайлан
Swift (iOS / macOS). prepare синхрон, complete async.
import GeregeEIDPades // eid-mongolia-sdk-swift 0.10.0
let p = try EidPades.prepare(pdf: pdf, options: .init(
signerCert: signCertDER, verifyPage: true, fileName: "Гэрээ №12.pdf", lang: "mn"))
// p.dtbs → таны backend (hash session) эсвэл токен; p.state-ийг хадгална
let done = try await EidPades.complete(state: p.state, signature: signature)
// done.pdf; done.cms, docId, revIndex, sigRevSha256, outSha256, inSha256, pageSha256, priorCmsSha256 → backend → §5.2.6
// алдаа: EidPadesError (.code: .tsaUnavailable г.м.; .code.rawValue = "TSA_UNAVAILABLE")
Kotlin (Android). prepare синхрон (UI thread-ээс бүү дууд), complete suspend (Dispatchers.IO).
// implementation("com.github.gerege-systems:eid-mongolia-sdk-android:0.10.0")
import mn.eidmongolia.eid.pades.*
val p = EidPades.prepare(pdf, PrepareOptions(
signerCert = signCertDer, verifyPage = true, fileName = "Гэрээ №12.pdf", lang = "mn"))
// p.dtbs → таны backend (hash session); p.state-ийг хадгална
val done = EidPades.complete(p.state, signature) // suspend
// done.pdf; done.cms, docId, revIndex, sigRevSha256, outSha256, ... → backend → §5.2.6
// алдаа: EidPadesException (.code), жишээ нь EidPadesException.TsaUnavailable
C# (Windows — WinUI / WPF). Товчны handler-т; Prepare синхрон, CompleteAsync.
// dotnet add package GeregeSystems.EidMongolia.Pades --version 0.13.0
using GeregeSystems.EidMongolia.Pades;
var prepared = EidPades.Prepare(pdfBytes, new PrepareOptions {
SignerCert = signCertDer, VerifyPage = true, FileName = "Гэрээ №12.pdf", Lang = "mn",
});
// prepared.Dtbs → таны backend (hash session) эсвэл токен; prepared.State-ийг хадгална
byte[] signature = await backend.SignAsync(prepared.Dtbs); // таны API
Completed done = await EidPades.CompleteAsync(prepared.State, signature, cancellationToken: ct);
await File.WriteAllBytesAsync(path, done.Pdf, ct);
// done.Cms, DocId, RevIndex, SigRevSha256, OutSha256, InSha256, PageSha256, PriorCmsSha256 → backend → §5.2.6
// алдаа: EidPadesException (.Code: EidPadesErrorCode.TsaUnavailable г.м.)
USB токен (desktop). eID-ийн session оролцохгүй: prepare-д токены SIGN гэрчилгээ, PKCS#11 C_Sign (CKM_ECDSA, оролт = dtbs, дахин hash хийхгүй) → r‖s (P-256 = 64 байт) → complete. PKCS#11 дуудлага SDK-д байхгүй — таны апп хийнэ. Бүртгэлд "medium": "token", sessionID-гүй (§9.3).
// C#
var p = EidPades.Prepare(pdf, new PrepareOptions { SignerCert = tokenSignCertDer, VerifyPage = true, FileName = name });
byte[] rs = pkcs11.SignEcdsa(tokenKey, p.Dtbs); // таны PKCS#11 код: CKM_ECDSA → r‖s
Completed done = await EidPades.CompleteAsync(p.State, rs);
// Swift (macOS)
let p = try EidPades.prepare(pdf: pdf, options: .init(signerCert: tokenSignCertDER, verifyPage: true, fileName: name))
let done = try await EidPades.complete(state: p.state, signature: rs) // rs — токены r‖s (эсвэл DER)
5.2.4 API лавлах
Нэр платформ бүрд ижил (Go/C# — PascalCase). prepare(pdf, options) → {dtbs, digest, docId, state}; complete(state, signature, options?) → Completed. signature: RSA PKCS#1 v1.5 (утас), ECDSA r‖s эсвэл DER (токен) — гэрчилгээний түлхүүрээр таньна; RSA-PSS үгүй.
| Option | Шаардлага | Тайлбар |
|---|---|---|
signerCert | заавал | Зурагчийн SIGN гэрчилгээ, DER |
signerName | сонголт | PDF /Name; хоосон бол гэрчилгээний GIVENNAME SURNAME |
reason | сонголт | PDF /Reason; ≤ 127 UTF-16 нэгж, урт бол бичихгүй |
claimedRole | сонголт | Мэдүүлсэн үүрэг (signer-attributes-v2) — төлөөллийн хариунаас яг хэвээр, ≤ 900 тэмдэгт (§5.2.7) |
docId | сонголт | Жижиг үсгийн UUID; хоосон бол шинээр. Гарын үсэгтэй баримтад хуудаснаас уншина — зөрвөл REQUEST_INVALID |
verifyPage | сонголт | true бол QR-тэй баталгаажуулалтын хуудас — зөвхөн гарын үсэггүй баримтын эхний гарын үсэгт (§5.2.8). CA-д бүртгэхийн тулд эхний гарын үсэгт заавал true |
verifyPagePosition | сонголт | "last" (анхдагч) | "first" — баталгаажуулалтын хуудас төгсгөлд эсвэл 1-р хуудас болж орно (TS 0.30.0+, Go v0.15.0+ VerifyPagePosition, Swift / Android / .NET 0.8.0+). §5.2.8 |
fileName | verifyPage үед | 1–120 тэмдэгт (удирдах/bidi тэмдэгтийг хасаж NFC) |
lang | сонголт | Хуудасны хэл: mn (анхдагч) | en |
signingTime | сонголт | PDF /M, +08:00, секунд; анхдагч одоо |
chain | сонголт | Олгогчийн гинж (04, 03, 02) DER; анхдагч SDK-д суулгасан eID гинж |
appearance | сонголт | Харагдах гарын үсэг (TS 0.16.0+, Go v0.4.0+, Swift / Android / .NET 0.2.0+) — {page, x, y, width, height, lang}; байхгүй бол үл харагдах (доор) |
Харагдах гарын үсэг (appearance, сонголт). Өгвөл хуудсан дээр тэгш өнцөгт зурна: зурагчийн нэр, байгууллагын нэрийн өмнөөс бол үүрэг (claimedRole), «eID Mongolia-аар зурсан» (en: «Signed with eID Mongolia»), огноо YYYY-MM-DD HH:MM (+08) (signingTime; TSA-ийн цаг prepare-д мэдэгдэхгүй) ба QR → https://ca.eidmongolia.mn/verify/<docID>/<rev> (энэ гарын үсгийн revIndex). Байхгүй бол гарын үсэг үл харагдана.
page— оролтын баримтын хуудас, 1-ээс (тодорхой дугаар — юуг ч алгасахгүй, баталгаажуулалтын хуудас ч байж болно);0эсвэл байхгүй = оролтын eID баталгаажуулалтын хуудас биш сүүлийн хуудас: энэ prepare-ийн нэмэх болон өмнөх гарын үсгүүдийн нэмсэн баталгаажуулалтын хуудсууд тооцогдохгүй (TS 0.26.0+, Go v0.12.0+, Swift / Android / .NET 0.5.0+; өмнөх хувилбарт хоёр дахь гарын үсгийн тэмдэг баталгаажуулалтын хуудсан дээр буудаг байв). Баталгаажуулалтын хуудсыг бичвэрээр биш, тухайн баримтынhttps://ca.eidmongolia.mn/verify/<docId>холбоосоор (Link annotation) таньдаг — өөр баримтын эсвэл ийм холбоосгүй хуудас агуулгын хуудас гэж тооцогдоно. Бүх хуудас баталгаажуулалтын бол сүүлийн хуудас.x,y,width,height— pt (1/72 inch), хуудасны CropBox (байхгүй бол MediaBox)-ийн дэлгэцэнд харагдах (/Rotate-ийн дараах) зүүн доод булангаас; x баруун тийш, y дээш; 0.01 pt хүртэл тойрно. A4 = 595 × 842 pt.lang—mn|en; байхгүй болprepare-ийнlang(тэр нь байхгүй болmn).exact—trueбол тэмдгийг ягpage/x/y/width/height-д зурна, агуулгаас зайлсхийж зөөхгүй (иргэн өөрөө байрлуулсан үед; TS 0.29.0+, Go v0.14.0+, Swift / Android / .NET 0.7.0+). Анхдагчfalse— өмнөхтэй байт-ижил. Доор.REQUEST_INVALID: өндөр < 45 pt (QR багтахгүй), бичвэр багтахгүй (хэт нарийн), тэгш өнцөгт хуудаснаас гарсан,page> хуудасны тоо, утга 0…14400-аас гадна. Санал: 220 × 64 pt.- Харагдах гарын үсэг бүр файлыг ≈ 90 KB томсгоно (фонт, QR).
Байршил — агуулгатай давхцахгүй (TS 0.25.0+, Go v0.11.0+, Swift / Android / .NET 0.4.0+; Go репогийн pades/PLACEMENT_SPEC.md). x, y нь хүссэн байрлал. prepare зорилтот хуудсыг скан хийнэ (текст, зураас, дүүргэлт, зураг, form XObject, annotation; цагаан дэвсгэр, хуудсыг ≥ 50 % бүрхсэн дэвсгэр тооцогдохгүй). Хүссэн тэгш өнцөгт (4 pt зайтай) агуулгатай давхцвал тогтсон дарааллаар эхний чөлөөтэй байрлал руу зөөнө: эсрэг талын багана (ижил захын зай), дараа нь 12 pt алхмаар дээшээ — эхлээд хүссэн баганаар, дараа нь эсрэг баганаар. Хуудас нэмэхгүй, тэмдгийн хэмжээг өөрчлөхгүй. Өмнөх гарын үсэг зурагчийн тэмдэг (widget) ч агуулга тул хоёр дахь тэмдэг түүнийг дарахгүй. Хүссэн байрлал чөлөөтэй бол гаралт өмнөхтэй байт-ижил. Скан хийх хуудас нь appearance.page; 0/байхгүй бол баталгаажуулалтын хуудас биш сүүлийн хуудас (TS 0.26.0+, Go v0.12.0+, Swift / Android / .NET 0.5.0+ — дээрх page мөр), тиймээс хоёр дахь гарын үсэг зурагчийн тэмдэг баталгаажуулалтын хуудсан дээр биш, агуулгын сүүлийн хуудсан дээр (өмнөх тэмдгээс зайлсхийж) буух бөгөөд placement.page-д тэр хуудас гарна.
Үр дүн prepare-ийн хариунд: TS prepared.placement, Go Prepared.Placement, C# PreparedDocument.Placement, Swift/Kotlin placement = {page, x, y, width, height, status} (pt, appearance-тэй ижил координат; page — тэмдэг байгаа хуудас, 1-ээс). appearance өгөөгүй бол байхгүй.
status | Утга | Байрлал |
|---|---|---|
requested | Хүссэн тэгш өнцөгт чөлөөтэй | Хүссэн |
moved | Хүссэн газар агуулгатай давхцсан, чөлөөтэй газар олдсон (exact үед хэзээ ч биш) | Зөөгдсөн (x, y-ийг харуул) |
overlap | Чөлөөтэй газар алга (жишээ нь бүтэн хуудас текст, скан хийсэн зураг); exact үед — сонгосон тэгш өнцөгт агуулгатай давхцсан | Хүссэн — агуулга дээр зурагдана |
unscanned | Хуудсыг скан хийж чадсангүй (дэмжигдэхгүй шүүлтүүр, эвдэрсэн stream, хязгаар хэтэрсэн) | Хүссэн |
overlap / unscanned үед хэрэглэгчид мэдэгдэнэ — тэмдэг баримтын агуулгыг дарж магадгүй тул зурахаас өмнө (эсвэл дараа) «Гарын үсгийн тэмдэг баримтын агуулга дээр байрлаж магадгүй» гэх мэт анхааруулга харуулж, хүсвэл өөр хуудас/байрлал сонгуулна. Гарын үсэг аль ч тохиолдолд хүчинтэй. moved үед шинэ байрлалыг урьдчилан харуулахад placement.x, placement.y-ийг ашиглана.
Иргэн өөрөө байрлуулах (TS 0.29.0+, Go v0.14.0+, Swift / Android / .NET 0.7.0+; PLACEMENT_SPEC.md §6a, §6b). PDF-ийг урьдчилан харуулж тэмдгийг иргэнээр байрлуулах UI-д хоёр зүйл хэрэгтэй:
suggestPlacement(pdf, appearance)—prepare-ийн байршлын алгоритмыг гарын үсэг, гэрчилгээ, сүлжээгүйгээр ажиллуулна: шийдэгдсэнpage(0→ баталгаажуулалтын биш сүүлийн),x,y,width,height,status(ижил оролтодprepare-ийнplacement-тэй ижил),pageWidth,pageHeight(тухайн хуудасны харагдах хэмжээ, pt),pageCount,pages(хуудас бүрийн{width, height}, 0-ээс = 1-р хуудас;{0, 0}= геометр уншигдаагүй хуудас — тэнд тэмдэг тавих боломжгүй). Анхдагч байрлалыг харуулах ба өөр хуудас сонгуулахад.appearance.exact: true— иргэний тавьсан байрлалыгprepareхэзээ ч зөөхгүй. Тэгш өнцөгт хуудаснаас гарвалREQUEST_INVALID(тайрахгүй).placement.status:requestedэсвэлoverlap(доор агуулга бий — 4 pt зайтай ижил шалгуур; тэмдэг тэнд л зурагдана), хуудсыг скан хийж чадаагүй болunscanned.- Координат: хуудасны харагдах (
/Rotate-ийн дараах) зүүн доод булангаас, y дээш. Хуудсыгspx/pt-ээр зурдаг UI-д:left = x·s,top = (pageHeight − y − height)·s; буцаагаадx = left/s,y = pageHeight − top/s − height. pdf.js-ийнgetViewport({scale})-ийн хэмжээ =pages[i]× scale (CropBox, эргүүлэлтийн дараа). suggestPlacementнь хэмжээ, PDF бүтэц, шифрлэлт,appearance-ийн утга, хуудас/тэгш өнцөгтийг шалгана (PDF_TOO_LARGE,PDF_INVALID,PDF_ENCRYPTED,REQUEST_INVALID). Өмнөх гарын үсгүүд, гэрчилгээ, нэр тэмдэгт багтах эсэхийг шалгахгүй —prepareшалгана.
import { suggestPlacement, prepare } from "@gerege-systems/eid-mongolia-sdk/pades";
// 1) Анхдагч байрлал ба хуудасны хэмжээ (гарын үсэггүй, сүлжээгүй)
const s = await suggestPlacement(pdfBytes, { page: 0, x: 340, y: 36, width: 220, height: 64 });
// s.page, s.x, s.y, s.status — prepare-ийн сонгох байрлал; s.pages[i].width/height — хуудас бүр (pt)
const scale = canvasWidthPx / s.pageWidth;
const box = { left: s.x * scale, top: (s.pageHeight - s.y - s.height) * scale }; // UI дээр тэмдэг
// 2) Иргэн зөөсний дараа (page — 1-ээс, left/top — тухайн хуудсан дээрх px) → pt, exact
const ph = s.pages[page - 1].height;
const p = await prepare(pdfBytes, {
signerCert, verifyPage: true, fileName: file.name, lang: "mn",
appearance: { page, x: left / scale, y: ph - top / scale - 64, width: 220, height: 64, exact: true },
});
// p.placement.status: "requested" | "overlap" (агуулга дээр — иргэнд мэдэгдэнэ) | "unscanned"
| SDK | exact | suggestPlacement |
|---|---|---|
| TypeScript | appearance.exact | suggestPlacement(pdf: Uint8Array, appearance): Promise<PlacementSuggestion> |
| Go | Appearance.Exact | pades.SuggestPlacement(pdf []byte, a pades.Appearance) (pades.Suggestion, error) |
| Swift | SignatureAppearance(…, exact: true) | EidPades.suggestPlacement(pdf: Data, appearance: SignatureAppearance) throws -> PlacementSuggestion |
| Kotlin | SignatureAppearance(…, exact = true) | EidPades.suggestPlacement(pdf: ByteArray, appearance: SignatureAppearance): PlacementSuggestion (синхрон — UI thread-ээс бүү дууд) |
| C# | Appearance { Exact = true } | EidPades.SuggestPlacement(byte[] pdf, Appearance appearance) → PlacementSuggestion |
complete-ийн options (бүгд сонголт): tsaUrl (анхдагч https://tsa.timeserver.mn/ = discovery pdfSigning.clientSide.tsa), roots (анхдагч Mongolian National Root CA), HTTP: TS fetch, Go HTTPClient, Swift/Kotlin transport, C# HttpClient (proxy, sandbox, тест-д; анхдагч timeout 10 с, redirect үгүй, хариу ≤ 4 MiB).
| Completed | Утга |
|---|---|
pdf | Гарын үсэгтэй PDF (PAdES-BASELINE-LT) |
cms | CMS SignedData, DER — бүртгэлийн cms |
docId | Баримтын UUID — бүртгэлийн docID, /verify/{docId} |
revIndex | Энэ гарын үсгийн дугаар (1 = эхний) |
inSha256 · pageSha256 · sigRevSha256 · outSha256 | SHA-256 (hex): оролт · хуудастай хувилбар (хуудасгүй бол хоосон) · гарын үсгийн revision (DSS-ээс өмнө) · эцсийн файл |
hasPage | Энэ гарын үсэгт хуудас нэмэгдсэн эсэх |
priorCmsSha256 | Өмнөх гарын үсгүүдийн CMS-ийн SHA-256 (баримтын дарааллаар) |
priorRevisionSha256 | Өмнөх гарын үсгүүдийн revision-ий SHA-256 (priorCmsSha256-тай ижил дараалал; TS 0.15.2+, Go v0.3.2+) — бүртгэлийн SDK CA угсарсан өмнөх мөрийг үүгээр тааруулна |
signerCert | Зурагчийн гэрчилгээ, DER |
Хязгаар (discovery pdfSigning.clientSide): оролт ≤ maxInputBytes (10 223 616), гаралт ≤ maxOutputBytes (10 485 760), CMS ≤ placeholderBytes (28 672), нэг файлд ≤ 16 гарын үсэг.
code | Хэзээ | Дахин оролдох |
|---|---|---|
PDF_INVALID · PDF_ENCRYPTED | Задрахгүй · шифрлэгдсэн | Үгүй — файлаа солино |
PDF_LOCKED | Certification гарын үсэг (DocMDP P=1) өөрчлөлт хориглосон | Үгүй |
PDF_TOO_LARGE · PDF_TOO_MANY_SIGNATURES | Хэмжээ хэтэрсэн · 16 гарын үсэг хүрсэн | Үгүй |
PDF_PRIOR_SIGNATURE_INVALID · PDF_FOREIGN_SIGNATURE | Өмнөх гарын үсэг эвдэрсэн / дараа нь өөрчлөгдсөн · eID-ийн B-LT биш гадны гарын үсэг | Үгүй |
SIGNER_INVALID · REQUEST_INVALID | Гэрчилгээ / option буруу (жишээ нь docId зөрсөн, fileName алга) | Үгүй — оролтоо засна |
SIGNATURE_INVALID | Гарын үсэг dtbs ба гэрчилгээнд таарахгүй (өөр төхөөрөмж; RSA-PSS гарын үсэг ч — бүх SDK, Go v0.13.0+) | state хүчинтэй — шинэ hash session-ий гарын үсгээр |
TSA_UNAVAILABLE · LTV_UNAVAILABLE | Цагийн тэмдэг · OCSP/CRL авч чадсангүй (хөтөчид allowlist-ийн гаднах host — §5.2.9) | Тийм — ижил state + гарын үсгээр (TS e.retryable). TS 0.18.3+ complete OCSP/CRL-ийг өөрөө 2 удаа давтдаг (~1 с, ~3 с; ltvRetries, signal). Үл хамаарах (0.21.0 / v0.8.0): гэрчилгээ цуцлагдсан (OCSP «revoked» эсвэл CRL-д байгаа) → LTV_UNAVAILABLE, давтахгүй — TS e.cause instanceof CertificateRevokedError (/pades), e.retryable false; Go errors.Is(err, pades.ErrCertificateRevoked). Иргэн шинэ гэрчилгээ авах хэрэгтэй |
SELF_VALIDATION_FAILED · PLACEHOLDER_OVERFLOW | Гаралт өөрийн шалгалтад унасан · CMS placeholder-т багтсангүй | Үгүй — дэмжлэгт |
STATE_INVALID | state эвдэрсэн / өөр хувилбарын | Шинээр prepare + session |
INTERNAL | Бусад (TS, Swift, Kotlin, C#) | Үгүй |
Алдааны төрөл: TS PadesError (code, retryable), Go *pades.Error / pades.ErrorCode(err), Swift EidPadesError (code), Kotlin EidPadesException (code), C# EidPadesException (Code, RawCode).
5.2.5 Hash session (RP-API)
Шинэ endpoint биш — §5.1-ийн гарын үсгийн session. Гэрчилгээ сонгосон иргэний төхөөрөмж рүү: SIGN эрх. Аль замаар:
- Certificate-choice-оор гэрчилгээ авсан (
CERTэрх): push —POST /signature/notification/linked/{documentNumber},linkedSessionID= тэрCERTIFICATE_CHOICEsession (заавал; алга бол400 REQUEST_INVALID, нөхцөл зөрвөл409 CERT_CHOICE_INVALID— §5.1). PKI_READ-аар гэрчилгээ уншсан (certificate-choice байхгүй): push —POST /signature/notification/document/{documentNumber}; linked-ийг ашиглаж болохгүй (linkedSessionIDнь заавалCERTIFICATE_CHOICEsession).- QR / Web2App / App2App (аль ч тохиолдолд):
POST /signature/device-link/document/{documentNumber}(холбоос §4.3,sessionType=sign). - SDK: Go
Sign.DigestForSigner—Signer.CertChoiceSessionIDбайвал linked, эс бөгөөс…/notification/document/…;DeviceLinkForSigner— device-link. TSeid.signPdfDigest({ signer: { documentNumber }, flow })—…/notification/document/…эсвэл…/device-link/document/…(linked-гүй);{ etsi }бол…/etsi/…— олон утастай иргэнийг CA эхний идэвхтэй төхөөрөмж рүү чиглүүлж, гэрчилгээ зөрвөлSIGNATURE_INVALIDтулdocumentNumber-ийг сонго. - Ямар ч замаар
documentNumber= гэрчилгээ авсан тэр төхөөрөмж — өөр төхөөрөмж зурвалcompleteSIGNATURE_INVALID.
Body (linked жишээ):
IX=$(printf '[{"type":"displayTextAndPIN","displayText60":"Баримтад гарын үсэг зурах: Гэрээ №12.pdf"}]' | base64 | tr -d '\n')
curl -X POST https://rp.eidmongolia.mn/signature/notification/linked/$DOCUMENT_NUMBER \
-H "Authorization: Bearer $RP_SECRET" -H "Content-Type: application/json" \
-d '{
"relyingPartyUUID": "'"$RP_UUID"'",
"relyingPartyName": "Гэрээний систем",
"certificateLevel": "QUALIFIED",
"linkedSessionID": "'"$CERT_CHOICE_SESSION_ID"'",
"signatureProtocol": "RAW_DIGEST_SIGNATURE",
"signatureProtocolParameters": {
"digest": "'"$DTBS_B64"'",
"signatureAlgorithm": "sha256WithRSAEncryption",
"signatureAlgorithmParameters": { "hashAlgorithm": "SHA-256" }
},
"fileName": "Гэрээ №12.pdf",
"interactions": "'"$IX"'"
}'
# 200 {"sessionID":"…","vc":{"type":"alphaNumeric5","value":"90177"}}
digest=dtbs(32 байт, стандарт base64).signatureAlgorithmзаавалsha256WithRSAEncryption(PKCS#1 v1.5).displayText60=«Баримтад гарын үсэг зурах: <fileName>»(≤ 60 тэмдэгт, илүүг SDK тайрна — CA уртыг шалгахгүй);fileName—prepare-ийнхтэй ижил, ≤ 120.- Байгууллагын нэрийн өмнөөс бол
"onBehalfOf": "NTRMN-…"— session үүсэх ба дуусахад төлөөллийг шалгана (§10). linkedSessionIDзаавал — таны RP-ийнCERTIFICATE_CHOICEsession, нөхцөл §5.1 (зөрчил →409 CERT_CHOICE_INVALID). Идэвхтэй SIGN гэрчилгээгүй иргэнд session эхлэхгүй:409 NO_SIGNING_CERTIFICATE(§11).- Хариу:
signature.value(base64) —complete-ийн оролт.endResultOK-оос бусад болcompleteдуудахгүй;cert.value= 1-р алхмын гэрчилгээ.
5.2.6 POST /pdf/signatures — бүртгэл
complete-ийн дараа RP backend дуудна. PDF биш — CMS ба SDK-ийн тооцсон hash-ууд. SIGN эрх, JSON ≤ 64 KiB, үл таних талбар → 400 REQUEST_INVALID. Discovery: endpoints.pdf.signatures, pdfSigning.clientSide.register.
curl -X POST https://rp.eidmongolia.mn/pdf/signatures \
-H "Authorization: Bearer $RP_SECRET" -H "Content-Type: application/json" \
-d '{
"docID": "5b0c…", "revIndex": 1, "medium": "phone", "sessionID": "…",
"cms": "MIIL…", "fileName": "Гэрээ №12.pdf",
"inSha256": "…", "pageSha256": "…", "sigRevSha256": "…", "outSha256": "…",
"signerEtsi": "PNOMN-12345678"
}'
# 201 {"docID":"5b0c…","revIndex":1,"registeredAt":"2026-10-14T22:10:00+08:00","verifyUrl":"https://ca.eidmongolia.mn/verify/5b0c…"}
| Талбар | Шаардлага | Тайлбар |
|---|---|---|
docID | заавал | Completed.docId (жижиг үсгийн UUID). revIndex=1 бол шинэ баримт, эс бөгөөс бүртгэлтэй байна |
revIndex | заавал | 1–16 (Completed.revIndex) |
medium | заавал | phone | token |
sessionID | phone | UUID; medium=token үед илгээвэл 400. Hash session — ижил RP, гарын үсгийн (SIGNATURE) session, COMPLETE/OK, digest = dtbs, гэрчилгээ, onBehalfOf CMS-тэй ижил |
cms | заавал | base64 DER, ≤ placeholderBytes (28 672) |
fileName | заавал | ≤ 120 тэмдэгт — /verify, иргэний түүхэнд |
inSha256, sigRevSha256, outSha256 | заавал | Completed-ийн hex hash — CA файлгүй тул мэдүүлэг |
pageSha256 | revIndex = 1 | Completed.pageSha256 — revIndex = 1 үед заавал, revIndex > 1 үед илгээхгүй (эс бөгөөс 400 REQUEST_INVALID) |
priorCmsSha256[] | revIndex > 1 | Completed.priorCmsSha256 — өмнөх eID-ийн клиент талын гарын үсгүүд. revIndex > 1 үед priorCmsSha256 + priorRevisionSha256-ийн нийт тоо = revIndex − 1; revIndex = 1 үед илгээхгүй |
priorRevisionSha256[] | revIndex > 1 | CA-аар угсарсан файлын гарын үсгийн revision-ий SHA-256. priorCmsSha256 + priorRevisionSha256 нийлээд баримтын бүх бүртгэлтэй гарын үсгийг яг хамарна. Файлаас аль нь болохыг ялгах боломжгүй тул SDK (TS 0.15.2+ registrationFromCompleted + registerSignature, Go v0.3.2+ pades.Registration + PDF.RegisterSignature) эхлээд бүгдийг CMS-ээр илгээж, 409 PRIOR_MISMATCH бол эхний мөрүүдийг revision-оор дахин оролдоно. Гараар өгвөл SDK оролдохгүй |
onBehalfOf | сонголт | NTRMN-… (≤ 64) — CMS-ийн claimedRole-ийг тэр мөчийн төлөөллөөр дахин тулгана |
signerEtsi | сонголт | Хүлээж буй зурагч (PNOMN-…, ≤ 64) — CMS-ийн гэрчилгээний SERIALNUMBER-тэй тулгана. Үргэлж илгээхийг зөвлөнө — буруу иргэний гарын үсэг бүртгэгдэхээс сэргийлнэ |
CA шалгах: CMS-ийг pades-client/1 профайлаар дахин угсарч байт-ижил байхыг; signedAttrs = contentType, messageDigest, signingCertificateV2 (+ claimedRole); зурагчийн гэрчилгээ eID-ийн SIGN, идэвхтэй, цуцлагдаагүй; цагийн тэмдгийн imprint = гарын үсэг, TSA гинж үндэсний Root-д, genTime ≤ 1 цагийн өмнө.
- 201
{docID, revIndex, registeredAt, verifyUrl}; ижил session + ижил body-г дахин илгээвэл 200 (өмнөх хариу) — сүлжээ тасарсан үед аюулгүй давтана. SDK: TSRegisterSignatureResult.created, GoPDFSignatureRegistered.Created(201 → true, 200 → false; 0.15.2+ / v0.3.2+). - Хурдны хязгаар: RP тутам 600 / 10 мин; токенд нэмэлтээр (RP, иргэн) 30, RP 60 / 10 мин →
429 RATE_LIMITED,Retry-After: 600.
| HTTP | code | Хэзээ |
|---|---|---|
400 | REQUEST_INVALID · CMS_INVALID · SIGNATURE_INVALID · TIMESTAMP_INVALID | Талбар буруу/танихгүй · CMS задрахгүй / профайлд нийцэхгүй, эсвэл onBehalfOf-гүй атал CMS-д claimedRole бий · гарын үсэг таарахгүй · цагийн тэмдэг буруу |
403 | REPRESENTATION_REVOKED · TOKEN_ORG_MISMATCH · (кодгүй) | Бүртгэлийн мөчид төлөөлөл хүчингүй (байгууллага идэвхгүй/бүртгэлгүй ч энэ кодоор) · токены байгууллага зөрсөн · RP-д SIGN эрх алга |
404 | DOC_NOT_FOUND · (кодгүй) | revIndex > 1, баримт бүртгэлгүй · session олдсонгүй / өөр RP-ийнх |
409 | SIGNER_MISMATCH | signerEtsi CMS-ийн гэрчилгээний SERIALNUMBER-тэй таарахгүй |
409 | CLAIMED_ROLE_MISMATCH | CMS-ийн мэдүүлсэн үүрэг тэр мөчийн төлөөлөл / session-ий onBehalfOf-оос зөрсөн |
409 | DOC_ID_CONFLICT · PRIOR_MISMATCH · REGISTRY_CONFLICT | revIndex=1, docID бий · priorCmsSha256 бүртгэлтэйтэй таарахгүй · ижил гарын үсэг өөр body-оор бүртгэлтэй |
409 | SESSION_MISMATCH · SESSION_NOT_COMPLETE · SESSION_FAILED | Session-ий digest/гэрчилгээ CMS-тэй зөрсөн · дуусаагүй · амжилтгүй |
409 | SIGNER_CERT_INVALID · TOKEN_CERT_INVALID | Утасны SIGN гэрчилгээ идэвхгүй/цуцлагдсан · токены гэрчилгээ идэвхгүй |
413 · 429 | REQUEST_TOO_LARGE · RATE_LIMITED | Body > 64 KiB · хурдны хязгаар (Retry-After) |
503 | STATE_UNAVAILABLE | Түр — Retry-After: 5-ийн дараа ижил body-г давтана (идэмпотент) |
SDK тогтмол (0.18.3+ / Go v0.5.2+): TS PdfRegisterErrorCode / PDF_REGISTER_ERROR_CODES (ApiError.code, ForbiddenError.code); Go eid.PDFRegisterErrorCodes, eid.CodePriorMismatch, eid.CodeDocNotFound г.м. (*eid.APIError.Code).
5.2.7 Байгууллагын нэрийн өмнөөс
Мэдүүлсэн үүрэг (claimedRole) signedAttrs-д буюу dtbs-д ордог тул prepare-ээс өмнө хэрэгтэй: GET /organization/representations/etsi/{personEtsi}-ийн representations[] мөр бүрийн claimedRole (<албан тушаал>, <байгууллагын нэр> (NTRMN-…), ≤ 900 тэмдэгт; ?lang=mn|en). Өөрөө угсрахгүй, өөрчлөхгүй.
prepare:claimedRole= тэр утга;reason=«<байгууллагын нэр>-ийг төлөөлж»(≤ 127 UTF-16 нэгж).- Hash session:
"onBehalfOf": "NTRMN-…"— CA session үүсэх ба дуусахад төлөөллийг шалгана, утсанд «Та …-ийг төлөөлөн …» гарна. - Бүртгэл:
onBehalfOf— CA үүргийг тэр мөчийн төлөөллөөр дахин тооцож CMS-ийнхтэй тулгана (403 REPRESENTATION_REVOKED/409 CLAIMED_ROLE_MISMATCH).
5.2.8 Баталгаажуулалтын хуудас
verifyPage: true ба баримтад гарын үсэг байхгүй үед SDK төгсгөлд нэг A4 хуудас (тусдаа incremental revision, гарын үсгээс өмнө) нэмнэ: файлын нэр, хэмжээ, SHA-256, docId, QR ба холбоос https://ca.eidmongolia.mn/verify/<docId>, Adobe-д итгэмжлэх заавар (https://eidmongolia.mn/trust). Хэл lang: mn (анхдагч) | en; фонт Noto Sans (SDK-д суулгасан). Зурагчийн нэр хуудсанд гарахгүй. Дараагийн гарын үсгүүд хуудас нэмэхгүй, docId-г хуудасны холбооснос уншина.
Хуудсыг эхэнд (TS 0.30.0+, Go v0.15.0+, Swift / Android / .NET 0.8.0+; PAGE_SPEC.md §8a, PLACEMENT_SPEC.md §1b): verifyPagePosition: "first" бол хуудас баримтын 1-р хуудас болно (анхдагч "last" — төгсгөлд, өмнөхтэй байт-ижил; бусад утга REQUEST_INVALID). Хуудас нэмэгдэх үед л нөлөөтэй. appearance.page, suggestPlacement ба placement.page-ийн хуудасны дугаар үргэлж оролтын PDF-ийнх — нэмэгдсэн хуудсыг тоолохгүй (SDK өөрөө +1 болгож зурна), тиймээс урьдчилан харах UI-ийн дугаарыг шууд өгнө. Дараагийн гарын үсэгт эхэнд байгаа verify хуудас нь энгийн хуудас (тодорхой page түүнийг тоолно), page: 0 түүнийг алгасна. Үл харагдах гарын үсгийн widget гаралтын 1-р хуудсанд (энэ үед verify хуудас) орно.
5.2.9 Хөтөч: CSP, CORS, үндэсний CRL
Гэрчилгээний AIA/CDP нь http:// — https хуудаснаас mixed content. Хөтөчид SDK зөвхөн allowlist host-уудыг https:// болгож шууд татна: tsa.timeserver.mn, ocsp.eidmongolia.mn, crl.eidmongolia.mn. Эдгээр нь Access-Control-Allow-Origin: * (credential-гүй, preflight 204) өгнө — RP backend-ийн relay хэрэггүй. Бусад host-ийг татахгүй → LTV_UNAVAILABLE.
Content-Security-Policy: connect-src 'self' https://tsa.timeserver.mn https://ocsp.eidmongolia.mn https://crl.eidmongolia.mn
- Үндэсний CRL (
http://esign.gov.mn/MNICA-2022.crl,…/MNRCA-2021.crl) https/CORS-гүй тул бүх SDK толин хувийг ашиглана:https://crl.eidmongolia.mn/MNICA-2022.crl,https://crl.eidmongolia.mn/MNRCA-2021.crl. CRL-ийн гарын үсгийг олгогчоор нь шалгадаг тул толинд итгэх шаардлагагүй. - Backend (Go, Node, мобайл, .NET) дээр https upgrade хийгдэхгүй (TS:
httpsUpgradeхөтөчид анхдагчtrue, Node-дfalse).
5.2.10 Нийцэл (golden vectors)
Go reference-ийн pades/testdata/golden (tag pades-golden-v7, профайл pades-client/1) — 33 тохиолдол: Quartz PDF 1.3, object stream, linearized, хоёр дахь гарын үсэг, хуудасгүй хувилбарууд, mn/en хуудас, харагдах гарын үсэг (mn/en, хоёр дахь, хоёр удаа, /Rotate 90), тэмдгийн байршил (LibreOffice гэрээ, хүснэгт, /Rotate 90 footer, гурав дахь гарын үсэг, бүтэн хуудас → overlap), page: 0 (өмнөх баталгаажуулалтын хуудас(ууд)-ыг алгасах, тодорхой дугаар, хуурамч), exact (агуулга дээр → overlap, чөлөөтэй газар — авто байршлын гаралттай байт-ижил, /Rotate 90, page: 0, хуудаснаас гарсан → REQUEST_INVALID); PDF_FOREIGN_SIGNATURE, PDF_ENCRYPTED. Харагдах тохиолдол бүрд suggestPlacement-ийн хүлээгдэх үр дүн (expect.suggestion) — талбар бүрээр. Байршлын scanner-ийг placement/vectors.json (29 vector) хайрцаг бүрээр шалгана. SDK бүрийн CI vector-уудыг хуулж, tag-тай тулгаад тогтмол цаг, nonce, бичигдсэн TSA/OCSP/CRL хариугаар dtbs, digest, cmsSha256, sigRevSha256, outSha256, pageSha256, revIndex, priorCmsSha256-ийг шалгана (Kotlin, C# гаралтын PDF-ийг бүтнээр байтаар). Ингэснээр аль ч платформд гарын үсэг зурсан файл байт-ижил.
5.2.11 Дараагийн гарын үсэг ба шалгалт
- Олон зурагч: дараагийн зурагч өмнөх гаралтыг өөрчлөлтгүй
prepare-д өгнө; бүртгэлдrevIndex+priorCmsSha256. Нэг файлд ≤ 16. - RP шалгах:
completeалдаагүй бол гаралт аль хэдийн шалгагдсан (PAdES-BASELINE-LT, LTV бүрэн, бүх гарын үсэг хүчинтэй).cert.value-ийн иргэн = хүлээсэн иргэн гэдгийг §8-ын дагуу тулгаж, бүртгэлдsignerEtsiилгээнэ;201/200-ийн дараа л «зурагдсан» гэж харуулна. - Файлыг шалгах: SDK-ийн локал шалгагч — TS
validatePdf, Gopades.ValidatePDF(§5.2.12); бүртгэл —GET https://ca.eidmongolia.mn/v3/verify/doc/{docID}ба /verify/{docID} (§10.3).
5.2.12 PDF шалгах — гурван зам
Гарын үсэгтэй PDF-ийг шалгах гурван зам ижил дүрэм (CA-ийн нийтийн шалгагчийн код), ижил JSON тайлан (snake_case) гаргана — нэг conformance багцаар (Go pades/testdata/validate) гурвууланг тулгадаг. RP-д SDK-ийн локал шалгагчийг үндсэн зам гэж зөвлөнө: файл таны орчноос гарахгүй, CA-ийн ачаалал, квотоос хамаарахгүй.
| Зам | Хаана | Тэмдэглэл |
|---|---|---|
Хөтөч — TS validatePdf (0.20.0+) | Хэрэглэгчийн хөтөч дотор (Node-д ч) | Файл серверт илгээгдэхгүй. Цуцлалтыг https://ocsp.eidmongolia.mn / https://crl.eidmongolia.mn-ээс (CORS *) — зөвхөн гэрчилгээний serial явна. Bundle gzip ~134 KB |
Backend — Go pades.ValidatePDF (v0.7.0+), TS validatePdf (Node) | RP-ийн сервер | Хүлээн авсан гэрээ, баримтыг өөрийн системд шалгах үндсэн зам |
CA — POST https://ca.eidmongolia.mn/v3/validate/pdf | eID сервер (SiVa шиг, auth-гүй) | Файл хадгалагдахгүй (зөвхөн шалгаад хариу). Нэмэлтээр eID-ийн бүртгэлээс: session_id, doc_id, on_behalf_of_org, key_medium (token|phone|unknown). /verify хуудас |
npm install @gerege-systems/eid-mongolia-sdk@^0.38.1
go get github.com/gerege-systems/eid-mongolia-sdk-go@v0.21.0
# go.mod (pades): replace github.com/digitorus/pdf => github.com/craftzbay/pdf v0.1.3-eid.2
import { validatePdf, ValidatePdfError } from "@gerege-systems/eid-mongolia-sdk/validate";
try {
const rep = await validatePdf(new Uint8Array(await file.arrayBuffer())); // { offline?, time?, fetch?, signal? }
rep.indication; // "valid" | "invalid" | "indeterminate"
for (const s of rep.signatures) {
s.signer_name; s.signing_time; s.signature_level; s.certificate_level; s.problems; s.visible;
}
} catch (e) {
if (e instanceof ValidatePdfError) e.code; // PDF_NO_SIGNATURE | PDF_INVALID | PDF_ENCRYPTED | PDF_TOO_LARGE | PDF_TOO_MANY_SIGNATURES | UNSUPPORTED_ENVIRONMENT
}
rep, err := pades.ValidatePDF(ctx, pdfBytes, pades.ValidateOptions{}) // Offline: true — зөвхөн DSS
if pades.ErrorCode(err) == pades.CodePDFNoSignature { /* гарын үсэг алга */ }
if pades.ErrorCode(err) == pades.CodePDFEncrypted { /* шифрлэгдсэн — нууцлалгүй хувийг авна */ }
for _, s := range rep.Signatures {
_, _, _ = s.SignerName, s.SignatureLevel, s.CertificateLevel
}
- Шалгалт: ByteRange (завсар нь яг
/Contents) ба messageDigest, CMS гарын үсэг, signingCertificateV2, гинж (үндэсний Root CA 01 → 02/03 → 04/05), KU/EKU (нэвтрэлтийн гэрчилгээгээр зурсан →wrong_key_usage), хугацаа (цагийн тэмдгийн цагт), RFC 3161 токен, гарын үсгийн дараах revision (eID-ийн гарын үсэг/LTV-ээс өөр бүх өөрчлөлт →modified_after_signing), харагдах гарын үсэг, LTV (ltv.complete),signature_level(PAdES-BASELINE-B/-T/-LT),certificate_level(qcStatements:QUALIFIED/QSCD, бусадADVANCED). - Цуцлалт (eID-ийн 04/05-аас гарсан гэрчилгээнд): онлайн — AIA OCSP, эс бөгөөс CDP CRL; хариугүй эсвэл
offlineбол баримтын DSS; аль нь ч үгүй →revocation_unavailable(indeterminate). Цагийн тэмдгээс хойш цуцлагдсан →revoked_after_signing(хүчинтэй хэвээр).
0.21.0+ / v0.8.0 — хатуу: OCSP нь яг энэ гэрчилгээнийх (CertID = олгогчийн нэр/түлхүүрийн hash + serial), CRL нь гэрчилгээний олгогчийнх байх ёстой; «good» хариу шалгах цагт (цагийн тэмдгийн цаг) шинэлэг байна — хуучирсан, хугацаа дууссан, ирээдүйн, өөр олгогч/serial-ийн хариу тооцогдохгүй →revocation_unavailable(indeterminate). Олгогчийн гарын үсэгтэй «revoked» (OCSP эсвэл CRL, онлайн эсвэл DSS) нь «good»-ийг үргэлж давна →revoked(invalid). Өмнөvalidбайсан зарим баримт одооindeterminateэсвэлinvalid— UI/workflow-ооindeterminate-д бэлэн байлга. - Локал тайлангийн ялгаа:
session_id,doc_id,on_behalf_of_*байхгүй (CA-ийн бүртгэлийнх);key_mediumгэрчилгээнээс л — qcStatements-тэй иргэний гарын үсгийн гэрчилгээ →phone, бусадunknown.
SDK code | CA (/v3/validate/pdf) | Хэзээ | Хэрэглэгчид |
|---|---|---|---|
PDF_NO_SIGNATURE | no_signature (422) | Баримтад цахим гарын үсэг алга | Гарын үсэгтэй файлаа сонгоно |
PDF_INVALID | not_pdf (422) | PDF биш эсвэл задрахгүй; 0.21.0 / v0.8.0-аас бүтцийн урьдчилсан шалгалт — массив/толийн давхарлалт > 256, ObjStm мөчлөг, /Size > 2 097 152 (Go errors.Is(err, pades.ErrPDFStructure)) | Файлаа солино |
PDF_ENCRYPTED (0.20.1 / v0.7.1) | encrypted (422) | /Encrypt-тэй баримт — RC4, AES-128, AES-256, хоосон хэрэглэгчийн нууц үгтэй ч; 0.21.0 / v0.8.0-аас шууд толь /Encrypt << … >> ч. Өмнө нь PDF_INVALID | «Шифрлэгдсэн PDF-ийг шалгах боломжгүй — нууцлалыг арилгаад дахин оролдоно уу» |
PDF_TOO_LARGE | too_large (413) | > 10 MiB | Файлаа багасгана |
PDF_TOO_MANY_SIGNATURES | too_many_signatures (422) | > 16 гарын үсэг | Шалгах боломжгүй |
UNSUPPORTED_ENVIRONMENT (TS 0.20.1, зөвхөн хөтөч) | — | Web Crypto (crypto.subtle) алга: http:// хуудас (secure context биш) эсвэл хуучин хөтөч. Өмнө нь PDF_INVALID | https хуудас эсвэл шинэ хөтөч шаардлагатай |
REQUEST_INVALID | — | roots / intermediates / issuers задрахгүй (RP-ийн тохиргоо) | — |
| — | unprocessable (422) · empty, read_failed (400) · busy (503, Retry-After: 30) · 429 | Задлагч унасан / хэт төвөгтэй PDF · хоосон / уншигдсангүй · шалгагч ачаалалтай · IP тутам 10 минутад 60-аас их | Файлаа солино · дахин оролдоно (Retry-After) |
CA-ийн хүсэлт: бие — PDF-ийн түүхий байт (multipart биш), Content-Type: application/pdf, ≤ 10 485 760 байт; хариу {indication, signatures[], stamp_lookup?}. Хөтчөөс cross-origin дуудах боломжгүй (CORS зөвхөн eID-ийн өөрийн хуудсанд) — RP backend-ээс эсвэл SDK-ийн локал шалгагчаар.
curl -X POST https://ca.eidmongolia.mn/v3/validate/pdf \
-H "Content-Type: application/pdf" --data-binary @signed.pdf
5.3 Иргэний гэрчилгээг урьдчилан авах
POST /signature/certificate/{documentNumber} (body: relyingPartyUUID, relyingPartyName, certificateLevel) → {"sessionID", "expiresAt"}; session-ийг poll хийхэд cert ирнэ. CERT эрх шаардана (үгүй бол 403); иргэнд идэвхтэй SIGN гэрчилгээ байхгүй бол 409 NO_SIGNING_CERTIFICATE. Энэ хүсэлт push илгээхгүй — иргэн аппаа өөрөө нээж хүлээгдэж буй хүсэлтийг батална; мэдэгдэл хэрэгтэй бол …/certificate-choice/notification/etsi/{id}-г ашиглана. Иргэнийг урьдчилан мэдэхгүй бол POST /signature/certificate-choice/device-link/anonymous (QR — холбоос §4.3, sessionType=cert) эсвэл …/certificate-choice/notification/etsi/{id} (push) — иргэн аппаараа гэрчилгээгээ сонгоно.
Гэрчилгээ авах нь нэвтрэлт биш. certificate/certificate-choice session OK-оор дуусахад signature байхгүй — иргэн түлхүүрээ эзэмшдэгийг батлахгүй. Иргэнийг таних бол заавал нэвтрэлт (ACSP_V2) хийнэ.
5.4 Гарын үсэгтэй файлыг имэйлээр илгээх (POST /share)
Гарын үсэг зурсан PDF-ээ дараагийн гарын үсэг зурагч эсвэл хүлээн авагч руу хавсралтаар илгээнэ. CA файлыг хадгалахгүй: санах ойд барьж eID-ийн хүчинтэй гарын үсэгтэй эсэхийг шалгаад илгээж, шууд хаяна (диск, DB, логт файл ба бүтэн имэйл хаяг үлдэхгүй). Хүлээн авагч бүрт тусдаа имэйл (бусдын хаяг харагдахгүй). SIGN эрх, Bearer + IP allowlist (+ бүртгэлд заасан бол mTLS).
Идэвхтэй. Хязгаар, квотыг GET https://rp.eidmongolia.mn/.well-known/eid-ийн share блокоос уншина (SDK: eid.shareInfo() / c.ShareInfo(ctx)); share.enabled: false бол имэйлийн үйлчилгээ түр тохируулагдаагүй — 503.
curl -X POST https://rp.eidmongolia.mn/share \
-H "Authorization: Bearer $RP_SECRET" \
-F "file=@signed.pdf;type=application/pdf" \
-F "filename=Гэрээ №12.pdf" \
-F "to=next@example.mn" -F "to=legal@example.mn" \
-F "subject=Гэрээ — гарын үсэг" -F "message=Хоёр дахь гарын үсгийг зурна уу" -F "lang=mn"
# 200 {"sent":2,"sha256":"9f2c…","signatures":1,"docID":"5b0c…"}
| Талбар (multipart) | Шаардлага | Тайлбар |
|---|---|---|
file | заавал | ≤ 3 080 192 байт; одоогоор зөвхөн PDF — өргөтгөл .pdf, part-ийн Content-Type (application/pdf, application/octet-stream эсвэл хоосон) ба %PDF- эхлэл гурвуулаа таарна |
filename | сонголт | Хавсралтын нэр, ≤ 120 тэмдэгт; байхгүй бол part-ийн файлын нэр. Өгвөл .pdf-ээр төгссөн байна (формат энэ нэрээр шалгагдана — эс бөгөөс 415). Зам, удирдлагын ба "*:<>?|; тэмдэгт хасагдана |
to | заавал | 1–5 хаяг (талбарыг давтах эсвэл таслалаар); энгийн ASCII хаяг, display name үгүй; жижиг үсгээр нормчилно; давхардал → 400 |
subject | сонголт | ≤ 150 тэмдэгт; гарчиг үргэлж ‹RP нэр›: … угтвартай |
message | сонголт | ≤ 1000 тэмдэгт; HTML escape хийгдэнэ |
lang | сонголт | mn (анхдагч) | en |
Хариу 200: {sent, sha256, signatures, docID?} — docID нь гарын үсэг CA-д бүртгэлтэй (§5.2.6) үед л; тэр үед имэйлд https://ca.eidmongolia.mn/verify/{docID} холбоос орно. Үл мэдэгдэх талбар → 400 REQUEST_INVALID.
Гарын үсгийн дүрэм: ядаж нэг гарын үсэг хүчинтэй (бүрэн бүтэн, гинж үндэсний Root CA хүртэл, зурах үед цуцлагдаагүй) бөгөөд eID-ийн issuing CA-аас; аль ч гарын үсэг эвдэрсэн биш; eID-ийн бус гарын үсэг хуудсан дээр харагдахгүй. Эс бөгөөс 422 FILE_NOT_SIGNED + reason: no_signature (гарын үсэг алга) · invalid (эвдэрсэн / агуулга өөрчлөгдсөн / цуцлагдсан / хэт олон) · untrusted (eID-ийн гинжид холбогдохгүй эсвэл eID-ийн бус харагдах гарын үсэг).
| HTTP | code | Хэзээ / юу хийх |
|---|---|---|
400 | REQUEST_INVALID · FILE_EMPTY · RECIPIENT_REQUIRED · TOO_MANY_RECIPIENTS · RECIPIENT_INVALID · RECIPIENT_DUPLICATE | Талбарын алдаа — програмаа засна |
401 / 403 | — | RP auth / SIGN эрх, IP, mTLS |
413 | FILE_TOO_LARGE | Файл эсвэл имэйлийн хүсэлт хязгаараас их |
415 | FILE_TYPE_NOT_SUPPORTED | Дэмжигдэхгүй формат, задрахгүй эсвэл шифрлэгдсэн PDF |
422 | FILE_NOT_SIGNED (+reason) | Дээрх гарын үсгийн дүрэм |
429 | RATE_LIMITED | Квот — Retry-After-ийн дараа |
502 | MAIL_FAILED (+sent, failed, recipients) | Имэйлийн үйлчилгээ зарим/бүх хүлээн авагчид алдсан; recipients = to-гийн дарааллаар sent | failed — зөвхөн failed-ийг дахин илгээ |
503 | MAIL_UNAVAILABLE · VALIDATOR_UNAVAILABLE · VALIDATOR_BUSY | Имэйлийн үйлчилгээ түр хүрэхгүй · шалгагч түр хүрэхгүй / ачаалалтай (Retry-After) |
Хязгаар ба квот (discovery share: maxFileBytes, maxRecipients, maxSubjectChars, maxMessageChars, maxFilenameChars, quota): RP тутам цагт 300, өдөрт 3000 хүсэлт; хүлээн авагч тутам өдөрт 10 имэйл — RP тус бүрд тусдаа. Хүсэлт 150 с хүртэл үргэлжилж болно (хүлээн авагч бүрт дараалан илгээнэ) — HTTP client-ийн timeout-ыг үүнээс бага бүү тавь.
SDK: TS 0.18.0+ eid.share({ file, filename?, to, subject?, message?, lang? }) → ShareError (code, reason, sent/failed/recipients); Go v0.5.0+ c.Share(ctx, eid.ShareRequest{…}) → *eid.ShareError (errors.As).
Ижил төхөөрөмж (App2App / Web2App)
Иргэн таны сайт эсвэл аппыг eID Mongolia апп суусан тэр утсаараа ашиглаж байгаа бол QR уншуулах боломжгүй. Ийм үед device-link session-ийн холбоосоор eID Mongolia аппыг шууд нээж, иргэн батласны дараа таны вэб (Web2App) эсвэл апп (App2App) руу буцаана. Session, VC, хариуг шалгах дүрэм QR-тэй яг ижил — зөвхөн холбоосыг хэрхэн нээх нь өөр.
- Backend session эхлүүлнэ.
POST /authentication/device-link/anonymous(гарын үсэгтPOST /signature/device-link/…) — body-дinitialCallbackUrl: иргэнийг буцаах хаяг. ХариуныsessionToken,sessionSecret-ийг серверт хадгалж, холбоосыг §4.3-ын дагуу угсарна: утасны хөтөчидdeviceLinkType=Web2App, таны native апп доторApp2App. - Нэг товч, төхөөрөмжөөс хамаарсан үйлдэл. «eID Mongolia-аар нэвтрэх» товч нэг л байна. Компьютер дээр QR холбоосыг (
deviceLinkType=QR) харуулж 1 секунд тутам шинэчилнэ (initialCallbackUrlөгөхгүй — утас тусдаа төхөөрөмж тул буцах хөтөч байхгүй, та session-ээ poll хийнэ). Гар утасны хөтөч эсвэл таны native апп дээр Web2App / App2App холбоос руу шууд шилжинэ — тусдаа «Аппаар нээх» товч хэрэггүй. VC-г хоёр тохиолдолд дэлгэцэндээ харуулна. - Апп нээгдэнэ. iOS дээр
ca.eidmongolia.mn-ийн apple-app-site-association/dlзамыг хамардаг тул Universal Link eID Mongolia аппыг шууд нээнэ. Android дээр нийтлэгдсэнassetlinks.jsonба аппын App Link баталгаажсан бол мөн шууд нээнэ. Апп суугаагүй, холбоосын баталгаажуулалт/хэрэглэгчийн тохиргооноос болж шууд нээгдээгүй бол/dlхуудас хөтөчид нээгдэж аппыг автоматаар нээхийг оролдоно, «Аппаар нээх» товч болон App Store / Google Play холбоос харуулна — урсгал тэндээс үргэлжилнэ. - Таны тал руу буцна. Иргэн батласны дараа апп
initialCallbackUrlруу буцаана — заавалhttps://(Universal Link / App Link эсвэл таны вэб), host ньcallbackHosts-д бүртгэлтэй; custom scheme хүлээн авахгүй. Буцах хаягийн дүрэм, аппын зан төлөв, буцах хөтчийн hint — §4.4. - Үр дүнг poll хийнэ. Буцах хуудас
state-ээ (CSRF) тулгаад backend-ээрээGET /session/{sessionID}-г ердийнхөөрөө poll хийлгэнэ; хариуг 8-р хэсгийн дагуу шалгана. Буцах URL ирсэн гэдэг нь нэвтэрсэн гэсэн үг биш — зөвхөн шалгасан session-ий хариунд итгэнэ.
// Backend (SDK 0.6.1+): session эхлүүлээд бүх хариуг СЕРВЕРТ хадгална
import { buildDeviceLink, type DeviceLinkSession } from "@gerege-systems/eid-mongolia-sdk";
const s = await eid.auth.deviceLinkAnonymous(interactions, {
callbackUrl: mobile ? "https://example.mn/auth/eid/callback?state=" + csrf : undefined,
});
await flows.put(s.sessionId, { session: s, owner: req.session.id }); // token/secret/receivedAt/acsp — серверт
const link = (s: DeviceLinkSession, type: "QR" | "Web2App" | "App2App") =>
buildDeviceLink({
deviceLinkBase: s.deviceLinkBase!, deviceLinkType: type,
sessionToken: s.sessionToken!, sessionSecret: s.sessionSecret!,
sessionType: "auth", lang: "mon", receivedAt: s.receivedAt,
rpChallenge: s.acsp!.rpChallenge, relyingPartyName: s.acsp!.relyingPartyName,
interactions: s.acsp!.interactions, initialCallbackUrl: s.acsp!.initialCallbackUrl,
});
// хөтөчид зөвхөн: sessionId, vc, (гар утсанд) appLink
return { sessionId: s.sessionId, vc: s.vc, appLink: mobile ? link(s, "Web2App") : undefined };
// Backend: GET /eid/qr?sessionId=… — хөтөч 1 секунд тутам дуудна (Cache-Control: no-store)
const f = await flows.get(req.query.sessionId);
if (!f || f.owner !== req.session.id) return res.sendStatus(404);
res.set("Cache-Control", "no-store").json({ link: link(f.session, "QR") });
// Frontend: нэг товч — утсан дээр аппыг нээнэ, компьютер дээр секунд тутам шинэчлэгдэх QR
import { isMobileUserAgent } from "@gerege-systems/eid-mongolia-sdk/browser"; // iPadOS (desktop UA) ч танина
const mobile = isMobileUserAgent();
const { sessionId, vc, appLink } = await startEid({ mobile });
showVc(vc);
if (mobile) location.assign(appLink);
else setInterval(async () => showQR((await getJson("/eid/qr?sessionId=" + sessionId)).link), 1000);
- Шилжилтийг товч дарсантай ойр хий. Товч дарснаас хойш урт async гинжний (олон fetch, timer) дараа
locationсолиход iOS Universal Link-ийг алгасаж/dlхуудсыг хөтөчид нээж болно. Тэр хуудас аппыг нээдэг тул урсгал эвдрэхгүй ч нэг алхам нэмэгдэнэ — session эхлүүлэх нэг fetch-ийн дараа шууд шилжинэ. - Зөвхөн
/dlхолбоос.eidmongolia://approve?…custom scheme-ийг RP шууд дуудахгүй — энэ нь/dlхуудасны дотоод fallback. - Амьд жишээ: demo.eidmongolia.mn — RP демо: холбоосыг backend-д угсарч, утсан дээр буцах хаягийг
/auth/eid/callback(+retScheme/retPkg) болгож өгдөг.
6.1 Буцалт шинэ таб эсвэл өөр хөтөчид нээгдэх
Иргэн eID Mongolia апп дээр батласны дараа апп initialCallbackUrl-ийг хөтчийн гадаад навигациар нээдэг (§4.4). Энэ нь таны хуудсыг иргэний орхисон тэр табд буцааж харуулна гэсэн баталгаа биш: хуудас шинээр ачаалагдана, ихэнхдээ шинэ табд, заримдаа өөр хөтөчид нээгдэнэ. Тиймээс аппыг нээхээс өмнөх JavaScript-ийн санах ой дахь төлөв (React state/ref г.м.) буцаж ирсэн хуудсанд байхгүй.
sessionStorageтабаар тусдаа. Шинэ табд хоосон байна — flow-ийн төлөвийг зөвхөн тэнд хадгалбал алга болно.- Өөр хөтөч = өөр cookie,
localStorage, IndexedDB. Иргэн Chrome, Facebook/Instagram зэрэг аппын дотоод хөтчөөс (in-app browser) эхэлсэн болretScheme/retPkghint-гүй үед (§4.4) callback системийн default хөтчөөр (Safari г.м.) нээгдэнэ. Аппын дотоод хөтөч рүү буцаах hint байхгүй. - Жишээ алдаа. Хуудас «одоогийн урсгал» гэдгийг санах ойд л барьдаг байвал буцаж ирсэн хуудас бүх session-ийг нэвтрэлт гэж үзэж, PDF-д гарын үсэг зурсны дараа «Амжилттай нэвтэрлээ» гаргаж, гарын үсэгтэй PDF-ийг огт үүсгэхгүй, эсвэл «мэдээлэл олдсонгүй» гэж зогсоно.
Шаардлагатай хэв маяг
- Flow-ийн төлөвийг серверт хадгална. Session эхлүүлэхдээ backend-ээ урсгалын төрөл (нэвтрэлт / гарын үсэг), баримтын дугаар, digest,
rpChallenge(ACSP_V2 контекст) зэргийгsessionID-аар түлхүүрлэж, хэрэглэгчийн хөтчийн session (cookie)-той холбож хадгална. Зөвхөн клиент талд хадгалах шаардлагатай бол (жишээ нь серверт илгээхгүй эх PDF)localStorage/IndexedDB-дsessionID-аар түлхүүрлэж, TTL-тэй хадгална —sessionStorageэсвэл санах ойд дангаар нь хэзээ ч биш. - Өөрийн opaque
state-ийг callback-ийн query-д хийнэ. Сервер https callback-ийн path-ийг/auth/eid/callbackболгож, query-г хэвээр үлдээж, fragment (#…)-ийг хасдаг — тиймээс таны утга зөвхөн query-д (?state=…) амьд үлдэнэ.stateнь санамсаргүй, нэг удаагийн түлхүүр; буцахад түүгээр серверийн бүртгэлээ хайна. Secret, токен, хувийн мэдээлэл URL-д бүү хий.sessionId,userChallengeVerifier,retScheme,retPkgнэрийг өөрийн query параметрт бүү ашигла — апп хасаад өөрийнхийгөө нэмнэ. - Үр дүнг үргэлж серверээс авч шалгана. Буцах хуудас query-ийн
sessionId,state-д итгэхгүй: хадгалсанsessionID-тай тулгаад backend-ээрээGET /session/{sessionID}хийж хариуг §8-ын дагуу бүрэн шалгана. eID Mongolia сервер callback-т өөрөө ямар ч параметр нэмэхгүй; апп зөвхөнsessionId(нэвтрэлтэдuserChallengeVerifier) нэмдэг — энэ хоёр нотолгоо биш. - Үр дүнг эхлүүлсэн хэрэглэгчийн session-той холбоно. Callback таны session cookie-гүй хөтөчид (өөр хөтөч) ирвэл тэнд нэвтрүүлэхгүй — эс бөгөөс session fixation / login CSRF-ийн зам нээгдэнэ. Оронд нь «Эхлүүлсэн хөтөчтэй буцалтыг холбож чадсангүй. Эх хөтчөөс дахин эхлүүлнэ үү» гэж харуулна. Өөр хөтчөөр ирсэн verifier-ийг эх табын нэвтрэлтэд ашиглахгүй; ашиглах боломжгүй RP flow-ийг цуцлах эсвэл хугацаагаар дуусгана. Ижил төхөөрөмжийн нэвтрэлтэд эхлүүлсэн хөтчийн session-той таарсан callback ба verifier-ийг шалгасны дараа л дуусгана (§8.3).
- Эх таб poll хийсээр байна. Аппыг нээсний дараа эх хуудас
GET /session/{sessionID}-ийг (backend-ээр дамжуулж) poll хийсээр байх ёстой — татгалзсан, хугацаа дууссан болон дуусахыг хүлээж буй төлвийг эх табд харуулна. Татгалзах, хугацаа дуусах үед апп callback нээдэггүй тул эдгээр төлвийг зөвхөн эх таб л харуулж чадна. Web2App/App2App нэвтрэлтийн pollOKболсон ч эхлүүлсэн хөтчийн session-той таарсан callback-ийн verifier ирэх хүртэл хүлээж, түүнийг шалгасны дараа дуусгана. QR/Notification урсгал poll-оор дуусна; өөр хөтчийн callback-ийг Web2App/App2App нэвтрэлтэд орлуулахгүй. Callback хуудас ба эх таб зэрэг дуусгаж болзошгүй тул дуусгах алхмыг idempotent болго (эхнийх нь дуусгаж, дараагийнх нь үр дүнг л уншина). - Хугацаа ба цуцлалт. Session-ий хугацааг эхлүүлэх хариуны
expiresAt-аас авна (SDK:s.expiresAt) — одоогийн утга нэвтрэлт 300 с (5 мин), гарын үсэг 600 с (10 мин), discoverysessionTtl. Таны хадгалсан төлөв ч мөн адил TTL-тэй. Буцаж ирэхэд бүртгэл олдохгүй бол «Хүсэлтийн мэдээлэл олдсонгүй — өөр хөтөч эсвэл цонхонд эхэлсэн, эсвэл аль хэдийн дууссан байж болно. Дахин эхлүүлнэ үү», хугацаа дууссан бол «Хүсэлтийн хугацаа дууссан»,USER_REFUSED/TIMEOUTбол тодорхой мессеж харуулна. Дуусах, алдаа, цуцлах, хугацаа дуусах бүрд тухайнsessionID-ийн үлдэгдлийг устгана. - Утсан дээр аппыг хэрэглэгчийн үйлдлээр нээнэ. Universal Link-ийг товч дарсан (user gesture) үйлдлээс нээнэ. Урт бэлтгэлийн (файл уншиж digest тооцох, серверт session үүсгэх г.м.) дараах автомат шилжилтийг iOS хэрэглэгчийн үйлдэл гэж тооцохгүй — Universal Link алгасагдаж
/dlхуудас хөтөчид нээгдэнэ. Ийм урсгалд бэлтгэл дууссаны дараа «eID аппаар гарын үсэг зурах» мэт тусдаа товч гаргана; шинэ товшилт аппыг шууд нээнэ.
// Backend (SDK 0.6.1+): эхлүүлэх — төлөв серверт, sessionID + хэрэглэгчийн session-той холбоно
import { buildDeviceLink } from "@gerege-systems/eid-mongolia-sdk";
const state = crypto.randomUUID(); // opaque, нэг удаагийн
const s = await eid.auth.deviceLinkAnonymous(interactions, {
callbackUrl: `https://example.mn/auth/eid/callback?state=${state}`,
});
await flows.put(state, { // таны DB/кэш, TTL = session-ий expiresAt
sessionId: s.sessionId, kind: "login", acsp: s.acsp, // sessionToken/sessionSecret-ийг энд хадгална; sessionSecret хөтөчид очихгүй
owner: req.session.id, expiresAt: s.expiresAt ?? Date.now() + 300_000, // session-ий expiresAt (ms)
});
const appLink = link(s, "Web2App"); // §6-ийн link() — buildDeviceLink
return { appLink, vc: s.vc }; // эх таб: нээгээд poll хийсээр
// Backend: GET /auth/eid/callback?state=…&sessionId=…
const f = await flows.get(req.query.state);
if (!f || f.expiresAt < Date.now()) return render("not-found-or-expired");
if (f.owner !== req.session.id) return render("restart-in-original-browser"); // шалгалт/хадгалалтаас ӨМНӨ
if (req.query.sessionId !== f.sessionId) return render("error"); // session fixation
if (typeof req.query.userChallengeVerifier !== "string") return render("error");
const r = await eid.session.waitForResult(f.sessionId);
if (r.endResult !== "OK") return render("refused-or-timeout");
const who = eid.validator.validateAuth(r, f.acsp, {
expectedFlowType: "Web2App",
userChallengeVerifier: req.query.userChallengeVerifier, // нэг утга бүхий string гэдгийг эхлээд шалгана
}); // §8 — унавал ValidationError
await eid.validator.checkRevocation(who.certificate); // §13: revocation hook заавал тохируулсан байна
await completeOnce(f, who); // эх табын poll-той idempotent
await flows.delete(req.query.state);
// Frontend: зөвхөн клиентэд үлдэх зүйл (жишээ нь эх PDF) — sessionID-аар, TTL-тэй
const KEY = (sid: string) => `eid-flow:${sid}`;
function saveFlow(sid: string, v: { kind: string; digestHex?: string }) {
try { localStorage.setItem(KEY(sid), JSON.stringify({ ...v, exp: Date.now() + 600_000 })); } catch {}
}
function loadFlow(sid: string) {
try {
const r = JSON.parse(localStorage.getItem(KEY(sid)) ?? "null");
if (!r || r.exp < Date.now()) { localStorage.removeItem(KEY(sid)); return null; }
return r;
} catch { return null; }
}
- Лавлах хэрэгжилт: demo.eidmongolia.mn — аппыг нээхээс өмнө урсгалын төрөл, файлын нэр, digest-ийг
localStorage-д, эх PDF-ийг IndexedDB-дsessionID-аар түлхүүрлэж 10 минутын TTL-тэй хадгалдаг; буцахад төрлөөр нь сэргээж (гарын үсэг → poll → PDF суулгах), бүртгэл олдоогүй/хугацаа дууссан үед тодорхой мессеж харуулж, дуусах/алдаа/цуцлах үед устгадаг. Утсан дээрх PDF гарын үсэгт бэлтгэлийн дараа тусдаа товчоор аппыг нээдэг. - SDK:
@gerege-systems/eid-mongolia-sdk-д flow-ийн төлөв хадгалах туслах функц одоогоор байхгүй — дээрх хэв маягийг өөрийн backend-д хэрэгжүүлнэ. v3 холбоосыг backend-дbuildDeviceLink-ээр угсарна (§4.3).
6.2 Native апп (iOS / Android)
Таны iOS эсвэл Android апп eID Mongolia аппыг нээж, иргэн батласны дараа өөр рүүгээ буцааж авах (App2App) урсгал. Session, VC, хариуг шалгах дүрэм вэбтэй ижил; ялгаа нь холбоосыг хэрхэн нээх, буцалтыг хэрхэн хүлээж авах.
- Session-ийг зөвхөн backend эхлүүлнэ. Таны сервер RP-API-д
POST /authentication/device-link/anonymous(гарын үсэгт/signature/device-link/…) дуудаж,initialCallbackUrl-ийг өгнө. RP secret, mTLS түлхүүр,sessionSecretаппад хэзээ ч орохгүй — апп өөрийн backend-ээс зөвхөн бэлэн App2App холбоос (deviceLinkType=App2App, §4.3) ба VC-г авна. - Холбоосыг системээр нээнэ. iOS:
UIApplication.shared.open(url)— эхлээд.universalLinksOnly: true-ээр оролдож, бүтэхгүй бол энгийнopen(Safari-д/dlхуудас нээгдэж аппыг нээнэ).WKWebView,SFSafariViewControllerдотор нээхгүй — тэнд Universal Link ажиллахгүй. Android:startActivity(Intent(Intent.ACTION_VIEW, uri)).ca.eidmongolia.mn-ийнassetlinks.jsonнийтлэгдсэн; суусан аппын гарын үсэг таарч, домэйн баталгаажсан бөгөөд хэрэглэгч холбоос нээхийг зөвшөөрсөн бол Android App Link аппыг шууд нээнэ. Баталгаажаагүй эсвэл тохиргоогоор хаасан бол/dlхуудас хөтөчид нээгдэж аппыг нээхийг оролдоно. - Custom scheme-ийг шууд бүү дууд.
eidmongolia://…нь/dlхуудасны дотоод нөөц бөгөөд өөрчлөгдөж болно — зөвхөн/dlhttps холбоосыг нээнэ. - Буцах хаяг = таны Universal Link / App Link.
initialCallbackUrl-д өөрийн домэйныhttps://<таны host>/auth/eid/callback?state=…-ийг өгнө; таныapple-app-site-associationбаassetlinks.json/auth/eid/callbackзамыг хамарсан байна (§4.4). Custom scheme (myapp://…) хүлээн авахгүй. Native аппretScheme/retPkgөгөхгүй. Таны апп Universal Link-ийг хүлээж авч чадахгүй бол (AASA кэш, хэрэглэгч «Safari-д нээх» сонгосон) callback хөтөчид нээгдэнэ — тэр замд «Аппруугаа буцна уу» гэсэн энгийн хуудас байлгана. - Буцахад ба апп дахин идэвхжихэд backend-ээр шалгана. Callback ирэхэд (эсвэл иргэн аппаа гараар нээхэд — foreground) апп өөрийн backend-ээс үр дүнг асууна; backend
GET /session/{sessionID}хийж §8-ын дагуу шалгана. eID Mongolia апп нээлттэй байх хооронд poll хийсээр байна. Татгалзсан (USER_REFUSED) эсвэл хугацаа дууссан (TIMEOUT) үед callback ирэхгүй — эдгээрийг зөвхөн poll-оос мэднэ. Flow-ийн төлөвийг (state↔sessionID) серверт эсвэл аппын байнгын санд хадгална — буцах хооронд таны апп системээс хаагдаж болно (§6.1).
// iOS (SwiftUI). start = таны backend-ийн хариу { link, vc, state }
showVC(start.vc)
let url = URL(string: start.link)! // https://ca.eidmongolia.mn/dl?deviceLinkType=App2App&…&authCode=…
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { opened in
if !opened { UIApplication.shared.open(url) } // апп суугаагүй / UL алгассан → /dl хуудас
}
// Буцалт: https://example.mn/auth/eid/callback?state=…&sessionId=…
// (Associated Domains: applinks:example.mn; UIKit-д scene(_:continue:) NSUserActivity)
.onOpenURL { url in
guard url.path == "/auth/eid/callback",
let q = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems,
let state = q.first(where: { $0.name == "state" })?.value else { return }
let sid = q.first(where: { $0.name == "sessionId" })?.value
Task { await api.finishEid(state: state, sessionId: sid) } // backend: GET /session + §8
}
.onChange(of: scenePhase) { _, phase in
if phase == .active { Task { await api.checkPendingEid() } } // татгалзсан/TIMEOUT-ийг poll-оос
}
// Android. start = таны backend-ийн хариу { link, vc, state }
showVc(start.vc)
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(start.link))) // /dl → eID Mongolia апп
// AndroidManifest.xml — таны App Link (домэйндоо assetlinks.json нийтэлнэ)
// <intent-filter android:autoVerify="true">
// <action android:name="android.intent.action.VIEW" />
// <category android:name="android.intent.category.DEFAULT" />
// <category android:name="android.intent.category.BROWSABLE" />
// <data android:scheme="https" android:host="example.mn" android:path="/auth/eid/callback" />
// </intent-filter>
override fun onNewIntent(intent: Intent) { // onCreate-д ч ижил (апп хаагдсан байж болно)
super.onNewIntent(intent)
handleEidReturn(intent.data)
}
private fun handleEidReturn(uri: Uri?) {
if (uri?.path != "/auth/eid/callback") return
val state = uri.getQueryParameter("state") ?: return
lifecycleScope.launch { api.finishEid(state, uri.getQueryParameter("sessionId")) } // backend: GET /session + §8
}
override fun onResume() {
super.onResume()
lifecycleScope.launch { api.checkPendingEid() } // татгалзсан/TIMEOUT-ийг poll-оос
}
Шалгах жагсаалт (production-оос өмнө, iOS ба Android тус бүрд):
- eID Mongolia апп суусан: холбоос аппыг нээнэ (iOS Universal Link-ээр, Android баталгаажсан App Link-ээр шууд; үгүй бол
/dlхуудсаар), батлахад таны апп/auth/eid/callback?state=…&sessionId=…-ээр нээгдэж, backend шалгаад дуусна. - eID Mongolia апп суугаагүй:
/dlхуудас App Store / Google Play товч харуулна; таны апп руу буцахад sessionRUNNINGхэвээр — дахин эхлүүлэх боломж өгнө. - Татгалзах: callback ирэхгүй; таны апп руу гараар буцахад
USER_REFUSEDгэж харуулна. - Хугацаа дуусах: нэвтрэлт 5 минут (гарын үсэг 10 минут; discovery
sessionTtl) юу ч хийхгүй —TIMEOUTгэж харуулна. - Таны апп хаагдсан үед буцах: апп callback-аар хүйтнээр (cold start) нээгдэж урсгалаа сэргээнэ.
- Callback зөвшөөрөгдсөн эсэх: амжилттай нэвтрэлтийн хариуны
signature.flowTypeньApp2Appбайна. Бүртгэлгүй host/scheme-тэй callback session эхлүүлэхэд чимээгүй хаягдана (алдаа өгөхгүй), гэхдээ тэр session-ий Web2App/App2App холбоосыг аппDEVICE_LINK_NO_CALLBACK-оор татгалзана — зөвхөн QR ажиллана (§4.4). Тийм болcallbackHosts-оо шалгана. Шалтгаан тодорхойгүй бол туршилтынsessionID-ийг eIDsupport@gerege.com-д илгээж серверийн бүртгэлээр шалгуулна.
Session ба хариу
curl "https://rp.eidmongolia.mn/session/$SESSION_ID?timeoutMs=30000" \
-H "Authorization: Bearer $RP_SECRET"
timeoutMs — сервер энэ хугацаанд хүлээгээд (анхдагч 1000, дээд 120000 — их утгыг 120000 болгоно) төлөв өөрчлөгдмөгц буцна. state: RUNNING бол дахин дуудна. Өөр RP-ийн session → 404.
{
"state": "COMPLETE",
"result": { "endResult": "OK", "documentNumber": "3f9c1b2e-7d4a-4c58-9e21-0b6f8a1d2c34" },
"signatureProtocol": "ACSP_V2",
"signature": {
"value": "Zm9v…", // base64
"serverRandom": "…", // нэвтрэлтэд — base64, ≥ 18 байт
"userChallenge": "…", // нэвтрэлтэд — base64url, 43 тэмдэгт
"flowType": "Notification",
"initialCallbackUrl": "", // ACSP_V2-д зурагдсан callback (QR/push → хоосон)
"signatureAlgorithm": "rsassa-pss",
"signatureAlgorithmParameters": {
"hashAlgorithm": "SHA-512",
"maskGenAlgorithm": { "algorithm": "id-mgf1", "parameters": { "hashAlgorithm": "SHA-512" } },
"saltLength": 64, "trailerField": "0xbc"
}
},
"cert": { "value": "MIIF…", "certificateLevel": "QUALIFIED" }, // base64 DER
"person": { "firstName": "Бат", "lastName": "Дорж", "civilId": "12345678",
"regNo": "УБ12345678", "birthDate": "1990-01-01", "gender": "M" },
"interactionTypeUsed": "displayTextAndPIN"
}
| Талбар | Тайлбар |
|---|---|
state | RUNNING | COMPLETE |
result.endResult | OK — амжилт, гэхдээ зөвхөн §8-ын шалгалтын дараа (signature-гүй OK-г татгалзана — certificate / certificate-choice session-оос бусад). Бусад: USER_REFUSED (болон USER_REFUSED_* хувилбарууд), WRONG_VC, TIMEOUT, DOCUMENT_UNUSABLE (PIN блоклогдсон / төхөөрөмж идэвхгүй), REQUIRED_INTERACTION_NOT_SUPPORTED_BY_APP. OK-оос бусад бүх утгыг амжилтгүй гэж үзнэ. |
result.documentNumber | Иргэний төхөөрөмжийн тогтмол дугаар (UUID, регистртэй холбоогүй) — дараагийн session-д …/document/{documentNumber}-аар ашиглаж болно |
signature | Гарын үсэг ба параметр. serverRandom, userChallenge зөвхөн нэвтрэлтэд; signatureAlgorithmParameters зөвхөн rsassa-pss-д |
signature.initialCallbackUrl | Нэвтрэлтийн ACSP_V2 мөрөнд зурагдсан callback. QR ба push (Notification) үед хоосон; Web2App/App2App үед session эхлүүлэхэд илгээсэн түүхий URL. Өөрийн хадгалсан callback-тай тулгаж байж ашиглана — 8.3. Гарын үсгийн session-д энэ талбар байхгүй. |
cert | Иргэний X.509 гэрчилгээ (base64 DER) ба бодит түвшин |
person | Иргэний бүртгэлийн мэдээлэл: firstName, lastName (эцгийн нэр), familyName (ургийн овог), firstNameEn, lastNameEn, civilId, regNo, birthDate, gender (M|F|X), geID. Зөвхөн COMPLETE үед, байгаа талбарууд л |
onBehalfOf | Байгууллагын нэрийн өмнөөс session бол: orgEtsi, orgName, role, roleLabel, rightType (ADMIN|MANAGER), rightTypeLabel. role/roleLabel/rightType/rightTypeLabel нь хариу угсрах үеийн бүртгэлээс — эрх хасагдсан бол хоосон (§10) |
Хариуг шалгах (заавал)
state: "COMPLETE" + endResult: "OK" гэдэгт дангаар нь итгэж болохгүй. Сүлжээ, proxy, тохиргооны эсвэл серверийн алдаа хуурамч OK үүсгэж болно. OK бол signature-ийг заавал шалгана; signature-гүй OK-г татгалзана. Иргэнийг таньсан гэж үзэхээсээ өмнө доорх бүх шалгалтыг өөрийн backend дээр хийнэ. SDK-ийн ResponseValidator гинж, гэрчилгээ ба гарын үсгийг шалгана; урсгалын сонголтыг тодорхой өгч, OCSP/CRL hook тохируулаад await checkRevocation()-г тусад нь дуудна. Ашигласан ч signature-гүй OK-г таны код татгалздагийг тестээр батал.
Хүлээн авахын өмнө бүгд биелэх ёстой:
signature.valueбаcert.valueхоёулаа байгаа, хоосон биш — үгүй болendResult-аас үл хамааран татгалзана.- Гэрчилгээний гинж, хугацаа, түвшин, цуцлалт (OCSP/CRL) — 8.1.
- Гэрчилгээний төрөл (нэвтрэлт/гарын үсэг) ба иргэний дугаар — 8.2.
signature.value-г гэрчилгээний нийтийн түлхүүрээр, өөрийн хадгалсан challenge/digest-ээр шалгана — 8.3 (нэвтрэлт), 8.4 (гарын үсэг).- Нэвтрэлтэд:
signature.flowTypeнь таны санал болгосон төрлүүдийн нэг;Web2App/App2Appүед callback-ийнuserChallengeVerifierзаавал таарна — 8.3.
8.1 Гэрчилгээний гинж
cert.value-г үндэсний Root CA хүртэл шалгана. Гинж:
- Mongolian National Root CA (итгэлийн тулгуур) — татах
- Mongolian National Issuing CA (MDDC) — нийтийн URL-д одоогоор нийтлэгдээгүй; eIDsupport@gerege.com-оос хүсэж авна
- Gerege Issuing CA — татах
- eID Mongolia Issuing CA (иргэн) — татах / eID Mongolia Organization Issuing CA (байгууллага, тамга) — татах
- Итгэлийн тулгуур зөвхөн үндэсний Root — системийн (OS) trust store-д итгэхгүй. Завсрын CA-уудыг дээрх холбоосоос авч intermediates болгоно. Иргэний гэрчилгээнд AIA
caIssuers(http://crl.eidmongolia.mn/ca/eid-mongolia-issuing-ca.cer) бий, гэхдээ түүнээс татахад найдахгүй — завсрын CA-уудыг урьдчилан тохируулна. - Хугацаа:
notBefore ≤ одоо ≤ notAfter(цагийн зөрүү ≤ хэдэн минут). - Түвшин:
cert.certificateLevelнь хүссэнээс доошгүй (ADVANCED<QUALIFIED,QSCD). Хариуны утгад дангаар итгэхгүй — гэрчилгээнээс тогтооно: гарын үсгийн гэрчилгээнд qcStatementsQcCompliance→QUALIFIED(+QcSSCD→QSCD), qcStatements-гүй (USB токен) →ADVANCED; нэвтрэлтийн гэрчилгээнд qcStatements байхгүй тул профайлаар — KeyUsagekeyEncipherment-тэй (утас) →QUALIFIED, үгүй (токен) →ADVANCED. Хариу гэрчилгээнээс өндөр түвшин зарлавал татгалзана (SDK 0.19.0+ / Go v0.6.0+certificateLevelOf/eid.CertificateLevelOf). - Олгогч: иргэний гэрчилгээг зөвхөн eID Mongolia Issuing CA (04, SKI
b8a37f2c64ad677912db7d9e34f246358a2fb909) гаргана — ижил Root-ийн доорх өөр CA (байгууллагын 05 г.м.)-ийн гэрчилгээг иргэнийх гэж хүлээн авахгүй (SDK 0.19.0+ / v0.6.0+ анхдагч). - Цуцлалт: OCSP
http://ocsp.eidmongolia.mn/ocspэсвэл CRLhttp://crl.eidmongolia.mn/crl— гэрчилгээний AIA/CDP-д бичигдсэн.
8.2 Иргэнийг таних
- Subject:
SERIALNUMBER=PNOMN-<иргэний дугаар>, GIVENNAME=…, SURNAME=…, CN=<SURNAME GIVENNAME>, C=MN(CN 2026-10-13-аас; өмнө олгосон гэрчилгээнд CN байхгүй). Гадаад иргэндPNO<улсын код>-…хэлбэртэй. Иргэнийг дансандаа зөвхөнSERIALNUMBER-ийн иргэний дугаараар тулгана;person.regNo,geIDзэрэг бусад талбар гэрчилгээнд орохгүй тул тэдгээрээр данс хайхгүй. - Нэвтрэлтийн гэрчилгээ: KeyUsage
digitalSignature, EKUclientAuth. Гарын үсгийн гэрчилгээ: KeyUsagecontentCommitment(non-repudiation). Нэвтрэлтэд гарын үсгийн, гарын үсэгт нэвтрэлтийн гэрчилгээг хүлээн авахгүй.
8.3 Нэвтрэлтийн гарын үсэг — ACSP_V2
Гарын үсгийг доорх мөрийн hash-ийн эсрэг, гэрчилгээний нийтийн түлхүүрээр шалгана. Мөрийг өөрийн илгээсэн утгуудаар (серверийн хариунаас биш) угсарна — зөвхөн serverRandom, userChallenge, flowType, interactionTypeUsed болон доорх дүрмээр тулгасан signature.initialCallbackUrl хариунаас:
message = "smart-id" | "ACSP_V2" | serverRandom | rpChallenge | userChallenge
| B64(relyingPartyName) | B64(brokeredRpName) | B64(SHA-256(interactions))
| interactionTypeUsed | initialCallbackUrl | flowType
digest = HASH(UTF-8(message)) // HASH = хүсэлтийн hashAlgorithm
verify(cert.publicKey, signatureAlgorithm, digest, signature.value)
|— тусгаарлагч (ASCII 0x7C).serverRandom,rpChallenge— base64 мөрөөрөө (декод хийхгүй).B64()— стандарт base64; хоосон утга → хоосон мөр.brokeredRpNameодоогоор үргэлж хоосон.interactions— хүсэлтэд илгээсэн base64 мөр яг хэвээр; түүний SHA-256-г base64 болгоно.relyingPartyName— хүсэлтэд бичсэн түүхий утга.initialCallbackUrl— хариуныsignature.initialCallbackUrl; зөвхөн хоосон мөр эсвэл өөрийн session эхлүүлэхэд илгээсэн түүхий callback-тай байт бүрээр ижил утгыг зөвшөөрнө (нормчлохгүй). Web2App/App2App үед өөрийн хоосон биш callback-тай заавал ижил; QR ба push (Notification) үед одоогийн CA хоосон мөр зурна. Дурын callback утгыг хариунаас шууд бүү ашигла. SDK 0.6.1+ энэ талбарыг тулгаж ACSP_V2 мөрийг угсардаг.- Шалгах зүйлс:
serverRandom≥ 18 байт;userChallenge^[A-Za-z0-9_-]{43}$;flowType— доор;signatureAlgorithm/hashAlgorithmнь хүссэнтэй ижил (доошлуулахыг хүлээн авахгүй); PSS-д MGF1 hash = hash, salt = hash-ийн урт.
Урсгалыг тулгах (заавал). flowType гарын үсэг зурагдсан мөрөнд ордог. v3 холбоосонд (§4.3) deviceLinkType нь authCode-оор хамгаалагдсан; сервер эхэнд нээгдсэн төрлийг session-д бэхэлдэг. Session эхлүүлэхдээ хэрэглэгчид санал болгосон төрлүүдээ хадгална: дан QR → ["QR"]; QR + вэб товч → ["QR", "Web2App"]; native аппын товч → ["App2App"]; push → ["Notification"]. SDK нь callbackUrl өгсөн үед acsp.flowTypes-д App2App, Web2App хоёуланг нэмдэг — нарийсгах бол хадгалахаас өмнө acsp.flowTypes-ийг өөрөө тохируулна.
- MUST: гарын үсэг нь зөв гэж баталгаажсан
signature.flowTypeтаны санал болгосон төрлүүдэд байх ёстой. QR + товч нэг session-д байвал төрлийг энэ талбараас уншина; үр дүн callback эсвэл poll-оор ирснээр төрлийг тогтоохгүй. Өөр төрлийн холбоосоор дахин нээвэл сервер eID Mongolia аппадDEVICE_LINK_TYPE_CONFLICTбуцаана (таны backend-д биш); session эхний төрлөөрөө үргэлжилнэ. - MUST:
Web2AppбаApp2Appүед callback-ийнuserChallengeVerifier(§4.4) заавал байх баBASE64URL(SHA-256(UTF-8(userChallengeVerifier))) == signature.userChallenge. Poll эхэлжOKавсан ч verifier иртэл нэвтрүүлэхгүй; callback ирэхийг session-ийн хугацаанд хүлээнэ. Callback-ийнstate, session ID ба эхлүүлсэн хөтөч/аппын төлөвийг мөн тулгана. Үр дүнг зөвхөн тэр callback ирсэн хөтчийн session-д өгнө — өөр төхөөрөмжийг poll-оор нэвтрүүлэхгүй. - QR ба Notification: зөвшөөрөгдсөн төрөл, гарын үсэг, гэрчилгээ, цуцлалтыг шалгасны дараа poll-оор дуусгана; callback verifier шаардахгүй.
// Backend: session эхлүүлэхдээ санал болгосон төрлүүдийг хадгална.
// saved.acsp.flowTypes = ["QR", "Web2App"]; saved.sessionID = s.sessionId;
// result = SDK-ийн session.waitForResult()/session.poll()-оос авсан SessionResult (хавтгай бүтэц).
const flow = saved.acsp.flowTypes.find(f => f === result.flowType); // FlowType; гарын үсэг шалгагдаагүй
if (!flow) throw new Error("санал болгоогүй урсгал");
const sameDevice = flow === "Web2App" || flow === "App2App";
// verifier = state/session/хөтчийн төлөвийг тулгасан callback-аас серверт хадгалсан утга
if (sameDevice && !verifier) return waitForCallback(); // poll-ийн OK-г одоохондоо нэвтрэлт болгохгүй
const who = eid.validator.validateAuth(result, saved.acsp, {
expectedFlowType: flow, // SDK verifier-ийг same-device үед заавал тулгана
...(sameDevice ? { userChallengeVerifier: verifier } : {}),
});
await eid.validator.checkRevocation(who.certificate); // OCSP/CRL hook урьдчилан тохируулсан байх
await completeOnce(saved, who); // callback/poll давхардлыг нэг удаа дуусгана
SDK 0.6.1+ ашиглана. expectedFlowType-д шалгах гэж буй хариуны төрлийг өгч, SDK гарын үсэг ба хадгалсан acsp.flowTypes-тэй тулгасны дараа л түүнд итгэнэ. validateAuth() цуцлалтыг сүлжээгээр шалгахгүй; hook-гүй checkRevocation() ч шалгалт хийхгүй.
8.4 Гарын үсгийн шалгалт — RAW_DIGEST_SIGNATURE
signature.value нь таны илгээсэн digest (аль хэдийн hash-лагдсан) дээрх гарын үсэг: RSA-PSS бол EMSA-PSS-VERIFY (salt = hash-ийн урт), PKCS#1 v1.5 бол DigestInfo тулгалт. Digest-ээ өөрийн хадгалснаар (хариунаас биш) шалгана. Гарын үсгийн гэрчилгээ, QUALIFIED түвшин, гинж — 8.1–8.2-ын дагуу.
Байгууллага, тамга, PKI мэдээлэл
Тамга (SEAL) ба иргэний PKI мэдээлэл (PKI_READ) нь оператор тусгайлан олгосон RP-д л ажиллана; эрхгүй бол 403. Иргэний хувийн мэдээлэл тул PKI_READ-ийн иргэний endpoint-ууд болон /organization/representations нь тухайн иргэн таны RP-ээр утас эсвэл USB токеноор нэвтэрсэн (сүүлийн 24 ц) үед л ажиллана — үгүй бол 403. Зөвхөн утсаар (RP-API session) нэвтэрсэн нь тооцогдоно — USB токеноор нэвтрэлт (§9.2) энэ цонхыг нээхгүй.
| Эрх | Endpoint | Юу |
|---|---|---|
| — | GET /organization/representations/etsi/{personEtsi} | Иргэний төлөөлөх эрхтэй ACTIVE байгууллагууд (orgEtsi, orgRegister, orgName, orgNameEn, role, roleLabel, rightType, rightTypeLabel, source, sourceLabel, claimedRole, validFrom/To) — onBehalfOf-оос өмнө (§10) |
SEAL:{orgEtsi} | POST /seal/{orgEtsi} | Байгууллагын цахим тамга (интерактив биш, шууд хариу) — зөвхөн хүнгүй, автомат системийн баримтад; хүн байгууллагын нэрээр зурахад onBehalfOf (§10). Body {"digest":"<base64>"} — hash-ийн төрлийг digest-ийн уртаар (32/48/64 байт = SHA-256/384/512) тодорхойлно; hashType (сонголт) SHA256 | SHA384 | SHA512 (SHA-256 хэлбэр ч болно). Буруу base64, digest-ийн урт, танихгүй эсвэл урттай таарахгүй hashType → 400 REQUEST_INVALID. Хариу {orgEtsi, signature, cert, sealedAt}. Тамга тохируулаагүй/цуцлагдсан → 409 |
SEAL:{orgEtsi} | POST /seal/certificate/{orgEtsi} | Тамганы гэрчилгээ гаргуулах/шинэчлэх → {orgEtsi, certificate, serialNumber, subjectDn, issuerDn, notBefore, notAfter, certificateLevel} |
| — | GET /seal/certificate/{orgEtsi} | Одоогийн тамганы гэрчилгээ (дээрхтэй ижил хэлбэр) |
PKI_READ | GET /person/summary/etsi/{personEtsi} | Нэр, гэрчилгээ/төхөөрөмжийн тоо, байгууллагын тоо, таны RP-тэй хийсэн үйлдлийн тоолол |
PKI_READ | GET /certificates/etsi/{personEtsi} | Иргэний гэрчилгээнүүд (counts, certificates[]). Мөр бүрт issuedAt (олгосон), REVOKED-д revokedAt, цуцлагдсан/солигдсон бол revocationReason (RFC 5280 нэр: keyCompromise, superseded, cessationOfOperation …). status өөрчлөгдөхгүй — сунгалт/дахин бүртгэлээр солигдсон мөр SUSPENDED + revocationReason: "superseded" (солигдсон мөчийн revokedAt-тэй). Зөвхөн USB токены (keyMedium: "TOKEN") мөрөнд: tokenSerial — гэрчилгээ олгогдсон токены серийн дугаар (§9.1-ийн tokenSerial) ба байгууллагын токенд organization (etsi, name). Нэг токены AUTH + SIGN-ийг tokenSerial-аар бүлэглэнэ (сунгалтаар солигдсон мөр ч өөрийн токены дугаартай); MOBILE/HSM мөрөнд эдгээр талбар байхгүй |
PKI_READ | GET /devices/etsi/{personEtsi} | Төхөөрөмжүүд (devices[], activeCount, total) |
PKI_READ | GET /rp/activity/etsi/{personEtsi} | Иргэний зөвхөн таны RP-тэй хийсэн нэвтрэлт/гарын үсгийн түүх — таны RP-ээр дамжсан USB токеноор нэвтрэлтийг (§9.2) оруулна |
PKI_READ | GET /person/children/etsi/{personEtsi} | Асран хамгаалж буй хүүхдүүд (зөвхөн унших) |
SDK (PKI_READ) — TS eid.person.summary(personEtsi) / certificates / devices / activity / children; Go c.Person.Summary / Certificates / Devices / Activity / Children.
Тамганы эрх нь SEAL:NTRMN-… (SEAL:{orgEtsi}) хэлбэртэй бөгөөд зөвхөн тэр байгууллагад хүчинтэй. Ганц SEAL эсвэл SEAL:* тамгалах эрх олгохгүй (403). Байгууллагын төлөөлөл холбох, гарын үсэг зурагч нэмэх/хасах, латин нэр засахыг иргэн eID Mongolia аппаараа хийнэ (RP-API-д байхгүй).
9.1 USB токены гэрчилгээ (TOKEN_ISSUE)
Иргэний USB токен (PKCS#11) дээр хоёр гэрчилгээ олгоно: нэвтрэх (AUTH — KeyUsage digitalSignature, EKU clientAuth + emailProtection) ба гарын үсгийн (SIGN — digitalSignature + nonRepudiation). Түвшин ADVANCED (qcStatements үгүй), хугацаа 1 жил. Иргэн олголтыг eID утсаараа (PIN2) нэг удаа батална. RP-д TOKEN_ISSUE эрх (оператор тусгайлан олгоно) хэрэгтэй — үгүй бол 403.
- Токен дээр хоёр EC P-256 түлхүүр (AUTH, SIGN — өөр өөр) үүсгэж, тус бүрээс CSR (PKCS#10,
ecdsa-with-SHA256, ≤ 4 KiB) гаргана. CSR-ийн subject/extension-ийг CA үл тооно: subject нь иргэний бүртгэлээс (SERIALNUMBER=PNOMN-…, GIVENNAME, SURNAME, C=MN). Байгууллага subject-д ордоггүй. POST /token/certificate— иргэний утас руу SIGN push явна.onBehalfOfзаавал биш: хоосон/ирээгүй бол хувийн токен;NTRMN-…бол иргэн тухайн байгууллагынACTIVEтөлөөлөгч байх ёстой (§10-тай ижил шалгалт). Танихгүй талбар → 400.tokenSerial[A-Za-z0-9-]{1,64},tokenLabel≤ 64 — бүртгэлд л, гэрчилгээнд ордоггүй.appTransactionзаавал биш — апп баталгаа (≤ 16 KiB). Ижил CSR хосыг sessionRUNNINGүед дахин илгээвэл ижил хариу.
Утасны зурах digest =POST /token/certificate { "relyingPartyUUID": "<rp uuid>", "relyingPartyName": "Таны үйлчилгээ", "signer": { "etsi": "PNOMN-12345678" }, "csrAuth": "<base64 PKCS#10 DER>", "csrSign": "<base64 PKCS#10 DER>", "tokenSerial": "EP2003-0042A1", "tokenLabel": "Миний токен", "onBehalfOf": "NTRMN-1234567", "flow": "notification", "appTransaction": "eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlF…" } → 200 { "sessionID": "…", "vc": { "type": "alphaNumeric5", "value": "04817" }, "csrAuthSha256": "<hex>", "csrSignSha256": "<hex>", "expiresAt": "…", "supersedes": [{ "tokenSerial": "EP2003-0001" }] }SHA-256(SHA-256(csrAuth DER) || SHA-256(csrSign DER)); VC-г өөрөө тооцож тулгаж болно:(SHA-256(digest)-ийн сүүлийн 3 байт, big-endian) mod 100000, 5 оронтой.- Төлөв —
GET /session/{sessionID}?timeoutMs=-ийн хариундtokenCertificate:{"status":"PENDING|ISSUED|FAILED|EXPIRED","errorCode"?,"authSerialHex"?,"signSerialHex"?}. GET /token/certificate/{sessionID}(SDK: TSeid.token.certificateResult(sessionID), Goc.Token.CertificateResult) — зөвхөн үүсгэсэн RP (бусад → 404), 24 цаг давтан авч болно. Олголт дуусаагүй бол ≤ 15 с хүлээнэ, эсвэл503 ISSUANCE_PENDING+Retry-After. Хоёр гэрчилгээг токенд бичнэ.{ "status": "ISSUED", "auth": { "certificate": "<base64 DER>", "certificatePem": "…", "serialHex": "3F9A…", "notBefore": "…", "notAfter": "…" }, "sign": { "certificate": "…", "certificatePem": "…", "serialHex": "7C01…", "notBefore": "…", "notAfter": "…" }, "chain": ["<04 base64 DER>", "<03>", "<02>"], "chainPem": "…", "organization": { "etsi": "NTRMN-1234567", "name": "…" }, "keyMedium": "TOKEN", "certificateLevel": "ADVANCED" }organization— хувийн токендnull.chainнь root-гүй (04 → 03 → 02).
Сунгалт: ижил tokenSerial дээрх олголт амжилттай болох үед л тэр токены өмнөх хоёр гэрчилгээ цуцлагдана (superseded); иргэний бусад токен, утасны гэрчилгээ хөндөгдөхгүй. Шинэ олголт бүрт шинэ түлхүүр — хуучин түлхүүрийн CSR → CSR_KEY_REUSED. Нэг tokenSerial нэг л эзэнтэй.
Сунгалт scope-ийг хадгална (discovery tokenCertificate.scopeLockedOnRenewal: true): ижил tokenSerial дээр иргэний ACTIVE бүртгэл байвал (гэрчилгээ нь хугацаа дууссан ч) олголт = сунгалт. Байгууллагын токен зөвхөн тэр байгууллагаар (onBehalfOf ижил), хувийн токен зөвхөн хувиараа сунгагдана. Хувийн ↔ байгууллага, эсвэл өөр onBehalfOf бол үргэлж 409 TOKEN_SCOPE_CHANGE — override байхгүй. Scope солих = иргэн eID апп-аас токеноо цуцлаад (POST /v3/mobile/tokens/{tokenSerial}/revoke, PIN1) тэр serial дээр шинээр олгуулна. Хэрэглэгчид: «Токены төрлийг (хувийн/байгууллага) солихын тулд eID апп-аас токеныг цуцлаад шинээр олгуулна уу».
dryRun — утас руу push явуулахаас өмнөх шалгалт (discovery tokenCertificate.dryRun: true): supersedes нь бодит эхлэлийн хариунд ирэх үед утас руу PIN2 push аль хэдийн явсан байдаг — иргэн desktop дээр цуцалсан ч утсаараа баталбал хуучин токен солигдоно. Тиймээс урсгал: CSR → dryRun → (supersedes байвал) анхааруулга, иргэн зөвшөөрөх → бодит эхлэл. Бие нь бодит эхлэлтэй ижил + "dryRun": true; CA бүх шалгалтыг хийж ижил алдааны кодыг буцаана (өөр CSR хостой хүлээгдэж буй олголт 409 TOKEN_ISSUANCE_PENDING ч). Амжилттай бол 200 {"dryRun": true, "supersedes"?: [{"tokenSerial"}]} — session, утас руу push үүсэхгүй. Хурдны хязгаар: (RP, иргэн) 20/мин ба RP нийт 120/10 мин (429 RATE_LIMITED + Retry-After); push-ийн 3/цаг хязгаарт тоологдохгүй. Үр дүн нь шалгасан мөчийнх — бодит эхлэл дахин шалгана. SDK: TS eid.token.previewCertificate(input), Go c.Token.PreviewCertificate(ctx, in) (TS 0.23.0+ / Go v0.10.0+).
Хувийн токены автомат солилт (discovery tokenCertificate.personalAutoSupersede: true): иргэний хувийн токены хязгаар хүрсэн үед өөр tokenSerial-тай шинэ хувийн токен 409 биш — эхлэлийн хариунд supersedes ([{"tokenSerial"}]) ирнэ. Хувийн токенд шинэ олголт амжилттай болбол (PIN2 + гэрчилгээ) өмнөх хувийн токены AUTH + SIGN superseded (4)-оор цуцлагдана; татгалзсан / хугацаа дууссан олголт өмнөхийг хөндөхгүй. Анхааруулгыг push-ээс өмнө харуулахын тулд dryRun-аар урьдчилж авна: «Таны өмнөх USB токен (EP2003-…)-ийн гэрчилгээ хүчингүй болно». Сунгалт, байгууллагын токен, хязгаарт багтсан олголтод талбар ирэхгүй. 409 TOKEN_LIMIT_REACHED kind: PERSONAL зөвхөн limit: 0 (хувийн олголт хаалттай) үед; байгууллагын токен хатуу хязгаартай хэвээр. SDK: TS st.supersedes, Go st.Supersedes (TS 0.23.0+ / Go v0.10.0+).
Апп баталгаа (appTransaction, заавал биш) (discovery tokenCertificate.appTransaction: "optional"): eID Desktop-ийн macOS App Store build нь StoreKit 2 AppTransaction-ийн JWS (compact) мөрийг дамжуулна — RP үүнийг өөрчлөхгүй POST /token/certificate-д дамжуулна. CA Apple-ийн сервер рүү дуудахгүй, offline шалгана: гинж Apple Root CA - G3, гарын үсэг, bundleId, орчин (App Store эсвэл TestFlight; Xcode-ийн локал build татгалзана), signedDate ≤ 30 хоног. Ирсэн бол заавал хүчинтэй — эс бөгөөс 400 APP_ATTESTATION_INVALID (аль алхам унасныг хэлэхгүй), утас руу push явахгүй. Ирээгүй (Windows) бол баталгаагүй олголт. Хэмжээ ≤ 16 KiB (бие бүхэлдээ ≤ 32 KiB). SDK: TS issueCertificate({ …, appTransaction }), APP_ATTESTATION_INVALID, MAX_APP_TRANSACTION_BYTES; Go eid.TokenCertInput{…, AppTransaction}, eid.CodeAppAttestationInvalid, eid.MaxAppTransactionBytes — 16 KiB-аас их бол SDK илгээхгүй (TS 0.23.0+ / Go v0.10.0+).
| HTTP | code | Хэзээ |
|---|---|---|
400 | REQUEST_INVALID · CSR_INVALID · CSR_SIGNATURE_INVALID · CSR_KEY_UNSUPPORTED · CSR_KEYS_IDENTICAL | Талбар буруу/танихгүй · CSR задрахгүй · proof-of-possession буруу · P-256/ecdsa-with-SHA256 биш · хоёр CSR ижил түлхүүртэй |
400 | APP_ATTESTATION_INVALID | appTransaction ирсэн боловч хүчингүй (апп баталгаа) — утас руу push явахгүй. Шалтгааныг хэлэхгүй |
403/404 | REPRESENTATION_* · ORG_NOT_ACTIVE · ORG_NOT_FOUND | Зөвхөн onBehalfOf ирсэн үед (§10.4). GET-д 403 REPRESENTATION_REVOKED — эхлэл ба батлалтын хооронд эрх хасагдсан |
409 | NO_ACTIVE_DEVICE · KYC_INCOMPLETE | Идэвхтэй eID утас / хүчинтэй гарын үсгийн гэрчилгээ алга · латин нэр бүртгэлгүй |
409 | CSR_KEY_REUSED | Энэ түлхүүрт гэрчилгээ аль хэдийн олгогдсон — токенд шинэ түлхүүр үүсгэнэ |
409 | TOKEN_ISSUANCE_PENDING | Энэ иргэнд өөр олголт хүлээгдэж байна (нэг зэрэг нэг) |
409 | TOKEN_LIMIT_REACHED | Токены хязгаар хүрсэн (PERSONAL нь зөвхөн limit: 0 үед — хязгаар хүрсэн хувийн олголт supersedes-тэй 200). Нэмэлт талбар kind (PERSONAL/ORGANIZATION), limit, organization (зөвхөн ORGANIZATION); limit: 0 = тухайн төрлийн олголт хаалттай. Өмнөх токеноо түүн дээрээ сунгах эсвэл цуцлуулна. GET-д ч гарч болно (гэрчилгээ гараагүй) |
409 | TOKEN_IN_USE | tokenSerial өөр иргэнд хүчинтэй гэрчилгээтэй (эзнийг хэлэхгүй) |
409 | ACCOUNT_DELETE_PENDING | Иргэн «Бүртгэл устгах» хүсэлт өгсөн (хүлээгдэж буй) — эхлэл ба dryRun утас руу push явуулахаас өмнө 409 буцаана; PIN2 батлалтын дараа үүссэн бол GET /token/certificate/{sessionID}-д (409) ба session-ий tokenCertificate.errorCode-д ирнэ (TS TOKEN_CONFLICT_CODES 0.20.0+, Go eid.CodeAccountDeletePending v0.7.0+) |
409 | TOKEN_SCOPE_CHANGE | Сунгалт scope солих гэсэн (хувийн ↔ байгууллага, эсвэл өөр onBehalfOf). Иргэн eID апп-аас токеноо цуцлаад шинээр олгуулна. GET-д ч гарч болно (гэрчилгээ гараагүй); session-ий tokenCertificate.errorCode-д ч ирнэ |
409 (GET) | SESSION_NOT_COMPLETE · SESSION_FAILED · ISSUANCE_FAILED | Иргэн батлаагүй · татгалзсан/хугацаа дууссан · CA талын алдаа (мессежид шалтгаан) |
410 (GET) | RESULT_EXPIRED | Олголтын хугацаа дууссан — шинээр эхлүүлнэ |
429 | RATE_LIMITED | (RP, иргэн) 3/цаг, RP 60/10 мин — Retry-After |
503 | ISSUANCE_PENDING (GET) · STATE_UNAVAILABLE | Олгогдож байна / түр — Retry-After-ийн дараа дахин |
9.2 USB токеноор нэвтрэх
Иргэн USB токен дээрх AUTH гэрчилгээгээр (§9.1) таны үйлчилгээнд утасгүйгээр нэвтэрнэ. Challenge-ийг таны backend авна. Иргэний компьютер дээрх клиент (PKCS#11) токеноор зурна. Гарын үсгийг backend тань CA-аар шалгуулна. RP-д AUTH эрх хэрэгтэй — үгүй бол 403.
POST /token/auth/challenge(SDK: TSeid.token.authChallenge(), Goc.Token.AuthChallenge) — body хоосон ({}) байж болно;relyingPartyUUID/relyingPartyNameнь зөвхөн мэдээллийн.curl -sS https://rp.eidmongolia.mn/token/auth/challenge \ -H 'Authorization: Bearer <RP_API_SECRET>' -H 'Content-Type: application/json' -d '{}' → 200 { "challengeId": "<uuid>", "challenge": "<base64 32 байт>", "rpUUID": "<36 тэмдэгт, жижиг үсэг>", "expiresAt": "…" } // 2 минут, нэг удаагийн- Токеноор зурах — зурах утга =
SHA-256("eID-TOKEN-AUTH" ‖ rpUUID ‖ challenge)(challengeнь base64-ээс тайлсан 32 байт). AUTH түлхүүрээрCKM_ECDSA; гарын үсэг нь түүхийr‖s(64 байт, DER биш). POST /token/auth/verify—challengeId(UUID),certificate(AUTH гэрчилгээ, base64 DER ≤ 8 KiB),signature(base64 64 байт) — гурвуулаа заавал.
Иргэнийгcurl -sS https://rp.eidmongolia.mn/token/auth/verify \ -H 'Authorization: Bearer <RP_API_SECRET>' -H 'Content-Type: application/json' \ -d '{"challengeId":"<challengeId>","certificate":"<base64 DER>","signature":"<base64 r||s>"}' → 200 { "result": { "endResult": "OK" }, "identity": { "etsi": "PNOMN-…", "givenName": "…", "surname": "…" }, "cert": { "serialHex": "…", "notAfter": "…" }, "organization": { "etsi": "NTRMN-…", "name": "…" } } // зөвхөн байгууллагын токендidentity.etsi-ээр таниа (гэрчилгээнийSERIALNUMBER). CA-ийнOK-д дангаар итгэхгүй: SDK 0.19.0+ / Go v0.6.0+eid.token.authVerify(challenge, certDer, rs)(Goc.Token.AuthVerify(ctx, ch, certDER, rs)) — challenge-ийг бүтнээр нь өгнө; CA руу илгээхээс өмнөrpUUID= таны RP,r‖sнь дээрх утга дээр гэрчилгээний түлхүүрээр, гэрчилгээ Root хүртэл гинж, 04 олгогч, KeyUsagedigitalSignature+ EKUclientAuth-аар хүчинтэйг шалгана.
| HTTP | code | Хэзээ |
|---|---|---|
400 | REQUEST_INVALID · CERT_INVALID · SIGNATURE_INVALID | Талбар буруу/танихгүй · гэрчилгээ задрахгүй / eID Personal CA-аас олгогдоогүй / EC P-256 биш / clientAuth биш / бүртгэлд алга эсвэл USB токены AUTH гэрчилгээ биш (утасны гэрчилгээ хүлээн авахгүй) · гарын үсэг таарахгүй |
403 | RP_REQUIRED · (кодгүй) | RP танигдаагүй · AUTH эрхгүй |
409 | CHALLENGE_USED · CERT_NOT_ACTIVE | Challenge аль хэдийн ашиглагдсан · гэрчилгээ идэвхгүй, цуцлагдсан эсвэл хугацаа дууссан |
410 | CHALLENGE_EXPIRED | 2 минут өнгөрсөн — шинэ challenge авна |
429 | RATE_LIMITED | (RP, IP) тутам минутад 120 (challenge + verify), 1 минутын түгжээ — Retry-After-ийн дараа |
501 · 503 | — · STATE_UNAVAILABLE | Энэ орчинд идэвхгүй · түр — дахин оролдоно |
9.3 USB токеноор PDF гарын үсэг
Токены SIGN гэрчилгээгээр (§9.1) PAdES-BASELINE-LT гарын үсэг — утас, session, CA-ийн PDF endpoint оролцохгүй; PDF иргэний компьютер / таны серверээс гарахгүй. Урсгал §5.2-тэй ижил, зөвхөн 4-р алхам локал:
- prepare — PAdES SDK-аар (§5.2.2, жишээ §5.2.3) токены SIGN гэрчилгээтэй:
prepare(pdf, {signerCert, fileName, claimedRole?, …})→dtbs(32 байт),state. - Токеноор зурах — клиент PKCS#11
C_Sign,CKM_ECDSA, оролт =dtbs(дахин hash хийхгүй) → түүхийr‖s(64 байт). SDK PKCS#11-д хүрэхгүй — таны апп. - complete —
complete(state, r‖s)→ гарын үсэгтэй PDF (TSA, OCSP/CRL, DSS, өөрийн шалгалт). - Бүртгэл — RP backend
POST /pdf/signatures(§5.2.6),"medium": "token",sessionID-гүй. CA гэрчилгээ нь идэвхтэй токены гэрчилгээ эсэх, байгууллагын токенд үүрэг (claimedRole) тухайн байгууллагынх эсэхийг шалгана.SIGNэрх.
Байгууллагын токенд claimedRole-ийг §5.2.7-ийн дагуу төлөөллийн хариунаас авна — токенд session байхгүй тул төлөөллийг бүртгэлийн мөчид шалгана.
Байгууллагын нэрийн өмнөөс гарын үсэг (onBehalfOf)
Иргэн өөрийн PIN2 (гарын үсгийн) гэрчилгээгээр гарын үсэг зурж, тэр мөчид тухайн байгууллагыг төлөөлөх эрхтэй эсэхийг eID Mongolia өөрийн бүртгэлээр шалгана (Эстонийн Smart-ID загвар). Хүсэлтэд "onBehalfOf": "NTRMN-…" нэмэхэд л хангалттай — байгууллагын гэрчилгээ, тамга шаардлагагүй. Гэрээ, албан бичиг, тушаал зэрэг хүн байгууллагын нэрээр зурдаг баримтад энэ урсгалыг ашиглана.
10.1 Урьдчилсан нөхцөл
| Нөхцөл | Хэрхэн бүрдэх вэ |
|---|---|
Байгууллага eID-д бүртгэлтэй, ACTIVE | Гүйцэтгэх захирал eID Mongolia аппын «Миний байгууллагууд»-аас байгууллагаа ХУР (улсын бүртгэл)-аар холбоно — ХУР-ын гүйцэтгэх удирдлагад (generalR) иргэний РД бүртгэлтэй байвал байгууллага ACTIVE-аар үүсч, холбосон иргэн ADMIN болно. Оператор ч бүртгэж болно. |
Гарын үсэг зурагч тухайн байгууллагын ADMIN эсвэл MANAGER төлөөлөгч | ADMIN аппаасаа өөр иргэнийг РД-ээр нь (албан тушаалтай нь) MANAGER болгон нэмнэ. Нэмэгдсэн иргэн өөрийн утсандаа PIN2-оор баталгаажуулсны дараа л ACTIVE болно (тэр болтол REPRESENTATION_PENDING). Оператор ч нэмж болно. |
| Төлөөллийн хугацаа хүчинтэй | validFrom ≤ одоо < validTo (validTo хоосон = хугацаагүй) |
| Таны RP-ийн эрх | SIGN (PDF ба digest гарын үсэг). Төлөөллийн жагсаалт уншихад иргэн таны RP-ээр утас эсвэл USB токеноор нэвтэрсэн (сүүлийн 24 ц) байна. |
Байгууллага холбох, төлөөлөгч нэмэх/хасахыг иргэн апп-аараа, эсвэл оператор хийнэ — RP-API-д эдгээр үйлдэл байхгүй (§9). ADMIN ба MANAGER аль аль нь дангаараа байгууллагын нэрийн өмнөөс зурж чадна.
10.2 Урсгал
- Нэвтрүүлэх — иргэнийг ердийн аргаар нэвтрүүлж (§4, §8)
PNOMN-…-ийг нь мэднэ. - Төлөөлж чадах байгууллагуудыг авах — зөвхөн
ACTIVEбайгууллага, хүчинтэй, баталгаажсан төлөөлөл буцна:curl https://rp.eidmongolia.mn/organization/representations/etsi/PNOMN-12345678 \ -H "Authorization: Bearer $RP_SECRET" # 200 { "personEtsi": "PNOMN-12345678", "representations": [ { "orgEtsi": "NTRMN-1234567", "orgRegister": "1234567", "orgName": "Жишээ ХХК", "orgNameEn": "Jishee LLC", "role": "ceo", "roleLabel": "Гүйцэтгэх захирал", "rightType": "ADMIN", "rightTypeLabel": "Админ", "source": "REGISTRY", "sourceLabel": "Улсын бүртгэл", "claimedRole": "Гүйцэтгэх захирал, Жишээ ХХК (NTRMN-1234567)", "validFrom": "2026-10-01T00:00:00Z" } ] }role— машин утга (ХУР-аас холбогдсондceo; өмнө холбогдсон мөрөндfounder/stakeholderүлдэж болно;MANAGER-т ADMIN-ий бичсэн текст); өөрөө орчуулахгүй. ХүндroleLabel-ийг харуулна (?lang=mn|en→Accept-Language→ mn):ceo→ «Гүйцэтгэх захирал»,founder→ «Үүсгэн байгуулагч»,stakeholder→ «Төлөөлөн удирдах зөвлөлийн гишүүн», хоосон → «Гарын үсэг зурах эрхтэй ажилтан»,MANUAL→ оруулсан текст. МөнrightTypeLabel(ADMIN→ «Админ»,MANAGER→ «Гарын үсэг зурагч») баsourceLabel(REGISTRY→ «Улсын бүртгэл»,MANUAL→ «Захирал нэмсэн»), ижил хэлээр. Ингэснээр апп, desktop, таны үйлчилгээнд ижил нэр гарна.source:REGISTRY(ХУР) |MANUAL.validToзөвхөн хугацаатай үед.claimedRole— PDF-ийн CMS-д бичигдэх мэдүүлсэн үүрэг (≤ 900 тэмдэгт); PDF-д гарын үсэг зурахдаа яг хэвээр PAdES SDK-ийнprepare-д өгнө (§5.2.7).claimedRoleнь хүсэлтийн хэлээр гарна;POST /pdf/signaturesmn ба en хоёуланг хүлээн авна —?lang=en-ээр авсныг ч шууд ашиглаж болно. - Иргэн сонгоно — хувиараа эсвэл аль нэг байгууллагаар. Сонголтыг та хадгалж болно, гэхдээ энэ нь итгэх эх сурвалж биш — сервер гарын үсэг бүрд дахин шалгана.
- PDF гарын үсэг — §5.2-ын урсгал, нэмэлт нь хоёр: сонгосон мөрийн
claimedRole-ийг PAdES SDK-ийнprepare-д,"onBehalfOf": "NTRMN-1234567"-ийг hash session-ий body-д (§5.2.7).// PAdES SDK (Go) p, err := pades.Prepare(pdfBytes, pades.PrepareOptions{ SignerCert: certDER, FileName: "Гэрээ №12.pdf", ClaimedRole: claimedRole, // representations[].claimedRole: "Гүйцэтгэх захирал, Жишээ ХХК (NTRMN-1234567)" Reason: "Жишээ ХХК-ийг төлөөлж", }) # hash session — §5.2.5-ын body + onBehalfOf POST /signature/notification/linked/{documentNumber} { …, "signatureProtocolParameters": { "digest": "<dtbs base64>", "signatureAlgorithm": "sha256WithRSAEncryption", … }, "fileName": "Гэрээ №12.pdf", "onBehalfOf": "NTRMN-1234567" }Төлөөлөл session үүсэхэд шалгагдана — эрхгүй бол session үүсэхгүй, утас руу push очихгүй (§10.4). Иргэний батлах дэлгэцэд «Та Жишээ ХХК-ийг төлөөлөн гарын үсэг зурж байна» гарна.
- Үр дүн —
GET /session/{sessionID};COMPLETE/OKүедonBehalfOfблок (orgEtsi,orgName,role,roleLabel,rightType,rightTypeLabel) баsignature.valueирнэ.onBehalfOf.orgEtsi= таны хүссэн байгууллага гэдгийг заавал шалгана;rightTypeхоосон ирвэл тэр мөчид эрх баталгаажаагүй (хасагдсан эсвэл түр алдаа) —/organization/representations-аар дахин шалгана.roleньMANAGER-т хоосон байж болно. - complete ба бүртгэл —
complete-ийн дарааPOST /pdf/signatures(§5.2.6). CA тэр мөчийн төлөөллөөр үүргийг дахин тооцож CMS-ийнхтэй тулгана: эрх хасагдсан бол403 REPRESENTATION_REVOKED, үүрэг зөрвөл409 CLAIMED_ROLE_MISMATCH— файл хүчинтэй гарын үсэгтэй ч/verify-д бүртгэгдэхгүй.
Digest гарын үсэг (контейнерээ өөрөө угсардаг RP): §5.1-ийн POST /signature/notification/etsi/{id} (эсвэл …/document/…, …/device-link/…) body-д "onBehalfOf": "NTRMN-1234567" нэмнэ — шалгалт, утасны дэлгэц, session-ий onBehalfOf блок ижил. Гэхдээ CMS/PDF-ийг та угсардаг тул мэдүүлсэн үүргийг (§10.3) өөрөө нэмнэ; шалгалт зөвхөн session үүсгэх үед хийгдэнэ (дуусах үед onBehalfOf.rightType хоосон ирвэл эрх баталгаажаагүй). Нэвтрэлтийн хүсэлт ч onBehalfOf хүлээн авна — иргэн байгууллагыг төлөөлж нэвтэрнэ.
10.3 PDF/CMS-д юу орох вэ, шалгагч юу харах вэ
| Хаана | Утга |
|---|---|
CMS signed attribute signer-attributes-v2 (0.4.0.19122.1.1), claimedAttributes id-at-role (2.5.4.72) UTF8String | <албан тушаал>, <байгууллагын нэр> (NTRMN-<бүртгэл>). Албан тушаал: ceo → «Гүйцэтгэх захирал», founder → «Үүсгэн байгуулагч», stakeholder → «Төлөөлөн удирдах зөвлөлийн гишүүн», хоосон role → «Гарын үсэг зурах эрхтэй ажилтан», бусад нь бүртгэлийн текст (үргэлж монголоор). EU DSS «Claimed role»-оор харуулна. |
PDF /Reason | prepare-ийн reason-оор та өгнө (санал: <байгууллагын нэр>-ийг төлөөлж; автоматаар бичигдэхгүй) — Adobe Reader «Reason» |
PDF /Name, гэрчилгээний subject | Иргэний нэр — хувь хүн, өөрчлөгдөхгүй |
GET https://ca.eidmongolia.mn/v3/verify/doc/{docID|sessionID} ба /verify/{id} хуудас | signatures[]: on_behalf_of_org, on_behalf_of_org_name, on_behalf_of_org_register, claimed_role — хуудсанд «Жишээ ХХК-ийг төлөөлж», «Мэдүүлсэн үүрэг» |
POST https://ca.eidmongolia.mn/v3/validate/pdf | signatures[].claimed_roles (CMS-ээс — дурын PDF-д), reason; eID-ийн бүртгэлтэй гарын үсэгт on_behalf_of_org, on_behalf_of_org_register |
Иргэний түүх (GET /rp/activity/etsi/{personEtsi}, апп) | AUTH/SIGN мөрөнд onBehalfOfName (байгууллагын нэр); хувийн session-д байхгүй |
curl https://ca.eidmongolia.mn/v3/verify/doc/$DOC_ID
{
"doc_id": "…", "file_name": "Гэрээ №12.pdf", "created_at": "…",
"signatures": [
{ "session_id": "…", "signer_name": "Дорж Бат",
"signed_at": "…", "cert_serial": "…", "chain": ["…"],
"on_behalf_of_org": "NTRMN-1234567",
"on_behalf_of_org_name": "Жишээ ХХК",
"on_behalf_of_org_register": "1234567",
"claimed_role": "Гүйцэтгэх захирал, Жишээ ХХК (NTRMN-1234567)" }
]
}
CMS-ийн мэдүүлсэн үүрэг нь гарын үсгээр хамгаалагдсан мэдүүлэг — гадны шалгагч «claimed» гэж харуулна. «Тэр мөчид төлөөлөх эрхтэй байсан» гэсэн eID-ийн баталгаа нь /verify-ийн бүртгэл (on_behalf_of_org).
10.4 Алдаа
Хариу {"error":"<мессеж>","code":"<CODE>"} — салааллыг code-оор хийнэ. Гарын үсгийн (digest, PDF-ийн hash) ба нэвтрэлтийн session үүсгэх үед татгалзвал session үүсэхгүй, push очихгүй.
| HTTP | code | Хэзээ | Та юу хийх |
|---|---|---|---|
403 | REPRESENTATION_DENIED | Зурагч тухайн байгууллагын идэвхтэй төлөөлөгч биш (хасагдсан, татгалзсан, огт байхгүй) | Хувиараа зурахыг санал болгох, эсвэл ADMIN-аар нэмүүлэх |
403 | REPRESENTATION_PENDING | ADMIN нэмсэн ч иргэн PIN2-оор баталгаажуулаагүй | Иргэн аппаараа баталгаажуулна |
403 | REPRESENTATION_EXPIRED | Төлөөллийн хугацаа эхлээгүй / дууссан | ADMIN эсвэл оператороор сунгуулна |
403 | ORG_NOT_ACTIVE | Байгууллага ACTIVE биш (хүлээгдэж буй, түдгэлзсэн, цуцлагдсан) | Оператортой холбогдоно |
403 | SIGNER_UNIDENTIFIED | Зурагч тодорхойгүй (иргэн тодорхойгүй QR session-д onBehalfOf) | Иргэнийг etsi/documentNumber-аар эхлүүлнэ |
404 | ORG_NOT_FOUND | onBehalfOf eID-д бүртгэлгүй | Байгууллагыг аппаар холбуулна (§10.1) |
403 (POST /pdf/signatures) | REPRESENTATION_REVOKED | Session-оос хойш, бүртгэлээс өмнө эрх хасагдсан, эсвэл байгууллага идэвхгүй/бүртгэлгүй болсон (бүртгэлийн үед төлөөллийн бүх татгалзал энэ кодоор); файл гарын үсэгтэй ч /verify-д бүртгэгдээгүй | Эрх сэргэсэн бол шинээр зуруулна |
409 (POST /pdf/signatures) | CLAIMED_ROLE_MISMATCH | CMS-ийн мэдүүлсэн үүрэг тэр мөчийн төлөөлөл эсвэл session-ий onBehalfOf-оос зөрсөн | claimedRole-ийг төлөөллийн хариунаас (mn) яг хэвээр авна (§5.2.7) |
400 | (кодгүй) | Гэрчилгээ сонгуулах (certificate-choice) хүсэлтэд onBehalfOf | Зөвхөн нэвтрэлт, гарын үсгийн хүсэлтэд өгнө |
PDF-ийн бусад алдаа: бүртгэл §5.2.6, PAdES SDK §5.2.4.
10.5 Олон талт гэрээ
Хоёр байгууллага нэг гэрээнд дараалан зурна — гарын үсэг бүр өөрийн мэдүүлсэн үүрэг, /Reason-той:
- А байгууллага — А-ийн төлөөлөгчийн гэрчилгээ,
claimedRole(NTRMN-A-ийн мөр) →prepare→ hash session (onBehalfOf=NTRMN-A) →complete→ бүртгэл (revIndex: 1). СангийнdocId=D. - Б байгууллага — 1-р алхмын гаралтыг өөрчлөлтгүй
prepare-д (Б-ийн төлөөлөгчийн гэрчилгээ,NTRMN-B-ийнclaimedRole) — санD-г баримтаас уншина → hash session (onBehalfOf=NTRMN-B) →complete→ бүртгэл (docID: D,revIndex: 2,priorCmsSha256= А-ийн CMS-ийн hash). - Шалгах — эцсийн PDF-д 2 гарын үсэг;
GET https://ca.eidmongolia.mn/v3/verify/doc/Dхоёуланг нь байгууллагатай нь харуулна.
Хувь хүн ба байгууллага холилдож болно (onBehalfOf-гүй хүсэлт = хувийн гарын үсэг). Нэг байгууллагаас хэдэн хүн зурах ёстойг (хамтын эрх) та өөрийн бизнесийн дүрмээр шийднэ — eID зөвхөн «энэ хүн тэр мөчид төлөөлөх эрхтэй эсэх»-ийг шалгана.
10.6 onBehalfOf юу биш вэ
- Байгууллагын гэрчилгээ, тамга биш — хувь хүний квалификацтай гарын үсэг (гэрчилгээний subject = иргэн). Байгууллагад тусгай гэрчилгээ гаргахгүй.
- e-Seal (цахим тамга, §9) нь зөвхөн хүнгүй, автомат системийн баримтад (системийн гаргадаг лавлагаа, баримт г.м.) — хүн байгууллагын нэрээр зурахад биш.
- Төлөөллийг eID гарын үсэг зурах мөчид (hash session үүсэхэд ба PDF-ийн бүртгэлд) өөрийн бүртгэлээр шалгана; ХУР-ыг тэр мөчид дахин асуудаггүй. Дараа нь эрх хасагдсан ч өмнөх гарын үсэг хүчинтэй хэвээр.
- ADMIN/MANAGER ялгаа, хамтын гарын үсгийн шаардлагыг eID хэрэгжүүлэхгүй — RP-ийн бизнесийн дүрэм.
Ойлгомжтой алдааны кодууд.
Дэлгэрэнгүй
API-ийн алдааны JSON хариунд {"error":"…"} байна; тогтвортой алдааны кодтой хариу нь {"error":"…","code":"…"}. Мессеж нь хэрэглэгчид тайлбарлах зориулалттай; code байгаа бол програмын салааллыг түүнээр, үгүй бол HTTP төлөвөөр шийднэ. Proxy-ийн алдаа JSON биш байж болно. HTTP код:
| Код | Утга |
|---|---|
400 | Буруу хүсэлт: JSON задрахгүй, digest/rpChallenge-ийн урт, base64, hash/алгоритм, protocol буруу; RP-ийн бүртгэл хүчингүй; PDF бүртгэлийн REQUEST_INVALID (§5.2.6); CALLBACK_HOST_INVALID — өргөдлийн callbackHosts буруу. SUBJECT_NOT_ASCII — оператор таны mTLS CSR-ийг гарын үсэг зурахад татгалзана (subject-д кирилл г.м. ASCII бус утга; RP-API-аас ирэхгүй) (§3 mTLS). PDF бүртгэлийн CMS_INVALID, SIGNATURE_INVALID, TIMESTAMP_INVALID. Мессежид шалтгаан бий. |
401 | Authorization: Bearer алга (зөвхөн cert-тэй ч); secret буруу/хүчингүй; mTLS заавал RP cert-гүй эсвэл cert тухайн RP-ийнх биш/цуцлагдсан |
403 | IP allowlist-ийн гадна; RP-д энэ үйлдлийн эрх алга (AUTH, SIGN, CERT, SEAL, PKI_READ, TOKEN_ISSUE, QR_PRESENT); PDF бүртгэл/токены TOKEN_ORG_MISMATCH; onBehalfOf-д REPRESENTATION_DENIED/_PENDING/_EXPIRED/_REVOKED, ORG_NOT_ACTIVE, SIGNER_UNIDENTIFIED (§10.4); PKI_READ / organization/representations-д иргэн сүүлийн 24 цагт таны RP-ээр нэвтрээгүй |
404 | Session олдсонгүй (эсвэл өөр RP-ийнх); иргэн/байгууллага олдсонгүй (onBehalfOf-д ORG_NOT_FOUND); PDF бүртгэлийн DOC_NOT_FOUND; «Миний QR»-ийн QR_TOKEN_INVALID (§4.5) |
409 | NO_SIGNING_CERTIFICATE — гарын үсгийн (SIGNATURE) болон гэрчилгээ сонгуулах (CERTIFICATE_CHOICE) session эхлэхэд иргэнд идэвхтэй төхөөрөмж + хүчинтэй SIGN гэрчилгээ алга (танигдаагүй, хүүхэд, гэрчилгээгүй — бүгд нэг хариу, бүртгэлтэй эсэхийг задлахгүй). CERT_CHOICE_INVALID — linked linkedSessionID хүчингүй. |
409 | Төлөв зөрчилтэй: тамга тохируулаагүй/цуцлагдсан; гарын үсгийн гэрчилгээгүй; USB токены CSR_KEY_REUSED, TOKEN_LIMIT_REACHED, TOKEN_IN_USE, TOKEN_SCOPE_CHANGE, ACCOUNT_DELETE_PENDING г.м. (§9.1); PDF бүртгэлийн SIGNER_MISMATCH, SIGNER_CERT_INVALID, TOKEN_CERT_INVALID, SESSION_MISMATCH, SESSION_NOT_COMPLETE, SESSION_FAILED, DOC_ID_CONFLICT, REGISTRY_CONFLICT, PRIOR_MISMATCH, CLAIMED_ROLE_MISMATCH (§5.2.6) |
410 | RESULT_EXPIRED — токены гэрчилгээний үр дүнгийн хугацаа дууссан, шинээр эхлүүлнэ (§9.1); QR_PRESENTATION_GONE — «Миний QR»-ийн үр дүн аль хэдийн уншигдсан эсвэл хугацаа дууссан, шинэ QR-ээр дахин эхэлнэ (§4.5) |
413 | Хэт том: /v3/validate PDF ≤ 10 MiB (too_large); POST /pdf/signatures, /token/certificate-ийн body → REQUEST_TOO_LARGE; POST /share-д FILE_TOO_LARGE (§5.4) |
415 / 422 / 502 | POST /share: FILE_TYPE_NOT_SUPPORTED · FILE_NOT_SIGNED (+reason) · MAIL_FAILED (+recipients) — §5.4 |
422 | /v3/validate: not_pdf, encrypted, no_signature, too_many_signatures, unprocessable, not_certificate (§5.2.12) |
429 | Хязгаар — RATE_LIMITED (/pdf/signatures, /token/*, POST /share-ийн квот), /v3/validate — Retry-After header-ийг хүндэтгэнэ |
499 | Long-poll дуусахаас өмнө та холболтоо тасалсан |
503 | Proxy-ийн хурдны хязгаар эсвэл бүртгэлийн үйлчилгээ түр хүрэхгүй — түр хүлээгээд дахин; STATE_UNAVAILABLE (PDF бүртгэл, токен, linked гарын үсэг — Retry-After: 5-ийн дараа ижил body); /v3/validate/pdf-д busy; /v3/rp-applications-д rp_applications_maintenance; POST /share-д MAIL_UNAVAILABLE / VALIDATOR_* (§5.4); ISSUANCE_PENDING — токены гэрчилгээ олгогдож байна, Retry-After-ийн дараа |
500 | Дотоод алдаа — eIDsupport@gerege.com-д хүсэлтийн цаг, sessionID-тэй хамт мэдэгдэнэ |
rp.eidmongolia.mn
Бүх хүсэлтэд Authorization: Bearer rp_sk_… ба IP allowlist шаардлагатай.
Бүрэн лавлах
RP-API: https://rp.eidmongolia.mn (Bearer заавал; mTLS заавал RP-д client cert нэмэлтээр, мөн энд), Authorization: Bearer. «Session, бүртгэл, нийтийн» хэсгийн нийтийн замууд https://ca.eidmongolia.mn/v3-аас. {id} = ETSI / регистр / иргэний дугаар.
| Endpoint | Зориулалт |
|---|---|
| Нэвтрэлт | |
POST /authentication/notification/etsi/{id} | Push — иргэн тодорхой |
POST /authentication/notification/document/{documentNumber} | Push — төхөөрөмжөөр |
POST /authentication/device-link/anonymous | QR / App2App — иргэн тодорхойгүй |
POST /authentication/device-link/etsi/{id} · …/document/{documentNumber} | QR / App2App — иргэн тодорхой |
POST /authentication/qr | «Миний QR» — иргэний «Миний мэдээлэл»-ийг PIN2 гарын үсэгтэй авах (QR_PRESENT; дүн GET /session/{id}-ийн presentation {jws}, §4.5) |
| Гарын үсэг | |
POST /signature/notification/etsi/{id} · …/document/{documentNumber} | Push — digest-д гарын үсэг |
POST /signature/notification/linked/{documentNumber} | Нэвтрэлтийн session-тэй холбоотой гарын үсэг (linkedSessionID) |
POST /signature/device-link/etsi/{id} · …/document/{documentNumber} | QR / App2App — гарын үсэг |
POST /share | Гарын үсэгтэй файлыг имэйлээр илгээх (multipart; SIGN) — §5.4 |
POST /pdf/signatures | PDF гарын үсгийн бүртгэл (CMS + hash, PDF-гүй; SIGN) — §5.2.6. PDF-ийг PAdES SDK бэлтгэнэ (§5.2) |
POST /signature/certificate/{documentNumber} | Иргэний гэрчилгээ авах (session; CERT) |
POST /signature/certificate-choice/device-link/anonymous · …/notification/etsi/{id} | Иргэн аппаараа гэрчилгээгээ сонгох (CERT) |
| Байгууллага, тамга, PKI мэдээлэл (9) | |
GET /organization/representations/etsi/{personEtsi} | Иргэний төлөөлөх байгууллагууд |
POST /seal/{orgEtsi} | Цахим тамга (SEAL) |
POST /seal/certificate/{orgEtsi} · GET … | Тамганы гэрчилгээ гаргуулах (SEAL) · унших |
POST /token/auth/challenge · POST /token/auth/verify | USB токеноор нэвтрэх (AUTH, §9.2) |
POST /token/certificate · GET /token/certificate/{sessionID} | USB токены AUTH + SIGN гэрчилгээ (TOKEN_ISSUE, §9.1) |
GET /person/summary/etsi/{personEtsi} · /certificates/etsi/… · /devices/etsi/… · /rp/activity/etsi/… · /person/children/etsi/… | Иргэний PKI мэдээлэл (PKI_READ) |
| Session, бүртгэл, нийтийн | |
GET /session/{sessionID}?timeoutMs= | Төлөв, үр дүн (long-poll, ≤ 120000) |
POST https://ca.eidmongolia.mn/v3/rp-applications · GET …/{id} | RP-ийн өргөдөл, төлөв — түр зогссон (discovery registration.paused): 503 rp_applications_maintenance; хүсэлтийг registration.contact руу (§2) |
GET https://ca.eidmongolia.mn/v3/verify/{sessionID} | Гарын үсгийн баталгаажуулалт JSON-оор (auth-гүй). PDF-ийн QR нь HTML хуудас https://ca.eidmongolia.mn/verify/{docID} (харагдах гарын үсгийнх …/verify/{docID}/{rev}; хуучин баримтад {sessionID}) руу заана (нийтийн, auth-гүй) |
GET https://ca.eidmongolia.mn/v3/verify/doc/{docID|sessionID} | PDF баримтын бүх гарын үсэг бүртгэлээс (auth-гүй) — on_behalf_of_org, claimed_role (§10.3) |
POST https://ca.eidmongolia.mn/v3/validate/pdf · …/validate/certificate | Дурын гарын үсэгтэй PDF / гэрчилгээг шалгах (auth-гүй, /verify хуудас ашигладаг) |
GET https://ca.eidmongolia.mn/.well-known/eid | Discovery |
GET https://ca.eidmongolia.mn/actuator/health | Үйлчилгээ амьд эсэх (200) |
| PKI (auth-гүй, HTTP ба HTTPS) | |
POST http://ocsp.eidmongolia.mn/ocsp · GET …/ocsp/{base64} | OCSP (RFC 6960) |
GET http://crl.eidmongolia.mn/crl · http://crl.eidmongolia.mn/crl/org · http://crl.eidmongolia.mn/crl/ca | CRL: иргэн · байгууллага/тамга · завсрын CA |
GET http://crl.eidmongolia.mn/ca/gerege-issuing-ca.cer | Gerege Issuing CA (завсрын гэрчилгээ) |
GET http://crl.eidmongolia.mn/ca/eid-mongolia-issuing-ca.cer · …/ca/eid-mongolia-org-issuing-ca.cer | eID Mongolia Issuing CA (иргэн) · Organization Issuing CA (байгууллага, тамга) |
POST https://tsa.timeserver.mn/ | RFC 3161 цагийн тэмдэг (TSA) — PAdES complete (discovery endpoints.tsa, §3) |
Аюулгүй байдал нь бэлэн суусан.
Дэлгэрэнгүй
TypeScript / Node.js (≥ 18) — @gerege-systems/eid-mongolia-sdk: session үүсгэх, poll хийх, ResponseValidator-аар гэрчилгээний гинж, хугацаа, түвшин болон гарын үсгийг шалгана. Revocation болон урсгалын нэмэлт шалгалтыг RP тохируулна (§8). Үндсэн entry (EidClient, RP secret) зөвхөн backend-д; хөтөчид /button (§4.1) ба /pades (§5.2) — secret-гүй. Эх код: github.com/gerege-systems/eid-mongolia-sdk (MIT).
npm install @gerege-systems/eid-mongolia-sdk@^0.38.1
Go (≥ 1.25) — github.com/gerege-systems/eid-mongolia-sdk-go: TypeScript хувилбартай функциональ ижил — багц eid (RP-API, device link, validator, PDF) eidlogin («eID-ээр нэвтрэх» товчны net/http handler) ба pades (клиент талын PAdES reference + Verify). eid, eidlogin нь golang.org/x/crypto-оос бусад гадны хамааралгүй; pades PDF/CMS сангууд (digitorus г.м.) татна.
go get github.com/gerege-systems/eid-mongolia-sdk-go@v0.21.0
// go.mod — pades ашиглавал (Go replace-ийг дамжуулдаггүй):
replace github.com/digitorus/pdf => github.com/craftzbay/pdf v0.1.3-eid.2
0.6.1+ ашиглана: зурагдсан initialCallbackUrl-ийг шалгах засвар багтсан. baseUrl анхдагчаар https://rp.eidmongolia.mn (/v3-гүй).
0.38.1+ (зөвхөн TS): <eid-login-button>-ийн бичвэр «e-ID» — «e-ID-ээр нэвтрэх», "Log in with e-ID" (7 хэлэнд; товч, цонхны гарчиг, холболтын алдаа). Аппын нэр «eID Mongolia» хэвээр.
0.38.0+ (зөвхөн TS): <eid-login-button>-ийн лого нь шинэ eID тэмдэг (дөрвөлжин inline SVG, light/dark хувилбар theme-ийг дагана) — товч ба QR цонхны толгойд; тэмдэгт үсэг байхгүй тул бичвэр бүтэн харагдана. --eid-logo / --eid-logo-dark deprecated (алдаагүй үл хэрэгжинэ); part="icon", icon="eid|none", variant өөрчлөгдөөгүй. ::part(icon)-оор лого сольж байсан бол тэр CSS-ийг хасна уу.
0.37.0+ (Go v0.21.0; .NET 0.13.0): «Миний QR» (§4.5) — TS eid.qr.parse / start / result / check / verify, parseQr, verifyQrPresentation, QR_PREFIX; Go c.QR.Parse / Start / Result / Check / Verify, eid.ParseQR, eid.VerifyQRPresentation; .NET EidQr.Parse / VerifyAsync, EidQrClient. POST /authentication/qr ({qr, relyingPartyUUID, relyingPartyName, nonce}), иргэний PS256 JWS-ийг x5c гинж + OCSP-оор шалгана; алдаа QrError / *eid.QRError + ErrQR* / EidQrException.Code. Гурван SDK CA-ийн ижил test vector давдаг.
0.36.0+ (зөвхөн TS): <eid-login-button> 7 хэлтэй — lang = mn | en | ru | zh | fr | es | ar (ar → RTL цонх); EID_LOGIN_LANGS, resolveEidLang экспорт. button.js-ийн байт өөрчлөгдсөн тул өөрийн домэйнд байршуулсан бол шинэчилж, CSP hash-аа дахин тооцно; shadow CSS хэвээр (§4.1).
0.35.0+ (Go v0.20.0; Swift, Android 0.10.0; .NET 0.12.0): гарын үсгийн зураг / байгууллагын тамганы зургийн API (appearance.signatureImage / stampImage, person.signatureImage, Representation.stampImage, бүртгэлийн images, IMAGE_MISMATCH) хасагдсан; харагдах гарын үсэг нь QR тамга (220 × 64) л.
0.32.0 (Go v0.17.0): гарын үсгийн зургийн RP туслах (person.signatureImage, Representation.stampImage) — түүх: 2026-10-24-нд зургийн боломжийн хамт хасагдсан.
0.31.0 (Go v0.16.0; Swift, Android, .NET 0.9.0): харагдах гарын үсгийн тэмдэгт гарын үсгийн зураг / байгууллагын тамга — түүх: 2026-10-24-нд бүрэн хасагдсан; харагдах гарын үсэг нь QR тамга (220 × 64) хэвээр.
0.30.1+ (Go v0.15.2; .NET 0.8.1; Swift, Android — нөлөөгүй): PDF parser эвдэрсэн эсвэл зориуд бүтээсэн файл (хаагдаагүй массив/мөр, ObjStm /N хэтэрсэн, /Extends гогцоо) дээр гацаж CPU/санах ой зарцуулдаг байсныг засав — одоо PDF_INVALID. API өөрчлөгдөөгүй. Go: pades-ийг импортолдог бол өөрийн go.mod-д replace github.com/digitorus/pdf => github.com/craftzbay/pdf v0.1.3-eid.2 заавал (eid/eidlogin-д хэрэггүй).
0.30.0+ (Go v0.15.0; Swift, Android, .NET 0.8.0): verifyPagePosition: "first" — баталгаажуулалтын хуудсыг баримтын эхэнд (1-р хуудас) нэмнэ; хуудасны дугаар (appearance.page, suggestPlacement) оролтын PDF-ийнх хэвээр — §5.2.8. Анхдагч "last" өмнөхтэй байт-ижил. Golden pades-golden-v9.
0.29.0+ (Go v0.14.0; Swift, Android, .NET 0.7.0): иргэн тэмдгийг өөрөө байрлуулах — appearance.exact (зөөхгүй; status requested / overlap) ба suggestPlacement(pdf, appearance) (гарын үсэггүйгээр анхдагч байрлал, хуудас бүрийн хэмжээ) — § иргэн өөрөө байрлуулах. exact-гүй бол өмнөхтэй байт-ижил. Golden pades-golden-v7.
0.28.0+ (Go v0.13.1 — зөвхөн баримт): <eid-login-button> «e-ID» wordmark лого (inline SVG; гадны файлгүй), өнгө --eid-logo / --eid-logo-dark; part="icon" одоо <svg> — түүх: 0.38.0-д eID тэмдгээр солигдсон.
0.27.0+ (Go v0.13.0; Swift, Android, .NET 0.6.0): waitForResult / WaitForResult (зөвхөн TS, Go — .NET-д session клиент байхгүй, QR-ийн ResultAsync өөрийн 600 с хүлээлттэй) session дуустал хүлээнэ (expiresAt + 15 с, эс бөгөөс discovery sessionTtl, эс бөгөөс 600 с — өмнө 150 с-т тасалдаг байв); бүртгэлийн туслах medium-ийг sessionID-аас тодорхойлж, зөрчил ба claimedRole-той боловч onBehalfOf-гүй бүртгэлийг CA-д илгээхээс өмнө татгалзана; RSA-PSS гарын үсэг бүх SDK-д SIGNATURE_INVALID; токены алдааны кодын тогтмолууд (TS TOKEN_ERROR_CODES, Go TokenErrorCodes); TS OCSP «good» кэш nextUpdate-ээс хэтрэхгүй; Kotlin HTTP 10 с нь нийт хугацаа.
0.26.0+ (Go v0.12.0; Swift, Android, .NET 0.5.0): харагдах гарын үсгийн appearance.page 0 / байхгүй = eID баталгаажуулалтын хуудас биш сүүлийн хуудас (§ харагдах гарын үсэг) — өмнө нь хоёр дахь гарын үсгийн тэмдэг эхний гарын үсгийн баталгаажуулалтын хуудсан дээр буудаг байв. Тодорхой дугаар хэвээр. Golden pades-golden-v6.
0.25.0+ (Go v0.11.0; Swift, Android, .NET 0.4.0): харагдах гарын үсгийн тэмдэг хуудасны агуулгатай давхцахгүй (§ байршил) — prepare-ийн хариунд placement (status: requested · moved · overlap · unscanned); overlap / unscanned үед хэрэглэгчид мэдэгдэнэ. Golden pades-golden-v5.
0.24.0+ (зөвхөн TypeScript, /convert): eid-mongolia-convert-assets нь soffice.data-аас Writer → PDF-д хэрэггүй файлуудыг хасна (99.7 → 43.2 MB; PDF гаралт өөрчлөгдөхгүй) ба файл бүрийн хажууд урьдчилж шахсан .br / .gz бичнэ — серверт brotli_static / gzip_static асаавал иргэний эхний татал ~77 MB → ~41 MB. Asset-ийн хавтас <хувилбар>-<hash>/ болсон тул шинэчилсний дараа иргэд нэг удаа дахин татна. converterStatus().downloadBytes — brotli хэмжээ. Дэлгэрэнгүй: SDK-ийн README §3e.
0.23.0+ (Go v0.10.0): USB токен (§9.1) — appTransaction (апп баталгаа, ≤ 16 KiB) ба APP_ATTESTATION_INVALID; эхлэлийн хариунд supersedes; push-ээс өмнөх dryRun — TS eid.token.previewCertificate(input), Go c.Token.PreviewCertificate(ctx, in); discovery tokenCertificate.appTransaction / personalAutoSupersede / dryRun. Нэг товч нэвтрэлтийн хугацаа CA-ийн session-оос урт байхгүй (sessionTtl; TS client.auth.sessionTtl(), Go c.Auth.SessionTTL(ctx)).
0.22.0+ (Go v0.9.0): /certificates/etsi-ийн USB токены гэрчилгээнд tokenSerial (§9) — токен бүрийн AUTH+SIGN-ийг үүгээр бүлэглэнэ. .NET 0.3.0 nuget.org-д (цуцлалтын хатуужуулалт ижил; Completed.FileName, EidPades.Registration).
0.21.0+ (Go v0.8.0) — зан төлөвийн өөрчлөлт, аюулгүй байдлын засвар: (1) цуцлалтын нотолгоо хатуу (§5.2.12) — өмнө valid байсан зарим баримт indeterminate (revocation_unavailable) эсвэл invalid (revoked); (2) complete-д цуцлагдсан гэрчилгээ → LTV_UNAVAILABLE, давтахгүй — шинэ TS CertificateRevokedError (/pades, e.cause), Go pades.ErrCertificateRevoked; (3) задлахын өмнөх бүтцийн шалгалт (гүн давхарлалт, ObjStm мөчлөг) → PDF_INVALID / STATE_INVALID, Go pades.ErrPDFStructure; шууд толь /Encrypt << → PDF_ENCRYPTED; (4) baseUrl (Go ResolveBaseURL / NewClient) зөвхөн https:// — http:// зөвхөн localhost / loopback (тест, mock), бусад нь алдаа; (5) алдааны зам/текстэд төхөөрөмжийн дугаар (/linked/…), иргэний нэр орохгүй. Word → PDF ашигладаг бол шинэчилсний дараа npx eid-mongolia-convert-assets <хавтас>-ийг дахин ажиллуул — удаан CPU-тай компьютерт хөрвүүлэлт гацдаг байсныг засах pthreadPoolSize 8 asset-д ордог.
0.20.1+ (Go v0.7.1): PDF шалгагчийн алдаа тодорхой болсон (§5.2.12) — шифрлэгдсэн PDF → PDF_ENCRYPTED (өмнө нь PDF_INVALID; Go v0.7.0 RC4-өөр шифрлэгдсэн баримтын зөв гарын үсгийг malformed_signature гэж буруу тайлагнадаг байв), хөтөчид crypto.subtle алга (http хуудас, хуучин хөтөч) → UNSUPPORTED_ENVIRONMENT.
0.20.0+ (Go v0.7.0): PDF-ийг локал шалгах (§5.2.12) — TS validatePdf (@gerege-systems/eid-mongolia-sdk/validate, хөтөч/Node), Go pades.ValidatePDF (pades.Verify deprecated); /v3/validate/pdf-тэй ижил тайлан + certificate_level. «eID-ээр нэвтрэх» товчны eid-login-error {status, code, retryAfter}, завгүй (429/503) үед countdown (§4.1). Токены 409 ACCOUNT_DELETE_PENDING. TS ocspRevocationChecker олгогч өөрөө зурж өөрийн гэрчилгээг шигтгэсэн хариуг хүлээн авна.
0.19.0+ (Go v0.6.0) — BREAKING, аюулгүй анхдагч: (1) гэрчилгээний зориулалт анхдагчаар шалгагдана (TS checkKeyUsage анхдагч true; Go CheckKeyUsage deprecated, унтраах SkipKeyUsageCheck) — нэвтрэлтийн (PIN1) түлхүүрийн гарын үсэг validateSign-д тэнцэхгүй; (2) allowedIssuerSkis / AllowedIssuerSKIs анхдагч [CITIZEN_ISSUING_CA_SKI] (trust anchor-тай үед; унтраах allowAnyIssuer / AllowAnyIssuer); (3) certificateLevel гэрчилгээнээс (§8), хариу өндөр зарлавал татгалзана; (4) token.authVerify(challenge, …) / Token.AuthVerify(ctx, ch, …) — эхний аргумент challenge объект, SDK өөрөө r‖s, rpUUID, гэрчилгээг шалгана (§9.2); (5) «eID-ээр нэвтрэх» start анхдагчаар нэг IP-ээс минутад 20, хязгаарлагч/store дүүрвэл fail-closed. Нэмэлт: NATIONAL_ROOT_CA_PEM, CITIZEN_INTERMEDIATES_PEM (Go eid.CitizenIntermediatesPEM) — intermediatesPem өгөөгүй бол анхдагч; TS registerSignature 80 с (pdfTimeoutMs, Go PDFTimeout-тэй ижил); Go pades.Complete LTV retry (LTVRetries) — TS ба Go хоёулаа LTV retry-тэй; Go NewOCSPChecker олгогч өөрөө зурж өөрийн гэрчилгээг шигтгэсэн хариуг хүлээн авна. Шилжих: authVerify(ch.challengeId, …) → authVerify(ch, …); өөр CA-тай тест орчинд allowAnyIssuer: true.
0.18.3+ (Go v0.5.2): /pades complete OCSP/CRL (DSS) цуглуулалт түр унавал ижил state, гарын үсэг, цагийн тэмдгээр өөрөө 2 удаа дахин оролдоно (~1 с, ~3 с + jitter; PIN дахин асуухгүй) — ltvRetries (анхдагч 2, 0 = унтраах), signal (AbortSignal); бүгд унавал LTV_UNAVAILABLE. POST /pdf/signatures-ийн кодын тогтмол (§5.2). Go eid.NewOCSPChecker: OCSPOptions.Observe (төлөв, responder, хугацаа, кэш — PII-гүй), хариугүй үед *eid.OCSPError (Kind: timeout, http_status, parse, signature …; errors.Is(err, eid.ErrOCSPNoAnswer)), кэш = min(CacheTTL, NextUpdate), олгогчийн гаргасан delegated OCSP responder (RFC 6960 §4.2.2.2).
0.18.1+ (Go v0.5.1): USB токены сунгалтын scope түгжээ (§9.1) — TS TokenConflictCode / TOKEN_CONFLICT_CODES (TOKEN_LIMIT_REACHED, TOKEN_IN_USE, TOKEN_SCOPE_CHANGE), eid.token.certificateInfo() (discovery tokenCertificate, scopeLockedOnRenewal); Go eid.CodeTokenScopeChange, eid.CodeTokenInUse, c.Token.CertificateInfo(ctx) (ScopeLockedOnRenewal).
0.18.0+ (Go v0.5.0): гарын үсэгтэй файлыг имэйлээр (§5.4) — TS eid.share({ file, filename?, to, subject?, message?, lang? }) → { sent, sha256, signatures, docID? }, eid.shareInfo() (discovery share: enabled, хязгаар, quota), алдаа ShareError (code, reason, sent/failed/recipients; SHARE_ERROR_CODES); Go c.Share(ctx, eid.ShareRequest{…}), c.ShareInfo(ctx), *eid.ShareError (errors.As; Unwrap → *eid.APIError). Иргэний гэрчилгээнд issuedAt, revokedAt, revocationReason (Go IssuedAt, RevokedAt, RevocationReason) — §9.
0.17.0+ (зөвхөн TypeScript, хөтөч): Word → PDF хөтөч дотор — шинэ entry @gerege-systems/eid-mongolia-sdk/convert: wordToPdf(file, { assetsBase, onProgress, signal }) → { pdf, pages, warnings }, converterStatus() (татах хэмжээ, кэштэй эсэх), isWordFile(), disposeConverter(), ConvertError. Иргэний .docx / .doc баримтыг LibreOffice-ийн WebAssembly хувилбар (@matbee/libreoffice-converter@2.7.2, MPL-2.0, optional peerDependency — Node backend-д суулгахгүй) Web Worker-т PDF болгоно; файл сервер рүү очихгүй. Гарын үсэг хувиргасан PDF-д зурагдана — тэр PDF-ийг иргэнд зурахаас өмнө заавал харуулж, шалгасныг нь тэмдэглүүлнэ; дараа нь /pades prepare (§5.2). Хост дээр: хуудас Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp (тэр хуудас руу бүтэн ачааллаар), asset-уудыг build үед npx eid-mongolia-convert-assets public/eid-convert-аар өөрийн хост дээр (~190 MB; 0.24.0+ эхний татал brotli ~41 MB / gzip ~61 MB; эхний удаа нэг татаж хөтөчид хадгална), worker-ийн скриптэд тусдаа CSP default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval'. Дэлгэрэнгүй: SDK-ийн README §3e.
0.16.0+ (Go v0.4.0; Swift, Android, .NET 0.2.0): харагдах гарын үсэг (сонголттой) — PAdES prepare-ийн appearance (Go PrepareOptions.Appearance, *pades.Appearance): page (1-ээс; 0 эсвэл байхгүй = оролтын сүүлийн хуудас — 0.26.0+ / v0.12.0+: eID-ийн баталгаажуулалтын хуудсыг алгасна), x, y, width, height (pt), lang (mn | en). Координат нь хуудасны CropBox (байхгүй бол MediaBox)-ийн дэлгэцэнд харагдах (/Rotate-ийн дараах) зүүн доод булангаас, x баруун тийш, y дээш. Тэмдэгт: нэр, байгууллагын нэрийн өмнөөс бол үүрэг (claimedRole), «eID Mongolia-аар зурсан», огноо (+08), QR → https://ca.eidmongolia.mn/verify/<docID>/<rev>. Өндөр < 45 pt, бичвэр багтахгүй эсвэл тэгш өнцөгт хуудаснаас гарвал REQUEST_INVALID. appearance байхгүй бол гарын үсэг үл харагдах (§5.2.4).
0.15.4+: <eid-login-button>-ийн label, icon="eid|none", variant="default|icon" (44×44, нэр нь aria-label → label → анхдагч текст), ::part(icon) / ::part(label); mountEidLoginButton-д ижил сонголт. Анхдагч харагдац өөрчлөгдөөгүй (§4.1).
0.15.3+: товчны QR цонхонд регистрийн дугаарын хэсэг нэг товч болж ижил цонхонд дэлгэгдэнэ; VC том, хугацааны тоолуур VC-ийн доор.
0.15.2+ (Go v0.3.2): «eID-ээр нэвтрэх» — push-д регистрийн oracle хаалт (decoy / DisableDecoy, pushFloorMs / PushFloor), иргэний гэрчилгээг AIA OCSP-оор шалгана (revocation, revocationMode; identity.revocation), аудитын onEvent / OnEvent + auditKey. Шинэ: ocspRevocationChecker() (Go eid.NewOCSPChecker), validator.checkRevocationWith (Go CheckRevocationWith). PAdES бүртгэл: complete().priorRevisionSha256, CA угсарсан өмнөх гарын үсгийг registerSignature өөрөө тааруулна, created (§5.2.6).
0.15.1+ (Go v0.3.1): «eID-ээр нэвтрэх» товч (§4.1) — өөрийн rateLimit / RateLimits өгөхөд анхдагч push хязгаар (нэг дугаарт 3/мин) хэвээр; төрөл тус бүрээр (start/push) нэгтгэнэ, утга нь {limit, windowMs} / функц / false; defaultRateLimit / DefaultRateLimit() export. Товчны өнгө: --eid-* хуудасны аль ч түвшнээс гэгээн, харанхуй хоёуланд; харанхуйд --eid-<нэр>-dark давуу (17 токен — SDK README).
0.15.0+ (Go v0.3.0): PDF (PAdES) клиент талд — @gerege-systems/eid-mongolia-sdk/pades (prepare, complete, PadesError, PROFILE_VERSION), EidClient.signPdfDigest(), registerSignature(), registrationFromCompleted(), Representation.claimedRole; Go pades (Prepare/Complete/Verify, Registration), PDF.RegisterSignature, Sign.DigestForSigner/DeviceLinkForSigner. Бүх entry Node.js ≥ 18. Swift, Android, .NET — §5.2.2.
0.14.2+ (Go v0.2.2): session start-ын хариуны CA-ийн expiresAt-ийг уншина; төрөлжсөн алдаа — TS NoSigningCertificateError, CertChoiceInvalidError, RequestInvalidError (instanceof), Go ErrNoSigningCertificate, ErrCertChoiceInvalid, ErrRequestInvalid (errors.Is).
0.14.1+ (Go v0.2.1): товчны theme="auto|light|dark" (auto = html[data-theme] → .dark/.light → OS), фонт хуудаснаас өвлөгдөх засвар; клиент IP — trustProxy / getClientIp(req) (Go: eidlogin.ForwardedClientIP(n), eidlogin.RemoteAddrIP); onSuccess-д sessionId; Redis store жишээ (§4.1). Гарын үсэг: баримтын дугаараар device-link-ээр эхлүүлэх (sign.deviceLinkByDocument; Go DeviceLinkByDocument), certificate-choice (sign.certificateChoiceByEtsi / certificateChoiceDeviceLink → validator.validateCertificateChoice) ба сонгосон төхөөрөмж рүү digestForSigner / deviceLinkForSigner (Go: DigestForSigner / DeviceLinkForSigner); validateSign-ийн expectedAlgorithm / expectedCertificate (Go: ValidateSignOptions{ExpectedAlgorithm, ExpectedCertificate}).
0.14.0+ (Go v0.2.0): «eID-ээр нэвтрэх» нэг товч (§4.1) — backend-д createEidLogin({ client, onSuccess, callbackUrl, … }) (Go: eidlogin.New(eidlogin.Config{…}), /auth/eid/ дор start / status / callback), хөтөчид @gerege-systems/eid-mongolia-sdk/button-ийн <eid-login-button> Web Component ба mountEidLoginButton(). RP-API AUTH (QR, push, Web2App) дээр ажиллана; таны API secret backend-ээс гарахгүй.
0.12.1+ (Go v0.1.5): representations[].rightTypeLabel / .sourceLabel ба session-ий onBehalfOf.rightTypeLabel — эрхийн төрөл (ADMIN/MANAGER), эх үүсвэрийг (REGISTRY/MANUAL) хүнд харуулахдаа үүнийг ашиглана, кодыг өөрөө орчуулахгүй (§10). Хэл нь roleLabel-тэй ижил (language / Config.Language → Accept-Language).
0.12.0+ (Go v0.1.4): representations[].roleLabel ба session-ий onBehalfOf.roleLabel — албан тушаалыг хүнд харуулахдаа үүнийг ашиглана, role (машин утга)-ийг өөрөө орчуулахгүй (§10). Хэл: language: "en" (Go Config.Language) → Accept-Language; анхдагч илгээхгүй (сервер mn).
0.11.0+ (Go v0.1.3): validator гинжийг RFC 5280-аар шалгана (Go: x509.Certificate.Verify) — pathLenConstraint, CA-ийн keyCertSign ба хугацаа, EKU (auth → clientAuth, sign → emailProtection; checkKeyUsage-ээс үл хамаарна), үл таних critical extension → татгалзана; name constraints-тэй CA гинжинд байвал TS fail-closed. intermediatesPem-д 02, 03, 04-ийг бүгдийг өгнө. 4 MiB-ээс их JSON → ResponseTooLargeError; getRepresentations().raw.
0.10.0+ (Go v0.1.2): session хариуны person блок (нэр, civilId, regNo, birthDate…) ба raw — гарын үсэгт ордоггүй тул personMatchesCertificate(person, subject)-оор гэрчилгээтэй тулгана; алдааны retryable / retryAfterMs; SignOptions.fileName; eid.person (PKI_READ) ба eid.token; validator-т trust.allowedIssuerSkis, checkKeyUsage, schemeName; checkRevocationStatus() → good / unchecked / skipped; AuthenticationError CA-ийн 401-ийг proxy-ийнхоос ялгана. Алдааны текстэд PNOMN, РД, documentNumber, гэрчилгээний CN орохгүй.
0.9.1+: validateSign(result, digest, { expectedFlowType }) (Go: ValidateSignOptions{ExpectedFlowType}) — хариуны flowType-ийг эхлүүлсэн урсгалтай тулгана. RAW_DIGEST гарын үсэгт flowType ордоггүй тул энэ нь RP-API хариуны тулгалт, validateAuth-ийнх шиг криптограф баталгаа биш.
0.9.0+: parseSubject() / баталгаажсан хариуны parsedSubject (нэр, овог, serialNumber); гарын үсгийн алгоритм түлхүүрийн төрөлтэй уягдсан, ECDSA зөвхөн SHA-256.
0.7.0+: eid.organization.getRepresentations(personEtsi); eid.sign.digestByEtsi(…, { onBehalfOf }); session хариуны onBehalfOf төрөлтэй; алдааны e.code (ON_BEHALF_ERROR_CODES) — §10.
validateAuth()/validateSign() нь синхрон бөгөөд OCSP/CRL хүсэлт хийдэггүй. trust.revocation-д RevocationChecker өгөөд, буцсан certificate-д await checkRevocation() дуудна. Hook тохируулаагүй бол checkRevocation() шалгалт хийхгүй. Бэлэн AIA OCSP шалгагч: ocspRevocationChecker() (0.15.2+; Go eid.NewOCSPChecker(eid.OCSPOptions{})) — олгогч / OCSPSigning responder-ийн гарын үсэг, serial, хугацааг шалгаж, хариугүй бол unknown. «eID-ээр нэвтрэх» товч үүнийг анхдагчаар хэрэглэдэг (§4.1).
Шалгагчийн интерфейс: (cert: X509Certificate, issuer: X509Certificate | undefined) => Promise<"good" | "revoked" | "unknown">. §8-ын дагуу OCSP/CRL-ийн гарын үсэг, issuer, serial болон хүчинтэй хугацааг баталгаажуулж төлөв буцаана. Issuer олдоогүй, хариу хүчингүй эсвэл төлөв тогтоогдоогүй бол unknown; сүлжээний алдааг good гэж үзэхгүй. Root болон бүх intermediate PEM-үүдийг итгэмжлэгдсэн эхээс авч backend-д тохируулна. createEid(revocation, intermediatesPem)-ийн массивд auth/sign issuer болон Root хүртэлх бүх завсрын CA-г (Mongolian National Issuing CA (MDDC) → Gerege Issuing CA → eID Mongolia Issuing CA / Organization Issuing CA) оруулна; ганц issuer PEM хангалтгүй.
import { EidClient, sha256Base64, type RevocationChecker } from "@gerege-systems/eid-mongolia-sdk";
// Шалгагч: ocspRevocationChecker() (SDK-ийн AIA OCSP) эсвэл өөрийн OCSP/CRL.
export function createEid(revocation: RevocationChecker, intermediatesPem: string[]) {
return new EidClient({
baseUrl: "https://rp.eidmongolia.mn",
credentials: { rpUUID: process.env.EID_RP_UUID!, rpName: "Интернэт банк", apiSecret: process.env.EID_RP_SECRET! },
trust: {
trustAnchorsPem: [process.env.EID_NATIONAL_ROOT_PEM!],
intermediatesPem, // auth/sign issuer болон бүх завсрын CA-ийн PEM
revocation,
revocationMode: "hard-fail", // revoked, unknown үед татгалзана
},
});
}
// Нэвтрэлт (push): VC-г хэрэглэгчид харуулсны дараа үр дүнг хүлээнэ.
export async function authenticateByPush(
eid: EidClient, etsiId: string, showVc: (vc: string) => void,
) {
const s = await eid.auth.notificationByEtsi(etsiId, [
{ type: "displayTextAndPIN", displayText60: "Интернэт банкинд нэвтрэх" },
]);
if (!s.vc) throw new Error("Session VC алга");
showVc(s.vc);
const result = await eid.session.waitForResult(s.sessionId);
const who = eid.validator.validateAuth(result, s.acsp!, {
expectedFlowType: "Notification",
});
await eid.validator.checkRevocation(who.certificate);
return who; // Зөвхөн бүх шалгалт амжилттай болсны дараа нэвтрүүлнэ.
}
// Дурын баримтын digest-д (§5.1; CMS/XAdES-ийг та угсарна). PDF (PAdES) бол §5.2 — prepare/complete.
export async function signDigest(
eid: EidClient, etsiId: string, docBytes: Uint8Array, showVc: (vc: string) => void,
) {
const digest = sha256Base64(docBytes);
const g = await eid.sign.digestByEtsi(etsiId, digest, [
{ type: "displayTextAndPIN", displayText60: "Гэрээнд гарын үсэг зурах" },
]);
if (!g.vc) throw new Error("Session VC алга");
showVc(g.vc);
const result = await eid.session.waitForResult(g.sessionId);
const sig = eid.validator.validateSign(result, digest);
await eid.validator.checkRevocation(sig.certificate);
return sig; // Шалгалт дууссаны дараа гарын үсгийг хадгална.
}
Жишээний функцүүдийг backend-ийн session болон callback handler-т ашиглана. ValidationError эсвэл revocation шалгагчийн алдаа гарвал нэвтрэлт/гарын үсгийг зөвшөөрөхгүй. Web2App/App2App-д expectedFlowType болон callback-ийн userChallengeVerifier-ийг заавал өгнө; QR + товч хосолсон үед §8-ын урсгал шалгах дүрмийг мөрдөнө.
buildDeviceLink({…}) — v3 QR / Web2App / App2App холбоос authCode-той (§4.3, жишээ §6); qrDeviceLinkTicker(input, onLink) — SSE/WebSocket-д QR-ийг секунд тутам гаргана. Зөвхөн backend entry-д (sessionSecret).
«Миний QR» (§4.5): TS eid.qr (0.37.0+), Go c.QR (v0.21.0+), .NET EidQrClient (0.13.0+, namespace GeregeSystems.EidMongolia.Qr) — parse → start → result, иргэний JWS-ийн гинж / OCSP / гарын үсгийн шалгалт SDK дотор.
Swift, Android-д зөвхөн PAdES SDK; .NET-д PAdES + «Миний QR» (§5.2.2). RP-API-ийн (нэвтрэлт, гарын үсгийн session) SDK бусад хэлэнд одоогоор байхгүй — HTTP API-г шууд ашиглана; 8-р хэсгийн шалгалтыг өөрөө хэрэгжүүлнэ.
Туршилт ба тусламж
- Амьд demo: demo.eidmongolia.mn — QR / push нэвтрэлт, PDF гарын үсэг бүрэн урсгалаар; /shop — «Миний QR» касс.
- Гарын үсэг шалгах: ca.eidmongolia.mn/verify — таны системийн гаргасан PDF-ийг иргэн, гуравдагч тал шалгана.
- Гэрчилгээний төлөв: OCSP, CRL. Adobe Reader-т итгэмжлэх: заавар.
- Тусламж: eIDsupport@gerege.com, 7777-3773. Асуудлаа
sessionID, хүсэлтийн цаг (UTC+8), HTTP кодтой хамт илгээнэ — секретээ хэзээ ч бүү илгээ.
Production-д гарахаас өмнө: секрет зөвхөн серверт · IP allowlist · үндэсний Root-оор гинж шалгадаг · ACSP_V2/digest-ийн криптограф шалгалт · endResult-ийн OK-оос бусад утга бүрийг амжилтгүй гэж боловсруулдаг · session poll нэг-нэгээр.