@gerege-systems/eid-mongolia-sdk 0.10.0 → 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/README.md CHANGED
@@ -254,7 +254,9 @@ try {
254
254
  Түр алдаа — `ApiError.retryable` үнэн, `retryAfterMs`-ийн (CA-ийн `Retry-After`, секунд эсвэл огноо) дараа давтана:
255
255
  prepare 429 `PDF_PREPARE_BUSY`, 503 `LTV_UNAVAILABLE` / `TSA_UNAVAILABLE`; document 503 `DOCUMENT_PENDING`,
256
256
  `TSA_UNAVAILABLE`, `LTV_UNAVAILABLE` (`RETRYABLE_ERROR_CODES`). `headers` — хариуны header. PDF дуудлагын хугацаа
257
- `pdfTimeoutMs` (анхдагч 80000).
257
+ `pdfTimeoutMs` (анхдагч 80000). `pdf.document` санах ойд `pdfMaxBytes` (анхдагч `DEFAULT_PDF_MAX_BYTES` = 64 MiB)
258
+ хүртэл уншина; хэтэрвэл `ResponseTooLargeError`. JSON хариу 4 MiB-ээс хэтэрвэл мөн `ResponseTooLargeError` (таслахгүй).
259
+ `getRepresentations`-ийн `raw` — серверийн түүхий JSON.
258
260
 
259
261
  Олон талт гэрээ: А байгууллагын гаралтыг өөрчлөлтгүй Б-ийн `prepare`-д (`docID`, өөр `onBehalfOf`) илгээнэ.
260
262
  Session-ий `onBehalfOf` блок (`role`/`rightType`) нь хариу угсрах үеийн бүртгэл; `pdf.signer.onBehalfOf`/`claimedRole`
@@ -281,8 +283,16 @@ verifyTokenCertificates(res, csrAuth, csrSign); // гэрчилгээ бүр CSR
281
283
  const ch = await eid.token.authChallenge();
282
284
  const digest = tokenAuthDigest(ch.rpUUID, ch.challenge); // токен CKM_ECDSA → түүхий r||s
283
285
  const who = await eid.token.authVerify(ch.challengeId, authCertDer, rs);
286
+
287
+ // токеноор PDF (PAdES, SIGN эрх) — session/утасгүй; байгууллагын токенд onBehalfOf = тухайн байгууллага
288
+ const p = await eid.pdf.tokenPrepare({ pdf, fileName: "Гэрээ.pdf", signerEtsi, certificate: signCertDer });
289
+ const rs = /* токен (PKCS#11): CKM_ECDSA(p.hashToSign) → түүхий r||s, 64 байт — p.expiresAt хүртэл */ new Uint8Array(64);
290
+ const blk = await eid.pdf.tokenFinalize(p.signingId, rs); // READY; 503 DOCUMENT_PENDING → ижил rs-ийг дахин
291
+ const signed = await eid.pdf.tokenDocument(p.signingId); // SHA-256 = blk.outSha256 тулга
284
292
  ```
285
293
 
294
+ `/token/auth/*`, `/pdf/token/*` нь CA-д байгаа ч `/.well-known/eid`-д хараахан жагсаагдаагүй (гэрээний тест WARN).
295
+
286
296
  ## 4. QR ба ижил төхөөрөмж — device link v3
287
297
 
288
298
  Иргэн өөр төхөөрөмжийн дэлгэц дээрх **QR**-ийг утсаараа уншина, эсвэл RP-ийн сайт/аппыг eID апптай **ижил
@@ -371,7 +381,12 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
371
381
 
372
382
  1. `state === COMPLETE && endResult === OK`
373
383
  2. Иргэний гэрчилгээг **trust anchor (Mongolian National Root CA)** хүртэл chain-аар баталгаажуулна
374
- (Root → National Issuing CA → Gerege Issuing CA → eID Mongolia Issuing CA → иргэн)
384
+ (Root → National Issuing CA → Gerege Issuing CA → eID Mongolia Issuing CA → иргэн) — RFC 5280 замын шалгалт
385
+ (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): issuer нэр + AKI/SKI + гарын үсэг, CA basicConstraints +
386
+ **pathLenConstraint** (04 нь `pathlen:0`), CA-ийн keyUsage keyCertSign, CA бүрийн хугацаа, **EKU** (auth →
387
+ clientAuth, sign → emailProtection — CA-ийн профайл; CA-д EKU байвал түүнд багтах), үл таних **critical
388
+ extension** → татгалзана. **Name constraints**-тэй CA гинжинд байвал fail-closed (CA-ийн бодит гинжинд алга).
389
+ `intermediatesPem`-д 02, 03, 04-ийг бүгдийг өгнө (хариунд гинж ирдэггүй). certificatePolicies-ийг шалгахгүй.
375
390
  3. Гэрчилгээ хүчинтэй хугацаанд + шаардсан `certificateLevel` (default `QUALIFIED`)
376
391
  4. Гарын үсгийг иргэний public key-ээр шалгана:
377
392
  - **auth** → ACSP_V2 payload (`smart-id|ACSP_V2|serverRandom|rpChallenge|userChallenge|B64(rpName)|B64(brokeredRpName)|B64(SHA-256(interactions))|interactionTypeUsed|initialCallbackUrl|flowType`, hash = хүсэлтийн `hashAlgorithm`) дээр — `flowType`, `userChallenge`, `serverRandom` ба алгоритмын downgrade-ийг мөн шалгана;
@@ -404,6 +419,7 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
404
419
  | `SessionFailedError` | endResult ≠ OK (TIMEOUT, USER_REFUSED…) — `.endResult`-оор салга |
405
420
  | `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
406
421
  | `ApiError` | бусад HTTP алдаа (`.status`, `.body`, серверийн `.code`, `.retryAfterMs`, `.headers`, `.retryable` — ж: 404 `ORG_NOT_FOUND`) |
422
+ | `ResponseTooLargeError` | хариу SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — `.op`, `.limit` |
407
423
  | `NetworkError` | timeout/сүлжээ (`.op` — таних тэмдэггүй зам, `.timeout`) |
408
424
  | `EidOidcError` | «eID-ээр нэвтрэх» (OIDC) — `.code`: `access_denied`, `login_required`, `state_mismatch`, `invalid_grant`, `invalid_id_token`, `server_error` … |
409
425
  | `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase` https биш, token/secret/lang хэлбэр, төрөлд хэрэгтэй талбар дутуу) |
@@ -225,6 +225,8 @@ interface Representation {
225
225
  interface RepresentationsResponse {
226
226
  personEtsi: string;
227
227
  representations: Representation[];
228
+ /** Серверийн түүхий JSON (SDK задлаагүй шинэ талбарт). */
229
+ raw?: unknown;
228
230
  }
229
231
  /** Session-ий байгууллагын блок — байгууллага + тухайн иргэний эрх (бүртгэлээс real-time). */
230
232
  interface OrgResult {
@@ -395,6 +397,15 @@ declare class NetworkError extends EidError {
395
397
  timeout?: boolean;
396
398
  });
397
399
  }
