@gerege-systems/eid-mongolia-sdk 0.14.2 → 0.15.1

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
@@ -26,8 +26,6 @@ __export(index_exports, {
26
26
  CITIZEN_ISSUING_CA_SKI: () => CITIZEN_ISSUING_CA_SKI,
27
27
  CertChoiceInvalidError: () => CertChoiceInvalidError,
28
28
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
29
- DEFAULT_PDF_MAX_BYTES: () => DEFAULT_PDF_MAX_BYTES,
30
- DEFAULT_PDF_TIMEOUT_MS: () => DEFAULT_PDF_TIMEOUT_MS,
31
29
  DEFAULT_SIGN_SESSION_TTL_MS: () => DEFAULT_SIGN_SESSION_TTL_MS,
32
30
  DeviceLinkError: () => DeviceLinkError,
33
31
  EidClient: () => EidClient,
@@ -38,7 +36,7 @@ __export(index_exports, {
38
36
  NoSigningCertificateError: () => NoSigningCertificateError,
39
37
  ON_BEHALF_ERROR_CODES: () => ON_BEHALF_ERROR_CODES,
40
38
  OrganizationApi: () => OrganizationApi,
41
- PdfApi: () => PdfApi,
39
+ PDF_SIGNATURE_REGISTER_FIELDS: () => PDF_SIGNATURE_REGISTER_FIELDS,
42
40
  PersonApi: () => PersonApi,
43
41
  RETRYABLE_ERROR_CODES: () => RETRYABLE_ERROR_CODES,
44
42
  RequestInvalidError: () => RequestInvalidError,
@@ -52,7 +50,9 @@ __export(index_exports, {
52
50
  acspV2Digest: () => acspV2Digest,
53
51
  buildAcspV2Payload: () => buildAcspV2Payload,
54
52
  buildDeviceLink: () => buildDeviceLink,
53
+ buildRegisterBody: () => buildRegisterBody,
55
54
  createEidLogin: () => createEidLogin,
55
+ defaultRateLimit: () => defaultRateLimit,
56
56
  deviceLink: () => deviceLink,
57
57
  deviceLinkInput: () => deviceLinkInput,
58
58
  forwardedClientIp: () => forwardedClientIp,
@@ -67,6 +67,7 @@ __export(index_exports, {
67
67
  personMatchesCertificate: () => personMatchesCertificate,
68
68
  qrDeviceLinkTicker: () => qrDeviceLinkTicker,
69
69
  randomChallenge: () => randomChallenge,
70
+ registrationFromCompleted: () => registrationFromCompleted,
70
71
  resolveBaseUrl: () => resolveBaseUrl,
71
72
  sessionExpiry: () => sessionExpiry,
72
73
  sha256Base64: () => sha256Base64,
@@ -226,15 +227,6 @@ var Http = class {
226
227
  get(path, timeoutMs) {
227
228
  return this.json("GET", path, void 0, void 0, timeoutMs);
228
229
  }
229
- /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
230
- postMultipart(path, form, timeoutMs) {
231
- return this.json("POST", path, form, void 0, timeoutMs);
232
- }
233
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
234
- async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
235
- const res = await this.send("GET", path, void 0, void 0, timeoutMs);
236
- return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
237
- }
238
230
  async json(method, path, body, contentType, timeoutMs) {
239
231
  const res = await this.send(method, path, body, contentType, timeoutMs);
240
232
  const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
@@ -339,8 +331,6 @@ var RP_ROUTES = {
339
331
  signDeviceLinkDocument: { method: "POST", path: "/signature/device-link/document/{documentNumber}" },
340
332
  certChoiceDeviceLink: { method: "POST", path: "/signature/certificate-choice/device-link/anonymous" },
341
333
  certChoiceEtsi: { method: "POST", path: "/signature/certificate-choice/notification/etsi/{semanticsIdentifier}" },
342
- pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
343
- pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
344
334
  orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
345
335
  session: { method: "GET", path: "/session/{sessionId}" },
346
336
  personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
@@ -349,15 +339,29 @@ var RP_ROUTES = {
349
339
  personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
350
340
  personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
351
341
  tokenCertificate: { method: "POST", path: "/token/certificate" },
352
- tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
342
+ tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" },
343
+ pdfSignatures: { method: "POST", path: "/pdf/signatures" }
353
344
  };
354
345
  var RP_ROUTES_UNDISCOVERED = {
355
346
  tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
356
- tokenAuthVerify: { method: "POST", path: "/token/auth/verify" },
357
- pdfTokenPrepare: { method: "POST", path: "/pdf/token/prepare" },
358
- pdfTokenFinalize: { method: "POST", path: "/pdf/token/{signingID}/finalize" },
359
- pdfTokenDocument: { method: "GET", path: "/pdf/token/{signingID}/document" }
347
+ tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
360
348
  };
349
+ var PDF_SIGNATURE_REGISTER_FIELDS = [
350
+ "docID",
351
+ "revIndex",
352
+ "medium",
353
+ "sessionID",
354
+ "cms",
355
+ "fileName",
356
+ "inSha256",
357
+ "pageSha256",
358
+ "sigRevSha256",
359
+ "outSha256",
360
+ "priorCmsSha256",
361
+ "priorRevisionSha256",
362
+ "onBehalfOf",
363
+ "signerEtsi"
364
+ ];
361
365
  function routePath(route, ...args) {
362
366
  let i = 0;
363
367
  const out = route.path.replace(/\{[^}]*\}/g, () => {
@@ -520,6 +524,84 @@ var AuthApi = class {
520
524
  };
521
525
 
522
526
  // src/sign.ts
527
+ function registrationFromCompleted(done, extra) {
528
+ const out = {
529
+ docID: done.docId,
530
+ revIndex: done.revIndex,
531
+ cms: done.cms,
532
+ fileName: extra.fileName,
533
+ inSha256: done.inSha256,
534
+ sigRevSha256: done.sigRevSha256,
535
+ outSha256: done.outSha256
536
+ };
537
+ if (extra.medium) out.medium = extra.medium;
538
+ if (extra.sessionID) out.sessionID = extra.sessionID;
539
+ if (done.hasPage && done.pageSha256) out.pageSha256 = done.pageSha256;
540
+ if (done.priorCmsSha256.length) out.priorCmsSha256 = done.priorCmsSha256;
541
+ if (extra.priorRevisionSha256?.length) out.priorRevisionSha256 = extra.priorRevisionSha256;
542
+ if (extra.onBehalfOf) out.onBehalfOf = extra.onBehalfOf;
543
+ if (extra.signerEtsi) out.signerEtsi = extra.signerEtsi;
544
+ return out;
545
+ }
546
+ var HEX64 = /^[0-9a-f]{64}$/;
547
+ var MAX_CMS_BYTES = 28672;
548
+ function buildRegisterBody(input) {
549
+ const fail2 = (m) => {
550
+ throw new ValidationError(`registerSignature: ${m}`);
551
+ };
552
+ if (!UUID_RE.test(input.docID ?? "")) fail2("docID UUID \u0431\u0438\u0448");
553
+ if (!Number.isInteger(input.revIndex) || input.revIndex < 1 || input.revIndex > 16) fail2("revIndex 1\u201316");
554
+ const hex = (name, v) => {
555
+ const x = v?.trim().toLowerCase() ?? "";
556
+ if (!HEX64.test(x)) fail2(`${name} SHA-256 hex (64) \u0431\u0438\u0448`);
557
+ return x;
558
+ };
559
+ let cmsB64;
560
+ if (typeof input.cms === "string") {
561
+ const raw = Buffer.from(input.cms, "base64");
562
+ if (raw.length === 0 || raw.toString("base64") !== input.cms) fail2("cms \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442 base64 \u0431\u0438\u0448");
563
+ cmsB64 = input.cms;
564
+ if (raw.length > MAX_CMS_BYTES) fail2(`cms > ${MAX_CMS_BYTES} \u0431\u0430\u0439\u0442`);
565
+ } else {
566
+ if (!input.cms?.length) fail2("cms \u0445\u043E\u043E\u0441\u043E\u043D");
567
+ if (input.cms.length > MAX_CMS_BYTES) fail2(`cms > ${MAX_CMS_BYTES} \u0431\u0430\u0439\u0442`);
568
+ cmsB64 = Buffer.from(input.cms).toString("base64");
569
+ }
570
+ const fileName = input.fileName?.trim() ?? "";
571
+ 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`);
572
+ const sessionID = input.sessionID?.trim() ?? "";
573
+ const medium = input.medium ?? (sessionID ? "phone" : "token");
574
+ if (medium !== "phone" && medium !== "token") fail2("medium: phone | token");
575
+ if (medium === "phone" && !sessionID) fail2("phone-\u0434 sessionID \u0437\u0430\u0430\u0432\u0430\u043B");
576
+ const priorCms = (input.priorCmsSha256 ?? []).map((h, i) => hex(`priorCmsSha256[${i}]`, h));
577
+ const priorRev = (input.priorRevisionSha256 ?? []).map((h, i) => hex(`priorRevisionSha256[${i}]`, h));
578
+ if (priorCms.length + priorRev.length !== input.revIndex - 1) fail2("priorCmsSha256 + priorRevisionSha256-\u0438\u0439\u043D \u0442\u043E\u043E = revIndex \u2212 1");
579
+ if (input.revIndex === 1 && !input.pageSha256) fail2("revIndex 1-\u0434 pageSha256 \u0437\u0430\u0430\u0432\u0430\u043B");
580
+ const body = { docID: input.docID, revIndex: input.revIndex, medium };
581
+ if (sessionID) body.sessionID = sessionID;
582
+ body.cms = cmsB64;
583
+ body.fileName = fileName;
584
+ body.inSha256 = hex("inSha256", input.inSha256);
585
+ if (input.pageSha256) body.pageSha256 = hex("pageSha256", input.pageSha256);
586
+ body.sigRevSha256 = hex("sigRevSha256", input.sigRevSha256);
587
+ body.outSha256 = hex("outSha256", input.outSha256);
588
+ if (priorCms.length) body.priorCmsSha256 = priorCms;
589
+ if (priorRev.length) body.priorRevisionSha256 = priorRev;
590
+ if (input.onBehalfOf?.trim()) body.onBehalfOf = input.onBehalfOf.trim();
591
+ if (input.signerEtsi?.trim()) body.signerEtsi = input.signerEtsi.trim();
592
+ 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}`);
593
+ return body;
594
+ }
595
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
596
+ 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: ";
597
+ var PDF_MAX_FILE_NAME = 120;
598
+ function dtbsBase64(v) {
599
+ const raw = typeof v === "string" ? Buffer.from(v, "base64") : Buffer.from(v);
600
+ if (raw.length !== 32 || typeof v === "string" && raw.toString("base64") !== v) {
601
+ throw new ValidationError("pdfDigest: dtbs \u043D\u044C 32 \u0431\u0430\u0439\u0442 (SHA-256) \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
602
+ }
603
+ return raw.toString("base64");
604
+ }
523
605
  function rec2(v) {
524
606
  return v && typeof v === "object" ? v : {};
525
607
  }
@@ -604,6 +686,77 @@ var SignApi = class {
604
686
  const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
605
687
  return this.notification(routePath(RP_ROUTES.signNotificationDocument, documentNumber), body, sign);
606
688
  }
689
+ /**
690
+ * Клиент талын PAdES (`@gerege-systems/eid-mongolia-sdk/pades`): `prepare()`-ийн DTBS-д иргэнээр гарын үсэг зуруулна.
691
+ * PDF CA руу ОЧИХГҮЙ — зөвхөн 32 байт. CA-ийн өөрийн PAdES session-тэй ижил: `RAW_DIGEST_SIGNATURE`,
692
+ * `sha256WithRSAEncryption` (CMS-д PKCS#1 v1.5; legacy ECDSA иргэнд сервер `ecdsa-with-SHA256`), `displayTextAndPIN`
693
+ * «Баримтад гарын үсэг зурах: <fileName>» (60 тэмдэгтэд тайрна).
694
+ *
695
+ * Дараа нь `session.waitForResult(sessionId)` → `validator.validateSign(result, s.digest)` → `signature.value`-г
696
+ * (base64 → байт) `complete(state, signature)`-д.
697
+ */
698
+ async pdfDigest(input) {
699
+ const digest = dtbsBase64(input.dtbs);
700
+ const fileName = input.fileName?.trim() ?? "";
701
+ if (!fileName || Array.from(fileName).length > PDF_MAX_FILE_NAME) {
702
+ throw new ValidationError(`pdfDigest: fileName \u0437\u0430\u0430\u0432\u0430\u043B, \u2264 ${PDF_MAX_FILE_NAME} \u0442\u044D\u043C\u0434\u044D\u0433\u0442`);
703
+ }
704
+ const signer = input.signer;
705
+ const etsi = signer?.etsi?.trim();
706
+ const doc = signer?.documentNumber?.trim();
707
+ 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");
708
+ const deviceLink2 = input.flow === "device-link";
709
+ const display = Array.from(PDF_DISPLAY_TEXT + fileName).slice(0, 60).join("");
710
+ const interactions = interactionsToBase64([{ type: "displayTextAndPIN", displayText60: display }]);
711
+ const body = {
712
+ relyingPartyUUID: this.creds.rpUUID,
713
+ relyingPartyName: this.creds.rpName,
714
+ certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
715
+ signatureProtocol: "RAW_DIGEST_SIGNATURE",
716
+ signatureProtocolParameters: {
717
+ digest,
718
+ signatureAlgorithm: "sha256WithRSAEncryption",
719
+ signatureAlgorithmParameters: { hashAlgorithm: "SHA-256" }
720
+ },
721
+ interactions,
722
+ fileName
723
+ };
724
+ if (input.initialCallbackUrl?.trim()) body.initialCallbackUrl = input.initialCallbackUrl.trim();
725
+ if (input.onBehalfOf?.trim()) body.onBehalfOf = input.onBehalfOf.trim();
726
+ const route = deviceLink2 ? etsi ? RP_ROUTES.signDeviceLinkEtsi : RP_ROUTES.signDeviceLinkDocument : etsi ? RP_ROUTES.signNotificationEtsi : RP_ROUTES.signNotificationDocument;
727
+ const r = rec2(await this.http.post(routePath(route, etsi ?? doc), body));
728
+ const receivedAt = Date.now();
729
+ const vc = typeof r.vc === "object" && r.vc !== null ? rec2(r.vc).value : r.vc;
730
+ let dl = null;
731
+ if (deviceLink2) {
732
+ if (!r.sessionToken || !r.sessionSecret || !r.deviceLinkBase) {
733
+ throw new ValidationError("pdfDigest: device-link \u0445\u0430\u0440\u0438\u0443\u043D\u0434 sessionToken/sessionSecret/deviceLinkBase \u0430\u043B\u0433\u0430");
734
+ }
735
+ dl = { sessionToken: String(r.sessionToken), sessionSecret: String(r.sessionSecret), deviceLinkBase: String(r.deviceLinkBase) };
736
+ }
737
+ return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, digest, interactions, deviceLink: dl, receivedAt };
738
+ }
739
+ /**
740
+ * `POST /pdf/signatures` — `pades.complete()`-ийн дараа CMS-ийг CA-д бүртгэнэ (`/verify/{docID}`, иргэний түүх). PDF
741
+ * илгээгдэхгүй. SIGN эрх. CA CMS-ийг шалгана: 04-ийн SIGN cert, session digest = SHA-256(signedAttrs), claimedRole ↔
742
+ * төлөөлөл. Алдаа: 403 `REPRESENTATION_REVOKED`, 409 `CLAIMED_ROLE_MISMATCH` / `REGISTRY_CONFLICT` /
743
+ * `DOC_ID_CONFLICT` / `PDF_FOREIGN_SIGNATURE` (`ApiError.code`). Бүртгэлгүй файл ч Adobe/EU DSS-д хүчинтэй — зөвхөн
744
+ * `/verify` «бүртгэлгүй» гэнэ.
745
+ */
746
+ async registerSignature(input) {
747
+ return this.registerBody(buildRegisterBody(input));
748
+ }
749
+ async registerBody(body) {
750
+ const raw = await this.http.post(routePath(RP_ROUTES.pdfSignatures), body);
751
+ const r = rec2(raw);
752
+ return {
753
+ docID: String(r.docID ?? body.docID),
754
+ revIndex: Number(r.revIndex ?? 0),
755
+ registeredAt: typeof r.registeredAt === "string" ? r.registeredAt : "",
756
+ verifyUrl: String(r.verifyUrl ?? ""),
757
+ raw
758
+ };
759
+ }
607
760
  /** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
608
761
  async textByEtsi(id, text, interactions, opts) {
609
762
  return this.digestByEtsi(id, sha256Base64(text), interactions, opts);
@@ -719,7 +872,6 @@ function parseSessionResult(raw) {
719
872
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
720
873
  interactionTypeUsed: str(r.interactionTypeUsed),
721
874
  onBehalfOf: parseOrg(r.onBehalfOf),
722
- pdf: parsePdf(r.pdf),
723
875
  person: parsePerson(r.person),
724
876
  tokenCertificate: parseTokenCert(r.tokenCertificate),
725
877
  raw
@@ -757,28 +909,6 @@ function personMatchesCertificate(person, subject) {
757
909
  const serial = typeof subject === "string" ? subject : subject?.serialNumber;
758
910
  return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
759
911
  }
760
- function parsePdf(raw) {
761
- const p = asRecord(raw);
762
- const docID = str(p.docID);
763
- if (!docID) return null;
764
- const out = { docID, documentStatus: String(p.documentStatus ?? "") };
765
- if (p.errorCode != null) out.errorCode = String(p.errorCode);
766
- if (p.outSha256 != null) out.outSha256 = String(p.outSha256);
767
- if (p.size != null) out.size = Number(p.size);
768
- if (p.expiresAt != null) out.expiresAt = String(p.expiresAt);
769
- if (p.signatureLevel != null) out.signatureLevel = String(p.signatureLevel);
770
- const sg = asRecord(p.signer);
771
- if (sg.etsi != null) {
772
- out.signer = { etsi: String(sg.etsi), certSerial: String(sg.certSerial ?? "") };
773
- if (sg.onBehalfOf) out.signer.onBehalfOf = String(sg.onBehalfOf);
774
- if (sg.claimedRole) out.signer.claimedRole = String(sg.claimedRole);
775
- }
776
- const v = asRecord(p.validation);
777
- if (v.indication != null) {
778
- out.validation = { indication: String(v.indication), ltv: v.ltv === true, signatures: Number(v.signatures ?? 0) };
779
- }
780
- return out;
781
- }
782
912
  function parseSigParams(raw) {
783
913
  const p = asRecord(raw);
784
914
  const hash = str(p.hashAlgorithm);
@@ -837,160 +967,6 @@ var SessionApi = class {
837
967
  }
838
968
  };
839
969
 
840
- // src/pdf.ts
841
- var import_node_crypto2 = require("crypto");
842
- var DEFAULT_PDF_MAX_BYTES = 64 << 20;
843
- var DEFAULT_PDF_TIMEOUT_MS = 8e4;
844
- function rec3(v) {
845
- return v && typeof v === "object" ? v : {};
846
- }
847
- var PdfApi = class {
848
- constructor(http, creds, defaults) {
849
- this.http = http;
850
- this.creds = creds;
851
- this.defaults = defaults;
852
- }
853
- http;
854
- creds;
855
- defaults;
856
- /**
857
- * POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. Түр алдаа: `ApiError` 429
858
- * PDF_PREPARE_BUSY, 503 LTV_UNAVAILABLE / TSA_UNAVAILABLE — `retryAfterMs`-ийн дараа дахин (`retryable`).
859
- */
860
- async prepare(input) {
861
- const request = {
862
- relyingPartyUUID: this.creds.rpUUID,
863
- relyingPartyName: this.creds.rpName,
864
- certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
865
- signer: input.signer,
866
- flow: input.flow ?? "notification",
867
- fileName: input.fileName
868
- };
869
- if (input.initialCallbackUrl?.trim()) request.initialCallbackUrl = input.initialCallbackUrl.trim();
870
- if (input.docID?.trim()) request.docID = input.docID.trim();
871
- if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
872
- const form = new FormData();
873
- form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
874
- form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
875
- const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
876
- const receivedAt = Date.now();
877
- const vc = rec3(r.vc).value;
878
- const dl = rec3(r.deviceLink);
879
- const digest = typeof r.digest === "string" && r.digest ? r.digest : typeof dl.digest === "string" && dl.digest ? dl.digest : void 0;
880
- return {
881
- sessionId: String(r.sessionID),
882
- docId: String(r.docID),
883
- vc: vc != null ? String(vc) : null,
884
- ...digest !== void 0 ? { digest } : {},
885
- deviceLink: dl.sessionToken ? {
886
- sessionToken: String(dl.sessionToken),
887
- sessionSecret: String(dl.sessionSecret),
888
- deviceLinkBase: String(dl.deviceLinkBase),
889
- digest: String(dl.digest),
890
- interactions: String(dl.interactions)
891
- } : null,
892
- receivedAt
893
- };
894
- }
895
- /**
896
- * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
897
- * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`. Баримт
898
- * бэлэн болоогүй бол `ApiError` 503 `.code` DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE — `retryAfterMs`-ийн
899
- * дараа дахин татна (`retryable`).
900
- */
901
- document(sessionId) {
902
- return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.timeout, this.maxBytes);
903
- }
904
- get timeout() {
905
- return this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS;
906
- }
907
- get maxBytes() {
908
- return this.defaults.maxBytes ?? DEFAULT_PDF_MAX_BYTES;
909
- }
910
- /**
911
- * USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
912
- * (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
913
- * зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
914
- * LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
915
- * TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
916
- */
917
- async tokenPrepare(input) {
918
- if (!input.pdf?.length || !input.certificate?.length || !input.signerEtsi?.trim() || !input.fileName?.trim()) {
919
- throw new Error("eid: pdf token: pdf, certificate, signerEtsi, fileName \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
920
- }
921
- const request = {
922
- relyingPartyUUID: this.creds.rpUUID,
923
- relyingPartyName: this.creds.rpName,
924
- signer: { etsi: input.signerEtsi.trim() },
925
- certificate: Buffer.from(input.certificate).toString("base64"),
926
- fileName: input.fileName
927
- };
928
- if (input.docID?.trim()) request.docID = input.docID.trim();
929
- if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
930
- const form = new FormData();
931
- form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
932
- form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
933
- const raw = await this.http.postMultipart(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenPrepare), form, this.timeout);
934
- const r = rec3(raw);
935
- const hashToSign = new Uint8Array(Buffer.from(String(r.hashToSign ?? ""), "base64"));
936
- if (!r.signingID || hashToSign.length !== 32) {
937
- throw new ValidationError("pdf token prepare: signingID / hashToSign (32 \u0431\u0430\u0439\u0442) \u0431\u0443\u0440\u0443\u0443");
938
- }
939
- return {
940
- signingId: String(r.signingID),
941
- docId: String(r.docID ?? ""),
942
- hashToSign,
943
- hashAlgorithm: String(r.hashAlgorithm ?? ""),
944
- signatureAlgorithm: String(r.signatureAlgorithm ?? ""),
945
- signatureFormat: String(r.signatureFormat ?? ""),
946
- expiresAt: String(r.expiresAt ?? ""),
947
- raw
948
- };
949
- }
950
- /**
951
- * POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
952
- * Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
953
- * Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
954
- * TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
955
- */
956
- async tokenFinalize(signingId, signature) {
957
- 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)");
958
- const r = await this.http.post(
959
- routePath(RP_ROUTES_UNDISCOVERED.pdfTokenFinalize, signingId),
960
- { signature: Buffer.from(signature).toString("base64") },
961
- this.timeout
962
- );
963
- return r;
964
- }
965
- /**
966
- * GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
967
- * хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
968
- */
969
- tokenDocument(signingId) {
970
- return this.http.getBytes(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenDocument, signingId), this.timeout, this.maxBytes);
971
- }
972
- /**
973
- * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
974
- * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
975
- *
976
- * @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
977
- */
978
- async documentVerified(sessionId, expected) {
979
- let want;
980
- if (typeof expected === "string") {
981
- want = expected;
982
- } else {
983
- if (expected.documentStatus !== "READY") throw new ValidationError(`pdf.documentStatus ${expected.documentStatus} \u2260 READY`);
984
- want = expected.outSha256;
985
- }
986
- 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");
987
- const bytes = await this.document(sessionId);
988
- const got = (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
989
- 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");
990
- return bytes;
991
- }
992
- };
993
-
994
970
  // src/organization.ts
995
971
  var OrganizationApi = class {
996
972
  constructor(http) {
@@ -1006,7 +982,7 @@ var OrganizationApi = class {
1006
982
  };
1007
983
 
1008
984
  // src/person.ts
1009
- function rec4(v) {
985
+ function rec3(v) {
1010
986
  return v && typeof v === "object" ? v : {};
1011
987
  }
1012
988
  var PersonApi = class {
@@ -1016,7 +992,7 @@ var PersonApi = class {
1016
992
  http;
1017
993
  async get(path) {
1018
994
  const raw = await this.http.get(path);
1019
- return { ...rec4(raw), raw };
995
+ return { ...rec3(raw), raw };
1020
996
  }
1021
997
  /** Иргэний бүх гэрчилгээ + статусын тоолол. */
1022
998
  certificates(personEtsi) {
@@ -1046,10 +1022,10 @@ var PersonApi = class {
1046
1022
  };
1047
1023
 
1048
1024
  // src/token.ts
1049
- var import_node_crypto4 = require("crypto");
1025
+ var import_node_crypto3 = require("crypto");
1050
1026
 
1051
1027
  // src/subject.ts
1052
- var import_node_crypto3 = require("crypto");
1028
+ var import_node_crypto2 = require("crypto");
1053
1029
  var OIDS = {
1054
1030
  "2.5.4.42": "givenName",
1055
1031
  "2.5.4.4": "surname",
@@ -1114,7 +1090,7 @@ function stringOf(buf, t) {
1114
1090
  }
1115
1091
  }
1116
1092
  function parseSubject(cert) {
1117
- const der = cert instanceof import_node_crypto3.X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
1093
+ const der = cert instanceof import_node_crypto2.X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
1118
1094
  const top = tlv(der, 0, der.length);
1119
1095
  if (top.tag !== 48) throw new ValidationError("subject: Certificate SEQUENCE \u0431\u0438\u0448");
1120
1096
  const tbs = tlv(der, top.start, top.end);
@@ -1187,12 +1163,12 @@ function certExtensions(der) {
1187
1163
  }
1188
1164
 
1189
1165
  // src/token.ts
1190
- function rec5(v) {
1166
+ function rec4(v) {
1191
1167
  return v && typeof v === "object" ? v : {};
1192
1168
  }
1193
1169
  function hexSha256Equal(hex, data) {
1194
1170
  if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
1195
- return (0, import_node_crypto4.timingSafeEqual)((0, import_node_crypto4.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
1171
+ return (0, import_node_crypto3.timingSafeEqual)((0, import_node_crypto3.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
1196
1172
  }
1197
1173
  var TOKEN_RESULT_TIMEOUT_MS = 3e4;
1198
1174
  var TokenApi = class {
@@ -1221,11 +1197,11 @@ var TokenApi = class {
1221
1197
  flow: "notification"
1222
1198
  };
1223
1199
  if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
1224
- const r = rec5(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
1200
+ const r = rec4(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
1225
1201
  if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
1226
1202
  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");
1227
1203
  }
1228
- const vc = rec5(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
1204
+ const vc = rec4(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
1229
1205
  return {
1230
1206
  sessionId: String(r.sessionID ?? ""),
1231
1207
  vc: vc != null ? String(vc) : null,
@@ -1240,7 +1216,7 @@ var TokenApi = class {
1240
1216
  */
1241
1217
  async certificateResult(sessionId) {
1242
1218
  const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
1243
- const r = rec5(raw);
1219
+ const r = rec4(raw);
1244
1220
  const item = (v) => v && typeof v === "object" ? v : null;
1245
1221
  return {
1246
1222
  status: String(r.status ?? ""),
@@ -1256,7 +1232,7 @@ var TokenApi = class {
1256
1232
  }
1257
1233
  /** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
1258
1234
  async authChallenge() {
1259
- const r = rec5(
1235
+ const r = rec4(
1260
1236
  await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
1261
1237
  relyingPartyUUID: this.creds.rpUUID,
1262
1238
  relyingPartyName: this.creds.rpName
@@ -1277,7 +1253,7 @@ var TokenApi = class {
1277
1253
  async authVerify(challengeId, certDer, signature) {
1278
1254
  let serial;
1279
1255
  try {
1280
- serial = parseSubject(new import_node_crypto4.X509Certificate(certDer)).serialNumber;
1256
+ serial = parseSubject(new import_node_crypto3.X509Certificate(certDer)).serialNumber;
1281
1257
  } catch {
1282
1258
  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");
1283
1259
  }
@@ -1287,12 +1263,12 @@ var TokenApi = class {
1287
1263
  certificate: Buffer.from(certDer).toString("base64"),
1288
1264
  signature: Buffer.from(signature).toString("base64")
1289
1265
  });
1290
- const r = rec5(raw);
1291
- if (rec5(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1292
- const id = rec5(r.identity);
1266
+ const r = rec4(raw);
1267
+ if (rec4(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1268
+ const id = rec4(r.identity);
1293
1269
  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");
1294
- const org = rec5(r.organization);
1295
- const cert = rec5(r.cert);
1270
+ const org = rec4(r.organization);
1271
+ const cert = rec4(r.cert);
1296
1272
  return {
1297
1273
  identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
1298
1274
  serialHex: String(cert.serialHex ?? ""),
@@ -1308,7 +1284,7 @@ function csrSpki(csr) {
1308
1284
  const version = tlv(csr, info.start, info.end);
1309
1285
  const subject = tlv(csr, version.end, info.end);
1310
1286
  const spki = tlv(csr, subject.end, info.end);
1311
- return (0, import_node_crypto4.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
1287
+ return (0, import_node_crypto3.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
1312
1288
  format: "der",
1313
1289
  type: "spki"
1314
1290
  });
@@ -1327,7 +1303,7 @@ function verifyTokenCertificates(result, csrAuth, csrSign) {
1327
1303
  }
1328
1304
  let got;
1329
1305
  try {
1330
- got = new import_node_crypto4.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
1306
+ got = new import_node_crypto3.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
1331
1307
  } catch {
1332
1308
  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`);
1333
1309
  }
@@ -1340,13 +1316,13 @@ function tokenAuthDigest(rpUUID, challengeB64) {
1340
1316
  }
1341
1317
  const ch = Buffer.from(challengeB64, "base64");
1342
1318
  if (ch.length !== 32 || ch.toString("base64") !== challengeB64) throw new Error("token auth: challenge 32 \u0431\u0430\u0439\u0442 base64 \u0431\u0438\u0448");
1343
- return (0, import_node_crypto4.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1319
+ return (0, import_node_crypto3.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1344
1320
  }
1345
1321
  function tokenLimitOf(err) {
1346
1322
  if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
1347
1323
  let b;
1348
1324
  try {
1349
- b = rec5(JSON.parse(err.body));
1325
+ b = rec4(JSON.parse(err.body));
1350
1326
  } catch {
1351
1327
  return void 0;
1352
1328
  }
@@ -1359,7 +1335,7 @@ function tokenLimitOf(err) {
1359
1335
  }
1360
1336
 
1361
1337
  // src/validator.ts
1362
- var import_node_crypto5 = require("crypto");
1338
+ var import_node_crypto4 = require("crypto");
1363
1339
  var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
1364
1340
  var ResponseValidator = class {
1365
1341
  anchors;
@@ -1373,8 +1349,8 @@ var ResponseValidator = class {
1373
1349
  checkKeyUsage;
1374
1350
  schemeName;
1375
1351
  constructor(trust = {}) {
1376
- this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
1377
- this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
1352
+ this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
1353
+ this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
1378
1354
  this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
1379
1355
  this.skew = trust.clockSkewMs ?? 0;
1380
1356
  this.allowUntrusted = trust.allowUntrusted ?? false;
@@ -1555,7 +1531,7 @@ var ResponseValidator = class {
1555
1531
  leafOf(result, protocol) {
1556
1532
  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");
1557
1533
  if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
1558
- const leaf = new import_node_crypto5.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
1534
+ const leaf = new import_node_crypto4.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
1559
1535
  this.checkValidity(leaf);
1560
1536
  this.checkLevel(String(result.certificateLevel ?? ""));
1561
1537
  this.checkPurpose(leaf, protocol);
@@ -1689,16 +1665,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
1689
1665
  return fromResponse;
1690
1666
  }
1691
1667
  function userChallengeOf(verifier) {
1692
- return (0, import_node_crypto5.createHash)("sha256").update(verifier, "utf8").digest("base64url");
1668
+ return (0, import_node_crypto4.createHash)("sha256").update(verifier, "utf8").digest("base64url");
1693
1669
  }
1694
1670
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
1695
1671
  const sig = Buffer.from(sigB64, "base64");
1696
1672
  const h = nodeHash(hash);
1697
1673
  if (alg === "rsassa-pss") {
1698
- return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
1674
+ return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
1699
1675
  }
1700
- if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto5.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
1701
- return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PADDING }, sig);
1676
+ if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto4.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
1677
+ return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PADDING }, sig);
1702
1678
  }
1703
1679
  function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
1704
1680
  const sig = Buffer.from(sigB64, "base64");
@@ -1707,7 +1683,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
1707
1683
  if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
1708
1684
  let em;
1709
1685
  try {
1710
- em = (0, import_node_crypto5.publicDecrypt)({ key, padding: import_node_crypto5.constants.RSA_NO_PADDING }, sig);
1686
+ em = (0, import_node_crypto4.publicDecrypt)({ key, padding: import_node_crypto4.constants.RSA_NO_PADDING }, sig);
1711
1687
  } catch {
1712
1688
  return false;
1713
1689
  }
@@ -1791,7 +1767,7 @@ function mgf1(hash, seed, length) {
1791
1767
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
1792
1768
  const c = Buffer.alloc(4);
1793
1769
  c.writeUInt32BE(i, 0);
1794
- out.push((0, import_node_crypto5.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1770
+ out.push((0, import_node_crypto4.createHash)(nodeHash(hash)).update(seed).update(c).digest());
1795
1771
  }
1796
1772
  return Buffer.concat(out).subarray(0, length);
1797
1773
  }
@@ -1815,7 +1791,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
1815
1791
  const psLen = emLen - hLen - sLen - 2;
1816
1792
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
1817
1793
  const salt = db.subarray(psLen + 1);
1818
- const h2 = (0, import_node_crypto5.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1794
+ const h2 = (0, import_node_crypto4.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1819
1795
  return h.equals(h2);
1820
1796
  }
1821
1797
  function keyUsageBits(cert) {
@@ -1949,7 +1925,6 @@ function resolveBaseUrl(baseUrl) {
1949
1925
  var EidClient = class {
1950
1926
  auth;
1951
1927
  sign;
1952
- pdf;
1953
1928
  organization;
1954
1929
  person;
1955
1930
  token;
@@ -1971,11 +1946,6 @@ var EidClient = class {
1971
1946
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1972
1947
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1973
1948
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level, sessionTtlMs: cfg.signSessionTtlMs });
1974
- this.pdf = new PdfApi(http, cfg.credentials, {
1975
- certificateLevel: level,
1976
- timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
1977
- maxBytes: cfg.pdfMaxBytes && cfg.pdfMaxBytes > 0 ? cfg.pdfMaxBytes : DEFAULT_PDF_MAX_BYTES
1978
- });
1979
1949
  this.organization = new OrganizationApi(http);
1980
1950
  this.person = new PersonApi(http);
1981
1951
  this.token = new TokenApi(http, cfg.credentials);
@@ -1983,6 +1953,14 @@ var EidClient = class {
1983
1953
  this.validator = new ResponseValidator(cfg.trust ?? {});
1984
1954
  this.rpName = cfg.credentials.rpName;
1985
1955
  }
1956
+ /** {@link SignApi.pdfDigest} — клиент талын PAdES-ийн DTBS-д гарын үсгийн session (PDF CA руу очихгүй). */
1957
+ signPdfDigest(input) {
1958
+ return this.sign.pdfDigest(input);
1959
+ }
1960
+ /** {@link SignApi.registerSignature} — `pades.complete()`-ийн CMS-ийг CA-д бүртгэнэ (`POST /pdf/signatures`). */
1961
+ registerSignature(input) {
1962
+ return this.sign.registerSignature(input);
1963
+ }
1986
1964
  };
1987
1965
 
1988
1966
  // src/devicelink.ts
@@ -2028,7 +2006,7 @@ function openOrShowQR(session, opts) {
2028
2006
  }
2029
2007
 
2030
2008
  // src/devicelinkv3.ts
2031
- var import_node_crypto6 = require("crypto");
2009
+ var import_node_crypto5 = require("crypto");
2032
2010
  var TYPES = ["QR", "Web2App", "App2App"];
2033
2011
  var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
2034
2012
  var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
@@ -2085,7 +2063,7 @@ function buildDeviceLink(p) {
2085
2063
  callback,
2086
2064
  unprotected
2087
2065
  ].join("|");
2088
- const authCode = (0, import_node_crypto6.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
2066
+ const authCode = (0, import_node_crypto5.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
2089
2067
  return `${unprotected}&authCode=${authCode}`;
2090
2068
  }
2091
2069
  function deviceLinkInput(s, deviceLinkType, lang) {
@@ -2129,7 +2107,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
2129
2107
  }
2130
2108
 
2131
2109
  // src/login.ts
2132
- var import_node_crypto7 = require("crypto");
2110
+ var import_node_crypto6 = require("crypto");
2133
2111
  function memoryLoginStore() {
2134
2112
  const m = /* @__PURE__ */ new Map();
2135
2113
  return {
@@ -2154,6 +2132,51 @@ function memoryLoginStore() {
2154
2132
  }
2155
2133
  };
2156
2134
  }
2135
+ var defaultRateLimit = Object.freeze({
2136
+ push: Object.freeze({ limit: 3, windowMs: 6e4 })
2137
+ });
2138
+ var RATE_KINDS = ["start", "push"];
2139
+ function windowLimiter(limit, windowMs) {
2140
+ const hits = /* @__PURE__ */ new Map();
2141
+ return (key) => {
2142
+ const now = Date.now();
2143
+ const h = (hits.get(key) ?? []).filter((t) => now - t < windowMs);
2144
+ if (hits.size > 1e4) hits.clear();
2145
+ if (h.length >= limit) {
2146
+ hits.set(key, h);
2147
+ return false;
2148
+ }
2149
+ h.push(now);
2150
+ hits.set(key, h);
2151
+ return true;
2152
+ };
2153
+ }
2154
+ function buildRateLimit(opt) {
2155
+ const extra = typeof opt === "function" ? opt : void 0;
2156
+ const user = opt && typeof opt === "object" ? opt : {};
2157
+ for (const k of Object.keys(user)) {
2158
+ if (!RATE_KINDS.includes(k)) throw new Error(`createEidLogin: rateLimit.${k} \u2014 \u0437\u04E9\u0432\u0445\u04E9\u043D start | push`);
2159
+ }
2160
+ const merged = { ...defaultRateLimit, ...user };
2161
+ const perKind = /* @__PURE__ */ new Map();
2162
+ for (const k of RATE_KINDS) {
2163
+ const r = merged[k];
2164
+ if (r === void 0 || r === false) continue;
2165
+ if (typeof r === "function") {
2166
+ perKind.set(k, r);
2167
+ continue;
2168
+ }
2169
+ if (!(Number.isInteger(r.limit) && r.limit > 0 && r.windowMs > 0)) {
2170
+ throw new Error(`createEidLogin: rateLimit.${k} \u2014 limit (\u0431\u04AF\u0445\u044D\u043B > 0), windowMs (> 0)`);
2171
+ }
2172
+ perKind.set(k, windowLimiter(r.limit, r.windowMs));
2173
+ }
2174
+ return async (key) => {
2175
+ const fn = perKind.get(key.slice(0, key.indexOf(":")));
2176
+ if (fn && !await fn(key)) return false;
2177
+ return extra ? extra(key) : true;
2178
+ };
2179
+ }
2157
2180
  var CALLBACK_PATH = "/auth/eid/callback";
2158
2181
  function forwardedClientIp(headers, trustProxy) {
2159
2182
  if (trustProxy === false || trustProxy === 0) return void 0;
@@ -2166,7 +2189,7 @@ function forwardedClientIp(headers, trustProxy) {
2166
2189
  return header(headers, "x-real-ip").trim() || void 0;
2167
2190
  }
2168
2191
  var ID_RE = /^(\p{L}{2}[0-9]{8}|[0-9]{8,14}|PNOMN-[0-9]{8,14})$/u;
2169
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
2192
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
2170
2193
  var B64URL_RE = /^[A-Za-z0-9_-]{43}$/;
2171
2194
  var IOS_BROWSERS = [
2172
2195
  [/CriOS/i, "googlechromes"],
@@ -2210,11 +2233,11 @@ function readCookie(h, name) {
2210
2233
  }
2211
2234
  return "";
2212
2235
  }
2213
- var sha256hex = (s) => (0, import_node_crypto7.createHash)("sha256").update(s).digest("hex");
2236
+ var sha256hex = (s) => (0, import_node_crypto6.createHash)("sha256").update(s).digest("hex");
2214
2237
  function safeEqualHex(a, b) {
2215
2238
  const x = Buffer.from(a, "hex");
2216
2239
  const y = Buffer.from(b, "hex");
2217
- return x.length === y.length && x.length > 0 && (0, import_node_crypto7.timingSafeEqual)(x, y);
2240
+ return x.length === y.length && x.length > 0 && (0, import_node_crypto6.timingSafeEqual)(x, y);
2218
2241
  }
2219
2242
  function toIdentity(sessionId, r, v, flowType) {
2220
2243
  const s = v.parsedSubject;
@@ -2253,17 +2276,7 @@ function createEidLogin(opts) {
2253
2276
  const successRedirect = opts.successRedirect ?? "/";
2254
2277
  const failureRedirect = opts.failureRedirect ?? "/";
2255
2278
  const onError = opts.onError ?? ((e) => console.error("eid-login:", e instanceof Error ? e.message : e));
2256
- const pushHits = /* @__PURE__ */ new Map();
2257
- const rateLimit = opts.rateLimit ?? ((key) => {
2258
- if (!key.startsWith("push:")) return true;
2259
- const now = Date.now();
2260
- const hits = (pushHits.get(key) ?? []).filter((t) => now - t < 6e4);
2261
- if (pushHits.size > 1e4) pushHits.clear();
2262
- if (hits.length >= 3) return false;
2263
- hits.push(now);
2264
- pushHits.set(key, hits);
2265
- return true;
2266
- });
2279
+ const rateLimit = buildRateLimit(opts.rateLimit);
2267
2280
  const displayText = (opts.displayText ?? `${client.rpName}-\u0434 \u043D\u044D\u0432\u0442\u0440\u044D\u0445`).slice(0, 60);
2268
2281
  const interactions = [{ type: "displayTextAndPIN", displayText60: displayText }];
2269
2282
  const json = (status2, body, extra = {}) => ({
@@ -2297,7 +2310,7 @@ function createEidLogin(opts) {
2297
2310
  const dot = raw.indexOf(".");
2298
2311
  const sid = raw.slice(0, dot);
2299
2312
  const binding = raw.slice(dot + 1);
2300
- if (dot < 0 || !UUID_RE.test(sid) || !B64URL_RE.test(binding)) return void 0;
2313
+ if (dot < 0 || !UUID_RE2.test(sid) || !B64URL_RE.test(binding)) return void 0;
2301
2314
  const tx = await store.get(sid);
2302
2315
  if (!tx || !safeEqualHex(tx.bindingHash, sha256hex(binding))) return void 0;
2303
2316
  if (tx.expiresAt <= Date.now()) {
@@ -2354,7 +2367,7 @@ function createEidLogin(opts) {
2354
2367
  });
2355
2368
  }
2356
2369
  }
2357
- const binding = (0, import_node_crypto7.randomBytes)(32).toString("base64url");
2370
+ const binding = (0, import_node_crypto6.randomBytes)(32).toString("base64url");
2358
2371
  const full = { ...tx, bindingHash: sha256hex(binding), expiresAt: Date.now() + ttl };
2359
2372
  await store.set(full.sessionId, full, ttl);
2360
2373
  const setCookie = `${cookieName}=${full.sessionId}.${binding}; ${cookieAttrs(Math.ceil(ttl / 1e3))}`;
@@ -2523,8 +2536,6 @@ function createEidLogin(opts) {
2523
2536
  CITIZEN_ISSUING_CA_SKI,
2524
2537
  CertChoiceInvalidError,
2525
2538
  DEFAULT_BASE_URL,
2526
- DEFAULT_PDF_MAX_BYTES,
2527
- DEFAULT_PDF_TIMEOUT_MS,
2528
2539
  DEFAULT_SIGN_SESSION_TTL_MS,
2529
2540
  DeviceLinkError,
2530
2541
  EidClient,
@@ -2535,7 +2546,7 @@ function createEidLogin(opts) {
2535
2546
  NoSigningCertificateError,
2536
2547
  ON_BEHALF_ERROR_CODES,
2537
2548
  OrganizationApi,
2538
- PdfApi,
2549
+ PDF_SIGNATURE_REGISTER_FIELDS,
2539
2550
  PersonApi,
2540
2551
  RETRYABLE_ERROR_CODES,
2541
2552
  RequestInvalidError,
@@ -2549,7 +2560,9 @@ function createEidLogin(opts) {
2549
2560
  acspV2Digest,
2550
2561
  buildAcspV2Payload,
2551
2562
  buildDeviceLink,
2563
+ buildRegisterBody,
2552
2564
  createEidLogin,
2565
+ defaultRateLimit,
2553
2566
  deviceLink,
2554
2567
  deviceLinkInput,
2555
2568
  forwardedClientIp,
@@ -2564,6 +2577,7 @@ function createEidLogin(opts) {
2564
2577
  personMatchesCertificate,
2565
2578
  qrDeviceLinkTicker,
2566
2579
  randomChallenge,
2580
+ registrationFromCompleted,
2567
2581
  resolveBaseUrl,
2568
2582
  sessionExpiry,
2569
2583
  sha256Base64,