@gerege-systems/eid-mongolia-sdk 0.14.1 → 0.15.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
@@ -24,9 +24,8 @@ __export(index_exports, {
24
24
  AuthApi: () => AuthApi,
25
25
  AuthenticationError: () => AuthenticationError,
26
26
  CITIZEN_ISSUING_CA_SKI: () => CITIZEN_ISSUING_CA_SKI,
27
+ CertChoiceInvalidError: () => CertChoiceInvalidError,
27
28
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
28
- DEFAULT_PDF_MAX_BYTES: () => DEFAULT_PDF_MAX_BYTES,
29
- DEFAULT_PDF_TIMEOUT_MS: () => DEFAULT_PDF_TIMEOUT_MS,
30
29
  DEFAULT_SIGN_SESSION_TTL_MS: () => DEFAULT_SIGN_SESSION_TTL_MS,
31
30
  DeviceLinkError: () => DeviceLinkError,
32
31
  EidClient: () => EidClient,
@@ -34,11 +33,13 @@ __export(index_exports, {
34
33
  ForbiddenError: () => ForbiddenError,
35
34
  Http: () => Http,
36
35
  NetworkError: () => NetworkError,
36
+ NoSigningCertificateError: () => NoSigningCertificateError,
37
37
  ON_BEHALF_ERROR_CODES: () => ON_BEHALF_ERROR_CODES,
38
38
  OrganizationApi: () => OrganizationApi,
39
- PdfApi: () => PdfApi,
39
+ PDF_SIGNATURE_REGISTER_FIELDS: () => PDF_SIGNATURE_REGISTER_FIELDS,
40
40
  PersonApi: () => PersonApi,
41
41
  RETRYABLE_ERROR_CODES: () => RETRYABLE_ERROR_CODES,
42
+ RequestInvalidError: () => RequestInvalidError,
42
43
  ResponseTooLargeError: () => ResponseTooLargeError,
43
44
  ResponseValidator: () => ResponseValidator,
44
45
  SessionApi: () => SessionApi,
@@ -49,6 +50,7 @@ __export(index_exports, {
49
50
  acspV2Digest: () => acspV2Digest,
50
51
  buildAcspV2Payload: () => buildAcspV2Payload,
51
52
  buildDeviceLink: () => buildDeviceLink,
53
+ buildRegisterBody: () => buildRegisterBody,
52
54
  createEidLogin: () => createEidLogin,
53
55
  deviceLink: () => deviceLink,
54
56
  deviceLinkInput: () => deviceLinkInput,
@@ -57,15 +59,18 @@ __export(index_exports, {
57
59
  isMobileUserAgent: () => isMobileUserAgent,
58
60
  memoryLoginStore: () => memoryLoginStore,
59
61
  openOrShowQR: () => openOrShowQR,
62
+ parseExpiresAt: () => parseExpiresAt,
60
63
  parseRetryAfter: () => parseRetryAfter,
61
64
  parseSessionResult: () => parseSessionResult,
62
65
  parseSubject: () => parseSubject,
63
66
  personMatchesCertificate: () => personMatchesCertificate,
64
67
  qrDeviceLinkTicker: () => qrDeviceLinkTicker,
65
68
  randomChallenge: () => randomChallenge,
69
+ registrationFromCompleted: () => registrationFromCompleted,
66
70
  resolveBaseUrl: () => resolveBaseUrl,
67
71
  sessionExpiry: () => sessionExpiry,
68
72
  sha256Base64: () => sha256Base64,
73
+ startExpiry: () => startExpiry,
69
74
  tokenAuthDigest: () => tokenAuthDigest,
70
75
  tokenLimitOf: () => tokenLimitOf,
71
76
  userChallengeOf: () => userChallengeOf,
@@ -138,6 +143,12 @@ var ApiError = class extends EidError {
138
143
  return (this.status === 429 || this.status === 503) && (this.retryAfterMs ?? 0) > 0;
139
144
  }
140
145
  };
146
+ var NoSigningCertificateError = class extends ApiError {
147
+ };
148
+ var CertChoiceInvalidError = class extends ApiError {
149
+ };
150
+ var RequestInvalidError = class extends ApiError {
151
+ };
141
152
  var RETRYABLE_ERROR_CODES = [
142
153
  "PDF_PREPARE_BUSY",
143
154
  "DOCUMENT_PENDING",
@@ -215,15 +226,6 @@ var Http = class {
215
226
  get(path, timeoutMs) {
216
227
  return this.json("GET", path, void 0, void 0, timeoutMs);
217
228
  }
218
- /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
219
- postMultipart(path, form, timeoutMs) {
220
- return this.json("POST", path, form, void 0, timeoutMs);
221
- }
222
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
223
- async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
224
- const res = await this.send("GET", path, void 0, void 0, timeoutMs);
225
- return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
226
- }
227
229
  async json(method, path, body, contentType, timeoutMs) {
228
230
  const res = await this.send(method, path, body, contentType, timeoutMs);
229
231
  const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
@@ -269,7 +271,8 @@ var Http = class {
269
271
  { headers, retryAfterMs }
270
272
  );
271
273
  }
272
- throw new ApiError(res.status, text, code, { headers, retryAfterMs });
274
+ const Typed = code === "NO_SIGNING_CERTIFICATE" ? NoSigningCertificateError : code === "CERT_CHOICE_INVALID" ? CertChoiceInvalidError : code === "REQUEST_INVALID" ? RequestInvalidError : ApiError;
275
+ throw new Typed(res.status, text, code, { headers, retryAfterMs });
273
276
  }
274
277
  };
275
278
  async function readLimited(res, limit, op) {
@@ -327,8 +330,6 @@ var RP_ROUTES = {
327
330
  signDeviceLinkDocument: { method: "POST", path: "/signature/device-link/document/{documentNumber}" },
328
331
  certChoiceDeviceLink: { method: "POST", path: "/signature/certificate-choice/device-link/anonymous" },
329
332
  certChoiceEtsi: { method: "POST", path: "/signature/certificate-choice/notification/etsi/{semanticsIdentifier}" },
330
- pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
331
- pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
332
333
  orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
333
334
  session: { method: "GET", path: "/session/{sessionId}" },
334
335
  personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
@@ -337,15 +338,29 @@ var RP_ROUTES = {
337
338
  personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
338
339
  personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
339
340
  tokenCertificate: { method: "POST", path: "/token/certificate" },
340
- tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
341
+ tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" },
342
+ pdfSignatures: { method: "POST", path: "/pdf/signatures" }
341
343
  };
342
344
  var RP_ROUTES_UNDISCOVERED = {
343
345
  tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
344
- tokenAuthVerify: { method: "POST", path: "/token/auth/verify" },
345
- pdfTokenPrepare: { method: "POST", path: "/pdf/token/prepare" },
346
- pdfTokenFinalize: { method: "POST", path: "/pdf/token/{signingID}/finalize" },
347
- pdfTokenDocument: { method: "GET", path: "/pdf/token/{signingID}/document" }
346
+ tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
348
347
  };
348
+ var PDF_SIGNATURE_REGISTER_FIELDS = [
349
+ "docID",
350
+ "revIndex",
351
+ "medium",
352
+ "sessionID",
353
+ "cms",
354
+ "fileName",
355
+ "inSha256",
356
+ "pageSha256",
357
+ "sigRevSha256",
358
+ "outSha256",
359
+ "priorCmsSha256",
360
+ "priorRevisionSha256",
361
+ "onBehalfOf",
362
+ "signerEtsi"
363
+ ];
349
364
  function routePath(route, ...args) {
350
365
  let i = 0;
351
366
  const out = route.path.replace(/\{[^}]*\}/g, () => {
@@ -395,6 +410,21 @@ function acspV2Digest(p, hashAlgorithm) {
395
410
  return (0, import_node_crypto.createHash)(nodeHash(hashAlgorithm)).update(buildAcspV2Payload(p)).digest();
396
411
  }
397
412
 
413
+ // src/types.ts
414
+ var DEFAULT_SIGN_SESSION_TTL_MS = 6e5;
415
+ function sessionExpiry(sentAtMs, ttlMs) {
416
+ return sentAtMs + (ttlMs && ttlMs > 0 ? ttlMs : DEFAULT_SIGN_SESSION_TTL_MS);
417
+ }
418
+ function parseExpiresAt(v) {
419
+ if (typeof v !== "string" || !/^\d{4}-\d{2}-\d{2}T/.test(v)) return void 0;
420
+ const t = Date.parse(v);
421
+ return Number.isFinite(t) ? t : void 0;
422
+ }
423
+ function startExpiry(raw, sentAtMs, ttlMs) {
424
+ const r = raw && typeof raw === "object" ? raw : {};
425
+ return parseExpiresAt(r.expiresAt) ?? sessionExpiry(sentAtMs, ttlMs);
426
+ }
427
+
398
428
  // src/auth.ts
399
429
  function rec(v) {
400
430
  return v && typeof v === "object" ? v : {};
@@ -440,11 +470,19 @@ var AuthApi = class {
440
470
  parseNotification(raw, acsp) {
441
471
  const r = rec(raw);
442
472
  const vc = rec(r.vc).value;
443
- return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, rpChallenge: acsp.rpChallenge, acsp };
473
+ const expiresAt = parseExpiresAt(r.expiresAt);
474
+ return {
475
+ sessionId: String(r.sessionID),
476
+ vc: vc != null ? String(vc) : null,
477
+ rpChallenge: acsp.rpChallenge,
478
+ acsp,
479
+ ...expiresAt !== void 0 ? { expiresAt } : {}
480
+ };
444
481
  }
445
482
  parseDeviceLink(raw, acsp) {
446
483
  const r = rec(raw);
447
484
  const vc = typeof r.vc === "object" && r.vc !== null ? rec(r.vc).value : r.vc;
485
+ const expiresAt = parseExpiresAt(r.expiresAt);
448
486
  return {
449
487
  sessionId: String(r.sessionID),
450
488
  sessionToken: r.sessionToken != null ? String(r.sessionToken) : null,
@@ -454,7 +492,8 @@ var AuthApi = class {
454
492
  rpChallenge: acsp.rpChallenge,
455
493
  acsp,
456
494
  receivedAt: Date.now(),
457
- sessionType: "auth"
495
+ sessionType: "auth",
496
+ ...expiresAt !== void 0 ? { expiresAt } : {}
458
497
  };
459
498
  }
460
499
  /** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
@@ -483,13 +522,85 @@ var AuthApi = class {
483
522
  }
484
523
  };
485
524
 
486
- // src/types.ts
487
- var DEFAULT_SIGN_SESSION_TTL_MS = 6e5;
488
- function sessionExpiry(sentAtMs, ttlMs) {
489
- return sentAtMs + (ttlMs && ttlMs > 0 ? ttlMs : DEFAULT_SIGN_SESSION_TTL_MS);
490
- }
491
-
492
525
  // src/sign.ts
526
+ function registrationFromCompleted(done, extra) {
527
+ const out = {
528
+ docID: done.docId,
529
+ revIndex: done.revIndex,
530
+ cms: done.cms,
531
+ fileName: extra.fileName,
532
+ inSha256: done.inSha256,
533
+ sigRevSha256: done.sigRevSha256,
534
+ outSha256: done.outSha256
535
+ };
536
+ if (extra.medium) out.medium = extra.medium;
537
+ if (extra.sessionID) out.sessionID = extra.sessionID;
538
+ if (done.hasPage && done.pageSha256) out.pageSha256 = done.pageSha256;
539
+ if (done.priorCmsSha256.length) out.priorCmsSha256 = done.priorCmsSha256;
540
+ if (extra.priorRevisionSha256?.length) out.priorRevisionSha256 = extra.priorRevisionSha256;
541
+ if (extra.onBehalfOf) out.onBehalfOf = extra.onBehalfOf;
542
+ if (extra.signerEtsi) out.signerEtsi = extra.signerEtsi;
543
+ return out;
544
+ }
545
+ var HEX64 = /^[0-9a-f]{64}$/;
546
+ var MAX_CMS_BYTES = 28672;
547
+ function buildRegisterBody(input) {
548
+ const fail2 = (m) => {
549
+ throw new ValidationError(`registerSignature: ${m}`);
550
+ };
551
+ if (!UUID_RE.test(input.docID ?? "")) fail2("docID UUID \u0431\u0438\u0448");
552
+ if (!Number.isInteger(input.revIndex) || input.revIndex < 1 || input.revIndex > 16) fail2("revIndex 1\u201316");
553
+ const hex = (name, v) => {
554
+ const x = v?.trim().toLowerCase() ?? "";
555
+ if (!HEX64.test(x)) fail2(`${name} SHA-256 hex (64) \u0431\u0438\u0448`);
556
+ return x;
557
+ };
558
+ let cmsB64;
559
+ if (typeof input.cms === "string") {
560
+ const raw = Buffer.from(input.cms, "base64");
561
+ if (raw.length === 0 || raw.toString("base64") !== input.cms) fail2("cms \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442 base64 \u0431\u0438\u0448");
562
+ cmsB64 = input.cms;
563
+ if (raw.length > MAX_CMS_BYTES) fail2(`cms > ${MAX_CMS_BYTES} \u0431\u0430\u0439\u0442`);
564
+ } else {
565
+ if (!input.cms?.length) fail2("cms \u0445\u043E\u043E\u0441\u043E\u043D");
566
+ if (input.cms.length > MAX_CMS_BYTES) fail2(`cms > ${MAX_CMS_BYTES} \u0431\u0430\u0439\u0442`);
567
+ cmsB64 = Buffer.from(input.cms).toString("base64");
568
+ }
569
+ const fileName = input.fileName?.trim() ?? "";
570
+ if (!fileName || Array.from(fileName).length > PDF_MAX_FILE_NAME) fail2(`fileName \u0437\u0430\u0430\u0432\u0430\u043B, \u2264 ${PDF_MAX_FILE_NAME} \u0442\u044D\u043C\u0434\u044D\u0433\u0442`);
571
+ const sessionID = input.sessionID?.trim() ?? "";
572
+ const medium = input.medium ?? (sessionID ? "phone" : "token");
573
+ if (medium !== "phone" && medium !== "token") fail2("medium: phone | token");
574
+ if (medium === "phone" && !sessionID) fail2("phone-\u0434 sessionID \u0437\u0430\u0430\u0432\u0430\u043B");
575
+ const priorCms = (input.priorCmsSha256 ?? []).map((h, i) => hex(`priorCmsSha256[${i}]`, h));
576
+ const priorRev = (input.priorRevisionSha256 ?? []).map((h, i) => hex(`priorRevisionSha256[${i}]`, h));
577
+ if (priorCms.length + priorRev.length !== input.revIndex - 1) fail2("priorCmsSha256 + priorRevisionSha256-\u0438\u0439\u043D \u0442\u043E\u043E = revIndex \u2212 1");
578
+ if (input.revIndex === 1 && !input.pageSha256) fail2("revIndex 1-\u0434 pageSha256 \u0437\u0430\u0430\u0432\u0430\u043B");
579
+ const body = { docID: input.docID, revIndex: input.revIndex, medium };
580
+ if (sessionID) body.sessionID = sessionID;
581
+ body.cms = cmsB64;
582
+ body.fileName = fileName;
583
+ body.inSha256 = hex("inSha256", input.inSha256);
584
+ if (input.pageSha256) body.pageSha256 = hex("pageSha256", input.pageSha256);
585
+ body.sigRevSha256 = hex("sigRevSha256", input.sigRevSha256);
586
+ body.outSha256 = hex("outSha256", input.outSha256);
587
+ if (priorCms.length) body.priorCmsSha256 = priorCms;
588
+ if (priorRev.length) body.priorRevisionSha256 = priorRev;
589
+ if (input.onBehalfOf?.trim()) body.onBehalfOf = input.onBehalfOf.trim();
590
+ if (input.signerEtsi?.trim()) body.signerEtsi = input.signerEtsi.trim();
591
+ for (const k of Object.keys(body)) if (!PDF_SIGNATURE_REGISTER_FIELDS.includes(k)) fail2(`\u0434\u043E\u0442\u043E\u043E\u0434: \u04AF\u043B \u043C\u044D\u0434\u044D\u0433\u0434\u044D\u0445 \u0442\u0430\u043B\u0431\u0430\u0440 ${k}`);
592
+ return body;
593
+ }
594
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
595
+ var PDF_DISPLAY_TEXT = "\u0411\u0430\u0440\u0438\u043C\u0442\u0430\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0445: ";
596
+ var PDF_MAX_FILE_NAME = 120;
597
+ function dtbsBase64(v) {
598
+ const raw = typeof v === "string" ? Buffer.from(v, "base64") : Buffer.from(v);
599
+ if (raw.length !== 32 || typeof v === "string" && raw.toString("base64") !== v) {
600
+ throw new ValidationError("pdfDigest: dtbs \u043D\u044C 32 \u0431\u0430\u0439\u0442 (SHA-256) \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
601
+ }
602
+ return raw.toString("base64");
603
+ }
493
604
  function rec2(v) {
494
605
  return v && typeof v === "object" ? v : {};
495
606
  }
@@ -540,7 +651,7 @@ var SignApi = class {
540
651
  vc: vcOf(r),
541
652
  rpChallenge: "",
542
653
  ...sign ? { sign } : {},
543
- expiresAt: sessionExpiry(sent, this.defaults.sessionTtlMs)
654
+ expiresAt: startExpiry(r, sent, this.defaults.sessionTtlMs)
544
655
  };
545
656
  }
546
657
  async deviceLink(path, body, extra) {
@@ -555,7 +666,7 @@ var SignApi = class {
555
666
  rpChallenge: "",
556
667
  receivedAt: Date.now(),
557
668
  ...extra,
558
- expiresAt: sessionExpiry(sent, this.defaults.sessionTtlMs)
669
+ expiresAt: startExpiry(r, sent, this.defaults.sessionTtlMs)
559
670
  };
560
671
  }
561
672
  /**
@@ -574,6 +685,77 @@ var SignApi = class {
574
685
  const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
575
686
  return this.notification(routePath(RP_ROUTES.signNotificationDocument, documentNumber), body, sign);
576
687
  }
688
+ /**
689
+ * Клиент талын PAdES (`@gerege-systems/eid-mongolia-sdk/pades`): `prepare()`-ийн DTBS-д иргэнээр гарын үсэг зуруулна.
690
+ * PDF CA руу ОЧИХГҮЙ — зөвхөн 32 байт. CA-ийн өөрийн PAdES session-тэй ижил: `RAW_DIGEST_SIGNATURE`,
691
+ * `sha256WithRSAEncryption` (CMS-д PKCS#1 v1.5; legacy ECDSA иргэнд сервер `ecdsa-with-SHA256`), `displayTextAndPIN`
692
+ * «Баримтад гарын үсэг зурах: <fileName>» (60 тэмдэгтэд тайрна).
693
+ *
694
+ * Дараа нь `session.waitForResult(sessionId)` → `validator.validateSign(result, s.digest)` → `signature.value`-г
695
+ * (base64 → байт) `complete(state, signature)`-д.
696
+ */
697
+ async pdfDigest(input) {
698
+ const digest = dtbsBase64(input.dtbs);
699
+ const fileName = input.fileName?.trim() ?? "";
700
+ if (!fileName || Array.from(fileName).length > PDF_MAX_FILE_NAME) {
701
+ throw new ValidationError(`pdfDigest: fileName \u0437\u0430\u0430\u0432\u0430\u043B, \u2264 ${PDF_MAX_FILE_NAME} \u0442\u044D\u043C\u0434\u044D\u0433\u0442`);
702
+ }
703
+ const signer = input.signer;
704
+ const etsi = signer?.etsi?.trim();
705
+ const doc = signer?.documentNumber?.trim();
706
+ if (!etsi === !doc) throw new ValidationError("pdfDigest: signer \u2014 etsi \u044D\u0441\u0432\u044D\u043B documentNumber-\u0438\u0439\u043D \u044F\u0433 \u043D\u044D\u0433 \u043D\u044C");
707
+ const deviceLink2 = input.flow === "device-link";
708
+ const display = Array.from(PDF_DISPLAY_TEXT + fileName).slice(0, 60).join("");
709
+ const interactions = interactionsToBase64([{ type: "displayTextAndPIN", displayText60: display }]);
710
+ const body = {
711
+ relyingPartyUUID: this.creds.rpUUID,
712
+ relyingPartyName: this.creds.rpName,
713
+ certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
714
+ signatureProtocol: "RAW_DIGEST_SIGNATURE",
715
+ signatureProtocolParameters: {
716
+ digest,
717
+ signatureAlgorithm: "sha256WithRSAEncryption",
718
+ signatureAlgorithmParameters: { hashAlgorithm: "SHA-256" }
719
+ },
720
+ interactions,
721
+ fileName
722
+ };
723
+ if (input.initialCallbackUrl?.trim()) body.initialCallbackUrl = input.initialCallbackUrl.trim();
724
+ if (input.onBehalfOf?.trim()) body.onBehalfOf = input.onBehalfOf.trim();
725
+ const route = deviceLink2 ? etsi ? RP_ROUTES.signDeviceLinkEtsi : RP_ROUTES.signDeviceLinkDocument : etsi ? RP_ROUTES.signNotificationEtsi : RP_ROUTES.signNotificationDocument;
726
+ const r = rec2(await this.http.post(routePath(route, etsi ?? doc), body));
727
+ const receivedAt = Date.now();
728
+ const vc = typeof r.vc === "object" && r.vc !== null ? rec2(r.vc).value : r.vc;
729
+ let dl = null;
730
+ if (deviceLink2) {
731
+ if (!r.sessionToken || !r.sessionSecret || !r.deviceLinkBase) {
732
+ throw new ValidationError("pdfDigest: device-link \u0445\u0430\u0440\u0438\u0443\u043D\u0434 sessionToken/sessionSecret/deviceLinkBase \u0430\u043B\u0433\u0430");
733
+ }
734
+ dl = { sessionToken: String(r.sessionToken), sessionSecret: String(r.sessionSecret), deviceLinkBase: String(r.deviceLinkBase) };
735
+ }
736
+ return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, digest, interactions, deviceLink: dl, receivedAt };
737
+ }
738
+ /**
739
+ * `POST /pdf/signatures` — `pades.complete()`-ийн дараа CMS-ийг CA-д бүртгэнэ (`/verify/{docID}`, иргэний түүх). PDF
740
+ * илгээгдэхгүй. SIGN эрх. CA CMS-ийг шалгана: 04-ийн SIGN cert, session digest = SHA-256(signedAttrs), claimedRole ↔
741
+ * төлөөлөл. Алдаа: 403 `REPRESENTATION_REVOKED`, 409 `CLAIMED_ROLE_MISMATCH` / `REGISTRY_CONFLICT` /
742
+ * `DOC_ID_CONFLICT` / `PDF_FOREIGN_SIGNATURE` (`ApiError.code`). Бүртгэлгүй файл ч Adobe/EU DSS-д хүчинтэй — зөвхөн
743
+ * `/verify` «бүртгэлгүй» гэнэ.
744
+ */
745
+ async registerSignature(input) {
746
+ return this.registerBody(buildRegisterBody(input));
747
+ }
748
+ async registerBody(body) {
749
+ const raw = await this.http.post(routePath(RP_ROUTES.pdfSignatures), body);
750
+ const r = rec2(raw);
751
+ return {
752
+ docID: String(r.docID ?? body.docID),
753
+ revIndex: Number(r.revIndex ?? 0),
754
+ registeredAt: typeof r.registeredAt === "string" ? r.registeredAt : "",
755
+ verifyUrl: String(r.verifyUrl ?? ""),
756
+ raw
757
+ };
758
+ }
577
759
  /** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
578
760
  async textByEtsi(id, text, interactions, opts) {
579
761
  return this.digestByEtsi(id, sha256Base64(text), interactions, opts);
@@ -689,7 +871,6 @@ function parseSessionResult(raw) {
689
871
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
690
872
  interactionTypeUsed: str(r.interactionTypeUsed),
691
873
  onBehalfOf: parseOrg(r.onBehalfOf),
692
- pdf: parsePdf(r.pdf),
693
874
  person: parsePerson(r.person),
694
875
  tokenCertificate: parseTokenCert(r.tokenCertificate),
695
876
  raw
@@ -727,28 +908,6 @@ function personMatchesCertificate(person, subject) {
727
908
  const serial = typeof subject === "string" ? subject : subject?.serialNumber;
728
909
  return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
729
910
  }
730
- function parsePdf(raw) {
731
- const p = asRecord(raw);
732
- const docID = str(p.docID);
733
- if (!docID) return null;
734
- const out = { docID, documentStatus: String(p.documentStatus ?? "") };
735
- if (p.errorCode != null) out.errorCode = String(p.errorCode);
736
- if (p.outSha256 != null) out.outSha256 = String(p.outSha256);
737
- if (p.size != null) out.size = Number(p.size);
738
- if (p.expiresAt != null) out.expiresAt = String(p.expiresAt);
739
- if (p.signatureLevel != null) out.signatureLevel = String(p.signatureLevel);
740
- const sg = asRecord(p.signer);
741
- if (sg.etsi != null) {
742
- out.signer = { etsi: String(sg.etsi), certSerial: String(sg.certSerial ?? "") };
743
- if (sg.onBehalfOf) out.signer.onBehalfOf = String(sg.onBehalfOf);
744
- if (sg.claimedRole) out.signer.claimedRole = String(sg.claimedRole);
745
- }
746
- const v = asRecord(p.validation);
747
- if (v.indication != null) {
748
- out.validation = { indication: String(v.indication), ltv: v.ltv === true, signatures: Number(v.signatures ?? 0) };
749
- }
750
- return out;
751
- }
752
911
  function parseSigParams(raw) {
753
912
  const p = asRecord(raw);
754
913
  const hash = str(p.hashAlgorithm);
@@ -807,160 +966,6 @@ var SessionApi = class {
807
966
  }
808
967
  };
809
968
 
810
- // src/pdf.ts
811
- var import_node_crypto2 = require("crypto");
812
- var DEFAULT_PDF_MAX_BYTES = 64 << 20;
813
- var DEFAULT_PDF_TIMEOUT_MS = 8e4;
814
- function rec3(v) {
815
- return v && typeof v === "object" ? v : {};
816
- }
817
- var PdfApi = class {
818
- constructor(http, creds, defaults) {
819
- this.http = http;
820
- this.creds = creds;
821
- this.defaults = defaults;
822
- }
823
- http;
824
- creds;
825
- defaults;
826
- /**
827
- * POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. Түр алдаа: `ApiError` 429
828
- * PDF_PREPARE_BUSY, 503 LTV_UNAVAILABLE / TSA_UNAVAILABLE — `retryAfterMs`-ийн дараа дахин (`retryable`).
829
- */
830
- async prepare(input) {
831
- const request = {
832
- relyingPartyUUID: this.creds.rpUUID,
833
- relyingPartyName: this.creds.rpName,
834
- certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
835
- signer: input.signer,
836
- flow: input.flow ?? "notification",
837
- fileName: input.fileName
838
- };
839
- if (input.initialCallbackUrl?.trim()) request.initialCallbackUrl = input.initialCallbackUrl.trim();
840
- if (input.docID?.trim()) request.docID = input.docID.trim();
841
- if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
842
- const form = new FormData();
843
- form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
844
- form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
845
- const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
846
- const receivedAt = Date.now();
847
- const vc = rec3(r.vc).value;
848
- const dl = rec3(r.deviceLink);
849
- const digest = typeof r.digest === "string" && r.digest ? r.digest : typeof dl.digest === "string" && dl.digest ? dl.digest : void 0;
850
- return {
851
- sessionId: String(r.sessionID),
852
- docId: String(r.docID),
853
- vc: vc != null ? String(vc) : null,
854
- ...digest !== void 0 ? { digest } : {},
855
- deviceLink: dl.sessionToken ? {
856
- sessionToken: String(dl.sessionToken),
857
- sessionSecret: String(dl.sessionSecret),
858
- deviceLinkBase: String(dl.deviceLinkBase),
859
- digest: String(dl.digest),
860
- interactions: String(dl.interactions)
861
- } : null,
862
- receivedAt
863
- };
864
- }
865
- /**
866
- * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
867
- * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`. Баримт
868
- * бэлэн болоогүй бол `ApiError` 503 `.code` DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE — `retryAfterMs`-ийн
869
- * дараа дахин татна (`retryable`).
870
- */
871
- document(sessionId) {
872
- return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.timeout, this.maxBytes);
873
- }
874
- get timeout() {
875
- return this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS;
876
- }
877
- get maxBytes() {
878
- return this.defaults.maxBytes ?? DEFAULT_PDF_MAX_BYTES;
879
- }
880
- /**
881
- * USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
882
- * (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
883
- * зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
884
- * LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
885
- * TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
886
- */
887
- async tokenPrepare(input) {
888
- if (!input.pdf?.length || !input.certificate?.length || !input.signerEtsi?.trim() || !input.fileName?.trim()) {
889
- throw new Error("eid: pdf token: pdf, certificate, signerEtsi, fileName \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
890
- }
891
- const request = {
892
- relyingPartyUUID: this.creds.rpUUID,
893
- relyingPartyName: this.creds.rpName,
894
- signer: { etsi: input.signerEtsi.trim() },
895
- certificate: Buffer.from(input.certificate).toString("base64"),
896
- fileName: input.fileName
897
- };
898
- if (input.docID?.trim()) request.docID = input.docID.trim();
899
- if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
900
- const form = new FormData();
901
- form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
902
- form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
903
- const raw = await this.http.postMultipart(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenPrepare), form, this.timeout);
904
- const r = rec3(raw);
905
- const hashToSign = new Uint8Array(Buffer.from(String(r.hashToSign ?? ""), "base64"));
906
- if (!r.signingID || hashToSign.length !== 32) {
907
- throw new ValidationError("pdf token prepare: signingID / hashToSign (32 \u0431\u0430\u0439\u0442) \u0431\u0443\u0440\u0443\u0443");
908
- }
909
- return {
910
- signingId: String(r.signingID),
911
- docId: String(r.docID ?? ""),
912
- hashToSign,
913
- hashAlgorithm: String(r.hashAlgorithm ?? ""),
914
- signatureAlgorithm: String(r.signatureAlgorithm ?? ""),
915
- signatureFormat: String(r.signatureFormat ?? ""),
916
- expiresAt: String(r.expiresAt ?? ""),
917
- raw
918
- };
919
- }
920
- /**
921
- * POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
922
- * Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
923
- * Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
924
- * TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
925
- */
926
- async tokenFinalize(signingId, signature) {
927
- 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)");
928
- const r = await this.http.post(
929
- routePath(RP_ROUTES_UNDISCOVERED.pdfTokenFinalize, signingId),
930
- { signature: Buffer.from(signature).toString("base64") },
931
- this.timeout
932
- );
933
- return r;
934
- }
935
- /**
936
- * GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
937
- * хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
938
- */
939
- tokenDocument(signingId) {
940
- return this.http.getBytes(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenDocument, signingId), this.timeout, this.maxBytes);
941
- }
942
- /**
943
- * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
944
- * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
945
- *
946
- * @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
947
- */
948
- async documentVerified(sessionId, expected) {
949
- let want;
950
- if (typeof expected === "string") {
951
- want = expected;
952
- } else {
953
- if (expected.documentStatus !== "READY") throw new ValidationError(`pdf.documentStatus ${expected.documentStatus} \u2260 READY`);
954
- want = expected.outSha256;
955
- }
956
- if (!want || !/^[0-9a-fA-F]{64}$/.test(want)) throw new ValidationError("pdf.outSha256 \u0430\u043B\u0433\u0430 \u044D\u0441\u0432\u044D\u043B SHA-256 hex \u0431\u0438\u0448");
957
- const bytes = await this.document(sessionId);
958
- const got = (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
959
- if (got !== want.toLowerCase()) throw new ValidationError("\u0422\u0430\u0442\u0441\u0430\u043D PDF-\u0438\u0439\u043D SHA-256 \u043D\u044C pdf.outSha256-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
960
- return bytes;
961
- }
962
- };
963
-
964
969
  // src/organization.ts
965
970
  var OrganizationApi = class {
966
971
  constructor(http) {
@@ -976,7 +981,7 @@ var OrganizationApi = class {
976
981
  };
977
982
 
978
983
  // src/person.ts
979
- function rec4(v) {
984
+ function rec3(v) {
980
985
  return v && typeof v === "object" ? v : {};
981
986
  }
982
987
  var PersonApi = class {
@@ -986,7 +991,7 @@ var PersonApi = class {
986
991
  http;
987
992
  async get(path) {
988
993
  const raw = await this.http.get(path);
989
- return { ...rec4(raw), raw };
994
+ return { ...rec3(raw), raw };
990
995
  }
991
996
  /** Иргэний бүх гэрчилгээ + статусын тоолол. */
992
997
  certificates(personEtsi) {
@@ -1016,10 +1021,10 @@ var PersonApi = class {
1016
1021
  };
1017
1022
 
1018
1023
  // src/token.ts
1019
- var import_node_crypto4 = require("crypto");
1024
+ var import_node_crypto3 = require("crypto");
1020
1025
 
1021
1026
  // src/subject.ts
1022
- var import_node_crypto3 = require("crypto");
1027
+ var import_node_crypto2 = require("crypto");
1023
1028
  var OIDS = {
1024
1029
  "2.5.4.42": "givenName",
1025
1030
  "2.5.4.4": "surname",
@@ -1084,7 +1089,7 @@ function stringOf(buf, t) {
1084
1089
  }
1085
1090
  }
1086
1091
  function parseSubject(cert) {
1087
- const der = cert instanceof import_node_crypto3.X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
1092
+ const der = cert instanceof import_node_crypto2.X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
1088
1093
  const top = tlv(der, 0, der.length);
1089
1094
  if (top.tag !== 48) throw new ValidationError("subject: Certificate SEQUENCE \u0431\u0438\u0448");
1090
1095
  const tbs = tlv(der, top.start, top.end);
@@ -1157,12 +1162,12 @@ function certExtensions(der) {
1157
1162
  }
1158
1163
 
1159
1164
  // src/token.ts
1160
- function rec5(v) {
1165
+ function rec4(v) {
1161
1166
  return v && typeof v === "object" ? v : {};
1162
1167
  }
1163
1168
  function hexSha256Equal(hex, data) {
1164
1169
  if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
1165
- return (0, import_node_crypto4.timingSafeEqual)((0, import_node_crypto4.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
1170
+ return (0, import_node_crypto3.timingSafeEqual)((0, import_node_crypto3.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
1166
1171
  }
1167
1172
  var TOKEN_RESULT_TIMEOUT_MS = 3e4;
1168
1173
  var TokenApi = class {
@@ -1191,11 +1196,11 @@ var TokenApi = class {
1191
1196
  flow: "notification"
1192
1197
  };
1193
1198
  if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
1194
- const r = rec5(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
1199
+ const r = rec4(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
1195
1200
  if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
1196
1201
  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");
1197
1202
  }
1198
- const vc = rec5(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
1203
+ const vc = rec4(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
1199
1204
  return {
1200
1205
  sessionId: String(r.sessionID ?? ""),
1201
1206
  vc: vc != null ? String(vc) : null,
@@ -1210,7 +1215,7 @@ var TokenApi = class {
1210
1215
  */
1211
1216
  async certificateResult(sessionId) {
1212
1217
  const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
1213
- const r = rec5(raw);
1218
+ const r = rec4(raw);
1214
1219
  const item = (v) => v && typeof v === "object" ? v : null;
1215
1220
  return {
1216
1221
  status: String(r.status ?? ""),
@@ -1226,7 +1231,7 @@ var TokenApi = class {
1226
1231
  }
1227
1232
  /** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
1228
1233
  async authChallenge() {
1229
- const r = rec5(
1234
+ const r = rec4(
1230
1235
  await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
1231
1236
  relyingPartyUUID: this.creds.rpUUID,
1232
1237
  relyingPartyName: this.creds.rpName
@@ -1247,7 +1252,7 @@ var TokenApi = class {
1247
1252
  async authVerify(challengeId, certDer, signature) {
1248
1253
  let serial;
1249
1254
  try {
1250
- serial = parseSubject(new import_node_crypto4.X509Certificate(certDer)).serialNumber;
1255
+ serial = parseSubject(new import_node_crypto3.X509Certificate(certDer)).serialNumber;
1251
1256
  } catch {
1252
1257
  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");
1253
1258
  }
@@ -1257,12 +1262,12 @@ var TokenApi = class {
1257
1262
  certificate: Buffer.from(certDer).toString("base64"),
1258
1263
  signature: Buffer.from(signature).toString("base64")
1259
1264
  });
1260
- const r = rec5(raw);
1261
- if (rec5(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1262
- const id = rec5(r.identity);
1265
+ const r = rec4(raw);
1266
+ if (rec4(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1267
+ const id = rec4(r.identity);
1263
1268
  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");
1264
- const org = rec5(r.organization);
1265
- const cert = rec5(r.cert);
1269
+ const org = rec4(r.organization);
1270
+ const cert = rec4(r.cert);
1266
1271
  return {
1267
1272
  identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
1268
1273
  serialHex: String(cert.serialHex ?? ""),
@@ -1278,7 +1283,7 @@ function csrSpki(csr) {
1278
1283
  const version = tlv(csr, info.start, info.end);
1279
1284
  const subject = tlv(csr, version.end, info.end);
1280
1285
  const spki = tlv(csr, subject.end, info.end);
1281
- return (0, import_node_crypto4.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
1286
+ return (0, import_node_crypto3.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
1282
1287
  format: "der",
1283
1288
  type: "spki"
1284
1289
  });
@@ -1297,7 +1302,7 @@ function verifyTokenCertificates(result, csrAuth, csrSign) {
1297
1302
  }
1298
1303
  let got;
1299
1304
  try {
1300
- got = new import_node_crypto4.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
1305
+ got = new import_node_crypto3.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
1301
1306
  } catch {
1302
1307
  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`);
1303
1308
  }
@@ -1310,13 +1315,13 @@ function tokenAuthDigest(rpUUID, challengeB64) {
1310
1315
  }
1311
1316
  const ch = Buffer.from(challengeB64, "base64");
1312
1317
  if (ch.length !== 32 || ch.toString("base64") !== challengeB64) throw new Error("token auth: challenge 32 \u0431\u0430\u0439\u0442 base64 \u0431\u0438\u0448");
1313
- return (0, import_node_crypto4.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1318
+ return (0, import_node_crypto3.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1314
1319
  }
1315
1320
  function tokenLimitOf(err) {
1316
1321
  if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
1317
1322
  let b;
1318
1323
  try {
1319
- b = rec5(JSON.parse(err.body));
1324
+ b = rec4(JSON.parse(err.body));
1320
1325
  } catch {
1321
1326
  return void 0;
1322
1327
  }
@@ -1329,7 +1334,7 @@ function tokenLimitOf(err) {
1329
1334
  }
1330
1335
 
1331
1336
  // src/validator.ts
1332
- var import_node_crypto5 = require("crypto");
1337
+ var import_node_crypto4 = require("crypto");
1333
1338
  var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
1334
1339
  var ResponseValidator = class {
1335
1340
  anchors;
@@ -1343,8 +1348,8 @@ var ResponseValidator = class {
1343
1348
  checkKeyUsage;
1344
1349
  schemeName;
1345
1350
  constructor(trust = {}) {
1346
- this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
1347
- this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
1351
+ this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
1352
+ this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
1348
1353
  this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
1349
1354
  this.skew = trust.clockSkewMs ?? 0;
1350
1355
  this.allowUntrusted = trust.allowUntrusted ?? false;
@@ -1525,7 +1530,7 @@ var ResponseValidator = class {
1525
1530
  leafOf(result, protocol) {
1526
1531
  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");
1527
1532
  if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
1528
- const leaf = new import_node_crypto5.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
1533
+ const leaf = new import_node_crypto4.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
1529
1534
  this.checkValidity(leaf);
1530
1535
  this.checkLevel(String(result.certificateLevel ?? ""));
1531
1536
  this.checkPurpose(leaf, protocol);
@@ -1659,16 +1664,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
1659
1664
  return fromResponse;
1660
1665
  }
1661
1666
  function userChallengeOf(verifier) {
1662
- return (0, import_node_crypto5.createHash)("sha256").update(verifier, "utf8").digest("base64url");
1667
+ return (0, import_node_crypto4.createHash)("sha256").update(verifier, "utf8").digest("base64url");
1663
1668
  }
1664
1669
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
1665
1670
  const sig = Buffer.from(sigB64, "base64");
1666
1671
  const h = nodeHash(hash);
1667
1672
  if (alg === "rsassa-pss") {
1668
- return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
1673
+ return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
1669
1674
  }
1670
- if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto5.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
1671
- return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PADDING }, sig);
1675
+ if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto4.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
1676
+ return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PADDING }, sig);
1672
1677
  }
1673
1678
  function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
1674
1679
  const sig = Buffer.from(sigB64, "base64");
@@ -1677,7 +1682,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
1677
1682
  if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
1678
1683
  let em;
1679
1684
  try {
1680
- em = (0, import_node_crypto5.publicDecrypt)({ key, padding: import_node_crypto5.constants.RSA_NO_PADDING }, sig);
1685
+ em = (0, import_node_crypto4.publicDecrypt)({ key, padding: import_node_crypto4.constants.RSA_NO_PADDING }, sig);
1681
1686
  } catch {
1682
1687
  return false;
1683
1688
  }
@@ -1761,7 +1766,7 @@ function mgf1(hash, seed, length) {
1761
1766
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
1762
1767
  const c = Buffer.alloc(4);
1763
1768
  c.writeUInt32BE(i, 0);
1764
- out.push((0, import_node_crypto5.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1769
+ out.push((0, import_node_crypto4.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1765
1770
  }
1766
1771
  return Buffer.concat(out).subarray(0, length);
1767
1772
  }
@@ -1785,7 +1790,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
1785
1790
  const psLen = emLen - hLen - sLen - 2;
1786
1791
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
1787
1792
  const salt = db.subarray(psLen + 1);
1788
- const h2 = (0, import_node_crypto5.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1793
+ const h2 = (0, import_node_crypto4.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1789
1794
  return h.equals(h2);
1790
1795
  }
1791
1796
  function keyUsageBits(cert) {
@@ -1919,7 +1924,6 @@ function resolveBaseUrl(baseUrl) {
1919
1924
  var EidClient = class {
1920
1925
  auth;
1921
1926
  sign;
1922
- pdf;
1923
1927
  organization;
1924
1928
  person;
1925
1929
  token;
@@ -1941,11 +1945,6 @@ var EidClient = class {
1941
1945
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1942
1946
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1943
1947
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level, sessionTtlMs: cfg.signSessionTtlMs });
1944
- this.pdf = new PdfApi(http, cfg.credentials, {
1945
- certificateLevel: level,
1946
- timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
1947
- maxBytes: cfg.pdfMaxBytes && cfg.pdfMaxBytes > 0 ? cfg.pdfMaxBytes : DEFAULT_PDF_MAX_BYTES
1948
- });
1949
1948
  this.organization = new OrganizationApi(http);
1950
1949
  this.person = new PersonApi(http);
1951
1950
  this.token = new TokenApi(http, cfg.credentials);
@@ -1953,6 +1952,14 @@ var EidClient = class {
1953
1952
  this.validator = new ResponseValidator(cfg.trust ?? {});
1954
1953
  this.rpName = cfg.credentials.rpName;
1955
1954
  }
1955
+ /** {@link SignApi.pdfDigest} — клиент талын PAdES-ийн DTBS-д гарын үсгийн session (PDF CA руу очихгүй). */
1956
+ signPdfDigest(input) {
1957
+ return this.sign.pdfDigest(input);
1958
+ }
1959
+ /** {@link SignApi.registerSignature} — `pades.complete()`-ийн CMS-ийг CA-д бүртгэнэ (`POST /pdf/signatures`). */
1960
+ registerSignature(input) {
1961
+ return this.sign.registerSignature(input);
1962
+ }
1956
1963
  };
1957
1964
 
1958
1965
  // src/devicelink.ts
@@ -1998,7 +2005,7 @@ function openOrShowQR(session, opts) {
1998
2005
  }
1999
2006
 
2000
2007
  // src/devicelinkv3.ts
2001
- var import_node_crypto6 = require("crypto");
2008
+ var import_node_crypto5 = require("crypto");
2002
2009
  var TYPES = ["QR", "Web2App", "App2App"];
2003
2010
  var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
2004
2011
  var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
@@ -2055,7 +2062,7 @@ function buildDeviceLink(p) {
2055
2062
  callback,
2056
2063
  unprotected
2057
2064
  ].join("|");
2058
- const authCode = (0, import_node_crypto6.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
2065
+ const authCode = (0, import_node_crypto5.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
2059
2066
  return `${unprotected}&authCode=${authCode}`;
2060
2067
  }
2061
2068
  function deviceLinkInput(s, deviceLinkType, lang) {
@@ -2099,7 +2106,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
2099
2106
  }
2100
2107
 
2101
2108
  // src/login.ts
2102
- var import_node_crypto7 = require("crypto");
2109
+ var import_node_crypto6 = require("crypto");
2103
2110
  function memoryLoginStore() {
2104
2111
  const m = /* @__PURE__ */ new Map();
2105
2112
  return {
@@ -2136,7 +2143,7 @@ function forwardedClientIp(headers, trustProxy) {
2136
2143
  return header(headers, "x-real-ip").trim() || void 0;
2137
2144
  }
2138
2145
  var ID_RE = /^(\p{L}{2}[0-9]{8}|[0-9]{8,14}|PNOMN-[0-9]{8,14})$/u;
2139
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
2146
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
2140
2147
  var B64URL_RE = /^[A-Za-z0-9_-]{43}$/;
2141
2148
  var IOS_BROWSERS = [
2142
2149
  [/CriOS/i, "googlechromes"],
@@ -2180,11 +2187,11 @@ function readCookie(h, name) {
2180
2187
  }
2181
2188
  return "";
2182
2189
  }
2183
- var sha256hex = (s) => (0, import_node_crypto7.createHash)("sha256").update(s).digest("hex");
2190
+ var sha256hex = (s) => (0, import_node_crypto6.createHash)("sha256").update(s).digest("hex");
2184
2191
  function safeEqualHex(a, b) {
2185
2192
  const x = Buffer.from(a, "hex");
2186
2193
  const y = Buffer.from(b, "hex");
2187
- return x.length === y.length && x.length > 0 && (0, import_node_crypto7.timingSafeEqual)(x, y);
2194
+ return x.length === y.length && x.length > 0 && (0, import_node_crypto6.timingSafeEqual)(x, y);
2188
2195
  }
2189
2196
  function toIdentity(sessionId, r, v, flowType) {
2190
2197
  const s = v.parsedSubject;
@@ -2267,7 +2274,7 @@ function createEidLogin(opts) {
2267
2274
  const dot = raw.indexOf(".");
2268
2275
  const sid = raw.slice(0, dot);
2269
2276
  const binding = raw.slice(dot + 1);
2270
- if (dot < 0 || !UUID_RE.test(sid) || !B64URL_RE.test(binding)) return void 0;
2277
+ if (dot < 0 || !UUID_RE2.test(sid) || !B64URL_RE.test(binding)) return void 0;
2271
2278
  const tx = await store.get(sid);
2272
2279
  if (!tx || !safeEqualHex(tx.bindingHash, sha256hex(binding))) return void 0;
2273
2280
  if (tx.expiresAt <= Date.now()) {
@@ -2324,7 +2331,7 @@ function createEidLogin(opts) {
2324
2331
  });
2325
2332
  }
2326
2333
  }
2327
- const binding = (0, import_node_crypto7.randomBytes)(32).toString("base64url");
2334
+ const binding = (0, import_node_crypto6.randomBytes)(32).toString("base64url");
2328
2335
  const full = { ...tx, bindingHash: sha256hex(binding), expiresAt: Date.now() + ttl };
2329
2336
  await store.set(full.sessionId, full, ttl);
2330
2337
  const setCookie = `${cookieName}=${full.sessionId}.${binding}; ${cookieAttrs(Math.ceil(ttl / 1e3))}`;
@@ -2491,9 +2498,8 @@ function createEidLogin(opts) {
2491
2498
  AuthApi,
2492
2499
  AuthenticationError,
2493
2500
  CITIZEN_ISSUING_CA_SKI,
2501
+ CertChoiceInvalidError,
2494
2502
  DEFAULT_BASE_URL,
2495
- DEFAULT_PDF_MAX_BYTES,
2496
- DEFAULT_PDF_TIMEOUT_MS,
2497
2503
  DEFAULT_SIGN_SESSION_TTL_MS,
2498
2504
  DeviceLinkError,
2499
2505
  EidClient,
@@ -2501,11 +2507,13 @@ function createEidLogin(opts) {
2501
2507
  ForbiddenError,
2502
2508
  Http,
2503
2509
  NetworkError,
2510
+ NoSigningCertificateError,
2504
2511
  ON_BEHALF_ERROR_CODES,
2505
2512
  OrganizationApi,
2506
- PdfApi,
2513
+ PDF_SIGNATURE_REGISTER_FIELDS,
2507
2514
  PersonApi,
2508
2515
  RETRYABLE_ERROR_CODES,
2516
+ RequestInvalidError,
2509
2517
  ResponseTooLargeError,
2510
2518
  ResponseValidator,
2511
2519
  SessionApi,
@@ -2516,6 +2524,7 @@ function createEidLogin(opts) {
2516
2524
  acspV2Digest,
2517
2525
  buildAcspV2Payload,
2518
2526
  buildDeviceLink,
2527
+ buildRegisterBody,
2519
2528
  createEidLogin,
2520
2529
  deviceLink,
2521
2530
  deviceLinkInput,
@@ -2524,15 +2533,18 @@ function createEidLogin(opts) {
2524
2533
  isMobileUserAgent,
2525
2534
  memoryLoginStore,
2526
2535
  openOrShowQR,
2536
+ parseExpiresAt,
2527
2537
  parseRetryAfter,
2528
2538
  parseSessionResult,
2529
2539
  parseSubject,
2530
2540
  personMatchesCertificate,
2531
2541
  qrDeviceLinkTicker,
2532
2542
  randomChallenge,
2543
+ registrationFromCompleted,
2533
2544
  resolveBaseUrl,
2534
2545
  sessionExpiry,
2535
2546
  sha256Base64,
2547
+ startExpiry,
2536
2548
  tokenAuthDigest,
2537
2549
  tokenLimitOf,
2538
2550
  userChallengeOf,