400
+ /**
401
+ * Хариуны бие SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — бие таслагдаж задлагдахгүй. `op` — method +
402
+ * таних тэмдэггүй зам.
403
+ */
404
+ declare class ResponseTooLargeError extends EidError {
405
+ readonly op: string;
406
+ readonly limit: number;
407
+ constructor(op: string, limit: number);
408
+ }
398
409
  /**
399
410
  * Session амжилтгүй дууссан (endResult ≠ OK): TIMEOUT, USER_REFUSED*, DOCUMENT_UNUSABLE гм.
400
411
  * endResult-оор салгаж RP UI-д тохирох мессеж харуулна.
@@ -417,4 +428,4 @@ declare class ValidationError extends EidError {
417
428
  declare class DeviceLinkError extends EidError {
418
429
  }
419
430
 
420
- export { type AcspContext as A, isMobileUserAgent as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, openOrShowQR as G, type HashAlgorithm as H, type Interaction as I, parseRetryAfter as J, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, ValidationError as V, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, DeviceLinkError as f, type DeviceLinkInput as g, type EndResult as h, ForbiddenError as i, type HttpErrorInit as j, type NavigatorLike as k, NetworkError as l, type OnBehalfErrorCode as m, type OpenOrShowQROptions as n, type OrgResult as o, type PdfDocumentStatus as p, type PdfSigner as q, RETRYABLE_ERROR_CODES as r, type Representation as s, type RetryableErrorCode as t, SessionFailedError as u, type SessionState as v, type SignatureAlgorithmParameters as w, type SignatureProtocol as x, type TokenCertStatus as y, deviceLink as z };
431
+ export { type AcspContext as A, deviceLink as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, isMobileUserAgent as G, type HashAlgorithm as H, type Interaction as I, openOrShowQR as J, parseRetryAfter as K, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, ValidationError as V, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, DeviceLinkError as f, type DeviceLinkInput as g, type EndResult as h, ForbiddenError as i, type HttpErrorInit as j, type NavigatorLike as k, NetworkError as l, type OnBehalfErrorCode as m, type OpenOrShowQROptions as n, type OrgResult as o, type PdfDocumentStatus as p, type PdfSigner as q, RETRYABLE_ERROR_CODES as r, type Representation as s, ResponseTooLargeError as t, type RetryableErrorCode as u, SessionFailedError as v, type SessionState as w, type SignatureAlgorithmParameters as x, type SignatureProtocol as y, type TokenCertStatus as z };
@@ -225,6 +225,8 @@ interface Representation {
225
225
  interface RepresentationsResponse {
226
226
  personEtsi: string;
227
227
  representations: Representation[];
228
+ /** Серверийн түүхий JSON (SDK задлаагүй шинэ талбарт). */
229
+ raw?: unknown;
228
230
  }
229
231
  /** Session-ий байгууллагын блок — байгууллага + тухайн иргэний эрх (бүртгэлээс real-time). */
230
232
  interface OrgResult {
@@ -395,6 +397,15 @@ declare class NetworkError extends EidError {
395
397
  timeout?: boolean;
396
398
  });
397
399
  }
