@gerege-systems/eid-mongolia-sdk 0.9.1 → 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,
@@ -828,9 +1248,10 @@ var ResponseValidator = class {
828
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");
829
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");
830
1250
  if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
831
- 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"));
832
1252
  this.checkValidity(leaf);
833
1253
  this.checkLevel(String(result.certificateLevel ?? ""));
1254
+ this.checkPurpose(leaf, protocol);
834
1255
  if (this.anchors.length === 0) {
835
1256
  if (!this.allowUntrusted) {
836
1257
  throw new ValidationError(
@@ -840,8 +1261,35 @@ var ResponseValidator = class {
840
1261
  } else {
841
1262
  this.verifyChain(leaf);
842
1263
  }
1264
+ this.checkIssuer(leaf);
843
1265
  return leaf;
844
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
+ }
845
1293
  checkValidity(cert) {
846
1294
  const now = Date.now();
847
1295
  const from = Date.parse(cert.validFrom);
@@ -884,14 +1332,23 @@ var ResponseValidator = class {
884
1332
  * дуудна (тэдгээр нь sync; revocation нь сүлжээ I/O тул async).
885
1333
  */
886
1334
  async checkRevocation(cert) {
887
- 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";
888
1343
  const status = await this.revocation(cert, this.findIssuer(cert));
1344
+ if (status === "good") return "good";
889
1345
  if (status === "revoked") {
890
1346
  throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
891
1347
  }
892
- if (status === "unknown" && this.revocationMode === "hard-fail") {
1348
+ if (this.revocationMode === "hard-fail") {
893
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)");
894
1350
  }
1351
+ return "unchecked";
895
1352
  }
896
1353
  };
897
1354
  function signedCallbackUrl(fromResponse, rpCallback, flowType) {
@@ -906,16 +1363,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
906
1363
  return fromResponse;
907
1364
  }
908
1365
  function userChallengeOf(verifier) {
909
- 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");
910
1367
  }
911
1368
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
912
1369
  const sig = Buffer.from(sigB64, "base64");
913
1370
  const h = nodeHash(hash);
914
1371
  if (alg === "rsassa-pss") {
915
- 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);
916
1373
  }
917
- if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto4.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
918
- 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);
919
1376
  }
920
1377
  function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
921
1378
  const sig = Buffer.from(sigB64, "base64");
@@ -924,7 +1381,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
924
1381
  if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
925
1382
  let em;
926
1383
  try {
927
- 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);
928
1385
  } catch {
929
1386
  return false;
930
1387
  }
@@ -1008,7 +1465,7 @@ function mgf1(hash, seed, length) {
1008
1465
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
1009
1466
  const c = Buffer.alloc(4);
1010
1467
  c.writeUInt32BE(i, 0);
1011
- 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());
1012
1469
  }
1013
1470
  return Buffer.concat(out).subarray(0, length);
1014
1471
  }
@@ -1032,9 +1489,42 @@ function emsaPssVerify(mHash, em, emBits, hash) {
1032
1489
  const psLen = emLen - hLen - sLen - 2;
1033
1490
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
1034
1491
  const salt = db.subarray(psLen + 1);
1035
- 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();
1036
1493
  return h.equals(h2);
1037
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
+ }
1038
1528
  function isCa(cert) {
1039
1529
  return cert.ca === true;
1040
1530
  }
@@ -1062,6 +1552,8 @@ var EidClient = class {
1062
1552
  sign;
1063
1553
  pdf;
1064
1554
  organization;
1555
+ person;
1556
+ token;
1065
1557
  session;
1066
1558
  validator;
1067
1559
  constructor(cfg) {
@@ -1077,8 +1569,10 @@ var EidClient = class {
1077
1569
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1078
1570
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1079
1571
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
1080
- 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 });
1081
1573
  this.organization = new OrganizationApi(http);
1574
+ this.person = new PersonApi(http);
1575
+ this.token = new TokenApi(http, cfg.credentials);
1082
1576
  this.session = new SessionApi(http);
1083
1577
  this.validator = new ResponseValidator(cfg.trust ?? {});
1084
1578
  }
@@ -1127,7 +1621,7 @@ function openOrShowQR(session, opts) {
1127
1621
  }
1128
1622
 
1129
1623
  // src/devicelinkv3.ts
1130
- var import_node_crypto5 = require("crypto");
1624
+ var import_node_crypto6 = require("crypto");
1131
1625
  var TYPES = ["QR", "Web2App", "App2App"];
