RP-API v3

Цахим үнэмлэхийг
системдээ холбо.

Нэвтрэлт, хууль ёсны гарын үсэг, байгууллагын тамга — нэг RP-API-аар. TypeScript, Go SDK нь QR, push, Web2App, OCSP, rate limit-ийг бэлэн шийдэж өгнө.

$npm i @gerege-systems/eid-mongolia-sdk
Push нэвтрэлтcurl
POST https://rp.eidmongolia.mn/authentication
     /notification/etsi/PNOMN-…
Authorization: Bearer rp_sk_…

{
  "relyingPartyUUID": "…",
  "relyingPartyName": "Таны үйлчилгээ",
  "certificateLevel": "QUALIFIED",
  "signatureProtocol": "ACSP_V2"
}
200Хариу
{
  "sessionID": "…",
  "vc": { "type": "alphaNumeric5", "value": "48271" },
  "expiresAt": "2026-10-26T04:05:00Z"
}
rp.eidmongolia.mnRP-API · Bearer + IP allowlist
ca.eidmongolia.mnНийтийн · discovery, verify, /dl
ocsp · crl.eidmongolia.mnГэрчилгээний төлөв · нээлттэй
tsa.timeserver.mnRFC 3161 · 20 r/s/IP
§1 · Хэрхэн ажилладаг

Session нээ. Код харуул. Хариугаа шалга.

01 · Таны backendSession эхлүүлэхPOST /authentication/…
02 · Вэб / аппVC кодыг харуулахvc.value · 5 оронтой
03 · Иргэний утасКод тулгаж, PIN оруулахPIN апп-аас гарахгүй
04 · Таны backendLong-poll хийхGET /session/{id}
05 · ЗаавалХариуг шалгахгинж · гарын үсэг · OCSP
sessionTtl.auth300 с sessionTtl.sign600 с timeoutMs≤ 120 000 нэг poll-д
Эхлэх заавар

Найман алхам. Эсвэл SDK-аар хоёр.

SDK-ийн createEidLogin (TS) эсвэл eidlogin.New (Go) нь 5–8-р алхмыг бүхэлд нь хийнэ. Хөтөчид <eid-login-button> тавихад л болно.

PIN-г хэзээ ч бүү асуу. VC бол PIN биш — дэлгэцэнд харуулах 5 оронтой код. rp_sk_… secret зөвхөн backend-д.
  1. 1БүртгүүлэхorgName, orgRegister, contact, purpose-оо илгээ → relyingPartyUUID + rp_sk_… авна.
  2. 2Secret-ээ хадгалахНэг л удаа харагдана. Хөтөч, мобайл апп руу хэзээ ч бүү дамжуул.
  3. 3Discovery уншихGET ca.eidmongolia.mn/.well-known/eid
  4. 4mTLS · сонголтоорEC P-256 түлхүүр, ASCII subject-тэй CSR илгээж rp.crt авна.
  5. 5Session эхлүүлэхPush: …/notification/etsi/{id} · QR: …/device-link/anonymous
  6. 6VC харуулахХариуны vc.value-г иргэнд харуул.
  7. 7Poll хийхGET /session/{sessionID} → COMPLETE · OK
  8. 8БаталгаажуулахГинж, хугацаа, түвшин, гарын үсэг, userChallenge-ийг өөрөө шалга. endResult-д ганцаар бүү итгэ.
Хөгжүүлэгчдэд — RP-API

Банк, төрийн байгууллага, цахим үйлчилгээ (Relying Party) eID Mongolia-д холбогдож иргэнээ утсаар нь нэвтрүүлж, баримтад хууль ёсны гарын үсэг зуруулна. Энэ хуудас бүртгэлээс эхлээд endpoint бүр, хариуг хэрхэн шалгах хүртэл бүх зүйлийг агуулна.

RP-API · https://rp.eidmongolia.mn · сүүлд шинэчилсэн 2026-10-26

§1

Хэрхэн ажилладаг

Иргэн бүр eID Mongolia аппад хоёр гэрчилгээтэй: нэвтрэлтийн (PIN1) ба гарын үсгийн (PIN2). Хувийн түлхүүрийн хэсгүүд иргэний утас болон серверт тусдаа хадгалагдаж, гарын үсэг зурахад хоёулаа оролцоно (2-of-2 threshold); бүтэн хувийн түлхүүр нэг газар хадгалагдахгүй. Гэрчилгээ нь Монгол Улсын үндэсний Root CA-аас гинжлэгдэнэ. Таны сервер (RP backend) RP-API руу session үүсгэж, иргэн утсаараа баталгаажуулж, та үр дүнг авна.

  1. Session үүсгэх. Таны backend POST …/authentication/… эсвэл POST …/signature/… дуудна. Хариунд sessionID ба 5 оронтой VC (баталгаажуулах код) ирнэ — VC-г хэрэглэгчдээ харуулна.
  2. Иргэн батлана. Утсанд push ирнэ (эсвэл QR уншуулна / App2App-аар апп нээгдэнэ). Иргэн дэлгэц дээрх VC-г тулгаад PIN1 (нэвтрэлт) эсвэл PIN2 (гарын үсэг) оруулна.
  3. Үр дүн авах. Backend GET …/session/{sessionID}-г long-poll хийнэ. state: COMPLETE, endResult: OK болоход гарын үсэг ба иргэний гэрчилгээ ирнэ.
  4. Хариуг шалгах. Гэрчилгээний гинж, хугацаа, түвшин, гарын үсгийн криптограф — бүгдийг өөрийн талд заавал шалгана. endResult: OK гэдэгт дангаар нь итгэхгүй.

Хоёр суваг

  • Push (notification) — иргэнийг регистрийн дугаар, иргэний дугаар эсвэл ETSI (PNOMN-…) тодорхойлогчоор нь заана; утсанд нь мэдэгдэл очно. Ширээний компьютер, оператортой үйлчилгээнд.
  • Device-link (QR / App2App) — иргэнийг урьдчилан мэдэхгүй. Компьютер дээр QR харуулж утсаар уншуулна, эсвэл иргэн утсаараа орж байгаа бол ижил холбоосоор аппыг шууд нээж, батлаад initialCallbackUrl-аар таны сайт/апп руу буцна (6-р хэсэг).

VC нь PIN биш. 5 оронтой VC-г зөвхөн дэлгэцэнд харуулна; хэрэглэгчээр оруулуулахгүй. PIN1/PIN2-ыг иргэн зөвхөн өөрийн утсан дээр оруулдаг — таны систем PIN-тэй хэзээ ч харьцахгүй.

§2 · RP болж бүртгүүлэх

API түлхүүрээ аваад эхэл.

Байгууллагын нэр, регистр, холбоо барих хүн, зорилго (AUTH · SIGN · CERT · QR_PRESENT), callback host-оо илгээнэ үү. Оператор хянаж баталгаажуулна.

AUTHSIGNCERTQR_PRESENT
ИмэйлeIDsupport@gerege.com Утас+976 7777 3773 Нийтийн /v3/rp-applications өргөдөл одоогоор түр зогссон.

Дэлгэрэнгүй

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_…) олгогдоно.

  1. Хүсэлт гаргах — доорх мэдээллийг оператор руу илгээнэ:
    МэдээлэлШаардлагаТайлбар
    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 тэмдэгт.

  2. Батлагдсаны дараа оператор танд relyingPartyUUID ба rp_sk_… secret-ийг өгнө. Secret зөвхөн нэг удаа харагдана — backend-ийнхээ нууцын санд хадгална, алдвал оператор шинээр гаргана (хуучин нь хүчингүй болно). IP allowlist-ийг оператор батлахдаа тохируулна; mTLS, тамга (SEAL), PKI_READ зэрэг нэмэлт эрхийг мөн оператор олгоно.

Эрх, secret, callbackHosts болон бусад тохиргооны өөрчлөлтийг оператортой тохирно.

§3

Холболт ба хамгаалалт

Base URL ба танилт

