@gerege-systems/eid-mongolia-sdk 0.9.1 → 0.11.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,10 @@ __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_MAX_BYTES: () => DEFAULT_PDF_MAX_BYTES,
29
+ DEFAULT_PDF_TIMEOUT_MS: () => DEFAULT_PDF_TIMEOUT_MS,
27
30
  DeviceLinkError: () => DeviceLinkError,
28
31
  EID_ACR_ADVANCED: () => EID_ACR_ADVANCED,
29
32
  EID_ACR_QUALIFIED: () => EID_ACR_QUALIFIED,
@@ -38,10 +41,14 @@ __export(index_exports, {
38
41
  ON_BEHALF_ERROR_CODES: () => ON_BEHALF_ERROR_CODES,
39
42
  OrganizationApi: () => OrganizationApi,
40
43
  PdfApi: () => PdfApi,
44
+ PersonApi: () => PersonApi,
45
+ RETRYABLE_ERROR_CODES: () => RETRYABLE_ERROR_CODES,
46
+ ResponseTooLargeError: () => ResponseTooLargeError,
41
47
  ResponseValidator: () => ResponseValidator,
42
48
  SessionApi: () => SessionApi,
43
49
  SessionFailedError: () => SessionFailedError,
44
50
  SignApi: () => SignApi,
51
+ TokenApi: () => TokenApi,
45
52
  ValidationError: () => ValidationError,
46
53
  acspV2Digest: () => acspV2Digest,
47
54
  buildAcspV2Payload: () => buildAcspV2Payload,
@@ -50,16 +57,21 @@ __export(index_exports, {
50
57
  interactionsToBase64: () => interactionsToBase64,
51
58
  isMobileUserAgent: () => isMobileUserAgent,
52
59
  openOrShowQR: () => openOrShowQR,
60
+ parseRetryAfter: () => parseRetryAfter,
53
61
  parseSessionResult: () => parseSessionResult,
54
62
  parseSubject: () => parseSubject,
63
+ personMatchesCertificate: () => personMatchesCertificate,
55
64
  pkceChallenge: () => pkceChallenge,
56
65
  qrDeviceLinkTicker: () => qrDeviceLinkTicker,
57
66
  randomChallenge: () => randomChallenge,
58
67
  resolveBaseUrl: () => resolveBaseUrl,
59
68
  sha256Base64: () => sha256Base64,
69
+ tokenAuthDigest: () => tokenAuthDigest,
70
+ tokenLimitOf: () => tokenLimitOf,
60
71
  userChallengeOf: () => userChallengeOf,
61
72
  verifyPayloadSignature: () => verifyPayloadSignature,
62
- verifyPrehashedSignature: () => verifyPrehashedSignature
73
+ verifyPrehashedSignature: () => verifyPrehashedSignature,
74
+ verifyTokenCertificates: () => verifyTokenCertificates
63
75
  });
64
76
  module.exports = __toCommonJS(index_exports);
65
77
 
@@ -71,25 +83,79 @@ var EidError = class extends Error {
71
83
  }
72
84
  };
73
85
  var AuthenticationError = class extends EidError {
86
+ /** Серверийн `{"code"}`; байхгүй / JSON биш (proxy) бол undefined. */
87
+ code;
88
+ /** Серверийн `{"error"}` текст (логт); JSON биш (proxy) бол undefined. */
89
+ serverMessage;
90
+ /** Хариуны бие (таслагдаагүй текст). */
91
+ body;
92
+ headers;
93
+ constructor(message, init) {
94
+ super(message);
95
+ this.code = init?.code;
96
+ this.serverMessage = init?.serverMessage;
97
+ this.body = init?.body;
98
+ this.headers = init?.headers;
99
+ }
74
100
  };
75
101
  var ForbiddenError = class extends EidError {
76
- constructor(message, code) {
102
+ constructor(message, code, init) {
77
103
  super(message);
78
104
  this.code = code;
105
+ this.headers = init?.headers;
106
+ this.retryAfterMs = init?.retryAfterMs;
79
107
  }
80
108
  code;
109
+ headers;
110
+ /** Retry-After (мс), байхгүй бол undefined. */
111
+ retryAfterMs;
81
112
  };
82
113
  var ApiError = class extends EidError {
83
- constructor(status, body, code) {
114
+ constructor(status, body, code, init) {
84
115
  super(`RP-API ${status}: ${body}`);
85
116
  this.status = status;
86
117
  this.body = body;
87
118
  this.code = code;
119
+ this.headers = init?.headers;
120
+ this.retryAfterMs = init?.retryAfterMs;
88
121
  }
89
122
  status;
90
123
  body;
91
124
  code;
125
+ headers;
126
+ /**
127
+ * Retry-After (мс, секунд эсвэл HTTP-огноо задалсан); байхгүй/буруу бол undefined. CA түр алдаанд (429
128
+ * PDF_PREPARE_BUSY / RATE_LIMITED, 503 DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE г.м.) тавина.
129
+ */
130
+ retryAfterMs;
131
+ /**
132
+ * CA-ийн түр алдаа ({@link RETRYABLE_ERROR_CODES}-ийн нэг, эсвэл 429/503 + Retry-After): `retryAfterMs`-ийн дараа
133
+ * ижил хүсэлтийг давтаж болно.
134
+ */
135
+ get retryable() {
136
+ if (this.code && RETRYABLE_ERROR_CODES.includes(this.code)) return true;
137
+ return (this.status === 429 || this.status === 503) && (this.retryAfterMs ?? 0) > 0;
138
+ }
92
139
  };
140
+ var RETRYABLE_ERROR_CODES = [
141
+ "PDF_PREPARE_BUSY",
142
+ "DOCUMENT_PENDING",
143
+ "TSA_UNAVAILABLE",
144
+ "LTV_UNAVAILABLE",
145
+ "RATE_LIMITED",
146
+ "STATE_UNAVAILABLE",
147
+ "ISSUANCE_PENDING"
148
+ ];
149
+ function parseRetryAfter(value, now = Date.now()) {
150
+ const v = (value ?? "").trim();
151
+ if (!v) return void 0;
152
+ if (/^-?\d+$/.test(v)) {
153
+ const n = Number(v);
154
+ return n > 0 && n <= 86400 ? n * 1e3 : void 0;
155
+ }
156
+ const t = Date.parse(v);
157
+ return Number.isNaN(t) || t <= now ? void 0 : t - now;
158
+ }
93
159
  var ON_BEHALF_ERROR_CODES = [
94
160
  "REPRESENTATION_DENIED",
95
161
  "REPRESENTATION_PENDING",
@@ -100,7 +166,28 @@ var ON_BEHALF_ERROR_CODES = [
100
166
  "REPRESENTATION_REVOKED"
101
167
  ];
102
168
  var NetworkError = class extends EidError {
169
+ /** "GET /session/…" (таних тэмдэг хасагдсан зам). */
170
+ op;
171
+ /** Хүсэлт хугацаа хэтэрсэн. */
172
+ timeout;
173
+ constructor(message, init) {
174
+ super(message);
175
+ this.op = init?.op;
176
+ this.timeout = init?.timeout;
177
+ }
178
+ };
179
+ var ResponseTooLargeError = class extends EidError {
180
+ constructor(op, limit) {
181
+ super(`${op}: \u0445\u0430\u0440\u0438\u0443 ${limit} \u0431\u0430\u0439\u0442\u0430\u0430\u0441 \u0438\u0445`);
182
+ this.op = op;
183
+ this.limit = limit;
184
+ }
185
+ op;
186
+ limit;
103
187
  };
188
+ function maskPath(path) {
189
+ return path.split(/[?#]/)[0].replace(/\/(etsi|document)\/[^/?#\s]+/g, "/$1/\u2026");
190
+ }
104
191
  var SessionFailedError = class extends EidError {
105
192
  constructor(endResult) {
106
193
  super(`Session \u0430\u043C\u0436\u0438\u043B\u0442\u0433\u04AF\u0439: ${endResult}`);
@@ -115,6 +202,7 @@ var DeviceLinkError = class extends EidError {
115
202
 
116
203
  // src/http.ts
117
204
  var DEFAULT_TIMEOUT_MS = 15e3;
205
+ var MAX_JSON_BYTES = 4 << 20;
118
206
  var Http = class {
119
207
  constructor(cfg) {
120
208
  this.cfg = cfg;
@@ -130,14 +218,14 @@ var Http = class {
130
218
  postMultipart(path, form, timeoutMs) {
131
219
  return this.json("POST", path, form, void 0, timeoutMs);
132
220
  }
133
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
134
- async getBytes(path, timeoutMs) {
221
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
222
+ async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
135
223
  const res = await this.send("GET", path, void 0, void 0, timeoutMs);
136
- return new Uint8Array(await res.arrayBuffer());
224
+ return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
137
225
  }
138
226
  async json(method, path, body, contentType, timeoutMs) {
139
227
  const res = await this.send(method, path, body, contentType, timeoutMs);
140
- const text = await res.text();
228
+ const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
141
229
  return text ? JSON.parse(text) : {};
142
230
  }
143
231
  async send(method, path, body, contentType, timeoutMs) {
@@ -154,28 +242,62 @@ var Http = class {
154
242
  };
155
243
  if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
156
244
  let res;
245
+ const op = `${method} ${maskPath(path)}`;
157
246
  try {
158
247
  res = await f(url, init);
159
248
  } catch (e) {
160
249
  const err = e;
161
250
  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}`);
251
+ 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
252
  }
164
- throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${method} ${path}): ${err.message}`);
253
+ 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
254
  }
166
255
  if (res.status < 300) return res;
167
256
  const text = await res.text();
168
257
  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)");
258
+ const headers = res.headers;
259
+ const retryAfterMs = parseRetryAfter(headers.get("Retry-After"));
260
+ if (res.status === 401) {
261
+ 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 });
262
+ }
170
263
  if (res.status === 403) {
171
264
  throw new ForbiddenError(
172
265
  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
266
+ code,
267
+ { headers, retryAfterMs }
174
268
  );
175
269
  }
176
- throw new ApiError(res.status, text, code);
270
+ throw new ApiError(res.status, text, code, { headers, retryAfterMs });
177
271
  }
178
272
  };
273
+ async function readLimited(res, limit, op) {
274
+ const declared = Number(res.headers.get("content-length") ?? NaN);
275
+ if (declared > limit) {
276
+ await res.body?.cancel().catch(() => void 0);
277
+ throw new ResponseTooLargeError(op, limit);
278
+ }
279
+ if (!res.body) return new Uint8Array(0);
280
+ const reader = res.body.getReader();
281
+ const chunks = [];
282
+ let total = 0;
283
+ for (; ; ) {
284
+ const { done, value } = await reader.read();
285
+ if (done) break;
286
+ total += value.byteLength;
287
+ if (total > limit) {
288
+ await reader.cancel().catch(() => void 0);
289
+ throw new ResponseTooLargeError(op, limit);
290
+ }
291
+ chunks.push(value);
292
+ }
293
+ const out = new Uint8Array(total);
294
+ let off = 0;
295
+ for (const c of chunks) {
296
+ out.set(c, off);
297
+ off += c.byteLength;
298
+ }
299
+ return out;
300
+ }
179
301
  function errorBody(text) {
180
302
  try {
181
303
  const v = JSON.parse(text);
@@ -190,6 +312,51 @@ function errorBody(text) {
190
312
  }
191
313
  }
192
314
 
315
+ // src/contract.ts
316
+ var RP_ROUTES = {
317
+ authNotificationEtsi: { method: "POST", path: "/authentication/notification/etsi/{semanticsIdentifier}" },
318
+ authNotificationDocument: { method: "POST", path: "/authentication/notification/document/{documentNumber}" },
319
+ authDeviceLinkAnonymous: { method: "POST", path: "/authentication/device-link/anonymous" },
320
+ authDeviceLinkEtsi: { method: "POST", path: "/authentication/device-link/etsi/{semanticsIdentifier}" },
321
+ signNotificationEtsi: { method: "POST", path: "/signature/notification/etsi/{semanticsIdentifier}" },
322
+ signNotificationDocument: { method: "POST", path: "/signature/notification/document/{documentNumber}" },
323
+ pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
324
+ pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
325
+ orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
326
+ session: { method: "GET", path: "/session/{sessionId}" },
327
+ personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
328
+ personDevices: { method: "GET", path: "/devices/etsi/{personEtsi}" },
329
+ personActivity: { method: "GET", path: "/rp/activity/etsi/{personEtsi}" },
330
+ personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
331
+ personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
332
+ tokenCertificate: { method: "POST", path: "/token/certificate" },
333
+ tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
334
+ };
335
+ var RP_ROUTES_UNDISCOVERED = {
336
+ tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
337
+ tokenAuthVerify: { method: "POST", path: "/token/auth/verify" },
338
+ pdfTokenPrepare: { method: "POST", path: "/pdf/token/prepare" },
339
+ pdfTokenFinalize: { method: "POST", path: "/pdf/token/{signingID}/finalize" },
340
+ pdfTokenDocument: { method: "GET", path: "/pdf/token/{signingID}/document" }
341
+ };
342
+ function routePath(route, ...args) {
343
+ let i = 0;
344
+ const out = route.path.replace(/\{[^}]*\}/g, () => {
345
+ const a = args[i++];
346
+ if (a === void 0) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0434\u0443\u0442\u0443\u0443`);
347
+ return encodeURIComponent(a);
348
+ });
349
+ if (i !== args.length) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0438\u043B\u04AF\u04AF`);
350
+ return out;
351
+ }
352
+ var OIDC_EXPECT = {
353
+ defaultScope: "openid",
354
+ pkceMethod: "S256",
355
+ idTokenAlg: "ES256",
356
+ /** `Authorization: Basic` (RFC 6749 §2.3.1). */
357
+ tokenEndpointAuthMethod: "client_secret_basic"
358
+ };
359
+
193
360
  // src/crypto.ts
194
361
  var import_node_crypto = require("crypto");
195
362
  function sha256Base64(data) {
@@ -292,25 +459,25 @@ var AuthApi = class {
292
459
  /** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
293
460
  async notificationByEtsi(id, interactions, opts) {
294
461
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
295
- const raw = await this.http.post(`/authentication/notification/etsi/${encodeURIComponent(id)}`, body);
462
+ const raw = await this.http.post(routePath(RP_ROUTES.authNotificationEtsi, id), body);
296
463
  return this.parseNotification(raw, acsp);
297
464
  }
298
465
  /** Push — баримтын дугаараар (тодорхой төхөөрөмж рүү). */
299
466
  async notificationByDocument(documentNumber, interactions, opts) {
300
467
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
301
- const raw = await this.http.post(`/authentication/notification/document/${encodeURIComponent(documentNumber)}`, body);
468
+ const raw = await this.http.post(routePath(RP_ROUTES.authNotificationDocument, documentNumber), body);
302
469
  return this.parseNotification(raw, acsp);
303
470
  }
304
471
  /** QR/App2App — нэр томьёололгүй (иргэн хэн болохыг урьдчилж мэдэхгүй). QR код үүсгэхэд. */
305
472
  async deviceLinkAnonymous(interactions, opts) {
306
473
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
307
- const raw = await this.http.post(`/authentication/device-link/anonymous`, body);
474
+ const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkAnonymous), body);
308
475
  return this.parseDeviceLink(raw, acsp);
309
476
  }
310
477
  /** QR/App2App — тодорхой иргэн (РД/civil_id/ETSI). */
311
478
  async deviceLinkByEtsi(id, interactions, opts) {
312
479
  const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
313
- const raw = await this.http.post(`/authentication/device-link/etsi/${encodeURIComponent(id)}`, body);
480
+ const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkEtsi, id), body);
314
481
  return this.parseDeviceLink(raw, acsp);
315
482
  }
316
483
  };
@@ -343,6 +510,7 @@ var SignApi = class {
343
510
  };
344
511
  if (opts?.callbackUrl?.trim()) body.initialCallbackUrl = opts.callbackUrl.trim();
345
512
  if (opts?.onBehalfOf?.trim()) body.onBehalfOf = opts.onBehalfOf.trim();
513
+ if (opts?.fileName?.trim()) body.fileName = opts.fileName.trim();
346
514
  return body;
347
515
  }
348
516
  parse(raw) {
@@ -355,12 +523,12 @@ var SignApi = class {
355
523
  * digest-ийг гаднаас бэлдэж өгнө — текст бус бинар баримтад тохиромжтой.
356
524
  */
357
525
  async digestByEtsi(id, digestB64, interactions, opts) {
358
- const raw = await this.http.post(`/signature/notification/etsi/${encodeURIComponent(id)}`, this.body(digestB64, interactions, opts));
526
+ const raw = await this.http.post(routePath(RP_ROUTES.signNotificationEtsi, id), this.body(digestB64, interactions, opts));
359
527
  return this.parse(raw);
360
528
  }
361
529
  /** Бэлэн digest-ээр push sign — баримтын дугаараар (auth-аас гарсан documentNumber дээр). */
362
530
  async digestByDocument(documentNumber, digestB64, interactions, opts) {
363
- const raw = await this.http.post(`/signature/notification/document/${encodeURIComponent(documentNumber)}`, this.body(digestB64, interactions, opts));
531
+ const raw = await this.http.post(routePath(RP_ROUTES.signNotificationDocument, documentNumber), this.body(digestB64, interactions, opts));
364
532
  return this.parse(raw);
365
533
  }
366
534
  /** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
@@ -401,9 +569,44 @@ function parseSessionResult(raw) {
401
569
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
402
570
  interactionTypeUsed: str(r.interactionTypeUsed),
403
571
  onBehalfOf: parseOrg(r.onBehalfOf),
404
- pdf: parsePdf(r.pdf)
572
+ pdf: parsePdf(r.pdf),
573
+ person: parsePerson(r.person),
574
+ tokenCertificate: parseTokenCert(r.tokenCertificate),
575
+ raw
405
576
  };
406
577
  }
578
+ var PERSON_STRINGS = [
579
+ "firstName",
580
+ "lastName",
581
+ "familyName",
582
+ "firstNameEn",
583
+ "lastNameEn",
584
+ "civilId",
585
+ "regNo",
586
+ "birthDate",
587
+ "gender"
588
+ ];
589
+ function parsePerson(raw) {
590
+ const p = asRecord(raw);
591
+ const out = {};
592
+ for (const k of PERSON_STRINGS) if (typeof p[k] === "string" && p[k]) out[k] = p[k];
593
+ if (typeof p.geID === "number" && p.geID !== 0) out.geID = p.geID;
594
+ return Object.keys(out).length ? out : null;
595
+ }
596
+ function parseTokenCert(raw) {
597
+ const t = asRecord(raw);
598
+ const status = str(t.status);
599
+ if (!status) return null;
600
+ const out = { status };
601
+ if (t.errorCode) out.errorCode = String(t.errorCode);
602
+ if (t.authSerialHex) out.authSerialHex = String(t.authSerialHex);
603
+ if (t.signSerialHex) out.signSerialHex = String(t.signSerialHex);
604
+ return out;
605
+ }
606
+ function personMatchesCertificate(person, subject) {
607
+ const serial = typeof subject === "string" ? subject : subject?.serialNumber;
608
+ return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
609
+ }
407
610
  function parsePdf(raw) {
408
611
  const p = asRecord(raw);
409
612
  const docID = str(p.docID);
@@ -457,7 +660,7 @@ var SessionApi = class {
457
660
  /** Нэг удаагийн long-poll. serverPollMs хүртэл хүлээнэ. */
458
661
  async poll(sessionId, serverPollMs = SERVER_POLL_MS) {
459
662
  const raw = await this.http.get(
460
- `/session/${encodeURIComponent(sessionId)}?timeoutMs=${serverPollMs}`,
663
+ `${routePath(RP_ROUTES.session, sessionId)}?timeoutMs=${serverPollMs}`,
461
664
  serverPollMs + 1e4
462
665
  // HTTP timeout нь серверийн poll-оос урт
463
666
  );
@@ -482,6 +685,8 @@ var SessionApi = class {
482
685
 
483
686
  // src/pdf.ts
484
687
  var import_node_crypto2 = require("crypto");
688
+ var DEFAULT_PDF_MAX_BYTES = 64 << 20;
689
+ var DEFAULT_PDF_TIMEOUT_MS = 8e4;
485
690
  function rec3(v) {
486
691
  return v && typeof v === "object" ? v : {};
487
692
  }
@@ -494,7 +699,10 @@ var PdfApi = class {
494
699
  http;
495
700
  creds;
496
701
  defaults;
497
- /** POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. */
702
+ /**
703
+ * POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. Түр алдаа: `ApiError` 429
704
+ * PDF_PREPARE_BUSY, 503 LTV_UNAVAILABLE / TSA_UNAVAILABLE — `retryAfterMs`-ийн дараа дахин (`retryable`).
705
+ */
498
706
  async prepare(input) {
499
707
  const request = {
500
708
  relyingPartyUUID: this.creds.rpUUID,
@@ -510,7 +718,7 @@ var PdfApi = class {
510
718
  const form = new FormData();
511
719
  form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
512
720
  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));
721
+ const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
514
722
  const receivedAt = Date.now();
515
723
  const vc = rec3(r.vc).value;
516
724
  const dl = rec3(r.deviceLink);
@@ -532,10 +740,80 @@ var PdfApi = class {
532
740
  }
533
741
  /**
534
742
  * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
535
- * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
743
+ * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`. Баримт
744
+ * бэлэн болоогүй бол `ApiError` 503 `.code` DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE — `retryAfterMs`-ийн
745
+ * дараа дахин татна (`retryable`).
536
746
  */
537
747
  document(sessionId) {
538
- return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
748
+ return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.timeout, this.maxBytes);
749
+ }
750
+ get timeout() {
751
+ return this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS;
752
+ }
753
+ get maxBytes() {
754
+ return this.defaults.maxBytes ?? DEFAULT_PDF_MAX_BYTES;
755
+ }
756
+ /**
757
+ * USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
758
+ * (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
759
+ * зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
760
+ * LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
761
+ * TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
762
+ */
763
+ async tokenPrepare(input) {
764
+ if (!input.pdf?.length || !input.certificate?.length || !input.signerEtsi?.trim() || !input.fileName?.trim()) {
765
+ throw new Error("eid: pdf token: pdf, certificate, signerEtsi, fileName \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
766
+ }
767
+ const request = {
768
+ relyingPartyUUID: this.creds.rpUUID,
769
+ relyingPartyName: this.creds.rpName,
770
+ signer: { etsi: input.signerEtsi.trim() },
771
+ certificate: Buffer.from(input.certificate).toString("base64"),
772
+ fileName: input.fileName
773
+ };
774
+ if (input.docID?.trim()) request.docID = input.docID.trim();
775
+ if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
776
+ const form = new FormData();
777
+ form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
778
+ form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
779
+ const raw = await this.http.postMultipart(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenPrepare), form, this.timeout);
780
+ const r = rec3(raw);
781
+ const hashToSign = new Uint8Array(Buffer.from(String(r.hashToSign ?? ""), "base64"));
782
+ if (!r.signingID || hashToSign.length !== 32) {
783
+ throw new ValidationError("pdf token prepare: signingID / hashToSign (32 \u0431\u0430\u0439\u0442) \u0431\u0443\u0440\u0443\u0443");
784
+ }
785
+ return {
786
+ signingId: String(r.signingID),
787
+ docId: String(r.docID ?? ""),
788
+ hashToSign,
789
+ hashAlgorithm: String(r.hashAlgorithm ?? ""),
790
+ signatureAlgorithm: String(r.signatureAlgorithm ?? ""),
791
+ signatureFormat: String(r.signatureFormat ?? ""),
792
+ expiresAt: String(r.expiresAt ?? ""),
793
+ raw
794
+ };
795
+ }
796
+ /**
797
+ * POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
798
+ * Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
799
+ * Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
800
+ * TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
801
+ */
802
+ async tokenFinalize(signingId, signature) {
803
+ if (signature?.length !== 64) throw new Error("eid: pdf token: signature \u043D\u044C \u0442\u04AF\u04AF\u0445\u0438\u0439 r||s 64 \u0431\u0430\u0439\u0442 \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439 (P-256, DER \u0431\u0438\u0448)");
804
+ const r = await this.http.post(
805
+ routePath(RP_ROUTES_UNDISCOVERED.pdfTokenFinalize, signingId),
806
+ { signature: Buffer.from(signature).toString("base64") },
807
+ this.timeout
808
+ );
809
+ return r;
810
+ }
811
+ /**
812
+ * GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
813
+ * хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
814
+ */
815
+ tokenDocument(signingId) {
816
+ return this.http.getBytes(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenDocument, signingId), this.timeout, this.maxBytes);
539
817
  }
540
818
  /**
541
819
  * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
@@ -567,13 +845,53 @@ var OrganizationApi = class {
567
845
  http;
568
846
  /** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
569
847
  async getRepresentations(personEtsi) {
570
- const raw = await this.http.get(`/organization/representations/etsi/${encodeURIComponent(personEtsi)}`);
848
+ const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
571
849
  const list = Array.isArray(raw.representations) ? raw.representations : [];
572
- return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
850
+ return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list, raw };
573
851
  }
574
852
  };
575
853
 
576
- // src/validator.ts
854
+ // src/person.ts
855
+ function rec4(v) {
856
+ return v && typeof v === "object" ? v : {};
857
+ }
858
+ var PersonApi = class {
859
+ constructor(http) {
860
+ this.http = http;
861
+ }
862
+ http;
863
+ async get(path) {
864
+ const raw = await this.http.get(path);
865
+ return { ...rec4(raw), raw };
866
+ }
867
+ /** Иргэний бүх гэрчилгээ + статусын тоолол. */
868
+ certificates(personEtsi) {
869
+ return this.get(routePath(RP_ROUTES.personCertificates, personEtsi));
870
+ }
871
+ /** Иргэний холбоотой төхөөрөмжүүд (идэвхтэй + идэвхгүй). */
872
+ devices(personEtsi) {
873
+ return this.get(routePath(RP_ROUTES.personDevices, personEtsi));
874
+ }
875
+ /** Иргэний үйл ажиллагааны түүх. */
876
+ activity(personEtsi, query) {
877
+ const q = new URLSearchParams();
878
+ if (query?.flow) q.set("flow", query.flow);
879
+ if (query?.limit && query.limit > 0) q.set("limit", String(query.limit));
880
+ if (query?.offset && query.offset > 0) q.set("offset", String(query.offset));
881
+ const qs = q.toString();
882
+ return this.get(routePath(RP_ROUTES.personActivity, personEtsi) + (qs ? `?${qs}` : ""));
883
+ }
884
+ /** Самбарын нэгдсэн тоо. */
885
+ summary(personEtsi) {
886
+ return this.get(routePath(RP_ROUTES.personSummary, personEtsi));
887
+ }
888
+ /** Асран хамгаалж буй хүүхдүүд (зөвхөн унших). */
889
+ children(personEtsi) {
890
+ return this.get(routePath(RP_ROUTES.personChildren, personEtsi));
891
+ }
892
+ };
893
+
894
+ // src/token.ts
577
895
  var import_node_crypto4 = require("crypto");
578
896
 
579
897
  // src/subject.ts
@@ -680,8 +998,215 @@ function parseSubject(cert) {
680
998
  out.name = full || out.commonName || null;
681
999
  return out;
682
1000
  }
1001
+ function certExtension(der, oid) {
1002
+ return certExtensions(der).find((e) => e.oid === oid)?.value;
1003
+ }
1004
+ function certExtensions(der) {
1005
+ const out = [];
1006
+ const top = tlv(der, 0, der.length);
1007
+ const tbs = tlv(der, top.start, top.end);
1008
+ let f = tlv(der, tbs.start, tbs.end);
1009
+ if (f.tag === 160) f = tlv(der, f.end, tbs.end);
1010
+ for (let k = 0; k < 5; k++) f = tlv(der, f.end, tbs.end);
1011
+ for (let pos = f.end; pos < tbs.end; ) {
1012
+ const t = tlv(der, pos, tbs.end);
1013
+ if (t.tag === 163) {
1014
+ const seq = tlv(der, t.start, t.end);
1015
+ for (let ep = seq.start; ep < seq.end; ) {
1016
+ const ext = tlv(der, ep, seq.end);
1017
+ const id = tlv(der, ext.start, ext.end);
1018
+ let v = tlv(der, id.end, ext.end);
1019
+ let critical = false;
1020
+ if (v.tag === 1) {
1021
+ critical = v.end > v.start && der[v.start] !== 0;
1022
+ v = tlv(der, v.end, ext.end);
1023
+ }
1024
+ if (v.tag !== 4) throw new ValidationError("extension: extnValue OCTET STRING \u0431\u0438\u0448");
1025
+ out.push({ oid: oidOf(der, id), critical, value: der.subarray(v.start, v.end) });
1026
+ ep = ext.end;
1027
+ }
1028
+ return out;
1029
+ }
1030
+ pos = t.end;
1031
+ }
1032
+ return out;
1033
+ }
1034
+
1035
+ // src/token.ts
1036
+ function rec5(v) {
1037
+ return v && typeof v === "object" ? v : {};
1038
+ }
1039
+ function hexSha256Equal(hex, data) {
1040
+ if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
1041
+ return (0, import_node_crypto4.timingSafeEqual)((0, import_node_crypto4.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
1042
+ }
1043
+ var TOKEN_RESULT_TIMEOUT_MS = 3e4;
1044
+ var TokenApi = class {
1045
+ constructor(http, creds) {
1046
+ this.http = http;
1047
+ this.creds = creds;
1048
+ }
1049
+ http;
1050
+ creds;
1051
+ /**
1052
+ * POST /token/certificate — токены AUTH + SIGN гэрчилгээ олголт эхлүүлнэ (иргэн утсаараа батална). CA-ийн буцаасан
1053
+ * CSR hash-ууд илгээсэнтэй таарахгүй бол ValidationError.
1054
+ */
1055
+ async issueCertificate(input) {
1056
+ if (!input.signerEtsi?.trim() || !input.csrAuth?.length || !input.csrSign?.length || !input.tokenSerial?.trim()) {
1057
+ throw new Error("token: signerEtsi, csrAuth, csrSign, tokenSerial \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
1058
+ }
1059
+ const body = {
1060
+ relyingPartyUUID: this.creds.rpUUID,
1061
+ relyingPartyName: this.creds.rpName,
1062
+ signer: { etsi: input.signerEtsi.trim() },
1063
+ csrAuth: Buffer.from(input.csrAuth).toString("base64"),
1064
+ csrSign: Buffer.from(input.csrSign).toString("base64"),
1065
+ tokenSerial: input.tokenSerial.trim(),
1066
+ onBehalfOf: input.onBehalfOf?.trim() ?? "",
1067
+ flow: "notification"
1068
+ };
1069
+ if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
1070
+ const r = rec5(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
1071
+ if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
1072
+ 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");
1073
+ }
1074
+ const vc = rec5(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
1075
+ return {
1076
+ sessionId: String(r.sessionID ?? ""),
1077
+ vc: vc != null ? String(vc) : null,
1078
+ csrAuthSha256: String(r.csrAuthSha256).toLowerCase(),
1079
+ csrSignSha256: String(r.csrSignSha256).toLowerCase(),
1080
+ ...typeof r.expiresAt === "string" ? { expiresAt: r.expiresAt } : {}
1081
+ };
1082
+ }
1083
+ /**
1084
+ * GET /token/certificate/{sessionID} — олгосон AUTH + SIGN гэрчилгээ + гинж. Токенд бичихээс өмнө
1085
+ * {@link verifyTokenCertificates}-ээр CSR-ийн түлхүүртэй тулга.
1086
+ */
1087
+ async certificateResult(sessionId) {
1088
+ const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
1089
+ const r = rec5(raw);
1090
+ const item = (v) => v && typeof v === "object" ? v : null;
1091
+ return {
1092
+ status: String(r.status ?? ""),
1093
+ auth: item(r.auth),
1094
+ sign: item(r.sign),
1095
+ chain: Array.isArray(r.chain) ? r.chain.map(String) : [],
1096
+ chainPem: String(r.chainPem ?? ""),
1097
+ organization: r.organization && typeof r.organization === "object" ? r.organization : null,
1098
+ keyMedium: String(r.keyMedium ?? ""),
1099
+ certificateLevel: String(r.certificateLevel ?? ""),
1100
+ raw
1101
+ };
1102
+ }
1103
+ /** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
1104
+ async authChallenge() {
1105
+ const r = rec5(
1106
+ await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
1107
+ relyingPartyUUID: this.creds.rpUUID,
1108
+ relyingPartyName: this.creds.rpName
1109
+ })
1110
+ );
1111
+ return {
1112
+ challengeId: String(r.challengeId ?? ""),
1113
+ challenge: String(r.challenge ?? ""),
1114
+ rpUUID: String(r.rpUUID ?? ""),
1115
+ expiresAt: String(r.expiresAt ?? "")
1116
+ };
1117
+ }
1118
+ /**
1119
+ * POST /token/auth/verify — токены AUTH гэрчилгээ (DER) + challenge дээрх түүхий r||s. CA гинж, төлөв, цуцлалт, гарын
1120
+ * үсгийг шалгана; SDK нэмж endResult === OK ба identity.etsi === гэрчилгээний SERIALNUMBER-ийг тулгана (эс бөгөөс
1121
+ * ValidationError). Challenge нэг удаагийн — алдааны дараа шинээр авна.
1122
+ */
1123
+ async authVerify(challengeId, certDer, signature) {
1124
+ let serial;
1125
+ try {
1126
+ serial = parseSubject(new import_node_crypto4.X509Certificate(certDer)).serialNumber;
1127
+ } catch {
1128
+ 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");
1129
+ }
1130
+ 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");
1131
+ const raw = await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthVerify), {
1132
+ challengeId,
1133
+ certificate: Buffer.from(certDer).toString("base64"),
1134
+ signature: Buffer.from(signature).toString("base64")
1135
+ });
1136
+ const r = rec5(raw);
1137
+ if (rec5(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1138
+ const id = rec5(r.identity);
1139
+ 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");
1140
+ const org = rec5(r.organization);
1141
+ const cert = rec5(r.cert);
1142
+ return {
1143
+ identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
1144
+ serialHex: String(cert.serialHex ?? ""),
1145
+ notAfter: String(cert.notAfter ?? ""),
1146
+ organization: org.etsi ? { etsi: String(org.etsi), name: String(org.name ?? "") } : null,
1147
+ raw
1148
+ };
1149
+ }
1150
+ };
1151
+ function csrSpki(csr) {
1152
+ const top = tlv(csr, 0, csr.length);
1153
+ const info = tlv(csr, top.start, top.end);
1154
+ const version = tlv(csr, info.start, info.end);
1155
+ const subject = tlv(csr, version.end, info.end);
1156
+ const spki = tlv(csr, subject.end, info.end);
1157
+ return (0, import_node_crypto4.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
1158
+ format: "der",
1159
+ type: "spki"
1160
+ });
1161
+ }
1162
+ function verifyTokenCertificates(result, csrAuth, csrSign) {
1163
+ if (!result?.auth || !result.sign) throw new ValidationError("token: auth/sign \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
1164
+ for (const [name, item, csr] of [
1165
+ ["auth", result.auth, csrAuth],
1166
+ ["sign", result.sign, csrSign]
1167
+ ]) {
1168
+ let want;
1169
+ try {
1170
+ want = csrSpki(csr);
1171
+ } catch {
1172
+ throw new Error(`token: ${name} CSR \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
1173
+ }
1174
+ let got;
1175
+ try {
1176
+ got = new import_node_crypto4.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
1177
+ } catch {
1178
+ 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`);
1179
+ }
1180
+ 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`);
1181
+ }
1182
+ }
1183
+ function tokenAuthDigest(rpUUID, challengeB64) {
1184
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(rpUUID)) {
1185
+ 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");
1186
+ }
1187
+ const ch = Buffer.from(challengeB64, "base64");
1188
+ if (ch.length !== 32 || ch.toString("base64") !== challengeB64) throw new Error("token auth: challenge 32 \u0431\u0430\u0439\u0442 base64 \u0431\u0438\u0448");
1189
+ return (0, import_node_crypto4.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1190
+ }
1191
+ function tokenLimitOf(err) {
1192
+ if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
1193
+ let b;
1194
+ try {
1195
+ b = rec5(JSON.parse(err.body));
1196
+ } catch {
1197
+ return void 0;
1198
+ }
1199
+ if (typeof b.limit !== "number" || !Number.isInteger(b.limit) || b.limit < 0) return void 0;
1200
+ if (b.kind === "PERSONAL") return { kind: "PERSONAL", limit: b.limit };
1201
+ if (b.kind === "ORGANIZATION" && typeof b.organization === "string" && b.organization.startsWith("NTRMN-")) {
1202
+ return { kind: "ORGANIZATION", limit: b.limit, organization: b.organization };
1203
+ }
1204
+ return void 0;
1205
+ }
683
1206
 
684
1207
  // src/validator.ts
1208
+ var import_node_crypto5 = require("crypto");
1209
+ var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
685
1210
  var ResponseValidator = class {
686
1211
  anchors;
687
1212
  intermediates;
@@ -690,14 +1215,28 @@ var ResponseValidator = class {
690
1215
  allowUntrusted;
691
1216
  revocation;
692
1217
  revocationMode;
1218
+ allowedIssuers;
1219
+ checkKeyUsage;
1220
+ schemeName;
693
1221
  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));
1222
+ this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
1223
+ this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
696
1224
  this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
697
1225
  this.skew = trust.clockSkewMs ?? 0;
698
1226
  this.allowUntrusted = trust.allowUntrusted ?? false;
699
1227
  this.revocation = trust.revocation;
700
1228
  this.revocationMode = trust.revocationMode ?? "hard-fail";
1229
+ if (trust.allowedIssuerSkis?.length) {
1230
+ this.allowedIssuers = new Set(
1231
+ trust.allowedIssuerSkis.map((s) => {
1232
+ const ski = s.trim().replace(/:/g, "").toLowerCase();
1233
+ if (!/^(?:[0-9a-f]{2})+$/.test(ski)) throw new Error(`ResponseValidator: allowedIssuerSkis: "${s}" hex key identifier \u0431\u0438\u0448`);
1234
+ return ski;
1235
+ })
1236
+ );
1237
+ }
1238
+ this.checkKeyUsage = trust.checkKeyUsage ?? false;
1239
+ this.schemeName = trust.schemeName;
701
1240
  }
702
1241
  // ── нийтийн API ──
703
1242
  /**
@@ -725,6 +1264,7 @@ var ResponseValidator = class {
725
1264
  if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
726
1265
  const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
727
1266
  const payload = buildAcspV2Payload({
1267
+ ...this.schemeName ? { schemeName: this.schemeName } : {},
728
1268
  serverRandom,
729
1269
  rpChallenge: acsp.rpChallenge,
730
1270
  userChallenge,
@@ -828,9 +1368,10 @@ var ResponseValidator = class {
828
1368
  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
1369
  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
1370
  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"));
1371
+ const leaf = new import_node_crypto5.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
832
1372
  this.checkValidity(leaf);
833
1373
  this.checkLevel(String(result.certificateLevel ?? ""));
1374
+ this.checkPurpose(leaf, protocol);
834
1375
  if (this.anchors.length === 0) {
835
1376
  if (!this.allowUntrusted) {
836
1377
  throw new ValidationError(
@@ -838,10 +1379,37 @@ var ResponseValidator = class {
838
1379
  );
839
1380
  }
840
1381
  } else {
841
- this.verifyChain(leaf);
1382
+ this.verifyChain(leaf, protocol === "ACSP_V2" ? EKU_CLIENT_AUTH : EKU_EMAIL_PROTECTION);
842
1383
  }
1384
+ this.checkIssuer(leaf);
843
1385
  return leaf;
844
1386
  }
1387
+ /**
1388
+ * checkKeyUsage үед гэрчилгээний зориулалт протоколд тохирох эсэх (CA crypto/ca_profile.go): ACSP_V2 (auth) → KU
1389
+ * digitalSignature + EKU clientAuth (эсвэл anyExtendedKeyUsage); RAW_DIGEST_SIGNATURE (sign) → KU contentCommitment.
1390
+ */
1391
+ checkPurpose(leaf, protocol) {
1392
+ if (!this.checkKeyUsage) return;
1393
+ const ku = keyUsageBits(leaf);
1394
+ if (protocol === "ACSP_V2") {
1395
+ 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");
1396
+ const eku = extKeyUsage(leaf);
1397
+ if (!eku.includes("1.3.6.1.5.5.7.3.2") && !eku.includes("2.5.29.37.0")) {
1398
+ 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)");
1399
+ }
1400
+ return;
1401
+ }
1402
+ 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");
1403
+ }
1404
+ /** allowedIssuerSkis үед leaf-ийн шууд issuer-ийн SKI (issuer-т алга бол leaf-ийн AKI) жагсаалтад байх ёстой. */
1405
+ checkIssuer(leaf) {
1406
+ if (!this.allowedIssuers) return;
1407
+ const issuer = this.findIssuer(leaf);
1408
+ const ski = (issuer && subjectKeyId(issuer)) ?? authorityKeyId(leaf);
1409
+ if (!ski || !this.allowedIssuers.has(ski)) {
1410
+ 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)");
1411
+ }
1412
+ }
845
1413
  checkValidity(cert) {
846
1414
  const now = Date.now();
847
1415
  const from = Date.parse(cert.validFrom);
@@ -855,21 +1423,40 @@ var ResponseValidator = class {
855
1423
  const need = rank[this.requiredLevel] ?? 2;
856
1424
  if (got < need) throw new ValidationError(`\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0442\u04AF\u0432\u0448\u0438\u043D \u0445\u04AF\u0440\u044D\u043B\u0446\u044D\u0445\u0433\u04AF\u0439: ${level} < ${this.requiredLevel}`);
857
1425
  }
858
- /** leaf-ээс эхлэн issuer-ийг intermediates/anchors дотроос олж, anchor хүртэл chain зангидна. */
859
- verifyChain(leaf) {
860
- let current = leaf;
861
- const pool = [...this.intermediates, ...this.anchors];
862
- for (let depth = 0; depth < 8; depth++) {
863
- for (const anchor of this.anchors) {
864
- if (isCa(anchor) && current.checkIssued(anchor) && current.verify(anchor.publicKey)) return;
1426
+ /**
1427
+ * RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
1428
+ * замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
1429
+ * гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
1430
+ * завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
1431
+ * бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
1432
+ * fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
1433
+ * хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
1434
+ */
1435
+ verifyChain(leaf, usage) {
1436
+ const now = Math.min(Math.max(Date.now(), Date.parse(leaf.validFrom)), Date.parse(leaf.validTo));
1437
+ const leafReason = criticalReason(leaf) ?? ekuReason(leaf, usage);
1438
+ if (leafReason) throw new ValidationError(`\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0433\u0438\u043D\u0436 \u0445\u04AF\u0447\u0438\u043D\u0433\u04AF\u0439: ${leafReason}`);
1439
+ let reason;
1440
+ const extend = (cur, cas) => {
1441
+ if (cas.length >= 8) return false;
1442
+ for (const [cand, isAnchor] of [
1443
+ ...this.anchors.map((c) => [c, true]),
1444
+ ...this.intermediates.map((c) => [c, false])
1445
+ ]) {
1446
+ if (cas.includes(cand) || !signedBy(cur, cand)) continue;
1447
+ const bad = caReason(cand, cas.length, now, usage);
1448
+ if (bad) {
1449
+ reason ??= bad;
1450
+ continue;
1451
+ }
1452
+ if (isAnchor || extend(cand, [...cas, cand])) return true;
865
1453
  }
866
- const issuer = pool.find(
867
- (c) => isCa(c) && current.checkIssued(c) && current.verify(c.publicKey)
868
- );
869
- if (!issuer) break;
870
- current = issuer;
871
- }
872
- throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0438\u0442\u0433\u044D\u043B\u0446\u043B\u0438\u0439\u043D \u0433\u0438\u043D\u0436 trust anchor (Root CA) \u0445\u04AF\u0440\u0441\u044D\u043D\u0433\u04AF\u0439");
1454
+ return false;
1455
+ };
1456
+ if (extend(leaf, [])) return;
1457
+ throw new ValidationError(
1458
+ reason ? `\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0433\u0438\u043D\u0436 \u0445\u04AF\u0447\u0438\u043D\u0433\u04AF\u0439: ${reason}` : "\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0438\u0442\u0433\u044D\u043B\u0446\u043B\u0438\u0439\u043D \u0433\u0438\u043D\u0436 trust anchor (Root CA) \u0445\u04AF\u0440\u0441\u044D\u043D\u0433\u04AF\u0439"
1459
+ );
873
1460
  }
874
1461
  /** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
875
1462
  findIssuer(leaf) {
@@ -884,14 +1471,23 @@ var ResponseValidator = class {
884
1471
  * дуудна (тэдгээр нь sync; revocation нь сүлжээ I/O тул async).
885
1472
  */
886
1473
  async checkRevocation(cert) {
887
- if (!this.revocation) return;
1474
+ await this.checkRevocationStatus(cert);
1475
+ }
1476
+ /**
1477
+ * {@link checkRevocation} + юу шийдсэнийг буцаана (лог/аудитад): "good" | "unchecked" (unknown, soft-fail) |
1478
+ * "skipped" (hook алга). "revoked", мөн hard-fail горимд "unknown" → ValidationError; hook-ийн алдаа дамжина.
1479
+ */
1480
+ async checkRevocationStatus(cert) {
1481
+ if (!this.revocation) return "skipped";
888
1482
  const status = await this.revocation(cert, this.findIssuer(cert));
1483
+ if (status === "good") return "good";
889
1484
  if (status === "revoked") {
890
1485
  throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
891
1486
  }
892
- if (status === "unknown" && this.revocationMode === "hard-fail") {
1487
+ if (this.revocationMode === "hard-fail") {
893
1488
  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
1489
  }
1490
+ return "unchecked";
895
1491
  }
896
1492
  };
897
1493
  function signedCallbackUrl(fromResponse, rpCallback, flowType) {
@@ -906,16 +1502,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
906
1502
  return fromResponse;
907
1503
  }
908
1504
  function userChallengeOf(verifier) {
909
- return (0, import_node_crypto4.createHash)("sha256").update(verifier, "utf8").digest("base64url");
1505
+ return (0, import_node_crypto5.createHash)("sha256").update(verifier, "utf8").digest("base64url");
910
1506
  }
911
1507
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
912
1508
  const sig = Buffer.from(sigB64, "base64");
913
1509
  const h = nodeHash(hash);
914
1510
  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);
1511
+ return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
916
1512
  }
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);
1513
+ if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto5.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
1514
+ return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PADDING }, sig);
919
1515
  }
920
1516
  function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
921
1517
  const sig = Buffer.from(sigB64, "base64");
@@ -924,7 +1520,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
924
1520
  if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
925
1521
  let em;
926
1522
  try {
927
- em = (0, import_node_crypto4.publicDecrypt)({ key, padding: import_node_crypto4.constants.RSA_NO_PADDING }, sig);
1523
+ em = (0, import_node_crypto5.publicDecrypt)({ key, padding: import_node_crypto5.constants.RSA_NO_PADDING }, sig);
928
1524
  } catch {
929
1525
  return false;
930
1526
  }
@@ -1008,7 +1604,7 @@ function mgf1(hash, seed, length) {
1008
1604
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
1009
1605
  const c = Buffer.alloc(4);
1010
1606
  c.writeUInt32BE(i, 0);
1011
- out.push((0, import_node_crypto4.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1607
+ out.push((0, import_node_crypto5.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1012
1608
  }
1013
1609
  return Buffer.concat(out).subarray(0, length);
1014
1610
  }
@@ -1032,12 +1628,118 @@ function emsaPssVerify(mHash, em, emBits, hash) {
1032
1628
  const psLen = emLen - hLen - sLen - 2;
1033
1629
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
1034
1630
  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();
1631
+ const h2 = (0, import_node_crypto5.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1036
1632
  return h.equals(h2);
1037
1633
  }
1634
+ function keyUsageBits(cert) {
1635
+ const v = certExtension(cert.raw, "2.5.29.15");
1636
+ if (!v) return 0;
1637
+ const bs = tlv(v, 0, v.length);
1638
+ if (bs.tag !== 3 || bs.end - bs.start < 2) return 0;
1639
+ return v[bs.start + 1];
1640
+ }
1641
+ function extKeyUsage(cert) {
1642
+ const v = certExtension(cert.raw, "2.5.29.37");
1643
+ if (!v) return [];
1644
+ const seq = tlv(v, 0, v.length);
1645
+ const out = [];
1646
+ for (let p = seq.start; p < seq.end; ) {
1647
+ const t = tlv(v, p, seq.end);
1648
+ out.push(oidOf(v, t));
1649
+ p = t.end;
1650
+ }
1651
+ return out;
1652
+ }
1653
+ function subjectKeyId(cert) {
1654
+ const v = certExtension(cert.raw, "2.5.29.14");
1655
+ if (!v) return void 0;
1656
+ const o = tlv(v, 0, v.length);
1657
+ return o.tag === 4 && o.end > o.start ? Buffer.from(v.subarray(o.start, o.end)).toString("hex") : void 0;
1658
+ }
1659
+ function authorityKeyId(cert) {
1660
+ const v = certExtension(cert.raw, "2.5.29.35");
1661
+ if (!v) return void 0;
1662
+ const seq = tlv(v, 0, v.length);
1663
+ if (seq.start >= seq.end) return void 0;
1664
+ const k = tlv(v, seq.start, seq.end);
1665
+ return k.tag === 128 && k.end > k.start ? Buffer.from(v.subarray(k.start, k.end)).toString("hex") : void 0;
1666
+ }
1038
1667
  function isCa(cert) {
1039
1668
  return cert.ca === true;
1040
1669
  }
1670
+ var EKU_ANY = "2.5.29.37.0";
1671
+ var EKU_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2";
1672
+ var EKU_EMAIL_PROTECTION = "1.3.6.1.5.5.7.3.4";
1673
+ var OID_NAME_CONSTRAINTS = "2.5.29.30";
1674
+ var HANDLED_CRITICAL = /* @__PURE__ */ new Set([
1675
+ "2.5.29.14",
1676
+ // subjectKeyIdentifier
1677
+ "2.5.29.15",
1678
+ // keyUsage
1679
+ "2.5.29.17",
1680
+ // subjectAltName
1681
+ "2.5.29.19",
1682
+ // basicConstraints
1683
+ "2.5.29.30",
1684
+ // nameConstraints (CA-д — caReason fail-closed)
1685
+ "2.5.29.31",
1686
+ // cRLDistributionPoints
1687
+ "2.5.29.32",
1688
+ // certificatePolicies
1689
+ "2.5.29.35",
1690
+ // authorityKeyIdentifier
1691
+ "2.5.29.37",
1692
+ // extKeyUsage
1693
+ "1.3.6.1.5.5.7.1.1"
1694
+ // authorityInfoAccess
1695
+ ]);
1696
+ function signedBy(child, cand) {
1697
+ if (child.issuer !== cand.subject) return false;
1698
+ const aki = authorityKeyId(child);
1699
+ const ski = subjectKeyId(cand);
1700
+ if (aki && ski && aki !== ski) return false;
1701
+ try {
1702
+ return child.verify(cand.publicKey);
1703
+ } catch {
1704
+ return false;
1705
+ }
1706
+ }
1707
+ function criticalReason(cert) {
1708
+ return certExtensions(cert.raw).some((e) => e.critical && !HANDLED_CRITICAL.has(e.oid)) ? "\u04AF\u043B \u0442\u0430\u043D\u0438\u0445 critical extension" : void 0;
1709
+ }
1710
+ function ekuReason(cert, usage) {
1711
+ const eku = extKeyUsage(cert);
1712
+ return eku.length && !eku.includes(usage) && !eku.includes(EKU_ANY) ? "\u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 ExtKeyUsage \u044D\u043D\u044D \u04AF\u0439\u043B\u0434\u044D\u043B\u0434 \u0442\u043E\u0445\u0438\u0440\u043E\u0445\u0433\u04AF\u0439 (\u0433\u0438\u043D\u0436\u0438\u0439\u043D EKU)" : void 0;
1713
+ }
1714
+ function caReason(ca, below, now, usage) {
1715
+ if (!isCa(ca)) return "\u0437\u0430\u0432\u0441\u0440\u044B\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D CA \u0431\u0438\u0448 \u044D\u0441\u0432\u044D\u043B keyCertSign \u044D\u0440\u0445\u0433\u04AF\u0439";
1716
+ if (certExtension(ca.raw, "2.5.29.15") && !(keyUsageBits(ca) & 4)) return "CA-\u0438\u0439\u043D KeyUsage-\u0434 keyCertSign \u0430\u043B\u0433\u0430";
1717
+ const from = Date.parse(ca.validFrom);
1718
+ const to = Date.parse(ca.validTo);
1719
+ if (Number.isNaN(from) || Number.isNaN(to) || now < from || now > to) {
1720
+ return "\u0433\u0438\u043D\u0436\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0434\u0443\u0443\u0441\u0441\u0430\u043D \u044D\u0441\u0432\u044D\u043B \u0445\u04AF\u0447\u0438\u043D\u0442\u044D\u0439 \u0431\u043E\u043B\u043E\u043E\u0433\u04AF\u0439";
1721
+ }
1722
+ const pathLen = basicConstraintsPathLen(ca);
1723
+ if (pathLen !== void 0 && below > pathLen) return "pathLenConstraint \u0437\u04E9\u0440\u0447\u0441\u04E9\u043D (\u0437\u0430\u0432\u0441\u0440\u044B\u043D CA \u0445\u044D\u0442 \u043E\u043B\u043E\u043D)";
1724
+ if (certExtension(ca.raw, OID_NAME_CONSTRAINTS)) return "CA-\u0434 name constraints \u0431\u0430\u0439\u043D\u0430 (SDK \u04AF\u043D\u044D\u043B\u0434\u044D\u0433\u0433\u04AF\u0439 \u2014 fail-closed)";
1725
+ return criticalReason(ca) ?? ekuReason(ca, usage);
1726
+ }
1727
+ function basicConstraintsPathLen(cert) {
1728
+ const v = certExtension(cert.raw, "2.5.29.19");
1729
+ if (!v) return void 0;
1730
+ const seq = tlv(v, 0, v.length);
1731
+ for (let p = seq.start; p < seq.end; ) {
1732
+ const t = tlv(v, p, seq.end);
1733
+ if (t.tag === 2) {
1734
+ if (t.end - t.start > 4 || (v[t.start] & 128) !== 0) throw new ValidationError("basicConstraints: pathLen \u0431\u0443\u0440\u0443\u0443");
1735
+ let n = 0;
1736
+ for (let i = t.start; i < t.end; i++) n = n * 256 + v[i];
1737
+ return n;
1738
+ }
1739
+ p = t.end;
1740
+ }
1741
+ return void 0;
1742
+ }
1041
1743
 
1042
1744
  // src/client.ts
1043
1745
  var DEFAULT_BASE_URL = "https://rp.eidmongolia.mn";
@@ -1062,6 +1764,8 @@ var EidClient = class {
1062
1764
  sign;
1063
1765
  pdf;
1064
1766
  organization;
1767
+ person;
1768
+ token;
1065
1769
  session;
1066
1770
  validator;
1067
1771
  constructor(cfg) {
@@ -1077,8 +1781,14 @@ var EidClient = class {
1077
1781
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1078
1782
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1079
1783
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
1080
- this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level });
1784
+ this.pdf = new PdfApi(http, cfg.credentials, {
1785
+ certificateLevel: level,
1786
+ timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
1787
+ maxBytes: cfg.pdfMaxBytes && cfg.pdfMaxBytes > 0 ? cfg.pdfMaxBytes : DEFAULT_PDF_MAX_BYTES
1788
+ });
1081
1789
  this.organization = new OrganizationApi(http);
1790
+ this.person = new PersonApi(http);
1791
+ this.token = new TokenApi(http, cfg.credentials);
1082
1792
  this.session = new SessionApi(http);
1083
1793
  this.validator = new ResponseValidator(cfg.trust ?? {});
1084
1794
  }
@@ -1127,7 +1837,7 @@ function openOrShowQR(session, opts) {
1127
1837
  }
1128
1838
 
1129
1839
  // src/devicelinkv3.ts
1130
- var import_node_crypto5 = require("crypto");
1840
+ var import_node_crypto6 = require("crypto");
1131
1841
  var TYPES = ["QR", "Web2App", "App2App"];
1132
1842
  var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
1133
1843
  var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
@@ -1184,7 +1894,7 @@ function buildDeviceLink(p) {
1184
1894
  callback,
1185
1895
  unprotected
1186
1896
  ].join("|");
1187
- const authCode = (0, import_node_crypto5.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
1897
+ const authCode = (0, import_node_crypto6.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
1188
1898
  return `${unprotected}&authCode=${authCode}`;
1189
1899
  }
1190
1900
  function qrDeviceLinkTicker(input, onLink, opts) {
@@ -1197,7 +1907,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
1197
1907
  }
1198
1908
 
1199
1909
  // src/oidc.ts
1200
- var import_node_crypto6 = require("crypto");
1910
+ var import_node_crypto7 = require("crypto");
1201
1911
  var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
1202
1912
  var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
1203
1913
  var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
@@ -1234,7 +1944,7 @@ var EidOidcClient = class {
1234
1944
  if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
1235
1945
  if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
1236
1946
  this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
1237
- this.scope = cfg.scope ?? "openid";
1947
+ this.scope = cfg.scope ?? OIDC_EXPECT.defaultScope;
1238
1948
  }
1239
1949
  cfg;
1240
1950
  issuer;
@@ -1282,7 +1992,7 @@ var EidOidcClient = class {
1282
1992
  state,
1283
1993
  nonce,
1284
1994
  code_challenge: await pkceChallenge(codeVerifier),
1285
- code_challenge_method: "S256"
1995
+ code_challenge_method: OIDC_EXPECT.pkceMethod
1286
1996
  });
1287
1997
  if (opts.display) p.set("display", opts.display);
1288
1998
  u.search = p.toString();
@@ -1361,7 +2071,7 @@ var EidOidcClient = class {
1361
2071
  const header = decodeJsonPart(parts[0]);
1362
2072
  const payload = decodeJsonPart(parts[1]);
1363
2073
  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");
2074
+ if (header.alg !== OIDC_EXPECT.idTokenAlg) throw bad("alg ES256 \u0431\u0438\u0448");
1365
2075
  const kid = typeof header.kid === "string" ? header.kid : "";
1366
2076
  if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
1367
2077
  const pick = (ks) => ks.find(
@@ -1454,7 +2164,7 @@ function formEncode(s) {
1454
2164
  function timingSafeEqualStr(a, b) {
1455
2165
  const x = Buffer.from(a, "utf8");
1456
2166
  const y = Buffer.from(b, "utf8");
1457
- return x.length === y.length && (0, import_node_crypto6.timingSafeEqual)(x, y);
2167
+ return x.length === y.length && (0, import_node_crypto7.timingSafeEqual)(x, y);
1458
2168
  }
1459
2169
  function decodeJsonPart(s) {
1460
2170
  try {
@@ -1477,7 +2187,10 @@ function callbackParams(input) {
1477
2187
  ApiError,
1478
2188
  AuthApi,
1479
2189
  AuthenticationError,
2190
+ CITIZEN_ISSUING_CA_SKI,
1480
2191
  DEFAULT_BASE_URL,
2192
+ DEFAULT_PDF_MAX_BYTES,
2193
+ DEFAULT_PDF_TIMEOUT_MS,
1481
2194
  DeviceLinkError,
1482
2195
  EID_ACR_ADVANCED,
1483
2196
  EID_ACR_QUALIFIED,
@@ -1492,10 +2205,14 @@ function callbackParams(input) {
1492
2205
  ON_BEHALF_ERROR_CODES,
1493
2206
  OrganizationApi,
1494
2207
  PdfApi,
2208
+ PersonApi,
2209
+ RETRYABLE_ERROR_CODES,
2210
+ ResponseTooLargeError,
1495
2211
  ResponseValidator,
1496
2212
  SessionApi,
1497
2213
  SessionFailedError,
1498
2214
  SignApi,
2215
+ TokenApi,
1499
2216
  ValidationError,
1500
2217
  acspV2Digest,
1501
2218
  buildAcspV2Payload,
@@ -1504,14 +2221,19 @@ function callbackParams(input) {
1504
2221
  interactionsToBase64,
1505
2222
  isMobileUserAgent,
1506
2223
  openOrShowQR,
2224
+ parseRetryAfter,
1507
2225
  parseSessionResult,
1508
2226
  parseSubject,
2227
+ personMatchesCertificate,
1509
2228
  pkceChallenge,
1510
2229
  qrDeviceLinkTicker,
1511
2230
  randomChallenge,
1512
2231
  resolveBaseUrl,
1513
2232
  sha256Base64,
2233
+ tokenAuthDigest,
2234
+ tokenLimitOf,
1514
2235
  userChallengeOf,
1515
2236
  verifyPayloadSignature,
1516
- verifyPrehashedSignature
2237
+ verifyPrehashedSignature,
2238
+ verifyTokenCertificates
1517
2239
  });