@gerege-systems/eid-mongolia-sdk 0.15.1 → 0.15.3
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 +35 -3
- package/dist/button.cjs +70 -20
- package/dist/button.d.cts +2 -0
- package/dist/button.d.ts +2 -0
- package/dist/button.js +70 -20
- package/dist/chunk-52O3T7DZ.js +1043 -0
- package/dist/index.cjs +837 -68
- package/dist/index.d.cts +113 -1
- package/dist/index.d.ts +113 -1
- package/dist/index.js +265 -28
- 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
|
@@ -59,6 +59,7 @@ __export(index_exports, {
|
|
|
59
59
|
interactionsToBase64: () => interactionsToBase64,
|
|
60
60
|
isMobileUserAgent: () => isMobileUserAgent,
|
|
61
61
|
memoryLoginStore: () => memoryLoginStore,
|
|
62
|
+
ocspRevocationChecker: () => ocspRevocationChecker,
|
|
62
63
|
openOrShowQR: () => openOrShowQR,
|
|
63
64
|
parseExpiresAt: () => parseExpiresAt,
|
|
64
65
|
parseRetryAfter: () => parseRetryAfter,
|
|
@@ -224,6 +225,13 @@ var Http = class {
|
|
|
224
225
|
post(path, body, timeoutMs) {
|
|
225
226
|
return this.json("POST", path, JSON.stringify(body), "application/json", timeoutMs);
|
|
226
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
|
+
}
|
|
227
235
|
get(path, timeoutMs) {
|
|
228
236
|
return this.json("GET", path, void 0, void 0, timeoutMs);
|
|
229
237
|
}
|
|
@@ -537,12 +545,32 @@ function registrationFromCompleted(done, extra) {
|
|
|
537
545
|
if (extra.medium) out.medium = extra.medium;
|
|
538
546
|
if (extra.sessionID) out.sessionID = extra.sessionID;
|
|
539
547
|
if (done.hasPage && done.pageSha256) out.pageSha256 = done.pageSha256;
|
|
540
|
-
if (
|
|
541
|
-
|
|
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
|
+
}
|
|
542
558
|
if (extra.onBehalfOf) out.onBehalfOf = extra.onBehalfOf;
|
|
543
559
|
if (extra.signerEtsi) out.signerEtsi = extra.signerEtsi;
|
|
544
560
|
return out;
|
|
545
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
|
+
}
|
|
546
574
|
var HEX64 = /^[0-9a-f]{64}$/;
|
|
547
575
|
var MAX_CMS_BYTES = 28672;
|
|
548
576
|
function buildRegisterBody(input) {
|
|
@@ -696,7 +724,7 @@ var SignApi = class {
|
|
|
696
724
|
* (base64 → байт) `complete(state, signature)`-д.
|
|
697
725
|
*/
|
|
698
726
|
async pdfDigest(input) {
|
|
699
|
-
const
|
|
727
|
+
const digest2 = dtbsBase64(input.dtbs);
|
|
700
728
|
const fileName = input.fileName?.trim() ?? "";
|
|
701
729
|
if (!fileName || Array.from(fileName).length > PDF_MAX_FILE_NAME) {
|
|
702
730
|
throw new ValidationError(`pdfDigest: fileName \u0437\u0430\u0430\u0432\u0430\u043B, \u2264 ${PDF_MAX_FILE_NAME} \u0442\u044D\u043C\u0434\u044D\u0433\u0442`);
|
|
@@ -714,7 +742,7 @@ var SignApi = class {
|
|
|
714
742
|
certificateLevel: input.certificateLevel ?? this.defaults.certificateLevel,
|
|
715
743
|
signatureProtocol: "RAW_DIGEST_SIGNATURE",
|
|
716
744
|
signatureProtocolParameters: {
|
|
717
|
-
digest,
|
|
745
|
+
digest: digest2,
|
|
718
746
|
signatureAlgorithm: "sha256WithRSAEncryption",
|
|
719
747
|
signatureAlgorithmParameters: { hashAlgorithm: "SHA-256" }
|
|
720
748
|
},
|
|
@@ -734,7 +762,7 @@ var SignApi = class {
|
|
|
734
762
|
}
|
|
735
763
|
dl = { sessionToken: String(r.sessionToken), sessionSecret: String(r.sessionSecret), deviceLinkBase: String(r.deviceLinkBase) };
|
|
736
764
|
}
|
|
737
|
-
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 };
|
|
738
766
|
}
|
|
739
767
|
/**
|
|
740
768
|
* `POST /pdf/signatures` — `pades.complete()`-ийн дараа CMS-ийг CA-д бүртгэнэ (`/verify/{docID}`, иргэний түүх). PDF
|
|
@@ -744,16 +772,38 @@ var SignApi = class {
|
|
|
744
772
|
* `/verify` «бүртгэлгүй» гэнэ.
|
|
745
773
|
*/
|
|
746
774
|
async registerSignature(input) {
|
|
747
|
-
|
|
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
|
+
}
|
|
748
797
|
}
|
|
749
798
|
async registerBody(body) {
|
|
750
|
-
const raw = await this.http.
|
|
799
|
+
const { status, body: raw } = await this.http.postWithStatus(routePath(RP_ROUTES.pdfSignatures), body);
|
|
751
800
|
const r = rec2(raw);
|
|
752
801
|
return {
|
|
753
802
|
docID: String(r.docID ?? body.docID),
|
|
754
803
|
revIndex: Number(r.revIndex ?? 0),
|
|
755
804
|
registeredAt: typeof r.registeredAt === "string" ? r.registeredAt : "",
|
|
756
805
|
verifyUrl: String(r.verifyUrl ?? ""),
|
|
806
|
+
created: status === 201,
|
|
757
807
|
raw
|
|
758
808
|
};
|
|
759
809
|
}
|
|
@@ -1113,11 +1163,11 @@ function parseSubject(cert) {
|
|
|
1113
1163
|
if (atv.tag !== 48) throw new ValidationError("subject: AttributeTypeAndValue SEQUENCE \u0431\u0438\u0448");
|
|
1114
1164
|
const oidT = tlv(der, atv.start, atv.end);
|
|
1115
1165
|
const valT = tlv(der, oidT.end, atv.end);
|
|
1116
|
-
const
|
|
1166
|
+
const oid2 = oidOf(der, oidT);
|
|
1117
1167
|
const value = stringOf(der, valT);
|
|
1118
|
-
if (value !== void 0 && !(
|
|
1119
|
-
out.attributes[
|
|
1120
|
-
const key = OIDS[
|
|
1168
|
+
if (value !== void 0 && !(oid2 in out.attributes)) {
|
|
1169
|
+
out.attributes[oid2] = value;
|
|
1170
|
+
const key = OIDS[oid2];
|
|
1121
1171
|
if (key) out[key] = value;
|
|
1122
1172
|
}
|
|
1123
1173
|
ap = atv.end;
|
|
@@ -1128,8 +1178,8 @@ function parseSubject(cert) {
|
|
|
1128
1178
|
out.name = full || out.commonName || null;
|
|
1129
1179
|
return out;
|
|
1130
1180
|
}
|
|
1131
|
-
function certExtension(der,
|
|
1132
|
-
return certExtensions(der).find((e) => e.oid ===
|
|
1181
|
+
function certExtension(der, oid2) {
|
|
1182
|
+
return certExtensions(der).find((e) => e.oid === oid2)?.value;
|
|
1133
1183
|
}
|
|
1134
1184
|
function certExtensions(der) {
|
|
1135
1185
|
const out = [];
|
|
@@ -1141,9 +1191,9 @@ function certExtensions(der) {
|
|
|
1141
1191
|
for (let pos = f.end; pos < tbs.end; ) {
|
|
1142
1192
|
const t = tlv(der, pos, tbs.end);
|
|
1143
1193
|
if (t.tag === 163) {
|
|
1144
|
-
const
|
|
1145
|
-
for (let ep =
|
|
1146
|
-
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);
|
|
1147
1197
|
const id = tlv(der, ext.start, ext.end);
|
|
1148
1198
|
let v = tlv(der, id.end, ext.end);
|
|
1149
1199
|
let critical = false;
|
|
@@ -1432,10 +1482,10 @@ var ResponseValidator = class {
|
|
|
1432
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})`
|
|
1433
1483
|
);
|
|
1434
1484
|
}
|
|
1435
|
-
const
|
|
1485
|
+
const digest2 = Buffer.from(digestB64, "base64");
|
|
1436
1486
|
const { alg, hash } = this.algorithmOf(result, leaf, o.expectedAlgorithm ?? null, hashAlgorithm);
|
|
1437
|
-
if (
|
|
1438
|
-
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)) {
|
|
1439
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");
|
|
1440
1490
|
}
|
|
1441
1491
|
return {
|
|
@@ -1641,13 +1691,20 @@ var ResponseValidator = class {
|
|
|
1641
1691
|
* "skipped" (hook алга). "revoked", мөн hard-fail горимд "unknown" → ValidationError; hook-ийн алдаа дамжина.
|
|
1642
1692
|
*/
|
|
1643
1693
|
async checkRevocationStatus(cert) {
|
|
1644
|
-
|
|
1645
|
-
|
|
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));
|
|
1646
1703
|
if (status === "good") return "good";
|
|
1647
1704
|
if (status === "revoked") {
|
|
1648
1705
|
throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
|
|
1649
1706
|
}
|
|
1650
|
-
if (
|
|
1707
|
+
if (mode === "hard-fail") {
|
|
1651
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)");
|
|
1652
1709
|
}
|
|
1653
1710
|
return "unchecked";
|
|
@@ -1676,9 +1733,9 @@ function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
|
1676
1733
|
if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto4.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
|
|
1677
1734
|
return (0, import_node_crypto4.verify)(h, payload, { key, padding: import_node_crypto4.constants.RSA_PKCS1_PADDING }, sig);
|
|
1678
1735
|
}
|
|
1679
|
-
function verifyPrehashedSignature(
|
|
1736
|
+
function verifyPrehashedSignature(digest2, sigB64, key, alg, hash) {
|
|
1680
1737
|
const sig = Buffer.from(sigB64, "base64");
|
|
1681
|
-
if (alg === "ecdsa-with-SHA256") return ecdsaP256VerifyPrehashed(
|
|
1738
|
+
if (alg === "ecdsa-with-SHA256") return ecdsaP256VerifyPrehashed(digest2, sig, key);
|
|
1682
1739
|
const modBits = key.asymmetricKeyDetails?.modulusLength ?? 0;
|
|
1683
1740
|
if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
|
|
1684
1741
|
let em;
|
|
@@ -1687,10 +1744,10 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
|
1687
1744
|
} catch {
|
|
1688
1745
|
return false;
|
|
1689
1746
|
}
|
|
1690
|
-
if (alg === "rsassa-pss") return emsaPssVerify(
|
|
1747
|
+
if (alg === "rsassa-pss") return emsaPssVerify(digest2, em, modBits - 1, hash);
|
|
1691
1748
|
const prefix = DIGEST_INFO[hash];
|
|
1692
1749
|
if (!prefix) return false;
|
|
1693
|
-
const t = Buffer.concat([prefix,
|
|
1750
|
+
const t = Buffer.concat([prefix, digest2]);
|
|
1694
1751
|
const expected = Buffer.concat([Buffer.from([0, 1]), Buffer.alloc(em.length - t.length - 3, 255), Buffer.from([0]), t]);
|
|
1695
1752
|
return em.length === expected.length && em.equals(expected);
|
|
1696
1753
|
}
|
|
@@ -1741,7 +1798,7 @@ function derInts(sig) {
|
|
|
1741
1798
|
}
|
|
1742
1799
|
return i === sig.length ? [out[0], out[1]] : null;
|
|
1743
1800
|
}
|
|
1744
|
-
function ecdsaP256VerifyPrehashed(
|
|
1801
|
+
function ecdsaP256VerifyPrehashed(digest2, sig, key) {
|
|
1745
1802
|
if (key.asymmetricKeyDetails?.namedCurve !== "prime256v1") return false;
|
|
1746
1803
|
const jwk = key.export({ format: "jwk" });
|
|
1747
1804
|
if (!jwk.x || !jwk.y) return false;
|
|
@@ -1751,7 +1808,7 @@ function ecdsaP256VerifyPrehashed(digest, sig, key) {
|
|
|
1751
1808
|
const [r, sv] = rs;
|
|
1752
1809
|
const { n } = P256;
|
|
1753
1810
|
if (r <= 0n || r >= n || sv <= 0n || sv >= n) return false;
|
|
1754
|
-
const z = BigInt("0x" +
|
|
1811
|
+
const z = BigInt("0x" + digest2.subarray(0, 32).toString("hex"));
|
|
1755
1812
|
const w = inv(sv, n);
|
|
1756
1813
|
const R = ptAdd(ptMul(mod(z * w, n), [P256.gx, P256.gy]), ptMul(mod(r * w, n), Q));
|
|
1757
1814
|
return R !== null && mod(R[0], n) === r;
|
|
@@ -1804,10 +1861,10 @@ function keyUsageBits(cert) {
|
|
|
1804
1861
|
function extKeyUsage(cert) {
|
|
1805
1862
|
const v = certExtension(cert.raw, "2.5.29.37");
|
|
1806
1863
|
if (!v) return [];
|
|
1807
|
-
const
|
|
1864
|
+
const seq2 = tlv(v, 0, v.length);
|
|
1808
1865
|
const out = [];
|
|
1809
|
-
for (let p =
|
|
1810
|
-
const t = tlv(v, p,
|
|
1866
|
+
for (let p = seq2.start; p < seq2.end; ) {
|
|
1867
|
+
const t = tlv(v, p, seq2.end);
|
|
1811
1868
|
out.push(oidOf(v, t));
|
|
1812
1869
|
p = t.end;
|
|
1813
1870
|
}
|
|
@@ -1822,9 +1879,9 @@ function subjectKeyId(cert) {
|
|
|
1822
1879
|
function authorityKeyId(cert) {
|
|
1823
1880
|
const v = certExtension(cert.raw, "2.5.29.35");
|
|
1824
1881
|
if (!v) return void 0;
|
|
1825
|
-
const
|
|
1826
|
-
if (
|
|
1827
|
-
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);
|
|
1828
1885
|
return k.tag === 128 && k.end > k.start ? Buffer.from(v.subarray(k.start, k.end)).toString("hex") : void 0;
|
|
1829
1886
|
}
|
|
1830
1887
|
function isCa(cert) {
|
|
@@ -1890,9 +1947,9 @@ function caReason(ca, below, now, usage) {
|
|
|
1890
1947
|
function basicConstraintsPathLen(cert) {
|
|
1891
1948
|
const v = certExtension(cert.raw, "2.5.29.19");
|
|
1892
1949
|
if (!v) return void 0;
|
|
1893
|
-
const
|
|
1894
|
-
for (let p =
|
|
1895
|
-
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);
|
|
1896
1953
|
if (t.tag === 2) {
|
|
1897
1954
|
if (t.end - t.start > 4 || (v[t.start] & 128) !== 0) throw new ValidationError("basicConstraints: pathLen \u0431\u0443\u0440\u0443\u0443");
|
|
1898
1955
|
let n = 0;
|
|
@@ -1963,6 +2020,614 @@ var EidClient = class {
|
|
|
1963
2020
|
}
|
|
1964
2021
|
};
|
|
1965
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
|
+
|
|
1966
2631
|
// src/devicelink.ts
|
|
1967
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}$/;
|
|
1968
2633
|
var VC_RE = /^[0-9]{5}$/;
|
|
@@ -2006,7 +2671,7 @@ function openOrShowQR(session, opts) {
|
|
|
2006
2671
|
}
|
|
2007
2672
|
|
|
2008
2673
|
// src/devicelinkv3.ts
|
|
2009
|
-
var
|
|
2674
|
+
var import_node_crypto6 = require("crypto");
|
|
2010
2675
|
var TYPES = ["QR", "Web2App", "App2App"];
|
|
2011
2676
|
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
2012
2677
|
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
@@ -2063,7 +2728,7 @@ function buildDeviceLink(p) {
|
|
|
2063
2728
|
callback,
|
|
2064
2729
|
unprotected
|
|
2065
2730
|
].join("|");
|
|
2066
|
-
const authCode = (0,
|
|
2731
|
+
const authCode = (0, import_node_crypto6.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
2067
2732
|
return `${unprotected}&authCode=${authCode}`;
|
|
2068
2733
|
}
|
|
2069
2734
|
function deviceLinkInput(s, deviceLinkType, lang) {
|
|
@@ -2107,7 +2772,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
2107
2772
|
}
|
|
2108
2773
|
|
|
2109
2774
|
// src/login.ts
|
|
2110
|
-
var
|
|
2775
|
+
var import_node_crypto7 = require("crypto");
|
|
2111
2776
|
function memoryLoginStore() {
|
|
2112
2777
|
const m = /* @__PURE__ */ new Map();
|
|
2113
2778
|
return {
|
|
@@ -2233,13 +2898,43 @@ function readCookie(h, name) {
|
|
|
2233
2898
|
}
|
|
2234
2899
|
return "";
|
|
2235
2900
|
}
|
|
2236
|
-
var sha256hex = (s) => (0,
|
|
2901
|
+
var sha256hex = (s) => (0, import_node_crypto7.createHash)("sha256").update(s).digest("hex");
|
|
2237
2902
|
function safeEqualHex(a, b) {
|
|
2238
2903
|
const x = Buffer.from(a, "hex");
|
|
2239
2904
|
const y = Buffer.from(b, "hex");
|
|
2240
|
-
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
|
+
}
|
|
2241
2929
|
}
|
|
2242
|
-
|
|
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) {
|
|
2243
2938
|
const s = v.parsedSubject;
|
|
2244
2939
|
const etsi = s.serialNumber ?? "";
|
|
2245
2940
|
const m = /^PNOMN-([0-9]+)$/.exec(etsi);
|
|
@@ -2255,7 +2950,8 @@ function toIdentity(sessionId, r, v, flowType) {
|
|
|
2255
2950
|
certificateLevel: v.certificateLevel,
|
|
2256
2951
|
certificateDerB64: r.certificateDerB64 ?? "",
|
|
2257
2952
|
flowType,
|
|
2258
|
-
person: personMatchesCertificate(r.person, s) ? r.person ?? null : null
|
|
2953
|
+
person: personMatchesCertificate(r.person, s) ? r.person ?? null : null,
|
|
2954
|
+
revocation
|
|
2259
2955
|
};
|
|
2260
2956
|
}
|
|
2261
2957
|
function createEidLogin(opts) {
|
|
@@ -2277,6 +2973,28 @@ function createEidLogin(opts) {
|
|
|
2277
2973
|
const failureRedirect = opts.failureRedirect ?? "/";
|
|
2278
2974
|
const onError = opts.onError ?? ((e) => console.error("eid-login:", e instanceof Error ? e.message : e));
|
|
2279
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
|
+
}
|
|
2280
2998
|
const displayText = (opts.displayText ?? `${client.rpName}-\u0434 \u043D\u044D\u0432\u0442\u0440\u044D\u0445`).slice(0, 60);
|
|
2281
2999
|
const interactions = [{ type: "displayTextAndPIN", displayText60: displayText }];
|
|
2282
3000
|
const json = (status2, body, extra = {}) => ({
|
|
@@ -2367,11 +3085,18 @@ function createEidLogin(opts) {
|
|
|
2367
3085
|
});
|
|
2368
3086
|
}
|
|
2369
3087
|
}
|
|
2370
|
-
|
|
3088
|
+
return { ...await save(tx), appLink };
|
|
3089
|
+
}
|
|
3090
|
+
async function save(tx) {
|
|
3091
|
+
const binding = (0, import_node_crypto7.randomBytes)(32).toString("base64url");
|
|
2371
3092
|
const full = { ...tx, bindingHash: sha256hex(binding), expiresAt: Date.now() + ttl };
|
|
2372
3093
|
await store.set(full.sessionId, full, ttl);
|
|
2373
3094
|
const setCookie = `${cookieName}=${full.sessionId}.${binding}; ${cookieAttrs(Math.ceil(ttl / 1e3))}`;
|
|
2374
|
-
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 });
|
|
2375
3100
|
}
|
|
2376
3101
|
async function start(req) {
|
|
2377
3102
|
const url = new URL(req.url, "http://x");
|
|
@@ -2397,22 +3122,40 @@ function createEidLogin(opts) {
|
|
|
2397
3122
|
const mode = body.mode;
|
|
2398
3123
|
if (mode !== "qr" && mode !== "push" && mode !== "web2app") return json(400, { error: "invalid_mode" });
|
|
2399
3124
|
const lang = body.lang === "en" ? "eng" : "mon";
|
|
2400
|
-
const
|
|
3125
|
+
const ctx2 = { request: req, mode };
|
|
2401
3126
|
if (mode === "web2app" && !opts.callbackUrl) return json(400, { error: "web2app_not_configured" });
|
|
2402
3127
|
const idCode = typeof body.idCode === "string" ? body.idCode.replace(/\s+/g, "").toUpperCase() : "";
|
|
2403
3128
|
if (mode === "push" && !ID_RE.test(idCode)) return json(400, { error: "invalid_id" });
|
|
2404
|
-
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
|
+
};
|
|
2405
3133
|
const ip = req.ip || (opts.trustProxy ? forwardedClientIp(req.headers, opts.trustProxy) : void 0);
|
|
2406
3134
|
if (ip && !await rateLimit(`start:${ip}`)) return limited();
|
|
2407
3135
|
if (mode === "push" && !await rateLimit(`push:${idCode}`)) return limited();
|
|
3136
|
+
const began = Date.now();
|
|
2408
3137
|
let r;
|
|
3138
|
+
let err;
|
|
3139
|
+
let decoy = false;
|
|
2409
3140
|
try {
|
|
2410
3141
|
r = await begin(req, mode, idCode, lang);
|
|
2411
3142
|
} catch (e) {
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
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;
|
|
2415
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" });
|
|
3157
|
+
}
|
|
3158
|
+
if (mode === "push") await emit("push_sent", r.tx.sessionId, { mode, pushKey: pushKey(idCode), decoy }, ctx2);
|
|
2416
3159
|
if (mode === "web2app") {
|
|
2417
3160
|
return { status: 302, headers: { Location: r.appLink, "Cache-Control": "no-store", "Referrer-Policy": "no-referrer", "Set-Cookie": r.setCookie }, body: "" };
|
|
2418
3161
|
}
|
|
@@ -2424,30 +3167,49 @@ function createEidLogin(opts) {
|
|
|
2424
3167
|
}
|
|
2425
3168
|
async function finish(req, tx, r, flow, verifier) {
|
|
2426
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);
|
|
2427
3180
|
if (!await store.delete(tx.sessionId)) return void 0;
|
|
2428
|
-
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);
|
|
2429
3183
|
return out;
|
|
2430
3184
|
}
|
|
2431
3185
|
async function status(req) {
|
|
2432
3186
|
if (req.method !== "GET") return json(405, { error: "method_not_allowed" });
|
|
2433
3187
|
const tx = await loadTx(req);
|
|
2434
3188
|
if (!tx) return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2435
|
-
const
|
|
3189
|
+
const ctx2 = { request: req, mode: tx.mode };
|
|
2436
3190
|
let r;
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
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" });
|
|
2443
3205
|
}
|
|
2444
|
-
onError(e, ctx);
|
|
2445
|
-
return json(502, { error: "eid_unavailable" });
|
|
2446
3206
|
}
|
|
2447
3207
|
const expiresIn = Math.max(0, Math.ceil((tx.expiresAt - Date.now()) / 1e3));
|
|
2448
3208
|
if (r.state !== "COMPLETE") return json(200, { state: "PENDING", qr: qrLink(tx), vc: tx.vc, expiresIn });
|
|
2449
3209
|
if (r.endResult !== "OK") {
|
|
2450
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 });
|
|
2451
3213
|
return json(200, { state: "FAILED", error: r.endResult ?? "UNKNOWN" }, { "Set-Cookie": clearCookie });
|
|
2452
3214
|
}
|
|
2453
3215
|
if (tx.mode === "web2app") return json(200, { state: "WAIT_CALLBACK" });
|
|
@@ -2456,9 +3218,10 @@ function createEidLogin(opts) {
|
|
|
2456
3218
|
if (!out) return json(200, { state: "EXPIRED" }, { "Set-Cookie": clearCookie });
|
|
2457
3219
|
return json(200, { state: "OK", redirect: out.redirect, data: out.data }, mergeHeaders({ "Set-Cookie": clearCookie }, out.headers));
|
|
2458
3220
|
} catch (e) {
|
|
2459
|
-
onError(e,
|
|
3221
|
+
onError(e, ctx2);
|
|
2460
3222
|
if (!(e instanceof ValidationError || e instanceof SessionFailedError)) return json(500, { error: "internal" });
|
|
2461
3223
|
await store.delete(tx.sessionId);
|
|
3224
|
+
await unverified(tx, e, ctx2);
|
|
2462
3225
|
return json(200, { state: "FAILED", error: "VALIDATION" }, { "Set-Cookie": clearCookie });
|
|
2463
3226
|
}
|
|
2464
3227
|
}
|
|
@@ -2469,19 +3232,23 @@ function createEidLogin(opts) {
|
|
|
2469
3232
|
const verifier = q.get("userChallengeVerifier") ?? "";
|
|
2470
3233
|
const fail2 = (code) => redirect(failTo(code), { "Set-Cookie": clearCookie });
|
|
2471
3234
|
const tx = await loadTx(req);
|
|
2472
|
-
if (!tx
|
|
2473
|
-
|
|
2474
|
-
|
|
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
|
+
}
|
|
2475
3241
|
let r;
|
|
2476
3242
|
try {
|
|
2477
3243
|
r = await client.session.poll(tx.sessionId, 1e4);
|
|
2478
3244
|
} catch (e) {
|
|
2479
|
-
onError(e,
|
|
3245
|
+
onError(e, ctx2);
|
|
2480
3246
|
return fail2("eid_unavailable");
|
|
2481
3247
|
}
|
|
2482
3248
|
if (r.state !== "COMPLETE") return fail2("not_complete");
|
|
2483
3249
|
if (r.endResult !== "OK") {
|
|
2484
3250
|
await store.delete(tx.sessionId);
|
|
3251
|
+
await endEvent(tx, r.endResult, ctx2);
|
|
2485
3252
|
return fail2(r.endResult ?? "UNKNOWN");
|
|
2486
3253
|
}
|
|
2487
3254
|
try {
|
|
@@ -2489,9 +3256,10 @@ function createEidLogin(opts) {
|
|
|
2489
3256
|
if (!out) return fail2("expired");
|
|
2490
3257
|
return redirect(out.redirect ?? successRedirect, mergeHeaders({ "Set-Cookie": clearCookie }, out.headers));
|
|
2491
3258
|
} catch (e) {
|
|
2492
|
-
onError(e,
|
|
3259
|
+
onError(e, ctx2);
|
|
2493
3260
|
if (!(e instanceof ValidationError || e instanceof SessionFailedError)) return fail2("internal");
|
|
2494
3261
|
await store.delete(tx.sessionId);
|
|
3262
|
+
await unverified(tx, e, ctx2);
|
|
2495
3263
|
return fail2("VALIDATION");
|
|
2496
3264
|
}
|
|
2497
3265
|
}
|
|
@@ -2521,7 +3289,7 @@ function createEidLogin(opts) {
|
|
|
2521
3289
|
mode: "qr"
|
|
2522
3290
|
});
|
|
2523
3291
|
}
|
|
2524
|
-
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 });
|
|
2525
3293
|
const h = new Headers();
|
|
2526
3294
|
for (const [k, v] of Object.entries(r.headers)) for (const x of [v].flat()) h.append(k, x);
|
|
2527
3295
|
return new Response(r.status === 302 || r.status === 303 || r.body === "" ? null : r.body, { status: r.status, headers: h });
|
|
@@ -2569,6 +3337,7 @@ function createEidLogin(opts) {
|
|
|
2569
3337
|
interactionsToBase64,
|
|
2570
3338
|
isMobileUserAgent,
|
|
2571
3339
|
memoryLoginStore,
|
|
3340
|
+
ocspRevocationChecker,
|
|
2572
3341
|
openOrShowQR,
|
|
2573
3342
|
parseExpiresAt,
|
|
2574
3343
|
parseRetryAfter,
|