ХостЮундТанилт
https://rp.eidmongolia.mnRP-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/ocspOCSP (гэрчилгээний 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.

  1. Түлхүүр ба 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-ийг CA 400, {"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 зөв эсэх
  2. Зөвхөн rp.csr-ийг илгээнэ. relyingPartyUUID-тайгаа eIDsupport@gerege.com руу. Гарын үсгийг eID Mongolia-гийн оператор Organization Issuing CA-аар зурна — RP өөрөө гэрчилгээ зурахгүй, өөр CA-ийн гэрчилгээ хүлээн авахгүй.
  3. Хариуд гинжтэй 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
  4. Клиентээ тохируулна. Хаяг хэвээр 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 });
  5. Шалгаад, дараа нь заавал болгуулна. 4-р хэсгийн нэвтрэлтийн хүсэлтийг client cert-тэй явуулахад 200 + sessionID ирж байвал cert зөв. Үүний дараа л оператороос RP-д require_mtls-ийг асаалгана — оператор таны гэрчилгээний subject-ийг (certSubject) RP-д холбож асаана. Эрт асаавал cert суугаагүй хүсэлт бүр 401. certSubject-ийг өөрөө бөглөх шаардлагагүй: тэр нь гэрчилгээний RFC 2253 хэлбэр (C=MN,O=…,CN=… — CSR-ийн дарааллын урвуу), оператор олгосон гэрчилгээнээс шууд авна.
  6. Асаасны дараа дахин нэг шалгана: 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), алга бол discovery sessionTtl + 15 с, эс бөгөөс 600 с. Хугацаа дууссан session-ийг CA COMPLETE/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+).

§4

Нэвтрэлт

Санал болгох арга: нэг товч нэвтрэлт (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Утасны хөтөч (товч өөрөө шилжинэ); lang302 → eID апп (Web2App холбоос) + cookie
GET /auth/eid/statuseid_login cookie{"state":"PENDING", qr, vc, expiresIn} · WAIT_CALLBACK · {"state":"OK", redirect?, data?} + таны header · {"state":"FAILED", error} · EXPIRED
GET /auth/eid/callbackАпп буцаана: ?sessionId=…&userChallengeVerifier=… + cookie303 → 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 (Bun server.requestIP(req)?.address, Deno info.remoteAddr.hostname, Vercel req.headers.get("x-real-ip")); trustProxy-оос давуу.
  • handleRequest-д ip (Express req.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 · Poll120 с · 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, InsecureCookieeid_login, /, SecureТүр cookie (HttpOnly, SameSite=Lax). Secure-ийг зөвхөн http dev-д унтраана.
rateLimit / RateLimits, RateLimitstart: нэг 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 / Loggerconsole.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, DisablePushFloor1000 мс / 1 сPush start-ийн хариу (бодит, decoy, 502) хамгийн багадаа энэ хугацаанд — decoy-г хугацаагаар ялгахаас. TS 0 / Go DisablePushFloor — унтраана. 429, 400-д хамаарахгүй
revocation / Revocation, DisableRevocationAIA OCSPonSuccess-ийн өмнө гэрчилгээний цуцлалт: анхдагч TS ocspRevocationChecker(), Go eid.NewOCSPChecker(eid.OCSPOptions{}). Цуцлагдсан / олгогч мэдэхгүй → FAILED VALIDATION. revocation: false / DisableRevocation: true — шалгахгүй (цуцалсан гэрчилгээгээр нэвтэрнэ)
revocationMode / RevocationModesoft-failOCSP хариугүй (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")
langmn (анхдагч) | 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 хуудасны хэл
modeauto (анхдагч: утас → Web2App, бусад → QR цонх) | qr | app
themeauto (анхдагч) | light | dark. auto: хуудасны html[data-theme="dark|light"] → html.dark / html.light → OS (prefers-color-scheme); хуудас горимоо солиход шууд дагана. light/dark хуудсыг давна. Фонт хуудаснаас өвлөгдөнө (товч, цонх, input).
success-urlBackend redirect өгөөгүй үед амжилтын дараа шилжих URL
labelТовчны текст (0.15.4+); анхдагч lang-ийн «e-ID-ээр нэвтрэх» / «Log in with e-ID» (0.38.1+). label="" = variant="icon"
iconeid (анхдагч — eID тэмдэг, дөрвөлжин inline SVG; 0.38.0+ light/dark хувилбар нь theme-ийг дагана) | none (0.15.4+). icon="none" + текстгүй бол хоосон товч үүсгэхгүй — анхдагч текст гарна
variantdefault (лого + текст) | icon — зөвхөн лого, 44×44 (0.15.4+). Нэр нь хостын aria-label → label → анхдагч текст
eid-login-successdetail = status-ийн OK хариу; cancelable — preventDefault() хийвэл шилжихгүй
eid-login-errordetail.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).
  • start POST нь JSON заавал (cross-origin preflight шаардана), Sec-Fetch-Site: cross-site → 403. Push анхдагчаар нэг дугаарт минутад 3 — иргэний утсыг мэдэгдлээр дарахаас (MFA fatigue). Иргэн бүртгэлтэй эсэхийг задлахгүй (0.15.2+ / v0.3.2+): eID дугаарыг татгалзвал бодит хариутай ижил decoy session (утас руу юу ч очихгүй, status PENDING → EXPIRED), push start-ийн бүх хариу ≥ 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): цуцлагдсан → FAILED VALIDATION, OCSP хариугүй бол зөвшөөрч revocation = "unchecked" (revocationMode: "hard-fail" — татгалзана).
  • onSuccess-д шинэ session id үүсгэнэ (session fixation); data-д нууц бүү хий. Олон instance, serverless (Vercel г.м.) дээр анхдагч санах ойн store ажиллахгүй — Redis зэрэг хуваалцсан store.
  • Хөтөч дээрх eid-login-success нь UX-ийн дохио л — нэвтрэлтийг таны backend onSuccess-д тавьсан 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).

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"}
ТалбарУтга
sessionIDUUID — GET /session/{sessionID} poll-д
sessionTokenХолбоосонд яг хэвээр орно (URL-safe, 32 тэмдэгт [A-Za-z0-9_-])
sessionSecretСтандарт Base64 (padding-тэй, 32 байт → 44 тэмдэгт) — authCode-ийн HMAC түлхүүр (BASE64-DECODE(sessionSecret)). Зөвхөн таны backend-д — хөтөч, апп руу хэзээ ч бүү гарга
deviceLinkBasehttps://ca.eidmongolia.mn/dl — холбоосын угтвар
vc5 оронтой баталгаажуулах код — дэлгэцэндээ харуулж иргэн утсан дээрхтэй нүдээр тулгана (ПИН биш)
expiresAtSession-ий бодит дуусах хугацаа (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}
ПараметрУтга
deviceLinkTypeQR — өөр төхөөрөмжийн (компьютер) дэлгэц дээрх QR · Web2App — утасны хөтөч дэх товч · App2App — таны апп дахь товч
elapsedSecondsЗөвхөн QR: floor(now − receivedAt) секунд, тэргүүлэх 0-гүй бүхэл тоо (0, 7, 22)
sessionTokenХариуны sessionToken
sessionTypeauth (нэвтрэлт) · sign (гарын үсэг) · cert (гэрчилгээ сонгох)
version1.0
langISO 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) ) )
Талбарauthsigncert
signatureProtocolACSP_V2RAW_DIGEST_SIGNATUREхоосон
challengeилгээсэн rpChallenge (base64 текстээрээ)илгээсэн digest (base64 текст)хоосон
relyingPartyNameхүсэлтэд илгээсэн яг тэр мөр (UTF-8, Кирилл хэвээр), стандарт base64-аар
brokeredRpNameүргэлж хоосон (B64("") = хоосон мөр)
interactionsилгээсэн base64 мөр яг хэвээр (hash хийхгүй)хоосон
initialCallbackUrlQR ба 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_EXPIREDQR хуучирсан (authCode зөв) — ихэнхдээ QR секунд тутам шинэчлэгдээгүй«Дэлгэц дээрх шинэ QR-ийг уншуулна уу»
DEVICE_LINK_NO_CALLBACKWeb2App/App2App холбоос боловч session-д зөвшөөрөгдсөн initialCallbackUrl алгаХолбоос хүчингүй
DEVICE_LINK_TYPE_CONFLICTSession өөр төрлөөр эсвэл өөр төхөөрөмжөөр аль хэдийн нээгдсэнХолбоос хүчингүй
SESSION_NOT_ACTIVESession дууссан эсвэл хугацаа хэтэрсэнДахин эхлүүлэх
FLOW_TYPE_MISMATCHГарын үсгийн flowType ≠ нээгдсэн холбоосын төрөлАлдаа
DEVICE_LINK_REQUIREDDevice-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#xexample.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, Android ACTION_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://…, *) 400 CALLBACK_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 хэлбэрээр авна. Регистр бичүүлэх, үнэмлэх хуулах шаардлагагүй. Аттрибут сонгохгүй — иргэн бүгдийг хуваалцана эсвэл татгалзана.

  1. Уншина — QR-ийн агуулга EIDMN-QR1:<token> (token = 32 санамсаргүй байтын base64url, 43 тэмдэгт). Токен нэг удаагийн, 120 секунд хүчинтэй, хувийн мэдээлэл агуулахгүй; иргэн шинэ QR гаргахад өмнөх нь хүчингүй болно. SDK: qr.parse(scanned) → {token, qr}; өөр агуулга бол QR_FORMAT_INVALID.
  2. Хүсэлт — backend-ээсээ POST /authentication/qr (qr.start): уншсан qr + nonce. CA токеныг хэрэглэж (дахин ашиглагдахгүй) иргэний утас руу хүсэлт илгээнэ; хариунд зөвхөн sessionID (VC байхгүй).
  3. Иргэн баталгаажуулна — утсанд ганц өгүүлбэр: «Та <таны RP-ийн бүртгэлийн нэр>-д «Миний мэдээлэл» хэсгийн бүх мэдээллийг хуваалцах гэж байна» ба PIN2. PIN2 оруулах нь зөвшөөрөл; апп мэдээллээ илгээж, иргэний SIGN түлхүүрээр (SplitKey) гарын үсэг зурна.
  4. Үр дүн — 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_INVALIDJSON буруу, танихгүй талбар, nonce-ийн урт/тэмдэгт буруу
