@gerege-systems/eid-mongolia-sdk 0.15.0 → 0.15.2
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 +83 -14
- package/dist/button.cjs +55 -45
- package/dist/button.js +55 -45
- package/dist/chunk-52O3T7DZ.js +1043 -0
- package/dist/index.cjs +885 -79
- package/dist/index.d.cts +135 -4
- package/dist/index.d.ts +135 -4
- package/dist/index.js +312 -39
- package/dist/pades.cjs +16 -3
- package/dist/pades.d.cts +5 -0
- package/dist/pades.d.ts +5 -0
- package/dist/pades.js +61 -1028
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,12 +52,14 @@ __export(index_exports, {
|
|
|
52
52
|
buildDeviceLink: () => buildDeviceLink,
|
|
53
53
|
buildRegisterBody: () => buildRegisterBody,
|
|
54
54
|
createEidLogin: () => createEidLogin,
|
|
55
|
+
defaultRateLimit: () => defaultRateLimit,
|
|
55
56
|
deviceLink: () => deviceLink,
|
|
56
57
|
deviceLinkInput: () => deviceLinkInput,
|
|
57
58
|
forwardedClientIp: () => forwardedClientIp,
|
|
58
59
|
interactionsToBase64: () => interactionsToBase64,
|
|
59
60
|
isMobileUserAgent: () => isMobileUserAgent,
|
|
60
61
|
memoryLoginStore: () => memoryLoginStore,
|
|
62
|
+
ocspRevocationChecker: () => ocspRevocationChecker,
|
|
61
63
|
openOrShowQR: () => openOrShowQR,
|
|
62
64
|
parseExpiresAt: () => parseExpiresAt,
|
|
63
65
|
parseRetryAfter: () => parseRetryAfter,
|
|
@@ -223,6 +225,13 @@ var Http = class {
|
|
|
223
225
|
post(path, body, timeoutMs) {
|
|
224
226
|
return this.json("POST", path, JSON.stringify(body), "application/json", timeoutMs);
|
|
225
227
|
}
|
|
228
|
+
/** {@link post} + HTTP статус (201 / 200-г ялгах: `POST /pdf/signatures`). */
|
|
229
|
+
async postWithStatus(path, body, timeoutMs) {
|
|
230
|
+
const op = `POST ${maskPath(path)}`;
|
|
231
|
+
const res = await this.send("POST", path, JSON.stringify(body), "application/json", timeoutMs);
|
|
232
|
+
const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, op));
|
|
233
|
+
return { status: res.status, body: text ? JSON.parse(text) : {} };
|
|
234
|
+
}
|
|
226
235
|
get(path, timeoutMs) {
|
|
227
236
|
return this.json("GET", path, void 0, void 0, timeoutMs);
|
|
228
237
|
}
|
|
@@ -536,12 +545,32 @@ function registrationFromCompleted(done, extra) {
|
|
|
536
545
|
if (extra.medium) out.medium = extra.medium;
|
|
537
546
|
if (extra.sessionID) out.sessionID = extra.sessionID;
|
|
538
547
|
if (done.hasPage && done.pageSha256) out.pageSha256 = done.pageSha256;
|
|
539
|
-
if (
|
|
540
|
-
|
|
548
|
+
if (extra.priorRevisionSha256?.length) {
|
|
549
|
+
if (done.priorCmsSha256.length) out.priorCmsSha256 = done.priorCmsSha256;
|
|
550
|
+
out.priorRevisionSha256 = extra.priorRevisionSha256;
|
|
551
|
+
} else if (done.priorCmsSha256.length) {
|
|
552
|
+
out.priorCmsSha256 = done.priorCmsSha256;
|
|
553
|
+
const rev = done.priorRevisionSha256 ?? [];
|
|
554
|
+
if (rev.length === done.priorCmsSha256.length) {
|
|
555
|
+
out.priorSignatures = done.priorCmsSha256.map((c, i) => ({ cmsSha256: c, revisionSha256: rev[i] }));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
541
558
|
if (extra.onBehalfOf) out.onBehalfOf = extra.onBehalfOf;
|
|
542
559
|
if (extra.signerEtsi) out.signerEtsi = extra.signerEtsi;
|
|
543
560
|
return out;
|
|
544
561
|
}
|
|
562
|
+
function priorSplits(n) {
|
|
563
|
+
const out = [];
|
|
564
|
+
const seen = /* @__PURE__ */ new Set();
|
|
565
|
+
for (let k = 1; k <= n; k++) {
|
|
566
|
+
seen.add((1 << k) - 1);
|
|
567
|
+
out.push(Array.from({ length: n }, (_, i) => i < k));
|
|
568
|
+
}
|
|
569
|
+
if (n <= 4) {
|
|
570
|
+
for (let m = 1; m < 1 << n; m++) if (!seen.has(m)) out.push(Array.from({ length: n }, (_, i) => (m & 1 << i) !== 0));
|
|
571
|
+
}
|
|
572
|
+
return out;
|
|
573
|
+
}
|
|
545
574
|
var HEX64 = /^[0-9a-f]{64}$/;
|
|
546
575
|
var MAX_CMS_BYTES = 28672;
|
|
547
576
|
function buildRegisterBody(input) {
|
|
@@ -695,7 +724,7 @@ var SignApi = class {
|
|
|
695
724
|
* (base64 → байт) `complete(state, signature)`-д.
|
|
696
725
|
*/
|
|
697
726
|
async pdfDigest(input) {
|
|
698
|
-
const
|
|
727
|
+
const digest2 = dtbsBase64(input.dtbs);
|
|
699
728
|
const fileName = input.fileName?.trim() ?? "";
|
|
700
729
|
if (!fileName || Array.from(fileName).length > PDF_MAX_FILE_NAME) {
|
|
701
730
|
throw new ValidationError(`pdfDigest: fileName \u0437\u0430\u0430\u0432\u0430\u043B, \u2264 ${PDF_MAX_FILE_NAME} \u0442\u044D\u043C\u0434\u044D\u0433\u0442`);
|
|
@@ -713,7 +742,7 @@ var SignApi = class {
|
|
|
713
742
|
certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
|
|
714
743
|
signatureProtocol: "RAW_DIGEST_SIGNATURE",
|
|
715
744
|
signatureProtocolParameters: {
|
|
716
|
-
digest,
|
|
745
|
+
digest: digest2,
|
|
717
746
|
signatureAlgorithm: "sha256WithRSAEncryption",
|
|
718
747
|
signatureAlgorithmParameters: { hashAlgorithm: "SHA-256" }
|
|
719
748
|
},
|
|
@@ -733,7 +762,7 @@ var SignApi = class {
|
|
|
733
762
|
}
|
|
734
763
|
dl = { sessionToken: String(r.sessionToken), sessionSecret: String(r.sessionSecret), deviceLinkBase: String(r.deviceLinkBase) };
|
|
735
764
|
}
|
|
736
|
-
return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, digest, interactions, deviceLink: dl, receivedAt };
|
|
765
|
+
return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, digest: digest2, interactions, deviceLink: dl, receivedAt };
|
|
737
766
|
}
|
|
738
767
|
/**
|
|
739
768
|
* `POST /pdf/signatures` — `pades.complete()`-ийн дараа CMS-ийг CA-д бүртгэнэ (`/verify/{docID}`, иргэний түүх). PDF
|
|
@@ -743,16 +772,38 @@ var SignApi = class {
|
|
|
743
772
|
* `/verify` «бүртгэлгүй» гэнэ.
|
|
744
773
|
*/
|
|
745
774
|
async registerSignature(input) {
|
|
746
|
-
|
|
775
|
+
const body = buildRegisterBody(input);
|
|
776
|
+
const prior = input.priorSignatures ?? [];
|
|
777
|
+
if (input.priorRevisionSha256?.length || prior.length === 0 || prior.length !== input.revIndex - 1) return this.registerBody(body);
|
|
778
|
+
try {
|
|
779
|
+
return await this.registerBody(body);
|
|
780
|
+
} catch (first) {
|
|
781
|
+
if (!(first instanceof ApiError && first.code === "PRIOR_MISMATCH")) throw first;
|
|
782
|
+
for (const asRevision of priorSplits(prior.length)) {
|
|
783
|
+
const next = {
|
|
784
|
+
...input,
|
|
785
|
+
priorSignatures: void 0,
|
|
786
|
+
priorCmsSha256: prior.filter((_, i) => !asRevision[i]).map((p) => p.cmsSha256),
|
|
787
|
+
priorRevisionSha256: prior.filter((_, i) => asRevision[i]).map((p) => p.revisionSha256)
|
|
788
|
+
};
|
|
789
|
+
try {
|
|
790
|
+
return await this.registerBody(buildRegisterBody(next));
|
|
791
|
+
} catch (e) {
|
|
792
|
+
if (!(e instanceof ApiError && e.code === "PRIOR_MISMATCH")) throw e;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
throw first;
|
|
796
|
+
}
|
|
747
797
|
}
|
|
748
798
|
async registerBody(body) {
|
|
749
|
-
const raw = await this.http.
|
|
799
|
+
const { status, body: raw } = await this.http.postWithStatus(routePath(RP_ROUTES.pdfSignatures), body);
|
|
750
800
|
const r = rec2(raw);
|
|
751
801
|
return {
|
|
752
802
|
docID: String(r.docID ?? body.docID),
|
|
753
803
|
revIndex: Number(r.revIndex ?? 0),
|
|
754
804
|
registeredAt: typeof r.registeredAt === "string" ? r.registeredAt : "",
|
|
755
805
|
verifyUrl: String(r.verifyUrl ?? ""),
|
|
806
|
+
created: status === 201,
|
|
756
807
|
raw
|
|
757
808
|
};
|
|
758
809
|
}
|
|
@@ -1112,11 +1163,11 @@ function parseSubject(cert) {
|
|
|
1112
1163
|
if (atv.tag !== 48) throw new ValidationError("subject: AttributeTypeAndValue SEQUENCE \u0431\u0438\u0448");
|
|
1113
1164
|
const oidT = tlv(der, atv.start, atv.end);
|
|
1114
1165
|
const valT = tlv(der, oidT.end, atv.end);
|
|
1115
|
-
const
|
|
1166
|
+
const oid2 = oidOf(der, oidT);
|
|
1116
1167
|
const value = stringOf(der, valT);
|
|
1117
|
-
if (value !== void 0 && !(
|
|
1118
|
-
out.attributes[
|
|
1119
|
-
const key = OIDS[
|
|
1168
|
+
if (value !== void 0 && !(oid2 in out.attributes)) {
|
|
1169
|
+
out.attributes[oid2] = value;
|
|
1170
|
+
const key = OIDS[oid2];
|
|
1120
1171
|
if (key) out[key] = value;
|
|
1121
1172
|
}
|
|
1122
1173
|
ap = atv.end;
|
|
@@ -1127,8 +1178,8 @@ function parseSubject(cert) {
|
|
|
1127
1178
|
out.name = full || out.commonName || null;
|
|
1128
1179
|
return out;
|
|
1129
1180
|
}
|
|
1130
|
-
function certExtension(der,
|
|
1131
|
-
return certExtensions(der).find((e) => e.oid ===
|
|
1181
|
+
function certExtension(der, oid2) {
|
|
1182
|
+
return certExtensions(der).find((e) => e.oid === oid2)?.value;
|
|
1132
1183
|
}
|
|
1133
1184
|
function certExtensions(der) {
|
|
1134
1185
|
const out = [];
|
|
@@ -1140,9 +1191,9 @@ function certExtensions(der) {
|
|
|
1140
1191
|
for (let pos = f.end; pos < tbs.end; ) {
|
|
1141
1192
|
const t = tlv(der, pos, tbs.end);
|
|
1142
1193
|
if (t.tag === 163) {
|
|
1143
|
-
const
|
|
1144
|
-
for (let ep =
|
|
1145
|
-
const ext = tlv(der, ep,
|
|
1194
|
+
const seq2 = tlv(der, t.start, t.end);
|
|
1195
|
+
for (let ep = seq2.start; ep < seq2.end; ) {
|
|
1196
|
+
const ext = tlv(der, ep, seq2.end);
|
|
1146
1197
|
const id = tlv(der, ext.start, ext.end);
|
|
1147
1198
|
let v = tlv(der, id.end, ext.end);
|
|
1148
1199
|
let critical = false;
|
|
@@ -1431,10 +1482,10 @@ var ResponseValidator = class {
|
|
|
1431
1482
|
`\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D certificate-choice-\u043E\u043E\u0440 \u0441\u043E\u043D\u0433\u043E\u0441\u043E\u043D\u0442\u043E\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439 (serial ${leaf.serialNumber} \u2260 ${o.expectedCertificate.serialNumber})`
|
|
1432
1483
|
);
|
|
1433
1484
|
}
|
|
1434
|
-
const
|
|
1485
|
+
const digest2 = Buffer.from(digestB64, "base64");
|
|
1435
1486
|
const { alg, hash } = this.algorithmOf(result, leaf, o.expectedAlgorithm ?? null, hashAlgorithm);
|
|
1436
|
-
if (
|
|
1437
|
-
if (!verifyPrehashedSignature(
|
|
1487
|
+
if (digest2.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
1488
|
+
if (!verifyPrehashedSignature(digest2, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
1438
1489
|
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");
|
|
1439
1490
|
}
|
|
1440
1491
|
return {
|
|
@@ -1640,13 +1691,20 @@ var ResponseValidator = class {
|
|
|
1640
1691
|
* "skipped" (hook алга). "revoked", мөн hard-fail горимд "unknown" → ValidationError; hook-ийн алдаа дамжина.
|
|
1641
1692
|
*/
|
|
1642
1693
|
async checkRevocationStatus(cert) {
|
|
1643
|
-
|
|
1644
|
-
|
|
1694
|
+
return this.checkRevocationWith(cert, this.revocation, this.revocationMode);
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* {@link checkRevocationStatus}-тэй ижил, гэхдээ TrustConfig-ийн биш өгсөн checker ба горимоор (issuer-ийг trust
|
|
1698
|
+
* pool-оос олно). checker алга бол "skipped"; горим анхдагч "hard-fail".
|
|
1699
|
+
*/
|
|
1700
|
+
async checkRevocationWith(cert, checker, mode = "hard-fail") {
|
|
1701
|
+
if (!checker) return "skipped";
|
|
1702
|
+
const status = await checker(cert, this.findIssuer(cert));
|
|
1645
1703
|
if (status === "good") return "good";
|
|
1646
1704
|
if (status === "revoked") {
|
|
1647
1705
|
throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
|
|
1648
1706
|
}
|
|
1649
|
-
if (
|
|
1707
|
+
if (mode === "hard-fail") {
|
|
1650
1708
|
throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 revocation \u0442\u04E9\u043B\u04E9\u0432 \u0442\u043E\u0433\u0442\u043E\u043E\u0433\u0434\u0441\u043E\u043D\u0433\u04AF\u0439 (hard-fail)");
|
|
1651
1709
|
}
|
|
1652
1710
|
return "unchecked";
|
|
@@ -1675,9 +1733,9 @@ function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
|
1675
1733
|
if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto4.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
|
|
1676
1734
|
return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PADDING }, sig);
|
|
1677
1735
|
}
|
|
1678
|
-
function verifyPrehashedSignature(
|
|
1736
|
+
function verifyPrehashedSignature(digest2, sigB64, key, alg, hash) {
|
|
1679
1737
|
const sig = Buffer.from(sigB64, "base64");
|
|
1680
|
-
if (alg === "ecdsa-with-SHA256") return ecdsaP256VerifyPrehashed(
|
|
1738
|
+
if (alg === "ecdsa-with-SHA256") return ecdsaP256VerifyPrehashed(digest2, sig, key);
|
|
1681
1739
|
const modBits = key.asymmetricKeyDetails?.modulusLength ?? 0;
|
|
1682
1740
|
if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
|
|
1683
1741
|
let em;
|
|
@@ -1686,10 +1744,10 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
|
1686
1744
|
} catch {
|
|
1687
1745
|
return false;
|
|
1688
1746
|
}
|
|
1689
|
-
if (alg === "rsassa-pss") return emsaPssVerify(
|
|
1747
|
+
if (alg === "rsassa-pss") return emsaPssVerify(digest2, em, modBits - 1, hash);
|
|
1690
1748
|
const prefix = DIGEST_INFO[hash];
|
|
1691
1749
|
if (!prefix) return false;
|
|
1692
|
-
const t = Buffer.concat([prefix,
|
|
1750
|
+
const t = Buffer.concat([prefix, digest2]);
|
|
1693
1751
|
const expected = Buffer.concat([Buffer.from([0, 1]), Buffer.alloc(em.length - t.length - 3, 255), Buffer.from([0]), t]);
|
|
1694
1752
|
return em.length === expected.length && em.equals(expected);
|
|
1695
1753
|
}
|
|
@@ -1740,7 +1798,7 @@ function derInts(sig) {
|
|
|
1740
1798
|
}
|
|
1741
1799
|
return i === sig.length ? [out[0], out[1]] : null;
|
|
1742
1800
|
}
|
|
1743
|
-
function ecdsaP256VerifyPrehashed(
|
|
1801
|
+
function ecdsaP256VerifyPrehashed(digest2, sig, key) {
|
|
1744
1802
|
if (key.asymmetricKeyDetails?.namedCurve !== "prime256v1") return false;
|
|
1745
1803
|
const jwk = key.export({ format: "jwk" });
|
|
1746
1804
|
if (!jwk.x || !jwk.y) return false;
|
|
@@ -1750,7 +1808,7 @@ function ecdsaP256VerifyPrehashed(digest, sig, key) {
|
|
|
1750
1808
|
const [r, sv] = rs;
|
|
1751
1809
|
const { n } = P256;
|
|
1752
1810
|
if (r <= 0n || r >= n || sv <= 0n || sv >= n) return false;
|
|
1753
|
-
const z = BigInt("0x" +
|
|
1811
|
+
const z = BigInt("0x" + digest2.subarray(0, 32).toString("hex"));
|
|
1754
1812
|
const w = inv(sv, n);
|
|
1755
1813
|
const R = ptAdd(ptMul(mod(z * w, n), [P256.gx, P256.gy]), ptMul(mod(r * w, n), Q));
|
|
1756
1814
|
return R !== null && mod(R[0], n) === r;
|
|
@@ -1803,10 +1861,10 @@ function keyUsageBits(cert) {
|
|
|
1803
1861
|
function extKeyUsage(cert) {
|
|
1804
1862
|
const v = certExtension(cert.raw, "2.5.29.37");
|
|
1805
1863
|
if (!v) return [];
|
|
1806
|
-
const
|
|
1864
|
+
const seq2 = tlv(v, 0, v.length);
|
|
1807
1865
|
const out = [];
|
|
1808
|
-
for (let p =
|
|
1809
|
-
const t = tlv(v, p,
|
|
1866
|
+
for (let p = seq2.start; p < seq2.end; ) {
|
|
1867
|
+
const t = tlv(v, p, seq2.end);
|
|
1810
1868
|
out.push(oidOf(v, t));
|
|
1811
1869
|
p = t.end;
|
|
1812
1870
|
}
|
|
@@ -1821,9 +1879,9 @@ function subjectKeyId(cert) {
|
|
|
1821
1879
|
function authorityKeyId(cert) {
|
|
1822
1880
|
const v = certExtension(cert.raw, "2.5.29.35");
|
|
1823
1881
|
if (!v) return void 0;
|
|
1824
|
-
const
|
|
1825
|
-
if (
|
|
1826
|
-
const k = tlv(v,
|
|
1882
|
+
const seq2 = tlv(v, 0, v.length);
|
|
1883
|
+
if (seq2.start >= seq2.end) return void 0;
|
|
1884
|
+
const k = tlv(v, seq2.start, seq2.end);
|
|
1827
1885
|
return k.tag === 128 && k.end > k.start ? Buffer.from(v.subarray(k.start, k.end)).toString("hex") : void 0;
|
|
1828
1886
|
}
|
|
1829
1887
|
function isCa(cert) {
|
|
@@ -1889,9 +1947,9 @@ function caReason(ca, below, now, usage) {
|
|
|
1889
1947
|
function basicConstraintsPathLen(cert) {
|
|
1890
1948
|
const v = certExtension(cert.raw, "2.5.29.19");
|
|
1891
1949
|
if (!v) return void 0;
|
|
1892
|
-
const
|
|
1893
|
-
for (let p =
|
|
1894
|
-
const t = tlv(v, p,
|
|
1950
|
+
const seq2 = tlv(v, 0, v.length);
|
|
1951
|
+
for (let p = seq2.start; p < seq2.end; ) {
|
|
1952
|
+
const t = tlv(v, p, seq2.end);
|
|
1895
1953
|
if (t.tag === 2) {
|
|
1896
1954
|
if (t.end - t.start > 4 || (v[t.start] & 128) !== 0) throw new ValidationError("basicConstraints: pathLen \u0431\u0443\u0440\u0443\u0443");
|
|
1897
1955
|
let n = 0;
|
|
@@ -1962,6 +2020,614 @@ var EidClient = class {
|
|
|
1962
2020
|
}
|
|
1963
2021
|
};
|
|
1964
2022
|
|
|
2023
|
+
// src/ocsp.ts
|
|
2024
|
+
var import_node_crypto5 = require("crypto");
|
|
2025
|
+
|
|
2026
|
+
// src/pades/bytes.ts
|
|
2027
|
+
var enc = new TextEncoder();
|
|
2028
|
+
function concat(parts) {
|
|
2029
|
+
let n = 0;
|
|
2030
|
+
for (const p of parts) n += p.length;
|
|
2031
|
+
const out = new Uint8Array(n);
|
|
2032
|
+
let o = 0;
|
|
2033
|
+
for (const p of parts) {
|
|
2034
|
+
out.set(p, o);
|
|
2035
|
+
o += p.length;
|
|
2036
|
+
}
|
|
2037
|
+
return out;
|
|
2038
|
+
}
|
|
2039
|
+
function equal(a, b) {
|
|
2040
|
+
if (a.length !== b.length) return false;
|
|
2041
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
2042
|
+
return true;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
// src/pades/asn1.ts
|
|
2046
|
+
var Asn1Error = class extends Error {
|
|
2047
|
+
};
|
|
2048
|
+
var TAG = {
|
|
2049
|
+
BOOLEAN: 1,
|
|
2050
|
+
INTEGER: 2,
|
|
2051
|
+
BIT_STRING: 3,
|
|
2052
|
+
OCTET_STRING: 4,
|
|
2053
|
+
NULL: 5,
|
|
2054
|
+
OID: 6,
|
|
2055
|
+
ENUMERATED: 10,
|
|
2056
|
+
UTF8String: 12,
|
|
2057
|
+
SEQUENCE: 48,
|
|
2058
|
+
SET: 49,
|
|
2059
|
+
PrintableString: 19,
|
|
2060
|
+
IA5String: 22,
|
|
2061
|
+
UTCTime: 23,
|
|
2062
|
+
GeneralizedTime: 24
|
|
2063
|
+
};
|
|
2064
|
+
function readNode(b, off = 0) {
|
|
2065
|
+
if (off + 2 > b.length) throw new Asn1Error("asn1: \u0434\u0443\u0442\u0443\u0443");
|
|
2066
|
+
const tag = b[off];
|
|
2067
|
+
if ((tag & 31) === 31) throw new Asn1Error("asn1: \u043E\u043B\u043E\u043D \u0431\u0430\u0439\u0442 tag \u0434\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u0445\u0433\u04AF\u0439");
|
|
2068
|
+
let len = b[off + 1];
|
|
2069
|
+
let p = off + 2;
|
|
2070
|
+
if (len & 128) {
|
|
2071
|
+
const n = len & 127;
|
|
2072
|
+
if (n === 0 || n > 4) throw new Asn1Error("asn1: \u0443\u0440\u0442 \u0431\u0443\u0440\u0443\u0443");
|
|
2073
|
+
if (p + n > b.length) throw new Asn1Error("asn1: \u0434\u0443\u0442\u0443\u0443");
|
|
2074
|
+
if (b[p] === 0) throw new Asn1Error("asn1: \u0443\u0440\u0442 minimal \u0431\u0438\u0448");
|
|
2075
|
+
len = 0;
|
|
2076
|
+
for (let i = 0; i < n; i++) len = len * 256 + b[p + i];
|
|
2077
|
+
if (len < 128) throw new Asn1Error("asn1: \u0443\u0440\u0442 minimal \u0431\u0438\u0448");
|
|
2078
|
+
p += n;
|
|
2079
|
+
}
|
|
2080
|
+
if (p + len > b.length) throw new Asn1Error("asn1: \u0434\u0443\u0442\u0443\u0443");
|
|
2081
|
+
return { tag, raw: b.subarray(off, p + len), value: b.subarray(p, p + len) };
|
|
2082
|
+
}
|
|
2083
|
+
function parseOne(b) {
|
|
2084
|
+
const n = readNode(b, 0);
|
|
2085
|
+
if (n.raw.length !== b.length) throw new Asn1Error("asn1: \u0438\u043B\u04AF\u04AF \u04E9\u0433\u04E9\u0433\u0434\u04E9\u043B");
|
|
2086
|
+
return n;
|
|
2087
|
+
}
|
|
2088
|
+
function children(n) {
|
|
2089
|
+
const v = n instanceof Uint8Array ? n : n.value;
|
|
2090
|
+
const out = [];
|
|
2091
|
+
for (let p = 0; p < v.length; ) {
|
|
2092
|
+
const c = readNode(v, p);
|
|
2093
|
+
out.push(c);
|
|
2094
|
+
p += c.raw.length;
|
|
2095
|
+
}
|
|
2096
|
+
return out;
|
|
2097
|
+
}
|
|
2098
|
+
function expect(n, tag, what) {
|
|
2099
|
+
if (!n || n.tag !== tag) throw new Asn1Error(`asn1: ${what} \u0445\u04AF\u043B\u044D\u044D\u0441\u044D\u043D`);
|
|
2100
|
+
return n;
|
|
2101
|
+
}
|
|
2102
|
+
function lenBytes(n) {
|
|
2103
|
+
if (n < 128) return Uint8Array.of(n);
|
|
2104
|
+
const out = [];
|
|
2105
|
+
for (let x = n; x > 0; x = Math.floor(x / 256)) out.unshift(x & 255);
|
|
2106
|
+
return Uint8Array.from([128 | out.length, ...out]);
|
|
2107
|
+
}
|
|
2108
|
+
function tlv2(tag, ...content) {
|
|
2109
|
+
const v = concat(content);
|
|
2110
|
+
return concat([Uint8Array.of(tag), lenBytes(v.length), v]);
|
|
2111
|
+
}
|
|
2112
|
+
var seq = (...c) => tlv2(TAG.SEQUENCE, ...c);
|
|
2113
|
+
var octet = (b) => tlv2(TAG.OCTET_STRING, b);
|
|
2114
|
+
var nullDer = () => Uint8Array.of(5, 0);
|
|
2115
|
+
function oid(s) {
|
|
2116
|
+
const parts = s.split(".").map((x) => BigInt(x));
|
|
2117
|
+
const body = [];
|
|
2118
|
+
const enc2 = (v) => {
|
|
2119
|
+
const tmp = [Number(v & 0x7fn)];
|
|
2120
|
+
for (v >>= 7n; v > 0n; v >>= 7n) tmp.unshift(Number(v & 0x7fn) | 128);
|
|
2121
|
+
body.push(...tmp);
|
|
2122
|
+
};
|
|
2123
|
+
enc2(parts[0] * 40n + parts[1]);
|
|
2124
|
+
for (const p of parts.slice(2)) enc2(p);
|
|
2125
|
+
return tlv2(TAG.OID, Uint8Array.from(body));
|
|
2126
|
+
}
|
|
2127
|
+
function oidString(v) {
|
|
2128
|
+
const out = [];
|
|
2129
|
+
let cur = 0n;
|
|
2130
|
+
for (const b of v) {
|
|
2131
|
+
cur = cur << 7n | BigInt(b & 127);
|
|
2132
|
+
if (!(b & 128)) {
|
|
2133
|
+
out.push(cur);
|
|
2134
|
+
cur = 0n;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
if (out.length === 0) return "";
|
|
2138
|
+
const first = out[0];
|
|
2139
|
+
const a = first < 40n ? 0n : first < 80n ? 1n : 2n;
|
|
2140
|
+
return [a, first - a * 40n, ...out.slice(1)].join(".");
|
|
2141
|
+
}
|
|
2142
|
+
function bigFromBytes(b) {
|
|
2143
|
+
let v = 0n;
|
|
2144
|
+
for (const x of b) v = v << 8n | BigInt(x);
|
|
2145
|
+
return v;
|
|
2146
|
+
}
|
|
2147
|
+
function intValue(v) {
|
|
2148
|
+
if (v.length === 0) throw new Asn1Error("asn1: \u0445\u043E\u043E\u0441\u043E\u043D INTEGER");
|
|
2149
|
+
let x = bigFromBytes(v);
|
|
2150
|
+
if (v[0] & 128) x -= 1n << BigInt(v.length * 8);
|
|
2151
|
+
return x;
|
|
2152
|
+
}
|
|
2153
|
+
function parseTime(n) {
|
|
2154
|
+
const s = String.fromCharCode(...n.value);
|
|
2155
|
+
let m;
|
|
2156
|
+
if (n.tag === TAG.UTCTime) {
|
|
2157
|
+
m = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})?(Z|[+-]\d{4})$/.exec(s);
|
|
2158
|
+
if (!m) throw new Asn1Error("asn1: UTCTime \u0431\u0443\u0440\u0443\u0443");
|
|
2159
|
+
let y = Number(m[1]);
|
|
2160
|
+
y += y < 50 ? 2e3 : 1900;
|
|
2161
|
+
return mkDate(y, m[2], m[3], m[4], m[5], m[6] ?? "00", "", m[7]);
|
|
2162
|
+
}
|
|
2163
|
+
if (n.tag === TAG.GeneralizedTime) {
|
|
2164
|
+
m = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d+)?(Z|[+-]\d{4})$/.exec(s);
|
|
2165
|
+
if (!m) throw new Asn1Error("asn1: GeneralizedTime \u0431\u0443\u0440\u0443\u0443");
|
|
2166
|
+
return mkDate(Number(m[1]), m[2], m[3], m[4], m[5], m[6], m[7] ?? "", m[8]);
|
|
2167
|
+
}
|
|
2168
|
+
throw new Asn1Error("asn1: \u0446\u0430\u0433 \u0445\u04AF\u043B\u044D\u044D\u0441\u044D\u043D");
|
|
2169
|
+
}
|
|
2170
|
+
function parsePreciseTime(n) {
|
|
2171
|
+
const date = parseTime(n);
|
|
2172
|
+
const m = /\.(\d+)/.exec(String.fromCharCode(...n.value));
|
|
2173
|
+
const nanos = m ? Number((m[1] + "000000000").slice(0, 9)) : 0;
|
|
2174
|
+
return { date, nanos };
|
|
2175
|
+
}
|
|
2176
|
+
function mkDate(y, mo, d, h, mi, s, frac, z) {
|
|
2177
|
+
let t = Date.UTC(y, Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));
|
|
2178
|
+
if (frac) t += Math.floor(Number("0" + frac) * 1e3);
|
|
2179
|
+
if (z !== "Z") {
|
|
2180
|
+
const off = (Number(z.slice(1, 3)) * 60 + Number(z.slice(3, 5))) * 6e4;
|
|
2181
|
+
t += z[0] === "+" ? -off : off;
|
|
2182
|
+
}
|
|
2183
|
+
return new Date(t);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
// src/pades/crypto.ts
|
|
2187
|
+
var subtlePromise;
|
|
2188
|
+
var nodeImport = (m) => import(
|
|
2189
|
+
/* @vite-ignore */
|
|
2190
|
+
/* webpackIgnore: true */
|
|
2191
|
+
m
|
|
2192
|
+
);
|
|
2193
|
+
async function webcrypto() {
|
|
2194
|
+
const g = globalThis.crypto;
|
|
2195
|
+
if (g?.subtle) return g;
|
|
2196
|
+
const m = await nodeImport("node:crypto");
|
|
2197
|
+
if (!m.webcrypto) throw new Error("Web Crypto \u0430\u043B\u0433\u0430");
|
|
2198
|
+
return m.webcrypto;
|
|
2199
|
+
}
|
|
2200
|
+
function subtle() {
|
|
2201
|
+
subtlePromise ??= webcrypto().then((c) => c.subtle);
|
|
2202
|
+
return subtlePromise;
|
|
2203
|
+
}
|
|
2204
|
+
async function sha1(b) {
|
|
2205
|
+
return new Uint8Array(await (await subtle()).digest("SHA-1", b));
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// src/pades/x509.ts
|
|
2209
|
+
var OID = {
|
|
2210
|
+
rsaEncryption: "1.2.840.113549.1.1.1",
|
|
2211
|
+
rsaPSS: "1.2.840.113549.1.1.10",
|
|
2212
|
+
sha256WithRSA: "1.2.840.113549.1.1.11",
|
|
2213
|
+
sha384WithRSA: "1.2.840.113549.1.1.12",
|
|
2214
|
+
sha512WithRSA: "1.2.840.113549.1.1.13",
|
|
2215
|
+
ecPublicKey: "1.2.840.10045.2.1",
|
|
2216
|
+
ecdsaSHA256: "1.2.840.10045.4.3.2",
|
|
2217
|
+
ecdsaSHA384: "1.2.840.10045.4.3.3",
|
|
2218
|
+
ecdsaSHA512: "1.2.840.10045.4.3.4",
|
|
2219
|
+
p256: "1.2.840.10045.3.1.7",
|
|
2220
|
+
p384: "1.3.132.0.34",
|
|
2221
|
+
p521: "1.3.132.0.35",
|
|
2222
|
+
sha1: "1.3.14.3.2.26",
|
|
2223
|
+
sha256: "2.16.840.1.101.3.4.2.1",
|
|
2224
|
+
sha384: "2.16.840.1.101.3.4.2.2",
|
|
2225
|
+
sha512: "2.16.840.1.101.3.4.2.3",
|
|
2226
|
+
commonName: "2.5.4.3",
|
|
2227
|
+
surname: "2.5.4.4",
|
|
2228
|
+
givenName: "2.5.4.42",
|
|
2229
|
+
extKeyUsage: "2.5.29.37",
|
|
2230
|
+
keyUsage: "2.5.29.15",
|
|
2231
|
+
basicConstraints: "2.5.29.19",
|
|
2232
|
+
crlDP: "2.5.29.31",
|
|
2233
|
+
aia: "1.3.6.1.5.5.7.1.1",
|
|
2234
|
+
ocspMethod: "1.3.6.1.5.5.7.48.1",
|
|
2235
|
+
kpTimeStamping: "1.3.6.1.5.5.7.3.8",
|
|
2236
|
+
kpOCSPSigning: "1.3.6.1.5.5.7.3.9",
|
|
2237
|
+
kpAny: "2.5.29.37.0"
|
|
2238
|
+
};
|
|
2239
|
+
var CertError = class extends Error {
|
|
2240
|
+
};
|
|
2241
|
+
function bitString(n) {
|
|
2242
|
+
expect(n, TAG.BIT_STRING, "BIT STRING");
|
|
2243
|
+
if (n.value.length === 0) throw new Asn1Error("BIT STRING \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
2244
|
+
return n.value.subarray(1);
|
|
2245
|
+
}
|
|
2246
|
+
function decodeDirString(n) {
|
|
2247
|
+
if (n.tag === 30) {
|
|
2248
|
+
let s = "";
|
|
2249
|
+
for (let i = 0; i + 1 < n.value.length; i += 2) s += String.fromCharCode(n.value[i] << 8 | n.value[i + 1]);
|
|
2250
|
+
return s;
|
|
2251
|
+
}
|
|
2252
|
+
if (n.tag === TAG.UTF8String) return new TextDecoder().decode(n.value);
|
|
2253
|
+
return String.fromCharCode(...n.value);
|
|
2254
|
+
}
|
|
2255
|
+
function nameAttrs(name) {
|
|
2256
|
+
const out = [];
|
|
2257
|
+
for (const rdn of children(name)) {
|
|
2258
|
+
for (const atv of children(rdn)) {
|
|
2259
|
+
const [t, v] = children(atv);
|
|
2260
|
+
if (!t || !v) continue;
|
|
2261
|
+
out.push([oidString(t.value), decodeDirString(v)]);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
return out;
|
|
2265
|
+
}
|
|
2266
|
+
function parseCertificate(der) {
|
|
2267
|
+
try {
|
|
2268
|
+
const top = parseOne(der);
|
|
2269
|
+
expect(top, TAG.SEQUENCE, "Certificate");
|
|
2270
|
+
const [tbsN, sigAlg, sigN] = children(top);
|
|
2271
|
+
const tbs = expect(tbsN, TAG.SEQUENCE, "TBSCertificate");
|
|
2272
|
+
const f = children(tbs);
|
|
2273
|
+
let i = 0;
|
|
2274
|
+
let version = 1;
|
|
2275
|
+
if (f[0]?.tag === 160) {
|
|
2276
|
+
const v = expect(children(f[0])[0], TAG.INTEGER, "version");
|
|
2277
|
+
version = v.value[0] + 1;
|
|
2278
|
+
i++;
|
|
2279
|
+
}
|
|
2280
|
+
const serial = expect(f[i++], TAG.INTEGER, "serial").value;
|
|
2281
|
+
i++;
|
|
2282
|
+
const issuer = expect(f[i++], TAG.SEQUENCE, "issuer");
|
|
2283
|
+
const validity = children(expect(f[i++], TAG.SEQUENCE, "validity"));
|
|
2284
|
+
const subject = expect(f[i++], TAG.SEQUENCE, "subject");
|
|
2285
|
+
const spki = expect(f[i++], TAG.SEQUENCE, "SPKI");
|
|
2286
|
+
const [algN, pkN] = children(spki);
|
|
2287
|
+
const algParts = children(expect(algN, TAG.SEQUENCE, "AlgorithmIdentifier"));
|
|
2288
|
+
const keyAlg = oidString(expect(algParts[0], TAG.OID, "alg").value);
|
|
2289
|
+
const curve = keyAlg === OID.ecPublicKey && algParts[1]?.tag === TAG.OID ? oidString(algParts[1].value) : void 0;
|
|
2290
|
+
const extensions = [];
|
|
2291
|
+
for (; i < f.length; i++) {
|
|
2292
|
+
if (f[i].tag !== 163) continue;
|
|
2293
|
+
for (const e of children(expect(children(f[i])[0], TAG.SEQUENCE, "extensions"))) {
|
|
2294
|
+
const p = children(e);
|
|
2295
|
+
const o = oidString(expect(p[0], TAG.OID, "ext oid").value);
|
|
2296
|
+
let critical = false;
|
|
2297
|
+
let val = p[1];
|
|
2298
|
+
if (val?.tag === TAG.BOOLEAN) {
|
|
2299
|
+
critical = val.value[0] !== 0;
|
|
2300
|
+
val = p[2];
|
|
2301
|
+
}
|
|
2302
|
+
extensions.push({ oid: o, critical, value: expect(val, TAG.OCTET_STRING, "ext value").value });
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
const subj = nameAttrs(subject);
|
|
2306
|
+
const iss = nameAttrs(issuer);
|
|
2307
|
+
const last = (a, o) => a.filter(([k]) => k === o).map(([, v]) => v).pop() ?? "";
|
|
2308
|
+
const cert = {
|
|
2309
|
+
raw: top.raw,
|
|
2310
|
+
tbs: tbs.raw,
|
|
2311
|
+
version,
|
|
2312
|
+
serial,
|
|
2313
|
+
sigAlg: expect(sigAlg, TAG.SEQUENCE, "sigAlg"),
|
|
2314
|
+
signature: bitString(sigN),
|
|
2315
|
+
rawIssuer: issuer.raw,
|
|
2316
|
+
rawSubject: subject.raw,
|
|
2317
|
+
spki: spki.raw,
|
|
2318
|
+
keyAlg,
|
|
2319
|
+
curve,
|
|
2320
|
+
publicKey: bitString(pkN),
|
|
2321
|
+
notBefore: parseTime(validity[0]),
|
|
2322
|
+
notAfter: parseTime(validity[1]),
|
|
2323
|
+
extensions,
|
|
2324
|
+
subjectCN: last(subj, OID.commonName),
|
|
2325
|
+
issuerCN: last(iss, OID.commonName),
|
|
2326
|
+
// Go SignerDisplayName: Subject.Names-ийн давталтын сүүлийн утга.
|
|
2327
|
+
givenName: last(subj, OID.givenName),
|
|
2328
|
+
surname: last(subj, OID.surname),
|
|
2329
|
+
ocspServers: [],
|
|
2330
|
+
crlDPs: [],
|
|
2331
|
+
isCA: false,
|
|
2332
|
+
basicConstraintsValid: false,
|
|
2333
|
+
keyUsage: 0,
|
|
2334
|
+
extKeyUsage: []
|
|
2335
|
+
};
|
|
2336
|
+
for (const e of extensions) parseExtension(cert, e);
|
|
2337
|
+
return cert;
|
|
2338
|
+
} catch (e) {
|
|
2339
|
+
throw new CertError(`\u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439: ${e instanceof Error ? e.message : String(e)}`);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
function parseExtension(c, e) {
|
|
2343
|
+
const n = parseOne(e.value);
|
|
2344
|
+
switch (e.oid) {
|
|
2345
|
+
case OID.basicConstraints: {
|
|
2346
|
+
c.basicConstraintsValid = true;
|
|
2347
|
+
const p = children(n);
|
|
2348
|
+
c.isCA = p[0]?.tag === TAG.BOOLEAN && p[0].value[0] !== 0;
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
case OID.keyUsage: {
|
|
2352
|
+
const bs = n.value;
|
|
2353
|
+
let ku = 0;
|
|
2354
|
+
for (let i = 1; i < bs.length; i++) {
|
|
2355
|
+
for (let b = 0; b < 8; b++) if (bs[i] & 128 >> b) ku |= 1 << (i - 1) * 8 + b;
|
|
2356
|
+
}
|
|
2357
|
+
c.keyUsage = ku;
|
|
2358
|
+
break;
|
|
2359
|
+
}
|
|
2360
|
+
case OID.extKeyUsage:
|
|
2361
|
+
c.extKeyUsage = children(n).map((x) => oidString(x.value));
|
|
2362
|
+
break;
|
|
2363
|
+
case OID.aia:
|
|
2364
|
+
for (const ad of children(n)) {
|
|
2365
|
+
const [m, loc] = children(ad);
|
|
2366
|
+
if (m && loc && oidString(m.value) === OID.ocspMethod && loc.tag === 134) c.ocspServers.push(String.fromCharCode(...loc.value));
|
|
2367
|
+
}
|
|
2368
|
+
break;
|
|
2369
|
+
case OID.crlDP:
|
|
2370
|
+
for (const dp of children(n)) {
|
|
2371
|
+
for (const f of children(dp)) {
|
|
2372
|
+
if (f.tag !== 160) continue;
|
|
2373
|
+
for (const fn of children(f)) {
|
|
2374
|
+
if (fn.tag !== 160) continue;
|
|
2375
|
+
for (const gn of children(fn)) if (gn.tag === 134) c.crlDPs.push(String.fromCharCode(...gn.value));
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
break;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
var HASH_BY_OID = {
|
|
2383
|
+
[OID.sha1]: "SHA-1",
|
|
2384
|
+
[OID.sha256]: "SHA-256",
|
|
2385
|
+
[OID.sha384]: "SHA-384",
|
|
2386
|
+
[OID.sha512]: "SHA-512"
|
|
2387
|
+
};
|
|
2388
|
+
function schemeFor(alg) {
|
|
2389
|
+
const p = children(alg);
|
|
2390
|
+
const o = oidString(expect(p[0], TAG.OID, "sigAlg").value);
|
|
2391
|
+
switch (o) {
|
|
2392
|
+
case "1.2.840.113549.1.1.5":
|
|
2393
|
+
return { kind: "rsa", hash: "SHA-1" };
|
|
2394
|
+
case OID.sha256WithRSA:
|
|
2395
|
+
return { kind: "rsa", hash: "SHA-256" };
|
|
2396
|
+
case OID.sha384WithRSA:
|
|
2397
|
+
return { kind: "rsa", hash: "SHA-384" };
|
|
2398
|
+
case OID.sha512WithRSA:
|
|
2399
|
+
return { kind: "rsa", hash: "SHA-512" };
|
|
2400
|
+
case OID.ecdsaSHA256:
|
|
2401
|
+
return { kind: "ecdsa", hash: "SHA-256" };
|
|
2402
|
+
case OID.ecdsaSHA384:
|
|
2403
|
+
return { kind: "ecdsa", hash: "SHA-384" };
|
|
2404
|
+
case OID.ecdsaSHA512:
|
|
2405
|
+
return { kind: "ecdsa", hash: "SHA-512" };
|
|
2406
|
+
case OID.rsaPSS: {
|
|
2407
|
+
let hash = "SHA-1";
|
|
2408
|
+
let salt = 20;
|
|
2409
|
+
for (const f of p[1] ? children(p[1]) : []) {
|
|
2410
|
+
if (f.tag === 160) hash = HASH_BY_OID[oidString(children(children(f)[0])[0].value)] ?? hash;
|
|
2411
|
+
if (f.tag === 162) salt = Number(children(f)[0].value[0]);
|
|
2412
|
+
}
|
|
2413
|
+
return { kind: "pss", hash, saltLength: salt };
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
throw new CertError(`\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u0433\u0438\u0439\u043D \u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C \u0434\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u0445\u0433\u04AF\u0439: ${o}`);
|
|
2417
|
+
}
|
|
2418
|
+
var CURVES = {
|
|
2419
|
+
[OID.p256]: { name: "P-256", size: 32 },
|
|
2420
|
+
[OID.p384]: { name: "P-384", size: 48 },
|
|
2421
|
+
[OID.p521]: { name: "P-521", size: 66 }
|
|
2422
|
+
};
|
|
2423
|
+
function ecdsaDerToRaw(der, size) {
|
|
2424
|
+
const [r, s] = children(parseOne(der));
|
|
2425
|
+
const out = new Uint8Array(2 * size);
|
|
2426
|
+
const put = (v, at) => {
|
|
2427
|
+
let x = v;
|
|
2428
|
+
while (x.length > 0 && x[0] === 0) x = x.subarray(1);
|
|
2429
|
+
if (x.length > size) throw new CertError("ECDSA \u0443\u0442\u0433\u0430 \u0445\u044D\u0442 \u0443\u0440\u0442");
|
|
2430
|
+
out.set(x, at + size - x.length);
|
|
2431
|
+
};
|
|
2432
|
+
put(expect(r, TAG.INTEGER, "r").value, 0);
|
|
2433
|
+
put(expect(s, TAG.INTEGER, "s").value, size);
|
|
2434
|
+
return out;
|
|
2435
|
+
}
|
|
2436
|
+
async function verifySignature(pub, alg, data, sig) {
|
|
2437
|
+
const sch = schemeFor(alg);
|
|
2438
|
+
return verifyWith(pub, sch, data, sig);
|
|
2439
|
+
}
|
|
2440
|
+
async function verifyWith(pub, sch, data, sig) {
|
|
2441
|
+
const s = await subtle();
|
|
2442
|
+
try {
|
|
2443
|
+
if (sch.kind === "ecdsa") {
|
|
2444
|
+
if (pub.keyAlg !== OID.ecPublicKey) return false;
|
|
2445
|
+
const c = CURVES[pub.curve ?? ""];
|
|
2446
|
+
if (!c) return false;
|
|
2447
|
+
const key2 = await s.importKey("spki", pub.spki, { name: "ECDSA", namedCurve: c.name }, false, ["verify"]);
|
|
2448
|
+
return await s.verify({ name: "ECDSA", hash: sch.hash }, key2, ecdsaDerToRaw(sig, c.size), data);
|
|
2449
|
+
}
|
|
2450
|
+
if (pub.keyAlg !== OID.rsaEncryption) return false;
|
|
2451
|
+
if (sch.kind === "pss") {
|
|
2452
|
+
const key2 = await s.importKey("spki", pub.spki, { name: "RSA-PSS", hash: sch.hash }, false, ["verify"]);
|
|
2453
|
+
return await s.verify({ name: "RSA-PSS", saltLength: sch.saltLength ?? 32 }, key2, sig, data);
|
|
2454
|
+
}
|
|
2455
|
+
const key = await s.importKey("spki", pub.spki, { name: "RSASSA-PKCS1-v1_5", hash: sch.hash }, false, ["verify"]);
|
|
2456
|
+
return await s.verify({ name: "RSASSA-PKCS1-v1_5" }, key, sig, data);
|
|
2457
|
+
} catch {
|
|
2458
|
+
return false;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
function checkSignature(parent, alg, signed, sig) {
|
|
2462
|
+
return verifySignature(parent, alg, signed, sig);
|
|
2463
|
+
}
|
|
2464
|
+
var HANDLED = /* @__PURE__ */ new Set([
|
|
2465
|
+
OID.keyUsage,
|
|
2466
|
+
OID.extKeyUsage,
|
|
2467
|
+
OID.basicConstraints,
|
|
2468
|
+
"2.5.29.17",
|
|
2469
|
+
// SAN
|
|
2470
|
+
"2.5.29.30",
|
|
2471
|
+
// name constraints
|
|
2472
|
+
"2.5.29.32",
|
|
2473
|
+
// policies
|
|
2474
|
+
OID.crlDP,
|
|
2475
|
+
OID.aia,
|
|
2476
|
+
"2.5.29.14",
|
|
2477
|
+
"2.5.29.35"
|
|
2478
|
+
]);
|
|
2479
|
+
|
|
2480
|
+
// src/pades/cms.ts
|
|
2481
|
+
var HASHES2 = {
|
|
2482
|
+
[OID.sha1]: "SHA-1",
|
|
2483
|
+
[OID.sha256]: "SHA-256",
|
|
2484
|
+
[OID.sha384]: "SHA-384",
|
|
2485
|
+
[OID.sha512]: "SHA-512"
|
|
2486
|
+
};
|
|
2487
|
+
|
|
2488
|
+
// src/pades/ltv.ts
|
|
2489
|
+
var MAX_TSA_SKEW_MS = 10 * 6e4;
|
|
2490
|
+
async function ocspRequest(c, issuer) {
|
|
2491
|
+
const keyHash = await sha1(issuer.publicKey);
|
|
2492
|
+
const nameHash = await sha1(issuer.rawSubject);
|
|
2493
|
+
const certID = seq(seq(oid(OID.sha1), nullDer()), octet(nameHash), octet(keyHash), Uint8Array.of(TAG.INTEGER, ...lenOf(c.serial.length), ...c.serial));
|
|
2494
|
+
return seq(seq(seq(seq(certID))));
|
|
2495
|
+
}
|
|
2496
|
+
function lenOf(n) {
|
|
2497
|
+
if (n < 128) return [n];
|
|
2498
|
+
const out = [];
|
|
2499
|
+
for (let x = n; x > 0; x = Math.floor(x / 256)) out.unshift(x & 255);
|
|
2500
|
+
return [128 | out.length, ...out];
|
|
2501
|
+
}
|
|
2502
|
+
var OID_OCSP_BASIC = "1.3.6.1.5.5.7.48.1.1";
|
|
2503
|
+
async function parseOCSPForCert(der, c, issuer) {
|
|
2504
|
+
const top = children(expect(parseOne(der), TAG.SEQUENCE, "OCSPResponse"));
|
|
2505
|
+
if (expect(top[0], TAG.ENUMERATED, "status").value[0] !== 0) throw new Error("OCSP response status");
|
|
2506
|
+
const rb = children(expect(children(expect(top[1], 160, "responseBytes"))[0], TAG.SEQUENCE, "ResponseBytes"));
|
|
2507
|
+
if (oidString(expect(rb[0], TAG.OID, "responseType").value) !== OID_OCSP_BASIC) throw new Error("bad OCSP response type");
|
|
2508
|
+
const basic = children(expect(parseOne(expect(rb[1], TAG.OCTET_STRING, "response").value), TAG.SEQUENCE, "BasicOCSPResponse"));
|
|
2509
|
+
const tbs = expect(basic[0], TAG.SEQUENCE, "tbsResponseData");
|
|
2510
|
+
const sigAlg = expect(basic[1], TAG.SEQUENCE, "sigAlg");
|
|
2511
|
+
const sigBits = expect(basic[2], TAG.BIT_STRING, "signature").value.subarray(1);
|
|
2512
|
+
const tf = children(tbs);
|
|
2513
|
+
let i = 0;
|
|
2514
|
+
if (tf[0]?.tag === 160) i++;
|
|
2515
|
+
const rid = tf[i++];
|
|
2516
|
+
if (rid.tag !== 161 && rid.tag !== 162) throw new Error("invalid responder id tag");
|
|
2517
|
+
i++;
|
|
2518
|
+
const responses = children(expect(tf[i], TAG.SEQUENCE, "responses"));
|
|
2519
|
+
if (responses.length === 0) throw new Error("OCSP response contains bad number of responses");
|
|
2520
|
+
const serial = intValue(c.serial);
|
|
2521
|
+
let single;
|
|
2522
|
+
for (const r of responses) {
|
|
2523
|
+
const f = children(r);
|
|
2524
|
+
const cid2 = children(f[0]);
|
|
2525
|
+
if (intValue(expect(cid2[3], TAG.INTEGER, "serial").value) === serial) {
|
|
2526
|
+
single = f;
|
|
2527
|
+
break;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
if (!single) throw new Error("no response matching the supplied certificate");
|
|
2531
|
+
let certificate;
|
|
2532
|
+
if (basic[3]?.tag === 160) {
|
|
2533
|
+
const certs = children(expect(children(basic[3])[0], TAG.SEQUENCE, "certs"));
|
|
2534
|
+
if (certs[0]) certificate = parseCertificate(certs[0].raw);
|
|
2535
|
+
}
|
|
2536
|
+
if (certificate) {
|
|
2537
|
+
if (!await verifySignature(certificate, sigAlg, tbs.raw, sigBits)) throw new Error("bad signature on embedded certificate");
|
|
2538
|
+
if (!await checkSignature(issuer, certificate.sigAlg, certificate.tbs, certificate.signature)) throw new Error("bad OCSP signature");
|
|
2539
|
+
} else if (!await verifySignature(issuer, sigAlg, tbs.raw, sigBits)) {
|
|
2540
|
+
throw new Error("bad OCSP signature");
|
|
2541
|
+
}
|
|
2542
|
+
const cid = children(single[0]);
|
|
2543
|
+
const hashAlg = oidString(children(cid[0])[0].value);
|
|
2544
|
+
if (![OID.sha1, OID.sha256, OID.sha384, OID.sha512].includes(hashAlg)) throw new Error("unsupported issuer hash algorithm");
|
|
2545
|
+
const st = single[1];
|
|
2546
|
+
const status = st.tag === 128 ? "good" : st.tag === 161 ? "revoked" : st.tag === 130 ? "unknown" : void 0;
|
|
2547
|
+
if (!status) throw new Error("OCSP certStatus");
|
|
2548
|
+
const thisUpdate = parsePreciseTime(expect(single[2], TAG.GeneralizedTime, "thisUpdate"));
|
|
2549
|
+
let nextUpdate;
|
|
2550
|
+
for (const x of single.slice(3)) {
|
|
2551
|
+
if (x.tag === 160) nextUpdate = parsePreciseTime(children(x)[0]).date;
|
|
2552
|
+
if (x.tag === 161) {
|
|
2553
|
+
for (const ext of children(children(x)[0])) {
|
|
2554
|
+
const e = children(ext);
|
|
2555
|
+
if (e[1]?.tag === TAG.BOOLEAN && e[1].value[0] !== 0) throw new Error("unsupported critical extension");
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
return { status, thisUpdate, nextUpdate, certificate };
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// src/ocsp.ts
|
|
2563
|
+
var CACHE_TTL_MS = 5 * 6e4;
|
|
2564
|
+
var CACHE_MAX = 1024;
|
|
2565
|
+
var MAX_BYTES = 16 << 10;
|
|
2566
|
+
var CLOCK_SKEW_MS = 5 * 6e4;
|
|
2567
|
+
var MAX_AGE_MS = 48 * 36e5;
|
|
2568
|
+
function ocspRevocationChecker(opts = {}) {
|
|
2569
|
+
const doFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
2570
|
+
const timeoutMs = opts.timeoutMs ?? 2e3;
|
|
2571
|
+
const totalMs = Math.max(timeoutMs, 3e3);
|
|
2572
|
+
const now = opts.now ?? Date.now;
|
|
2573
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2574
|
+
async function ask(leaf, issuer) {
|
|
2575
|
+
const body = await ocspRequest(leaf, issuer);
|
|
2576
|
+
const deadline = Date.now() + totalMs;
|
|
2577
|
+
for (const raw of leaf.ocspServers) {
|
|
2578
|
+
const left = deadline - Date.now();
|
|
2579
|
+
if (left <= 0) break;
|
|
2580
|
+
try {
|
|
2581
|
+
const u = new URL(raw);
|
|
2582
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") continue;
|
|
2583
|
+
const res = await doFetch(u.toString(), {
|
|
2584
|
+
method: "POST",
|
|
2585
|
+
headers: { "Content-Type": "application/ocsp-request", Accept: "application/ocsp-response" },
|
|
2586
|
+
body,
|
|
2587
|
+
redirect: "manual",
|
|
2588
|
+
signal: AbortSignal.timeout(Math.min(timeoutMs, left))
|
|
2589
|
+
});
|
|
2590
|
+
if (res.status !== 200) continue;
|
|
2591
|
+
const der = new Uint8Array(await res.arrayBuffer());
|
|
2592
|
+
if (der.length > MAX_BYTES) continue;
|
|
2593
|
+
const resp = await parseOCSPForCert(der, leaf, issuer);
|
|
2594
|
+
const t = now();
|
|
2595
|
+
const rc = resp.certificate;
|
|
2596
|
+
if (rc && !equal(rc.raw, issuer.raw)) {
|
|
2597
|
+
if (!rc.extKeyUsage.includes(OID.kpOCSPSigning)) continue;
|
|
2598
|
+
if (t < rc.notBefore.getTime() || t > rc.notAfter.getTime()) continue;
|
|
2599
|
+
}
|
|
2600
|
+
const tu = resp.thisUpdate.date.getTime();
|
|
2601
|
+
if (tu > t + CLOCK_SKEW_MS || tu < t - MAX_AGE_MS) continue;
|
|
2602
|
+
if (resp.nextUpdate && resp.nextUpdate.getTime() < t) continue;
|
|
2603
|
+
return resp.status === "good" ? "good" : "revoked";
|
|
2604
|
+
} catch {
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
return void 0;
|
|
2608
|
+
}
|
|
2609
|
+
return async (cert, issuer) => {
|
|
2610
|
+
if (!issuer) return "unknown";
|
|
2611
|
+
let leaf, iss;
|
|
2612
|
+
try {
|
|
2613
|
+
leaf = parseCertificate(new Uint8Array(cert.raw));
|
|
2614
|
+
iss = parseCertificate(new Uint8Array(issuer.raw));
|
|
2615
|
+
} catch {
|
|
2616
|
+
return "unknown";
|
|
2617
|
+
}
|
|
2618
|
+
const key = (0, import_node_crypto5.createHash)("sha256").update(issuer.raw).digest("hex").slice(0, 16) + ":" + cert.serialNumber;
|
|
2619
|
+
const exp = cache.get(key);
|
|
2620
|
+
if (exp !== void 0 && now() < exp) return "good";
|
|
2621
|
+
const status = await ask(leaf, iss);
|
|
2622
|
+
if (!status) return "unknown";
|
|
2623
|
+
if (status === "good") {
|
|
2624
|
+
if (cache.size >= CACHE_MAX) cache.clear();
|
|
2625
|
+
cache.set(key, now() + CACHE_TTL_MS);
|
|
2626
|
+
}
|
|
2627
|
+
return status;
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
|
|
1965
2631
|
// src/devicelink.ts
|
|
1966
2632
|
var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
1967
2633
|
var VC_RE = /^[0-9]{5}$/;
|
|
@@ -2005,7 +2671,7 @@ function openOrShowQR(session, opts) {
|
|
|
2005
2671
|
}
|
|
2006
2672
|
|
|
2007
2673
|
// src/devicelinkv3.ts
|
|
2008
|
-
var
|
|
2674
|
+
var import_node_crypto6 = require("crypto");
|
|
2009
2675
|
var TYPES = ["QR", "Web2App", "App2App"];
|
|
2010
2676
|
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
2011
2677
|
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
@@ -2062,7 +2728,7 @@ function buildDeviceLink(p) {
|
|
|
2062
2728
|
callback,
|
|
2063
2729
|
unprotected
|
|
2064
2730
|
].join("|");
|
|
2065
|
-
const authCode = (0,
|
|
2731
|
+
const authCode = (0, import_node_crypto6.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
2066
2732
|
return `${unprotected}&authCode=${authCode}`;
|
|
2067
2733
|
}
|
|
2068
2734
|
function deviceLinkInput(s, deviceLinkType, lang) {
|
|
@@ -2106,7 +2772,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
2106
2772
|
}
|
|
2107
2773
|
|
|
2108
2774
|
// src/login.ts
|
|
2109
|
-
var
|
|
2775
|
+
var import_node_crypto7 = require("crypto");
|
|
2110
2776
|
function memoryLoginStore() {
|
|
2111
2777
|
const m = /* @__PURE__ */ new Map();
|
|
2112
2778
|
return {
|
|
@@ -2131,6 +2797,51 @@ function memoryLoginStore() {
|
|
|
2131
2797
|
}
|
|
2132
2798
|
};
|
|
2133
2799
|
}
|
|
2800
|
+
var defaultRateLimit = Object.freeze({
|
|
2801
|
+
push: Object.freeze({ limit: 3, windowMs: 6e4 })
|
|
2802
|
+
});
|
|
2803
|
+
var RATE_KINDS = ["start", "push"];
|
|
2804
|
+
function windowLimiter(limit, windowMs) {
|
|
2805
|
+
const hits = /* @__PURE__ */ new Map();
|
|
2806
|
+
return (key) => {
|
|
2807
|
+
const now = Date.now();
|
|
2808
|
+
const h = (hits.get(key) ?? []).filter((t) => now - t < windowMs);
|
|
2809
|
+
if (hits.size > 1e4) hits.clear();
|
|
2810
|
+
if (h.length >= limit) {
|
|
2811
|
+
hits.set(key, h);
|
|
2812
|
+
return false;
|
|
2813
|
+
}
|
|
2814
|
+
h.push(now);
|
|
2815
|
+
hits.set(key, h);
|
|
2816
|
+
return true;
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
function buildRateLimit(opt) {
|
|
2820
|
+
const extra = typeof opt === "function" ? opt : void 0;
|
|
2821
|
+
const user = opt && typeof opt === "object" ? opt : {};
|
|
2822
|
+
for (const k of Object.keys(user)) {
|
|
2823
|
+
if (!RATE_KINDS.includes(k)) throw new Error(`createEidLogin: rateLimit.${k} \u2014 \u0437\u04E9\u0432\u0445\u04E9\u043D start | push`);
|
|
2824
|
+
}
|
|
2825
|
+
const merged = { ...defaultRateLimit, ...user };
|
|
2826
|
+
const perKind = /* @__PURE__ */ new Map();
|
|
2827
|
+
for (const k of RATE_KINDS) {
|
|
2828
|
+
const r = merged[k];
|
|
2829
|
+
if (r === void 0 || r === false) continue;
|
|
2830
|
+
if (typeof r === "function") {
|
|
2831
|
+
perKind.set(k, r);
|
|
2832
|
+
continue;
|
|
2833
|
+
}
|
|
2834
|
+
if (!(Number.isInteger(r.limit) && r.limit > 0 && r.windowMs > 0)) {
|
|
2835
|
+
throw new Error(`createEidLogin: rateLimit.${k} \u2014 limit (\u0431\u04AF\u0445\u044D\u043B > 0), windowMs (> 0)`);
|
|
2836
|
+
}
|
|
2837
|
+
perKind.set(k, windowLimiter(r.limit, r.windowMs));
|
|
2838
|
+
}
|
|
2839
|
+
return async (key) => {
|
|
2840
|
+
const fn = perKind.get(key.slice(0, key.indexOf(":")));
|
|
2841
|
+
if (fn && !await fn(key)) return false;
|
|
2842
|
+
return extra ? extra(key) : true;
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2134
2845
|
var CALLBACK_PATH = "/auth/eid/callback";
|
|
2135
2846
|
function forwardedClientIp(headers, trustProxy) {
|
|
2136
2847
|
if (trustProxy === false || trustProxy === 0) return void 0;
|
|
@@ -2187,13 +2898,43 @@ function readCookie(h, name) {
|
|
|
2187
2898
|
}
|
|
2188
2899
|
return "";
|
|
2189
2900
|
}
|
|
2190
|
-
var sha256hex = (s) => (0,
|
|
2901
|
+
var sha256hex = (s) => (0, import_node_crypto7.createHash)("sha256").update(s).digest("hex");
|
|
2191
2902
|
function safeEqualHex(a, b) {
|
|
2192
2903
|
const x = Buffer.from(a, "hex");
|
|
2193
2904
|
const y = Buffer.from(b, "hex");
|
|
2194
|
-
return x.length === y.length && x.length > 0 && (0,
|
|
2905
|
+
return x.length === y.length && x.length > 0 && (0, import_node_crypto7.timingSafeEqual)(x, y);
|
|
2906
|
+
}
|
|
2907
|
+
function sleep(ms2, signal) {
|
|
2908
|
+
return new Promise((resolve) => {
|
|
2909
|
+
if (ms2 <= 0 || signal?.aborted) return resolve();
|
|
2910
|
+
const done = () => {
|
|
2911
|
+
clearTimeout(t);
|
|
2912
|
+
signal?.removeEventListener("abort", done);
|
|
2913
|
+
resolve();
|
|
2914
|
+
};
|
|
2915
|
+
const t = setTimeout(done, ms2);
|
|
2916
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
function isRefusal(e) {
|
|
2920
|
+
if (!(e instanceof ApiError) || e.status < 400 || e.status >= 500 || e.status === 401 || e.status === 403 || e.status === 429) {
|
|
2921
|
+
return false;
|
|
2922
|
+
}
|
|
2923
|
+
try {
|
|
2924
|
+
JSON.parse(e.body);
|
|
2925
|
+
return true;
|
|
2926
|
+
} catch {
|
|
2927
|
+
return false;
|
|
2928
|
+
}
|
|
2195
2929
|
}
|
|
2196
|
-
|
|
2930
|
+
var RevocationFailure = class extends ValidationError {
|
|
2931
|
+
constructor(message, revoked) {
|
|
2932
|
+
super(message);
|
|
2933
|
+
this.revoked = revoked;
|
|
2934
|
+
}
|
|
2935
|
+
revoked;
|
|
2936
|
+
};
|
|
2937
|
+
function toIdentity(sessionId, r, v, flowType, revocation) {
|
|
2197
2938
|
const s = v.parsedSubject;
|
|
2198
2939
|
const etsi = s.serialNumber ?? "";
|
|
2199
2940
|
const m = /^PNOMN-([0-9]+)$/.exec(etsi);
|
|
@@ -2209,7 +2950,8 @@ function toIdentity(sessionId, r, v, flowType) {
|
|
|
2209
2950
|
certificateLevel: v.certificateLevel,
|
|
2210
2951
|
certificateDerB64: r.certificateDerB64 ?? "",
|
|
2211
2952
|
flowType,
|
|
2212
|
-
person: personMatchesCertificate(r.person, s) ? r.person ?? null : null
|
|
2953
|
+
person: personMatchesCertificate(r.person, s) ? r.person ?? null : null,
|
|
2954
|
+
revocation
|
|
2213
2955
|
};
|
|
2214
2956
|
}
|
|
2215
2957
|
function createEidLogin(opts) {
|
|
@@ -2230,17 +2972,29 @@ function createEidLogin(opts) {
|
|
|
2230
2972
|
const successRedirect = opts.successRedirect ?? "/";
|
|
2231
2973
|
const failureRedirect = opts.failureRedirect ?? "/";
|
|
2232
2974
|
const onError = opts.onError ?? ((e) => console.error("eid-login:", e instanceof Error ? e.message : e));
|
|
2233
|
-
const
|
|
2234
|
-
const
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2975
|
+
const rateLimit = buildRateLimit(opts.rateLimit);
|
|
2976
|
+
const decoyOn = opts.decoy !== false;
|
|
2977
|
+
const pushFloorMs = opts.pushFloorMs ?? 1e3;
|
|
2978
|
+
const revocation = opts.revocation === false ? void 0 : opts.revocation ?? ocspRevocationChecker();
|
|
2979
|
+
const revocationMode = opts.revocationMode ?? "soft-fail";
|
|
2980
|
+
const auditKey = opts.auditKey ?? (0, import_node_crypto7.randomBytes)(32);
|
|
2981
|
+
const pushKey = (idCode) => (0, import_node_crypto7.createHmac)("sha256", auditKey).update(`eid-push-audit:${idCode}`).digest("hex").slice(0, 32);
|
|
2982
|
+
async function emit(kind, sessionId, meta, ctx2) {
|
|
2983
|
+
if (!opts.onEvent) return;
|
|
2984
|
+
try {
|
|
2985
|
+
await opts.onEvent({ kind, sessionId, meta });
|
|
2986
|
+
} catch (e) {
|
|
2987
|
+
onError(e, ctx2);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
function endEvent(tx, endResult, ctx2) {
|
|
2991
|
+
const kind = endResult?.startsWith("USER_REFUSED") ? "refused" : endResult === "TIMEOUT" ? "expired" : "failed";
|
|
2992
|
+
return emit(kind, tx.sessionId, { mode: tx.mode, endResult: endResult ?? "UNKNOWN" }, ctx2);
|
|
2993
|
+
}
|
|
2994
|
+
function unverified(tx, e, ctx2) {
|
|
2995
|
+
const reason = e instanceof RevocationFailure ? e.revoked ? "revoked" : "revocation_unavailable" : "validation";
|
|
2996
|
+
return emit("unverified", tx.sessionId, { mode: tx.mode, reason }, ctx2);
|
|
2997
|
+
}
|
|
2244
2998
|
const displayText = (opts.displayText ?? `${client.rpName}-\u0434 \u043D\u044D\u0432\u0442\u0440\u044D\u0445`).slice(0, 60);
|
|
2245
2999
|
const interactions = [{ type: "displayTextAndPIN", displayText60: displayText }];
|
|
2246
3000
|
const json = (status2, body, extra = {}) => ({
|
|
@@ -2331,11 +3085,18 @@ function createEidLogin(opts) {
|
|
|
2331
3085
|
});
|
|
2332
3086
|
}
|
|
2333
3087
|
}
|
|
2334
|
-
|
|
3088
|
+
return { ...await save(tx), appLink };
|
|
3089
|
+
}
|
|
3090
|
+
async function save(tx) {
|
|
3091
|
+
const binding = (0, import_node_crypto7.randomBytes)(32).toString("base64url");
|
|
2335
3092
|
const full = { ...tx, bindingHash: sha256hex(binding), expiresAt: Date.now() + ttl };
|
|
2336
3093
|
await store.set(full.sessionId, full, ttl);
|
|
2337
3094
|
const setCookie = `${cookieName}=${full.sessionId}.${binding}; ${cookieAttrs(Math.ceil(ttl / 1e3))}`;
|
|
2338
|
-
return { tx: full, setCookie, appLink };
|
|
3095
|
+
return { tx: full, setCookie, appLink: void 0 };
|
|
3096
|
+
}
|
|
3097
|
+
function startDecoy() {
|
|
3098
|
+
const acsp = void 0;
|
|
3099
|
+
return save({ sessionId: (0, import_node_crypto7.randomUUID)(), mode: "push", vc: String((0, import_node_crypto7.randomInt)(1e5)).padStart(5, "0"), acsp, decoy: true });
|
|
2339
3100
|
}
|
|
2340
3101
|
async function start(req) {
|
|
2341
3102
|
const url = new URL(req.url, "http://x");
|
|
@@ -2361,22 +3122,40 @@ function createEidLogin(opts) {
|
|
|
2361
3122
|
const mode = body.mode;
|
|
2362
3123
|
if (mode !== "qr" && mode !== "push" && mode !== "web2app") return json(400, { error: "invalid_mode" });
|
|
2363
3124
|
const lang = body.lang === "en" ? "eng" : "mon";
|
|
2364
|
-
const
|
|
3125
|
+
const ctx2 = { request: req, mode };
|
|
2365
3126
|
if (mode === "web2app" && !opts.callbackUrl) return json(400, { error: "web2app_not_configured" });
|
|
2366
3127
|
const idCode = typeof body.idCode === "string" ? body.idCode.replace(/\s+/g, "").toUpperCase() : "";
|
|
2367
3128
|
if (mode === "push" && !ID_RE.test(idCode)) return json(400, { error: "invalid_id" });
|
|
2368
|
-
const limited = () =>
|
|
3129
|
+
const limited = async () => {
|
|
3130
|
+
if (mode === "push") await emit("push_limited", null, { mode, pushKey: pushKey(idCode) }, ctx2);
|
|
3131
|
+
return json(429, { error: "rate_limited" }, { "Retry-After": "60" });
|
|
3132
|
+
};
|
|
2369
3133
|
const ip = req.ip || (opts.trustProxy ? forwardedClientIp(req.headers, opts.trustProxy) : void 0);
|
|
2370
3134
|
if (ip && !await rateLimit(`start:${ip}`)) return limited();
|
|
2371
3135
|
if (mode === "push" && !await rateLimit(`push:${idCode}`)) return limited();
|
|
3136
|
+
const began = Date.now();
|
|
2372
3137
|
let r;
|
|
3138
|
+
let err;
|
|
3139
|
+
let decoy = false;
|
|
2373
3140
|
try {
|
|
2374
3141
|
r = await begin(req, mode, idCode, lang);
|
|
2375
3142
|
} catch (e) {
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
3143
|
+
err = e;
|
|
3144
|
+
}
|
|
3145
|
+
if (err !== void 0 && mode === "push" && decoyOn && isRefusal(err)) {
|
|
3146
|
+
const ae = err;
|
|
3147
|
+
onError(new Error(`eid-login: eID push-\u0438\u0439\u0433 \u0442\u0430\u0442\u0433\u0430\u043B\u0437\u0430\u0432 (${ae.status}${ae.code ? ` ${ae.code}` : ""}); decoy session-\u043E\u043E\u0440 \u0445\u0430\u0440\u0438\u0443\u043B\u0430\u0432`), ctx2);
|
|
3148
|
+
err = void 0;
|
|
3149
|
+
r = await startDecoy();
|
|
3150
|
+
decoy = true;
|
|
3151
|
+
}
|
|
3152
|
+
if (mode === "push" && pushFloorMs > 0) await sleep(began + pushFloorMs - Date.now(), req.signal);
|
|
3153
|
+
if (err !== void 0 || !r) {
|
|
3154
|
+
onError(err, ctx2);
|
|
3155
|
+
if (err instanceof ApiError && err.status === 429) return limited();
|
|
3156
|
+
return json(502, { error: "eid_unavailable" });
|
|
2379
3157
|
}
|
|
3158
|
+
if (mode === "push") await emit("push_sent", r.tx.sessionId, { mode, pushKey: pushKey(idCode), decoy }, ctx2);
|
|
2380
3159
|
if (mode === "web2app") {
|
|
2381
3160
|
return { status: 302, headers: { Location: r.appLink, "Cache-Control": "no-store", "Referrer-Policy": "no-referrer", "Set-Cookie": r.setCookie }, body: "" };
|
|
2382
3161
|
}
|
|
@@ -2388,30 +3167,49 @@ function createEidLogin(opts) {
|
|
|
2388
3167
|
}
|
|
2389
3168
|
async function finish(req, tx, r, flow, verifier) {
|
|
2390
3169
|
const v = client.validator.validateAuth(r, tx.acsp, { expectedFlowType: flow, userChallengeVerifier: verifier });
|
|
3170
|
+
const ctx2 = { request: req, mode: tx.mode };
|
|
3171
|
+
let rev;
|
|
3172
|
+
let status2;
|
|
3173
|
+
const checker = revocation && (async (c, i) => status2 = await revocation(c, i));
|
|
3174
|
+
try {
|
|
3175
|
+
rev = await client.validator.checkRevocationWith(v.certificate, checker, revocationMode);
|
|
3176
|
+
} catch (e) {
|
|
3177
|
+
throw new RevocationFailure(`revocation: ${e instanceof Error ? e.message : String(e)}`, status2 === "revoked");
|
|
3178
|
+
}
|
|
3179
|
+
if (rev === "unchecked") onError(new Error("eid-login: OCSP \u0445\u0430\u0440\u0438\u0443\u0433\u04AF\u0439 \u2014 \u0446\u0443\u0446\u043B\u0430\u043B\u0442 \u0448\u0430\u043B\u0433\u0430\u043B\u0433\u04AF\u0439 \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u0432 (soft-fail)"), ctx2);
|
|
2391
3180
|
if (!await store.delete(tx.sessionId)) return void 0;
|
|
2392
|
-
const out = await opts.onSuccess(toIdentity(tx.sessionId, r, v, flow
|
|
3181
|
+
const out = await opts.onSuccess(toIdentity(tx.sessionId, r, v, flow, rev), ctx2) ?? {};
|
|
3182
|
+
await emit("success", tx.sessionId, { mode: tx.mode, flow, revocation: rev }, ctx2);
|
|
2393
3183
|
return out;
|
|
2394
3184
|
}
|
|
2395
3185
|
async function status(req) {
|
|
2396
3186
|
if (req.method !== "GET") return json(405, { error: "method_not_allowed" });
|
|
2397
3187
|
const tx = await loadTx(req);
|
|
2398
3188
|
if (!tx) return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2399
|
-
const
|
|
3189
|
+
const ctx2 = { request: req, mode: tx.mode };
|
|
2400
3190
|
let r;
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
3191
|
+
if (tx.decoy) {
|
|
3192
|
+
await sleep(pollMs, req.signal);
|
|
3193
|
+
r = { state: "RUNNING" };
|
|
3194
|
+
} else {
|
|
3195
|
+
try {
|
|
3196
|
+
r = await client.session.poll(tx.sessionId, pollMs);
|
|
3197
|
+
} catch (e) {
|
|
3198
|
+
if (e instanceof ApiError && e.status === 404) {
|
|
3199
|
+
await store.delete(tx.sessionId);
|
|
3200
|
+
await emit("expired", tx.sessionId, { mode: tx.mode }, ctx2);
|
|
3201
|
+
return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
3202
|
+
}
|
|
3203
|
+
onError(e, ctx2);
|
|
3204
|
+
return json(502, { error: "eid_unavailable" });
|
|
2407
3205
|
}
|
|
2408
|
-
onError(e, ctx);
|
|
2409
|
-
return json(502, { error: "eid_unavailable" });
|
|
2410
3206
|
}
|
|
2411
3207
|
const expiresIn = Math.max(0, Math.ceil((tx.expiresAt - Date.now()) / 1e3));
|
|
2412
3208
|
if (r.state !== "COMPLETE") return json(200, { state: "PENDING", qr: qrLink(tx), vc: tx.vc, expiresIn });
|
|
2413
3209
|
if (r.endResult !== "OK") {
|
|
2414
3210
|
await store.delete(tx.sessionId);
|
|
3211
|
+
await endEvent(tx, r.endResult, ctx2);
|
|
3212
|
+
if (tx.mode === "push" && r.endResult === "TIMEOUT") return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2415
3213
|
return json(200, { state: "FAILED", error: r.endResult ?? "UNKNOWN" }, { "Set-Cookie": clearCookie });
|
|
2416
3214
|
}
|
|
2417
3215
|
if (tx.mode === "web2app") return json(200, { state: "WAIT_CALLBACK" });
|
|
@@ -2420,9 +3218,10 @@ function createEidLogin(opts) {
|
|
|
2420
3218
|
if (!out) return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2421
3219
|
return json(200, { state: "OK", redirect: out.redirect, data: out.data }, mergeHeaders({ "Set-Cookie": clearCookie }, out.headers));
|
|
2422
3220
|
} catch (e) {
|
|
2423
|
-
onError(e,
|
|
3221
|
+
onError(e, ctx2);
|
|
2424
3222
|
if (!(e instanceof ValidationError || e instanceof SessionFailedError)) return json(500, { error: "internal" });
|
|
2425
3223
|
await store.delete(tx.sessionId);
|
|
3224
|
+
await unverified(tx, e, ctx2);
|
|
2426
3225
|
return json(200, { state: "FAILED", error: "VALIDATION" }, { "Set-Cookie": clearCookie });
|
|
2427
3226
|
}
|
|
2428
3227
|
}
|
|
@@ -2433,19 +3232,23 @@ function createEidLogin(opts) {
|
|
|
2433
3232
|
const verifier = q.get("userChallengeVerifier") ?? "";
|
|
2434
3233
|
const fail2 = (code) => redirect(failTo(code), { "Set-Cookie": clearCookie });
|
|
2435
3234
|
const tx = await loadTx(req);
|
|
2436
|
-
if (!tx
|
|
2437
|
-
|
|
2438
|
-
|
|
3235
|
+
if (!tx) return fail2("expired");
|
|
3236
|
+
const ctx2 = { request: req, mode: tx.mode };
|
|
3237
|
+
if (tx.sessionId !== sid || tx.mode !== "web2app" || !verifier) {
|
|
3238
|
+
await emit("flow_refused", tx.sessionId, { mode: tx.mode, reason: "unbound" }, ctx2);
|
|
3239
|
+
return fail2("unbound");
|
|
3240
|
+
}
|
|
2439
3241
|
let r;
|
|
2440
3242
|
try {
|
|
2441
3243
|
r = await client.session.poll(tx.sessionId, 1e4);
|
|
2442
3244
|
} catch (e) {
|
|
2443
|
-
onError(e,
|
|
3245
|
+
onError(e, ctx2);
|
|
2444
3246
|
return fail2("eid_unavailable");
|
|
2445
3247
|
}
|
|
2446
3248
|
if (r.state !== "COMPLETE") return fail2("not_complete");
|
|
2447
3249
|
if (r.endResult !== "OK") {
|
|
2448
3250
|
await store.delete(tx.sessionId);
|
|
3251
|
+
await endEvent(tx, r.endResult, ctx2);
|
|
2449
3252
|
return fail2(r.endResult ?? "UNKNOWN");
|
|
2450
3253
|
}
|
|
2451
3254
|
try {
|
|
@@ -2453,9 +3256,10 @@ function createEidLogin(opts) {
|
|
|
2453
3256
|
if (!out) return fail2("expired");
|
|
2454
3257
|
return redirect(out.redirect ?? successRedirect, mergeHeaders({ "Set-Cookie": clearCookie }, out.headers));
|
|
2455
3258
|
} catch (e) {
|
|
2456
|
-
onError(e,
|
|
3259
|
+
onError(e, ctx2);
|
|
2457
3260
|
if (!(e instanceof ValidationError || e instanceof SessionFailedError)) return fail2("internal");
|
|
2458
3261
|
await store.delete(tx.sessionId);
|
|
3262
|
+
await unverified(tx, e, ctx2);
|
|
2459
3263
|
return fail2("VALIDATION");
|
|
2460
3264
|
}
|
|
2461
3265
|
}
|
|
@@ -2485,7 +3289,7 @@ function createEidLogin(opts) {
|
|
|
2485
3289
|
mode: "qr"
|
|
2486
3290
|
});
|
|
2487
3291
|
}
|
|
2488
|
-
const r = await handleRequest({ method: req.method, url: req.url, headers, body, ip });
|
|
3292
|
+
const r = await handleRequest({ method: req.method, url: req.url, headers, body, ip, signal: req.signal });
|
|
2489
3293
|
const h = new Headers();
|
|
2490
3294
|
for (const [k, v] of Object.entries(r.headers)) for (const x of [v].flat()) h.append(k, x);
|
|
2491
3295
|
return new Response(r.status === 302 || r.status === 303 || r.body === "" ? null : r.body, { status: r.status, headers: h });
|
|
@@ -2526,12 +3330,14 @@ function createEidLogin(opts) {
|
|
|
2526
3330
|
buildDeviceLink,
|
|
2527
3331
|
buildRegisterBody,
|
|
2528
3332
|
createEidLogin,
|
|
3333
|
+
defaultRateLimit,
|
|
2529
3334
|
deviceLink,
|
|
2530
3335
|
deviceLinkInput,
|
|
2531
3336
|
forwardedClientIp,
|
|
2532
3337
|
interactionsToBase64,
|
|
2533
3338
|
isMobileUserAgent,
|
|
2534
3339
|
memoryLoginStore,
|
|
3340
|
+
ocspRevocationChecker,
|
|
2535
3341
|
openOrShowQR,
|
|
2536
3342
|
parseExpiresAt,
|
|
2537
3343
|
parseRetryAfter,
|