@gerege-systems/eid-mongolia-sdk 0.14.2 → 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.js CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ deviceLink,
3
+ isMobileUserAgent,
4
+ openOrShowQR
5
+ } from "./chunk-REWNE7PO.js";
1
6
  import {
2
7
  ApiError,
3
8
  AuthenticationError,
@@ -13,12 +18,9 @@ import {
13
18
  ResponseTooLargeError,
14
19
  SessionFailedError,
15
20
  ValidationError,
16
- deviceLink,
17
- isMobileUserAgent,
18
21
  maskPath,
19
- openOrShowQR,
20
22
  parseRetryAfter
21
- } from "./chunk-DXVHOZO7.js";
23
+ } from "./chunk-TB2KQH3W.js";
22
24
 
23
25
  // src/http.ts
24
26
  var DEFAULT_TIMEOUT_MS = 15e3;
@@ -34,15 +36,6 @@ var Http = class {
34
36
  get(path, timeoutMs) {
35
37
  return this.json("GET", path, void 0, void 0, timeoutMs);
36
38
  }
37
- /** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
38
- postMultipart(path, form, timeoutMs) {
39
- return this.json("POST", path, form, void 0, timeoutMs);
40
- }
41
- /** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
42
- async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
43
- const res = await this.send("GET", path, void 0, void 0, timeoutMs);
44
- return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
45
- }
46
39
  async json(method, path, body, contentType, timeoutMs) {
47
40
  const res = await this.send(method, path, body, contentType, timeoutMs);
48
41
  const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
@@ -147,8 +140,6 @@ var RP_ROUTES = {
147
140
  signDeviceLinkDocument: { method: "POST", path: "/signature/device-link/document/{documentNumber}" },
148
141
  certChoiceDeviceLink: { method: "POST", path: "/signature/certificate-choice/device-link/anonymous" },
149
142
  certChoiceEtsi: { method: "POST", path: "/signature/certificate-choice/notification/etsi/{semanticsIdentifier}" },
150
- pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
151
- pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
152
143
  orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
153
144
  session: { method: "GET", path: "/session/{sessionId}" },
154
145
  personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
@@ -157,15 +148,29 @@ var RP_ROUTES = {
157
148
  personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
158
149
  personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
159
150
  tokenCertificate: { method: "POST", path: "/token/certificate" },
160
- tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
151
+ tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" },
152
+ pdfSignatures: { method: "POST", path: "/pdf/signatures" }
161
153
  };
162
154
  var RP_ROUTES_UNDISCOVERED = {
163
155
  tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
164
- tokenAuthVerify: { method: "POST", path: "/token/auth/verify" },
165
- pdfTokenPrepare: { method: "POST", path: "/pdf/token/prepare" },
166
- pdfTokenFinalize: { method: "POST", path: "/pdf/token/{signingID}/finalize" },
167
- pdfTokenDocument: { method: "GET", path: "/pdf/token/{signingID}/document" }
156
+ tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
168
157
  };
158
+ var PDF_SIGNATURE_REGISTER_FIELDS = [
159
+ "docID",
160
+ "revIndex",
161
+ "medium",
162
+ "sessionID",
163
+ "cms",
164
+ "fileName",
165
+ "inSha256",
166
+ "pageSha256",
167
+ "sigRevSha256",
168
+ "outSha256",
169
+ "priorCmsSha256",
170
+ "priorRevisionSha256",
171
+ "onBehalfOf",
172
+ "signerEtsi"
173
+ ];
169
174
  function routePath(route, ...args) {
170
175
  let i = 0;
171
176
  const out = route.path.replace(/\{[^}]*\}/g, () => {
@@ -328,6 +333,84 @@ var AuthApi = class {
328
333
  };
329
334
 
330
335
  // src/sign.ts
336
+ function registrationFromCompleted(done, extra) {
337
+ const out = {
338
+ docID: done.docId,
339
+ revIndex: done.revIndex,
340
+ cms: done.cms,
341
+ fileName: extra.fileName,
342
+ inSha256: done.inSha256,
343
+ sigRevSha256: done.sigRevSha256,
344
+ outSha256: done.outSha256
345
+ };
346
+ if (extra.medium) out.medium = extra.medium;
347
+ if (extra.sessionID) out.sessionID = extra.sessionID;
348
+ if (done.hasPage && done.pageSha256) out.pageSha256 = done.pageSha256;
349
+ if (done.priorCmsSha256.length) out.priorCmsSha256 = done.priorCmsSha256;
350
+ if (extra.priorRevisionSha256?.length) out.priorRevisionSha256 = extra.priorRevisionSha256;
351
+ if (extra.onBehalfOf) out.onBehalfOf = extra.onBehalfOf;
352
+ if (extra.signerEtsi) out.signerEtsi = extra.signerEtsi;
353
+ return out;
354
+ }
355
+ var HEX64 = /^[0-9a-f]{64}$/;
356
+ var MAX_CMS_BYTES = 28672;
357
+ function buildRegisterBody(input) {
358
+ const fail2 = (m) => {
359
+ throw new ValidationError(`registerSignature: ${m}`);
360
+ };
361
+ if (!UUID_RE.test(input.docID ?? "")) fail2("docID UUID \u0431\u0438\u0448");
362
+ if (!Number.isInteger(input.revIndex) || input.revIndex < 1 || input.revIndex > 16) fail2("revIndex 1\u201316");
363
+ const hex = (name, v) => {
364
+ const x = v?.trim().toLowerCase() ?? "";
365
+ if (!HEX64.test(x)) fail2(`${name} SHA-256 hex (64) \u0431\u0438\u0448`);
366
+ return x;
367
+ };
368
+ let cmsB64;
369
+ if (typeof input.cms === "string") {
370
+ const raw = Buffer.from(input.cms, "base64");
371
+ if (raw.length === 0 || raw.toString("base64") !== input.cms) fail2("cms \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442 base64 \u0431\u0438\u0448");
372
+ cmsB64 = input.cms;
373
+ if (raw.length > MAX_CMS_BYTES) fail2(`cms > ${MAX_CMS_BYTES} \u0431\u0430\u0439\u0442`);
374
+ } else {
375
+ if (!input.cms?.length) fail2("cms \u0445\u043E\u043E\u0441\u043E\u043D");
376
+ if (input.cms.length > MAX_CMS_BYTES) fail2(`cms > ${MAX_CMS_BYTES} \u0431\u0430\u0439\u0442`);
377
+ cmsB64 = Buffer.from(input.cms).toString("base64");
378
+ }
379
+ const fileName = input.fileName?.trim() ?? "";
380
+ 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`);
381
+ const sessionID = input.sessionID?.trim() ?? "";
382
+ const medium = input.medium ?? (sessionID ? "phone" : "token");
383
+ if (medium !== "phone" && medium !== "token") fail2("medium: phone | token");
384
+ if (medium === "phone" && !sessionID) fail2("phone-\u0434 sessionID \u0437\u0430\u0430\u0432\u0430\u043B");
385
+ const priorCms = (input.priorCmsSha256 ?? []).map((h, i) => hex(`priorCmsSha256[${i}]`, h));
386
+ const priorRev = (input.priorRevisionSha256 ?? []).map((h, i) => hex(`priorRevisionSha256[${i}]`, h));
387
+ if (priorCms.length + priorRev.length !== input.revIndex - 1) fail2("priorCmsSha256 + priorRevisionSha256-\u0438\u0439\u043D \u0442\u043E\u043E = revIndex \u2212 1");
388
+ if (input.revIndex === 1 && !input.pageSha256) fail2("revIndex 1-\u0434 pageSha256 \u0437\u0430\u0430\u0432\u0430\u043B");
389
+ const body = { docID: input.docID, revIndex: input.revIndex, medium };
390
+ if (sessionID) body.sessionID = sessionID;
391
+ body.cms = cmsB64;
392
+ body.fileName = fileName;
393
+ body.inSha256 = hex("inSha256", input.inSha256);
394
+ if (input.pageSha256) body.pageSha256 = hex("pageSha256", input.pageSha256);
395
+ body.sigRevSha256 = hex("sigRevSha256", input.sigRevSha256);
396
+ body.outSha256 = hex("outSha256", input.outSha256);
397
+ if (priorCms.length) body.priorCmsSha256 = priorCms;
398
+ if (priorRev.length) body.priorRevisionSha256 = priorRev;
399
+ if (input.onBehalfOf?.trim()) body.onBehalfOf = input.onBehalfOf.trim();
400
+ if (input.signerEtsi?.trim()) body.signerEtsi = input.signerEtsi.trim();
401
+ 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}`);
402
+ return body;
403
+ }
404
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
405
+ 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: ";
406
+ var PDF_MAX_FILE_NAME = 120;
407
+ function dtbsBase64(v) {
408
+ const raw = typeof v === "string" ? Buffer.from(v, "base64") : Buffer.from(v);
409
+ if (raw.length !== 32 || typeof v === "string" && raw.toString("base64") !== v) {
410
+ throw new ValidationError("pdfDigest: dtbs \u043D\u044C 32 \u0431\u0430\u0439\u0442 (SHA-256) \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
411
+ }
412
+ return raw.toString("base64");
413
+ }
331
414
  function rec2(v) {
332
415
  return v && typeof v === "object" ? v : {};
333
416
  }
@@ -412,6 +495,77 @@ var SignApi = class {
412
495
  const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
413
496
  return this.notification(routePath(RP_ROUTES.signNotificationDocument, documentNumber), body, sign);
414
497
  }
498
+ /**
499
+ * Клиент талын PAdES (`@gerege-systems/eid-mongolia-sdk/pades`): `prepare()`-ийн DTBS-д иргэнээр гарын үсэг зуруулна.
500
+ * PDF CA руу ОЧИХГҮЙ — зөвхөн 32 байт. CA-ийн өөрийн PAdES session-тэй ижил: `RAW_DIGEST_SIGNATURE`,
501
+ * `sha256WithRSAEncryption` (CMS-д PKCS#1 v1.5; legacy ECDSA иргэнд сервер `ecdsa-with-SHA256`), `displayTextAndPIN`
502
+ * «Баримтад гарын үсэг зурах: <fileName>» (60 тэмдэгтэд тайрна).
503
+ *
504
+ * Дараа нь `session.waitForResult(sessionId)` → `validator.validateSign(result, s.digest)` → `signature.value`-г
505
+ * (base64 → байт) `complete(state, signature)`-д.
506
+ */
507
+ async pdfDigest(input) {
508
+ const digest = dtbsBase64(input.dtbs);
509
+ const fileName = input.fileName?.trim() ?? "";
510
+ if (!fileName || Array.from(fileName).length > PDF_MAX_FILE_NAME) {
511
+ throw new ValidationError(`pdfDigest: fileName \u0437\u0430\u0430\u0432\u0430\u043B, \u2264 ${PDF_MAX_FILE_NAME} \u0442\u044D\u043C\u0434\u044D\u0433\u0442`);
512
+ }
513
+ const signer = input.signer;
514
+ const etsi = signer?.etsi?.trim();
515
+ const doc = signer?.documentNumber?.trim();
516
+ 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");
517
+ const deviceLink2 = input.flow === "device-link";
518
+ const display = Array.from(PDF_DISPLAY_TEXT + fileName).slice(0, 60).join("");
519
+ const interactions = interactionsToBase64([{ type: "displayTextAndPIN", displayText60: display }]);
520
+ const body = {
521
+ relyingPartyUUID: this.creds.rpUUID,
522
+ relyingPartyName: this.creds.rpName,
523
+ certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
524
+ signatureProtocol: "RAW_DIGEST_SIGNATURE",
525
+ signatureProtocolParameters: {
526
+ digest,
527
+ signatureAlgorithm: "sha256WithRSAEncryption",
528
+ signatureAlgorithmParameters: { hashAlgorithm: "SHA-256" }
529
+ },
530
+ interactions,
531
+ fileName
532
+ };
533
+ if (input.initialCallbackUrl?.trim()) body.initialCallbackUrl = input.initialCallbackUrl.trim();
534
+ if (input.onBehalfOf?.trim()) body.onBehalfOf = input.onBehalfOf.trim();
535
+ const route = deviceLink2 ? etsi ? RP_ROUTES.signDeviceLinkEtsi : RP_ROUTES.signDeviceLinkDocument : etsi ? RP_ROUTES.signNotificationEtsi : RP_ROUTES.signNotificationDocument;
536
+ const r = rec2(await this.http.post(routePath(route, etsi ?? doc), body));
537
+ const receivedAt = Date.now();
538
+ const vc = typeof r.vc === "object" && r.vc !== null ? rec2(r.vc).value : r.vc;
539
+ let dl = null;
540
+ if (deviceLink2) {
541
+ if (!r.sessionToken || !r.sessionSecret || !r.deviceLinkBase) {
542
+ throw new ValidationError("pdfDigest: device-link \u0445\u0430\u0440\u0438\u0443\u043D\u0434 sessionToken/sessionSecret/deviceLinkBase \u0430\u043B\u0433\u0430");
543
+ }
544
+ dl = { sessionToken: String(r.sessionToken), sessionSecret: String(r.sessionSecret), deviceLinkBase: String(r.deviceLinkBase) };
545
+ }
546
+ return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, digest, interactions, deviceLink: dl, receivedAt };
547
+ }
548
+ /**
549
+ * `POST /pdf/signatures` — `pades.complete()`-ийн дараа CMS-ийг CA-д бүртгэнэ (`/verify/{docID}`, иргэний түүх). PDF
550
+ * илгээгдэхгүй. SIGN эрх. CA CMS-ийг шалгана: 04-ийн SIGN cert, session digest = SHA-256(signedAttrs), claimedRole ↔
551
+ * төлөөлөл. Алдаа: 403 `REPRESENTATION_REVOKED`, 409 `CLAIMED_ROLE_MISMATCH` / `REGISTRY_CONFLICT` /
552
+ * `DOC_ID_CONFLICT` / `PDF_FOREIGN_SIGNATURE` (`ApiError.code`). Бүртгэлгүй файл ч Adobe/EU DSS-д хүчинтэй — зөвхөн
553
+ * `/verify` «бүртгэлгүй» гэнэ.
554
+ */
555
+ async registerSignature(input) {
556
+ return this.registerBody(buildRegisterBody(input));
557
+ }
558
+ async registerBody(body) {
559
+ const raw = await this.http.post(routePath(RP_ROUTES.pdfSignatures), body);
560
+ const r = rec2(raw);
561
+ return {
562
+ docID: String(r.docID ?? body.docID),
563
+ revIndex: Number(r.revIndex ?? 0),
564
+ registeredAt: typeof r.registeredAt === "string" ? r.registeredAt : "",
565
+ verifyUrl: String(r.verifyUrl ?? ""),
566
+ raw
567
+ };
568
+ }
415
569
  /** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
416
570
  async textByEtsi(id, text, interactions, opts) {
417
571
  return this.digestByEtsi(id, sha256Base64(text), interactions, opts);
@@ -527,7 +681,6 @@ function parseSessionResult(raw) {
527
681
  signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
528
682
  interactionTypeUsed: str(r.interactionTypeUsed),
529
683
  onBehalfOf: parseOrg(r.onBehalfOf),
530
- pdf: parsePdf(r.pdf),
531
684
  person: parsePerson(r.person),
532
685
  tokenCertificate: parseTokenCert(r.tokenCertificate),
533
686
  raw
@@ -565,28 +718,6 @@ function personMatchesCertificate(person, subject) {
565
718
  const serial = typeof subject === "string" ? subject : subject?.serialNumber;
566
719
  return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
567
720
  }
568
- function parsePdf(raw) {
569
- const p = asRecord(raw);
570
- const docID = str(p.docID);
571
- if (!docID) return null;
572
- const out = { docID, documentStatus: String(p.documentStatus ?? "") };
573
- if (p.errorCode != null) out.errorCode = String(p.errorCode);
574
- if (p.outSha256 != null) out.outSha256 = String(p.outSha256);
575
- if (p.size != null) out.size = Number(p.size);
576
- if (p.expiresAt != null) out.expiresAt = String(p.expiresAt);
577
- if (p.signatureLevel != null) out.signatureLevel = String(p.signatureLevel);
578
- const sg = asRecord(p.signer);
579
- if (sg.etsi != null) {
580
- out.signer = { etsi: String(sg.etsi), certSerial: String(sg.certSerial ?? "") };
581
- if (sg.onBehalfOf) out.signer.onBehalfOf = String(sg.onBehalfOf);
582
- if (sg.claimedRole) out.signer.claimedRole = String(sg.claimedRole);
583
- }
584
- const v = asRecord(p.validation);
585
- if (v.indication != null) {
586
- out.validation = { indication: String(v.indication), ltv: v.ltv === true, signatures: Number(v.signatures ?? 0) };
587
- }
588
- return out;
589
- }
590
721
  function parseSigParams(raw) {
591
722
  const p = asRecord(raw);
592
723
  const hash = str(p.hashAlgorithm);
@@ -645,160 +776,6 @@ var SessionApi = class {
645
776
  }
646
777
  };
647
778
 
648
- // src/pdf.ts
649
- import { createHash as createHash2 } from "crypto";
650
- var DEFAULT_PDF_MAX_BYTES = 64 << 20;
651
- var DEFAULT_PDF_TIMEOUT_MS = 8e4;
652
- function rec3(v) {
653
- return v && typeof v === "object" ? v : {};
654
- }
655
- var PdfApi = class {
656
- constructor(http, creds, defaults) {
657
- this.http = http;
658
- this.creds = creds;
659
- this.defaults = defaults;
660
- }
661
- http;
662
- creds;
663
- defaults;
664
- /**
665
- * POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. Түр алдаа: `ApiError` 429
666
- * PDF_PREPARE_BUSY, 503 LTV_UNAVAILABLE / TSA_UNAVAILABLE — `retryAfterMs`-ийн дараа дахин (`retryable`).
667
- */
668
- async prepare(input) {
669
- const request = {
670
- relyingPartyUUID: this.creds.rpUUID,
671
- relyingPartyName: this.creds.rpName,
672
- certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
673
- signer: input.signer,
674
- flow: input.flow ?? "notification",
675
- fileName: input.fileName
676
- };
677
- if (input.initialCallbackUrl?.trim()) request.initialCallbackUrl = input.initialCallbackUrl.trim();
678
- if (input.docID?.trim()) request.docID = input.docID.trim();
679
- if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
680
- const form = new FormData();
681
- form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
682
- form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
683
- const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
684
- const receivedAt = Date.now();
685
- const vc = rec3(r.vc).value;
686
- const dl = rec3(r.deviceLink);
687
- const digest = typeof r.digest === "string" && r.digest ? r.digest : typeof dl.digest === "string" && dl.digest ? dl.digest : void 0;
688
- return {
689
- sessionId: String(r.sessionID),
690
- docId: String(r.docID),
691
- vc: vc != null ? String(vc) : null,
692
- ...digest !== void 0 ? { digest } : {},
693
- deviceLink: dl.sessionToken ? {
694
- sessionToken: String(dl.sessionToken),
695
- sessionSecret: String(dl.sessionSecret),
696
- deviceLinkBase: String(dl.deviceLinkBase),
697
- digest: String(dl.digest),
698
- interactions: String(dl.interactions)
699
- } : null,
700
- receivedAt
701
- };
702
- }
703
- /**
704
- * GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
705
- * session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`. Баримт
706
- * бэлэн болоогүй бол `ApiError` 503 `.code` DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE — `retryAfterMs`-ийн
707
- * дараа дахин татна (`retryable`).
708
- */
709
- document(sessionId) {
710
- return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.timeout, this.maxBytes);
711
- }
712
- get timeout() {
713
- return this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS;
714
- }
715
- get maxBytes() {
716
- return this.defaults.maxBytes ?? DEFAULT_PDF_MAX_BYTES;
717
- }
718
- /**
719
- * USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
720
- * (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
721
- * зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
722
- * LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
723
- * TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
724
- */
725
- async tokenPrepare(input) {
726
- if (!input.pdf?.length || !input.certificate?.length || !input.signerEtsi?.trim() || !input.fileName?.trim()) {
727
- throw new Error("eid: pdf token: pdf, certificate, signerEtsi, fileName \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
728
- }
729
- const request = {
730
- relyingPartyUUID: this.creds.rpUUID,
731
- relyingPartyName: this.creds.rpName,
732
- signer: { etsi: input.signerEtsi.trim() },
733
- certificate: Buffer.from(input.certificate).toString("base64"),
734
- fileName: input.fileName
735
- };
736
- if (input.docID?.trim()) request.docID = input.docID.trim();
737
- if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
738
- const form = new FormData();
739
- form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
740
- form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
741
- const raw = await this.http.postMultipart(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenPrepare), form, this.timeout);
742
- const r = rec3(raw);
743
- const hashToSign = new Uint8Array(Buffer.from(String(r.hashToSign ?? ""), "base64"));
744
- if (!r.signingID || hashToSign.length !== 32) {
745
- throw new ValidationError("pdf token prepare: signingID / hashToSign (32 \u0431\u0430\u0439\u0442) \u0431\u0443\u0440\u0443\u0443");
746
- }
747
- return {
748
- signingId: String(r.signingID),
749
- docId: String(r.docID ?? ""),
750
- hashToSign,
751
- hashAlgorithm: String(r.hashAlgorithm ?? ""),
752
- signatureAlgorithm: String(r.signatureAlgorithm ?? ""),
753
- signatureFormat: String(r.signatureFormat ?? ""),
754
- expiresAt: String(r.expiresAt ?? ""),
755
- raw
756
- };
757
- }
758
- /**
759
- * POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
760
- * Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
761
- * Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
762
- * TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
763
- */
764
- async tokenFinalize(signingId, signature) {
765
- 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)");
766
- const r = await this.http.post(
767
- routePath(RP_ROUTES_UNDISCOVERED.pdfTokenFinalize, signingId),
768
- { signature: Buffer.from(signature).toString("base64") },
769
- this.timeout
770
- );
771
- return r;
772
- }
773
- /**
774
- * GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
775
- * хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
776
- */
777
- tokenDocument(signingId) {
778
- return this.http.getBytes(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenDocument, signingId), this.timeout, this.maxBytes);
779
- }
780
- /**
781
- * {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
782
- * `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
783
- *
784
- * @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
785
- */
786
- async documentVerified(sessionId, expected) {
787
- let want;
788
- if (typeof expected === "string") {
789
- want = expected;
790
- } else {
791
- if (expected.documentStatus !== "READY") throw new ValidationError(`pdf.documentStatus ${expected.documentStatus} \u2260 READY`);
792
- want = expected.outSha256;
793
- }
794
- 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");
795
- const bytes = await this.document(sessionId);
796
- const got = createHash2("sha256").update(bytes).digest("hex");
797
- 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");
798
- return bytes;
799
- }
800
- };
801
-
802
779
  // src/organization.ts