403 RP_REQUIREDБүртгэлтэй RP биш эсвэл relyingPartyUUID канон хэлбэрийн биш
403 PERMISSION_DENIEDRP-д QR_PRESENT алга
404 QR_TOKEN_INVALIDТокен танигдахгүй, 120 секунд өнгөрсөн, аль хэдийн ашиглагдсан, шинэ QR-ээр солигдсон эсвэл төхөөрөмж өөрчлөгдсөн — иргэнээс шинэ QR авч уншуулна
409 NO_SIGNING_CERTIFICATEИргэнд идэвхтэй QUALIFIED SIGN гэрчилгээ алга (токен хэрэглэгдсэн)
429 RATE_LIMITEDRP-ийн хязгаар (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 · typPS256 (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-д багцлагдсан)
isshttps://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 хийхгүй.

  1. JWS ≤ 524288 байт, яг 3 хэсэг, хэсэг бүр ^[A-Za-z0-9_-]+$ → эс бөгөөс QR_JWS_MALFORMED.
  2. Header: яг alg, typ, x5c гишүүнтэй; alg = PS256, typ = eidmn-myqr+jwt; x5c = хоосон биш 2 string; тус бүр X.509 DER болж задарна → эс бөгөөс QR_HEADER_INVALID. x5c[1] зөвхөн завсрын CA-ийн нэр дэвшигч — хэзээ ч итгэлийн зангуу биш.
  3. Гинж: x5c[0] → (x5c[1] ∪ таны завсрын CA-ууд) → таны итгэлийн зангуу (Үндэсний root CA), одоо ± 60 с. Leaf: CA биш; KU contentCommitment; түвшин QUALIFIED (QcStatements); олгогчийн SKI нь иргэний issuing CA-ийнх; RSA ≥ 2048 бит → эс бөгөөс QR_CHAIN_INVALID.
  4. Хүчингүй эсэх (OCSP, §8): revoked → QR_CERT_REVOKED; unknown / алдаа (hard-fail, анхдагч) → QR_REVOCATION_UNKNOWN.
  5. Гарын үсэг: RSASSA-PSS (SHA-256, MGF1-SHA-256, salt 32) — seg0 "." seg1-ийн ASCII байт дээр, leaf-ийн түлхүүрээр → эс бөгөөс QR_SIGNATURE_INVALID.
  6. Payload (JSON объект): iss = https://ca.eidmongolia.mn; sub = leaf-ийн subject serialNumber; rp_name, statement, nonce, sid, aud string; iat, exp бүхэл тоо; statement = дээрх томьёогоор яг → эс бөгөөс QR_CLAIMS_INVALID.
  7. aud = таны RP UUID (том/жижиг үсэг ялгахгүй) → QR_AUDIENCE_MISMATCH; sid = таны sessionID → QR_SESSION_MISMATCH; nonce = таны nonce яг → QR_NONCE_MISMATCH.
  8. Хугацаа: 0 < exp − iat ≤ 300 → эс бөгөөс QR_CLAIMS_INVALID; iat ≤ одоо + 60 → QR_NOT_YET_VALID; одоо < exp + 60 → QR_EXPIRED.
  9. 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_INVALIDSDK (parse)Уншсан агуулга «Миний QR» биш — сервер рүү явахгүй
QR_TOKEN_INVALID404Шинэ QR уншуулна
NO_SIGNING_CERTIFICATE409Иргэнд QUALIFIED SIGN гэрчилгээ алга
PERMISSION_DENIED · RP_REQUIRED403RP-ийн эрх / бүртгэл
QR_PRESENTATION_GONE410Үр дүн аль хэдийн уншигдсан эсвэл хугацаа дууссан — шинэ 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_INVALIDSDK (verify)Шалгалт унасан — мэдээлэлд итгэхгүй («Гарын үсэг баталгаажсангүй»)
QR_REVOCATION_UNKNOWNSDK (verify)OCSP хариу өгсөнгүй (түр) — итгэхгүй; JWS-ийг verify-аар дахин шалгаж болно
QR_PENDINGSDK (result)maxWaitMs дууссан ч иргэн хараахан хариулаагүй — session идэвхтэй, дахин result / check
татгалзал / CA-ийн хугацааendResultTS/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_* шалгалт унасан → итгэхгүй */ }
§5

Гарын үсэг

Иргэн 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_CHOICE session: COMPLETE/OK, ижил documentNumber, дууссанаас ≤ 5 мин, гэрчилгээ нь одоогийн SIGN гэрчилгээ, нэг л удаа. Өөр RP-ийнх эсвэл олдоогүй → 404, бусад зөрчил → 409 CERT_CHOICE_INVALID.
  • SDK — TS eid.sign.digestByEtsi / digestByDocument / textByEtsi (текстийн SHA-256-г SDK тооцно) / deviceLinkByEtsi / deviceLinkByDocument; Go c.Sign.DigestByEtsi / DigestByDocument / TextByEtsi / DeviceLinkByEtsi / DeviceLinkByDocument. PDF-ийн hash session: TS eid.sign.pdfDigest(input) (= eid.signPdfDigest), Go c.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.
  1. Гэрчилгээ — 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}.
  2. Байгууллагын нэрийн өмнөөс (сонголт) — representations[]-ийн сонгосон мөрийн claimedRole-ийг яг хэвээр prepare-д (§5.2.7).
  3. prepare — SDK PDF-ийг (эхний гарын үсэгт QR-тэй баталгаажуулалтын хуудас + гарын үсгийн талбар) бэлтгэж dtbs = SHA-256(DER(signedAttrs)) ба state буцаана. Сүлжээ ашиглахгүй. state нь бэлтгэсэн PDF-ийг агуулна — PDF-тэй ижил нууцлалтай хадгална (хуудас дахин ачаалагдах, апп унтрахад).
  4. Гарын үсэг — утас: RP backend dtbs-ийг digest болгож hash session эхлүүлнэ (§5.2.5), иргэн утсандаа файлын нэр ба таны RP-ийн нэрийг хараад PIN2 оруулна. Токен: клиент PKCS#11-ээр локал зурна (§9.3).
  5. complete — SDK гарын үсгийг гэрчилгээгээр шалгаж (буруу бол SIGNATURE_INVALID, state хэвээр), TSA, CMS, OCSP/CRL, DSS-ийг нэмээд гаралтаа өөрөө шалгана. Аль нэг алхам унавал файл буцаахгүй (§5.2.4).
  6. Бүртгэл — 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 backendgithub.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 + macOSeid-mongolia-sdk-swift 0.10.0, product GeregeEIDPades (SwiftPM)iOS 14, macOS 13, Swift 5.9фонт ≈ 108 KB