400
+ /**
401
+ * Хариуны бие SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — бие таслагдаж задлагдахгүй. `op` — method +
402
+ * таних тэмдэггүй зам.
403
+ */
404
+ declare class ResponseTooLargeError extends EidError {
405
+ readonly op: string;
406
+ readonly limit: number;
407
+ constructor(op: string, limit: number);
408
+ }
398
409
  /**
399
410
  * Session амжилтгүй дууссан (endResult ≠ OK): TIMEOUT, USER_REFUSED*, DOCUMENT_UNUSABLE гм.
400
411
  * endResult-оор салгаж RP UI-д тохирох мессеж харуулна.
@@ -417,4 +428,4 @@ declare class ValidationError extends EidError {
417
428
  declare class DeviceLinkError extends EidError {
418
429
  }
419
430
 
420
- export { type AcspContext as A, isMobileUserAgent as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, openOrShowQR as G, type HashAlgorithm as H, type Interaction as I, parseRetryAfter as J, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, ValidationError as V, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, DeviceLinkError as f, type DeviceLinkInput as g, type EndResult as h, ForbiddenError as i, type HttpErrorInit as j, type NavigatorLike as k, NetworkError as l, type OnBehalfErrorCode as m, type OpenOrShowQROptions as n, type OrgResult as o, type PdfDocumentStatus as p, type PdfSigner as q, RETRYABLE_ERROR_CODES as r, type Representation as s, type RetryableErrorCode as t, SessionFailedError as u, type SessionState as v, type SignatureAlgorithmParameters as w, type SignatureProtocol as x, type TokenCertStatus as y, deviceLink as z };
431
+ export { type AcspContext as A, deviceLink as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, isMobileUserAgent as G, type HashAlgorithm as H, type Interaction as I, openOrShowQR as J, parseRetryAfter as K, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, ValidationError as V, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, DeviceLinkError as f, type DeviceLinkInput as g, type EndResult as h, ForbiddenError as i, type HttpErrorInit as j, type NavigatorLike as k, NetworkError as l, type OnBehalfErrorCode as m, type OpenOrShowQROptions as n, type OrgResult as o, type PdfDocumentStatus as p, type PdfSigner as q, RETRYABLE_ERROR_CODES as r, type Representation as s, ResponseTooLargeError as t, type RetryableErrorCode as u, SessionFailedError as v, type SessionState as w, type SignatureAlgorithmParameters as x, type SignatureProtocol as y, type TokenCertStatus as z };
@@ -1 +1 @@
1
- export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions, z as deviceLink, B as isMobileUserAgent, G as openOrShowQR } from './browser-BWCDj8xO.cjs';
1
+ export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR } from './browser-BVR6gtrx.cjs';
package/dist/browser.d.ts CHANGED
@@ -1 +1 @@
1
- export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions, z as deviceLink, B as isMobileUserAgent, G as openOrShowQR } from './browser-BWCDj8xO.js';
1
+ export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR } from './browser-BVR6gtrx.js';
package/dist/browser.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  deviceLink,
5
5
  isMobileUserAgent,
6
6
  openOrShowQR
7
- } from "./chunk-YDVPZMAL.js";
7
+ } from "./chunk-QPJDZLHR.js";
8
8
  export {
9
9
  DeviceLinkError,
10
10
  EidError,
@@ -99,6 +99,15 @@ var NetworkError = class extends EidError {
99
99
  this.timeout = init?.timeout;
100
100
  }
101
101
  };
102
+ var ResponseTooLargeError = class extends EidError {
103
+ constructor(op, limit) {
104
+ super(`${op}: \u0445\u0430\u0440\u0438\u0443 ${limit} \u0431\u0430\u0439\u0442\u0430\u0430\u0441 \u0438\u0445`);
105
+ this.op = op;
106
+ this.limit = limit;
107
+ }
108
+ op;
109
+ limit;
110
+ };
102
111
  function maskPath(path) {
103
112
  return path.split(/[?#]/)[0].replace(/\/(etsi|document)\/[^/?#\s]+/g, "/$1/\u2026");
104
113
  }
@@ -165,6 +174,7 @@ export {
165
174
  parseRetryAfter,
166
175
  ON_BEHALF_ERROR_CODES,
167
176
  NetworkError,
177
+ ResponseTooLargeError,
168
178
  maskPath,
169
179
  SessionFailedError,
170
180
  ValidationError,
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  AuthenticationError: () => AuthenticationError,
26
26
  CITIZEN_ISSUING_CA_SKI: () => CITIZEN_ISSUING_CA_SKI,
27
27
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
28
+ DEFAULT_PDF_MAX_BYTES: () => DEFAULT_PDF_MAX_BYTES,
28
29
  DEFAULT_PDF_TIMEOUT_MS: () => DEFAULT_PDF_TIMEOUT_MS,
29
30
  DeviceLinkError: () => DeviceLinkError,
30
31
  EID_ACR_ADVANCED: () => EID_ACR_ADVANCED,
@@ -42,6 +43,7 @@ __export(index_exports, {
42
43
  PdfApi: () => PdfApi,
43
44
  PersonApi: () => PersonApi,
44
45
  RETRYABLE_ERROR_CODES: () => RETRYABLE_ERROR_CODES,
46
+ ResponseTooLargeError: () => ResponseTooLargeError,
45
47
  ResponseValidator: () => ResponseValidator,
46
48
  SessionApi: () => SessionApi,
47
49
  SessionFailedError: () => SessionFailedError,
@@ -174,6 +176,15 @@ var NetworkError = class extends EidError {
174
176
  this.timeout = init?.timeout;
175
177
  }
176
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;
187
+ };
177
188
  function maskPath(path) {
178
189
  return path.split(/[?#]/)[0].replace(/\/(etsi|document)\/[^/?#\s]+/g, "/$1/\u2026");
179
190
  }
@@ -191,6 +202,7 @@ var DeviceLinkError = class extends EidError {
191
202
 
192
203
  // src/http.ts
193
204
  var DEFAULT_TIMEOUT_MS = 15e3;
205
+ var MAX_JSON_BYTES = 4 << 20;
194
206
  var Http = class {
195
207
  constructor(cfg) {
196
208
  this.cfg = cfg;
@@ -206,14 +218,14 @@ var Http = class {
206
218
  postMultipart(path, form, timeoutMs) {
207
219
  return this.json("POST", path, form, void 0, timeoutMs);
208
220
  }
209
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
210
- async getBytes(path, timeoutMs) {
221
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
222
+ async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
211
223
  const res = await this.send("GET", path, void 0, void 0, timeoutMs);
212
- return new Uint8Array(await res.arrayBuffer());
224
+ return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
213
225
  }
214
226
  async json(method, path, body, contentType, timeoutMs) {
215
227
  const res = await this.send(method, path, body, contentType, timeoutMs);
216
- const text = await res.text();
228
+ const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
217
229
  return text ? JSON.parse(text) : {};
218
230
  }
219
231
  async send(method, path, body, contentType, timeoutMs) {
@@ -258,6 +270,34 @@ var Http = class {
258
270
  throw new ApiError(res.status, text, code, { headers, retryAfterMs });
259
271
  }
260
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
+ }
261
301
  function errorBody(text) {
262
302
  try {
263
303
  const v = JSON.parse(text);
@@ -294,7 +334,10 @@ var RP_ROUTES = {
294
334
  };
295
335
  var RP_ROUTES_UNDISCOVERED = {
296
336
  tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
297
- tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
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" }
298
341
  };
299
342
  function routePath(route, ...args) {
300
343
  let i = 0;
@@ -642,6 +685,7 @@ var SessionApi = class {
642
685
 
643
686
  // src/pdf.ts
644
687
  var import_node_crypto2 = require("crypto");
688
+ var DEFAULT_PDF_MAX_BYTES = 64 << 20;
645
689
  var DEFAULT_PDF_TIMEOUT_MS = 8e4;
646
690
  function rec3(v) {
647
691
  return v && typeof v === "object" ? v : {};
@@ -674,7 +718,7 @@ var PdfApi = class {
674
718
  const form = new FormData();
675
719
  form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
676
720
  form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
677
- const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS));
721
+ const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
678
722
  const receivedAt = Date.now();
679
723
  const vc = rec3(r.vc).value;
680
724
  const dl = rec3(r.deviceLink);
@@ -701,7 +745,75 @@ var PdfApi = class {
701
745
  * дараа дахин татна (`retryable`).
702
746
  */
703
747
  document(sessionId) {
704
- return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS);
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);
705
817
  }
706
818
  /**
707
819
  * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
@@ -735,7 +847,7 @@ var OrganizationApi = class {
735
847
  async getRepresentations(personEtsi) {
736
848
  const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
737
849
  const list = Array.isArray(raw.representations) ? raw.representations : [];
738
- return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
850
+ return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list, raw };
739
851
  }
740
852
  };
741
853
 
@@ -887,6 +999,10 @@ function parseSubject(cert) {
887
999
  return out;
888
1000
  }
889
1001
  function certExtension(der, oid) {
1002
+ return certExtensions(der).find((e) => e.oid === oid)?.value;
1003
+ }
1004
+ function certExtensions(der) {
1005
+ const out = [];
890
1006
  const top = tlv(der, 0, der.length);
891
1007
  const tbs = tlv(der, top.start, top.end);
892
1008
  let f = tlv(der, tbs.start, tbs.end);
@@ -900,16 +1016,20 @@ function certExtension(der, oid) {
900
1016
  const ext = tlv(der, ep, seq.end);
901
1017
  const id = tlv(der, ext.start, ext.end);
902
1018
  let v = tlv(der, id.end, ext.end);
903
- if (v.tag === 1) v = tlv(der, v.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
+ }
904
1024
  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);
1025
+ out.push({ oid: oidOf(der, id), critical, value: der.subarray(v.start, v.end) });
906
1026
  ep = ext.end;
907
1027
  }
908
- return void 0;
1028
+ return out;
909
1029
  }
910
1030
  pos = t.end;
911
1031
  }
912
- return void 0;
1032
+ return out;
913
1033
  }
914
1034
 
915
1035
  // src/token.ts
@@ -1259,7 +1379,7 @@ var ResponseValidator = class {
1259
1379
  );
1260
1380
  }
1261
1381
  } else {
1262
- this.verifyChain(leaf);
1382
+ this.verifyChain(leaf, protocol === "ACSP_V2" ? EKU_CLIENT_AUTH : EKU_EMAIL_PROTECTION);
1263
1383
  }
1264
1384
  this.checkIssuer(leaf);
1265
1385
  return leaf;
@@ -1303,21 +1423,40 @@ var ResponseValidator = class {
1303
1423
  const need = rank[this.requiredLevel] ?? 2;
1304
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}`);
1305
1425
  }
1306
- /** leaf-ээс эхлэн issuer-ийг intermediates/anchors дотроос олж, anchor хүртэл chain зангидна. */
1307
- verifyChain(leaf) {
1308
- let current = leaf;
1309
- const pool = [...this.intermediates, ...this.anchors];
1310
- for (let depth = 0; depth < 8; depth++) {
1311
- for (const anchor of this.anchors) {
1312
- 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;
1313
1453
  }
1314
- const issuer = pool.find(
1315
- (c) => isCa(c) && current.checkIssued(c) && current.verify(c.publicKey)
1316
- );
1317
- if (!issuer) break;
1318
- current = issuer;
1319
- }
1320
- 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
+ );
1321
1460
  }
