@gerege-systems/eid-mongolia-sdk 0.8.1 → 0.9.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/README.md +33 -2
- package/dist/{browser-DqKDj2nB.d.cts → browser-B_XtyuFG.d.cts} +1 -1
- package/dist/{browser-DqKDj2nB.d.ts → browser-B_XtyuFG.d.ts} +1 -1
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/index.cjs +181 -23
- package/dist/index.d.cts +77 -7
- package/dist/index.d.ts +77 -7
- package/dist/index.js +172 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -151,8 +151,19 @@ const result = await eid.session.waitForResult(s.sessionId); // long-poll
|
|
|
151
151
|
const who = eid.validator.validateAuth(result, s.acsp); // ← гинж + ACSP_V2 крипто-баталгаажуулна
|
|
152
152
|
|
|
153
153
|
console.log(who.documentNumber, who.subject); // баталгаажсан иргэн
|
|
154
|
+
const { name, serialNumber } = who.parsedSubject; // "Болд Бат", "PNOMN-12345678" (givenName/surname/commonName/country…)
|
|
154
155
|
```
|
|
155
156
|
|
|
157
|
+
Subject-ийн талбарууд (`parsedSubject`, `validateSign`-д ч мөн) нь `parseSubject(cert)`-ээс — OID 2.5.4.42/4/3/5/6/10/97,
|
|
158
|
+
UTF8String/PrintableString/BMPString. `parseSubject` нь зөвхөн задлалт: баталгаажаагүй гэрчилгээний нэрэнд бүү итгэ.
|
|
159
|
+
|
|
160
|
+
> **Ижил утсан дээр push хэрэглэхгүй.** Push (`notificationBy*`)-ийн хүсэлт eID аппад мэдэгдэл / «хүлээгдэж буй»
|
|
161
|
+
> жагсаалтаар ирдэг; Smart-ID ба eID-д Notification төрлийн device link **байхгүй** (CA `docs/DEVICE_LINK_V3.md` §3).
|
|
162
|
+
> Хуучин `/dl?sessionId&vc` холбоос (`deviceLink`) push session-д ч юу ч өгөхгүй (сервер `DEVICE_LINK_REQUIRED`).
|
|
163
|
+
> Иргэн RP-ийг eID апптай ижил утсан дээр нээсэн бол push-ийн оронд **device-link Web2App** урсгал хэрэглэ
|
|
164
|
+
> (`auth.deviceLink*` / `pdf.prepare({ flow: "device-link" })` + `buildDeviceLink(…, "Web2App")`, §4) — товч аппыг шууд
|
|
165
|
+
> нээж, callback-аар буцна. Push-д «eID аппаа нээнэ үү» гэсэн текст л хангалттай.
|
|
166
|
+
|
|
156
167
|
## 3. Гарын үсэг (qualified signature)
|
|
157
168
|
|
|
158
169
|
```ts
|
|
@@ -171,6 +182,16 @@ const sig = eid.validator.validateSign(result, digest); // digest-ийн эср
|
|
|
171
182
|
console.log(sig.signatureValueB64, sig.subject);
|
|
172
183
|
```
|
|
173
184
|
|
|
185
|
+
Device-link / push-оор эхлүүлсэн sign session-д эхлүүлсэн урсгалаа `expectedFlowType`-оор тулга (`validateAuth`-тай
|
|
186
|
+
ижил: зөрвөл `ValidationError`). Гуравдах параметр нь `hashAlgorithm` мөр (хуучин хэлбэр) эсвэл объект:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
eid.validator.validateSign(result, digest, { expectedFlowType: "QR" }); // эсвэл "Web2App" / "App2App" / "Notification"
|
|
190
|
+
eid.validator.validateSign(result, digest384, { hashAlgorithm: "SHA-384", expectedFlowType: "Web2App" });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
RAW_DIGEST_SIGNATURE-д `flowType` гарын үсэгт ордоггүй (зөвхөн digest зурагдана) — энэ нь RP-API хариуны тулгалт.
|
|
194
|
+
|
|
174
195
|
## 3a. Байгууллагын нэрийн өмнөөс гарын үсэг (onBehalfOf)
|
|
175
196
|
|
|
176
197
|
Иргэн **өөрийн PIN2 гэрчилгээгээр** зурна; тухайн мөчид байгууллагыг төлөөлөх эрхтэйг eID **өөрийн бүртгэлээр**
|
|
@@ -198,7 +219,7 @@ try {
|
|
|
198
219
|
// MUST: endResult OK, pdf.documentStatus READY, pdf.signer.onBehalfOf = хүссэн байгууллага,
|
|
199
220
|
// татсан файлын SHA-256 = pdf.outSha256, validation.indication "valid", ltv true.
|
|
200
221
|
if (r.pdf?.documentStatus !== "READY" || r.pdf.signer?.onBehalfOf !== "NTRMN-6235972") throw new Error("…");
|
|
201
|
-
const signed = await eid.pdf.
|
|
222
|
+
const signed = await eid.pdf.documentVerified(s.sessionId, r.pdf); // SHA-256 ≠ pdf.outSha256 → ValidationError
|
|
202
223
|
console.log(r.pdf.signer.claimedRole); // "Гүйцэтгэх захирал, Гэрэгэ Системс ХХК (NTRMN-6235972)"
|
|
203
224
|
} catch (e) {
|
|
204
225
|
if ((e instanceof ForbiddenError || e instanceof ApiError) && e.code) {
|
|
@@ -212,6 +233,11 @@ try {
|
|
|
212
233
|
// — CMS/PDF-ийг та угсардаг тул мэдүүлсэн үүргийг (signer-attributes-v2) өөрөө нэмнэ.
|
|
213
234
|
```
|
|
214
235
|
|
|
236
|
+
`prepare`-ийн хариу `digest` (DTBS, base64) — иргэний зурах 32 байт, push ба device-link хоёуланд (хуучин CA push-д
|
|
237
|
+
`undefined`). Иргэний гарын үсгийг өөрөө шалгах бол: `eid.validator.validateSign(r, s.digest!, { expectedFlowType: "QR" })`
|
|
238
|
+
(харуулсан урсгалаа). Гарын үсэгтэй PDF-ийг
|
|
239
|
+
`pdf.document()`-ийн оронд `pdf.documentVerified(sessionId, r.pdf)`-аар тат — `READY` + `outSha256` тулгалтыг хийнэ.
|
|
240
|
+
|
|
215
241
|
Олон талт гэрээ: А байгууллагын гаралтыг өөрчлөлтгүй Б-ийн `prepare`-д (`docID`, өөр `onBehalfOf`) илгээнэ.
|
|
216
242
|
Session-ий `onBehalfOf` блок (`role`/`rightType`) нь хариу угсрах үеийн бүртгэл; `pdf.signer.onBehalfOf`/`claimedRole`
|
|
217
243
|
нь баримтад бичигдсэн утга.
|
|
@@ -312,7 +338,12 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
312
338
|
`initialCallbackUrl`-ийг хариуны `signature.initialCallbackUrl`-аас авна (CA урсгалаар шийднэ: QR/Notification →
|
|
313
339
|
`""`, Web2App/App2App → таны callback) — зөвхөн `""` эсвэл
|
|
314
340
|
таны илгээсэн callback байхад, Web2App/App2App-д заавал таны callback; өөр утга → татгалзана
|
|
315
|
-
- **sign** → RP-ийн өгсөн digest
|
|
341
|
+
- **sign** → RP-ийн өгсөн digest дээр; `expectedFlowType` өгвөл хариуны `flowType` ЯГ таарах (гарын үсэгт ордоггүй — хариуны тулгалт)
|
|
342
|
+
- алгоритм нь гэрчилгээний түлхүүртэй уях: `ecdsa-with-SHA256` зөвхөн EC P-256 түлхүүрт, RSA алгоритмууд зөвхөн
|
|
343
|
+
RSA түлхүүрт (alg confusion хаалт). Hash нь үргэлж хүссэнтэй ЯГ тэнцүү. RP-API session-ий гарын үсэг CA дээр
|
|
344
|
+
SplitKey RSA (одоогийн бүх мобайл гэрчилгээ) эсвэл legacy threshold ECDSA P-256-аас гарна; ECDSA credential
|
|
345
|
+
зөвхөн SHA-256 digest зурдаг (өөр hash хүссэн session-д сервер гарын үсэг гаргахгүй), тиймээс SHA-384/512
|
|
346
|
+
хүссэн session-д `ecdsa-with-SHA256` хариу ирвэл хуурамч гэж татгалзана.
|
|
316
347
|
|
|
317
348
|
Аль нэг алхам бүтэлгүйтвэл `ValidationError` шиднэ — хариунд **итгэхгүй**.
|
|
318
349
|
|
|
@@ -318,4 +318,4 @@ declare class ValidationError extends EidError {
|
|
|
318
318
|
declare class DeviceLinkError extends EidError {
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type
|
|
321
|
+
export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfSessionBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfDocumentStatus as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
|
|
@@ -318,4 +318,4 @@ declare class ValidationError extends EidError {
|
|
|
318
318
|
declare class DeviceLinkError extends EidError {
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type
|
|
321
|
+
export { type AcspContext as A, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, type HashAlgorithm as H, type Interaction as I, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PdfSessionBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, ValidationError as V, type SessionResult as a, type RepresentationsResponse as b, ApiError as c, AuthenticationError as d, DeviceLinkError as e, type DeviceLinkInput as f, type EndResult as g, ForbiddenError as h, type NavigatorLike as i, NetworkError as j, type OnBehalfErrorCode as k, type OpenOrShowQROptions as l, type OrgResult as m, type PdfDocumentStatus as n, type PdfSigner as o, type Representation as p, SessionFailedError as q, type SessionState as r, type SignatureAlgorithmParameters as s, type SignatureProtocol as t, deviceLink as u, isMobileUserAgent as v, openOrShowQR as w };
|
package/dist/browser.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-
|
|
1
|
+
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.cjs';
|
package/dist/browser.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-
|
|
1
|
+
export { e as DeviceLinkError, f as DeviceLinkInput, D as DeviceLinkSession, E as EidError, i as NavigatorLike, l as OpenOrShowQROptions, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.js';
|
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
isMobileUserAgent: () => isMobileUserAgent,
|
|
52
52
|
openOrShowQR: () => openOrShowQR,
|
|
53
53
|
parseSessionResult: () => parseSessionResult,
|
|
54
|
+
parseSubject: () => parseSubject,
|
|
54
55
|
pkceChallenge: () => pkceChallenge,
|
|
55
56
|
qrDeviceLinkTicker: () => qrDeviceLinkTicker,
|
|
56
57
|
randomChallenge: () => randomChallenge,
|
|
@@ -480,6 +481,7 @@ var SessionApi = class {
|
|
|
480
481
|
};
|
|
481
482
|
|
|
482
483
|
// src/pdf.ts
|
|
484
|
+
var import_node_crypto2 = require("crypto");
|
|
483
485
|
function rec3(v) {
|
|
484
486
|
return v && typeof v === "object" ? v : {};
|
|
485
487
|
}
|
|
@@ -512,10 +514,12 @@ var PdfApi = class {
|
|
|
512
514
|
const receivedAt = Date.now();
|
|
513
515
|
const vc = rec3(r.vc).value;
|
|
514
516
|
const dl = rec3(r.deviceLink);
|
|
517
|
+
const digest = typeof r.digest === "string" && r.digest ? r.digest : typeof dl.digest === "string" && dl.digest ? dl.digest : void 0;
|
|
515
518
|
return {
|
|
516
519
|
sessionId: String(r.sessionID),
|
|
517
520
|
docId: String(r.docID),
|
|
518
521
|
vc: vc != null ? String(vc) : null,
|
|
522
|
+
...digest !== void 0 ? { digest } : {},
|
|
519
523
|
deviceLink: dl.sessionToken ? {
|
|
520
524
|
sessionToken: String(dl.sessionToken),
|
|
521
525
|
sessionSecret: String(dl.sessionSecret),
|
|
@@ -533,6 +537,26 @@ var PdfApi = class {
|
|
|
533
537
|
document(sessionId) {
|
|
534
538
|
return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
|
|
535
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
542
|
+
* `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
|
|
543
|
+
*
|
|
544
|
+
* @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
|
|
545
|
+
*/
|
|
546
|
+
async documentVerified(sessionId, expected) {
|
|
547
|
+
let want;
|
|
548
|
+
if (typeof expected === "string") {
|
|
549
|
+
want = expected;
|
|
550
|
+
} else {
|
|
551
|
+
if (expected.documentStatus !== "READY") throw new ValidationError(`pdf.documentStatus ${expected.documentStatus} \u2260 READY`);
|
|
552
|
+
want = expected.outSha256;
|
|
553
|
+
}
|
|
554
|
+
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");
|
|
555
|
+
const bytes = await this.document(sessionId);
|
|
556
|
+
const got = (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
|
|
557
|
+
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");
|
|
558
|
+
return bytes;
|
|
559
|
+
}
|
|
536
560
|
};
|
|
537
561
|
|
|
538
562
|
// src/organization.ts
|
|
@@ -550,7 +574,114 @@ var OrganizationApi = class {
|
|
|
550
574
|
};
|
|
551
575
|
|
|
552
576
|
// src/validator.ts
|
|
553
|
-
var
|
|
577
|
+
var import_node_crypto4 = require("crypto");
|
|
578
|
+
|
|
579
|
+
// src/subject.ts
|
|
580
|
+
var import_node_crypto3 = require("crypto");
|
|
581
|
+
var OIDS = {
|
|
582
|
+
"2.5.4.42": "givenName",
|
|
583
|
+
"2.5.4.4": "surname",
|
|
584
|
+
"2.5.4.3": "commonName",
|
|
585
|
+
"2.5.4.5": "serialNumber",
|
|
586
|
+
"2.5.4.6": "country",
|
|
587
|
+
"2.5.4.10": "organization",
|
|
588
|
+
"2.5.4.97": "organizationIdentifier"
|
|
589
|
+
};
|
|
590
|
+
function tlv(buf, pos, limit) {
|
|
591
|
+
if (pos + 2 > limit) throw new ValidationError("subject: DER \u0442\u043E\u043B\u0433\u043E\u0439 \u0431\u043E\u0433\u0438\u043D\u043E");
|
|
592
|
+
const tag = buf[pos];
|
|
593
|
+
if ((tag & 31) === 31) throw new ValidationError("subject: \u043E\u043B\u043E\u043D \u0431\u0430\u0439\u0442 tag \u0434\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439");
|
|
594
|
+
let len = buf[pos + 1];
|
|
595
|
+
let i = pos + 2;
|
|
596
|
+
if (len & 128) {
|
|
597
|
+
const n = len & 127;
|
|
598
|
+
if (n === 0 || n > 3) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u0443\u0440\u0443\u0443");
|
|
599
|
+
if (i + n > limit) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u043E\u0433\u0438\u043D\u043E");
|
|
600
|
+
len = 0;
|
|
601
|
+
for (let k = 0; k < n; k++) len = len * 256 + buf[i++];
|
|
602
|
+
}
|
|
603
|
+
if (i + len > limit) throw new ValidationError("subject: DER \u0430\u0433\u0443\u0443\u043B\u0433\u0430 \u0445\u0438\u043B\u044D\u044D\u0441 \u0433\u0430\u0440\u043D\u0430");
|
|
604
|
+
return { tag, start: i, end: i + len };
|
|
605
|
+
}
|
|
606
|
+
function oidOf(buf, t) {
|
|
607
|
+
if (t.tag !== 6 || t.end === t.start) throw new ValidationError("subject: OID \u0431\u0438\u0448");
|
|
608
|
+
const parts = [];
|
|
609
|
+
let v = 0;
|
|
610
|
+
for (let i = t.start; i < t.end; i++) {
|
|
611
|
+
const b = buf[i];
|
|
612
|
+
v = v * 128 + (b & 127);
|
|
613
|
+
if (!(b & 128)) {
|
|
614
|
+
if (parts.length === 0) parts.push(v < 80 ? Math.floor(v / 40) : 2, v < 80 ? v % 40 : v - 80);
|
|
615
|
+
else parts.push(v);
|
|
616
|
+
v = 0;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return parts.join(".");
|
|
620
|
+
}
|
|
621
|
+
function stringOf(buf, t) {
|
|
622
|
+
const bytes = buf.subarray(t.start, t.end);
|
|
623
|
+
switch (t.tag) {
|
|
624
|
+
case 12:
|
|
625
|
+
try {
|
|
626
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
627
|
+
} catch {
|
|
628
|
+
throw new ValidationError("subject: UTF8String \u0431\u0443\u0440\u0443\u0443 UTF-8");
|
|
629
|
+
}
|
|
630
|
+
case 19:
|
|
631
|
+
// PrintableString
|
|
632
|
+
case 22:
|
|
633
|
+
// IA5String
|
|
634
|
+
case 20:
|
|
635
|
+
return Buffer.from(bytes).toString("latin1");
|
|
636
|
+
case 30: {
|
|
637
|
+
if (bytes.length % 2) throw new ValidationError("subject: BMPString \u0443\u0440\u0442 \u0441\u043E\u043D\u0434\u0433\u043E\u0439");
|
|
638
|
+
return Buffer.from(bytes).swap16().toString("utf16le");
|
|
639
|
+
}
|
|
640
|
+
default:
|
|
641
|
+
return void 0;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function parseSubject(cert) {
|
|
645
|
+
const der = cert instanceof import_node_crypto3.X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
|
|
646
|
+
const top = tlv(der, 0, der.length);
|
|
647
|
+
if (top.tag !== 48) throw new ValidationError("subject: Certificate SEQUENCE \u0431\u0438\u0448");
|
|
648
|
+
const tbs = tlv(der, top.start, top.end);
|
|
649
|
+
if (tbs.tag !== 48) throw new ValidationError("subject: tbsCertificate SEQUENCE \u0431\u0438\u0448");
|
|
650
|
+
let pos = tbs.start;
|
|
651
|
+
let first = tlv(der, pos, tbs.end);
|
|
652
|
+
if (first.tag === 160) {
|
|
653
|
+
pos = first.end;
|
|
654
|
+
first = tlv(der, pos, tbs.end);
|
|
655
|
+
}
|
|
656
|
+
let field = first;
|
|
657
|
+
for (let k = 0; k < 4; k++) field = tlv(der, field.end, tbs.end);
|
|
658
|
+
if (field.tag !== 48) throw new ValidationError("subject: Name SEQUENCE \u0431\u0438\u0448");
|
|
659
|
+
const out = { name: null, attributes: {} };
|
|
660
|
+
for (let sp = field.start; sp < field.end; ) {
|
|
661
|
+
const rdn = tlv(der, sp, field.end);
|
|
662
|
+
if (rdn.tag !== 49) throw new ValidationError("subject: RDN SET \u0431\u0438\u0448");
|
|
663
|
+
for (let ap = rdn.start; ap < rdn.end; ) {
|
|
664
|
+
const atv = tlv(der, ap, rdn.end);
|
|
665
|
+
if (atv.tag !== 48) throw new ValidationError("subject: AttributeTypeAndValue SEQUENCE \u0431\u0438\u0448");
|
|
666
|
+
const oidT = tlv(der, atv.start, atv.end);
|
|
667
|
+
const valT = tlv(der, oidT.end, atv.end);
|
|
668
|
+
const oid = oidOf(der, oidT);
|
|
669
|
+
const value = stringOf(der, valT);
|
|
670
|
+
if (value !== void 0 && !(oid in out.attributes)) {
|
|
671
|
+
out.attributes[oid] = value;
|
|
672
|
+
const key = OIDS[oid];
|
|
673
|
+
if (key) out[key] = value;
|
|
674
|
+
}
|
|
675
|
+
ap = atv.end;
|
|
676
|
+
}
|
|
677
|
+
sp = rdn.end;
|
|
678
|
+
}
|
|
679
|
+
const full = [out.givenName, out.surname].filter((s) => s).join(" ");
|
|
680
|
+
out.name = full || out.commonName || null;
|
|
681
|
+
return out;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/validator.ts
|
|
554
685
|
var ResponseValidator = class {
|
|
555
686
|
anchors;
|
|
556
687
|
intermediates;
|
|
@@ -560,8 +691,8 @@ var ResponseValidator = class {
|
|
|
560
691
|
revocation;
|
|
561
692
|
revocationMode;
|
|
562
693
|
constructor(trust = {}) {
|
|
563
|
-
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new
|
|
564
|
-
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new
|
|
694
|
+
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
|
|
695
|
+
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto4.X509Certificate(p));
|
|
565
696
|
this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
|
|
566
697
|
this.skew = trust.clockSkewMs ?? 0;
|
|
567
698
|
this.allowUntrusted = trust.allowUntrusted ?? false;
|
|
@@ -592,7 +723,7 @@ var ResponseValidator = class {
|
|
|
592
723
|
throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
593
724
|
}
|
|
594
725
|
if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
|
|
595
|
-
const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
726
|
+
const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
596
727
|
const payload = buildAcspV2Payload({
|
|
597
728
|
serverRandom,
|
|
598
729
|
rpChallenge: acsp.rpChallenge,
|
|
@@ -611,17 +742,22 @@ var ResponseValidator = class {
|
|
|
611
742
|
documentNumber: result.documentNumber,
|
|
612
743
|
certificate: leaf,
|
|
613
744
|
subject: leaf.subject,
|
|
745
|
+
parsedSubject: parseSubject(leaf),
|
|
614
746
|
certificateLevel: String(result.certificateLevel ?? "")
|
|
615
747
|
};
|
|
616
748
|
}
|
|
617
749
|
/**
|
|
618
750
|
* Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
|
|
619
|
-
* @param
|
|
751
|
+
* @param opts — `hashAlgorithm` мөр (хуучин хэлбэр; default SHA-256) эсвэл {@link ValidateSignOptions}.
|
|
620
752
|
*/
|
|
621
|
-
validateSign(result, digestB64,
|
|
753
|
+
validateSign(result, digestB64, opts = {}) {
|
|
754
|
+
const { hashAlgorithm = "SHA-256", expectedFlowType } = typeof opts === "string" ? { hashAlgorithm: opts } : opts;
|
|
622
755
|
const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
|
|
756
|
+
if (expectedFlowType && result.flowType !== expectedFlowType) {
|
|
757
|
+
throw new ValidationError(`flowType ${result.flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
|
|
758
|
+
}
|
|
623
759
|
const digest = Buffer.from(digestB64, "base64");
|
|
624
|
-
const { alg, hash } = this.algorithmOf(result, null, hashAlgorithm);
|
|
760
|
+
const { alg, hash } = this.algorithmOf(result, leaf, null, hashAlgorithm);
|
|
625
761
|
if (digest.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
626
762
|
if (!verifyPrehashedSignature(digest, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
627
763
|
throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u04E9\u0433\u0441\u04E9\u043D digest-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
@@ -631,27 +767,47 @@ var ResponseValidator = class {
|
|
|
631
767
|
signatureValueB64: result.signatureValueB64,
|
|
632
768
|
signatureAlgorithm: result.signatureAlgorithm,
|
|
633
769
|
certificate: leaf,
|
|
634
|
-
subject: leaf.subject
|
|
770
|
+
subject: leaf.subject,
|
|
771
|
+
parsedSubject: parseSubject(leaf)
|
|
635
772
|
};
|
|
636
773
|
}
|
|
637
774
|
/**
|
|
638
|
-
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй
|
|
639
|
-
*
|
|
775
|
+
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
|
|
776
|
+
* (downgrade / alg confusion хаалт).
|
|
777
|
+
*
|
|
778
|
+
* CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
|
|
779
|
+
* RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
|
|
780
|
+
* hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
|
|
781
|
+
* зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
|
|
782
|
+
* Иймд:
|
|
783
|
+
* - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
|
|
784
|
+
* - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
|
|
785
|
+
* EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
|
|
640
786
|
*/
|
|
641
|
-
algorithmOf(result, wantAlg, wantHash) {
|
|
787
|
+
algorithmOf(result, leaf, wantAlg, wantHash) {
|
|
642
788
|
const alg = result.signatureAlgorithm ?? "";
|
|
789
|
+
const keyType = leaf.publicKey.asymmetricKeyType;
|
|
643
790
|
let hash;
|
|
644
791
|
if (alg === "rsassa-pss") {
|
|
645
792
|
hash = String(result.signatureAlgorithmParameters?.hashAlgorithm ?? "");
|
|
793
|
+
if (!HASHES.has(hash)) throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 hashAlgorithm: ${hash}`);
|
|
646
794
|
const mgf = result.signatureAlgorithmParameters?.maskGenAlgorithm?.parameters?.hashAlgorithm;
|
|
647
795
|
if (mgf && mgf !== hash) throw new ValidationError("MGF1 hash \u043D\u044C hashAlgorithm-\u0430\u0430\u0441 \u04E9\u04E9\u0440");
|
|
648
796
|
if (result.signatureAlgorithmParameters?.saltLength != null && result.signatureAlgorithmParameters.saltLength !== hashLen(hash)) {
|
|
649
797
|
throw new ValidationError("saltLength \u2260 hLen");
|
|
650
798
|
}
|
|
799
|
+
if (keyType !== "rsa" && keyType !== "rsa-pss") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
|
|
651
800
|
} else if (/^sha(256|384|512)WithRSAEncryption$/.test(alg)) {
|
|
652
801
|
hash = "SHA-" + alg.slice(3, 6);
|
|
802
|
+
if (keyType !== "rsa") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
|
|
653
803
|
} else if (alg === "ecdsa-with-SHA256") {
|
|
654
804
|
hash = "SHA-256";
|
|
805
|
+
if (keyType !== "ec" || leaf.publicKey.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
|
|
806
|
+
throw new ValidationError(`${alg} \u043D\u044C EC P-256 \u0431\u0443\u0441 \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
|
|
807
|
+
}
|
|
808
|
+
if (wantHash !== hash) {
|
|
809
|
+
throw new ValidationError(`ecdsa-with-SHA256 \u0445\u0430\u0440\u0438\u0443 ${wantHash} \u0445\u04AF\u0441\u0441\u044D\u043D session-\u0434 \u0431\u043E\u043B\u043E\u043C\u0436\u0433\u04AF\u0439 (ECDSA credential \u0437\u04E9\u0432\u0445\u04E9\u043D SHA-256)`);
|
|
810
|
+
}
|
|
655
811
|
} else {
|
|
656
812
|
throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 signatureAlgorithm: ${alg}`);
|
|
657
813
|
}
|
|
@@ -672,7 +828,7 @@ var ResponseValidator = class {
|
|
|
672
828
|
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");
|
|
673
829
|
if (!result.signatureValueB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0430\u043B\u0433\u0430");
|
|
674
830
|
if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
|
|
675
|
-
const leaf = new
|
|
831
|
+
const leaf = new import_node_crypto4.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
|
|
676
832
|
this.checkValidity(leaf);
|
|
677
833
|
this.checkLevel(String(result.certificateLevel ?? ""));
|
|
678
834
|
if (this.anchors.length === 0) {
|
|
@@ -750,16 +906,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
|
|
|
750
906
|
return fromResponse;
|
|
751
907
|
}
|
|
752
908
|
function userChallengeOf(verifier) {
|
|
753
|
-
return (0,
|
|
909
|
+
return (0, import_node_crypto4.createHash)("sha256").update(verifier, "utf8").digest("base64url");
|
|
754
910
|
}
|
|
755
911
|
function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
756
912
|
const sig = Buffer.from(sigB64, "base64");
|
|
757
913
|
const h = nodeHash(hash);
|
|
758
914
|
if (alg === "rsassa-pss") {
|
|
759
|
-
return (0,
|
|
915
|
+
return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
|
|
760
916
|
}
|
|
761
|
-
if (alg === "ecdsa-with-SHA256") return (0,
|
|
762
|
-
return (0,
|
|
917
|
+
if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto4.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
|
|
918
|
+
return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PADDING }, sig);
|
|
763
919
|
}
|
|
764
920
|
function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
765
921
|
const sig = Buffer.from(sigB64, "base64");
|
|
@@ -768,7 +924,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
|
768
924
|
if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
|
|
769
925
|
let em;
|
|
770
926
|
try {
|
|
771
|
-
em = (0,
|
|
927
|
+
em = (0, import_node_crypto4.publicDecrypt)({ key, padding: import_node_crypto4.constants.RSA_NO_PADDING }, sig);
|
|
772
928
|
} catch {
|
|
773
929
|
return false;
|
|
774
930
|
}
|
|
@@ -841,6 +997,7 @@ function ecdsaP256VerifyPrehashed(digest, sig, key) {
|
|
|
841
997
|
const R = ptAdd(ptMul(mod(z * w, n), [P256.gx, P256.gy]), ptMul(mod(r * w, n), Q));
|
|
842
998
|
return R !== null && mod(R[0], n) === r;
|
|
843
999
|
}
|
|
1000
|
+
var HASHES = /* @__PURE__ */ new Set(["SHA-256", "SHA-384", "SHA-512"]);
|
|
844
1001
|
var DIGEST_INFO = {
|
|
845
1002
|
"SHA-256": Buffer.from("3031300d060960864801650304020105000420", "hex"),
|
|
846
1003
|
"SHA-384": Buffer.from("3041300d060960864801650304020205000430", "hex"),
|
|
@@ -851,7 +1008,7 @@ function mgf1(hash, seed, length) {
|
|
|
851
1008
|
for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
|
|
852
1009
|
const c = Buffer.alloc(4);
|
|
853
1010
|
c.writeUInt32BE(i, 0);
|
|
854
|
-
out.push((0,
|
|
1011
|
+
out.push((0, import_node_crypto4.createHash)(nodeHash(hash)).update(seed).update(c).digest());
|
|
855
1012
|
}
|
|
856
1013
|
return Buffer.concat(out).subarray(0, length);
|
|
857
1014
|
}
|
|
@@ -875,7 +1032,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
|
|
|
875
1032
|
const psLen = emLen - hLen - sLen - 2;
|
|
876
1033
|
if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
|
|
877
1034
|
const salt = db.subarray(psLen + 1);
|
|
878
|
-
const h2 = (0,
|
|
1035
|
+
const h2 = (0, import_node_crypto4.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
|
|
879
1036
|
return h.equals(h2);
|
|
880
1037
|
}
|
|
881
1038
|
function isCa(cert) {
|
|
@@ -970,7 +1127,7 @@ function openOrShowQR(session, opts) {
|
|
|
970
1127
|
}
|
|
971
1128
|
|
|
972
1129
|
// src/devicelinkv3.ts
|
|
973
|
-
var
|
|
1130
|
+
var import_node_crypto5 = require("crypto");
|
|
974
1131
|
var TYPES = ["QR", "Web2App", "App2App"];
|
|
975
1132
|
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
976
1133
|
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
@@ -1027,7 +1184,7 @@ function buildDeviceLink(p) {
|
|
|
1027
1184
|
callback,
|
|
1028
1185
|
unprotected
|
|
1029
1186
|
].join("|");
|
|
1030
|
-
const authCode = (0,
|
|
1187
|
+
const authCode = (0, import_node_crypto5.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
1031
1188
|
return `${unprotected}&authCode=${authCode}`;
|
|
1032
1189
|
}
|
|
1033
1190
|
function qrDeviceLinkTicker(input, onLink, opts) {
|
|
@@ -1040,7 +1197,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
1040
1197
|
}
|
|
1041
1198
|
|
|
1042
1199
|
// src/oidc.ts
|
|
1043
|
-
var
|
|
1200
|
+
var import_node_crypto6 = require("crypto");
|
|
1044
1201
|
var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
|
|
1045
1202
|
var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
|
|
1046
1203
|
var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
|
|
@@ -1297,7 +1454,7 @@ function formEncode(s) {
|
|
|
1297
1454
|
function timingSafeEqualStr(a, b) {
|
|
1298
1455
|
const x = Buffer.from(a, "utf8");
|
|
1299
1456
|
const y = Buffer.from(b, "utf8");
|
|
1300
|
-
return x.length === y.length && (0,
|
|
1457
|
+
return x.length === y.length && (0, import_node_crypto6.timingSafeEqual)(x, y);
|
|
1301
1458
|
}
|
|
1302
1459
|
function decodeJsonPart(s) {
|
|
1303
1460
|
try {
|
|
@@ -1348,6 +1505,7 @@ function callbackParams(input) {
|
|
|
1348
1505
|
isMobileUserAgent,
|
|
1349
1506
|
openOrShowQR,
|
|
1350
1507
|
parseSessionResult,
|
|
1508
|
+
parseSubject,
|
|
1351
1509
|
pkceChallenge,
|
|
1352
1510
|
qrDeviceLinkTicker,
|
|
1353
1511
|
randomChallenge,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-
|
|
2
|
-
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult,
|
|
1
|
+
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PdfSessionBlock, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-B_XtyuFG.cjs';
|
|
2
|
+
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, n as PdfDocumentStatus, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.cjs';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -172,6 +172,12 @@ interface PdfPrepareSession {
|
|
|
172
172
|
docId: string;
|
|
173
173
|
/** Иргэнд ХАРУУЛАХ баталгаажуулах код (ПИН биш). */
|
|
174
174
|
vc: string | null;
|
|
175
|
+
/**
|
|
176
|
+
* Иргэний зурах DTBS (base64, SHA-256 = 32 байт) — урсгалаас үл хамааран. Хариуны гарын үсгийг өөрөө шалгахад:
|
|
177
|
+
* `validator.validateSign(result, digest)`. Хуучин CA push хариунд талбаргүй → undefined (device-link-д
|
|
178
|
+
* `deviceLink.digest`-ээс).
|
|
179
|
+
*/
|
|
180
|
+
digest?: string;
|
|
175
181
|
deviceLink: {
|
|
176
182
|
sessionToken: string;
|
|
177
183
|
sessionSecret: string;
|
|
@@ -194,6 +200,13 @@ declare class PdfApi {
|
|
|
194
200
|
* session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
|
|
195
201
|
*/
|
|
196
202
|
document(sessionId: string): Promise<Uint8Array>;
|
|
203
|
+
/**
|
|
204
|
+
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
205
|
+
* `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
|
|
206
|
+
*
|
|
207
|
+
* @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
|
|
208
|
+
*/
|
|
209
|
+
documentVerified(sessionId: string, expected: PdfSessionBlock | string): Promise<Uint8Array>;
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
declare class OrganizationApi {
|
|
@@ -203,6 +216,36 @@ declare class OrganizationApi {
|
|
|
203
216
|
getRepresentations(personEtsi: string): Promise<RepresentationsResponse>;
|
|
204
217
|
}
|
|
205
218
|
|
|
219
|
+
/** Задалсан subject. Cert-д байхгүй талбар — undefined. Давхардсан OID-ийн эхнийх нь. */
|
|
220
|
+
interface CertSubject {
|
|
221
|
+
/** 2.5.4.42 GIVENNAME. */
|
|
222
|
+
givenName?: string;
|
|
223
|
+
/** 2.5.4.4 SURNAME. */
|
|
224
|
+
surname?: string;
|
|
225
|
+
/** 2.5.4.3 CN. */
|
|
226
|
+
commonName?: string;
|
|
227
|
+
/** 2.5.4.5 SERIALNUMBER — иргэнд ETSI (`PNOMN-<иргэний дугаар>`). */
|
|
228
|
+
serialNumber?: string;
|
|
229
|
+
/** 2.5.4.6 C (ж: "MN"). */
|
|
230
|
+
country?: string;
|
|
231
|
+
/** 2.5.4.10 O. */
|
|
232
|
+
organization?: string;
|
|
233
|
+
/** 2.5.4.97 organizationIdentifier (ж: `NTRMN-…`). */
|
|
234
|
+
organizationIdentifier?: string;
|
|
235
|
+
/** Харуулах нэр: «givenName surname», эс бөгөөс commonName, эс бөгөөс null. */
|
|
236
|
+
name: string | null;
|
|
237
|
+
/** Бүх атрибут, OID → утга (эхний утга). Дээрхээс бусад OID-д. */
|
|
238
|
+
attributes: Record<string, string>;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Гэрчилгээний subject-ийг задална. `cert` — `X509Certificate`, DER байт, эсвэл base64 DER
|
|
242
|
+
* (session хариуны `certificateDerB64`). Гэмтсэн DER → {@link ValidationError}.
|
|
243
|
+
*
|
|
244
|
+
* (!) Энэ нь зөвхөн задлалт — гэрчилгээнд итгэх эсэхийг `ResponseValidator` шийднэ. Баталгаажаагүй
|
|
245
|
+
* гэрчилгээний нэрэнд бүү итгэ: `validateAuth`/`validateSign`-ийн `parsedSubject`-ийг ашигла.
|
|
246
|
+
*/
|
|
247
|
+
declare function parseSubject(cert: X509Certificate | Uint8Array | string): CertSubject;
|
|
248
|
+
|
|
206
249
|
/**
|
|
207
250
|
* validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
|
|
208
251
|
* (device-link relay хаалт).
|
|
@@ -228,6 +271,20 @@ interface ValidateAuthOptions {
|
|
|
228
271
|
*/
|
|
229
272
|
userChallengeVerifier?: string;
|
|
230
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* validateSign-ийн нэмэлт шалгалт. Гуравдах параметрт мөр (`hashAlgorithm`) эсвэл энэ объектыг өгнө.
|
|
276
|
+
*/
|
|
277
|
+
interface ValidateSignOptions {
|
|
278
|
+
/** digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256). */
|
|
279
|
+
hashAlgorithm?: string;
|
|
280
|
+
/**
|
|
281
|
+
* RP-ийн эхлүүлсэн урсгал (device-link: "QR" / "Web2App" / "App2App"; push → "Notification"). Өгвөл хариуны
|
|
282
|
+
* `flowType` ЯГ ИЖИЛ байх ёстой, эс бөгөөс `ValidationError` — validateAuth-ийн `expectedFlowType`-тэй ижил.
|
|
283
|
+
* ⚠️ RAW_DIGEST_SIGNATURE-д `flowType` гарын үсэгт ОРДОГГҮЙ (зөвхөн digest зурагдана) — энэ нь RP-API хариуны
|
|
284
|
+
* тулгалт, криптограф баталгаа биш.
|
|
285
|
+
*/
|
|
286
|
+
expectedFlowType?: FlowType;
|
|
287
|
+
}
|
|
231
288
|
/**
|
|
232
289
|
* RevocationChecker — иргэний cert revoke хийгдсэн эсэхийг шалгах hook (OCSP/CRL).
|
|
233
290
|
* Сүлжээний I/O шаардсан тул async. RP нь cert доторх AIA (OCSP) / CDP (CRL) URL-аар
|
|
@@ -271,6 +328,8 @@ interface VerifiedIdentity {
|
|
|
271
328
|
certificate: X509Certificate;
|
|
272
329
|
/** Cert subject DN (түүхий). Ж: "C=MN, CN=..., SERIALNUMBER=PNOMN-...". */
|
|
273
330
|
subject: string;
|
|
331
|
+
/** Subject-ийн задалсан талбарууд (нэр, ETSI `serialNumber`, улс) — {@link parseSubject}. */
|
|
332
|
+
parsedSubject: CertSubject;
|
|
274
333
|
certificateLevel: string;
|
|
275
334
|
}
|
|
276
335
|
/** Баталгаажсан гарын үсэг (sign). */
|
|
@@ -280,6 +339,8 @@ interface VerifiedSignature {
|
|
|
280
339
|
signatureAlgorithm: string | null;
|
|
281
340
|
certificate: X509Certificate;
|
|
282
341
|
subject: string;
|
|
342
|
+
/** Subject-ийн задалсан талбарууд — {@link parseSubject}. */
|
|
343
|
+
parsedSubject: CertSubject;
|
|
283
344
|
}
|
|
284
345
|
declare class ResponseValidator {
|
|
285
346
|
private readonly anchors;
|
|
@@ -297,12 +358,21 @@ declare class ResponseValidator {
|
|
|
297
358
|
validateAuth(result: SessionResult, acsp: AcspContext, opts?: ValidateAuthOptions): VerifiedIdentity;
|
|
298
359
|
/**
|
|
299
360
|
* Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
|
|
300
|
-
* @param
|
|
361
|
+
* @param opts — `hashAlgorithm` мөр (хуучин хэлбэр; default SHA-256) эсвэл {@link ValidateSignOptions}.
|
|
301
362
|
*/
|
|
302
|
-
validateSign(result: SessionResult, digestB64: string,
|
|
363
|
+
validateSign(result: SessionResult, digestB64: string, opts?: string | ValidateSignOptions): VerifiedSignature;
|
|
303
364
|
/**
|
|
304
|
-
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй
|
|
305
|
-
*
|
|
365
|
+
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
|
|
366
|
+
* (downgrade / alg confusion хаалт).
|
|
367
|
+
*
|
|
368
|
+
* CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
|
|
369
|
+
* RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
|
|
370
|
+
* hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
|
|
371
|
+
* зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
|
|
372
|
+
* Иймд:
|
|
373
|
+
* - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
|
|
374
|
+
* - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
|
|
375
|
+
* EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
|
|
306
376
|
*/
|
|
307
377
|
private algorithmOf;
|
|
308
378
|
/** state/endResult, cert байгаа эсэх, chain, validity, certLevel — нийтлэг шалгалт. */
|
|
@@ -593,4 +663,4 @@ declare class EidOidcClient {
|
|
|
593
663
|
/** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
|
|
594
664
|
declare function pkceChallenge(codeVerifier: string): Promise<string>;
|
|
595
665
|
|
|
596
|
-
export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
|
|
666
|
+
export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-
|
|
2
|
-
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult,
|
|
1
|
+
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PdfSessionBlock, b as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-B_XtyuFG.js';
|
|
2
|
+
export { c as ApiError, d as AuthenticationError, e as DeviceLinkError, f as DeviceLinkInput, g as EndResult, h as ForbiddenError, i as NavigatorLike, j as NetworkError, O as ON_BEHALF_ERROR_CODES, k as OnBehalfErrorCode, l as OpenOrShowQROptions, m as OrgResult, n as PdfDocumentStatus, o as PdfSigner, p as Representation, q as SessionFailedError, r as SessionState, s as SignatureAlgorithmParameters, t as SignatureProtocol, V as ValidationError, u as deviceLink, v as isMobileUserAgent, w as openOrShowQR } from './browser-B_XtyuFG.js';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -172,6 +172,12 @@ interface PdfPrepareSession {
|
|
|
172
172
|
docId: string;
|
|
173
173
|
/** Иргэнд ХАРУУЛАХ баталгаажуулах код (ПИН биш). */
|
|
174
174
|
vc: string | null;
|
|
175
|
+
/**
|
|
176
|
+
* Иргэний зурах DTBS (base64, SHA-256 = 32 байт) — урсгалаас үл хамааран. Хариуны гарын үсгийг өөрөө шалгахад:
|
|
177
|
+
* `validator.validateSign(result, digest)`. Хуучин CA push хариунд талбаргүй → undefined (device-link-д
|
|
178
|
+
* `deviceLink.digest`-ээс).
|
|
179
|
+
*/
|
|
180
|
+
digest?: string;
|
|
175
181
|
deviceLink: {
|
|
176
182
|
sessionToken: string;
|
|
177
183
|
sessionSecret: string;
|
|
@@ -194,6 +200,13 @@ declare class PdfApi {
|
|
|
194
200
|
* session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
|
|
195
201
|
*/
|
|
196
202
|
document(sessionId: string): Promise<Uint8Array>;
|
|
203
|
+
/**
|
|
204
|
+
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
205
|
+
* `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
|
|
206
|
+
*
|
|
207
|
+
* @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
|
|
208
|
+
*/
|
|
209
|
+
documentVerified(sessionId: string, expected: PdfSessionBlock | string): Promise<Uint8Array>;
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
declare class OrganizationApi {
|
|
@@ -203,6 +216,36 @@ declare class OrganizationApi {
|
|
|
203
216
|
getRepresentations(personEtsi: string): Promise<RepresentationsResponse>;
|
|
204
217
|
}
|
|
205
218
|
|
|
219
|
+
/** Задалсан subject. Cert-д байхгүй талбар — undefined. Давхардсан OID-ийн эхнийх нь. */
|
|
220
|
+
interface CertSubject {
|
|
221
|
+
/** 2.5.4.42 GIVENNAME. */
|
|
222
|
+
givenName?: string;
|
|
223
|
+
/** 2.5.4.4 SURNAME. */
|
|
224
|
+
surname?: string;
|
|
225
|
+
/** 2.5.4.3 CN. */
|
|
226
|
+
commonName?: string;
|
|
227
|
+
/** 2.5.4.5 SERIALNUMBER — иргэнд ETSI (`PNOMN-<иргэний дугаар>`). */
|
|
228
|
+
serialNumber?: string;
|
|
229
|
+
/** 2.5.4.6 C (ж: "MN"). */
|
|
230
|
+
country?: string;
|
|
231
|
+
/** 2.5.4.10 O. */
|
|
232
|
+
organization?: string;
|
|
233
|
+
/** 2.5.4.97 organizationIdentifier (ж: `NTRMN-…`). */
|
|
234
|
+
organizationIdentifier?: string;
|
|
235
|
+
/** Харуулах нэр: «givenName surname», эс бөгөөс commonName, эс бөгөөс null. */
|
|
236
|
+
name: string | null;
|
|
237
|
+
/** Бүх атрибут, OID → утга (эхний утга). Дээрхээс бусад OID-д. */
|
|
238
|
+
attributes: Record<string, string>;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Гэрчилгээний subject-ийг задална. `cert` — `X509Certificate`, DER байт, эсвэл base64 DER
|
|
242
|
+
* (session хариуны `certificateDerB64`). Гэмтсэн DER → {@link ValidationError}.
|
|
243
|
+
*
|
|
244
|
+
* (!) Энэ нь зөвхөн задлалт — гэрчилгээнд итгэх эсэхийг `ResponseValidator` шийднэ. Баталгаажаагүй
|
|
245
|
+
* гэрчилгээний нэрэнд бүү итгэ: `validateAuth`/`validateSign`-ийн `parsedSubject`-ийг ашигла.
|
|
246
|
+
*/
|
|
247
|
+
declare function parseSubject(cert: X509Certificate | Uint8Array | string): CertSubject;
|
|
248
|
+
|
|
206
249
|
/**
|
|
207
250
|
* validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
|
|
208
251
|
* (device-link relay хаалт).
|
|
@@ -228,6 +271,20 @@ interface ValidateAuthOptions {
|
|
|
228
271
|
*/
|
|
229
272
|
userChallengeVerifier?: string;
|
|
230
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* validateSign-ийн нэмэлт шалгалт. Гуравдах параметрт мөр (`hashAlgorithm`) эсвэл энэ объектыг өгнө.
|
|
276
|
+
*/
|
|
277
|
+
interface ValidateSignOptions {
|
|
278
|
+
/** digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256). */
|
|
279
|
+
hashAlgorithm?: string;
|
|
280
|
+
/**
|
|
281
|
+
* RP-ийн эхлүүлсэн урсгал (device-link: "QR" / "Web2App" / "App2App"; push → "Notification"). Өгвөл хариуны
|
|
282
|
+
* `flowType` ЯГ ИЖИЛ байх ёстой, эс бөгөөс `ValidationError` — validateAuth-ийн `expectedFlowType`-тэй ижил.
|
|
283
|
+
* ⚠️ RAW_DIGEST_SIGNATURE-д `flowType` гарын үсэгт ОРДОГГҮЙ (зөвхөн digest зурагдана) — энэ нь RP-API хариуны
|
|
284
|
+
* тулгалт, криптограф баталгаа биш.
|
|
285
|
+
*/
|
|
286
|
+
expectedFlowType?: FlowType;
|
|
287
|
+
}
|
|
231
288
|
/**
|
|
232
289
|
* RevocationChecker — иргэний cert revoke хийгдсэн эсэхийг шалгах hook (OCSP/CRL).
|
|
233
290
|
* Сүлжээний I/O шаардсан тул async. RP нь cert доторх AIA (OCSP) / CDP (CRL) URL-аар
|
|
@@ -271,6 +328,8 @@ interface VerifiedIdentity {
|
|
|
271
328
|
certificate: X509Certificate;
|
|
272
329
|
/** Cert subject DN (түүхий). Ж: "C=MN, CN=..., SERIALNUMBER=PNOMN-...". */
|
|
273
330
|
subject: string;
|
|
331
|
+
/** Subject-ийн задалсан талбарууд (нэр, ETSI `serialNumber`, улс) — {@link parseSubject}. */
|
|
332
|
+
parsedSubject: CertSubject;
|
|
274
333
|
certificateLevel: string;
|
|
275
334
|
}
|
|
276
335
|
/** Баталгаажсан гарын үсэг (sign). */
|
|
@@ -280,6 +339,8 @@ interface VerifiedSignature {
|
|
|
280
339
|
signatureAlgorithm: string | null;
|
|
281
340
|
certificate: X509Certificate;
|
|
282
341
|
subject: string;
|
|
342
|
+
/** Subject-ийн задалсан талбарууд — {@link parseSubject}. */
|
|
343
|
+
parsedSubject: CertSubject;
|
|
283
344
|
}
|
|
284
345
|
declare class ResponseValidator {
|
|
285
346
|
private readonly anchors;
|
|
@@ -297,12 +358,21 @@ declare class ResponseValidator {
|
|
|
297
358
|
validateAuth(result: SessionResult, acsp: AcspContext, opts?: ValidateAuthOptions): VerifiedIdentity;
|
|
298
359
|
/**
|
|
299
360
|
* Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
|
|
300
|
-
* @param
|
|
361
|
+
* @param opts — `hashAlgorithm` мөр (хуучин хэлбэр; default SHA-256) эсвэл {@link ValidateSignOptions}.
|
|
301
362
|
*/
|
|
302
|
-
validateSign(result: SessionResult, digestB64: string,
|
|
363
|
+
validateSign(result: SessionResult, digestB64: string, opts?: string | ValidateSignOptions): VerifiedSignature;
|
|
303
364
|
/**
|
|
304
|
-
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй
|
|
305
|
-
*
|
|
365
|
+
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
|
|
366
|
+
* (downgrade / alg confusion хаалт).
|
|
367
|
+
*
|
|
368
|
+
* CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
|
|
369
|
+
* RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
|
|
370
|
+
* hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
|
|
371
|
+
* зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
|
|
372
|
+
* Иймд:
|
|
373
|
+
* - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
|
|
374
|
+
* - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
|
|
375
|
+
* EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
|
|
306
376
|
*/
|
|
307
377
|
private algorithmOf;
|
|
308
378
|
/** state/endResult, cert байгаа эсэх, chain, validity, certLevel — нийтлэг шалгалт. */
|
|
@@ -593,4 +663,4 @@ declare class EidOidcClient {
|
|
|
593
663
|
/** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
|
|
594
664
|
declare function pkceChallenge(codeVerifier: string): Promise<string>;
|
|
595
665
|
|
|
596
|
-
export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
|
|
666
|
+
export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
|
package/dist/index.js
CHANGED
|
@@ -381,6 +381,7 @@ var SessionApi = class {
|
|
|
381
381
|
};
|
|
382
382
|
|
|
383
383
|
// src/pdf.ts
|
|
384
|
+
import { createHash as createHash2 } from "crypto";
|
|
384
385
|
function rec3(v) {
|
|
385
386
|
return v && typeof v === "object" ? v : {};
|
|
386
387
|
}
|
|
@@ -413,10 +414,12 @@ var PdfApi = class {
|
|
|
413
414
|
const receivedAt = Date.now();
|
|
414
415
|
const vc = rec3(r.vc).value;
|
|
415
416
|
const dl = rec3(r.deviceLink);
|
|
417
|
+
const digest = typeof r.digest === "string" && r.digest ? r.digest : typeof dl.digest === "string" && dl.digest ? dl.digest : void 0;
|
|
416
418
|
return {
|
|
417
419
|
sessionId: String(r.sessionID),
|
|
418
420
|
docId: String(r.docID),
|
|
419
421
|
vc: vc != null ? String(vc) : null,
|
|
422
|
+
...digest !== void 0 ? { digest } : {},
|
|
420
423
|
deviceLink: dl.sessionToken ? {
|
|
421
424
|
sessionToken: String(dl.sessionToken),
|
|
422
425
|
sessionSecret: String(dl.sessionSecret),
|
|
@@ -434,6 +437,26 @@ var PdfApi = class {
|
|
|
434
437
|
document(sessionId) {
|
|
435
438
|
return this.http.getBytes(`/pdf/sign/${encodeURIComponent(sessionId)}/document`, 6e4);
|
|
436
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
442
|
+
* `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
|
|
443
|
+
*
|
|
444
|
+
* @throws {ValidationError} блок READY биш / `outSha256` алга / hash таарахгүй (файлд бүү итгэ).
|
|
445
|
+
*/
|
|
446
|
+
async documentVerified(sessionId, expected) {
|
|
447
|
+
let want;
|
|
448
|
+
if (typeof expected === "string") {
|
|
449
|
+
want = expected;
|
|
450
|
+
} else {
|
|
451
|
+
if (expected.documentStatus !== "READY") throw new ValidationError(`pdf.documentStatus ${expected.documentStatus} \u2260 READY`);
|
|
452
|
+
want = expected.outSha256;
|
|
453
|
+
}
|
|
454
|
+
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");
|
|
455
|
+
const bytes = await this.document(sessionId);
|
|
456
|
+
const got = createHash2("sha256").update(bytes).digest("hex");
|
|
457
|
+
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");
|
|
458
|
+
return bytes;
|
|
459
|
+
}
|
|
437
460
|
};
|
|
438
461
|
|
|
439
462
|
// src/organization.ts
|
|
@@ -451,7 +474,114 @@ var OrganizationApi = class {
|
|
|
451
474
|
};
|
|
452
475
|
|
|
453
476
|
// src/validator.ts
|
|
454
|
-
import { X509Certificate, constants, createHash as
|
|
477
|
+
import { X509Certificate as X509Certificate2, constants, createHash as createHash3, publicDecrypt, verify } from "crypto";
|
|
478
|
+
|
|
479
|
+
// src/subject.ts
|
|
480
|
+
import { X509Certificate } from "crypto";
|
|
481
|
+
var OIDS = {
|
|
482
|
+
"2.5.4.42": "givenName",
|
|
483
|
+
"2.5.4.4": "surname",
|
|
484
|
+
"2.5.4.3": "commonName",
|
|
485
|
+
"2.5.4.5": "serialNumber",
|
|
486
|
+
"2.5.4.6": "country",
|
|
487
|
+
"2.5.4.10": "organization",
|
|
488
|
+
"2.5.4.97": "organizationIdentifier"
|
|
489
|
+
};
|
|
490
|
+
function tlv(buf, pos, limit) {
|
|
491
|
+
if (pos + 2 > limit) throw new ValidationError("subject: DER \u0442\u043E\u043B\u0433\u043E\u0439 \u0431\u043E\u0433\u0438\u043D\u043E");
|
|
492
|
+
const tag = buf[pos];
|
|
493
|
+
if ((tag & 31) === 31) throw new ValidationError("subject: \u043E\u043B\u043E\u043D \u0431\u0430\u0439\u0442 tag \u0434\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439");
|
|
494
|
+
let len = buf[pos + 1];
|
|
495
|
+
let i = pos + 2;
|
|
496
|
+
if (len & 128) {
|
|
497
|
+
const n = len & 127;
|
|
498
|
+
if (n === 0 || n > 3) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u0443\u0440\u0443\u0443");
|
|
499
|
+
if (i + n > limit) throw new ValidationError("subject: DER \u0443\u0440\u0442 \u0431\u043E\u0433\u0438\u043D\u043E");
|
|
500
|
+
len = 0;
|
|
501
|
+
for (let k = 0; k < n; k++) len = len * 256 + buf[i++];
|
|
502
|
+
}
|
|
503
|
+
if (i + len > limit) throw new ValidationError("subject: DER \u0430\u0433\u0443\u0443\u043B\u0433\u0430 \u0445\u0438\u043B\u044D\u044D\u0441 \u0433\u0430\u0440\u043D\u0430");
|
|
504
|
+
return { tag, start: i, end: i + len };
|
|
505
|
+
}
|
|
506
|
+
function oidOf(buf, t) {
|
|
507
|
+
if (t.tag !== 6 || t.end === t.start) throw new ValidationError("subject: OID \u0431\u0438\u0448");
|
|
508
|
+
const parts = [];
|
|
509
|
+
let v = 0;
|
|
510
|
+
for (let i = t.start; i < t.end; i++) {
|
|
511
|
+
const b = buf[i];
|
|
512
|
+
v = v * 128 + (b & 127);
|
|
513
|
+
if (!(b & 128)) {
|
|
514
|
+
if (parts.length === 0) parts.push(v < 80 ? Math.floor(v / 40) : 2, v < 80 ? v % 40 : v - 80);
|
|
515
|
+
else parts.push(v);
|
|
516
|
+
v = 0;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return parts.join(".");
|
|
520
|
+
}
|
|
521
|
+
function stringOf(buf, t) {
|
|
522
|
+
const bytes = buf.subarray(t.start, t.end);
|
|
523
|
+
switch (t.tag) {
|
|
524
|
+
case 12:
|
|
525
|
+
try {
|
|
526
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
527
|
+
} catch {
|
|
528
|
+
throw new ValidationError("subject: UTF8String \u0431\u0443\u0440\u0443\u0443 UTF-8");
|
|
529
|
+
}
|
|
530
|
+
case 19:
|
|
531
|
+
// PrintableString
|
|
532
|
+
case 22:
|
|
533
|
+
// IA5String
|
|
534
|
+
case 20:
|
|
535
|
+
return Buffer.from(bytes).toString("latin1");
|
|
536
|
+
case 30: {
|
|
537
|
+
if (bytes.length % 2) throw new ValidationError("subject: BMPString \u0443\u0440\u0442 \u0441\u043E\u043D\u0434\u0433\u043E\u0439");
|
|
538
|
+
return Buffer.from(bytes).swap16().toString("utf16le");
|
|
539
|
+
}
|
|
540
|
+
default:
|
|
541
|
+
return void 0;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function parseSubject(cert) {
|
|
545
|
+
const der = cert instanceof X509Certificate ? new Uint8Array(cert.raw) : typeof cert === "string" ? new Uint8Array(Buffer.from(cert, "base64")) : cert;
|
|
546
|
+
const top = tlv(der, 0, der.length);
|
|
547
|
+
if (top.tag !== 48) throw new ValidationError("subject: Certificate SEQUENCE \u0431\u0438\u0448");
|
|
548
|
+
const tbs = tlv(der, top.start, top.end);
|
|
549
|
+
if (tbs.tag !== 48) throw new ValidationError("subject: tbsCertificate SEQUENCE \u0431\u0438\u0448");
|
|
550
|
+
let pos = tbs.start;
|
|
551
|
+
let first = tlv(der, pos, tbs.end);
|
|
552
|
+
if (first.tag === 160) {
|
|
553
|
+
pos = first.end;
|
|
554
|
+
first = tlv(der, pos, tbs.end);
|
|
555
|
+
}
|
|
556
|
+
let field = first;
|
|
557
|
+
for (let k = 0; k < 4; k++) field = tlv(der, field.end, tbs.end);
|
|
558
|
+
if (field.tag !== 48) throw new ValidationError("subject: Name SEQUENCE \u0431\u0438\u0448");
|
|
559
|
+
const out = { name: null, attributes: {} };
|
|
560
|
+
for (let sp = field.start; sp < field.end; ) {
|
|
561
|
+
const rdn = tlv(der, sp, field.end);
|
|
562
|
+
if (rdn.tag !== 49) throw new ValidationError("subject: RDN SET \u0431\u0438\u0448");
|
|
563
|
+
for (let ap = rdn.start; ap < rdn.end; ) {
|
|
564
|
+
const atv = tlv(der, ap, rdn.end);
|
|
565
|
+
if (atv.tag !== 48) throw new ValidationError("subject: AttributeTypeAndValue SEQUENCE \u0431\u0438\u0448");
|
|
566
|
+
const oidT = tlv(der, atv.start, atv.end);
|
|
567
|
+
const valT = tlv(der, oidT.end, atv.end);
|
|
568
|
+
const oid = oidOf(der, oidT);
|
|
569
|
+
const value = stringOf(der, valT);
|
|
570
|
+
if (value !== void 0 && !(oid in out.attributes)) {
|
|
571
|
+
out.attributes[oid] = value;
|
|
572
|
+
const key = OIDS[oid];
|
|
573
|
+
if (key) out[key] = value;
|
|
574
|
+
}
|
|
575
|
+
ap = atv.end;
|
|
576
|
+
}
|
|
577
|
+
sp = rdn.end;
|
|
578
|
+
}
|
|
579
|
+
const full = [out.givenName, out.surname].filter((s) => s).join(" ");
|
|
580
|
+
out.name = full || out.commonName || null;
|
|
581
|
+
return out;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/validator.ts
|
|
455
585
|
var ResponseValidator = class {
|
|
456
586
|
anchors;
|
|
457
587
|
intermediates;
|
|
@@ -461,8 +591,8 @@ var ResponseValidator = class {
|
|
|
461
591
|
revocation;
|
|
462
592
|
revocationMode;
|
|
463
593
|
constructor(trust = {}) {
|
|
464
|
-
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new
|
|
465
|
-
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new
|
|
594
|
+
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new X509Certificate2(p));
|
|
595
|
+
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new X509Certificate2(p));
|
|
466
596
|
this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
|
|
467
597
|
this.skew = trust.clockSkewMs ?? 0;
|
|
468
598
|
this.allowUntrusted = trust.allowUntrusted ?? false;
|
|
@@ -493,7 +623,7 @@ var ResponseValidator = class {
|
|
|
493
623
|
throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
494
624
|
}
|
|
495
625
|
if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
|
|
496
|
-
const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
626
|
+
const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
497
627
|
const payload = buildAcspV2Payload({
|
|
498
628
|
serverRandom,
|
|
499
629
|
rpChallenge: acsp.rpChallenge,
|
|
@@ -512,17 +642,22 @@ var ResponseValidator = class {
|
|
|
512
642
|
documentNumber: result.documentNumber,
|
|
513
643
|
certificate: leaf,
|
|
514
644
|
subject: leaf.subject,
|
|
645
|
+
parsedSubject: parseSubject(leaf),
|
|
515
646
|
certificateLevel: String(result.certificateLevel ?? "")
|
|
516
647
|
};
|
|
517
648
|
}
|
|
518
649
|
/**
|
|
519
650
|
* Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
|
|
520
|
-
* @param
|
|
651
|
+
* @param opts — `hashAlgorithm` мөр (хуучин хэлбэр; default SHA-256) эсвэл {@link ValidateSignOptions}.
|
|
521
652
|
*/
|
|
522
|
-
validateSign(result, digestB64,
|
|
653
|
+
validateSign(result, digestB64, opts = {}) {
|
|
654
|
+
const { hashAlgorithm = "SHA-256", expectedFlowType } = typeof opts === "string" ? { hashAlgorithm: opts } : opts;
|
|
523
655
|
const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
|
|
656
|
+
if (expectedFlowType && result.flowType !== expectedFlowType) {
|
|
657
|
+
throw new ValidationError(`flowType ${result.flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
|
|
658
|
+
}
|
|
524
659
|
const digest = Buffer.from(digestB64, "base64");
|
|
525
|
-
const { alg, hash } = this.algorithmOf(result, null, hashAlgorithm);
|
|
660
|
+
const { alg, hash } = this.algorithmOf(result, leaf, null, hashAlgorithm);
|
|
526
661
|
if (digest.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
527
662
|
if (!verifyPrehashedSignature(digest, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
528
663
|
throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u04E9\u0433\u0441\u04E9\u043D digest-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
@@ -532,27 +667,47 @@ var ResponseValidator = class {
|
|
|
532
667
|
signatureValueB64: result.signatureValueB64,
|
|
533
668
|
signatureAlgorithm: result.signatureAlgorithm,
|
|
534
669
|
certificate: leaf,
|
|
535
|
-
subject: leaf.subject
|
|
670
|
+
subject: leaf.subject,
|
|
671
|
+
parsedSubject: parseSubject(leaf)
|
|
536
672
|
};
|
|
537
673
|
}
|
|
538
674
|
/**
|
|
539
|
-
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй
|
|
540
|
-
*
|
|
675
|
+
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй ба иргэний түлхүүрийн төрөлтэй тулгана
|
|
676
|
+
* (downgrade / alg confusion хаалт).
|
|
677
|
+
*
|
|
678
|
+
* CA-ийн бодит зан (ca-eidmongolia-mn `service/memory/crypto_wired.go` `messageHash`, `domain/enums.go`):
|
|
679
|
+
* RP-API session-ий гарын үсэг хоёр замаас л гарна — SplitKey RSA (`rsassa-pss` / `shaNNNWithRSAEncryption`,
|
|
680
|
+
* hash = хүсэлтийн `hashAlgorithm`) ба legacy threshold ECDSA P-256 (ҮРГЭЛЖ `ecdsa-with-SHA256`). ECDSA credential
|
|
681
|
+
* зөвхөн 32 байт digest зурдаг: session-ий hash SHA-256 биш бол сервер гарын үсэг гаргахгүй (session OK болохгүй).
|
|
682
|
+
* Иймд:
|
|
683
|
+
* - hash нь ҮРГЭЛЖ хүссэнтэй яг тэнцүү — `ecdsa-with-SHA256` хариу SHA-384/512 хүссэн session-д ирвэл хуурамч;
|
|
684
|
+
* - `ecdsa-with-SHA256` нь хүссэн `rsassa-pss`-ийн оронд ирж болно (credential-ийн төрөл), ГЭХДЭЭ зөвхөн
|
|
685
|
+
* EC P-256 түлхүүртэй гэрчилгээнд; RSA алгоритмууд зөвхөн RSA түлхүүрт (alg confusion үгүй).
|
|
541
686
|
*/
|
|
542
|
-
algorithmOf(result, wantAlg, wantHash) {
|
|
687
|
+
algorithmOf(result, leaf, wantAlg, wantHash) {
|
|
543
688
|
const alg = result.signatureAlgorithm ?? "";
|
|
689
|
+
const keyType = leaf.publicKey.asymmetricKeyType;
|
|
544
690
|
let hash;
|
|
545
691
|
if (alg === "rsassa-pss") {
|
|
546
692
|
hash = String(result.signatureAlgorithmParameters?.hashAlgorithm ?? "");
|
|
693
|
+
if (!HASHES.has(hash)) throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 hashAlgorithm: ${hash}`);
|
|
547
694
|
const mgf = result.signatureAlgorithmParameters?.maskGenAlgorithm?.parameters?.hashAlgorithm;
|
|
548
695
|
if (mgf && mgf !== hash) throw new ValidationError("MGF1 hash \u043D\u044C hashAlgorithm-\u0430\u0430\u0441 \u04E9\u04E9\u0440");
|
|
549
696
|
if (result.signatureAlgorithmParameters?.saltLength != null && result.signatureAlgorithmParameters.saltLength !== hashLen(hash)) {
|
|
550
697
|
throw new ValidationError("saltLength \u2260 hLen");
|
|
551
698
|
}
|
|
699
|
+
if (keyType !== "rsa" && keyType !== "rsa-pss") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
|
|
552
700
|
} else if (/^sha(256|384|512)WithRSAEncryption$/.test(alg)) {
|
|
553
701
|
hash = "SHA-" + alg.slice(3, 6);
|
|
702
|
+
if (keyType !== "rsa") throw new ValidationError(`${alg} \u043D\u044C ${keyType} \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
|
|
554
703
|
} else if (alg === "ecdsa-with-SHA256") {
|
|
555
704
|
hash = "SHA-256";
|
|
705
|
+
if (keyType !== "ec" || leaf.publicKey.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
|
|
706
|
+
throw new ValidationError(`${alg} \u043D\u044C EC P-256 \u0431\u0443\u0441 \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439`);
|
|
707
|
+
}
|
|
708
|
+
if (wantHash !== hash) {
|
|
709
|
+
throw new ValidationError(`ecdsa-with-SHA256 \u0445\u0430\u0440\u0438\u0443 ${wantHash} \u0445\u04AF\u0441\u0441\u044D\u043D session-\u0434 \u0431\u043E\u043B\u043E\u043C\u0436\u0433\u04AF\u0439 (ECDSA credential \u0437\u04E9\u0432\u0445\u04E9\u043D SHA-256)`);
|
|
710
|
+
}
|
|
556
711
|
} else {
|
|
557
712
|
throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 signatureAlgorithm: ${alg}`);
|
|
558
713
|
}
|
|
@@ -573,7 +728,7 @@ var ResponseValidator = class {
|
|
|
573
728
|
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");
|
|
574
729
|
if (!result.signatureValueB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0430\u043B\u0433\u0430");
|
|
575
730
|
if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
|
|
576
|
-
const leaf = new
|
|
731
|
+
const leaf = new X509Certificate2(Buffer.from(result.certificateDerB64, "base64"));
|
|
577
732
|
this.checkValidity(leaf);
|
|
578
733
|
this.checkLevel(String(result.certificateLevel ?? ""));
|
|
579
734
|
if (this.anchors.length === 0) {
|
|
@@ -651,7 +806,7 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
|
|
|
651
806
|
return fromResponse;
|
|
652
807
|
}
|
|
653
808
|
function userChallengeOf(verifier) {
|
|
654
|
-
return
|
|
809
|
+
return createHash3("sha256").update(verifier, "utf8").digest("base64url");
|
|
655
810
|
}
|
|
656
811
|
function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
657
812
|
const sig = Buffer.from(sigB64, "base64");
|
|
@@ -742,6 +897,7 @@ function ecdsaP256VerifyPrehashed(digest, sig, key) {
|
|
|
742
897
|
const R = ptAdd(ptMul(mod(z * w, n), [P256.gx, P256.gy]), ptMul(mod(r * w, n), Q));
|
|
743
898
|
return R !== null && mod(R[0], n) === r;
|
|
744
899
|
}
|
|
900
|
+
var HASHES = /* @__PURE__ */ new Set(["SHA-256", "SHA-384", "SHA-512"]);
|
|
745
901
|
var DIGEST_INFO = {
|
|
746
902
|
"SHA-256": Buffer.from("3031300d060960864801650304020105000420", "hex"),
|
|
747
903
|
"SHA-384": Buffer.from("3041300d060960864801650304020205000430", "hex"),
|
|
@@ -752,7 +908,7 @@ function mgf1(hash, seed, length) {
|
|
|
752
908
|
for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
|
|
753
909
|
const c = Buffer.alloc(4);
|
|
754
910
|
c.writeUInt32BE(i, 0);
|
|
755
|
-
out.push(
|
|
911
|
+
out.push(createHash3(nodeHash(hash)).update(seed).update(c).digest());
|
|
756
912
|
}
|
|
757
913
|
return Buffer.concat(out).subarray(0, length);
|
|
758
914
|
}
|
|
@@ -776,7 +932,7 @@ function emsaPssVerify(mHash, em, emBits, hash) {
|
|
|
776
932
|
const psLen = emLen - hLen - sLen - 2;
|
|
777
933
|
if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
|
|
778
934
|
const salt = db.subarray(psLen + 1);
|
|
779
|
-
const h2 =
|
|
935
|
+
const h2 = createHash3(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
|
|
780
936
|
return h.equals(h2);
|
|
781
937
|
}
|
|
782
938
|
function isCa(cert) {
|
|
@@ -1206,6 +1362,7 @@ export {
|
|
|
1206
1362
|
isMobileUserAgent,
|
|
1207
1363
|
openOrShowQR,
|
|
1208
1364
|
parseSessionResult,
|
|
1365
|
+
parseSubject,
|
|
1209
1366
|
pkceChallenge,
|
|
1210
1367
|
qrDeviceLinkTicker,
|
|
1211
1368
|
randomChallenge,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gerege-systems/eid-mongolia-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|