Androideid-mongolia-sdk-android 0.10.0 (JitPack), mn.eidmongolia.eid.padesminSdk 24, JDK 17AAR ≈ 330 KB
Windows / .NETGeregeSystems.EidMongolia.Pades 0.13.0 (native C#; nuget.org).NET 8nupkg ≈ 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
fileNameverifyPage үед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 дээш. Хуудсыг s px/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"
SDKexactsuggestPlacement
TypeScriptappearance.exactsuggestPlacement(pdf: Uint8Array, appearance): Promise<PlacementSuggestion>
GoAppearance.Exactpades.SuggestPlacement(pdf []byte, a pades.Appearance) (pades.Suggestion, error)
SwiftSignatureAppearance(…, exact: true)EidPades.suggestPlacement(pdf: Data, appearance: SignatureAppearance) throws -> PlacementSuggestion
KotlinSignatureAppearance(…, 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)
cmsCMS SignedData, DER — бүртгэлийн cms
docIdБаримтын UUID — бүртгэлийн docID, /verify/{docId}
revIndexЭнэ гарын үсгийн дугаар (1 = эхний)
inSha256 · pageSha256 · sigRevSha256 · outSha256SHA-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_LOCKEDCertification гарын үсэг (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_INVALIDstate эвдэрсэн / өөр хувилбарынШинээр 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_CHOICE session (заавал; алга бол 400 REQUEST_INVALID, нөхцөл зөрвөл 409 CERT_CHOICE_INVALID — §5.1).
  • PKI_READ-аар гэрчилгээ уншсан (certificate-choice байхгүй): push — POST /signature/notification/document/{documentNumber}; linked-ийг ашиглаж болохгүй (linkedSessionID нь заавал CERTIFICATE_CHOICE session).
  • 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. TS eid.signPdfDigest({ signer: { documentNumber }, flow }) — …/notification/document/… эсвэл …/device-link/document/… (linked-гүй); { etsi } бол …/etsi/… — олон утастай иргэнийг CA эхний идэвхтэй төхөөрөмж рүү чиглүүлж, гэрчилгээ зөрвөл SIGNATURE_INVALID тул documentNumber-ийг сонго.
  • Ямар ч замаар documentNumber = гэрчилгээ авсан тэр төхөөрөмж — өөр төхөөрөмж зурвал complete SIGNATURE_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_CHOICE session, нөхцөл §5.1 (зөрчил → 409 CERT_CHOICE_INVALID). Идэвхтэй SIGN гэрчилгээгүй иргэнд session эхлэхгүй: 409 NO_SIGNING_CERTIFICATE (§11).
  • Хариу: signature.value (base64) — complete-ийн оролт. endResult OK-оос бусад бол 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
sessionIDphoneUUID; 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 файлгүй тул мэдүүлэг
pageSha256revIndex = 1Completed.pageSha256 — revIndex = 1 үед заавал, revIndex > 1 үед илгээхгүй (эс бөгөөс 400 REQUEST_INVALID)
priorCmsSha256[]revIndex > 1Completed.priorCmsSha256 — өмнөх eID-ийн клиент талын гарын үсгүүд. revIndex > 1 үед priorCmsSha256 + priorRevisionSha256-ийн нийт тоо = revIndex − 1; revIndex = 1 үед илгээхгүй
priorRevisionSha256[]revIndex > 1CA-аар угсарсан файлын гарын үсгийн 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: TS RegisterSignatureResult.created, Go PDFSignatureRegistered.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.
HTTPcodeХэзээ
400REQUEST_INVALID · CMS_INVALID · SIGNATURE_INVALID · TIMESTAMP_INVALIDТалбар буруу/танихгүй · CMS задрахгүй / профайлд нийцэхгүй, эсвэл onBehalfOf-гүй атал CMS-д claimedRole бий · гарын үсэг таарахгүй · цагийн тэмдэг буруу
403REPRESENTATION_REVOKED · TOKEN_ORG_MISMATCH · (кодгүй)Бүртгэлийн мөчид төлөөлөл хүчингүй (байгууллага идэвхгүй/бүртгэлгүй ч энэ кодоор) · токены байгууллага зөрсөн · RP-д SIGN эрх алга
404DOC_NOT_FOUND · (кодгүй)revIndex > 1, баримт бүртгэлгүй · session олдсонгүй / өөр RP-ийнх
409SIGNER_MISMATCHsignerEtsi CMS-ийн гэрчилгээний SERIALNUMBER-тэй таарахгүй
409CLAIMED_ROLE_MISMATCHCMS-ийн мэдүүлсэн үүрэг тэр мөчийн төлөөлөл / session-ий onBehalfOf-оос зөрсөн
409DOC_ID_CONFLICT · PRIOR_MISMATCH · REGISTRY_CONFLICTrevIndex=1, docID бий · priorCmsSha256 бүртгэлтэйтэй таарахгүй · ижил гарын үсэг өөр body-оор бүртгэлтэй
409SESSION_MISMATCH · SESSION_NOT_COMPLETE · SESSION_FAILEDSession-ий digest/гэрчилгээ CMS-тэй зөрсөн · дуусаагүй · амжилтгүй
409SIGNER_CERT_INVALID · TOKEN_CERT_INVALIDУтасны SIGN гэрчилгээ идэвхгүй/цуцлагдсан · токены гэрчилгээ идэвхгүй
413 · 429REQUEST_TOO_LARGE · RATE_LIMITEDBody > 64 KiB · хурдны хязгаар (Retry-After)
503STATE_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). Өөрөө угсрахгүй, өөрчлөхгүй.

  1. prepare: claimedRole = тэр утга; reason = «<байгууллагын нэр>-ийг төлөөлж» (≤ 127 UTF-16 нэгж).
  2. Hash session: "onBehalfOf": "NTRMN-…" — CA session үүсэх ба дуусахад төлөөллийг шалгана, утсанд «Та …-ийг төлөөлөн …» гарна.
  3. Бүртгэл: 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, Go pades.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/pdfeID сервер (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 codeCA (/v3/validate/pdf)ХэзээХэрэглэгчид
PDF_NO_SIGNATUREno_signature (422)Баримтад цахим гарын үсэг алгаГарын үсэгтэй файлаа сонгоно
PDF_INVALIDnot_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_LARGEtoo_large (413)> 10 MiBФайлаа багасгана
PDF_TOO_MANY_SIGNATUREStoo_many_signatures (422)> 16 гарын үсэгШалгах боломжгүй
UNSUPPORTED_ENVIRONMENT (TS 0.20.1, зөвхөн хөтөч)—Web Crypto (crypto.subtle) алга: http:// хуудас (secure context биш) эсвэл хуучин хөтөч. Өмнө нь PDF_INVALIDhttps хуудас эсвэл шинэ хөтөч шаардлагатай
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-ийн бус харагдах гарын үсэг).

HTTPcodeХэзээ / юу хийх
400REQUEST_INVALID · FILE_EMPTY · RECIPIENT_REQUIRED · TOO_MANY_RECIPIENTS · RECIPIENT_INVALID · RECIPIENT_DUPLICATEТалбарын алдаа — програмаа засна
401 / 403—RP auth / SIGN эрх, IP, mTLS
413FILE_TOO_LARGEФайл эсвэл имэйлийн хүсэлт хязгаараас их
415FILE_TYPE_NOT_SUPPORTEDДэмжигдэхгүй формат, задрахгүй эсвэл шифрлэгдсэн PDF
422FILE_NOT_SIGNED (+reason)Дээрх гарын үсгийн дүрэм
429RATE_LIMITEDКвот — Retry-After-ийн дараа
502MAIL_FAILED (+sent, failed, recipients)Имэйлийн үйлчилгээ зарим/бүх хүлээн авагчид алдсан; recipients = to-гийн дарааллаар sent | failed — зөвхөн failed-ийг дахин илгээ
503MAIL_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).