1322
1461
  /** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
1323
1462
  findIssuer(leaf) {
@@ -1528,6 +1667,79 @@ function authorityKeyId(cert) {
1528
1667
  function isCa(cert) {
1529
1668
  return cert.ca === true;
1530
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
+ }
1531
1743
 
1532
1744
  // src/client.ts
1533
1745
  var DEFAULT_BASE_URL = "https://rp.eidmongolia.mn";
@@ -1569,7 +1781,11 @@ var EidClient = class {
1569
1781
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1570
1782
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1571
1783
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
1572
- this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level, timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS });
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
+ });
1573
1789
  this.organization = new OrganizationApi(http);
1574
1790
  this.person = new PersonApi(http);
1575
1791
  this.token = new TokenApi(http, cfg.credentials);
@@ -1973,6 +2189,7 @@ function callbackParams(input) {
1973
2189
  AuthenticationError,
1974
2190
  CITIZEN_ISSUING_CA_SKI,
1975
2191
  DEFAULT_BASE_URL,
2192
+ DEFAULT_PDF_MAX_BYTES,
1976
2193
  DEFAULT_PDF_TIMEOUT_MS,
1977
2194
  DeviceLinkError,
1978
2195
  EID_ACR_ADVANCED,
@@ -1990,6 +2207,7 @@ function callbackParams(input) {
1990
2207
  PdfApi,
1991
2208
  PersonApi,
1992
2209
  RETRYABLE_ERROR_CODES,
2210
+ ResponseTooLargeError,
1993
2211
  ResponseValidator,
1994
2212
  SessionApi,
1995
2213
  SessionFailedError,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-BWCDj8xO.cjs';
2
- export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as RetryableErrorCode, u as SessionFailedError, v as SessionState, w as SignatureAlgorithmParameters, x as SignatureProtocol, T as TokenCertSessionBlock, y as TokenCertStatus, V as ValidationError, z as deviceLink, B as isMobileUserAgent, G as openOrShowQR, J as parseRetryAfter } from './browser-BWCDj8xO.cjs';
1
+ import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-BVR6gtrx.cjs';
2
+ export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as ResponseTooLargeError, u as RetryableErrorCode, v as SessionFailedError, w as SessionState, x as SignatureAlgorithmParameters, y as SignatureProtocol, T as TokenCertSessionBlock, z as TokenCertStatus, V as ValidationError, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR, K as parseRetryAfter } from './browser-BVR6gtrx.cjs';
3
3
  import { X509Certificate, KeyObject } from 'node:crypto';
4
4
 
5
5
  interface HttpConfig {
@@ -25,8 +25,8 @@ declare class Http {
25
25
  get(path: string, timeoutMs?: number): Promise<unknown>;
26
26
  /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
27
27
  postMultipart(path: string, form: FormData, timeoutMs?: number): Promise<unknown>;
28
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
29
- getBytes(path: string, timeoutMs?: number): Promise<Uint8Array>;
28
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
29
+ getBytes(path: string, timeoutMs?: number, maxBytes?: number): Promise<Uint8Array>;
30
30
  private json;
31
31
  private send;
32
32
  }
@@ -153,7 +153,11 @@ interface PdfDefaults {
153
153
  certificateLevel: CertificateLevel;
154
154
  /** prepare / document дуудлагын хугацаа (мс); анхдагч {@link DEFAULT_PDF_TIMEOUT_MS}. */
155
155
  timeoutMs?: number;
156
+ /** document / tokenDocument-ийн санах ойд унших дээд хэмжээ (байт); анхдагч {@link DEFAULT_PDF_MAX_BYTES}. */
157
+ maxBytes?: number;
156
158
  }
159
+ /** Гарын үсэгтэй PDF татахад санах ойд уншиж болох анхдагч дээд хэмжээ (64 MiB) — `ClientConfig.pdfMaxBytes`. */
160
+ declare const DEFAULT_PDF_MAX_BYTES: number;
157
161
  /**
158
162
  * PAdES prepare / баримт татах нэг дуудлагын анхдагч хугацаа (мс). CA prepare нь ≤ 10 MiB уншиж, өмнөх гарын үсэг +
159
163
  * LTV өгөгдөл шалгаад push эхлүүлдэг; баримт татахад угсралтыг ≤ 30с хүлээдэг (PADES_SIGNING §3.4). 80с нь хоёуланд
@@ -209,6 +213,35 @@ interface PdfPrepareSession {
209
213
  /** SDK хариу хүлээн авсан мөч (ms epoch) — QR-ийн `receivedAt` ({@link buildDeviceLink}). */
210
214
  receivedAt: number;
211
215
  }
