@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/README.md +127 -42
- package/dist/{browser-CSZjH9Xs.d.cts → browser-8k_VDSP8.d.cts} +8 -183
- package/dist/{browser-CSZjH9Xs.d.ts → browser-BZ3hvv6-.d.ts} +8 -183
- package/dist/browser.d.cts +2 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +5 -3
- package/dist/chunk-REWNE7PO.js +51 -0
- package/dist/{chunk-DXVHOZO7.js → chunk-TB2KQH3W.js} +1 -46
- package/dist/errors-Z7SgLVTr.d.cts +149 -0
- package/dist/errors-Z7SgLVTr.d.ts +149 -0
- package/dist/index.cjs +222 -245
- package/dist/index.d.cts +147 -156
- package/dist/index.d.ts +147 -156
- package/dist/index.js +209 -230
- package/dist/pades.cjs +3520 -0
- package/dist/pades.d.cts +128 -0
- package/dist/pades.d.ts +128 -0
- package/dist/pades.js +3484 -0
- package/package.json +16 -6
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
|
-
|
|
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,6 +50,7 @@ __export(index_exports, {
|
|
|
52
50
|
acspV2Digest: () => acspV2Digest,
|
|
53
51
|
buildAcspV2Payload: () => buildAcspV2Payload,
|
|
54
52
|
buildDeviceLink: () => buildDeviceLink,
|
|
53
|
+
buildRegisterBody: () => buildRegisterBody,
|
|
55
54
|
createEidLogin: () => createEidLogin,
|
|
56
55
|
deviceLink: () => deviceLink,
|
|
57
56
|
deviceLinkInput: () => deviceLinkInput,
|
|
@@ -67,6 +66,7 @@ __export(index_exports, {
|
|
|
67
66
|
personMatchesCertificate: () => personMatchesCertificate,
|
|
68
67
|
qrDeviceLinkTicker: () => qrDeviceLinkTicker,
|
|
69
68
|
randomChallenge: () => randomChallenge,
|
|
69
|
+
registrationFromCompleted: () => registrationFromCompleted,
|
|
70
70
|
resolveBaseUrl: () => resolveBaseUrl,
|
|
71
71
|
sessionExpiry: () => sessionExpiry,
|
|
72
72
|
sha256Base64: () => sha256Base64,
|
|
@@ -226,15 +226,6 @@ var Http = class {
|
|
|
226
226
|
get(path, timeoutMs) {
|
|
227
227
|
return this.json("GET", path, void 0, void 0, timeoutMs);
|
|
228
228
|
}
|
|
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
229
|
async json(method, path, body, contentType, timeoutMs) {
|
|
239
230
|
const res = await this.send(method, path, body, contentType, timeoutMs);
|
|
240
231
|
const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
|
|
@@ -339,8 +330,6 @@ var RP_ROUTES = {
|
|
|
339
330
|
signDeviceLinkDocument: { method: "POST", path: "/signature/device-link/document/{documentNumber}" },
|
|
340
331
|
certChoiceDeviceLink: { method: "POST", path: "/signature/certificate-choice/device-link/anonymous" },
|
|
341
332
|
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
333
|
orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
|
|
345
334
|
session: { method: "GET", path: "/session/{sessionId}" },
|
|
346
335
|
personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
|
|
@@ -349,15 +338,29 @@ var RP_ROUTES = {
|
|
|
349
338
|
personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
|
|
350
339
|
personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
|
|
351
340
|
tokenCertificate: { method: "POST", path: "/token/certificate" },
|
|
352
|
-
tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
|
|
341
|
+
tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" },
|
|
342
|
+
pdfSignatures: { method: "POST", path: "/pdf/signatures" }
|
|
353
343
|
};
|
|
354
344
|
var RP_ROUTES_UNDISCOVERED = {
|
|
355
345
|
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" }
|
|
346
|
+
tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
|
|
360
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
|
+
];
|
|
361
364
|
function routePath(route, ...args) {
|
|
362
365
|
let i = 0;
|
|
363
366
|
const out = route.path.replace(/\{[^}]*\}/g, () => {
|
|
@@ -520,6 +523,84 @@ var AuthApi = class {
|
|
|
520
523
|
};
|
|
521
524
|
|
|
522
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
|
+
}
|
|
523
604
|
function rec2(v) {
|
|
524
605
|
return v && typeof v === "object" ? v : {};
|
|
525
606
|
}
|
|
@@ -604,6 +685,77 @@ var SignApi = class {
|
|
|
604
685
|
const { body, sign } = this.body(digestB64, interactions, opts, "rsassa-pss");
|
|
605
686
|
return this.notification(routePath(RP_ROUTES.signNotificationDocument, documentNumber), body, sign);
|
|
606
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
|
+
}
|
|
607
759
|
/** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
|
|
608
760
|
async textByEtsi(id, text, interactions, opts) {
|
|
609
761
|
return this.digestByEtsi(id, sha256Base64(text), interactions, opts);
|
|
@@ -719,7 +871,6 @@ function parseSessionResult(raw) {
|
|
|
719
871
|
signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
|
|
720
872
|
interactionTypeUsed: str(r.interactionTypeUsed),
|
|
721
873
|
onBehalfOf: parseOrg(r.onBehalfOf),
|
|
722
|
-
pdf: parsePdf(r.pdf),
|
|
723
874
|
person: parsePerson(r.person),
|
|
724
875
|
tokenCertificate: parseTokenCert(r.tokenCertificate),
|
|
725
876
|
raw
|
|
@@ -757,28 +908,6 @@ function personMatchesCertificate(person, subject) {
|
|
|
757
908
|
const serial = typeof subject === "string" ? subject : subject?.serialNumber;
|
|
758
909
|
return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
|
|
759
910
|
}
|
|
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
911
|
function parseSigParams(raw) {
|
|
783
912
|
const p = asRecord(raw);
|
|
784
913
|
const hash = str(p.hashAlgorithm);
|
|
@@ -837,160 +966,6 @@ var SessionApi = class {
|
|
|
837
966
|
}
|
|
838
967
|
};
|
|
839
968
|
|
|
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
969
|
// src/organization.ts
|
|
995
970
|
var OrganizationApi = class {
|
|
996
971
|
constructor(http) {
|
|
@@ -1006,7 +981,7 @@ var OrganizationApi = class {
|
|
|
1006
981
|
};
|
|
1007
982
|
|
|
1008
983
|
// src/person.ts
|
|
1009
|
-
function
|
|
984
|
+
function rec3(v) {
|
|
1010
985
|
return v && typeof v === "object" ? v : {};
|
|
1011
986
|
}
|
|
1012
987
|
var PersonApi = class {
|
|
@@ -1016,7 +991,7 @@ var PersonApi = class {
|
|
|
1016
991
|
http;
|
|
1017
992
|
async get(path) {
|
|
1018
993
|
const raw = await this.http.get(path);
|
|
1019
|
-
return { ...
|
|
994
|
+
return { ...rec3(raw), raw };
|
|
1020
995
|
}
|
|
1021
996
|
/** Иргэний бүх гэрчилгээ + статусын тоолол. */
|
|
1022
997
|
certificates(personEtsi) {
|
|
@@ -1046,10 +1021,10 @@ var PersonApi = class {
|
|
|
1046
1021
|
};
|
|
1047
1022
|
|
|
1048
1023
|
// src/token.ts
|
|
1049
|
-
var
|
|
1024
|
+
var import_node_crypto3 = require("crypto");
|
|
1050
1025
|
|
|
1051
1026
|
// src/subject.ts
|
|
1052
|
-
var
|
|
1027
|
+
var import_node_crypto2 = require("crypto");
|
|
1053
1028
|
var OIDS = {
|
|
1054
1029
|
"2.5.4.42": "givenName",
|
|
1055
1030
|
"2.5.4.4": "surname",
|
|
@@ -1114,7 +1089,7 @@ function stringOf(buf, t) {
|
|
|
1114
1089
|
}
|
|
1115
1090
|
}
|
|
1116
1091
|
function parseSubject(cert) {
|
|
1117
|
-
const der = cert instanceof
|
|
1092
|
+
const der = cert instanceof import_node_crypto2.X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
|
|
1118
1093
|
const top = tlv(der, 0, der.length);
|
|
1119
1094
|
if (top.tag !== 48) throw new ValidationError("subject: Certificate SEQUENCE \u0431\u0438\u0448");
|
|
1120
1095
|
const tbs = tlv(der, top.start, top.end);
|
|
@@ -1187,12 +1162,12 @@ function certExtensions(der) {
|
|
|
1187
1162
|
}
|
|
1188
1163
|
|
|
1189
1164
|
// src/token.ts
|
|
1190
|
-
function
|
|
1165
|
+
function rec4(v) {
|
|
1191
1166
|
return v && typeof v === "object" ? v : {};
|
|
1192
1167
|
}
|
|
1193
1168
|
function hexSha256Equal(hex, data) {
|
|
1194
1169
|
if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
|
|
1195
|
-
return (0,
|
|
1170
|
+
return (0, import_node_crypto3.timingSafeEqual)((0, import_node_crypto3.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
|
|
1196
1171
|
}
|
|
1197
1172
|
var TOKEN_RESULT_TIMEOUT_MS = 3e4;
|
|
1198
1173
|
var TokenApi = class {
|
|
@@ -1221,11 +1196,11 @@ var TokenApi = class {
|
|
|
1221
1196
|
flow: "notification"
|
|
1222
1197
|
};
|
|
1223
1198
|
if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
|
|
1224
|
-
const r =
|
|
1199
|
+
const r = rec4(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
|
|
1225
1200
|
if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
|
|
1226
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");
|
|
1227
1202
|
}
|
|
1228
|
-
const vc =
|
|
1203
|
+
const vc = rec4(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
|
|
1229
1204
|
return {
|
|
1230
1205
|
sessionId: String(r.sessionID ?? ""),
|
|
1231
1206
|
vc: vc != null ? String(vc) : null,
|
|
@@ -1240,7 +1215,7 @@ var TokenApi = class {
|
|
|
1240
1215
|
*/
|
|
1241
1216
|
async certificateResult(sessionId) {
|
|
1242
1217
|
const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
|
|
1243
|
-
const r =
|
|
1218
|
+
const r = rec4(raw);
|
|
1244
1219
|
const item = (v) => v && typeof v === "object" ? v : null;
|
|
1245
1220
|
return {
|
|
1246
1221
|
status: String(r.status ?? ""),
|
|
@@ -1256,7 +1231,7 @@ var TokenApi = class {
|
|
|
1256
1231
|
}
|
|
1257
1232
|
/** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
|
|
1258
1233
|
async authChallenge() {
|
|
1259
|
-
const r =
|
|
1234
|
+
const r = rec4(
|
|
1260
1235
|
await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
|
|
1261
1236
|
relyingPartyUUID: this.creds.rpUUID,
|
|
1262
1237
|
relyingPartyName: this.creds.rpName
|
|
@@ -1277,7 +1252,7 @@ var TokenApi = class {
|
|
|
1277
1252
|
async authVerify(challengeId, certDer, signature) {
|
|
1278
1253
|
let serial;
|
|
1279
1254
|
try {
|
|
1280
|
-
serial = parseSubject(new
|
|
1255
|
+
serial = parseSubject(new import_node_crypto3.X509Certificate(certDer)).serialNumber;
|
|
1281
1256
|
} catch {
|
|
1282
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");
|
|
1283
1258
|
}
|
|
@@ -1287,12 +1262,12 @@ var TokenApi = class {
|
|
|
1287
1262
|
certificate: Buffer.from(certDer).toString("base64"),
|
|
1288
1263
|
signature: Buffer.from(signature).toString("base64")
|
|
1289
1264
|
});
|
|
1290
|
-
const r =
|
|
1291
|
-
if (
|
|
1292
|
-
const id =
|
|
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);
|
|
1293
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");
|
|
1294
|
-
const org =
|
|
1295
|
-
const cert =
|
|
1269
|
+
const org = rec4(r.organization);
|
|
1270
|
+
const cert = rec4(r.cert);
|
|
1296
1271
|
return {
|
|
1297
1272
|
identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
|
|
1298
1273
|
serialHex: String(cert.serialHex ?? ""),
|
|
@@ -1308,7 +1283,7 @@ function csrSpki(csr) {
|
|
|
1308
1283
|
const version = tlv(csr, info.start, info.end);
|
|
1309
1284
|
const subject = tlv(csr, version.end, info.end);
|
|
1310
1285
|
const spki = tlv(csr, subject.end, info.end);
|
|
1311
|
-
return (0,
|
|
1286
|
+
return (0, import_node_crypto3.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
|
|
1312
1287
|
format: "der",
|
|
1313
1288
|
type: "spki"
|
|
1314
1289
|
});
|
|
@@ -1327,7 +1302,7 @@ function verifyTokenCertificates(result, csrAuth, csrSign) {
|
|
|
1327
1302
|
}
|
|
1328
1303
|
let got;
|
|
1329
1304
|
try {
|
|
1330
|
-
got = new
|
|
1305
|
+
got = new import_node_crypto3.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
|
|
1331
1306
|
} catch {
|
|
1332
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`);
|
|
1333
1308
|
}
|
|
@@ -1340,13 +1315,13 @@ function tokenAuthDigest(rpUUID, challengeB64) {
|
|
|
1340
1315
|
}
|
|
1341
1316
|
const ch = Buffer.from(challengeB64, "base64");
|
|
1342
1317
|
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,
|
|
1318
|
+
return (0, import_node_crypto3.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
|
|
1344
1319
|
}
|
|
1345
1320
|
function tokenLimitOf(err) {
|
|
1346
1321
|
if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
|
|
1347
1322
|
let b;
|
|
1348
1323
|
try {
|
|
1349
|
-
b =
|
|
1324
|
+
b = rec4(JSON.parse(err.body));
|
|
1350
1325
|
} catch {
|
|
1351
1326
|
return void 0;
|
|
1352
1327
|
}
|
|
@@ -1359,7 +1334,7 @@ function tokenLimitOf(err) {
|
|
|
1359
1334
|
}
|
|
1360
1335
|
|
|
1361
1336
|
// src/validator.ts
|
|
1362
|
-
var
|
|
1337
|
+
var import_node_crypto4 = require("crypto");
|
|
1363
1338
|
var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
|
|
1364
1339
|
var ResponseValidator = class {
|
|
1365
1340
|
anchors;
|
|
@@ -1373,8 +1348,8 @@ var ResponseValidator = class {
|
|
|
1373
1348
|
checkKeyUsage;
|
|
1374
1349
|
schemeName;
|
|
1375
1350
|
constructor(trust = {}) {
|
|
1376
|
-
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new
|
|
1377
|
-
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new
|
|
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));
|
|
1378
1353
|
this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
|
|
1379
1354
|
this.skew = trust.clockSkewMs ?? 0;
|
|
1380
1355
|
this.allowUntrusted = trust.allowUntrusted ?? false;
|
|
@@ -1555,7 +1530,7 @@ var ResponseValidator = class {
|
|
|
1555
1530
|
leafOf(result, protocol) {
|
|
1556
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");
|
|
1557
1532
|
if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
|
|
1558
|
-
const leaf = new
|
|
1533
|
+
const leaf = new import_node_crypto4.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
|
|
1559
1534
|
this.checkValidity(leaf);
|
|
1560
1535
|
this.checkLevel(String(result.certificateLevel ?? ""));
|
|
1561
1536
|
this.checkPurpose(leaf, protocol);
|
|
@@ -1689,16 +1664,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
|
|
|
1689
1664
|
return fromResponse;
|
|
1690
1665
|
}
|
|
1691
1666
|
function userChallengeOf(verifier) {
|
|
1692
|
-
return (0,
|
|
1667
|
+
return (0, import_node_crypto4.createHash)("sha256").update(verifier, "utf8").digest("base64url");
|
|
1693
1668
|
}
|
|
1694
1669
|
function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
1695
1670
|
const sig = Buffer.from(sigB64, "base64");
|
|
1696
1671
|
const h = nodeHash(hash);
|
|
1697
1672
|
if (alg === "rsassa-pss") {
|
|
1698
|
-
return (0,
|
|
1673
|
+
return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
|
|
1699
1674
|
}
|
|
1700
|
-
if (alg === "ecdsa-with-SHA256") return (0,
|
|
1701
|
-
return (0,
|
|
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);
|
|
1702
1677
|
}
|
|
1703
1678
|
function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
1704
1679
|
const sig = Buffer.from(sigB64, "base64");
|
|
@@ -1707,7 +1682,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
|
1707
1682
|
if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
|
|
1708
1683
|
let em;
|
|
1709
1684
|
try {
|
|
1710
|
-
em = (0,
|
|
1685
|
+
em = (0, import_node_crypto4.publicDecrypt)({ key, padding: import_node_crypto4.constants.RSA_NO_PADDING }, sig);
|
|
1711
1686
|
} catch {
|
|
1712
1687
|
return false;
|
|
1713
1688
|
}
|
|
@@ -1791,7 +1766,7 @@ function mgf1(hash, seed, length) {
|
|
|
1791
1766
|
for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
|
|
1792
1767
|
const c = Buffer.alloc(4);
|
|
1793
1768
|
c.writeUInt32BE(i, 0);
|
|
1794
|
-
out.push((0,
|
|
1769
|
+
out.push((0, import_node_crypto4.createHash)(nodeHash(hash)).update(seed).update(c).digest());
|
|
1795
1770
|
}
|
|
1796
1771
|
return Buffer.concat(out).subarray(0, length);
|
|
1797
1772
|
}
|
|
@@ -1815,7 +1790,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
|
|
|
1815
1790
|
const psLen = emLen - hLen - sLen - 2;
|
|
1816
1791
|
if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
|
|
1817
1792
|
const salt = db.subarray(psLen + 1);
|
|
1818
|
-
const h2 = (0,
|
|
1793
|
+
const h2 = (0, import_node_crypto4.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
|
|
1819
1794
|
return h.equals(h2);
|
|
1820
1795
|
}
|
|
1821
1796
|
function keyUsageBits(cert) {
|
|
@@ -1949,7 +1924,6 @@ function resolveBaseUrl(baseUrl) {
|
|
|
1949
1924
|
var EidClient = class {
|
|
1950
1925
|
auth;
|
|
1951
1926
|
sign;
|
|
1952
|
-
pdf;
|
|
1953
1927
|
organization;
|
|
1954
1928
|
person;
|
|
1955
1929
|
token;
|
|
@@ -1971,11 +1945,6 @@ var EidClient = class {
|
|
|
1971
1945
|
const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
|
|
1972
1946
|
this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
|
|
1973
1947
|
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
1948
|
this.organization = new OrganizationApi(http);
|
|
1980
1949
|
this.person = new PersonApi(http);
|
|
1981
1950
|
this.token = new TokenApi(http, cfg.credentials);
|
|
@@ -1983,6 +1952,14 @@ var EidClient = class {
|
|
|
1983
1952
|
this.validator = new ResponseValidator(cfg.trust ?? {});
|
|
1984
1953
|
this.rpName = cfg.credentials.rpName;
|
|
1985
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
|
+
}
|
|
1986
1963
|
};
|
|
1987
1964
|
|
|
1988
1965
|
// src/devicelink.ts
|
|
@@ -2028,7 +2005,7 @@ function openOrShowQR(session, opts) {
|
|
|
2028
2005
|
}
|
|
2029
2006
|
|
|
2030
2007
|
// src/devicelinkv3.ts
|
|
2031
|
-
var
|
|
2008
|
+
var import_node_crypto5 = require("crypto");
|
|
2032
2009
|
var TYPES = ["QR", "Web2App", "App2App"];
|
|
2033
2010
|
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
2034
2011
|
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
@@ -2085,7 +2062,7 @@ function buildDeviceLink(p) {
|
|
|
2085
2062
|
callback,
|
|
2086
2063
|
unprotected
|
|
2087
2064
|
].join("|");
|
|
2088
|
-
const authCode = (0,
|
|
2065
|
+
const authCode = (0, import_node_crypto5.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
2089
2066
|
return `${unprotected}&authCode=${authCode}`;
|
|
2090
2067
|
}
|
|
2091
2068
|
function deviceLinkInput(s, deviceLinkType, lang) {
|
|
@@ -2129,7 +2106,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
2129
2106
|
}
|
|
2130
2107
|
|
|
2131
2108
|
// src/login.ts
|
|
2132
|
-
var
|
|
2109
|
+
var import_node_crypto6 = require("crypto");
|
|
2133
2110
|
function memoryLoginStore() {
|
|
2134
2111
|
const m = /* @__PURE__ */ new Map();
|
|
2135
2112
|
return {
|
|
@@ -2166,7 +2143,7 @@ function forwardedClientIp(headers, trustProxy) {
|
|
|
2166
2143
|
return header(headers, "x-real-ip").trim() || void 0;
|
|
2167
2144
|
}
|
|
2168
2145
|
var ID_RE = /^(\p{L}{2}[0-9]{8}|[0-9]{8,14}|PNOMN-[0-9]{8,14})$/u;
|
|
2169
|
-
var
|
|
2146
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
2170
2147
|
var B64URL_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
2171
2148
|
var IOS_BROWSERS = [
|
|
2172
2149
|
[/CriOS/i, "googlechromes"],
|
|
@@ -2210,11 +2187,11 @@ function readCookie(h, name) {
|
|
|
2210
2187
|
}
|
|
2211
2188
|
return "";
|
|
2212
2189
|
}
|
|
2213
|
-
var sha256hex = (s) => (0,
|
|
2190
|
+
var sha256hex = (s) => (0, import_node_crypto6.createHash)("sha256").update(s).digest("hex");
|
|
2214
2191
|
function safeEqualHex(a, b) {
|
|
2215
2192
|
const x = Buffer.from(a, "hex");
|
|
2216
2193
|
const y = Buffer.from(b, "hex");
|
|
2217
|
-
return x.length === y.length && x.length > 0 && (0,
|
|
2194
|
+
return x.length === y.length && x.length > 0 && (0, import_node_crypto6.timingSafeEqual)(x, y);
|
|
2218
2195
|
}
|
|
2219
2196
|
function toIdentity(sessionId, r, v, flowType) {
|
|
2220
2197
|
const s = v.parsedSubject;
|
|
@@ -2297,7 +2274,7 @@ function createEidLogin(opts) {
|
|
|
2297
2274
|
const dot = raw.indexOf(".");
|
|
2298
2275
|
const sid = raw.slice(0, dot);
|
|
2299
2276
|
const binding = raw.slice(dot + 1);
|
|
2300
|
-
if (dot < 0 || !
|
|
2277
|
+
if (dot < 0 || !UUID_RE2.test(sid) || !B64URL_RE.test(binding)) return void 0;
|
|
2301
2278
|
const tx = await store.get(sid);
|
|
2302
2279
|
if (!tx || !safeEqualHex(tx.bindingHash, sha256hex(binding))) return void 0;
|
|
2303
2280
|
if (tx.expiresAt <= Date.now()) {
|
|
@@ -2354,7 +2331,7 @@ function createEidLogin(opts) {
|
|
|
2354
2331
|
});
|
|
2355
2332
|
}
|
|
2356
2333
|
}
|
|
2357
|
-
const binding = (0,
|
|
2334
|
+
const binding = (0, import_node_crypto6.randomBytes)(32).toString("base64url");
|
|
2358
2335
|
const full = { ...tx, bindingHash: sha256hex(binding), expiresAt: Date.now() + ttl };
|
|
2359
2336
|
await store.set(full.sessionId, full, ttl);
|
|
2360
2337
|
const setCookie = `${cookieName}=${full.sessionId}.${binding}; ${cookieAttrs(Math.ceil(ttl / 1e3))}`;
|
|
@@ -2523,8 +2500,6 @@ function createEidLogin(opts) {
|
|
|
2523
2500
|
CITIZEN_ISSUING_CA_SKI,
|
|
2524
2501
|
CertChoiceInvalidError,
|
|
2525
2502
|
DEFAULT_BASE_URL,
|
|
2526
|
-
DEFAULT_PDF_MAX_BYTES,
|
|
2527
|
-
DEFAULT_PDF_TIMEOUT_MS,
|
|
2528
2503
|
DEFAULT_SIGN_SESSION_TTL_MS,
|
|
2529
2504
|
DeviceLinkError,
|
|
2530
2505
|
EidClient,
|
|
@@ -2535,7 +2510,7 @@ function createEidLogin(opts) {
|
|
|
2535
2510
|
NoSigningCertificateError,
|
|
2536
2511
|
ON_BEHALF_ERROR_CODES,
|
|
2537
2512
|
OrganizationApi,
|
|
2538
|
-
|
|
2513
|
+
PDF_SIGNATURE_REGISTER_FIELDS,
|
|
2539
2514
|
PersonApi,
|
|
2540
2515
|
RETRYABLE_ERROR_CODES,
|
|
2541
2516
|
RequestInvalidError,
|
|
@@ -2549,6 +2524,7 @@ function createEidLogin(opts) {
|
|
|
2549
2524
|
acspV2Digest,
|
|
2550
2525
|
buildAcspV2Payload,
|
|
2551
2526
|
buildDeviceLink,
|
|
2527
|
+
buildRegisterBody,
|
|
2552
2528
|
createEidLogin,
|
|
2553
2529
|
deviceLink,
|
|
2554
2530
|
deviceLinkInput,
|
|
@@ -2564,6 +2540,7 @@ function createEidLogin(opts) {
|
|
|
2564
2540
|
personMatchesCertificate,
|
|
2565
2541
|
qrDeviceLinkTicker,
|
|
2566
2542
|
randomChallenge,
|
|
2543
|
+
registrationFromCompleted,
|
|
2567
2544
|
resolveBaseUrl,
|
|
2568
2545
|
sessionExpiry,
|
|
2569
2546
|
sha256Base64,
|