§6

Ижил төхөөрөмж (App2App / Web2App)

Иргэн таны сайт эсвэл аппыг eID Mongolia апп суусан тэр утсаараа ашиглаж байгаа бол QR уншуулах боломжгүй. Ийм үед device-link session-ийн холбоосоор eID Mongolia аппыг шууд нээж, иргэн батласны дараа таны вэб (Web2App) эсвэл апп (App2App) руу буцаана. Session, VC, хариуг шалгах дүрэм QR-тэй яг ижил — зөвхөн холбоосыг хэрхэн нээх нь өөр.

  1. Backend session эхлүүлнэ. POST /authentication/device-link/anonymous (гарын үсэгт POST /signature/device-link/…) — body-д initialCallbackUrl: иргэнийг буцаах хаяг. Хариуны sessionToken, sessionSecret-ийг серверт хадгалж, холбоосыг §4.3-ын дагуу угсарна: утасны хөтөчид deviceLinkType=Web2App, таны native апп дотор App2App.
  2. Нэг товч, төхөөрөмжөөс хамаарсан үйлдэл. «eID Mongolia-аар нэвтрэх» товч нэг л байна. Компьютер дээр QR холбоосыг (deviceLinkType=QR) харуулж 1 секунд тутам шинэчилнэ (initialCallbackUrl өгөхгүй — утас тусдаа төхөөрөмж тул буцах хөтөч байхгүй, та session-ээ poll хийнэ). Гар утасны хөтөч эсвэл таны native апп дээр Web2App / App2App холбоос руу шууд шилжинэ — тусдаа «Аппаар нээх» товч хэрэггүй. VC-г хоёр тохиолдолд дэлгэцэндээ харуулна.
  3. Апп нээгдэнэ. iOS дээр ca.eidmongolia.mn-ийн apple-app-site-association /dl замыг хамардаг тул Universal Link eID Mongolia аппыг шууд нээнэ. Android дээр нийтлэгдсэн assetlinks.json ба аппын App Link баталгаажсан бол мөн шууд нээнэ. Апп суугаагүй, холбоосын баталгаажуулалт/хэрэглэгчийн тохиргооноос болж шууд нээгдээгүй бол /dl хуудас хөтөчид нээгдэж аппыг автоматаар нээхийг оролдоно, «Аппаар нээх» товч болон App Store / Google Play холбоос харуулна — урсгал тэндээс үргэлжилнэ.
  4. Таны тал руу буцна. Иргэн батласны дараа апп initialCallbackUrl руу буцаана — заавал https:// (Universal Link / App Link эсвэл таны вэб), host нь callbackHosts-д бүртгэлтэй; custom scheme хүлээн авахгүй. Буцах хаягийн дүрэм, аппын зан төлөв, буцах хөтчийн hint — §4.4.
  5. Үр дүнг 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/retPkg hint-гүй үед (§4.4) callback системийн default хөтчөөр (Safari г.м.) нээгдэнэ. Аппын дотоод хөтөч рүү буцаах hint байхгүй.
  • Жишээ алдаа. Хуудас «одоогийн урсгал» гэдгийг санах ойд л барьдаг байвал буцаж ирсэн хуудас бүх session-ийг нэвтрэлт гэж үзэж, PDF-д гарын үсэг зурсны дараа «Амжилттай нэвтэрлээ» гаргаж, гарын үсэгтэй PDF-ийг огт үүсгэхгүй, эсвэл «мэдээлэл олдсонгүй» гэж зогсоно.

Шаардлагатай хэв маяг

  1. Flow-ийн төлөвийг серверт хадгална. Session эхлүүлэхдээ backend-ээ урсгалын төрөл (нэвтрэлт / гарын үсэг), баримтын дугаар, digest, rpChallenge (ACSP_V2 контекст) зэргийг sessionID-аар түлхүүрлэж, хэрэглэгчийн хөтчийн session (cookie)-той холбож хадгална. Зөвхөн клиент талд хадгалах шаардлагатай бол (жишээ нь серверт илгээхгүй эх PDF) localStorage/IndexedDB-д sessionID-аар түлхүүрлэж, TTL-тэй хадгална — sessionStorage эсвэл санах ойд дангаар нь хэзээ ч биш.
  2. Өөрийн opaque state-ийг callback-ийн query-д хийнэ. Сервер https callback-ийн path-ийг /auth/eid/callback болгож, query-г хэвээр үлдээж, fragment (#…)-ийг хасдаг — тиймээс таны утга зөвхөн query-д (?state=…) амьд үлдэнэ. state нь санамсаргүй, нэг удаагийн түлхүүр; буцахад түүгээр серверийн бүртгэлээ хайна. Secret, токен, хувийн мэдээлэл URL-д бүү хий. sessionId, userChallengeVerifier, retScheme, retPkg нэрийг өөрийн query параметрт бүү ашигла — апп хасаад өөрийнхийгөө нэмнэ.
  3. Үр дүнг үргэлж серверээс авч шалгана. Буцах хуудас query-ийн sessionId, state-д итгэхгүй: хадгалсан sessionID-тай тулгаад backend-ээрээ GET /session/{sessionID} хийж хариуг §8-ын дагуу бүрэн шалгана. eID Mongolia сервер callback-т өөрөө ямар ч параметр нэмэхгүй; апп зөвхөн sessionId (нэвтрэлтэд userChallengeVerifier) нэмдэг — энэ хоёр нотолгоо биш.
  4. Үр дүнг эхлүүлсэн хэрэглэгчийн session-той холбоно. Callback таны session cookie-гүй хөтөчид (өөр хөтөч) ирвэл тэнд нэвтрүүлэхгүй — эс бөгөөс session fixation / login CSRF-ийн зам нээгдэнэ. Оронд нь «Эхлүүлсэн хөтөчтэй буцалтыг холбож чадсангүй. Эх хөтчөөс дахин эхлүүлнэ үү» гэж харуулна. Өөр хөтчөөр ирсэн verifier-ийг эх табын нэвтрэлтэд ашиглахгүй; ашиглах боломжгүй RP flow-ийг цуцлах эсвэл хугацаагаар дуусгана. Ижил төхөөрөмжийн нэвтрэлтэд эхлүүлсэн хөтчийн session-той таарсан callback ба verifier-ийг шалгасны дараа л дуусгана (§8.3).
  5. Эх таб poll хийсээр байна. Аппыг нээсний дараа эх хуудас GET /session/{sessionID}-ийг (backend-ээр дамжуулж) poll хийсээр байх ёстой — татгалзсан, хугацаа дууссан болон дуусахыг хүлээж буй төлвийг эх табд харуулна. Татгалзах, хугацаа дуусах үед апп callback нээдэггүй тул эдгээр төлвийг зөвхөн эх таб л харуулж чадна. Web2App/App2App нэвтрэлтийн poll OK болсон ч эхлүүлсэн хөтчийн session-той таарсан callback-ийн verifier ирэх хүртэл хүлээж, түүнийг шалгасны дараа дуусгана. QR/Notification урсгал poll-оор дуусна; өөр хөтчийн callback-ийг Web2App/App2App нэвтрэлтэд орлуулахгүй. Callback хуудас ба эх таб зэрэг дуусгаж болзошгүй тул дуусгах алхмыг idempotent болго (эхнийх нь дуусгаж, дараагийнх нь үр дүнг л уншина).
  6. Хугацаа ба цуцлалт. Session-ий хугацааг эхлүүлэх хариуны expiresAt-аас авна (SDK: s.expiresAt) — одоогийн утга нэвтрэлт 300 с (5 мин), гарын үсэг 600 с (10 мин), discovery sessionTtl. Таны хадгалсан төлөв ч мөн адил TTL-тэй. Буцаж ирэхэд бүртгэл олдохгүй бол «Хүсэлтийн мэдээлэл олдсонгүй — өөр хөтөч эсвэл цонхонд эхэлсэн, эсвэл аль хэдийн дууссан байж болно. Дахин эхлүүлнэ үү», хугацаа дууссан бол «Хүсэлтийн хугацаа дууссан», USER_REFUSED/TIMEOUT бол тодорхой мессеж харуулна. Дуусах, алдаа, цуцлах, хугацаа дуусах бүрд тухайн sessionID-ийн үлдэгдлийг устгана.
  7. Утсан дээр аппыг хэрэглэгчийн үйлдлээр нээнэ. 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, хариуг шалгах дүрэм вэбтэй ижил; ялгаа нь холбоосыг хэрхэн нээх, буцалтыг хэрхэн хүлээж авах.

  1. Session-ийг зөвхөн backend эхлүүлнэ. Таны сервер RP-API-д POST /authentication/device-link/anonymous (гарын үсэгт /signature/device-link/…) дуудаж, initialCallbackUrl-ийг өгнө. RP secret, mTLS түлхүүр, sessionSecret аппад хэзээ ч орохгүй — апп өөрийн backend-ээс зөвхөн бэлэн App2App холбоос (deviceLinkType=App2App, §4.3) ба VC-г авна.
  2. Холбоосыг системээр нээнэ. 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 хуудас хөтөчид нээгдэж аппыг нээхийг оролдоно.
  3. Custom scheme-ийг шууд бүү дууд. eidmongolia://… нь /dl хуудасны дотоод нөөц бөгөөд өөрчлөгдөж болно — зөвхөн /dl https холбоосыг нээнэ.
  4. Буцах хаяг = таны 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 хөтөчид нээгдэнэ — тэр замд «Аппруугаа буцна уу» гэсэн энгийн хуудас байлгана.
  5. Буцахад ба апп дахин идэвхжихэд 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 товч харуулна; таны апп руу буцахад session RUNNING хэвээр — дахин эхлүүлэх боломж өгнө.
  • Татгалзах: 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-д илгээж серверийн бүртгэлээр шалгуулна.
§7

Session ба хариу

state
RUNNINGCOMPLETE
endResult
OKUSER_REFUSEDTIMEOUTWRONG_VC
flowType
QRNotificationWeb2App
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"
}
ТалбарТайлбар
stateRUNNING | COMPLETE
result.endResultOK — амжилт, гэхдээ зөвхөн §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)
§8