216
+ /** USB токены PAdES — POST /pdf/token/prepare-ийн оролт. */
217
+ interface PdfTokenPrepareInput {
218
+ /** Эх PDF (≤ 10 223 616 байт). */
219
+ pdf: Uint8Array;
220
+ /** Баримтын нэр (≤ 120 тэмдэгт, заавал). */
221
+ fileName: string;
222
+ /** Иргэний ETSI (`PNOMN-…`) — токены гэрчилгээний SERIALNUMBER-тэй таарна. */
223
+ signerEtsi: string;
224
+ /** Токены SIGN гэрчилгээ (DER): eID Mongolia Issuing CA-аас, ACTIVE токенд холбогдсон. */
225
+ certificate: Uint8Array;
226
+ /** Олон гарын үсэгтэй баримтын docID (заавал биш, UUID). */
227
+ docID?: string;
228
+ /** Байгууллагын токенд ЗААВАЛ тухайн байгууллага (`NTRMN-…`); хувийн токенд хоосон. Зөрвөл 403 TOKEN_ORG_MISMATCH. */
229
+ onBehalfOf?: string;
230
+ }
231
+ /** POST /pdf/token/prepare-ийн хариу. */
232
+ interface PdfTokenPrepareSession {
233
+ signingId: string;
234
+ docId: string;
235
+ /** Токеноор зурах 32 байт (SHA-256 DTBS): CKM_ECDSA (prehashed) → түүхий r||s (64 байт). */
236
+ hashToSign: Uint8Array;
237
+ hashAlgorithm: string;
238
+ signatureAlgorithm: string;
239
+ /** "RAW_RS". */
240
+ signatureFormat: string;
241
+ /** tokenFinalize-ийн эцсийн хугацаа (prepare-ээс 10 мин), ISO-8601. */
242
+ expiresAt: string;
243
+ raw: unknown;
244
+ }
212
245
  declare class PdfApi {
213
246
  private readonly http;
214
247
  private readonly creds;
@@ -226,6 +259,28 @@ declare class PdfApi {
226
259
  * дараа дахин татна (`retryable`).
227
260
  */
228
261
  document(sessionId: string): Promise<Uint8Array>;
262
+ private get timeout();
263
+ private get maxBytes();
264
+ /**
265
+ * USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
266
+ * (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
267
+ * зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
268
+ * LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
269
+ * TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
270
+ */
271
+ tokenPrepare(input: PdfTokenPrepareInput): Promise<PdfTokenPrepareSession>;
272
+ /**
273
+ * POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
274
+ * Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
275
+ * Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
276
+ * TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
277
+ */
278
+ tokenFinalize(signingId: string, signature: Uint8Array): Promise<PdfSessionBlock>;
279
+ /**
280
+ * GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
281
+ * хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
282
+ */
283
+ tokenDocument(signingId: string): Promise<Uint8Array>;
229
284
  /**
230
285
  * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
231
286
  * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
@@ -696,7 +751,15 @@ declare class ResponseValidator {
696
751
  private checkIssuer;
697
752
  private checkValidity;
698
753
  private checkLevel;
699
- /** leaf-ээс эхлэн issuer-ийг intermediates/anchors дотроос олж, anchor хүртэл chain зангидна. */
754
+ /**
755
+ * RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
756
+ * замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
757
+ * гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
758
+ * завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
759
+ * бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
760
+ * fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
761
+ * хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
762
+ */
700
763
  private verifyChain;
701
764
  /** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
702
765
  private findIssuer;
@@ -734,6 +797,11 @@ interface ClientConfig {
734
797
  timeoutMs?: number;
735
798
  /** PAdES prepare / баримт татах дуудлагын хугацаа (мс). Анхдагч 80000 ({@link DEFAULT_PDF_TIMEOUT_MS}). */
736
799
  pdfTimeoutMs?: number;
800
+ /**
801
+ * `pdf.document` / `pdf.tokenDocument`-ийн санах ойд унших дээд хэмжээ (байт). Анхдагч 64 MiB
802
+ * ({@link DEFAULT_PDF_MAX_BYTES}); хэтэрвэл `ResponseTooLargeError`.
803
+ */
804
+ pdfMaxBytes?: number;
737
805
  /** undici Dispatcher (mTLS client cert). */
738
806
  dispatcher?: unknown;
739
807
  /** fetch орлуулагч (тест). */
@@ -989,4 +1057,4 @@ declare class EidOidcClient {
989
1057
  /** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
990
1058
  declare function pkceChallenge(codeVerifier: string): Promise<string>;
991
1059
 
992
- export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
1060
+ export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_MAX_BYTES, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PdfTokenPrepareInput, type PdfTokenPrepareSession, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-BWCDj8xO.js';
2
- export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as RetryableErrorCode, u as SessionFailedError, v as SessionState, w as SignatureAlgorithmParameters, x as SignatureProtocol, T as TokenCertSessionBlock, y as TokenCertStatus, V as ValidationError, z as deviceLink, B as isMobileUserAgent, G as openOrShowQR, J as parseRetryAfter } from './browser-BWCDj8xO.js';
1
+ import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-BVR6gtrx.js';
2
+ export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as ResponseTooLargeError, u as RetryableErrorCode, v as SessionFailedError, w as SessionState, x as SignatureAlgorithmParameters, y as SignatureProtocol, T as TokenCertSessionBlock, z as TokenCertStatus, V as ValidationError, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR, K as parseRetryAfter } from './browser-BVR6gtrx.js';
3
3
  import { X509Certificate, KeyObject } from 'node:crypto';
4
4
 
5
5
  interface HttpConfig {
@@ -25,8 +25,8 @@ declare class Http {
25
25
  get(path: string, timeoutMs?: number): Promise<unknown>;
26
26
  /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
27
27
  postMultipart(path: string, form: FormData, timeoutMs?: number): Promise<unknown>;
28
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
29
- getBytes(path: string, timeoutMs?: number): Promise<Uint8Array>;
28
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
29
+ getBytes(path: string, timeoutMs?: number, maxBytes?: number): Promise<Uint8Array>;
30
30
  private json;
31
31
  private send;
32
32
  }
@@ -153,7 +153,11 @@ interface PdfDefaults {
153
153
  certificateLevel: CertificateLevel;
154
154
  /** prepare / document дуудлагын хугацаа (мс); анхдагч {@link DEFAULT_PDF_TIMEOUT_MS}. */
155
155
  timeoutMs?: number;
156
+ /** document / tokenDocument-ийн санах ойд унших дээд хэмжээ (байт); анхдагч {@link DEFAULT_PDF_MAX_BYTES}. */
157
+ maxBytes?: number;
156
158
  }
159
+ /** Гарын үсэгтэй PDF татахад санах ойд уншиж болох анхдагч дээд хэмжээ (64 MiB) — `ClientConfig.pdfMaxBytes`. */
160
+ declare const DEFAULT_PDF_MAX_BYTES: number;
157
161
  /**
158
162
  * PAdES prepare / баримт татах нэг дуудлагын анхдагч хугацаа (мс). CA prepare нь ≤ 10 MiB уншиж, өмнөх гарын үсэг +
159
163
  * LTV өгөгдөл шалгаад push эхлүүлдэг; баримт татахад угсралтыг ≤ 30с хүлээдэг (PADES_SIGNING §3.4). 80с нь хоёуланд
@@ -209,6 +213,35 @@ interface PdfPrepareSession {
209
213
  /** SDK хариу хүлээн авсан мөч (ms epoch) — QR-ийн `receivedAt` ({@link buildDeviceLink}). */
210
214
  receivedAt: number;
211
215
  }
216
+ /** USB токены PAdES — POST /pdf/token/prepare-ийн оролт. */
217
+ interface PdfTokenPrepareInput {
218
+ /** Эх PDF (≤ 10 223 616 байт). */
219
+ pdf: Uint8Array;
220
+ /** Баримтын нэр (≤ 120 тэмдэгт, заавал). */
221
+ fileName: string;
222
+ /** Иргэний ETSI (`PNOMN-…`) — токены гэрчилгээний SERIALNUMBER-тэй таарна. */
223
+ signerEtsi: string;
224
+ /** Токены SIGN гэрчилгээ (DER): eID Mongolia Issuing CA-аас, ACTIVE токенд холбогдсон. */
225
+ certificate: Uint8Array;
226
+ /** Олон гарын үсэгтэй баримтын docID (заавал биш, UUID). */
227
+ docID?: string;
228
+ /** Байгууллагын токенд ЗААВАЛ тухайн байгууллага (`NTRMN-…`); хувийн токенд хоосон. Зөрвөл 403 TOKEN_ORG_MISMATCH. */
229
+ onBehalfOf?: string;
230
+ }
231
+ /** POST /pdf/token/prepare-ийн хариу. */
232
+ interface PdfTokenPrepareSession {
233
+ signingId: string;
234
+ docId: string;
235
+ /** Токеноор зурах 32 байт (SHA-256 DTBS): CKM_ECDSA (prehashed) → түүхий r||s (64 байт). */
236
+ hashToSign: Uint8Array;
237
+ hashAlgorithm: string;
238
+ signatureAlgorithm: string;
239
+ /** "RAW_RS". */
240
+ signatureFormat: string;
241
+ /** tokenFinalize-ийн эцсийн хугацаа (prepare-ээс 10 мин), ISO-8601. */
242
+ expiresAt: string;
243
+ raw: unknown;
244
+ }
212
245
  declare class PdfApi {
213
246
  private readonly http;
214
247
  private readonly creds;
@@ -226,6 +259,28 @@ declare class PdfApi {
226
259
  * дараа дахин татна (`retryable`).
227
260
  */
228
261
  document(sessionId: string): Promise<Uint8Array>;
262
+ private get timeout();
263
+ private get maxBytes();
264
+ /**
265
+ * USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
266
+ * (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
267
+ * зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
268
+ * LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
269
+ * TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
270
+ */
271
+ tokenPrepare(input: PdfTokenPrepareInput): Promise<PdfTokenPrepareSession>;
272
+ /**
273
+ * POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
274
+ * Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
275
+ * Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
276
+ * TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
277
+ */
278
+ tokenFinalize(signingId: string, signature: Uint8Array): Promise<PdfSessionBlock>;
279
+ /**
280
+ * GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
281
+ * хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
282
+ */
283
+ tokenDocument(signingId: string): Promise<Uint8Array>;
229
284
  /**
230
285
  * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
231
286
  * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
@@ -696,7 +751,15 @@ declare class ResponseValidator {
696
751
  private checkIssuer;
697
752
  private checkValidity;
698
753
  private checkLevel;
699
- /** leaf-ээс эхлэн issuer-ийг intermediates/anchors дотроос олж, anchor хүртэл chain зангидна. */
754
+ /**
755
+ * RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
756
+ * замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
757
+ * гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
758
+ * завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
759
+ * бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
760
+ * fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
761
+ * хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
762
+ */
700
763
  private verifyChain;
701
764
  /** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
702
765
  private findIssuer;
@@ -734,6 +797,11 @@ interface ClientConfig {
734
797
  timeoutMs?: number;
735
798
  /** PAdES prepare / баримт татах дуудлагын хугацаа (мс). Анхдагч 80000 ({@link DEFAULT_PDF_TIMEOUT_MS}). */
736
799
  pdfTimeoutMs?: number;
800
+ /**
801
+ * `pdf.document` / `pdf.tokenDocument`-ийн санах ойд унших дээд хэмжээ (байт). Анхдагч 64 MiB
802
+ * ({@link DEFAULT_PDF_MAX_BYTES}); хэтэрвэл `ResponseTooLargeError`.
803
+ */
804
+ pdfMaxBytes?: number;
737
805
  /** undici Dispatcher (mTLS client cert). */
738
806
  dispatcher?: unknown;
739
807
  /** fetch орлуулагч (тест). */
@@ -989,4 +1057,4 @@ declare class EidOidcClient {
989
1057
  /** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
990
1058
  declare function pkceChallenge(codeVerifier: string): Promise<string>;
991
1059
 
992
- export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
1060
+ export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_MAX_BYTES, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PdfTokenPrepareInput, type PdfTokenPrepareSession, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  NetworkError,
8
8
  ON_BEHALF_ERROR_CODES,
9
9
  RETRYABLE_ERROR_CODES,
10
+ ResponseTooLargeError,
10
11
  SessionFailedError,
11
12
  ValidationError,
12
13
  deviceLink,
@@ -14,10 +15,11 @@ import {
14
15
  maskPath,
15
16
  openOrShowQR,
16
17
  parseRetryAfter
17
- } from "./chunk-YDVPZMAL.js";
18
+ } from "./chunk-QPJDZLHR.js";
18
19
 
19
20
  // src/http.ts
20
21
  var DEFAULT_TIMEOUT_MS = 15e3;
22
+ var MAX_JSON_BYTES = 4 << 20;
21
23
  var Http = class {
22
24
  constructor(cfg) {
23
25
  this.cfg = cfg;
@@ -33,14 +35,14 @@ var Http = class {
33
35
  postMultipart(path, form, timeoutMs) {
34
36
  return this.json("POST", path, form, void 0, timeoutMs);
35
37
  }
36
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
37
- async getBytes(path, timeoutMs) {
38
+ /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
39
+ async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
38
40
  const res = await this.send("GET", path, void 0, void 0, timeoutMs);
39
- return new Uint8Array(await res.arrayBuffer());
41
+ return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
40
42
  }
41
43
  async json(method, path, body, contentType, timeoutMs) {
42
44
  const res = await this.send(method, path, body, contentType, timeoutMs);
43
- const text = await res.text();
45
+ const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
44
46
  return text ? JSON.parse(text) : {};
45
47
  }
46
48
  async send(method, path, body, contentType, timeoutMs) {
@@ -85,6 +87,34 @@ var Http = class {
85
87
  throw new ApiError(res.status, text, code, { headers, retryAfterMs });
86
88
  }
87
89
  };
90
+ async function readLimited(res, limit, op) {
91
+ const declared = Number(res.headers.get("content-length") ?? NaN);
92
+ if (declared > limit) {
93
+ await res.body?.cancel().catch(() => void 0);
94
+ throw new ResponseTooLargeError(op, limit);
95
+ }
96
+ if (!res.body) return new Uint8Array(0);
97
+ const reader = res.body.getReader();
98
+ const chunks = [];
99
+ let total = 0;
100
+ for (; ; ) {
101
+ const { done, value } = await reader.read();
102
+ if (done) break;
103
+ total += value.byteLength;
104
+ if (total > limit) {
105
+ await reader.cancel().catch(() => void 0);
106
+ throw new ResponseTooLargeError(op, limit);
107
+ }
108
+ chunks.push(value);
109
+ }
110
+ const out = new Uint8Array(total);
111
+ let off = 0;
112
+ for (const c of chunks) {
113
+ out.set(c, off);
114
+ off += c.byteLength;
115
+ }
116
+ return out;
117
+ }
88
118
  function errorBody(text) {
89
119
  try {
90
120
  const v = JSON.parse(text);
@@ -121,7 +151,10 @@ var RP_ROUTES = {
121
151
  };
122
152
  var RP_ROUTES_UNDISCOVERED = {
123
153
  tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
124
- tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
154
+ tokenAuthVerify: { method: "POST", path: "/token/auth/verify" },
155
+ pdfTokenPrepare: { method: "POST", path: "/pdf/token/prepare" },
156
+ pdfTokenFinalize: { method: "POST", path: "/pdf/token/{signingID}/finalize" },
157
+ pdfTokenDocument: { method: "GET", path: "/pdf/token/{signingID}/document" }
125
158
  };
126
159
  function routePath(route, ...args) {
127
160
  let i = 0;
@@ -469,6 +502,7 @@ var SessionApi = class {
469
502
 
470
503
  // src/pdf.ts
471
504
  import { createHash as createHash2 } from "crypto";
505
+ var DEFAULT_PDF_MAX_BYTES = 64 << 20;
472
506
  var DEFAULT_PDF_TIMEOUT_MS = 8e4;
473
507
  function rec3(v) {
474
508
  return v && typeof v === "object" ? v : {};
@@ -501,7 +535,7 @@ var PdfApi = class {
501
535
  const form = new FormData();
502
536
  form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
503
537
  form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
504
- const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS));
538
+ const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
505
539
  const receivedAt = Date.now();
506
540
  const vc = rec3(r.vc).value;
507
541
  const dl = rec3(r.deviceLink);
@@ -528,7 +562,75 @@ var PdfApi = class {
528
562
  * дараа дахин татна (`retryable`).
529
563
  */
530
564
  document(sessionId) {
531
- return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS);
565
+ return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.timeout, this.maxBytes);
566
+ }
567
+ get timeout() {
568
+ return this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS;
569
+ }
570
+ get maxBytes() {
571
+ return this.defaults.maxBytes ?? DEFAULT_PDF_MAX_BYTES;
572
+ }
573
+ /**
574
+ * USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
575
+ * (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
576
+ * зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
577
+ * LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
578
+ * TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
579
+ */
580
+ async tokenPrepare(input) {
581
+ if (!input.pdf?.length || !input.certificate?.length || !input.signerEtsi?.trim() || !input.fileName?.trim()) {
582
+ throw new Error("eid: pdf token: pdf, certificate, signerEtsi, fileName \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
583
+ }
584
+ const request = {
585
+ relyingPartyUUID: this.creds.rpUUID,
586
+ relyingPartyName: this.creds.rpName,
587
+ signer: { etsi: input.signerEtsi.trim() },
588
+ certificate: Buffer.from(input.certificate).toString("base64"),
589
+ fileName: input.fileName
590
+ };
591
+ if (input.docID?.trim()) request.docID = input.docID.trim();
592
+ if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
593
+ const form = new FormData();
594
+ form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
595
+ form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
596
+ const raw = await this.http.postMultipart(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenPrepare), form, this.timeout);
597
+ const r = rec3(raw);
598
+ const hashToSign = new Uint8Array(Buffer.from(String(r.hashToSign ?? ""), "base64"));
599
+ if (!r.signingID || hashToSign.length !== 32) {
600
+ throw new ValidationError("pdf token prepare: signingID / hashToSign (32 \u0431\u0430\u0439\u0442) \u0431\u0443\u0440\u0443\u0443");
601
+ }
602
+ return {
603
+ signingId: String(r.signingID),
604
+ docId: String(r.docID ?? ""),
605
+ hashToSign,
606
+ hashAlgorithm: String(r.hashAlgorithm ?? ""),
607
+ signatureAlgorithm: String(r.signatureAlgorithm ?? ""),
608
+ signatureFormat: String(r.signatureFormat ?? ""),
609
+ expiresAt: String(r.expiresAt ?? ""),
610
+ raw
611
+ };
612
+ }
613
+ /**
614
+ * POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
615
+ * Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
616
+ * Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
617
+ * TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
618
+ */
619
+ async tokenFinalize(signingId, signature) {
620
+ 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)");
621
+ const r = await this.http.post(
622
+ routePath(RP_ROUTES_UNDISCOVERED.pdfTokenFinalize, signingId),
623
+ { signature: Buffer.from(signature).toString("base64") },
624
+ this.timeout
625
+ );
626
+ return r;
627
+ }
628
+ /**
629
+ * GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
630
+ * хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
631
+ */
632
+ tokenDocument(signingId) {
633
+ return this.http.getBytes(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenDocument, signingId), this.timeout, this.maxBytes);
532
634
  }