1132
1626
  var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
1133
1627
  var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
@@ -1184,7 +1678,7 @@ function buildDeviceLink(p) {
1184
1678
  callback,
1185
1679
  unprotected
1186
1680
  ].join("|");
1187
- 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");
1188
1682
  return `${unprotected}&authCode=${authCode}`;
1189
1683
  }
1190
1684
  function qrDeviceLinkTicker(input, onLink, opts) {
@@ -1197,7 +1691,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
1197
1691
  }
1198
1692
 
1199
1693
  // src/oidc.ts
1200
- var import_node_crypto6 = require("crypto");
1694
+ var import_node_crypto7 = require("crypto");
1201
1695
  var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
1202
1696
  var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
1203
1697
  var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
@@ -1234,7 +1728,7 @@ var EidOidcClient = class {
1234
1728
  if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
1235
1729
  if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
1236
1730
  this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
1237
- this.scope = cfg.scope ?? "openid";
1731
+ this.scope = cfg.scope ?? OIDC_EXPECT.defaultScope;
1238
1732
  }
1239
1733
  cfg;
1240
1734
  issuer;
@@ -1282,7 +1776,7 @@ var EidOidcClient = class {
1282
1776
  state,
1283
1777
  nonce,
1284
1778
  code_challenge: await pkceChallenge(codeVerifier),
1285
- code_challenge_method: "S256"
1779
+ code_challenge_method: OIDC_EXPECT.pkceMethod
1286
1780
  });
1287
1781
  if (opts.display) p.set("display", opts.display);
1288
1782
  u.search = p.toString();
@@ -1361,7 +1855,7 @@ var EidOidcClient = class {
1361
1855
  const header = decodeJsonPart(parts[0]);
1362
1856
  const payload = decodeJsonPart(parts[1]);
1363
1857
  if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
1364
- 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");
1365
1859
  const kid = typeof header.kid === "string" ? header.kid : "";
1366
1860
  if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
1367
1861
  const pick = (ks) => ks.find(
@@ -1454,7 +1948,7 @@ function formEncode(s) {
1454
1948
  function timingSafeEqualStr(a, b) {
1455
1949
  const x = Buffer.from(a, "utf8");
1456
1950
  const y = Buffer.from(b, "utf8");
1457
- 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);
1458
1952
  }
1459
1953
  function decodeJsonPart(s) {
1460
1954
  try {
@@ -1477,7 +1971,9 @@ function callbackParams(input) {
1477
1971
  ApiError,
1478
1972
  AuthApi,
1479
1973
  AuthenticationError,
1974
+ CITIZEN_ISSUING_CA_SKI,
1480
1975
  DEFAULT_BASE_URL,
1976
+ DEFAULT_PDF_TIMEOUT_MS,
1481
1977
  DeviceLinkError,
1482
1978
  EID_ACR_ADVANCED,
1483
1979
  EID_ACR_QUALIFIED,
@@ -1492,10 +1988,13 @@ function callbackParams(input) {
1492
1988
  ON_BEHALF_ERROR_CODES,
1493
1989
  OrganizationApi,
1494
1990
  PdfApi,
1991
+ PersonApi,
1992
+ RETRYABLE_ERROR_CODES,
1495
1993
  ResponseValidator,
1496
1994
  SessionApi,
1497
1995
  SessionFailedError,
1498
1996
  SignApi,
1997
+ TokenApi,
1499
1998
  ValidationError,
1500
1999
  acspV2Digest,
1501
2000
  buildAcspV2Payload,
@@ -1504,14 +2003,19 @@ function callbackParams(input) {
1504
2003
  interactionsToBase64,
1505
2004
  isMobileUserAgent,
1506
2005
  openOrShowQR,
2006
+ parseRetryAfter,
1507
2007
  parseSessionResult,
1508
2008
  parseSubject,
2009
+ personMatchesCertificate,
1509
2010
  pkceChallenge,
1510
2011
  qrDeviceLinkTicker,
1511
2012
  randomChallenge,
1512
2013
  resolveBaseUrl,
1513
2014
  sha256Base64,
2015
+ tokenAuthDigest,
2016
+ tokenLimitOf,
1514
2017
  userChallengeOf,
1515
2018
  verifyPayloadSignature,
1516
- verifyPrehashedSignature
2019
+ verifyPrehashedSignature,
2020
+ verifyTokenCertificates
1517
2021
  });