Хариуг шалгах (заавал)

§8 · Хариуг заавал шалгах
Гэрчилгээний гинж, хугацаа, түвшинГарын үсгийн криптограф шалгалтflowType нь нээсэн холбоостой таарахuserChallengeVerifier ↔ userChallengestate ба хадгалсан sessionID таарахЗөвхөн endResult: OK-д итгэхгүй

state: "COMPLETE" + endResult: "OK" гэдэгт дангаар нь итгэж болохгүй. Сүлжээ, proxy, тохиргооны эсвэл серверийн алдаа хуурамч OK үүсгэж болно. OK бол signature-ийг заавал шалгана; signature-гүй OK-г татгалзана. Иргэнийг таньсан гэж үзэхээсээ өмнө доорх бүх шалгалтыг өөрийн backend дээр хийнэ. SDK-ийн ResponseValidator гинж, гэрчилгээ ба гарын үсгийг шалгана; урсгалын сонголтыг тодорхой өгч, OCSP/CRL hook тохируулаад await checkRevocation()-г тусад нь дуудна. Ашигласан ч signature-гүй OK-г таны код татгалздагийг тестээр батал.

Хүлээн авахын өмнө бүгд биелэх ёстой:

  1. signature.value ба cert.value хоёулаа байгаа, хоосон биш — үгүй бол endResult-аас үл хамааран татгалзана.
  2. Гэрчилгээний гинж, хугацаа, түвшин, цуцлалт (OCSP/CRL) — 8.1.
  3. Гэрчилгээний төрөл (нэвтрэлт/гарын үсэг) ба иргэний дугаар — 8.2.
  4. signature.value-г гэрчилгээний нийтийн түлхүүрээр, өөрийн хадгалсан challenge/digest-ээр шалгана — 8.3 (нэвтрэлт), 8.4 (гарын үсэг).
  5. Нэвтрэлтэд: signature.flowType нь таны санал болгосон төрлүүдийн нэг; Web2App/App2App үед callback-ийн userChallengeVerifier заавал таарна — 8.3.

8.1 Гэрчилгээний гинж

cert.value-г үндэсний Root CA хүртэл шалгана. Гинж:

  1. Mongolian National Root CA (итгэлийн тулгуур) — татах
  2. Mongolian National Issuing CA (MDDC) — нийтийн URL-д одоогоор нийтлэгдээгүй; eIDsupport@gerege.com-оос хүсэж авна
  3. Gerege Issuing CA — татах
  4. 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). Хариуны утгад дангаар итгэхгүй — гэрчилгээнээс тогтооно: гарын үсгийн гэрчилгээнд qcStatements QcCompliance → QUALIFIED (+ QcSSCD → QSCD), qcStatements-гүй (USB токен) → ADVANCED; нэвтрэлтийн гэрчилгээнд qcStatements байхгүй тул профайлаар — KeyUsage keyEncipherment-тэй (утас) → 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 эсвэл CRL http://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, EKU clientAuth. Гарын үсгийн гэрчилгээ: KeyUsage contentCommitment (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-ын дагуу.

§9

Байгууллага, тамга, 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_READGET /person/summary/etsi/{personEtsi}Нэр, гэрчилгээ/төхөөрөмжийн тоо, байгууллагын тоо, таны RP-тэй хийсэн үйлдлийн тоолол
PKI_READGET /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_READGET /devices/etsi/{personEtsi}Төхөөрөмжүүд (devices[], activeCount, total)
PKI_READGET /rp/activity/etsi/{personEtsi}Иргэний зөвхөн таны RP-тэй хийсэн нэвтрэлт/гарын үсгийн түүх — таны RP-ээр дамжсан USB токеноор нэвтрэлтийг (§9.2) оруулна
PKI_READGET /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.

  1. Токен дээр хоёр 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-д ордоггүй.
  2. POST /token/certificate — иргэний утас руу SIGN push явна. onBehalfOf заавал биш: хоосон/ирээгүй бол хувийн токен; NTRMN-… бол иргэн тухайн байгууллагын ACTIVE төлөөлөгч байх ёстой (§10-тай ижил шалгалт). Танихгүй талбар → 400. tokenSerial [A-Za-z0-9-]{1,64}, tokenLabel ≤ 64 — бүртгэлд л, гэрчилгээнд ордоггүй. appTransaction заавал биш — апп баталгаа (≤ 16 KiB). Ижил CSR хосыг session RUNNING үед дахин илгээвэл ижил хариу.
    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" }] }
    Утасны зурах digest = SHA-256(SHA-256(csrAuth DER) || SHA-256(csrSign DER)); VC-г өөрөө тооцож тулгаж болно: (SHA-256(digest)-ийн сүүлийн 3 байт, big-endian) mod 100000, 5 оронтой.
  3. Төлөв — GET /session/{sessionID}?timeoutMs=-ийн хариунд tokenCertificate: {"status":"PENDING|ISSUED|FAILED|EXPIRED","errorCode"?,"authSerialHex"?,"signSerialHex"?}.
  4. GET /token/certificate/{sessionID} (SDK: TS eid.token.certificateResult(sessionID), Go c.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+).