803
780
  var OrganizationApi = class {
804
781
  constructor(http) {
@@ -814,7 +791,7 @@ var OrganizationApi = class {
814
791
  };
815
792
 
816
793
  // src/person.ts
817
- function rec4(v) {
794
+ function rec3(v) {
818
795
  return v && typeof v === "object" ? v : {};
819
796
  }
820
797
  var PersonApi = class {
@@ -824,7 +801,7 @@ var PersonApi = class {
824
801
  http;
825
802
  async get(path) {
826
803
  const raw = await this.http.get(path);
827
- return { ...rec4(raw), raw };
804
+ return { ...rec3(raw), raw };
828
805
  }
829
806
  /** Иргэний бүх гэрчилгээ + статусын тоолол. */
830
807
  certificates(personEtsi) {
@@ -854,7 +831,7 @@ var PersonApi = class {
854
831
  };
855
832
 
856
833
  // src/token.ts
857
- import { X509Certificate as X509Certificate2, createHash as createHash3, createPublicKey, timingSafeEqual } from "crypto";
834
+ import { X509Certificate as X509Certificate2, createHash as createHash2, createPublicKey, timingSafeEqual } from "crypto";
858
835
 
859
836
  // src/subject.ts
860
837
  import { X509Certificate } from "crypto";
@@ -995,12 +972,12 @@ function certExtensions(der) {
995
972
  }
996
973
 
997
974
  // src/token.ts
998
- function rec5(v) {
975
+ function rec4(v) {
999
976
  return v && typeof v === "object" ? v : {};
1000
977
  }
1001
978
  function hexSha256Equal(hex, data) {
1002
979
  if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
1003
- return timingSafeEqual(createHash3("sha256").update(data).digest(), Buffer.from(hex, "hex"));
980
+ return timingSafeEqual(createHash2("sha256").update(data).digest(), Buffer.from(hex, "hex"));
1004
981
  }
1005
982
  var TOKEN_RESULT_TIMEOUT_MS = 3e4;
1006
983
  var TokenApi = class {
@@ -1029,11 +1006,11 @@ var TokenApi = class {
1029
1006
  flow: "notification"
1030
1007
  };
1031
1008
  if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
1032
- const r = rec5(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
1009
+ const r = rec4(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
1033
1010
  if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
1034
1011
  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");
1035
1012
  }
1036
- const vc = rec5(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
1013
+ const vc = rec4(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
1037
1014
  return {
1038
1015
  sessionId: String(r.sessionID ?? ""),
1039
1016
  vc: vc != null ? String(vc) : null,
@@ -1048,7 +1025,7 @@ var TokenApi = class {
1048
1025
  */
1049
1026
  async certificateResult(sessionId) {
1050
1027
  const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
1051
- const r = rec5(raw);
1028
+ const r = rec4(raw);
1052
1029
  const item = (v) => v && typeof v === "object" ? v : null;
1053
1030
  return {
1054
1031
  status: String(r.status ?? ""),
@@ -1064,7 +1041,7 @@ var TokenApi = class {
1064
1041
  }
1065
1042
  /** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
1066
1043
  async authChallenge() {
1067
- const r = rec5(
1044
+ const r = rec4(
1068
1045
  await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
1069
1046
  relyingPartyUUID: this.creds.rpUUID,
1070
1047
  relyingPartyName: this.creds.rpName
@@ -1095,12 +1072,12 @@ var TokenApi = class {
1095
1072
  certificate: Buffer.from(certDer).toString("base64"),
1096
1073
  signature: Buffer.from(signature).toString("base64")
1097
1074
  });
1098
- const r = rec5(raw);
1099
- if (rec5(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1100
- const id = rec5(r.identity);
1075
+ const r = rec4(raw);
1076
+ if (rec4(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
1077
+ const id = rec4(r.identity);
1101
1078
  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");
1102
- const org = rec5(r.organization);
1103
- const cert = rec5(r.cert);
1079
+ const org = rec4(r.organization);
1080
+ const cert = rec4(r.cert);
1104
1081
  return {
1105
1082
  identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
1106
1083
  serialHex: String(cert.serialHex ?? ""),
@@ -1148,13 +1125,13 @@ function tokenAuthDigest(rpUUID, challengeB64) {
1148
1125
  }
1149
1126
  const ch = Buffer.from(challengeB64, "base64");
1150
1127
  if (ch.length !== 32 || ch.toString("base64") !== challengeB64) throw new Error("token auth: challenge 32 \u0431\u0430\u0439\u0442 base64 \u0431\u0438\u0448");
1151
- return createHash3("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1128
+ return createHash2("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
1152
1129
  }
1153
1130
  function tokenLimitOf(err) {
1154
1131
  if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
1155
1132
  let b;
1156
1133
  try {
1157
- b = rec5(JSON.parse(err.body));
1134
+ b = rec4(JSON.parse(err.body));
1158
1135
  } catch {
1159
1136
  return void 0;
1160
1137
  }
@@ -1167,7 +1144,7 @@ function tokenLimitOf(err) {
1167
1144
  }
1168
1145
 
1169
1146
  // src/validator.ts
1170
- import { X509Certificate as X509Certificate3, constants, createHash as createHash4, publicDecrypt, verify } from "crypto";
1147
+ import { X509Certificate as X509Certificate3, constants, createHash as createHash3, publicDecrypt, verify } from "crypto";
1171
1148
  var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
1172
1149
  var ResponseValidator = class {
1173
1150
  anchors;
@@ -1497,7 +1474,7 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
1497
1474
  return fromResponse;
1498
1475
  }
1499
1476
  function userChallengeOf(verifier) {
1500
- return createHash4("sha256").update(verifier, "utf8").digest("base64url");
1477
+ return createHash3("sha256").update(verifier, "utf8").digest("base64url");
1501
1478
  }
1502
1479
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
1503
1480
  const sig = Buffer.from(sigB64, "base64");
@@ -1599,7 +1576,7 @@ function mgf1(hash, seed, length) {
1599
1576
  for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
1600
1577
  const c = Buffer.alloc(4);
1601
1578
  c.writeUInt32BE(i, 0);
1602
- out.push(createHash4(nodeHash(hash)).update(seed).update(c).digest());
1579
+ out.push(createHash3(nodeHash(hash)).update(seed).update(c).digest());
1603
1580
  }
1604
1581
  return Buffer.concat(out).subarray(0, length);
1605
1582
  }
@@ -1623,7 +1600,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
1623
1600
  const psLen = emLen - hLen - sLen - 2;
1624
1601
  if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
1625
1602
  const salt = db.subarray(psLen + 1);
1626
- const h2 = createHash4(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1603
+ const h2 = createHash3(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
1627
1604
  return h.equals(h2);
1628
1605
  }
1629
1606
  function keyUsageBits(cert) {
@@ -1757,7 +1734,6 @@ function resolveBaseUrl(baseUrl) {
1757
1734
  var EidClient = class {
1758
1735
  auth;
1759
1736
  sign;
1760
- pdf;
1761
1737
  organization;
1762
1738
  person;
1763
1739
  token;
@@ -1779,11 +1755,6 @@ var EidClient = class {
1779
1755
  const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
1780
1756
  this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
1781
1757
  this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level, sessionTtlMs: cfg.signSessionTtlMs });
1782
- this.pdf = new PdfApi(http, cfg.credentials, {
1783
- certificateLevel: level,
1784
- timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
1785
- maxBytes: cfg.pdfMaxBytes && cfg.pdfMaxBytes > 0 ? cfg.pdfMaxBytes : DEFAULT_PDF_MAX_BYTES
1786
- });
1787
1758
  this.organization = new OrganizationApi(http);
1788
1759
  this.person = new PersonApi(http);
1789
1760
  this.token = new TokenApi(http, cfg.credentials);
@@ -1791,6 +1762,14 @@ var EidClient = class {
1791
1762
  this.validator = new ResponseValidator(cfg.trust ?? {});
1792
1763
  this.rpName = cfg.credentials.rpName;
1793
1764
  }
1765
+ /** {@link SignApi.pdfDigest} — клиент талын PAdES-ийн DTBS-д гарын үсгийн session (PDF CA руу очихгүй). */
1766
+ signPdfDigest(input) {
1767
+ return this.sign.pdfDigest(input);
1768
+ }
1769
+ /** {@link SignApi.registerSignature} — `pades.complete()`-ийн CMS-ийг CA-д бүртгэнэ (`POST /pdf/signatures`). */
1770
+ registerSignature(input) {
1771
+ return this.sign.registerSignature(input);
1772
+ }
1794
1773
  };
1795
1774
 
1796
1775
  // src/devicelinkv3.ts
@@ -1895,7 +1874,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
1895
1874
  }
1896
1875
 
1897
1876
  // src/login.ts
1898
- import { createHash as createHash5, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
1877
+ import { createHash as createHash4, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
1899
1878
  function memoryLoginStore() {
1900
1879
  const m = /* @__PURE__ */ new Map();
1901
1880
  return {
@@ -1932,7 +1911,7 @@ function forwardedClientIp(headers, trustProxy) {
1932
1911
  return header(headers, "x-real-ip").trim() || void 0;
1933
1912
  }
1934
1913
  var ID_RE = /^(\p{L}{2}[0-9]{8}|[0-9]{8,14}|PNOMN-[0-9]{8,14})$/u;
1935
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1914
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1936
1915
  var B64URL_RE = /^[A-Za-z0-9_-]{43}$/;
1937
1916
  var IOS_BROWSERS = [
1938
1917
  [/CriOS/i, "googlechromes"],
@@ -1976,7 +1955,7 @@ function readCookie(h, name) {
1976
1955
  }
1977
1956
  return "";
1978
1957
  }
1979
- var sha256hex = (s) => createHash5("sha256").update(s).digest("hex");
1958
+ var sha256hex = (s) => createHash4("sha256").update(s).digest("hex");
1980
1959
  function safeEqualHex(a, b) {
1981
1960
  const x = Buffer.from(a, "hex");
1982
1961
  const y = Buffer.from(b, "hex");
@@ -2063,7 +2042,7 @@ function createEidLogin(opts) {
2063
2042
  const dot = raw.indexOf(".");
2064
2043
  const sid = raw.slice(0, dot);
2065
2044
  const binding = raw.slice(dot + 1);
2066
- if (dot < 0 || !UUID_RE.test(sid) || !B64URL_RE.test(binding)) return void 0;
2045
+ if (dot < 0 || !UUID_RE2.test(sid) || !B64URL_RE.test(binding)) return void 0;
2067
2046
  const tx = await store.get(sid);
2068
2047
  if (!tx || !safeEqualHex(tx.bindingHash, sha256hex(binding))) return void 0;
2069
2048
  if (tx.expiresAt <= Date.now()) {
@@ -2288,8 +2267,6 @@ export {
2288
2267
  CITIZEN_ISSUING_CA_SKI,
2289
2268
  CertChoiceInvalidError,
2290
2269
  DEFAULT_BASE_URL,
2291
- DEFAULT_PDF_MAX_BYTES,
2292
- DEFAULT_PDF_TIMEOUT_MS,
2293
2270
  DEFAULT_SIGN_SESSION_TTL_MS,
2294
2271
  DeviceLinkError,
2295
2272
  EidClient,
@@ -2300,7 +2277,7 @@ export {
2300
2277
  NoSigningCertificateError,
2301
2278
  ON_BEHALF_ERROR_CODES,
2302
2279
  OrganizationApi,
2303
- PdfApi,
2280
+ PDF_SIGNATURE_REGISTER_FIELDS,
2304
2281
  PersonApi,
2305
2282
  RETRYABLE_ERROR_CODES,
2306
2283
  RequestInvalidError,
@@ -2314,6 +2291,7 @@ export {
2314
2291
  acspV2Digest,
2315
2292
  buildAcspV2Payload,
2316
2293
  buildDeviceLink,
2294
+ buildRegisterBody,
2317
2295
  createEidLogin,
2318
2296
  deviceLink,
2319
2297
  deviceLinkInput,
@@ -2329,6 +2307,7 @@ export {
2329
2307
  personMatchesCertificate,
2330
2308
  qrDeviceLinkTicker,
2331
2309
  randomChallenge,
2310
+ registrationFromCompleted,
2332
2311
  resolveBaseUrl,
2333
2312
  sessionExpiry,
2334
2313
  sha256Base64,