533
635
  /**
534
636
  * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
@@ -562,7 +664,7 @@ var OrganizationApi = class {
562
664
  async getRepresentations(personEtsi) {
563
665
  const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
564
666
  const list = Array.isArray(raw.representations) ? raw.representations : [];
565
- return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
667
+ return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list, raw };
566
668
  }
567
669
  };
568
670
 
@@ -714,6 +816,10 @@ function parseSubject(cert) {
714
816
  return out;
715
817
  }
716
818
  function certExtension(der, oid) {
819
+ return certExtensions(der).find((e) => e.oid === oid)?.value;
820
+ }
821
+ function certExtensions(der) {
822
+ const out = [];
717
823
  const top = tlv(der, 0, der.length);
718
824
  const tbs = tlv(der, top.start, top.end);
719
825
  let f = tlv(der, tbs.start, tbs.end);
@@ -727,16 +833,20 @@ function certExtension(der, oid) {
727
833
  const ext = tlv(der, ep, seq.end);
728
834
  const id = tlv(der, ext.start, ext.end);
729
835
  let v = tlv(der, id.end, ext.end);
730
- if (v.tag === 1) v = tlv(der, v.end, ext.end);
836
+ let critical = false;
837
+ if (v.tag === 1) {
838
+ critical = v.end > v.start && der[v.start] !== 0;
839
+ v = tlv(der, v.end, ext.end);
840
+ }
731
841
  if (v.tag !== 4) throw new ValidationError("extension: extnValue OCTET STRING \u0431\u0438\u0448");
732
- if (oidOf(der, id) === oid) return der.subarray(v.start, v.end);
842
+ out.push({ oid: oidOf(der, id), critical, value: der.subarray(v.start, v.end) });
733
843
  ep = ext.end;
734
844
  }
735
- return void 0;
845
+ return out;
736
846
  }
737
847
  pos = t.end;
738
848
  }
739
- return void 0;
849
+ return out;
740
850
  }
741
851
 
742
852
  // src/token.ts
@@ -1086,7 +1196,7 @@ var ResponseValidator = class {
1086
1196
  );
1087
1197
  }
1088
1198
  } else {
1089
- this.verifyChain(leaf);
1199
+ this.verifyChain(leaf, protocol === "ACSP_V2" ? EKU_CLIENT_AUTH : EKU_EMAIL_PROTECTION);
1090
1200
  }
1091
1201
  this.checkIssuer(leaf);
1092
1202
  return leaf;
@@ -1130,21 +1240,40 @@ var ResponseValidator = class {
1130
1240
  const need = rank[this.requiredLevel] ?? 2;
1131
1241
  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}`);
1132
1242
  }
1133
- /** leaf-ээс эхлэн issuer-ийг intermediates/anchors дотроос олж, anchor хүртэл chain зангидна. */
1134
- verifyChain(leaf) {
1135
- let current = leaf;
1136
- const pool = [...this.intermediates, ...this.anchors];
1137
- for (let depth = 0; depth < 8; depth++) {
1138
- for (const anchor of this.anchors) {
1139
- if (isCa(anchor) && current.checkIssued(anchor) && current.verify(anchor.publicKey)) return;
1243
+ /**
1244
+ * RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
1245
+ * замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
1246
+ * гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
1247
+ * завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
1248
+ * бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
1249
+ * fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
1250
+ * хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
1251
+ */
1252
+ verifyChain(leaf, usage) {
1253
+ const now = Math.min(Math.max(Date.now(), Date.parse(leaf.validFrom)), Date.parse(leaf.validTo));
1254
+ const leafReason = criticalReason(leaf) ?? ekuReason(leaf, usage);
1255
+ 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}`);
1256
+ let reason;
1257
+ const extend = (cur, cas) => {
1258
+ if (cas.length >= 8) return false;
1259
+ for (const [cand, isAnchor] of [
1260
+ ...this.anchors.map((c) => [c, true]),
1261
+ ...this.intermediates.map((c) => [c, false])
1262
+ ]) {
1263
+ if (cas.includes(cand) || !signedBy(cur, cand)) continue;
1264
+ const bad = caReason(cand, cas.length, now, usage);
1265
+ if (bad) {
1266
+ reason ??= bad;
1267
+ continue;
1268
+ }
1269
+ if (isAnchor || extend(cand, [...cas, cand])) return true;
1140
1270
  }
1141
- const issuer = pool.find(
1142
- (c) => isCa(c) && current.checkIssued(c) && current.verify(c.publicKey)
1143
- );
1144
- if (!issuer) break;
1145
- current = issuer;
1146
- }
1147
- 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");
1271
+ return false;
1272
+ };
1273
+ if (extend(leaf, [])) return;
1274
+ throw new ValidationError(
1275
+ 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"
1276
+ );
1148
1277
  }
1149
1278
  /** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
1150
1279
  findIssuer(leaf) {
@@ -1355,6 +1484,79 @@ function authorityKeyId(cert) {
1355
1484
  function isCa(cert) {
1356
1485
  return cert.ca === true;
1357
1486
  }
1487
+ var EKU_ANY = "2.5.29.37.0";
1488
+ var EKU_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2";
1489
+ var EKU_EMAIL_PROTECTION = "1.3.6.1.5.5.7.3.4";
1490
+ var OID_NAME_CONSTRAINTS = "2.5.29.30";
1491
+ var HANDLED_CRITICAL = /* @__PURE__ */ new Set([
1492
+ "2.5.29.14",
1493
+ // subjectKeyIdentifier
1494
+ "2.5.29.15",
1495
+ // keyUsage
1496
+ "2.5.29.17",
1497
+ // subjectAltName
1498
+ "2.5.29.19",
1499
+ // basicConstraints
1500
+ "2.5.29.30",
1501
+ // nameConstraints (CA-д — caReason fail-closed)
1502
+ "2.5.29.31",
1503
+ // cRLDistributionPoints
1504
+ "2.5.29.32",
1505
+ // certificatePolicies
1506
+ "2.5.29.35",
1507
+ // authorityKeyIdentifier
1508
+ "2.5.29.37",
1509
+ // extKeyUsage
1510
+ "1.3.6.1.5.5.7.1.1"
1511
+ // authorityInfoAccess
1512
+ ]);
1513
+ function signedBy(child, cand) {
1514
+ if (child.issuer !== cand.subject) return false;
1515
+ const aki = authorityKeyId(child);
1516
+ const ski = subjectKeyId(cand);
1517
+ if (aki && ski && aki !== ski) return false;
1518
+ try {
1519
+ return child.verify(cand.publicKey);
1520
+ } catch {
1521
+ return false;
1522
+ }
1523
+ }
1524
+ function criticalReason(cert) {
1525
+ return certExtensions(cert.raw).some((e) => e.critical && !HANDLED_CRITICAL.has(e.oid)) ? "\u04AF\u043B \u0442\u0430\u043D\u0438\u0445 critical extension" : void 0;
1526
+ }
1527
+ function ekuReason(cert, usage) {
1528
+ const eku = extKeyUsage(cert);
1529
+ 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;
1530
+ }
1531
+ function caReason(ca, below, now, usage) {
1532
+ 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";
1533
+ if (certExtension(ca.raw, "2.5.29.15") && !(keyUsageBits(ca) & 4)) return "CA-\u0438\u0439\u043D KeyUsage-\u0434 keyCertSign \u0430\u043B\u0433\u0430";
1534
+ const from = Date.parse(ca.validFrom);
1535
+ const to = Date.parse(ca.validTo);
1536
+ if (Number.isNaN(from) || Number.isNaN(to) || now < from || now > to) {
1537
+ 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";
1538
+ }
1539
+ const pathLen = basicConstraintsPathLen(ca);
1540
+ 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)";
1541
+ 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)";
1542
+ return criticalReason(ca) ?? ekuReason(ca, usage);
1543
+ }
1544
+ function basicConstraintsPathLen(cert) {
1545
+ const v = certExtension(cert.raw, "2.5.29.19");
1546
+ if (!v) return void 0;
1547
+ const seq = tlv(v, 0, v.length);
1548
+ for (let p = seq.start; p < seq.end; ) {
1549
+ const t = tlv(v, p, seq.end);
1550
+ if (t.tag === 2) {
1551
+ if (t.end - t.start > 4 || (v[t.start] & 128) !== 0) throw new ValidationError("basicConstraints: pathLen \u0431\u0443\u0440\u0443\u0443");
1552
+ let n = 0;
1553
+ for (let i = t.start; i < t.end; i++) n = n * 256 + v[i];
1554
+ return n;
1555
+ }
1556
+ p = t.end;
1557
+ }
1558
+ return void 0;
1559
+ }
1358
1560
 
1359
1561
  // src/client.ts
1360
1562
  var DEFAULT_BASE_URL = "https://rp.eidmongolia.mn";
@@ -1396,7 +1598,11 @@ var EidClient = class {
1396
1598
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1397
1599
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1398
1600
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
1399
- this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level, timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS });
1601
+ this.pdf = new PdfApi(http, cfg.credentials, {
1602
+ certificateLevel: level,
1603
+ timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
1604
+ maxBytes: cfg.pdfMaxBytes && cfg.pdfMaxBytes > 0 ? cfg.pdfMaxBytes : DEFAULT_PDF_MAX_BYTES
1605
+ });
1400
1606
  this.organization = new OrganizationApi(http);
1401
1607
  this.person = new PersonApi(http);
1402
1608
  this.token = new TokenApi(http, cfg.credentials);
@@ -1757,6 +1963,7 @@ export {
1757
1963
  AuthenticationError,
1758
1964
  CITIZEN_ISSUING_CA_SKI,
1759
1965
  DEFAULT_BASE_URL,
1966
+ DEFAULT_PDF_MAX_BYTES,
1760
1967
  DEFAULT_PDF_TIMEOUT_MS,
1761
1968
  DeviceLinkError,
1762
1969
  EID_ACR_ADVANCED,
@@ -1774,6 +1981,7 @@ export {
1774
1981
  PdfApi,
1775
1982
  PersonApi,
1776
1983
  RETRYABLE_ERROR_CODES,
1984
+ ResponseTooLargeError,
1777
1985
  ResponseValidator,
1778
1986
  SessionApi,
1779
1987
  SessionFailedError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gerege-systems/eid-mongolia-sdk",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "scripts": {
31
31
  "build": "tsup src/index.ts src/browser.ts --format esm,cjs --dts --clean",
32
- "test": "npm run build && node --test --experimental-strip-types test/sdk.test.ts test/oidc.test.ts test/portal-gaps.test.ts",
32
+ "test": "npm run build && node --test --experimental-strip-types test/sdk.test.ts test/oidc.test.ts test/portal-gaps.test.ts test/chain.test.ts",
33
33
  "test:contract": "npm run build && node --experimental-strip-types test/contract.test.ts",
34
34
  "lint": "tsc --noEmit"
35
35
  },