HTTPcodeХэзээ
400REQUEST_INVALID · CSR_INVALID · CSR_SIGNATURE_INVALID · CSR_KEY_UNSUPPORTED · CSR_KEYS_IDENTICALТалбар буруу/танихгүй · CSR задрахгүй · proof-of-possession буруу · P-256/ecdsa-with-SHA256 биш · хоёр CSR ижил түлхүүртэй
400APP_ATTESTATION_INVALIDappTransaction ирсэн боловч хүчингүй (апп баталгаа) — утас руу push явахгүй. Шалтгааныг хэлэхгүй
403/404REPRESENTATION_* · ORG_NOT_ACTIVE · ORG_NOT_FOUNDЗөвхөн onBehalfOf ирсэн үед (§10.4). GET-д 403 REPRESENTATION_REVOKED — эхлэл ба батлалтын хооронд эрх хасагдсан
409NO_ACTIVE_DEVICE · KYC_INCOMPLETEИдэвхтэй eID утас / хүчинтэй гарын үсгийн гэрчилгээ алга · латин нэр бүртгэлгүй
409CSR_KEY_REUSEDЭнэ түлхүүрт гэрчилгээ аль хэдийн олгогдсон — токенд шинэ түлхүүр үүсгэнэ
409TOKEN_ISSUANCE_PENDINGЭнэ иргэнд өөр олголт хүлээгдэж байна (нэг зэрэг нэг)
409TOKEN_LIMIT_REACHEDТокены хязгаар хүрсэн (PERSONAL нь зөвхөн limit: 0 үед — хязгаар хүрсэн хувийн олголт supersedes-тэй 200). Нэмэлт талбар kind (PERSONAL/ORGANIZATION), limit, organization (зөвхөн ORGANIZATION); limit: 0 = тухайн төрлийн олголт хаалттай. Өмнөх токеноо түүн дээрээ сунгах эсвэл цуцлуулна. GET-д ч гарч болно (гэрчилгээ гараагүй)
409TOKEN_IN_USEtokenSerial өөр иргэнд хүчинтэй гэрчилгээтэй (эзнийг хэлэхгүй)
409ACCOUNT_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+)
409TOKEN_SCOPE_CHANGEСунгалт scope солих гэсэн (хувийн ↔ байгууллага, эсвэл өөр onBehalfOf). Иргэн eID апп-аас токеноо цуцлаад шинээр олгуулна. GET-д ч гарч болно (гэрчилгээ гараагүй); session-ий tokenCertificate.errorCode-д ч ирнэ
409 (GET)SESSION_NOT_COMPLETE · SESSION_FAILED · ISSUANCE_FAILEDИргэн батлаагүй · татгалзсан/хугацаа дууссан · CA талын алдаа (мессежид шалтгаан)
410 (GET)RESULT_EXPIREDОлголтын хугацаа дууссан — шинээр эхлүүлнэ
429RATE_LIMITED(RP, иргэн) 3/цаг, RP 60/10 мин — Retry-After
503ISSUANCE_PENDING (GET) · STATE_UNAVAILABLEОлгогдож байна / түр — Retry-After-ийн дараа дахин

9.2 USB токеноор нэвтрэх

