@gerege-systems/eid-mongolia-sdk 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,8 +4,10 @@
4
4
  (authentication) ба хууль ёсны цахим гарын үсэг (qualified signature) хийлгэх Node.js/TypeScript сан.
5
5
  RP-API v3, Smart-ID нийцтэй. Бүрэн баримт: <https://developer.eidmongolia.mn/>.
6
6
 
7
- > ⚠️ Зөвхөн **backend**-д ажиллана (`@gerege-systems/eid-mongolia-sdk/browser`-ийн legacy device-link туслахаас бусад).
8
- > API secret (`rp_sk_…`) браузер/гар утсанд хэзээ ч задлахгүй.
7
+ > ⚠️ SDK **backend**-д ажиллана: бүх дуудлага API secret (`rp_sk_…`)-ээр баталгаажна, secret-ийг браузер/гар утсанд
8
+ > хэзээ ч задлахгүй. Браузерт зөвхөн таны backend-ийн өгсөн холбоос/QR/товч харагдана — «eID-ээр нэвтрэх» товч ч
9
+ > энгийн холбоос, `code` → `id_token` солих нь backend-д (§1a). `@gerege-systems/eid-mongolia-sdk/browser`-ийн
10
+ > legacy device-link туслахууд 2026-10-07-оос ажиллахгүй (§4).
9
11
 
10
12
  ## Суулгах
11
13
 
@@ -17,9 +19,11 @@ Node.js ≥ 22 (global `fetch`, `node:crypto`).
17
19
 
18
20
  ## 1. RP бүртгүүлэх
19
21
 
