@gerege-systems/eid-mongolia-sdk 0.9.0 → 0.10.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/dist/index.cjs CHANGED
@@ -23,7 +23,9 @@ __export(index_exports, {
23
23
  ApiError: () => ApiError,
24
24
  AuthApi: () => AuthApi,
25
25
  AuthenticationError: () => AuthenticationError,
26
+ CITIZEN_ISSUING_CA_SKI: () => CITIZEN_ISSUING_CA_SKI,
26
27
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
28
+ DEFAULT_PDF_TIMEOUT_MS: () => DEFAULT_PDF_TIMEOUT_MS,
27
29
  DeviceLinkError: () => DeviceLinkError,
28
30
  EID_ACR_ADVANCED: () => EID_ACR_ADVANCED,
29
31
  EID_ACR_QUALIFIED: () => EID_ACR_QUALIFIED,
@@ -38,10 +40,13 @@ __export(index_exports, {
38
40
  ON_BEHALF_ERROR_CODES: () => ON_BEHALF_ERROR_CODES,
39
41
  OrganizationApi: () => OrganizationApi,
40
42
  PdfApi: () => PdfApi,
43
+ PersonApi: () => PersonApi,
44
+ RETRYABLE_ERROR_CODES: () => RETRYABLE_ERROR_CODES,
41
45
  ResponseValidator: () => ResponseValidator,
42
46
  SessionApi: () => SessionApi,
43
47
  SessionFailedError: () => SessionFailedError,
44
48
  SignApi: () => SignApi,
49
+ TokenApi: () => TokenApi,
45
50
  ValidationError: () => ValidationError,
46
51
  acspV2Digest: () => acspV2Digest,
47
52
  buildAcspV2Payload: () => buildAcspV2Payload,
@@ -50,16 +55,21 @@ __export(index_exports, {
50
55
  interactionsToBase64: () => interactionsToBase64,
51
56
  isMobileUserAgent: () => isMobileUserAgent,
52
57
  openOrShowQR: () => openOrShowQR,
58
+ parseRetryAfter: () => parseRetryAfter,
53
59
  parseSessionResult: () => parseSessionResult,
54
60
  parseSubject: () => parseSubject,
61
+ personMatchesCertificate: () => personMatchesCertificate,
55
62
  pkceChallenge: () => pkceChallenge,
56
63
  qrDeviceLinkTicker: () => qrDeviceLinkTicker,
57
64
  randomChallenge: () => randomChallenge,
58
65
  resolveBaseUrl: () => resolveBaseUrl,
59
66
  sha256Base64: () => sha256Base64,
67
+ tokenAuthDigest: () => tokenAuthDigest,
68
+ tokenLimitOf: () => tokenLimitOf,
60
69
  userChallengeOf: () => userChallengeOf,
61
70
  verifyPayloadSignature: () => verifyPayloadSignature,
62
- verifyPrehashedSignature: () => verifyPrehashedSignature
71
+ verifyPrehashedSignature: () => verifyPrehashedSignature,
72
+ verifyTokenCertificates: () => verifyTokenCertificates
63
73
  });
64
74
  module.exports = __toCommonJS(index_exports);
65
75
 
@@ -71,25 +81,79 @@ var EidError = class extends Error {
71
81
  }
72
82
  };
73
83
  var AuthenticationError = class extends EidError {
84
+ /** Серверийн `{"code"}`; байхгүй / JSON биш (proxy) бол undefined. */
85
+ code;
86
+ /** Серверийн `{"error"}` текст (логт); JSON биш (proxy) бол undefined. */
87
+ serverMessage;
88
+ /** Хариуны бие (таслагдаагүй текст). */
89
+ body;
90
+ headers;
91
+ constructor(message, init) {
92
+ super(message);
93
+ this.code = init?.code;
94
+ this.serverMessage = init?.serverMessage;
95
+ this.body = init?.body;
96
+ this.headers = init?.headers;
97
+ }
74
98
  };
75
99
  var ForbiddenError = class extends EidError {
76
- constructor(message, code) {
100
+ constructor(message, code, init) {
77
101
  super(message);
78
102
  this.code = code;
103
+ this.headers = init?.headers;
104
+ this.retryAfterMs = init?.retryAfterMs;
79
105
  }
80
106
  code;
107
+ headers;
108
+ /** Retry-After (мс), байхгүй бол undefined. */
109
+ retryAfterMs;
81
110
  };
82
111
  var ApiError = class extends EidError {
83
- constructor(status, body, code) {
112
+ constructor(status, body, code, init) {
84
113
  super(`RP-API ${status}: ${body}`);
85
114
  this.status = status;
86
115
  this.body = body;
87
116
  this.code = code;
117
+ this.headers = init?.headers;
118
+ this.retryAfterMs = init?.retryAfterMs;
88
119
  }
89
120
  status;
90
121
  body;
91
122
  code;
123
+ headers;
124
+ /**
125
+ * Retry-After (мс, секунд эсвэл HTTP-огноо задалсан); байхгүй/буруу бол undefined. CA түр алдаанд (429
126
+ * PDF_PREPARE_BUSY / RATE_LIMITED, 503 DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE г.м.) тавина.
127
+ */
128
+ retryAfterMs;
129
+ /**
130
+ * CA-ийн түр алдаа ({@link RETRYABLE_ERROR_CODES}-ийн нэг, эсвэл 429/503 + Retry-After): `retryAfterMs`-ийн дараа
131
+ * ижил хүсэлтийг давтаж болно.
132
+ */
133
+ get retryable() {
134
+ if (this.code && RETRYABLE_ERROR_CODES.includes(this.code)) return true;
135
+ return (this.status === 429 || this.status === 503) && (this.retryAfterMs ?? 0) > 0;
136
+ }
92
137
  };
138
+ var RETRYABLE_ERROR_CODES = [
139
+ "PDF_PREPARE_BUSY",
140
+ "DOCUMENT_PENDING",
141
+ "TSA_UNAVAILABLE",
142
+ "LTV_UNAVAILABLE",
143
+ "RATE_LIMITED",
144
+ "STATE_UNAVAILABLE",
145
+ "ISSUANCE_PENDING"
146
+ ];
147
+ function parseRetryAfter(value, now = Date.now()) {
148
+ const v = (value ?? "").trim();
149
+ if (!v) return void 0;
150
+ if (/^-?\d+$/.test(v)) {
151
+ const n = Number(v);
152
+ return n > 0 && n <= 86400 ? n * 1e3 : void 0;
153
+ }
154
+ const t = Date.parse(v);
155
+ return Number.isNaN(t) || t <= now ? void 0 : t - now;
156
+ }
93
157
  var ON_BEHALF_ERROR_CODES = [
94
158
  "REPRESENTATION_DENIED",
95
159
  "REPRESENTATION_PENDING",
@@ -100,7 +164,19 @@ var ON_BEHALF_ERROR_CODES = [
100
164
  "REPRESENTATION_REVOKED"
101
165
  ];
102
166
  var NetworkError = class extends EidError {
167
+ /** "GET /session/…" (таних тэмдэг хасагдсан зам). */
168
+ op;
169
+ /** Хүсэлт хугацаа хэтэрсэн. */
170
+ timeout;
171
+ constructor(message, init) {
172
+ super(message);
173
+ this.op = init?.op;
174
+ this.timeout = init?.timeout;
175
+ }
103
176
  };
177
+ function maskPath(path) {
178
+ return path.split(/[?#]/)[0].replace(/\/(etsi|document)\/[^/?#\s]+/g, "/$1/\u2026");
179
+ }
104
180
  var SessionFailedError = class extends EidError {
105
181
  constructor(endResult) {
106
182
  super(`Session \u0430\u043C\u0436\u0438\u043B\u0442\u0433\u04AF\u0439: ${endResult}`);
@@ -154,26 +230,32 @@ var Http = class {
154
230
  };
155
231
  if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
156
232
  let res;
233
+ const op = `${method} ${maskPath(path)}`;
157
234
  try {
158
235
  res = await f(url, init);
159
236
  } catch (e) {
160
237
  const err = e;
161
238
  if (err.name === "TimeoutError" || err.name === "AbortError") {
162
- throw new NetworkError(`\u0425\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${method} ${path}`);
239
+ throw new NetworkError(`\u0425\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${op}`, { op, timeout: true });
163
240
  }
164
- throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${method} ${path}): ${err.message}`);
241
+ throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${op}): ${err.message.split(url).join(op)}`, { op, timeout: false });
165
242
  }
166
243
  if (res.status < 300) return res;
167
244
  const text = await res.text();
168
245
  const { error, code } = errorBody(text);
169
- if (res.status === 401) throw new AuthenticationError("API secret \u0431\u0443\u0440\u0443\u0443 \u044D\u0441\u0432\u044D\u043B \u0431\u0430\u0439\u0445\u0433\u04AF\u0439 (401)");
246
+ const headers = res.headers;
247
+ const retryAfterMs = parseRetryAfter(headers.get("Retry-After"));
248
+ if (res.status === 401) {
249
+ throw new AuthenticationError(`API secret \u0431\u0443\u0440\u0443\u0443 \u044D\u0441\u0432\u044D\u043B \u0431\u0430\u0439\u0445\u0433\u04AF\u0439 (401${code ? ` ${code}` : ""})`, { code, serverMessage: error, body: text, headers });
250
+ }
170
251
  if (res.status === 403) {
171
252
  throw new ForbiddenError(
172
253
  error ? `RP-API 403${code ? ` ${code}` : ""}: ${error}` : "IP allowlist \u044D\u0441\u0432\u044D\u043B mTLS \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u04E9\u0433\u04AF\u0439 (403)",
173
- code
254
+ code,
255
+ { headers, retryAfterMs }
174
256
  );
175
257
  }
176
- throw new ApiError(res.status, text, code);
258
+ throw new ApiError(res.status, text, code, { headers, retryAfterMs });
177
259
  }
178
260
  };
179
261
  function errorBody(text) {
@@ -190,6 +272,48 @@ function errorBody(text) {
190
272
  }
191
273
  }
192
274
 
275
+ // src/contract.ts
276
+ var RP_ROUTES = {
277
+ authNotificationEtsi: { method: "POST", path: "/authentication/notification/etsi/{semanticsIdentifier}" },
278
+ authNotificationDocument: { method: "POST", path: "/authentication/notification/document/{documentNumber}" },
279
+ authDeviceLinkAnonymous: { method: "POST", path: "/authentication/device-link/anonymous" },
280
+ authDeviceLinkEtsi: { method: "POST", path: "/authentication/device-link/etsi/{semanticsIdentifier}" },
281
+ signNotificationEtsi: { method: "POST", path: "/signature/notification/etsi/{semanticsIdentifier}" },
282
+ signNotificationDocument: { method: "POST", path: "/signature/notification/document/{documentNumber}" },
283
+ pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
284
+ pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
285
+ orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
286
+ session: { method: "GET", path: "/session/{sessionId}" },
287
+ personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
288
+ personDevices: { method: "GET", path: "/devices/etsi/{personEtsi}" },
289
+ personActivity: { method: "GET", path: "/rp/activity/etsi/{personEtsi}" },
290
+ personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
291
+ personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
292
+ tokenCertificate: { method: "POST", path: "/token/certificate" },
293
+ tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
294
+ };
295
+ var RP_ROUTES_UNDISCOVERED = {
296
+ tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
297
+ tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
298
+ };
299
+ function routePath(route, ...args) {
300
+ let i = 0;
301
+ const out = route.path.replace(/\{[^}]*\}/g, () => {
302
+ const a = args[i++];
303
+ if (a === void 0) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0434\u0443\u0442\u0443\u0443`);
304
+ return encodeURIComponent(a);
305
+ });
306
+ if (i !== args.length) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0438\u043B\u04AF\u04AF`);
307
+ return out;
308
+ }
309
+ var OIDC_EXPECT = {
310
+ defaultScope: "openid",
311
+ pkceMethod: "S256",
312
+ idTokenAlg: "ES256",
313
+ /** `Authorization: Basic` (RFC 6749 §2.3.1). */
314
+ tokenEndpointAuthMethod: "client_secret_basic"
315
+ };
316
+
193
317
  // src/crypto.ts
194
318
  var import_node_crypto = require("crypto");
195
319
  function sha256Base64(data) {
@@ -292,25 +416,25 @@ var AuthApi = class {
292
416
  /** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
293
417
  async notificationByEtsi(id, interactions, opts) {
294
418
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
295
- const raw = await this.http.post(`/authentication/notification/etsi/${encodeURIComponent(id)}`, body);
419
+ const raw = await this.http.post(routePath(RP_ROUTES.authNotificationEtsi, id), body);
296
420
  return this.parseNotification(raw, acsp);
297
421
  }
298
422
  /** Push — баримтын дугаараар (тодорхой төхөөрөмж рүү). */
299
423
  async notificationByDocument(documentNumber, interactions, opts) {
300
424
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
301
- const raw = await this.http.post(`/authentication/notification/document/${encodeURIComponent(documentNumber)}`, body);
425
+ const raw = await this.http.post(routePath(RP_ROUTES.authNotificationDocument, documentNumber), body);
302
426
  return this.parseNotification(raw, acsp);
303
427
  }
304
428
  /** QR/App2App — нэр томьёололгүй (иргэн хэн болохыг урьдчилж мэдэхгүй). QR код үүсгэхэд. */
305
429
  async deviceLinkAnonymous(interactions, opts) {
306
430
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
307
- const raw = await this.http.post(`/authentication/device-link/anonymous`, body);
431
+ const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkAnonymous), body);
308
432
  return this.parseDeviceLink(raw, acsp);
309
433
  }
310
434
  /** QR/App2App — тодорхой иргэн (РД/civil_id/ETSI). */
311
435
  async deviceLinkByEtsi(id, interactions, opts) {
312
436
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
313
- const raw = await this.http.post(`/authentication/device-link/etsi/${encodeURIComponent(id)}`, body);
437
+ const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkEtsi, id), body);
314
438
  return this.parseDeviceLink(raw, acsp);
315
439
  }
316
440
  };
@@ -343,6 +467,7 @@ var SignApi = class {
343
467
  };
344
468
  if (opts?.callbackUrl?.trim()) body.initialCallbackUrl = opts.callbackUrl.trim();
345
469
  if (opts?.onBehalfOf?.trim()) body.onBehalfOf = opts.onBehalfOf.trim();
470
+ if (opts?.fileName?.trim()) body.fileName = opts.fileName.trim();
346
471
  return body;
347
472
  }
348
473
  parse(raw) {
@@ -355,12 +480,12 @@ var SignApi = class {
355
480
  * digest-ийг гаднаас бэлдэж өгнө — текст бус бинар баримтад тохиромжтой.
356
481
  */
357
482
  async digestByEtsi(id, digestB64, interactions, opts) {
358
- const raw = await this.http.post(`/signature/notification/etsi/${encodeURIComponent(id)}`, this.body(digestB64, interactions, opts));
483
+ const raw = await this.http.post(routePath(RP_ROUTES.signNotificationEtsi, id), this.body(digestB64, interactions, opts));
359
484
  return this.parse(raw);
360
485
  }
361
486
  /** Бэлэн digest-ээр push sign — баримтын дугаараар (auth-аас гарсан documentNumber дээр). */
362
487
  async digestByDocument(documentNumber, digestB64, interactions, opts) {
363
- const raw = await this.http.post(`/signature/notification/document/${encodeURIComponent(documentNumber)}`, this.body(digestB64, interactions, opts));
488
+ const raw = await this.http.post(routePath(RP_ROUTES.signNotificationDocument, documentNumber), this.body(digestB64, interactions, opts));
364
489
  return this.parse(raw);
365
490
  }
366
491
  /** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
@@ -401,9 +526,44 @@ function parseSessionResult(raw) {
401
526
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
402
527
  interactionTypeUsed: str(r.interactionTypeUsed),
403
528
  onBehalfOf: parseOrg(r.onBehalfOf),
404
- pdf: parsePdf(r.pdf)
529
+ pdf: parsePdf(r.pdf),
530
+ person: parsePerson(r.person),
531
+ tokenCertificate: parseTokenCert(r.tokenCertificate),
532
+ raw
405
533
  };
406
534
  }
535
+ var PERSON_STRINGS = [
536
+ "firstName",
537
+ "lastName",
538
+ "familyName",
539
+ "firstNameEn",
540
+ "lastNameEn",
541
+ "civilId",
542
+ "regNo",
543
+ "birthDate",
544
+ "gender"
545
+ ];
546
+ function parsePerson(raw) {
547
+ const p = asRecord(raw);
548
+ const out = {};
549
+ for (const k of PERSON_STRINGS) if (typeof p[k] === "string" && p[k]) out[k] = p[k];
550
+ if (typeof p.geID === "number" && p.geID !== 0) out.geID = p.geID;
551
+ return Object.keys(out).length ? out : null;
552
+ }
553
+ function parseTokenCert(raw) {
554
+ const t = asRecord(raw);
555
+ const status = str(t.status);
556
+ if (!status) return null;
557
+ const out = { status };
558
+ if (t.errorCode) out.errorCode = String(t.errorCode);
559
+ if (t.authSerialHex) out.authSerialHex = String(t.authSerialHex);
560
+ if (t.signSerialHex) out.signSerialHex = String(t.signSerialHex);
561
+ return out;
562
+ }
563
+ function personMatchesCertificate(person, subject) {
564
+ const serial = typeof subject === "string" ? subject : subject?.serialNumber;
565
+ return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
566
+ }
407
567
  function parsePdf(raw) {
408
568
  const p = asRecord(raw);
409
569
  const docID = str(p.docID);
@@ -457,7 +617,7 @@ var SessionApi = class {
457
617
  /** Нэг удаагийн long-poll. serverPollMs хүртэл хүлээнэ. */
458
618
  async poll(sessionId, serverPollMs = SERVER_POLL_MS) {
459
619
  const raw = await this.http.get(
460
- `/session/${encodeURIComponent(sessionId)}?timeoutMs=${serverPollMs}`,
620
+ `${routePath(RP_ROUTES.session, sessionId)}?timeoutMs=${serverPollMs}`,
461
621
  serverPollMs + 1e4
462
622
  // HTTP timeout нь серверийн poll-оос урт
463
623
  );
@@ -482,6 +642,7 @@ var SessionApi = class {
482
642
 
483
643
  // src/pdf.ts
484
644
  var import_node_crypto2 = require("crypto");
645
+ var DEFAULT_PDF_TIMEOUT_MS = 8e4;
485
646
  function rec3(v) {
486
647
  return v && typeof v === "object" ? v : {};
487
648
  }
@@ -494,7 +655,10 @@ var PdfApi = class {
494
655
  http;
495
656
  creds;
496
657
  defaults;
497
- /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
658
+ /**
659
+ * POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. Түр алдаа: `ApiError` 429
660
+ * PDF_PREPARE_BUSY, 503 LTV_UNAVAILABLE / TSA_UNAVAILABLE — `retryAfterMs`-ийн дараа дахин (`retryable`).
661
+ */
498
662
  async prepare(input) {
499
663
  const request = {
500
664
  relyingPartyUUID: this.creds.rpUUID,
@@ -510,7 +674,7 @@ var PdfApi = class {
510
674
  const form = new FormData();
511
675
  form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
512
676
  form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
513
- const r = rec3(await this.http.postMultipart("/pdf/sign/prepare", form, 6e4));
677
+ const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS));
514
678
  const receivedAt = Date.now();
515
679
  const vc = rec3(r.vc).value;
516
680
  const dl = rec3(r.deviceLink);
@@ -532,10 +696,12 @@ var PdfApi = class {
532
696
  }
533
697
  /**
534
698
  * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
535
- * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
699
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`. Баримт
700
+ * бэлэн болоогүй бол `ApiError` 503 `.code` DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE — `retryAfterMs`-ийн
701
+ * дараа дахин татна (`retryable`).
536
702
  */
537
703
  document(sessionId) {
538
- return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
704
+ return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS);
539
705
  }
540
706
  /**
541
707
  * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
@@ -567,13 +733,53 @@ var OrganizationApi = class {
567
733
  http;
568
734
  /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
569
735
  async getRepresentations(personEtsi) {
570
- const raw = await this.http.get(`/organization/representations/etsi/${encodeURIComponent(personEtsi)}`);
736
+ const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
571
737
  const list = Array.isArray(raw.representations) ? raw.representations : [];
572
738
  return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
573
739
  }
574
740
  };
575
741
 
576
- // src/validator.ts
742
+ // src/person.ts
743
+ function rec4(v) {
744
+ return v && typeof v === "object" ? v : {};
745
+ }
746
+ var PersonApi = class {
747
+ constructor(http) {
748
+ this.http = http;
749
+ }
750
+ http;
751
+ async get(path) {
752
+ const raw = await this.http.get(path);
753
+ return { ...rec4(raw), raw };
754
+ }
755
+ /** Иргэний бүх гэрчилгээ + статусын тоолол. */
756
+ certificates(personEtsi) {
757
+ return this.get(routePath(RP_ROUTES.personCertificates, personEtsi));
758
+ }
759
+ /** Иргэний холбоотой төхөөрөмжүүд (идэвхтэй + идэвхгүй). */
760
+ devices(personEtsi) {
761
+ return this.get(routePath(RP_ROUTES.personDevices, personEtsi));
762
+ }
763
+ /** Иргэний үйл ажиллагааны түүх. */
764
+ activity(personEtsi, query) {
765
+ const q = new URLSearchParams();
766
+ if (query?.flow) q.set("flow", query.flow);
767
+ if (query?.limit && query.limit > 0) q.set("limit", String(query.limit));
768
+ if (query?.offset && query.offset > 0) q.set("offset", String(query.offset));
769
+ const qs = q.toString();
770
+ return this.get(routePath(RP_ROUTES.personActivity, personEtsi) + (qs ? `?${qs}` : ""));
771
+ }
772
+ /** Самбарын нэгдсэн тоо. */
773
+ summary(personEtsi) {
774
+ return this.get(routePath(RP_ROUTES.personSummary, personEtsi));
775
+ }
776
+ /** Асран хамгаалж буй хүүхдүүд (зөвхөн унших). */
777
+ children(personEtsi) {
778
+ return this.get(routePath(RP_ROUTES.personChildren, personEtsi));
779
+ }
780
+ };
781
+
782
+ // src/token.ts
577
783
  var import_node_crypto4 = require("crypto");
578
784
 
579
785
  // src/subject.ts
@@ -680,8 +886,207 @@ function parseSubject(cert) {
680
886
  out.name = full || out.commonName || null;
681
887
  return out;
682
888
  }
889
+ function certExtension(der, oid) {
890
+ const top = tlv(der, 0, der.length);
891
+ const tbs = tlv(der, top.start, top.end);
892
+ let f = tlv(der, tbs.start, tbs.end);
893
+ if (f.tag === 160) f = tlv(der, f.end, tbs.end);
894
+ for (let k = 0; k < 5; k++) f = tlv(der, f.end, tbs.end);
895
+ for (let pos = f.end; pos < tbs.end; ) {
896
+ const t = tlv(der, pos, tbs.end);
897
+ if (t.tag === 163) {
898
+ const seq = tlv(der, t.start, t.end);
899
+ for (let ep = seq.start; ep < seq.end; ) {
900
+ const ext = tlv(der, ep, seq.end);
901
+ const id = tlv(der, ext.start, ext.end);
902
+ let v = tlv(der, id.end, ext.end);
903
+ if (v.tag === 1) v = tlv(der, v.end, ext.end);
904
+ if (v.tag !== 4) throw new ValidationError("extension: extnValue OCTET STRING \u0431\u0438\u0448");
905
+ if (oidOf(der, id) === oid) return der.subarray(v.start, v.end);
906
+ ep = ext.end;
907
+ }
908
+ return void 0;
909
+ }
910
+ pos = t.end;
911
+ }
912
+ return void 0;
913
+ }
914
+
915
+ // src/token.ts
916
+ function rec5(v) {
917
+ return v && typeof v === "object" ? v : {};
918
+ }
919
+ function hexSha256Equal(hex, data) {
920
+ if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
921
+ return (0, import_node_crypto4.timingSafeEqual)((0, import_node_crypto4.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
922
+ }
923
+ var TOKEN_RESULT_TIMEOUT_MS = 3e4;
924
+ var TokenApi = class {
925
+ constructor(http, creds) {
926
+ this.http = http;
927
+ this.creds = creds;
928
+ }
929
+ http;
930
+ creds;
931
+ /**
932
+ * POST /token/certificate — токены AUTH + SIGN гэрчилгээ олголт эхлүүлнэ (иргэн утсаараа батална). CA-ийн буцаасан
933
+ * CSR hash-ууд илгээсэнтэй таарахгүй бол ValidationError.
934
+ */
935
+ async issueCertificate(input) {
936
+ if (!input.signerEtsi?.trim() || !input.csrAuth?.length || !input.csrSign?.length || !input.tokenSerial?.trim()) {
937
+ throw new Error("token: signerEtsi, csrAuth, csrSign, tokenSerial \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
938
+ }
939
+ const body = {
940
+ relyingPartyUUID: this.creds.rpUUID,
941
+ relyingPartyName: this.creds.rpName,
942
+ signer: { etsi: input.signerEtsi.trim() },
943
+ csrAuth: Buffer.from(input.csrAuth).toString("base64"),
944
+ csrSign: Buffer.from(input.csrSign).toString("base64"),
945
+ tokenSerial: input.tokenSerial.trim(),
946
+ onBehalfOf: input.onBehalfOf?.trim() ?? "",
947
+ flow: "notification"
948
+ };
949
+ if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
950
+ const r = rec5(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
951
+ if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
952
+ throw new ValidationError("token: CA-\u0438\u0439\u043D csrAuthSha256/csrSignSha256 \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D CSR-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
953
+ }
954
+ const vc = rec5(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
955
+ return {
956
+ sessionId: String(r.sessionID ?? ""),
957
+ vc: vc != null ? String(vc) : null,
958
+ csrAuthSha256: String(r.csrAuthSha256).toLowerCase(),
959
+ csrSignSha256: String(r.csrSignSha256).toLowerCase(),
960
+ ...typeof r.expiresAt === "string" ? { expiresAt: r.expiresAt } : {}
961
+ };
962
+ }
963
+ /**
964
+ * GET /token/certificate/{sessionID} — олгосон AUTH + SIGN гэрчилгээ + гинж. Токенд бичихээс өмнө
965
+ * {@link verifyTokenCertificates}-ээр CSR-ийн түлхүүртэй тулга.
966
+ */
967
+ async certificateResult(sessionId) {
968
+ const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
969
+ const r = rec5(raw);
970
+ const item = (v) => v && typeof v === "object" ? v : null;
971
+ return {
972
+ status: String(r.status ?? ""),
973
+ auth: item(r.auth),
974
+ sign: item(r.sign),
975
+ chain: Array.isArray(r.chain) ? r.chain.map(String) : [],
976
+ chainPem: String(r.chainPem ?? ""),
977
+ organization: r.organization && typeof r.organization === "object" ? r.organization : null,
978
+ keyMedium: String(r.keyMedium ?? ""),
979
+ certificateLevel: String(r.certificateLevel ?? ""),
980
+ raw
981
+ };
982
+ }
983
+ /** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
984
+ async authChallenge() {
985
+ const r = rec5(
986
+ await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
987
+ relyingPartyUUID: this.creds.rpUUID,
988
+ relyingPartyName: this.creds.rpName
989
+ })
990
+ );
991
+ return {
992
+ challengeId: String(r.challengeId ?? ""),
993
+ challenge: String(r.challenge ?? ""),
994
+ rpUUID: String(r.rpUUID ?? ""),
995
+ expiresAt: String(r.expiresAt ?? "")
996
+ };
997
+ }
998
+ /**
999
+ * POST /token/auth/verify — токены AUTH гэрчилгээ (DER) + challenge дээрх түүхий r||s. CA гинж, төлөв, цуцлалт, гарын
1000
+ * үсгийг шалгана; SDK нэмж endResult === OK ба identity.etsi === гэрчилгээний SERIALNUMBER-ийг тулгана (эс бөгөөс
1001
+ * ValidationError). Challenge нэг удаагийн — алдааны дараа шинээр авна.
1002
+ */
1003
+ async authVerify(challengeId, certDer, signature) {
1004
+ let serial;
1005
+ try {
1006
+ serial = parseSubject(new import_node_crypto4.X509Certificate(certDer)).serialNumber;
1007
+ } catch {
1008
+ throw new Error("token auth: \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
1009
+ }
1010
+ if (!challengeId || !signature?.length) throw new Error("token auth: challengeId \u0431\u0430 signature \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
1011
+ const raw = await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthVerify), {
1012
+ challengeId,
1013
+ certificate: Buffer.from(certDer).toString("base64"),
1014
+ signature: Buffer.from(signature).toString("base64")
1015
+ });
1016
+ const r = rec5(raw);
1017
+ if (rec5(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1018
+ const id = rec5(r.identity);
1019
+ if (!id.etsi || id.etsi !== serial) throw new ValidationError("token auth: identity.etsi \u043D\u044C \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 SERIALNUMBER \u0431\u0438\u0448");
1020
+ const org = rec5(r.organization);
1021
+ const cert = rec5(r.cert);
1022
+ return {
1023
+ identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
1024
+ serialHex: String(cert.serialHex ?? ""),
1025
+ notAfter: String(cert.notAfter ?? ""),
1026
+ organization: org.etsi ? { etsi: String(org.etsi), name: String(org.name ?? "") } : null,
1027
+ raw
1028
+ };
1029
+ }
1030
+ };
1031
+ function csrSpki(csr) {
1032
+ const top = tlv(csr, 0, csr.length);
1033
+ const info = tlv(csr, top.start, top.end);
1034
+ const version = tlv(csr, info.start, info.end);
1035
+ const subject = tlv(csr, version.end, info.end);
1036
+ const spki = tlv(csr, subject.end, info.end);
1037
+ return (0, import_node_crypto4.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
1038
+ format: "der",
1039
+ type: "spki"
1040
+ });
1041
+ }
1042
+ function verifyTokenCertificates(result, csrAuth, csrSign) {
1043
+ if (!result?.auth || !result.sign) throw new ValidationError("token: auth/sign \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
1044
+ for (const [name, item, csr] of [
1045
+ ["auth", result.auth, csrAuth],
1046
+ ["sign", result.sign, csrSign]
1047
+ ]) {
1048
+ let want;
1049
+ try {
1050
+ want = csrSpki(csr);
1051
+ } catch {
1052
+ throw new Error(`token: ${name} CSR \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
1053
+ }
1054
+ let got;
1055
+ try {
1056
+ got = new import_node_crypto4.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
1057
+ } catch {
1058
+ throw new ValidationError(`token: ${name} \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
1059
+ }
1060
+ if (!got.equals(want)) throw new ValidationError(`token: ${name} \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440 CSR-\u0438\u0439\u043D\u0445\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
1061
+ }
1062
+ }
1063
+ function tokenAuthDigest(rpUUID, challengeB64) {
1064
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(rpUUID)) {
1065
+ throw new Error("token auth: rpUUID \u0436\u0438\u0436\u0438\u0433 \u04AF\u0441\u0433\u0438\u0439\u043D 36 \u0442\u044D\u043C\u0434\u044D\u0433\u0442 UUID \u0431\u0438\u0448");
1066
+ }
1067
+ const ch = Buffer.from(challengeB64, "base64");
1068
+ if (ch.length !== 32 || ch.toString("base64") !== challengeB64) throw new Error("token auth: challenge 32 \u0431\u0430\u0439\u0442 base64 \u0431\u0438\u0448");
1069
+ return (0, import_node_crypto4.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1070
+ }
1071
+ function tokenLimitOf(err) {
1072
+ if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
1073
+ let b;
1074
+ try {
1075
+ b = rec5(JSON.parse(err.body));
1076
+ } catch {
1077
+ return void 0;
1078
+ }
1079
+ if (typeof b.limit !== "number" || !Number.isInteger(b.limit) || b.limit < 0) return void 0;
1080
+ if (b.kind === "PERSONAL") return { kind: "PERSONAL", limit: b.limit };
1081
+ if (b.kind === "ORGANIZATION" && typeof b.organization === "string" && b.organization.startsWith("NTRMN-")) {
1082
+ return { kind: "ORGANIZATION", limit: b.limit, organization: b.organization };
1083
+ }
1084
+ return void 0;
1085
+ }
683
1086
 
684
1087
  // src/validator.ts
1088
+ var import_node_crypto5 = require("crypto");
1089
+ var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
685
1090
  var ResponseValidator = class {
686
1091
  anchors;
687
1092
  intermediates;
@@ -690,14 +1095,28 @@ var ResponseValidator = class {
690
1095
  allowUntrusted;
691
1096
  revocation;
692
1097
  revocationMode;
1098
+ allowedIssuers;
1099
+ checkKeyUsage;
1100
+ schemeName;
693
1101
  constructor(trust = {}) {
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));
1102
+ this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
1103
+ this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
696
1104
  this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
697
1105
  this.skew = trust.clockSkewMs ?? 0;
698
1106
  this.allowUntrusted = trust.allowUntrusted ?? false;
699
1107
  this.revocation = trust.revocation;
700
1108
  this.revocationMode = trust.revocationMode ?? "hard-fail";
1109
+ if (trust.allowedIssuerSkis?.length) {
1110
+ this.allowedIssuers = new Set(
1111
+ trust.allowedIssuerSkis.map((s) => {
1112
+ const ski = s.trim().replace(/:/g, "").toLowerCase();
1113
+ if (!/^(?:[0-9a-f]{2})+$/.test(ski)) throw new Error(`ResponseValidator: allowedIssuerSkis: "${s}" hex key identifier \u0431\u0438\u0448`);
1114
+ return ski;
1115
+ })
1116
+ );
1117
+ }
1118
+ this.checkKeyUsage = trust.checkKeyUsage ?? false;
1119
+ this.schemeName = trust.schemeName;
701
1120
  }
702
1121
  // ── нийтийн API ──
703
1122
  /**
@@ -725,6 +1144,7 @@ var ResponseValidator = class {
725
1144
  if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
726
1145
  const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
727
1146
  const payload = buildAcspV2Payload({
1147
+ ...this.schemeName ? { schemeName: this.schemeName } : {},
728
1148
  serverRandom,
729
1149
  rpChallenge: acsp.rpChallenge,
730
1150
  userChallenge,
@@ -748,10 +1168,14 @@ var ResponseValidator = class {
748
1168
  }
749
1169
  /**
750
1170
  * Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
751
- * @param hashAlgorithm — digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256).
1171
+ * @param opts — `hashAlgorithm` мөр (хуучин хэлбэр; default SHA-256) эсвэл {@link ValidateSignOptions}.
752
1172
  */
753
- validateSign(result, digestB64, hashAlgorithm = "SHA-256") {
1173
+ validateSign(result, digestB64, opts = {}) {
1174
+ const { hashAlgorithm = "SHA-256", expectedFlowType } = typeof opts === "string" ? { hashAlgorithm: opts } : opts;
754
1175
  const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
1176
+ if (expectedFlowType && result.flowType !== expectedFlowType) {
1177
+ throw new ValidationError(`flowType ${result.flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
1178
+ }
755
1179
  const digest = Buffer.from(digestB64, "base64");
756
1180
  const { alg, hash } = this.algorithmOf(result, leaf, null, hashAlgorithm);
757
1181
  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");
@@ -824,9 +1248,10 @@ var ResponseValidator = class {
824
1248
  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");
825
1249
  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");
826
1250
  if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
827
- const leaf = new import_node_crypto4.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
1251
+ const leaf = new import_node_crypto5.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
828
1252
  this.checkValidity(leaf);
829
1253
  this.checkLevel(String(result.certificateLevel ?? ""));
1254
+ this.checkPurpose(leaf, protocol);
830
1255
  if (this.anchors.length === 0) {
831
1256
  if (!this.allowUntrusted) {
832
1257
  throw new ValidationError(
@@ -836,8 +1261,35 @@ var ResponseValidator = class {
836
1261
  } else {
837
1262
  this.verifyChain(leaf);
838
1263
  }
1264
+ this.checkIssuer(leaf);
839
1265
  return leaf;
840
1266
  }
1267
+ /**
1268
+ * checkKeyUsage үед гэрчилгээний зориулалт протоколд тохирох эсэх (CA crypto/ca_profile.go): ACSP_V2 (auth) → KU
1269
+ * digitalSignature + EKU clientAuth (эсвэл anyExtendedKeyUsage); RAW_DIGEST_SIGNATURE (sign) → KU contentCommitment.
1270
+ */
1271
+ checkPurpose(leaf, protocol) {
1272
+ if (!this.checkKeyUsage) return;
1273
+ const ku = keyUsageBits(leaf);
1274
+ if (protocol === "ACSP_V2") {
1275
+ if (!(ku & 128)) throw new ValidationError("\u041D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 KeyUsage digitalSignature \u0430\u043B\u0433\u0430");
1276
+ const eku = extKeyUsage(leaf);
1277
+ if (!eku.includes("1.3.6.1.5.5.7.3.2") && !eku.includes("2.5.29.37.0")) {
1278
+ throw new ValidationError("\u041D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 ExtKeyUsage clientAuth \u0430\u043B\u0433\u0430 (\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u0433\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
1279
+ }
1280
+ return;
1281
+ }
1282
+ if (!(ku & 64)) throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u0433\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 KeyUsage nonRepudiation (contentCommitment) \u0430\u043B\u0433\u0430");
1283
+ }
1284
+ /** allowedIssuerSkis үед leaf-ийн шууд issuer-ийн SKI (issuer-т алга бол leaf-ийн AKI) жагсаалтад байх ёстой. */
1285
+ checkIssuer(leaf) {
1286
+ if (!this.allowedIssuers) return;
1287
+ const issuer = this.findIssuer(leaf);
1288
+ const ski = (issuer && subjectKeyId(issuer)) ?? authorityKeyId(leaf);
1289
+ if (!ski || !this.allowedIssuers.has(ski)) {
1290
+ throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u0433 \u0433\u0430\u0440\u0433\u0430\u0441\u0430\u043D CA \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u0433\u0434\u0441\u04E9\u043D \u0438\u0440\u0433\u044D\u043D\u0438\u0439 issuer \u0431\u0438\u0448 (allowedIssuerSkis)");
1291
+ }
1292
+ }
841
1293
  checkValidity(cert) {
842
1294
  const now = Date.now();
843
1295
  const from = Date.parse(cert.validFrom);
@@ -880,14 +1332,23 @@ var ResponseValidator = class {
880
1332
  * дуудна (тэдгээр нь sync; revocation нь сүлжээ I/O тул async).
881
1333
  */
882
1334
  async checkRevocation(cert) {
883
- if (!this.revocation) return;
1335
+ await this.checkRevocationStatus(cert);
1336
+ }
1337
+ /**
1338
+ * {@link checkRevocation} + юу шийдсэнийг буцаана (лог/аудитад): "good" | "unchecked" (unknown, soft-fail) |
1339
+ * "skipped" (hook алга). "revoked", мөн hard-fail горимд "unknown" → ValidationError; hook-ийн алдаа дамжина.
1340
+ */
1341
+ async checkRevocationStatus(cert) {
1342
+ if (!this.revocation) return "skipped";
884
1343
  const status = await this.revocation(cert, this.findIssuer(cert));
1344
+ if (status === "good") return "good";
885
1345
  if (status === "revoked") {
886
1346
  throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
887
1347
  }
888
- if (status === "unknown" && this.revocationMode === "hard-fail") {
1348
+ if (this.revocationMode === "hard-fail") {
889
1349
  throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 revocation \u0442\u04E9\u043B\u04E9\u0432 \u0442\u043E\u0433\u0442\u043E\u043E\u0433\u0434\u0441\u043E\u043D\u0433\u04AF\u0439 (hard-fail)");
890
1350
  }
1351
+ return "unchecked";
891
1352
  }
892
1353
  };
893
1354
  function signedCallbackUrl(fromResponse, rpCallback, flowType) {
@@ -902,16 +1363,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
902
1363
  return fromResponse;
903
1364
  }
904
1365
  function userChallengeOf(verifier) {
905
- return (0, import_node_crypto4.createHash)("sha256").update(verifier, "utf8").digest("base64url");
1366
+ return (0, import_node_crypto5.createHash)("sha256").update(verifier, "utf8").digest("base64url");
906
1367
  }
907
1368
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
908
1369
  const sig = Buffer.from(sigB64, "base64");
909
1370
  const h = nodeHash(hash);
910
1371
  if (alg === "rsassa-pss") {
911
- return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
1372
+ return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
912
1373
  }
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);
1374
+ if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto5.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
1375
+ return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PADDING }, sig);
915
1376
  }
916
1377
  function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
917
1378
  const sig = Buffer.from(sigB64, "base64");
@@ -920,7 +1381,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
920
1381
  if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
921
1382
  let em;
922
1383
  try {
923
- em = (0, import_node_crypto4.publicDecrypt)({ key, padding: import_node_crypto4.constants.RSA_NO_PADDING }, sig);
1384
+ em = (0, import_node_crypto5.publicDecrypt)({ key, padding: import_node_crypto5.constants.RSA_NO_PADDING }, sig);
924
1385
  } catch {
925
1386
  return false;
926
1387
  }
@@ -1004,7 +1465,7 @@ function mgf1(hash, seed, length) {
1004
1465
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
1005
1466
  const c = Buffer.alloc(4);
1006
1467
  c.writeUInt32BE(i, 0);
1007
- out.push((0, import_node_crypto4.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1468
+ out.push((0, import_node_crypto5.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1008
1469
  }
1009
1470
  return Buffer.concat(out).subarray(0, length);
1010
1471
  }
@@ -1028,9 +1489,42 @@ function emsaPssVerify(mHash, em, emBits, hash) {
1028
1489
  const psLen = emLen - hLen - sLen - 2;
1029
1490
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
1030
1491
  const salt = db.subarray(psLen + 1);
1031
- const h2 = (0, import_node_crypto4.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1492
+ const h2 = (0, import_node_crypto5.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1032
1493
  return h.equals(h2);
1033
1494
  }
1495
+ function keyUsageBits(cert) {
1496
+ const v = certExtension(cert.raw, "2.5.29.15");
1497
+ if (!v) return 0;
1498
+ const bs = tlv(v, 0, v.length);
1499
+ if (bs.tag !== 3 || bs.end - bs.start < 2) return 0;
1500
+ return v[bs.start + 1];
1501
+ }
1502
+ function extKeyUsage(cert) {
1503
+ const v = certExtension(cert.raw, "2.5.29.37");
1504
+ if (!v) return [];
1505
+ const seq = tlv(v, 0, v.length);
1506
+ const out = [];
1507
+ for (let p = seq.start; p < seq.end; ) {
1508
+ const t = tlv(v, p, seq.end);
1509
+ out.push(oidOf(v, t));
1510
+ p = t.end;
1511
+ }
1512
+ return out;
1513
+ }
1514
+ function subjectKeyId(cert) {
1515
+ const v = certExtension(cert.raw, "2.5.29.14");
1516
+ if (!v) return void 0;
1517
+ const o = tlv(v, 0, v.length);
1518
+ return o.tag === 4 && o.end > o.start ? Buffer.from(v.subarray(o.start, o.end)).toString("hex") : void 0;
1519
+ }
1520
+ function authorityKeyId(cert) {
1521
+ const v = certExtension(cert.raw, "2.5.29.35");
1522
+ if (!v) return void 0;
1523
+ const seq = tlv(v, 0, v.length);
1524
+ if (seq.start >= seq.end) return void 0;
1525
+ const k = tlv(v, seq.start, seq.end);
1526
+ return k.tag === 128 && k.end > k.start ? Buffer.from(v.subarray(k.start, k.end)).toString("hex") : void 0;
1527
+ }
1034
1528
  function isCa(cert) {
1035
1529
  return cert.ca === true;
1036
1530
  }
@@ -1058,6 +1552,8 @@ var EidClient = class {
1058
1552
  sign;
1059
1553
  pdf;
1060
1554
  organization;
1555
+ person;
1556
+ token;
1061
1557
  session;
1062
1558
  validator;
1063
1559
  constructor(cfg) {
@@ -1073,8 +1569,10 @@ var EidClient = class {
1073
1569
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1074
1570
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1075
1571
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
1076
- this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level });
1572
+ this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level, timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS });
1077
1573
  this.organization = new OrganizationApi(http);
1574
+ this.person = new PersonApi(http);
1575
+ this.token = new TokenApi(http, cfg.credentials);
1078
1576
  this.session = new SessionApi(http);
1079
1577
  this.validator = new ResponseValidator(cfg.trust ?? {});
1080
1578
  }
@@ -1123,7 +1621,7 @@ function openOrShowQR(session, opts) {
1123
1621
  }
1124
1622
 
1125
1623
  // src/devicelinkv3.ts
1126
- var import_node_crypto5 = require("crypto");
1624
+ var import_node_crypto6 = require("crypto");
1127
1625
  var TYPES = ["QR", "Web2App", "App2App"];
1128
1626
  var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
1129
1627
  var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
@@ -1180,7 +1678,7 @@ function buildDeviceLink(p) {
1180
1678
  callback,
1181
1679
  unprotected
1182
1680
  ].join("|");
1183
- const authCode = (0, import_node_crypto5.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
1681
+ const authCode = (0, import_node_crypto6.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
1184
1682
  return `${unprotected}&authCode=${authCode}`;
1185
1683
  }
1186
1684
  function qrDeviceLinkTicker(input, onLink, opts) {
@@ -1193,7 +1691,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
1193
1691
  }
1194
1692
 
1195
1693
  // src/oidc.ts
1196
- var import_node_crypto6 = require("crypto");
1694
+ var import_node_crypto7 = require("crypto");
1197
1695
  var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
1198
1696
  var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
1199
1697
  var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
@@ -1230,7 +1728,7 @@ var EidOidcClient = class {
1230
1728
  if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
1231
1729
  if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
1232
1730
  this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
1233
- this.scope = cfg.scope ?? "openid";
1731
+ this.scope = cfg.scope ?? OIDC_EXPECT.defaultScope;
1234
1732
  }
1235
1733
  cfg;
1236
1734
  issuer;
@@ -1278,7 +1776,7 @@ var EidOidcClient = class {
1278
1776
  state,
1279
1777
  nonce,
1280
1778
  code_challenge: await pkceChallenge(codeVerifier),
1281
- code_challenge_method: "S256"
1779
+ code_challenge_method: OIDC_EXPECT.pkceMethod
1282
1780
  });
1283
1781
  if (opts.display) p.set("display", opts.display);
1284
1782
  u.search = p.toString();
@@ -1357,7 +1855,7 @@ var EidOidcClient = class {
1357
1855
  const header = decodeJsonPart(parts[0]);
1358
1856
  const payload = decodeJsonPart(parts[1]);
1359
1857
  if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
1360
- if (header.alg !== "ES256") throw bad("alg ES256 \u0431\u0438\u0448");
1858
+ if (header.alg !== OIDC_EXPECT.idTokenAlg) throw bad("alg ES256 \u0431\u0438\u0448");
1361
1859
  const kid = typeof header.kid === "string" ? header.kid : "";
1362
1860
  if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
1363
1861
  const pick = (ks) => ks.find(
@@ -1450,7 +1948,7 @@ function formEncode(s) {
1450
1948
  function timingSafeEqualStr(a, b) {
1451
1949
  const x = Buffer.from(a, "utf8");
1452
1950
  const y = Buffer.from(b, "utf8");
1453
- return x.length === y.length && (0, import_node_crypto6.timingSafeEqual)(x, y);
1951
+ return x.length === y.length && (0, import_node_crypto7.timingSafeEqual)(x, y);
1454
1952
  }
1455
1953
  function decodeJsonPart(s) {
1456
1954
  try {
@@ -1473,7 +1971,9 @@ function callbackParams(input) {
1473
1971
  ApiError,
1474
1972
  AuthApi,
1475
1973
  AuthenticationError,
1974
+ CITIZEN_ISSUING_CA_SKI,
1476
1975
  DEFAULT_BASE_URL,
1976
+ DEFAULT_PDF_TIMEOUT_MS,
1477
1977
  DeviceLinkError,
1478
1978
  EID_ACR_ADVANCED,
1479
1979
  EID_ACR_QUALIFIED,
@@ -1488,10 +1988,13 @@ function callbackParams(input) {
1488
1988
  ON_BEHALF_ERROR_CODES,
1489
1989
  OrganizationApi,
1490
1990
  PdfApi,
1991
+ PersonApi,
1992
+ RETRYABLE_ERROR_CODES,
1491
1993
  ResponseValidator,
1492
1994
  SessionApi,
1493
1995
  SessionFailedError,
1494
1996
  SignApi,
1997
+ TokenApi,
1495
1998
  ValidationError,
1496
1999
  acspV2Digest,
1497
2000
  buildAcspV2Payload,
@@ -1500,14 +2003,19 @@ function callbackParams(input) {
1500
2003
  interactionsToBase64,
1501
2004
  isMobileUserAgent,
1502
2005
  openOrShowQR,
2006
+ parseRetryAfter,
1503
2007
  parseSessionResult,
1504
2008
  parseSubject,
2009
+ personMatchesCertificate,
1505
2010
  pkceChallenge,
1506
2011
  qrDeviceLinkTicker,
1507
2012
  randomChallenge,
1508
2013
  resolveBaseUrl,
1509
2014
  sha256Base64,
2015
+ tokenAuthDigest,
2016
+ tokenLimitOf,
1510
2017
  userChallengeOf,
1511
2018
  verifyPayloadSignature,
1512
- verifyPrehashedSignature
2019
+ verifyPrehashedSignature,
2020
+ verifyTokenCertificates
1513
2021
  });