Иргэн USB токен дээрх AUTH гэрчилгээгээр (§9.1) таны үйлчилгээнд утасгүйгээр нэвтэрнэ. Challenge-ийг таны backend авна. Иргэний компьютер дээрх клиент (PKCS#11) токеноор зурна. Гарын үсгийг backend тань CA-аар шалгуулна. RP-д AUTH эрх хэрэгтэй — үгүй бол 403.

  1. POST /token/auth/challenge (SDK: TS eid.token.authChallenge(), Go c.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 минут, нэг удаагийн
  2. Токеноор зурах — зурах утга = SHA-256("eID-TOKEN-AUTH" ‖ rpUUID ‖ challenge) (challenge нь base64-ээс тайлсан 32 байт). AUTH түлхүүрээр CKM_ECDSA; гарын үсэг нь түүхий r‖s (64 байт, DER биш).
  3. 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) (Go c.Token.AuthVerify(ctx, ch, certDER, rs)) — challenge-ийг бүтнээр нь өгнө; CA руу илгээхээс өмнө rpUUID = таны RP, r‖s нь дээрх утга дээр гэрчилгээний түлхүүрээр, гэрчилгээ Root хүртэл гинж, 04 олгогч, KeyUsage digitalSignature + EKU clientAuth-аар хүчинтэйг шалгана.
HTTPcodeХэзээ
400REQUEST_INVALID · CERT_INVALID · SIGNATURE_INVALIDТалбар буруу/танихгүй · гэрчилгээ задрахгүй / eID Personal CA-аас олгогдоогүй / EC P-256 биш / clientAuth биш / бүртгэлд алга эсвэл USB токены AUTH гэрчилгээ биш (утасны гэрчилгээ хүлээн авахгүй) · гарын үсэг таарахгүй
403RP_REQUIRED · (кодгүй)RP танигдаагүй · AUTH эрхгүй
409CHALLENGE_USED · CERT_NOT_ACTIVEChallenge аль хэдийн ашиглагдсан · гэрчилгээ идэвхгүй, цуцлагдсан эсвэл хугацаа дууссан
410CHALLENGE_EXPIRED2 минут өнгөрсөн — шинэ challenge авна
429RATE_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-р алхам локал:

  1. prepare — PAdES SDK-аар (§5.2.2, жишээ §5.2.3) токены SIGN гэрчилгээтэй: prepare(pdf, {signerCert, fileName, claimedRole?, …}) → dtbs (32 байт), state.
  2. Токеноор зурах — клиент PKCS#11 C_Sign, CKM_ECDSA, оролт = dtbs (дахин hash хийхгүй) → түүхий r‖s (64 байт). SDK PKCS#11-д хүрэхгүй — таны апп.
  3. complete — complete(state, r‖s) → гарын үсэгтэй PDF (TSA, OCSP/CRL, DSS, өөрийн шалгалт).
  4. Бүртгэл — RP backend POST /pdf/signatures (§5.2.6), "medium": "token", sessionID-гүй. CA гэрчилгээ нь идэвхтэй токены гэрчилгээ эсэх, байгууллагын токенд үүрэг (claimedRole) тухайн байгууллагынх эсэхийг шалгана. SIGN эрх.

Байгууллагын токенд claimedRole-ийг §5.2.7-ийн дагуу төлөөллийн хариунаас авна — токенд session байхгүй тул төлөөллийг бүртгэлийн мөчид шалгана.

§10

Байгууллагын нэрийн өмнөөс гарын үсэг (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 Урсгал

  1. Нэвтрүүлэх — иргэнийг ердийн аргаар нэвтрүүлж (§4, §8) PNOMN-…-ийг нь мэднэ.
  2. Төлөөлж чадах байгууллагуудыг авах — зөвхөн 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/signatures mn ба en хоёуланг хүлээн авна — ?lang=en-ээр авсныг ч шууд ашиглаж болно.

  3. Иргэн сонгоно — хувиараа эсвэл аль нэг байгууллагаар. Сонголтыг та хадгалж болно, гэхдээ энэ нь итгэх эх сурвалж биш — сервер гарын үсэг бүрд дахин шалгана.
  4. 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). Иргэний батлах дэлгэцэд «Та Жишээ ХХК-ийг төлөөлөн гарын үсэг зурж байна» гарна.

  5. Үр дүн — GET /session/{sessionID}; COMPLETE/OK үед onBehalfOf блок (orgEtsi, orgName, role, roleLabel, rightType, rightTypeLabel) ба signature.value ирнэ. onBehalfOf.orgEtsi = таны хүссэн байгууллага гэдгийг заавал шалгана; rightType хоосон ирвэл тэр мөчид эрх баталгаажаагүй (хасагдсан эсвэл түр алдаа) — /organization/representations-аар дахин шалгана. role нь MANAGER-т хоосон байж болно.
  6. 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 /Reasonprepare-ийн 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/pdfsignatures[].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 очихгүй.

HTTPcodeХэзээТа юу хийх
403REPRESENTATION_DENIEDЗурагч тухайн байгууллагын идэвхтэй төлөөлөгч биш (хасагдсан, татгалзсан, огт байхгүй)Хувиараа зурахыг санал болгох, эсвэл ADMIN-аар нэмүүлэх
403REPRESENTATION_PENDINGADMIN нэмсэн ч иргэн PIN2-оор баталгаажуулаагүйИргэн аппаараа баталгаажуулна
403REPRESENTATION_EXPIREDТөлөөллийн хугацаа эхлээгүй / дууссанADMIN эсвэл оператороор сунгуулна
403ORG_NOT_ACTIVEБайгууллага ACTIVE биш (хүлээгдэж буй, түдгэлзсэн, цуцлагдсан)Оператортой холбогдоно
403SIGNER_UNIDENTIFIEDЗурагч тодорхойгүй (иргэн тодорхойгүй QR session-д onBehalfOf)Иргэнийг etsi/documentNumber-аар эхлүүлнэ
404ORG_NOT_FOUNDonBehalfOf eID-д бүртгэлгүйБайгууллагыг аппаар холбуулна (§10.1)
403 (POST /pdf/signatures)REPRESENTATION_REVOKEDSession-оос хойш, бүртгэлээс өмнө эрх хасагдсан, эсвэл байгууллага идэвхгүй/бүртгэлгүй болсон (бүртгэлийн үед төлөөллийн бүх татгалзал энэ кодоор); файл гарын үсэгтэй ч /verify-д бүртгэгдээгүйЭрх сэргэсэн бол шинээр зуруулна
409 (POST /pdf/signatures)CLAIMED_ROLE_MISMATCHCMS-ийн мэдүүлсэн үүрэг тэр мөчийн төлөөлөл эсвэл session-ий onBehalfOf-оос зөрсөнclaimedRole-ийг төлөөллийн хариунаас (mn) яг хэвээр авна (§5.2.7)
400(кодгүй)Гэрчилгээ сонгуулах (certificate-choice) хүсэлтэд onBehalfOfЗөвхөн нэвтрэлт, гарын үсгийн хүсэлтэд өгнө

PDF-ийн бусад алдаа: бүртгэл §5.2.6, PAdES SDK §5.2.4.

10.5 Олон талт гэрээ

Хоёр байгууллага нэг гэрээнд дараалан зурна — гарын үсэг бүр өөрийн мэдүүлсэн үүрэг, /Reason-той:

  1. А байгууллага — А-ийн төлөөлөгчийн гэрчилгээ, claimedRole (NTRMN-A-ийн мөр) → prepare → hash session (onBehalfOf = NTRMN-A) → complete → бүртгэл (revIndex: 1). Сангийн docId = D.
  2. Б байгууллага — 1-р алхмын гаралтыг өөрчлөлтгүй prepare-д (Б-ийн төлөөлөгчийн гэрчилгээ, NTRMN-B-ийн claimedRole) — сан D-г баримтаас уншина → hash session (onBehalfOf = NTRMN-B) → complete → бүртгэл (docID: D, revIndex: 2, priorCmsSha256 = А-ийн CMS-ийн hash).
  3. Шалгах — эцсийн 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-ийн бизнесийн дүрэм.
§11 · Алдаа

Ойлгомжтой алдааны кодууд.

HTTP
401Bearer эсвэл mTLS403IP allowlist429rate_limited · Retry-After: 60503Хурдны хязгаар · busy
Device link
DEVICE_LINK_INVALIDDEVICE_LINK_EXPIREDDEVICE_LINK_NO_CALLBACKDEVICE_LINK_TYPE_CONFLICTSESSION_NOT_ACTIVEFLOW_TYPE_MISMATCH
Байгууллага · бүртгэл
ORG_NOT_FOUNDORG_NOT_ACTIVEREPRESENTATION_*SIGNER_UNIDENTIFIEDCALLBACK_HOST_INVALIDSUBJECT_NOT_ASCII

Дэлгэрэнгүй

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. Мессежид шалтгаан бий.
401Authorization: Bearer алга (зөвхөн cert-тэй ч); secret буруу/хүчингүй; mTLS заавал RP cert-гүй эсвэл cert тухайн RP-ийнх биш/цуцлагдсан
403IP 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-ээр нэвтрээгүй
404Session олдсонгүй (эсвэл өөр RP-ийнх); иргэн/байгууллага олдсонгүй (onBehalfOf-д ORG_NOT_FOUND); PDF бүртгэлийн DOC_NOT_FOUND; «Миний QR»-ийн QR_TOKEN_INVALID (§4.5)
409NO_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)
410RESULT_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 / 502POST /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-ийг хүндэтгэнэ
499Long-poll дуусахаас өмнө та холболтоо тасалсан
503Proxy-ийн хурдны хязгаар эсвэл бүртгэлийн үйлчилгээ түр хүрэхгүй — түр хүлээгээд дахин; 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-тэй хамт мэдэгдэнэ
§12 · Endpoint лавлах

rp.eidmongolia.mn

Бүх хүсэлтэд Authorization: Bearer rp_sk_… ба IP allowlist шаардлагатай.

МетодЗамТайлбар
POST/authentication/notification/etsi/{id}Push · ETSI, регистр, иргэний дугаар
POST/authentication/notification/document/{no}Push · төхөөрөмжийн дугаараар
POST/authentication/device-link/anonymousQR / Web2App / App2App · нэргүй
POST/authentication/device-link/etsi/{id}Device link · тодорхой иргэн
POST/authentication/qr«Миний QR» хүсэлт
POST/signature/device-link/…Гарын үсэг · sessionType: sign
POST/signature/certificate-choice/…Гэрчилгээ сонгох
GET/session/{sessionID}?timeoutMs=Үр дүн · long-poll ≤ 120 с
GETca.eidmongolia.mn/.well-known/eidDiscovery · endpoint, алгоритм, TTL

Бүрэн лавлах

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/anonymousQR / 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/signaturesPDF гарын үсгийн бүртгэл (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/verifyUSB токеноор нэвтрэх (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/eidDiscovery
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/caCRL: иргэн · байгууллага/тамга · завсрын CA
GET http://crl.eidmongolia.mn/ca/gerege-issuing-ca.cerGerege Issuing CA (завсрын гэрчилгээ)
GET http://crl.eidmongolia.mn/ca/eid-mongolia-issuing-ca.cer · …/ca/eid-mongolia-org-issuing-ca.cereID Mongolia Issuing CA (иргэн) · Organization Issuing CA (байгууллага, тамга)
POST https://tsa.timeserver.mn/RFC 3161 цагийн тэмдэг (TSA) — PAdES complete (discovery endpoints.tsa, §3)
§13 · SDK

Аюулгүй байдал нь бэлэн суусан.

TypeScriptБүрэн
npm i @gerege-systems/eid-mongolia-sdkEidClient, createEidLogin, device-link builder, QR ticker, waitForResult, <eid-login-button>. CDN-ээр ч ашиглана.
GoБүрэн
eid-mongolia-sdk-goeid.NewClient, eidlogin, BuildDeviceLink, TickQRDeviceLink, OCSP checker.
.NETХэсэгчлэн
Pades · QrPDF гарын үсэг ба QR задлах. RP-API клиент одоогоор байхгүй.
Decoy sessionRate limitHttpOnly cookieOCSP шалгалтRedis store7 хэлтэй товч

Дэлгэрэнгүй

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-р хэсгийн шалгалтыг өөрөө хэрэгжүүлнэ.

§14

Туршилт ба тусламж

  • Амьд 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 нэг-нэгээр.