20
- `POST https://ca.eidmongolia.mn/v3/rp-applications` (эсвэл [developer.eidmongolia.mn](https://developer.eidmongolia.mn)) —
21
- оператор баталсны дараа **relyingPartyUUID** ба **API secret** (`rp_sk_…`) олгогдоно; secret **зөвхөн нэг удаа**
22
- харагдана. Дэлгэрэнгүй: <https://developer.eidmongolia.mn/#registration>.
22
+ RP-г eID Mongolia-гийн оператор бүртгэнэ — хүсэлтээ <eIDsupport@gerege.com> эсвэл 7777-3773-аар гаргана
23
+ (нийтийн `POST /v3/rp-applications` 2026-10-14-нөөс засвартай: `503 rp_applications_maintenance`). Оператор
24
+ баталсны дараа **relyingPartyUUID** ба **API secret** (`rp_sk_…`) олгогдоно; secret **зөвхөн нэг удаа** харагдана.
25
+ Шаардлагатай мэдээлэл (байгууллага, зорилго `AUTH`/`SIGN`, `callbackHosts`, OIDC redirect URI…):
26
+ <https://developer.eidmongolia.mn/#registration>.
23
27
 
24
28
  ## 1a. «eID-ээр нэвтрэх» (OpenID Connect)
25
29
 
@@ -28,6 +32,11 @@ Node.js ≥ 22 (global `fetch`, `node:crypto`).
28
32
  (Authorization Code + PKCE `S256`) `id_token` өгнө — Google/Apple нэвтрэлттэй ижил. Утасны хөтчөөс дарахад
29
33
  хуудасгүйгээр шууд eID апп нээгдэнэ. Гэрээ: <https://developer.eidmongolia.mn/#oidc>.
30
34
 
35
+ **Хаана юу ажиллах.** Товч нь браузерт (энгийн `<a href>` таны backend-ийн `/auth/eid/login` руу); `EidOidcClient`
36
+ нь backend-д — authorize URL угсрах, callback-д `code`-ийг `client_secret`-ээр `id_token` болгон солих, гарын үсгийг
37
+ шалгах. eID-ийн OIDC client нь одоогоор зөвхөн **confidential client** (`client_secret_basic`): backend-гүй SPA эсвэл
38
+ native апп дангаараа token endpoint-ийг дуудаж чадахгүй — native апп ч `code`-ийг өөрийн backend-ээр солино (доор).
39
+
31
40
  **Бүртгэл.** `client_id` = таны RP UUID (`relyingPartyUUID`), `client_secret` = RP API secret (`rp_sk_…`).
32
41
  RP-д `AUTH` эрх, OIDC асаалттай, `redirect_uri` (https, ≤ 10) бүртгэлтэй байна — authorize/token-ий `redirect_uri`
33
42
  бүртгэлтэйтэй **тэмдэгт бүрээрээ** таарах ёстой. Иргэний ETSI дугаар (`pnomn` claim) хэрэгтэй бол `PNOMN` эрх
@@ -77,15 +86,23 @@ callback → `oidc.handleCallback(request.nextUrl.searchParams, tx)` (зөвхө
77
86
  хэрэглэгчийн tap-аас эхэлсэн redirect гинжид eID аппыг нээнэ.
78
87
 
79
88
  ```html
80
- <a class="eid-login" href="/auth/eid/login">eID-ээр нэвтрэх</a>
89
+ <a class="eid-login" href="/auth/eid/login">
90
+ <img src="/img/eid-mongolia.png" alt="" width="20" height="20">
91
+ eID Mongolia-аар нэвтрэх
92
+ </a>
81
93
  <style>
82
- .eid-login { display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
83
- padding: 0 1.25rem; border-radius: 8px; background: #0b3d91; color: #fff; font: 600 1rem/1 system-ui, sans-serif;
84
- text-decoration: none; }
85
- .eid-login:focus-visible { outline: 2px solid #0b3d91; outline-offset: 2px; }
94
+ .eid-login { display: inline-flex; align-items: center; justify-content: center; gap: .5rem; min-height: 44px;
95
+ padding: 0 1.25rem; border: 1px solid #d0d5dd; border-radius: 6px; background: #fff; color: #111827;
96
+ font: 500 1rem/1 system-ui, sans-serif; text-decoration: none; }
97
+ .eid-login:hover { background: #f5f7fa; }
98
+ .eid-login:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
86
99
  </style>
87
100
  ```
88
101
 
102
+ Бичвэр «eID Mongolia-аар нэвтрэх», зүүн талд eID Mongolia лого (<https://developer.eidmongolia.mn/logo-96.png>-ийг
103
+ татаж өөрийн хостоос үйлчил). Google/Apple-ийн товч шиг саармаг (цагаан, хүрээтэй) — таны сайтын бусад нэвтрэх
104
+ товчтой зэрэгцэж болно. Жишээ: <https://demo.eidmongolia.mn>.
105
+
89
106
  `createAuthorization({ display: "page" })` — утсан дээр ч нэвтрэх хуудас (QR / push) харуулна (ж: таблет киоск).
90
107
 
91
108
  **`id_token` claims** (`EidIdTokenClaims`): `sub` (pairwise — RP тус бүрд өөр, иргэнд тогтмол), `acr`
@@ -123,7 +140,7 @@ const eid = new EidClient({
123
140
  trust: { trustAnchorsPem: [process.env.EID_NATIONAL_ROOT_CA_PEM!] }, // ← Mongolian National Root CA, production-д ЗААВАЛ
124
141
  });
125
142
 
126
- // ETSI (PNOMN-<иргэний дугаар>) / регистр / иргэний дугаар — аль нь ч болно (сервер таьна)
143
+ // ETSI (PNOMN-<иргэний дугаар>) / регистр / иргэний дугаар — аль нь ч болно (сервер танина)
127
144
  const s = await eid.auth.notificationByEtsi("PNOMN-12345678", [
128
145
  { type: "displayTextAndPIN", displayText60: "Хаан Банк-д нэвтрэх" },
129
146
  ]);
@@ -134,8 +151,19 @@ const result = await eid.session.waitForResult(s.sessionId); // long-poll
134
151
  const who = eid.validator.validateAuth(result, s.acsp); // ← гинж + ACSP_V2 крипто-баталгаажуулна
135
152
 
136
153
  console.log(who.documentNumber, who.subject); // баталгаажсан иргэн
154
+ const { name, serialNumber } = who.parsedSubject; // "Болд Бат", "PNOMN-12345678" (givenName/surname/commonName/country…)
137
155
  ```
138
156
 
157
+ Subject-ийн талбарууд (`parsedSubject`, `validateSign`-д ч мөн) нь `parseSubject(cert)`-ээс — OID 2.5.4.42/4/3/5/6/10/97,
158
+ UTF8String/PrintableString/BMPString. `parseSubject` нь зөвхөн задлалт: баталгаажаагүй гэрчилгээний нэрэнд бүү итгэ.
159
+
160
+ > **Ижил утсан дээр push хэрэглэхгүй.** Push (`notificationBy*`)-ийн хүсэлт eID аппад мэдэгдэл / «хүлээгдэж буй»
161
+ > жагсаалтаар ирдэг; Smart-ID ба eID-д Notification төрлийн device link **байхгүй** (CA `docs/DEVICE_LINK_V3.md` §3).
162
+ > Хуучин `/dl?sessionId&vc` холбоос (`deviceLink`) push session-д ч юу ч өгөхгүй (сервер `DEVICE_LINK_REQUIRED`).
163
+ > Иргэн RP-ийг eID апптай ижил утсан дээр нээсэн бол push-ийн оронд **device-link Web2App** урсгал хэрэглэ
164
+ > (`auth.deviceLink*` / `pdf.prepare({ flow: "device-link" })` + `buildDeviceLink(…, "Web2App")`, §4) — товч аппыг шууд
165
+ > нээж, callback-аар буцна. Push-д «eID аппаа нээнэ үү» гэсэн текст л хангалттай.
166
+
139
167
  ## 3. Гарын үсэг (qualified signature)
140
168
 
141
169
  ```ts
@@ -181,7 +209,7 @@ try {
181
209
  // MUST: endResult OK, pdf.documentStatus READY, pdf.signer.onBehalfOf = хүссэн байгууллага,
182
210
  // татсан файлын SHA-256 = pdf.outSha256, validation.indication "valid", ltv true.
183
211
  if (r.pdf?.documentStatus !== "READY" || r.pdf.signer?.onBehalfOf !== "NTRMN-6235972") throw new Error("…");
184
- const signed = await eid.pdf.document(s.sessionId);
212
+ const signed = await eid.pdf.documentVerified(s.sessionId, r.pdf); // SHA-256 ≠ pdf.outSha256 → ValidationError
185
213
  console.log(r.pdf.signer.claimedRole); // "Гүйцэтгэх захирал, Гэрэгэ Системс ХХК (NTRMN-6235972)"
186
214
  } catch (e) {
187
215
  if ((e instanceof ForbiddenError || e instanceof ApiError) && e.code) {
@@ -195,6 +223,10 @@ try {
195
223
  // — CMS/PDF-ийг та угсардаг тул мэдүүлсэн үүргийг (signer-attributes-v2) өөрөө нэмнэ.
196
224
  ```
197
225
 
226
+ `prepare`-ийн хариу `digest` (DTBS, base64) — иргэний зурах 32 байт, push ба device-link хоёуланд (хуучин CA push-д
227
+ `undefined`). Иргэний гарын үсгийг өөрөө шалгах бол: `eid.validator.validateSign(r, s.digest!)`. Гарын үсэгтэй PDF-ийг
228
+ `pdf.document()`-ийн оронд `pdf.documentVerified(sessionId, r.pdf)`-аар тат — `READY` + `outSha256` тулгалтыг хийнэ.
229
+
198
230
  Олон талт гэрээ: А байгууллагын гаралтыг өөрчлөлтгүй Б-ийн `prepare`-д (`docID`, өөр `onBehalfOf`) илгээнэ.
199
231
  Session-ий `onBehalfOf` блок (`role`/`rightType`) нь хариу угсрах үеийн бүртгэл; `pdf.signer.onBehalfOf`/`claimedRole`
200
232
  нь баримтад бичигдсэн утга.
@@ -226,7 +258,8 @@ import { buildDeviceLink, type DeviceLinkSession } from "@gerege-systems/eid-mon
226
258
  // backend — session эхлүүлээд бүх хариуг СЕРВЕРТ хадгална (sessionToken/sessionSecret/receivedAt/acsp)
227
259
  const s = await eid.auth.deviceLinkAnonymous(
228
260
  [{ type: "displayTextAndPIN", displayText60: "Хаан Банк-д нэвтрэх" }],
229
- { callbackUrl: "https://bank.example.mn/eid/callback?state=…" }, // товч харуулах бол
261
+ { callbackUrl: "https://bank.example.mn/auth/eid/callback?state=…" }, // товч харуулах бол; host нь callbackHosts-д,
262
+ // зам нь үргэлж /auth/eid/callback болж нормчлогдоно (зөвхөн query хадгалагдана), https заавал
230
263
  );
231
264
  store.put(s.sessionId, s);
232
265
  res.json({ sessionId: s.sessionId, vc: s.vc }); // браузерт ЗӨВХӨН эдгээр
@@ -275,10 +308,9 @@ eid.validator.validateAuth(result, s.acsp, {
275
308
  QR ба товч хоёуланг харуулсан хуудсанд: callback-аар ирсэн үр дүнг `Web2App`/`App2App`-аар, poll-оор ирснийг
276
309
  `QR`-аар шалга (товчоор нээгдсэн session зөвхөн callback-аар дуусна).
277
310
 
278
- > ⚠️ **Шилжилт.** eID Mongolia 2.2.3 (build 59)-өөс өмнөх апп v3 холбоосыг танихгүй. RP-ийн v3 рүү шилжилтийг
279
- > тохиргооны унтраалгаар (анхдагч legacy) хийж, build 59 store-д гарч `min_version=59` тавигдах мөчид асаана.
280
- > Legacy `deviceLink(session)` (`{deviceLinkBase}?sessionId=…&vc=…`) болон `/browser` entry-ийн
281
- > `openOrShowQR` **deprecated** — тэр мөчөөс сервер статик холбоосыг хүлээн авахгүй.
311
+ > ⚠️ **Legacy холбоос хасагдсан (2026-10-07).** Статик `deviceLink(session)` (`{deviceLinkBase}?sessionId=…&vc=…`)
312
+ > болон `/browser` entry-ийн `openOrShowQR`-ээр нээгдсэн session-д сервер утасны үйлдлийг `DEVICE_LINK_REQUIRED`-аар
313
+ > татгалзана — зөвхөн v3 (`buildDeviceLink`) ажиллана. Аппын доод хувилбар: iOS 2.2.3 (59), Android 70.
282
314
 
283
315
  ## Аюулгүй байдал — яагаад `validator` заавал
284
316
 
@@ -293,9 +325,14 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
293
325
  - **auth** → ACSP_V2 payload (`smart-id|ACSP_V2|serverRandom|rpChallenge|userChallenge|B64(rpName)|B64(brokeredRpName)|B64(SHA-256(interactions))|interactionTypeUsed|initialCallbackUrl|flowType`, hash = хүсэлтийн `hashAlgorithm`) дээр — `flowType`, `userChallenge`, `serverRandom` ба алгоритмын downgrade-ийг мөн шалгана;
294
326
  `expectedFlowType` өгвөл `flowType` эхлүүлсэн урсгалтай ЯГ таарах, Web2App/App2App-д `userChallengeVerifier` заавал
295
327
  `initialCallbackUrl`-ийг хариуны `signature.initialCallbackUrl`-аас авна (CA урсгалаар шийднэ: QR/Notification →
296
- `""`, Web2App/App2App → таны callback; шилжилтийн үед legacy `/dl` ба push-д таны callback) — зөвхөн `""` эсвэл
328
+ `""`, Web2App/App2App → таны callback) — зөвхөн `""` эсвэл
297
329
  таны илгээсэн callback байхад, Web2App/App2App-д заавал таны callback; өөр утга → татгалзана
298
330
  - **sign** → RP-ийн өгсөн digest дээр
331
+ - алгоритм нь гэрчилгээний түлхүүртэй уях: `ecdsa-with-SHA256` зөвхөн EC P-256 түлхүүрт, RSA алгоритмууд зөвхөн
332
+ RSA түлхүүрт (alg confusion хаалт). Hash нь үргэлж хүссэнтэй ЯГ тэнцүү. RP-API session-ий гарын үсэг CA дээр
333
+ SplitKey RSA (одоогийн бүх мобайл гэрчилгээ) эсвэл legacy threshold ECDSA P-256-аас гарна; ECDSA credential
334
+ зөвхөн SHA-256 digest зурдаг (өөр hash хүссэн session-д сервер гарын үсэг гаргахгүй), тиймээс SHA-384/512
335
+ хүссэн session-д `ecdsa-with-SHA256` хариу ирвэл хуурамч гэж татгалзана.
299
336
 
300
337
  Аль нэг алхам бүтэлгүйтвэл `ValidationError` шиднэ — хариунд **итгэхгүй**.
301
338
 
@@ -216,9 +216,8 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
216
216
  * QR-д эсвэл «eID аппаар нээх» товчинд тавина.
217
217
  *
218
218
  * @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
219
- * eID Mongolia 2.2.3 (build 59) гармагц сервер унтраана (`EID_DEVICE_LINK_LEGACY=false`). Шинэ код
220
- * backend-д {@link buildDeviceLink} (v3) ашиглана; build 59-өөс өмнөх апп v3 холбоосыг танихгүй тул
221
- * RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
219
+ * Сервер 2026-10-07-нд хассан: ийм холбоосоор нээгдсэн session-д утасны үйлдлийг `DEVICE_LINK_REQUIRED`-аар
220
+ * татгалзана. Backend-д {@link buildDeviceLink} (v3) ашиглана.
222
221
  *
223
222
  * @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
224
223
  */
@@ -251,6 +250,8 @@ interface OpenOrShowQROptions {
251
250
  * Link-ийг алгасаж /dl хуудсыг нээдэг; тэр хуудас «Аппаар нээх» товч ба store холбоосыг санал болгоно.
252
251
  *
253
252
  * @throws {DeviceLinkError} холбоос угсрах боломжгүй бол ({@link deviceLink}).
253
+ * @deprecated Legacy {@link deviceLink} холбоос дээр тулгуурладаг — сервер 2026-10-07-нд хассан тул ажиллахгүй.
254
+ * QR-ийг backend-ийн `buildDeviceLink` (v3)-ээр, утсан дээр Web2App холбоосыг энгийн `<a href>`-ээр харуулна.
254
255
  */
255
256
  declare function openOrShowQR(session: DeviceLinkInput, opts: OpenOrShowQROptions): "opened" | "qr";
256
257
 
@@ -317,4 +318,4 @@ declare class ValidationError extends EidError {
317
318
  declare class DeviceLinkError extends EidError {
318
319
  }
319
320
 
320
- export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfDocumentStatus as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfSessionBlock as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
321
+ export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfSessionBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfDocumentStatus as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
@@ -216,9 +216,8 @@ type DeviceLinkInput = Pick<DeviceLinkSession, "sessionId" | "vc" | "deviceLinkB
216
216
  * QR-д эсвэл «eID аппаар нээх» товчинд тавина.
217
217
  *
218
218
  * @deprecated Legacy статик холбоос — холбоосын төрөл (QR/Web2App) хамгаалагдаагүй, relay-д нээлттэй.
219
- * eID Mongolia 2.2.3 (build 59) гармагц сервер унтраана (`EID_DEVICE_LINK_LEGACY=false`). Шинэ код
220
- * backend-д {@link buildDeviceLink} (v3) ашиглана; build 59-өөс өмнөх апп v3 холбоосыг танихгүй тул
221
- * RP v3 рүү шилжих нь тохиргооны унтраалгаар (анхдагч legacy) хийгдэнэ.
219
+ * Сервер 2026-10-07-нд хассан: ийм холбоосоор нээгдсэн session-д утасны үйлдлийг `DEVICE_LINK_REQUIRED`-аар
220
+ * татгалзана. Backend-д {@link buildDeviceLink} (v3) ашиглана.
222
221
  *
223
222
  * @throws {DeviceLinkError} deviceLinkBase байхгүй / http(s) биш, sessionId UUID биш, vc 5 орон биш.
224
223
  */
@@ -251,6 +250,8 @@ interface OpenOrShowQROptions {
251
250
  * Link-ийг алгасаж /dl хуудсыг нээдэг; тэр хуудас «Аппаар нээх» товч ба store холбоосыг санал болгоно.
252
251
  *
253
252
  * @throws {DeviceLinkError} холбоос угсрах боломжгүй бол ({@link deviceLink}).
253
+ * @deprecated Legacy {@link deviceLink} холбоос дээр тулгуурладаг — сервер 2026-10-07-нд хассан тул ажиллахгүй.
254
+ * QR-ийг backend-ийн `buildDeviceLink` (v3)-ээр, утсан дээр Web2App холбоосыг энгийн `<a href>`-ээр харуулна.
254
255
  */
255
256
  declare function openOrShowQR(session: DeviceLinkInput, opts: OpenOrShowQROptions): "opened" | "qr";
256
257
 
@@ -317,4 +318,4 @@ declare class ValidationError extends EidError {
317
318
  declare class DeviceLinkError extends EidError {
318
319
  }
319
320
 
320
- export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfDocumentStatus as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfSessionBlock as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
321
+ export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfSessionBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfDocumentStatus as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
@@ -1 +1 @@
1
- export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-Db82DlYN.cjs';
1
+ export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.cjs';
package/dist/browser.d.ts CHANGED
@@ -1 +1 @@
1
- export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-Db82DlYN.js';
1
+ export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.js';
package/dist/index.cjs CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  isMobileUserAgent: () => isMobileUserAgent,
52
52
  openOrShowQR: () => openOrShowQR,
53
53
  parseSessionResult: () => parseSessionResult,
54
+ parseSubject: () => parseSubject,
54
55
  pkceChallenge: () => pkceChallenge,
55
56
  qrDeviceLinkTicker: () => qrDeviceLinkTicker,
56
57
  randomChallenge: () => randomChallenge,
@@ -480,6 +481,7 @@ var SessionApi = class {
480
481
  };
481
482
 
482
483
  // src/pdf.ts
484
+ var import_node_crypto2 = require("crypto");
483
485
  function rec3(v) {
484
486
  return v && typeof v === "object" ? v : {};
485
487
  }
@@ -512,10 +514,12 @@ var PdfApi = class {
512
514
  const receivedAt = Date.now();
513
515
  const vc = rec3(r.vc).value;
514
516
  const dl = rec3(r.deviceLink);
517
+ const digest = typeof r.digest === "string" && r.digest ? r.digest : typeof dl.digest === "string" && dl.digest ? dl.digest : void 0;
515
518
  return {
516
519
  sessionId: String(r.sessionID),
517
520
  docId: String(r.docID),
518
521
  vc: vc != null ? String(vc) : null,
522
+ ...digest !== void 0 ? { digest } : {},
519
523
  deviceLink: dl.sessionToken ? {
520
524
  sessionToken: String(dl.sessionToken),
521
525
  sessionSecret: String(dl.sessionSecret),
@@ -533,6 +537,26 @@ var PdfApi = class {
533
537
  document(sessionId) {
534
538
  return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
535
539
  }
540
+ /**
541
+ * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
542
+ * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
543
+ *
544
+ * @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
545
+ */
546
+ async documentVerified(sessionId, expected) {
547
+ let want;
548
+ if (typeof expected === "string") {
549
+ want = expected;
550
+ } else {
551
+ if (expected.documentStatus !== "READY") throw new ValidationError(`pdf.documentStatus ${expected.documentStatus} \u2260 READY`);
552
+ want = expected.outSha256;
553
+ }
554
+ if (!want || !/^[0-9a-fA-F]{64}$/.test(want)) throw new ValidationError("pdf.outSha256 \u0430\u043B\u0433\u0430 \u044D\u0441\u0432\u044D\u043B SHA-256 hex \u0431\u0438\u0448");
555
+ const bytes = await this.document(sessionId);
556
+ const got = (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
557
+ if (got !== want.toLowerCase()) throw new ValidationError("\u0422\u0430\u0442\u0441\u0430\u043D PDF-\u0438\u0439\u043D SHA-256 \u043D\u044C pdf.outSha256-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
558
+ return bytes;
559
+ }
536
560
  };
537
561
 
538
562
  // src/organization.ts
@@ -550,7 +574,114 @@ var OrganizationApi = class {
550
574
  };
551
575
 
552
576
  // src/validator.ts
553
- var import_node_crypto2 = require("crypto");
577
+ var import_node_crypto4 = require("crypto");
578
+
579
+ // src/subject.ts
580
+ var import_node_crypto3 = require("crypto");
581
+ var OIDS = {
582
+ "2.5.4.42": "givenName",
583
+ "2.5.4.4": "surname",
584
+ "2.5.4.3": "commonName",
585
+ "2.5.4.5": "serialNumber",
586
+ "2.5.4.6": "country",
587
+ "2.5.4.10": "organization",
588
+ "2.5.4.97": "organizationIdentifier"
589
+ };
590
+ function tlv(buf, pos, limit) {
591
+ if (pos + 2 > limit) throw new ValidationError("subject: DER \u0442\u043E\u043B\u0433\u043E\u0439 \u0431\u043E\u0433\u0438\u043D\u043E");
592
+ const tag = buf[pos];
593
+ if ((tag & 31) === 31) throw new ValidationError("subject: \u043E\u043B\u043E\u043D \u0431\u0430\u0439\u0442 tag \u0434\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439");
594
+ let len = buf[pos + 1];
595
+ let i = pos + 2;
596
+ if (len & 128) {
597
+ const n = len & 127;
598
+ if (n === 0 || n > 3) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u0443\u0440\u0443\u0443");
599
+ if (i + n > limit) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u043E\u0433\u0438\u043D\u043E");
600
+ len = 0;
601
+ for (let k = 0; k < n; k++) len = len * 256 + buf[i++];
602
+ }
603
+ if (i + len > limit) throw new ValidationError("subject: DER \u0430\u0433\u0443\u0443\u043B\u0433\u0430 \u0445\u0438\u043B\u044D\u044D\u0441 \u0433\u0430\u0440\u043D\u0430");
604
+ return { tag, start: i, end: i + len };
605
+ }
606
+ function oidOf(buf, t) {
607
+ if (t.tag !== 6 || t.end === t.start) throw new ValidationError("subject: OID \u0431\u0438\u0448");
608
+ const parts = [];
609
+ let v = 0;
610
+ for (let i = t.start; i < t.end; i++) {
611
+ const b = buf[i];
612
+ v = v * 128 + (b & 127);
613
+ if (!(b & 128)) {
614
+ if (parts.length === 0) parts.push(v < 80 ? Math.floor(v / 40) : 2, v < 80 ? v % 40 : v - 80);
615
+ else parts.push(v);
616
+ v = 0;
617
+ }
618
+ }
619
+ return parts.join(".");
620
+ }
621
+ function stringOf(buf, t) {
622
+ const bytes = buf.subarray(t.start, t.end);
623
+ switch (t.tag) {
624
+ case 12:
625
+ try {
626
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
627
+ } catch {
628
+ throw new ValidationError("subject: UTF8String \u0431\u0443\u0440\u0443\u0443 UTF-8");
629
+ }
630
+ case 19:
631
+ // PrintableString
632
+ case 22:
633
+ // IA5String
634
+ case 20:
635
+ return Buffer.from(bytes).toString("latin1");
636
+ case 30: {
637
+ if (bytes.length % 2) throw new ValidationError("subject: BMPString \u0443\u0440\u0442 \u0441\u043E\u043D\u0434\u0433\u043E\u0439");
638
+ return Buffer.from(bytes).swap16().toString("utf16le");
639
+ }
640
+ default:
641
+ return void 0;
642
+ }
643
+ }
644
+ function parseSubject(cert) {
645
+ const der = cert instanceof import_node_crypto3.X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
646
+ const top = tlv(der, 0, der.length);
647
+ if (top.tag !== 48) throw new ValidationError("subject: Certificate SEQUENCE \u0431\u0438\u0448");
648
+ const tbs = tlv(der, top.start, top.end);
649
+ if (tbs.tag !== 48) throw new ValidationError("subject: tbsCertificate SEQUENCE \u0431\u0438\u0448");
650
+ let pos = tbs.start;
651
+ let first = tlv(der, pos, tbs.end);
652
+ if (first.tag === 160) {
653
+ pos = first.end;
654
+ first = tlv(der, pos, tbs.end);
655
+ }
656
+ let field = first;
657
+ for (let k = 0; k < 4; k++) field = tlv(der, field.end, tbs.end);
658
+ if (field.tag !== 48) throw new ValidationError("subject: Name SEQUENCE \u0431\u0438\u0448");
659
+ const out = { name: null, attributes: {} };
660
+ for (let sp = field.start; sp < field.end; ) {
661
+ const rdn = tlv(der, sp, field.end);
662
+ if (rdn.tag !== 49) throw new ValidationError("subject: RDN SET \u0431\u0438\u0448");
663
+ for (let ap = rdn.start; ap < rdn.end; ) {
664
+ const atv = tlv(der, ap, rdn.end);
665
+ if (atv.tag !== 48) throw new ValidationError("subject: AttributeTypeAndValue SEQUENCE \u0431\u0438\u0448");
666
+ const oidT = tlv(der, atv.start, atv.end);
667
+ const valT = tlv(der, oidT.end, atv.end);
668
+ const oid = oidOf(der, oidT);
669
+ const value = stringOf(der, valT);
670
+ if (value !== void 0 && !(oid in out.attributes)) {
671
+ out.attributes[oid] = value;
672
+ const key = OIDS[oid];
673
+ if (key) out[key] = value;
674
+ }
675
+ ap = atv.end;
676
+ }
677
+ sp = rdn.end;
678
+ }
679
+ const full = [out.givenName, out.surname].filter((s) => s).join(" ");
680
+ out.name = full || out.commonName || null;
681
+ return out;
682
+ }
683
+
684
+ // src/validator.ts
554
685
  var ResponseValidator = class {
555
686
  anchors;
556
687
  intermediates;
@@ -560,8 +691,8 @@ var ResponseValidator = class {
560
691
  revocation;
561
692
  revocationMode;
562
693
  constructor(trust = {}) {
563
- this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto2.X509Certificate(p));
564
- this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto2.X509Certificate(p));
694
+ this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
695
+ this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
565
696
  this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
566
697
  this.skew = trust.clockSkewMs ?? 0;
567
698
  this.allowUntrusted = trust.allowUntrusted ?? false;
@@ -592,7 +723,7 @@ var ResponseValidator = class {
592
723
  throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
593
724
  }
594
725
  if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
595
- const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
726
+ const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
596
727
  const payload = buildAcspV2Payload({
597
728
  serverRandom,
598
729
  rpChallenge: acsp.rpChallenge,
@@ -611,6 +742,7 @@ var ResponseValidator = class {
611
742
  documentNumber: result.documentNumber,
612
743
  certificate: leaf,
613
744
  subject: leaf.subject,
745
+ parsedSubject: parseSubject(leaf),
614
746
  certificateLevel: String(result.certificateLevel ?? "")
615
747
  };
616
748
  }
@@ -621,7 +753,7 @@ var ResponseValidator = class {
621
753
  validateSign(result, digestB64, hashAlgorithm = "SHA-256") {
622
754
  const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
623
755
  const digest = Buffer.from(digestB64, "base64");
624
- const { alg, hash } = this.algorithmOf(result, null, hashAlgorithm);
756
+ const { alg, hash } = this.algorithmOf(result, leaf, null, hashAlgorithm);
625
757
  if (digest.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
626
758
  if (!verifyPrehashedSignature(digest, result.signatureValueB64, leaf.publicKey, alg, hash)) {
627
759
  throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u04E9\u0433\u0441\u04E9\u043D digest-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
@@ -631,27 +763,47 @@ var ResponseValidator = class {
631
763
  signatureValueB64: result.signatureValueB64,
632
764
  signatureAlgorithm: result.signatureAlgorithm,
633
765
  certificate: leaf,
634
- subject: leaf.subject
766
+ subject: leaf.subject,
767
+ parsedSubject: parseSubject(leaf)
635
768
  };
636
769
  }
637
770
  /**
638
- * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй тулгана (downgrade хаалт).
639
- * ecdsa-with-SHA256 (хуучин cert) хүссэн rsassa-pss-ийн оронд ирэхийг зөвшөөрнө — SHA-256-аар л.
771
+ * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
772
+ * (downgrade / alg confusion хаалт).
773
+ *
774
+ * CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
775
+ * RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
776
+ * hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
777
+ * зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
778
+ * Иймд:
779
+ * - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
780
+ * - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
781
+ * EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
640
782
  */
641
- algorithmOf(result, wantAlg, wantHash) {
783
+ algorithmOf(result, leaf, wantAlg, wantHash) {
642
784
  const alg = result.signatureAlgorithm ?? "";
785
+ const keyType = leaf.publicKey.asymmetricKeyType;
643
786
  let hash;
644
787
  if (alg === "rsassa-pss") {
645
788
  hash = String(result.signatureAlgorithmParameters?.hashAlgorithm ?? "");
789
+ if (!HASHES.has(hash)) throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 hashAlgorithm: ${hash}`);
646
790
  const mgf = result.signatureAlgorithmParameters?.maskGenAlgorithm?.parameters?.hashAlgorithm;
647
791
  if (mgf && mgf !== hash) throw new ValidationError("MGF1 hash \u043D\u044C hashAlgorithm-\u0430\u0430\u0441 \u04E9\u04E9\u0440");
648
792
  if (result.signatureAlgorithmParameters?.saltLength != null && result.signatureAlgorithmParameters.saltLength !== hashLen(hash)) {
649
793
  throw new ValidationError("saltLength \u2260 hLen");
650
794
  }
795
+ if (keyType !== "rsa" && keyType !== "rsa-pss") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
651
796
  } else if (/^sha(256|384|512)WithRSAEncryption$/.test(alg)) {
652
797
  hash = "SHA-" + alg.slice(3, 6);
798
+ if (keyType !== "rsa") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
653
799
  } else if (alg === "ecdsa-with-SHA256") {
654
800
  hash = "SHA-256";
801
+ if (keyType !== "ec" || leaf.publicKey.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
802
+ throw new ValidationError(`${alg} \u043D\u044C EC P-256 \u0431\u0443\u0441 \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
803
+ }
804
+ if (wantHash !== hash) {
805
+ throw new ValidationError(`ecdsa-with-SHA256 \u0445\u0430\u0440\u0438\u0443 ${wantHash} \u0445\u04AF\u0441\u0441\u044D\u043D session-\u0434 \u0431\u043E\u043B\u043E\u043C\u0436\u0433\u04AF\u0439 (ECDSA credential \u0437\u04E9\u0432\u0445\u04E9\u043D SHA-256)`);
806
+ }
655
807
  } else {
656
808
  throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 signatureAlgorithm: ${alg}`);
657
809
  }
@@ -672,7 +824,7 @@ var ResponseValidator = class {
672
824
  if (!result.certificateDerB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
673
825
  if (!result.signatureValueB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0430\u043B\u0433\u0430");
674
826
  if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
675
- const leaf = new import_node_crypto2.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
827
+ const leaf = new import_node_crypto4.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
676
828
  this.checkValidity(leaf);
677
829
  this.checkLevel(String(result.certificateLevel ?? ""));
678
830
  if (this.anchors.length === 0) {
@@ -750,16 +902,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
750
902
  return fromResponse;
751
903
  }
752
904
  function userChallengeOf(verifier) {
753
- return (0, import_node_crypto2.createHash)("sha256").update(verifier, "utf8").digest("base64url");
905
+ return (0, import_node_crypto4.createHash)("sha256").update(verifier, "utf8").digest("base64url");
754
906
  }
755
907
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
756
908
  const sig = Buffer.from(sigB64, "base64");
757
909
  const h = nodeHash(hash);
758
910
  if (alg === "rsassa-pss") {
759
- return (0, import_node_crypto2.verify)(h, payload, { key, padding: import_node_crypto2.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
911
+ return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
760
912
  }
761
- if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto2.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
762
- return (0, import_node_crypto2.verify)(h, payload, { key, padding: import_node_crypto2.constants.RSA_PKCS1_PADDING }, sig);
913
+ if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto4.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
914
+ return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PADDING }, sig);
763
915
  }
764
916
  function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
765
917
  const sig = Buffer.from(sigB64, "base64");
@@ -768,7 +920,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
768
920
  if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
769
921
  let em;
770
922
  try {
771
- em = (0, import_node_crypto2.publicDecrypt)({ key, padding: import_node_crypto2.constants.RSA_NO_PADDING }, sig);
923
+ em = (0, import_node_crypto4.publicDecrypt)({ key, padding: import_node_crypto4.constants.RSA_NO_PADDING }, sig);
772
924
  } catch {
773
925
  return false;
774
926
  }
@@ -841,6 +993,7 @@ function ecdsaP256VerifyPrehashed(digest, sig, key) {
841
993
  const R = ptAdd(ptMul(mod(z * w, n), [P256.gx, P256.gy]), ptMul(mod(r * w, n), Q));
842
994
  return R !== null && mod(R[0], n) === r;
843
995
  }
996
+ var HASHES = /* @__PURE__ */ new Set(["SHA-256", "SHA-384", "SHA-512"]);
844
997
  var DIGEST_INFO = {
845
998
  "SHA-256": Buffer.from("3031300d060960864801650304020105000420", "hex"),
846
999
  "SHA-384": Buffer.from("3041300d060960864801650304020205000430", "hex"),
@@ -851,7 +1004,7 @@ function mgf1(hash, seed, length) {
851
1004
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
852
1005
  const c = Buffer.alloc(4);
853
1006
  c.writeUInt32BE(i, 0);
854
- out.push((0, import_node_crypto2.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1007
+ out.push((0, import_node_crypto4.createHash)(nodeHash(hash)).update(seed).update(c).digest());
855
1008
  }
856
1009
  return Buffer.concat(out).subarray(0, length);
857
1010
  }
@@ -875,7 +1028,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
875
1028
  const psLen = emLen - hLen - sLen - 2;
876
1029
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
877
1030
  const salt = db.subarray(psLen + 1);
878
- const h2 = (0, import_node_crypto2.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1031
+ const h2 = (0, import_node_crypto4.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
879
1032
  return h.equals(h2);
880
1033
  }
881
1034
  function isCa(cert) {
@@ -970,7 +1123,7 @@ function openOrShowQR(session, opts) {
970
1123
  }
971
1124
 
972
1125
  // src/devicelinkv3.ts
973
- var import_node_crypto3 = require("crypto");
1126
+ var import_node_crypto5 = require("crypto");
974
1127
  var TYPES = ["QR", "Web2App", "App2App"];
975
1128
  var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
976
1129
  var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
@@ -1027,7 +1180,7 @@ function buildDeviceLink(p) {
1027
1180
  callback,
1028
1181
  unprotected
1029
1182
  ].join("|");
1030
- const authCode = (0, import_node_crypto3.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
1183
+ const authCode = (0, import_node_crypto5.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
1031
1184
  return `${unprotected}&authCode=${authCode}`;
1032
1185
  }
1033
1186
  function qrDeviceLinkTicker(input, onLink, opts) {
@@ -1040,7 +1193,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
1040
1193
  }
1041
1194
 
1042
1195
  // src/oidc.ts
1043
- var import_node_crypto4 = require("crypto");
1196
+ var import_node_crypto6 = require("crypto");
1044
1197
  var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
1045
1198
  var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
1046
1199
  var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
@@ -1297,7 +1450,7 @@ function formEncode(s) {
1297
1450
  function timingSafeEqualStr(a, b) {
1298
1451
  const x = Buffer.from(a, "utf8");
1299
1452
  const y = Buffer.from(b, "utf8");
1300
- return x.length === y.length && (0, import_node_crypto4.timingSafeEqual)(x, y);
1453
+ return x.length === y.length && (0, import_node_crypto6.timingSafeEqual)(x, y);
1301
1454
  }
1302
1455
  function decodeJsonPart(s) {
1303
1456
  try {
@@ -1348,6 +1501,7 @@ function callbackParams(input) {
1348
1501
  isMobileUserAgent,
1349
1502
  openOrShowQR,
1350
1503
  parseSessionResult,
1504
+ parseSubject,
1351
1505
  pkceChallenge,
1352
1506
  qrDeviceLinkTicker,
1353
1507
  randomChallenge,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-Db82DlYN.cjs';
2
- export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, P as PdfDocumentStatus, n as PdfSessionBlock, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-Db82DlYN.cjs';
1
+ import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PdfSessionBlock, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-B_XtyuFG.cjs';
2
+ export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, n as PdfDocumentStatus, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.cjs';
3
3
  import { X509Certificate, KeyObject } from 'node:crypto';
4
4
 
5
5
  interface HttpConfig {
@@ -172,6 +172,12 @@ interface PdfPrepareSession {
172
172
  docId: string;
173
173
  /** Иргэнд ХАРУУЛАХ баталгаажуулах код (ПИН биш). */
174
174
  vc: string | null;
175
+ /**
176
+ * Иргэний зурах DTBS (base64, SHA-256 = 32 байт) — урсгалаас үл хамааран. Хариуны гарын үсгийг өөрөө шалгахад:
177
+ * `validator.validateSign(result, digest)`. Хуучин CA push хариунд талбаргүй → undefined (device-link-д
178
+ * `deviceLink.digest`-ээс).
179
+ */
180
+ digest?: string;
175
181
  deviceLink: {
176
182
  sessionToken: string;
177
183
  sessionSecret: string;
@@ -194,6 +200,13 @@ declare class PdfApi {
194
200
  * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
195
201
  */
196
202
  document(sessionId: string): Promise<Uint8Array>;
203
+ /**
204
+ * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
205
+ * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
206
+ *
207
+ * @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
208
+ */
209
+ documentVerified(sessionId: string, expected: PdfSessionBlock | string): Promise<Uint8Array>;
197
210
  }
198
211
 
199
212
  declare class OrganizationApi {
@@ -203,6 +216,36 @@ declare class OrganizationApi {
203
216
  getRepresentations(personEtsi: string): Promise<RepresentationsResponse>;
204
217
  }
205
218
 
219
+ /** Задалсан subject. Cert-д байхгүй талбар — undefined. Давхардсан OID-ийн эхнийх нь. */
220
+ interface CertSubject {
221
+ /** 2.5.4.42 GIVENNAME. */
222
+ givenName?: string;
223
+ /** 2.5.4.4 SURNAME. */
224
+ surname?: string;
225
+ /** 2.5.4.3 CN. */
226
+ commonName?: string;
227
+ /** 2.5.4.5 SERIALNUMBER — иргэнд ETSI (`PNOMN-<иргэний дугаар>`). */
228
+ serialNumber?: string;
229
+ /** 2.5.4.6 C (ж: "MN"). */
230
+ country?: string;
231
+ /** 2.5.4.10 O. */
232
+ organization?: string;
233
+ /** 2.5.4.97 organizationIdentifier (ж: `NTRMN-…`). */
234
+ organizationIdentifier?: string;
235
+ /** Харуулах нэр: «givenName surname», эс бөгөөс commonName, эс бөгөөс null. */
236
+ name: string | null;
237
+ /** Бүх атрибут, OID → утга (эхний утга). Дээрхээс бусад OID-д. */
238
+ attributes: Record<string, string>;
239
+ }
240
+ /**
241
+ * Гэрчилгээний subject-ийг задална. `cert` — `X509Certificate`, DER байт, эсвэл base64 DER
242
+ * (session хариуны `certificateDerB64`). Гэмтсэн DER → {@link ValidationError}.
243
+ *
244
+ * (!) Энэ нь зөвхөн задлалт — гэрчилгээнд итгэх эсэхийг `ResponseValidator` шийднэ. Баталгаажаагүй
245
+ * гэрчилгээний нэрэнд бүү итгэ: `validateAuth`/`validateSign`-ийн `parsedSubject`-ийг ашигла.
246
+ */
247
+ declare function parseSubject(cert: X509Certificate | Uint8Array | string): CertSubject;
248
+
206
249
  /**
207
250
  * validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
208
251
  * (device-link relay хаалт).
@@ -218,8 +261,7 @@ interface ValidateAuthOptions {
218
261
  * (verifier-тэй) ирсэн үр дүнг "Web2App"/"App2App"-аар, poll-оор ирснийг "QR"-аар шалга — товчоор
219
262
  * нээгдсэн session poll замаар OK болохгүй, callback-аар л дуусна.
220
263
  *
221
- * ⚠️ Legacy `/dl?sessionId&vc` холбоосонд төрөл хамгаалагдаагүй — build 59 + `min_version=59` хүртэл
222
- * энэ шалгалт relay-ээс бүрэн хамгаалахгүй.
264
+ * Legacy `/dl?sessionId&vc` холбоос 2026-10-07-нд серверээс хасагдсан — төрөл үргэлж authCode-оор хамгаалагдана.
223
265
  */
224
266
  expectedFlowType?: FlowType;
225
267
  /**
@@ -272,6 +314,8 @@ interface VerifiedIdentity {
272
314
  certificate: X509Certificate;
273
315
  /** Cert subject DN (түүхий). Ж: "C=MN, CN=..., SERIALNUMBER=PNOMN-...". */
274
316
  subject: string;
317
+ /** Subject-ийн задалсан талбарууд (нэр, ETSI `serialNumber`, улс) — {@link parseSubject}. */
318
+ parsedSubject: CertSubject;
275
319
  certificateLevel: string;
276
320
  }
277
321
  /** Баталгаажсан гарын үсэг (sign). */
@@ -281,6 +325,8 @@ interface VerifiedSignature {
281
325
  signatureAlgorithm: string | null;
282
326
  certificate: X509Certificate;
283
327
  subject: string;
328
+ /** Subject-ийн задалсан талбарууд — {@link parseSubject}. */
329
+ parsedSubject: CertSubject;
284
330
  }
285
331
  declare class ResponseValidator {
286
332
  private readonly anchors;
@@ -302,8 +348,17 @@ declare class ResponseValidator {
302
348
  */
303
349
  validateSign(result: SessionResult, digestB64: string, hashAlgorithm?: string): VerifiedSignature;
304
350
  /**
305
- * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй тулгана (downgrade хаалт).
306
- * ecdsa-with-SHA256 (хуучин cert) хүссэн rsassa-pss-ийн оронд ирэхийг зөвшөөрнө — SHA-256-аар л.
351
+ * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
352
+ * (downgrade / alg confusion хаалт).
353
+ *
354
+ * CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
355
+ * RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
356
+ * hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
357
+ * зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
358
+ * Иймд:
359
+ * - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
360
+ * - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
361
+ * EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
307
362
  */
308
363
  private algorithmOf;
309
364
  /** state/endResult, cert байгаа эсэх, chain, validity, certLevel — нийтлэг шалгалт. */
@@ -594,4 +649,4 @@ declare class EidOidcClient {
594
649
  /** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
595
650
  declare function pkceChallenge(codeVerifier: string): Promise<string>;
596
651
 
597
- export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
652
+ export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-Db82DlYN.js';
2
- export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, P as PdfDocumentStatus, n as PdfSessionBlock, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-Db82DlYN.js';
1
+ import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PdfSessionBlock, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-B_XtyuFG.js';
2
+ export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, n as PdfDocumentStatus, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.js';
3
3
  import { X509Certificate, KeyObject } from 'node:crypto';
4
4
 
5
5
  interface HttpConfig {
@@ -172,6 +172,12 @@ interface PdfPrepareSession {
172
172
  docId: string;
173
173
  /** Иргэнд ХАРУУЛАХ баталгаажуулах код (ПИН биш). */
174
174
  vc: string | null;
175
+ /**
176
+ * Иргэний зурах DTBS (base64, SHA-256 = 32 байт) — урсгалаас үл хамааран. Хариуны гарын үсгийг өөрөө шалгахад:
177
+ * `validator.validateSign(result, digest)`. Хуучин CA push хариунд талбаргүй → undefined (device-link-д
178
+ * `deviceLink.digest`-ээс).
179
+ */
180
+ digest?: string;
175
181
  deviceLink: {
176
182
  sessionToken: string;
177
183
  sessionSecret: string;
@@ -194,6 +200,13 @@ declare class PdfApi {
194
200
  * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
195
201
  */
196
202
  document(sessionId: string): Promise<Uint8Array>;
203
+ /**
204
+ * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
205
+ * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
206
+ *
207
+ * @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
208
+ */
209
+ documentVerified(sessionId: string, expected: PdfSessionBlock | string): Promise<Uint8Array>;
197
210
  }
198
211
 
199
212
  declare class OrganizationApi {
@@ -203,6 +216,36 @@ declare class OrganizationApi {
203
216
  getRepresentations(personEtsi: string): Promise<RepresentationsResponse>;
204
217
  }
205
218
 
219
+ /** Задалсан subject. Cert-д байхгүй талбар — undefined. Давхардсан OID-ийн эхнийх нь. */
220
+ interface CertSubject {
221
+ /** 2.5.4.42 GIVENNAME. */
222
+ givenName?: string;
223
+ /** 2.5.4.4 SURNAME. */
224
+ surname?: string;
225
+ /** 2.5.4.3 CN. */
226
+ commonName?: string;
227
+ /** 2.5.4.5 SERIALNUMBER — иргэнд ETSI (`PNOMN-<иргэний дугаар>`). */
228
+ serialNumber?: string;
229
+ /** 2.5.4.6 C (ж: "MN"). */
230
+ country?: string;
231
+ /** 2.5.4.10 O. */
232
+ organization?: string;
233
+ /** 2.5.4.97 organizationIdentifier (ж: `NTRMN-…`). */
234
+ organizationIdentifier?: string;
235
+ /** Харуулах нэр: «givenName surname», эс бөгөөс commonName, эс бөгөөс null. */
236
+ name: string | null;
237
+ /** Бүх атрибут, OID → утга (эхний утга). Дээрхээс бусад OID-д. */
238
+ attributes: Record<string, string>;
239
+ }
240
+ /**
241
+ * Гэрчилгээний subject-ийг задална. `cert` — `X509Certificate`, DER байт, эсвэл base64 DER
242
+ * (session хариуны `certificateDerB64`). Гэмтсэн DER → {@link ValidationError}.
243
+ *
244
+ * (!) Энэ нь зөвхөн задлалт — гэрчилгээнд итгэх эсэхийг `ResponseValidator` шийднэ. Баталгаажаагүй
245
+ * гэрчилгээний нэрэнд бүү итгэ: `validateAuth`/`validateSign`-ийн `parsedSubject`-ийг ашигла.
246
+ */
247
+ declare function parseSubject(cert: X509Certificate | Uint8Array | string): CertSubject;
248
+
206
249
  /**
207
250
  * validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
208
251
  * (device-link relay хаалт).
@@ -218,8 +261,7 @@ interface ValidateAuthOptions {
218
261
  * (verifier-тэй) ирсэн үр дүнг "Web2App"/"App2App"-аар, poll-оор ирснийг "QR"-аар шалга — товчоор
219
262
  * нээгдсэн session poll замаар OK болохгүй, callback-аар л дуусна.
220
263
  *
221
- * ⚠️ Legacy `/dl?sessionId&vc` холбоосонд төрөл хамгаалагдаагүй — build 59 + `min_version=59` хүртэл
222
- * энэ шалгалт relay-ээс бүрэн хамгаалахгүй.
264
+ * Legacy `/dl?sessionId&vc` холбоос 2026-10-07-нд серверээс хасагдсан — төрөл үргэлж authCode-оор хамгаалагдана.
223
265
  */
224
266
  expectedFlowType?: FlowType;
225
267
  /**
@@ -272,6 +314,8 @@ interface VerifiedIdentity {
272
314
  certificate: X509Certificate;
273
315
  /** Cert subject DN (түүхий). Ж: "C=MN, CN=..., SERIALNUMBER=PNOMN-...". */
274
316
  subject: string;
317
+ /** Subject-ийн задалсан талбарууд (нэр, ETSI `serialNumber`, улс) — {@link parseSubject}. */
318
+ parsedSubject: CertSubject;
275
319
  certificateLevel: string;
276
320
  }
277
321
  /** Баталгаажсан гарын үсэг (sign). */
@@ -281,6 +325,8 @@ interface VerifiedSignature {
281
325
  signatureAlgorithm: string | null;
282
326
  certificate: X509Certificate;
283
327
  subject: string;
328
+ /** Subject-ийн задалсан талбарууд — {@link parseSubject}. */
329
+ parsedSubject: CertSubject;
284
330
  }
285
331
  declare class ResponseValidator {
286
332
  private readonly anchors;
@@ -302,8 +348,17 @@ declare class ResponseValidator {
302
348
  */
303
349
  validateSign(result: SessionResult, digestB64: string, hashAlgorithm?: string): VerifiedSignature;
304
350
  /**
305
- * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй тулгана (downgrade хаалт).
306
- * ecdsa-with-SHA256 (хуучин cert) хүссэн rsassa-pss-ийн оронд ирэхийг зөвшөөрнө — SHA-256-аар л.
351
+ * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
352
+ * (downgrade / alg confusion хаалт).
353
+ *
354
+ * CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
355
+ * RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
356
+ * hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
357
+ * зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
358
+ * Иймд:
359
+ * - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
360
+ * - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
361
+ * EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
307
362
  */
308
363
  private algorithmOf;
309
364
  /** state/endResult, cert байгаа эсэх, chain, validity, certLevel — нийтлэг шалгалт. */
@@ -594,4 +649,4 @@ declare class EidOidcClient {
594
649
  /** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
595
650
  declare function pkceChallenge(codeVerifier: string): Promise<string>;
596
651
 
597
- export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
652
+ export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
package/dist/index.js CHANGED
@@ -381,6 +381,7 @@ var SessionApi = class {
381
381
  };
382
382
 
383
383
  // src/pdf.ts
384
+ import { createHash as createHash2 } from "crypto";
384
385
  function rec3(v) {
385
386
  return v && typeof v === "object" ? v : {};
386
387
  }
@@ -413,10 +414,12 @@ var PdfApi = class {
413
414
  const receivedAt = Date.now();
414
415
  const vc = rec3(r.vc).value;
415
416
  const dl = rec3(r.deviceLink);
417
+ const digest = typeof r.digest === "string" && r.digest ? r.digest : typeof dl.digest === "string" && dl.digest ? dl.digest : void 0;
416
418
  return {
417
419
  sessionId: String(r.sessionID),
418
420
  docId: String(r.docID),
419
421
  vc: vc != null ? String(vc) : null,
422
+ ...digest !== void 0 ? { digest } : {},
420
423
  deviceLink: dl.sessionToken ? {
421
424
  sessionToken: String(dl.sessionToken),
422
425
  sessionSecret: String(dl.sessionSecret),
@@ -434,6 +437,26 @@ var PdfApi = class {
434
437
  document(sessionId) {
435
438
  return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
436
439
  }
440
+ /**
441
+ * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
442
+ * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
443
+ *
444
+ * @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
445
+ */
446
+ async documentVerified(sessionId, expected) {
447
+ let want;
448
+ if (typeof expected === "string") {
449
+ want = expected;
450
+ } else {
451
+ if (expected.documentStatus !== "READY") throw new ValidationError(`pdf.documentStatus ${expected.documentStatus} \u2260 READY`);
452
+ want = expected.outSha256;
453
+ }
454
+ if (!want || !/^[0-9a-fA-F]{64}$/.test(want)) throw new ValidationError("pdf.outSha256 \u0430\u043B\u0433\u0430 \u044D\u0441\u0432\u044D\u043B SHA-256 hex \u0431\u0438\u0448");
455
+ const bytes = await this.document(sessionId);
456
+ const got = createHash2("sha256").update(bytes).digest("hex");
457
+ if (got !== want.toLowerCase()) throw new ValidationError("\u0422\u0430\u0442\u0441\u0430\u043D PDF-\u0438\u0439\u043D SHA-256 \u043D\u044C pdf.outSha256-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
458
+ return bytes;
459
+ }
437
460
  };
438
461
 
439
462
  // src/organization.ts
@@ -451,7 +474,114 @@ var OrganizationApi = class {
451
474
  };
452
475
 
453
476
  // src/validator.ts
454
- import { X509Certificate, constants, createHash as createHash2, publicDecrypt, verify } from "crypto";
477
+ import { X509Certificate as X509Certificate2, constants, createHash as createHash3, publicDecrypt, verify } from "crypto";
478
+
479
+ // src/subject.ts
480
+ import { X509Certificate } from "crypto";
481
+ var OIDS = {
482
+ "2.5.4.42": "givenName",
483
+ "2.5.4.4": "surname",
484
+ "2.5.4.3": "commonName",
485
+ "2.5.4.5": "serialNumber",
486
+ "2.5.4.6": "country",
487
+ "2.5.4.10": "organization",
488
+ "2.5.4.97": "organizationIdentifier"
489
+ };
490
+ function tlv(buf, pos, limit) {
491
+ if (pos + 2 > limit) throw new ValidationError("subject: DER \u0442\u043E\u043B\u0433\u043E\u0439 \u0431\u043E\u0433\u0438\u043D\u043E");
492
+ const tag = buf[pos];
493
+ if ((tag & 31) === 31) throw new ValidationError("subject: \u043E\u043B\u043E\u043D \u0431\u0430\u0439\u0442 tag \u0434\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439");
494
+ let len = buf[pos + 1];
495
+ let i = pos + 2;
496
+ if (len & 128) {
497
+ const n = len & 127;
498
+ if (n === 0 || n > 3) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u0443\u0440\u0443\u0443");
499
+ if (i + n > limit) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u043E\u0433\u0438\u043D\u043E");
500
+ len = 0;
501
+ for (let k = 0; k < n; k++) len = len * 256 + buf[i++];
502
+ }
503
+ if (i + len > limit) throw new ValidationError("subject: DER \u0430\u0433\u0443\u0443\u043B\u0433\u0430 \u0445\u0438\u043B\u044D\u044D\u0441 \u0433\u0430\u0440\u043D\u0430");
504
+ return { tag, start: i, end: i + len };
505
+ }
506
+ function oidOf(buf, t) {
507
+ if (t.tag !== 6 || t.end === t.start) throw new ValidationError("subject: OID \u0431\u0438\u0448");
508
+ const parts = [];
509
+ let v = 0;
510
+ for (let i = t.start; i < t.end; i++) {
511
+ const b = buf[i];
512
+ v = v * 128 + (b & 127);
513
+ if (!(b & 128)) {
514
+ if (parts.length === 0) parts.push(v < 80 ? Math.floor(v / 40) : 2, v < 80 ? v % 40 : v - 80);
515
+ else parts.push(v);
516
+ v = 0;
517
+ }
518
+ }
519
+ return parts.join(".");
520
+ }
521
+ function stringOf(buf, t) {
522
+ const bytes = buf.subarray(t.start, t.end);
523
+ switch (t.tag) {
524
+ case 12:
525
+ try {
526
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
527
+ } catch {
528
+ throw new ValidationError("subject: UTF8String \u0431\u0443\u0440\u0443\u0443 UTF-8");
529
+ }
530
+ case 19:
531
+ // PrintableString
532
+ case 22:
533
+ // IA5String
534
+ case 20:
535
+ return Buffer.from(bytes).toString("latin1");
536
+ case 30: {
537
+ if (bytes.length % 2) throw new ValidationError("subject: BMPString \u0443\u0440\u0442 \u0441\u043E\u043D\u0434\u0433\u043E\u0439");
538
+ return Buffer.from(bytes).swap16().toString("utf16le");
539
+ }
540
+ default:
541
+ return void 0;
542
+ }
543
+ }
544
+ function parseSubject(cert) {
545
+ const der = cert instanceof X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
546
+ const top = tlv(der, 0, der.length);
547
+ if (top.tag !== 48) throw new ValidationError("subject: Certificate SEQUENCE \u0431\u0438\u0448");
548
+ const tbs = tlv(der, top.start, top.end);
549
+ if (tbs.tag !== 48) throw new ValidationError("subject: tbsCertificate SEQUENCE \u0431\u0438\u0448");
550
+ let pos = tbs.start;
551
+ let first = tlv(der, pos, tbs.end);
552
+ if (first.tag === 160) {
553
+ pos = first.end;
554
+ first = tlv(der, pos, tbs.end);
555
+ }
556
+ let field = first;
557
+ for (let k = 0; k < 4; k++) field = tlv(der, field.end, tbs.end);
558
+ if (field.tag !== 48) throw new ValidationError("subject: Name SEQUENCE \u0431\u0438\u0448");
559
+ const out = { name: null, attributes: {} };
560
+ for (let sp = field.start; sp < field.end; ) {
561
+ const rdn = tlv(der, sp, field.end);
562
+ if (rdn.tag !== 49) throw new ValidationError("subject: RDN SET \u0431\u0438\u0448");
563
+ for (let ap = rdn.start; ap < rdn.end; ) {
564
+ const atv = tlv(der, ap, rdn.end);
565
+ if (atv.tag !== 48) throw new ValidationError("subject: AttributeTypeAndValue SEQUENCE \u0431\u0438\u0448");
566
+ const oidT = tlv(der, atv.start, atv.end);
567
+ const valT = tlv(der, oidT.end, atv.end);
568
+ const oid = oidOf(der, oidT);
569
+ const value = stringOf(der, valT);
570
+ if (value !== void 0 && !(oid in out.attributes)) {
571
+ out.attributes[oid] = value;
572
+ const key = OIDS[oid];
573
+ if (key) out[key] = value;
574
+ }
575
+ ap = atv.end;
576
+ }
577
+ sp = rdn.end;
578
+ }
579
+ const full = [out.givenName, out.surname].filter((s) => s).join(" ");
580
+ out.name = full || out.commonName || null;
581
+ return out;
582
+ }
583
+
584
+ // src/validator.ts
455
585
  var ResponseValidator = class {
456
586
  anchors;
457
587
  intermediates;
@@ -461,8 +591,8 @@ var ResponseValidator = class {
461
591
  revocation;
462
592
  revocationMode;
463
593
  constructor(trust = {}) {
464
- this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new X509Certificate(p));
465
- this.intermediates = (trust.intermediatesPem ?? []).map((p) => new X509Certificate(p));
594
+ this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new X509Certificate2(p));
595
+ this.intermediates = (trust.intermediatesPem ?? []).map((p) => new X509Certificate2(p));
466
596
  this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
467
597
  this.skew = trust.clockSkewMs ?? 0;
468
598
  this.allowUntrusted = trust.allowUntrusted ?? false;
@@ -493,7 +623,7 @@ var ResponseValidator = class {
493
623
  throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
494
624
  }
495
625
  if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
496
- const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
626
+ const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
497
627
  const payload = buildAcspV2Payload({
498
628
  serverRandom,
499
629
  rpChallenge: acsp.rpChallenge,
@@ -512,6 +642,7 @@ var ResponseValidator = class {
512
642
  documentNumber: result.documentNumber,
513
643
  certificate: leaf,
514
644
  subject: leaf.subject,
645
+ parsedSubject: parseSubject(leaf),
515
646
  certificateLevel: String(result.certificateLevel ?? "")
516
647
  };
517
648
  }
@@ -522,7 +653,7 @@ var ResponseValidator = class {
522
653
  validateSign(result, digestB64, hashAlgorithm = "SHA-256") {
523
654
  const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
524
655
  const digest = Buffer.from(digestB64, "base64");
525
- const { alg, hash } = this.algorithmOf(result, null, hashAlgorithm);
656
+ const { alg, hash } = this.algorithmOf(result, leaf, null, hashAlgorithm);
526
657
  if (digest.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
527
658
  if (!verifyPrehashedSignature(digest, result.signatureValueB64, leaf.publicKey, alg, hash)) {
528
659
  throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u04E9\u0433\u0441\u04E9\u043D digest-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
@@ -532,27 +663,47 @@ var ResponseValidator = class {
532
663
  signatureValueB64: result.signatureValueB64,
533
664
  signatureAlgorithm: result.signatureAlgorithm,
534
665
  certificate: leaf,
535
- subject: leaf.subject
666
+ subject: leaf.subject,
667
+ parsedSubject: parseSubject(leaf)
536
668
  };
537
669
  }
538
670
  /**
539
- * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй тулгана (downgrade хаалт).
540
- * ecdsa-with-SHA256 (хуучин cert) хүссэн rsassa-pss-ийн оронд ирэхийг зөвшөөрнө — SHA-256-аар л.
671
+ * Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
672
+ * (downgrade / alg confusion хаалт).
673
+ *
674
+ * CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
675
+ * RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
676
+ * hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
677
+ * зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
678
+ * Иймд:
679
+ * - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
680
+ * - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
681
+ * EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
541
682
  */
542
- algorithmOf(result, wantAlg, wantHash) {
683
+ algorithmOf(result, leaf, wantAlg, wantHash) {
543
684
  const alg = result.signatureAlgorithm ?? "";
685
+ const keyType = leaf.publicKey.asymmetricKeyType;
544
686
  let hash;
545
687
  if (alg === "rsassa-pss") {
546
688
  hash = String(result.signatureAlgorithmParameters?.hashAlgorithm ?? "");
689
+ if (!HASHES.has(hash)) throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 hashAlgorithm: ${hash}`);
547
690
  const mgf = result.signatureAlgorithmParameters?.maskGenAlgorithm?.parameters?.hashAlgorithm;
548
691
  if (mgf && mgf !== hash) throw new ValidationError("MGF1 hash \u043D\u044C hashAlgorithm-\u0430\u0430\u0441 \u04E9\u04E9\u0440");
549
692
  if (result.signatureAlgorithmParameters?.saltLength != null && result.signatureAlgorithmParameters.saltLength !== hashLen(hash)) {
550
693
  throw new ValidationError("saltLength \u2260 hLen");
551
694
  }
695
+ if (keyType !== "rsa" && keyType !== "rsa-pss") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
552
696
  } else if (/^sha(256|384|512)WithRSAEncryption$/.test(alg)) {
553
697
  hash = "SHA-" + alg.slice(3, 6);
698
+ if (keyType !== "rsa") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
554
699
  } else if (alg === "ecdsa-with-SHA256") {
555
700
  hash = "SHA-256";
701
+ if (keyType !== "ec" || leaf.publicKey.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
702
+ throw new ValidationError(`${alg} \u043D\u044C EC P-256 \u0431\u0443\u0441 \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
703
+ }
704
+ if (wantHash !== hash) {
705
+ throw new ValidationError(`ecdsa-with-SHA256 \u0445\u0430\u0440\u0438\u0443 ${wantHash} \u0445\u04AF\u0441\u0441\u044D\u043D session-\u0434 \u0431\u043E\u043B\u043E\u043C\u0436\u0433\u04AF\u0439 (ECDSA credential \u0437\u04E9\u0432\u0445\u04E9\u043D SHA-256)`);
706
+ }
556
707
  } else {
557
708
  throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 signatureAlgorithm: ${alg}`);
558
709
  }
@@ -573,7 +724,7 @@ var ResponseValidator = class {
573
724
  if (!result.certificateDerB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
574
725
  if (!result.signatureValueB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0430\u043B\u0433\u0430");
575
726
  if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
576
- const leaf = new X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
727
+ const leaf = new X509Certificate2(Buffer.from(result.certificateDerB64, "base64"));
577
728
  this.checkValidity(leaf);
578
729
  this.checkLevel(String(result.certificateLevel ?? ""));
579
730
  if (this.anchors.length === 0) {
@@ -651,7 +802,7 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
651
802
  return fromResponse;
652
803
  }
653
804
  function userChallengeOf(verifier) {
654
- return createHash2("sha256").update(verifier, "utf8").digest("base64url");
805
+ return createHash3("sha256").update(verifier, "utf8").digest("base64url");
655
806
  }
656
807
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
657
808
  const sig = Buffer.from(sigB64, "base64");
@@ -742,6 +893,7 @@ function ecdsaP256VerifyPrehashed(digest, sig, key) {
742
893
  const R = ptAdd(ptMul(mod(z * w, n), [P256.gx, P256.gy]), ptMul(mod(r * w, n), Q));
743
894
  return R !== null && mod(R[0], n) === r;
744
895
  }
896
+ var HASHES = /* @__PURE__ */ new Set(["SHA-256", "SHA-384", "SHA-512"]);
745
897
  var DIGEST_INFO = {
746
898
  "SHA-256": Buffer.from("3031300d060960864801650304020105000420", "hex"),
747
899
  "SHA-384": Buffer.from("3041300d060960864801650304020205000430", "hex"),
@@ -752,7 +904,7 @@ function mgf1(hash, seed, length) {
752
904
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
753
905
  const c = Buffer.alloc(4);
754
906
  c.writeUInt32BE(i, 0);
755
- out.push(createHash2(nodeHash(hash)).update(seed).update(c).digest());
907
+ out.push(createHash3(nodeHash(hash)).update(seed).update(c).digest());
756
908
  }
757
909
  return Buffer.concat(out).subarray(0, length);
758
910
  }
@@ -776,7 +928,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
776
928
  const psLen = emLen - hLen - sLen - 2;
777
929
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
778
930
  const salt = db.subarray(psLen + 1);
779
- const h2 = createHash2(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
931
+ const h2 = createHash3(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
780
932
  return h.equals(h2);
781
933
  }
782
934
  function isCa(cert) {
@@ -1206,6 +1358,7 @@ export {
1206
1358
  isMobileUserAgent,
1207
1359
  openOrShowQR,
1208
1360
  parseSessionResult,
1361
+ parseSubject,
1209
1362
  pkceChallenge,
1210
1363
  qrDeviceLinkTicker,
1211
1364
  randomChallenge,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gerege-systems/eid-mongolia-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
5
5
  "license": "MIT",
6
6
  "type": "module",