@gerege-systems/eid-mongolia-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/index.cjs +740 -0
- package/dist/index.d.cts +447 -0
- package/dist/index.d.ts +447 -0
- package/dist/index.js +693 -0
- package/package.json +58 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ApiError: () => ApiError,
|
|
24
|
+
AuthApi: () => AuthApi,
|
|
25
|
+
AuthenticationError: () => AuthenticationError,
|
|
26
|
+
EidClient: () => EidClient,
|
|
27
|
+
EidError: () => EidError,
|
|
28
|
+
ForbiddenError: () => ForbiddenError,
|
|
29
|
+
Http: () => Http,
|
|
30
|
+
NetworkError: () => NetworkError,
|
|
31
|
+
ResponseValidator: () => ResponseValidator,
|
|
32
|
+
SessionApi: () => SessionApi,
|
|
33
|
+
SessionFailedError: () => SessionFailedError,
|
|
34
|
+
SignApi: () => SignApi,
|
|
35
|
+
ValidationError: () => ValidationError,
|
|
36
|
+
acspV2Digest: () => acspV2Digest,
|
|
37
|
+
buildAcspV2Payload: () => buildAcspV2Payload,
|
|
38
|
+
interactionsToBase64: () => interactionsToBase64,
|
|
39
|
+
parseSessionResult: () => parseSessionResult,
|
|
40
|
+
randomChallenge: () => randomChallenge,
|
|
41
|
+
sha256Base64: () => sha256Base64,
|
|
42
|
+
verifyPayloadSignature: () => verifyPayloadSignature,
|
|
43
|
+
verifyPrehashedSignature: () => verifyPrehashedSignature
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(index_exports);
|
|
46
|
+
|
|
47
|
+
// src/errors.ts
|
|
48
|
+
var EidError = class extends Error {
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = new.target.name;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var AuthenticationError = class extends EidError {
|
|
55
|
+
};
|
|
56
|
+
var ForbiddenError = class extends EidError {
|
|
57
|
+
};
|
|
58
|
+
var ApiError = class extends EidError {
|
|
59
|
+
constructor(status, body) {
|
|
60
|
+
super(`RP-API ${status}: ${body}`);
|
|
61
|
+
this.status = status;
|
|
62
|
+
this.body = body;
|
|
63
|
+
}
|
|
64
|
+
status;
|
|
65
|
+
body;
|
|
66
|
+
};
|
|
67
|
+
var NetworkError = class extends EidError {
|
|
68
|
+
};
|
|
69
|
+
var SessionFailedError = class extends EidError {
|
|
70
|
+
constructor(endResult) {
|
|
71
|
+
super(`Session \u0430\u043C\u0436\u0438\u043B\u0442\u0433\u04AF\u0439: ${endResult}`);
|
|
72
|
+
this.endResult = endResult;
|
|
73
|
+
}
|
|
74
|
+
endResult;
|
|
75
|
+
};
|
|
76
|
+
var ValidationError = class extends EidError {
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/http.ts
|
|
80
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
81
|
+
var Http = class {
|
|
82
|
+
constructor(cfg) {
|
|
83
|
+
this.cfg = cfg;
|
|
84
|
+
}
|
|
85
|
+
cfg;
|
|
86
|
+
post(path, body, timeoutMs) {
|
|
87
|
+
return this.request("POST", path, body, timeoutMs);
|
|
88
|
+
}
|
|
89
|
+
get(path, timeoutMs) {
|
|
90
|
+
return this.request("GET", path, void 0, timeoutMs);
|
|
91
|
+
}
|
|
92
|
+
async request(method, path, body, timeoutMs) {
|
|
93
|
+
const f = this.cfg.fetchImpl ?? fetch;
|
|
94
|
+
const url = this.cfg.baseUrl + path;
|
|
95
|
+
const init = {
|
|
96
|
+
method,
|
|
97
|
+
headers: {
|
|
98
|
+
Authorization: `Bearer ${this.cfg.apiSecret}`,
|
|
99
|
+
...body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
100
|
+
},
|
|
101
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
102
|
+
signal: AbortSignal.timeout(timeoutMs ?? this.cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS)
|
|
103
|
+
};
|
|
104
|
+
if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
|
|
105
|
+
let res;
|
|
106
|
+
try {
|
|
107
|
+
res = await f(url, init);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
const err = e;
|
|
110
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
111
|
+
throw new NetworkError(`\u0425\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${method} ${path}`);
|
|
112
|
+
}
|
|
113
|
+
throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${method} ${path}): ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
const text = await res.text();
|
|
116
|
+
if (res.status === 401) throw new AuthenticationError("API secret \u0431\u0443\u0440\u0443\u0443 \u044D\u0441\u0432\u044D\u043B \u0431\u0430\u0439\u0445\u0433\u04AF\u0439 (401)");
|
|
117
|
+
if (res.status === 403) throw new ForbiddenError("IP allowlist \u044D\u0441\u0432\u044D\u043B mTLS \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u04E9\u0433\u04AF\u0439 (403)");
|
|
118
|
+
if (res.status >= 300) throw new ApiError(res.status, text);
|
|
119
|
+
return text ? JSON.parse(text) : {};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/crypto.ts
|
|
124
|
+
var import_node_crypto = require("crypto");
|
|
125
|
+
function sha256Base64(data) {
|
|
126
|
+
return (0, import_node_crypto.createHash)("sha256").update(data).digest("base64");
|
|
127
|
+
}
|
|
128
|
+
function randomChallenge(bytes = 64) {
|
|
129
|
+
return (0, import_node_crypto.randomBytes)(bytes).toString("base64");
|
|
130
|
+
}
|
|
131
|
+
function nodeHash(h) {
|
|
132
|
+
return h.toLowerCase().replace("-", "");
|
|
133
|
+
}
|
|
134
|
+
function hashLen(h) {
|
|
135
|
+
return (0, import_node_crypto.createHash)(nodeHash(h)).digest().length;
|
|
136
|
+
}
|
|
137
|
+
function interactionsToBase64(interactions) {
|
|
138
|
+
return Buffer.from(JSON.stringify(interactions), "utf8").toString("base64");
|
|
139
|
+
}
|
|
140
|
+
var b64 = (s) => Buffer.from(s, "utf8").toString("base64");
|
|
141
|
+
function buildAcspV2Payload(p) {
|
|
142
|
+
const message = [
|
|
143
|
+
p.schemeName ?? "smart-id",
|
|
144
|
+
"ACSP_V2",
|
|
145
|
+
p.serverRandom,
|
|
146
|
+
p.rpChallenge,
|
|
147
|
+
p.userChallenge,
|
|
148
|
+
b64(p.relyingPartyName),
|
|
149
|
+
b64(p.brokeredRpName ?? ""),
|
|
150
|
+
(0, import_node_crypto.createHash)("sha256").update(p.interactions, "utf8").digest("base64"),
|
|
151
|
+
p.interactionTypeUsed,
|
|
152
|
+
p.initialCallbackUrl ?? "",
|
|
153
|
+
p.flowType
|
|
154
|
+
].join("|");
|
|
155
|
+
return Buffer.from(message, "utf8");
|
|
156
|
+
}
|
|
157
|
+
function acspV2Digest(p, hashAlgorithm) {
|
|
158
|
+
return (0, import_node_crypto.createHash)(nodeHash(hashAlgorithm)).update(buildAcspV2Payload(p)).digest();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/auth.ts
|
|
162
|
+
function rec(v) {
|
|
163
|
+
return v && typeof v === "object" ? v : {};
|
|
164
|
+
}
|
|
165
|
+
var AuthApi = class {
|
|
166
|
+
constructor(http, creds, defaults) {
|
|
167
|
+
this.http = http;
|
|
168
|
+
this.creds = creds;
|
|
169
|
+
this.defaults = defaults;
|
|
170
|
+
}
|
|
171
|
+
http;
|
|
172
|
+
creds;
|
|
173
|
+
defaults;
|
|
174
|
+
/** v3 nested body + validator-т хэрэгтэй контекст (ижил утгуудаас — байт зөрөхгүй). */
|
|
175
|
+
baseBody(rpChallenge, interactions, flows, opts) {
|
|
176
|
+
const callback = opts?.callbackUrl?.trim() ?? "";
|
|
177
|
+
const acsp = {
|
|
178
|
+
rpChallenge,
|
|
179
|
+
relyingPartyName: this.creds.rpName,
|
|
180
|
+
brokeredRpName: "",
|
|
181
|
+
interactions: interactionsToBase64(interactions),
|
|
182
|
+
initialCallbackUrl: callback,
|
|
183
|
+
flowTypes: callback ? [...flows, "App2App", "Web2App"] : flows,
|
|
184
|
+
signatureAlgorithm: opts?.signatureAlgorithm ?? "rsassa-pss",
|
|
185
|
+
hashAlgorithm: opts?.hashAlgorithm ?? "SHA-512"
|
|
186
|
+
};
|
|
187
|
+
const body = {
|
|
188
|
+
relyingPartyUUID: this.creds.rpUUID,
|
|
189
|
+
relyingPartyName: acsp.relyingPartyName,
|
|
190
|
+
certificateLevel: opts?.certificateLevel ?? this.defaults.certificateLevel,
|
|
191
|
+
signatureProtocol: "ACSP_V2",
|
|
192
|
+
signatureProtocolParameters: {
|
|
193
|
+
rpChallenge,
|
|
194
|
+
signatureAlgorithm: acsp.signatureAlgorithm,
|
|
195
|
+
signatureAlgorithmParameters: { hashAlgorithm: acsp.hashAlgorithm }
|
|
196
|
+
},
|
|
197
|
+
interactions: acsp.interactions
|
|
198
|
+
};
|
|
199
|
+
if (callback) body.initialCallbackUrl = callback;
|
|
200
|
+
if (opts?.onBehalfOf?.trim()) body.onBehalfOf = opts.onBehalfOf.trim();
|
|
201
|
+
return { body, acsp };
|
|
202
|
+
}
|
|
203
|
+
parseNotification(raw, acsp) {
|
|
204
|
+
const r = rec(raw);
|
|
205
|
+
const vc = rec(r.vc).value;
|
|
206
|
+
return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, rpChallenge: acsp.rpChallenge, acsp };
|
|
207
|
+
}
|
|
208
|
+
parseDeviceLink(raw, acsp) {
|
|
209
|
+
const r = rec(raw);
|
|
210
|
+
return {
|
|
211
|
+
sessionId: String(r.sessionID),
|
|
212
|
+
sessionToken: r.sessionToken != null ? String(r.sessionToken) : null,
|
|
213
|
+
sessionSecret: r.sessionSecret != null ? String(r.sessionSecret) : null,
|
|
214
|
+
deviceLinkBase: r.deviceLinkBase != null ? String(r.deviceLinkBase) : null,
|
|
215
|
+
rpChallenge: acsp.rpChallenge,
|
|
216
|
+
acsp
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
|
|
220
|
+
async notificationByEtsi(id, interactions, opts) {
|
|
221
|
+
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
|
|
222
|
+
const raw = await this.http.post(`/authentication/notification/etsi/${encodeURIComponent(id)}`, body);
|
|
223
|
+
return this.parseNotification(raw, acsp);
|
|
224
|
+
}
|
|
225
|
+
/** Push — баримтын дугаараар (тодорхой төхөөрөмж рүү). */
|
|
226
|
+
async notificationByDocument(documentNumber, interactions, opts) {
|
|
227
|
+
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
|
|
228
|
+
const raw = await this.http.post(`/authentication/notification/document/${encodeURIComponent(documentNumber)}`, body);
|
|
229
|
+
return this.parseNotification(raw, acsp);
|
|
230
|
+
}
|
|
231
|
+
/** QR/App2App — нэр томьёололгүй (иргэн хэн болохыг урьдчилж мэдэхгүй). QR код үүсгэхэд. */
|
|
232
|
+
async deviceLinkAnonymous(interactions, opts) {
|
|
233
|
+
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
|
|
234
|
+
const raw = await this.http.post(`/authentication/device-link/anonymous`, body);
|
|
235
|
+
return this.parseDeviceLink(raw, acsp);
|
|
236
|
+
}
|
|
237
|
+
/** QR/App2App — тодорхой иргэн (РД/civil_id/ETSI). */
|
|
238
|
+
async deviceLinkByEtsi(id, interactions, opts) {
|
|
239
|
+
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
|
|
240
|
+
const raw = await this.http.post(`/authentication/device-link/etsi/${encodeURIComponent(id)}`, body);
|
|
241
|
+
return this.parseDeviceLink(raw, acsp);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/sign.ts
|
|
246
|
+
function rec2(v) {
|
|
247
|
+
return v && typeof v === "object" ? v : {};
|
|
248
|
+
}
|
|
249
|
+
var SignApi = class {
|
|
250
|
+
constructor(http, creds, defaults) {
|
|
251
|
+
this.http = http;
|
|
252
|
+
this.creds = creds;
|
|
253
|
+
this.defaults = defaults;
|
|
254
|
+
}
|
|
255
|
+
http;
|
|
256
|
+
creds;
|
|
257
|
+
defaults;
|
|
258
|
+
body(digestB64, interactions, opts) {
|
|
259
|
+
const body = {
|
|
260
|
+
relyingPartyUUID: this.creds.rpUUID,
|
|
261
|
+
relyingPartyName: this.creds.rpName,
|
|
262
|
+
certificateLevel: opts?.certificateLevel ?? this.defaults.certificateLevel,
|
|
263
|
+
signatureProtocol: "RAW_DIGEST_SIGNATURE",
|
|
264
|
+
signatureProtocolParameters: {
|
|
265
|
+
digest: digestB64,
|
|
266
|
+
signatureAlgorithm: opts?.signatureAlgorithm ?? "rsassa-pss",
|
|
267
|
+
signatureAlgorithmParameters: { hashAlgorithm: opts?.hashAlgorithm ?? "SHA-256" }
|
|
268
|
+
},
|
|
269
|
+
interactions: interactionsToBase64(interactions)
|
|
270
|
+
};
|
|
271
|
+
if (opts?.callbackUrl?.trim()) body.initialCallbackUrl = opts.callbackUrl.trim();
|
|
272
|
+
if (opts?.onBehalfOf?.trim()) body.onBehalfOf = opts.onBehalfOf.trim();
|
|
273
|
+
return body;
|
|
274
|
+
}
|
|
275
|
+
parse(raw) {
|
|
276
|
+
const r = rec2(raw);
|
|
277
|
+
const vc = rec2(r.vc).value;
|
|
278
|
+
return { sessionId: String(r.sessionID), vc: vc != null ? String(vc) : null, rpChallenge: "" };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Бэлэн digest-ээр push sign (РД/civil_id/ETSI). Баримт (PDF/файл)-ын байтын SHA-256
|
|
282
|
+
* digest-ийг гаднаас бэлдэж өгнө — текст бус бинар баримтад тохиромжтой.
|
|
283
|
+
*/
|
|
284
|
+
async digestByEtsi(id, digestB64, interactions, opts) {
|
|
285
|
+
const raw = await this.http.post(`/signature/notification/etsi/${encodeURIComponent(id)}`, this.body(digestB64, interactions, opts));
|
|
286
|
+
return this.parse(raw);
|
|
287
|
+
}
|
|
288
|
+
/** Бэлэн digest-ээр push sign — баримтын дугаараар (auth-аас гарсан documentNumber дээр). */
|
|
289
|
+
async digestByDocument(documentNumber, digestB64, interactions, opts) {
|
|
290
|
+
const raw = await this.http.post(`/signature/notification/document/${encodeURIComponent(documentNumber)}`, this.body(digestB64, interactions, opts));
|
|
291
|
+
return this.parse(raw);
|
|
292
|
+
}
|
|
293
|
+
/** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
|
|
294
|
+
async textByEtsi(id, text, interactions, opts) {
|
|
295
|
+
return this.digestByEtsi(id, sha256Base64(text), interactions, opts);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/session.ts
|
|
300
|
+
var SERVER_POLL_MS = 3e4;
|
|
301
|
+
var MAX_WAIT_MS = 15e4;
|
|
302
|
+
function asRecord(v) {
|
|
303
|
+
return v && typeof v === "object" ? v : {};
|
|
304
|
+
}
|
|
305
|
+
function str(v) {
|
|
306
|
+
return v != null ? String(v) : null;
|
|
307
|
+
}
|
|
308
|
+
function parseSessionResult(raw) {
|
|
309
|
+
const r = asRecord(raw);
|
|
310
|
+
const result = asRecord(r.result);
|
|
311
|
+
const sig = asRecord(r.signature);
|
|
312
|
+
const cert = asRecord(r.cert);
|
|
313
|
+
return {
|
|
314
|
+
state: String(r.state ?? ""),
|
|
315
|
+
endResult: str(result.endResult),
|
|
316
|
+
documentNumber: str(result.documentNumber),
|
|
317
|
+
certificateDerB64: str(cert.value),
|
|
318
|
+
certificateLevel: str(cert.certificateLevel),
|
|
319
|
+
signatureValueB64: str(sig.value),
|
|
320
|
+
signatureAlgorithm: str(sig.signatureAlgorithm),
|
|
321
|
+
signatureProtocol: str(r.signatureProtocol ?? sig.signatureProtocol),
|
|
322
|
+
serverRandom: str(sig.serverRandom),
|
|
323
|
+
userChallenge: str(sig.userChallenge),
|
|
324
|
+
flowType: str(sig.flowType),
|
|
325
|
+
signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
|
|
326
|
+
interactionTypeUsed: str(r.interactionTypeUsed),
|
|
327
|
+
onBehalfOf: parseOrg(r.onBehalfOf)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function parseSigParams(raw) {
|
|
331
|
+
const p = asRecord(raw);
|
|
332
|
+
const hash = str(p.hashAlgorithm);
|
|
333
|
+
if (!hash) return null;
|
|
334
|
+
const mgf = asRecord(p.maskGenAlgorithm);
|
|
335
|
+
return {
|
|
336
|
+
hashAlgorithm: hash,
|
|
337
|
+
maskGenAlgorithm: mgf.algorithm ? { algorithm: String(mgf.algorithm), parameters: { hashAlgorithm: String(asRecord(mgf.parameters).hashAlgorithm ?? hash) } } : void 0,
|
|
338
|
+
saltLength: p.saltLength != null ? Number(p.saltLength) : void 0,
|
|
339
|
+
trailerField: str(p.trailerField) ?? void 0
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function parseOrg(raw) {
|
|
343
|
+
const o = asRecord(raw);
|
|
344
|
+
const etsi = str(o.orgEtsi);
|
|
345
|
+
if (!etsi) return null;
|
|
346
|
+
return {
|
|
347
|
+
orgEtsi: etsi,
|
|
348
|
+
orgName: str(o.orgName),
|
|
349
|
+
role: str(o.role),
|
|
350
|
+
rightType: str(o.rightType)
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
var SessionApi = class {
|
|
354
|
+
constructor(http) {
|
|
355
|
+
this.http = http;
|
|
356
|
+
}
|
|
357
|
+
http;
|
|
358
|
+
/** Нэг удаагийн long-poll. serverPollMs хүртэл хүлээнэ. */
|
|
359
|
+
async poll(sessionId, serverPollMs = SERVER_POLL_MS) {
|
|
360
|
+
const raw = await this.http.get(
|
|
361
|
+
`/session/${encodeURIComponent(sessionId)}?timeoutMs=${serverPollMs}`,
|
|
362
|
+
serverPollMs + 1e4
|
|
363
|
+
// HTTP timeout нь серверийн poll-оос урт
|
|
364
|
+
);
|
|
365
|
+
return parseSessionResult(raw);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* COMPLETE болтол давтаж poll хийнэ. maxWaitMs дуустал хүлээгээд гараагүй бол сүүлчийн
|
|
369
|
+
* (RUNNING) үр дүнг буцаана — дуудагч endResult==null-ийг TIMEOUT мэт боловсруулна.
|
|
370
|
+
*/
|
|
371
|
+
async waitForResult(sessionId, opts) {
|
|
372
|
+
const deadline = Date.now() + (opts?.maxWaitMs ?? MAX_WAIT_MS);
|
|
373
|
+
const pollMs = opts?.serverPollMs ?? SERVER_POLL_MS;
|
|
374
|
+
let last = { state: "RUNNING" };
|
|
375
|
+
while (Date.now() < deadline) {
|
|
376
|
+
const remaining = deadline - Date.now();
|
|
377
|
+
last = await this.poll(sessionId, Math.max(1e3, Math.min(pollMs, remaining)));
|
|
378
|
+
if (last.state === "COMPLETE") return last;
|
|
379
|
+
}
|
|
380
|
+
return last;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// src/validator.ts
|
|
385
|
+
var import_node_crypto2 = require("crypto");
|
|
386
|
+
var ResponseValidator = class {
|
|
387
|
+
anchors;
|
|
388
|
+
intermediates;
|
|
389
|
+
requiredLevel;
|
|
390
|
+
skew;
|
|
391
|
+
allowUntrusted;
|
|
392
|
+
revocation;
|
|
393
|
+
revocationMode;
|
|
394
|
+
constructor(trust = {}) {
|
|
395
|
+
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto2.X509Certificate(p));
|
|
396
|
+
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto2.X509Certificate(p));
|
|
397
|
+
this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
|
|
398
|
+
this.skew = trust.clockSkewMs ?? 0;
|
|
399
|
+
this.allowUntrusted = trust.allowUntrusted ?? false;
|
|
400
|
+
this.revocation = trust.revocation;
|
|
401
|
+
this.revocationMode = trust.revocationMode ?? "hard-fail";
|
|
402
|
+
}
|
|
403
|
+
// ── нийтийн API ──
|
|
404
|
+
/**
|
|
405
|
+
* Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
|
|
406
|
+
* @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
|
|
407
|
+
*/
|
|
408
|
+
validateAuth(result, acsp) {
|
|
409
|
+
const leaf = this.precheck(result, "ACSP_V2");
|
|
410
|
+
const { serverRandom, userChallenge, flowType, interactionTypeUsed } = result;
|
|
411
|
+
if (!serverRandom || Buffer.from(serverRandom, "base64").length < 18) throw new ValidationError("serverRandom \u0430\u043B\u0433\u0430/\u0431\u043E\u0433\u0438\u043D\u043E");
|
|
412
|
+
if (!userChallenge || !/^[A-Za-z0-9_-]{43}$/.test(userChallenge)) throw new ValidationError("userChallenge \u0431\u0443\u0440\u0443\u0443 \u0445\u044D\u043B\u0431\u044D\u0440\u0442\u044D\u0439");
|
|
413
|
+
if (!flowType || !acsp.flowTypes.includes(flowType)) {
|
|
414
|
+
throw new ValidationError(`flowType ${flowType} \u043D\u044C \u0441\u0430\u043D\u0430\u043B \u0431\u043E\u043B\u0433\u043E\u0441\u043E\u043D \u0443\u0440\u0441\u0433\u0430\u043B\u0434 \u0431\u0430\u0439\u0445\u0433\u04AF\u0439`);
|
|
415
|
+
}
|
|
416
|
+
if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
|
|
417
|
+
const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
418
|
+
const payload = buildAcspV2Payload({
|
|
419
|
+
serverRandom,
|
|
420
|
+
rpChallenge: acsp.rpChallenge,
|
|
421
|
+
userChallenge,
|
|
422
|
+
relyingPartyName: acsp.relyingPartyName,
|
|
423
|
+
brokeredRpName: acsp.brokeredRpName,
|
|
424
|
+
interactions: acsp.interactions,
|
|
425
|
+
interactionTypeUsed,
|
|
426
|
+
initialCallbackUrl: acsp.initialCallbackUrl,
|
|
427
|
+
flowType
|
|
428
|
+
});
|
|
429
|
+
if (!verifyPayloadSignature(payload, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
430
|
+
throw new ValidationError("Auth \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 ACSP_V2 payload-\u0442\u043E\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439 (\u0445\u0443\u0443\u0440\u0430\u043C\u0447 \u0445\u0430\u0440\u0438\u0443 \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
documentNumber: result.documentNumber,
|
|
434
|
+
certificate: leaf,
|
|
435
|
+
subject: leaf.subject,
|
|
436
|
+
certificateLevel: String(result.certificateLevel ?? "")
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
|
|
441
|
+
* @param hashAlgorithm — digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256).
|
|
442
|
+
*/
|
|
443
|
+
validateSign(result, digestB64, hashAlgorithm = "SHA-256") {
|
|
444
|
+
const leaf = this.precheck(result, "RAW_DIGEST_SIGNATURE");
|
|
445
|
+
const digest = Buffer.from(digestB64, "base64");
|
|
446
|
+
const { alg, hash } = this.algorithmOf(result, null, hashAlgorithm);
|
|
447
|
+
if (digest.length !== hashLen(hash)) throw new ValidationError("digest \u0443\u0440\u0442 hashAlgorithm-\u0442\u0430\u0439 \u0442\u0430\u0430\u0440\u0430\u0445\u0433\u04AF\u0439");
|
|
448
|
+
if (!verifyPrehashedSignature(digest, result.signatureValueB64, leaf.publicKey, alg, hash)) {
|
|
449
|
+
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");
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
documentNumber: result.documentNumber,
|
|
453
|
+
signatureValueB64: result.signatureValueB64,
|
|
454
|
+
signatureAlgorithm: result.signatureAlgorithm,
|
|
455
|
+
certificate: leaf,
|
|
456
|
+
subject: leaf.subject
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Хариуны signatureAlgorithm + hashAlgorithm-ийг RP-ийн хүссэнтэй тулгана (downgrade хаалт).
|
|
461
|
+
* ecdsa-with-SHA256 (хуучин cert) хүссэн rsassa-pss-ийн оронд ирэхийг зөвшөөрнө — SHA-256-аар л.
|
|
462
|
+
*/
|
|
463
|
+
algorithmOf(result, wantAlg, wantHash) {
|
|
464
|
+
const alg = result.signatureAlgorithm ?? "";
|
|
465
|
+
let hash;
|
|
466
|
+
if (alg === "rsassa-pss") {
|
|
467
|
+
hash = String(result.signatureAlgorithmParameters?.hashAlgorithm ?? "");
|
|
468
|
+
const mgf = result.signatureAlgorithmParameters?.maskGenAlgorithm?.parameters?.hashAlgorithm;
|
|
469
|
+
if (mgf && mgf !== hash) throw new ValidationError("MGF1 hash \u043D\u044C hashAlgorithm-\u0430\u0430\u0441 \u04E9\u04E9\u0440");
|
|
470
|
+
if (result.signatureAlgorithmParameters?.saltLength != null && result.signatureAlgorithmParameters.saltLength !== hashLen(hash)) {
|
|
471
|
+
throw new ValidationError("saltLength \u2260 hLen");
|
|
472
|
+
}
|
|
473
|
+
} else if (/^sha(256|384|512)WithRSAEncryption$/.test(alg)) {
|
|
474
|
+
hash = "SHA-" + alg.slice(3, 6);
|
|
475
|
+
} else if (alg === "ecdsa-with-SHA256") {
|
|
476
|
+
hash = "SHA-256";
|
|
477
|
+
} else {
|
|
478
|
+
throw new ValidationError(`\u0414\u044D\u043C\u0436\u0438\u0433\u0434\u044D\u044D\u0433\u04AF\u0439 signatureAlgorithm: ${alg}`);
|
|
479
|
+
}
|
|
480
|
+
if (wantAlg && alg !== wantAlg && alg !== "ecdsa-with-SHA256") {
|
|
481
|
+
throw new ValidationError(`signatureAlgorithm ${alg} \u2260 \u0445\u04AF\u0441\u0441\u044D\u043D ${wantAlg}`);
|
|
482
|
+
}
|
|
483
|
+
if (hash !== wantHash) throw new ValidationError(`hashAlgorithm ${hash} \u2260 \u0445\u04AF\u0441\u0441\u044D\u043D ${wantHash}`);
|
|
484
|
+
return { alg, hash };
|
|
485
|
+
}
|
|
486
|
+
// ── дотоод ──
|
|
487
|
+
/** state/endResult, cert байгаа эсэх, chain, validity, certLevel — нийтлэг шалгалт. */
|
|
488
|
+
precheck(result, protocol) {
|
|
489
|
+
if (result.state !== "COMPLETE") throw new ValidationError(`Session \u0434\u0443\u0443\u0441\u0430\u0430\u0433\u04AF\u0439 (state=${result.state})`);
|
|
490
|
+
if (result.endResult !== "OK") throw new SessionFailedError(result.endResult ?? "UNKNOWN");
|
|
491
|
+
if (result.signatureProtocol && result.signatureProtocol !== protocol) {
|
|
492
|
+
throw new ValidationError(`signatureProtocol ${result.signatureProtocol} \u2260 ${protocol}`);
|
|
493
|
+
}
|
|
494
|
+
if (!result.certificateDerB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
|
|
495
|
+
if (!result.signatureValueB64) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0430\u043B\u0433\u0430");
|
|
496
|
+
if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
|
|
497
|
+
const leaf = new import_node_crypto2.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
|
|
498
|
+
this.checkValidity(leaf);
|
|
499
|
+
this.checkLevel(String(result.certificateLevel ?? ""));
|
|
500
|
+
if (this.anchors.length === 0) {
|
|
501
|
+
if (!this.allowUntrusted) {
|
|
502
|
+
throw new ValidationError(
|
|
503
|
+
"Trust anchor \u0442\u043E\u0445\u0438\u0440\u0443\u0443\u043B\u0430\u0430\u0433\u04AF\u0439 \u2014 \u0438\u0442\u0433\u044D\u043B\u0446\u044D\u043B \u0448\u0430\u043B\u0433\u0430\u0445 \u0431\u043E\u043B\u043E\u043C\u0436\u0433\u04AF\u0439 (production-\u0434 trustAnchorsPem \u04E9\u0433, \u044D\u0441\u0432\u044D\u043B allowUntrusted=true)"
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
this.verifyChain(leaf);
|
|
508
|
+
}
|
|
509
|
+
return leaf;
|
|
510
|
+
}
|
|
511
|
+
checkValidity(cert) {
|
|
512
|
+
const now = Date.now();
|
|
513
|
+
const from = Date.parse(cert.validFrom);
|
|
514
|
+
const to = Date.parse(cert.validTo);
|
|
515
|
+
if (!Number.isNaN(from) && now + this.skew < from) throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0445\u04AF\u0447\u0438\u043D\u0442\u044D\u0439 \u0431\u043E\u043B\u043E\u043E\u0433\u04AF\u0439 \u0431\u0430\u0439\u043D\u0430");
|
|
516
|
+
if (!Number.isNaN(to) && now - this.skew > to) throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0434\u0443\u0443\u0441\u0441\u0430\u043D");
|
|
517
|
+
}
|
|
518
|
+
checkLevel(level) {
|
|
519
|
+
const rank = { ADVANCED: 1, QUALIFIED: 2, QSCD: 3 };
|
|
520
|
+
const got = rank[level] ?? 0;
|
|
521
|
+
const need = rank[this.requiredLevel] ?? 2;
|
|
522
|
+
if (got < need) throw new ValidationError(`\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0442\u04AF\u0432\u0448\u0438\u043D \u0445\u04AF\u0440\u044D\u043B\u0446\u044D\u0445\u0433\u04AF\u0439: ${level} < ${this.requiredLevel}`);
|
|
523
|
+
}
|
|
524
|
+
/** leaf-ээс эхлэн issuer-ийг intermediates/anchors дотроос олж, anchor хүртэл chain зангидна. */
|
|
525
|
+
verifyChain(leaf) {
|
|
526
|
+
let current = leaf;
|
|
527
|
+
const pool = [...this.intermediates, ...this.anchors];
|
|
528
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
529
|
+
for (const anchor of this.anchors) {
|
|
530
|
+
if (isCa(anchor) && current.checkIssued(anchor) && current.verify(anchor.publicKey)) return;
|
|
531
|
+
}
|
|
532
|
+
const issuer = pool.find(
|
|
533
|
+
(c) => isCa(c) && current.checkIssued(c) && current.verify(c.publicKey)
|
|
534
|
+
);
|
|
535
|
+
if (!issuer) break;
|
|
536
|
+
current = issuer;
|
|
537
|
+
}
|
|
538
|
+
throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0438\u0442\u0433\u044D\u043B\u0446\u043B\u0438\u0439\u043D \u0433\u0438\u043D\u0436 trust anchor (Root CA) \u0445\u04AF\u0440\u0441\u044D\u043D\u0433\u04AF\u0439");
|
|
539
|
+
}
|
|
540
|
+
/** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
|
|
541
|
+
findIssuer(leaf) {
|
|
542
|
+
return [...this.intermediates, ...this.anchors].find(
|
|
543
|
+
(c) => isCa(c) && leaf.checkIssued(c) && leaf.verify(c.publicKey)
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* checkRevocation — тохируулсан RevocationChecker hook-оор cert-ийн revoke төлвийг
|
|
548
|
+
* шалгана. revocation тохируулаагүй бол no-op (true буцаана). "revoked" үед, мөн
|
|
549
|
+
* hard-fail горимд "unknown" үед ValidationError шиднэ. validateAuth/Sign-ийн дараа
|
|
550
|
+
* дуудна (тэдгээр нь sync; revocation нь сүлжээ I/O тул async).
|
|
551
|
+
*/
|
|
552
|
+
async checkRevocation(cert) {
|
|
553
|
+
if (!this.revocation) return;
|
|
554
|
+
const status = await this.revocation(cert, this.findIssuer(cert));
|
|
555
|
+
if (status === "revoked") {
|
|
556
|
+
throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
|
|
557
|
+
}
|
|
558
|
+
if (status === "unknown" && this.revocationMode === "hard-fail") {
|
|
559
|
+
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)");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
564
|
+
const sig = Buffer.from(sigB64, "base64");
|
|
565
|
+
const h = nodeHash(hash);
|
|
566
|
+
if (alg === "rsassa-pss") {
|
|
567
|
+
return (0, import_node_crypto2.verify)(h, payload, { key, padding: import_node_crypto2.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
|
|
568
|
+
}
|
|
569
|
+
if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto2.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
|
|
570
|
+
return (0, import_node_crypto2.verify)(h, payload, { key, padding: import_node_crypto2.constants.RSA_PKCS1_PADDING }, sig);
|
|
571
|
+
}
|
|
572
|
+
function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
573
|
+
const sig = Buffer.from(sigB64, "base64");
|
|
574
|
+
if (alg === "ecdsa-with-SHA256") return ecdsaP256VerifyPrehashed(digest, sig, key);
|
|
575
|
+
const modBits = key.asymmetricKeyDetails?.modulusLength ?? 0;
|
|
576
|
+
if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
|
|
577
|
+
let em;
|
|
578
|
+
try {
|
|
579
|
+
em = (0, import_node_crypto2.publicDecrypt)({ key, padding: import_node_crypto2.constants.RSA_NO_PADDING }, sig);
|
|
580
|
+
} catch {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
if (alg === "rsassa-pss") return emsaPssVerify(digest, em, modBits - 1, hash);
|
|
584
|
+
const prefix = DIGEST_INFO[hash];
|
|
585
|
+
if (!prefix) return false;
|
|
586
|
+
const t = Buffer.concat([prefix, digest]);
|
|
587
|
+
const expected = Buffer.concat([Buffer.from([0, 1]), Buffer.alloc(em.length - t.length - 3, 255), Buffer.from([0]), t]);
|
|
588
|
+
return em.length === expected.length && em.equals(expected);
|
|
589
|
+
}
|
|
590
|
+
var P256 = {
|
|
591
|
+
p: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
|
|
592
|
+
n: 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n,
|
|
593
|
+
b: 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn,
|
|
594
|
+
gx: 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296n,
|
|
595
|
+
gy: 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5n
|
|
596
|
+
};
|
|
597
|
+
var mod = (a, m) => (a % m + m) % m;
|
|
598
|
+
function inv(a, m) {
|
|
599
|
+
let [g, x, y, u] = [mod(a, m), 1n, 0n, m];
|
|
600
|
+
while (g !== 0n) {
|
|
601
|
+
const q = u / g;
|
|
602
|
+
[u, g, y, x] = [g, u - q * g, x, y - q * x];
|
|
603
|
+
}
|
|
604
|
+
return mod(y, m);
|
|
605
|
+
}
|
|
606
|
+
function ptAdd(P, Q) {
|
|
607
|
+
if (!P) return Q;
|
|
608
|
+
if (!Q) return P;
|
|
609
|
+
const { p } = P256;
|
|
610
|
+
let l;
|
|
611
|
+
if (P[0] === Q[0]) {
|
|
612
|
+
if (mod(P[1] + Q[1], p) === 0n) return null;
|
|
613
|
+
l = mod((3n * P[0] * P[0] - 3n) * inv(2n * P[1], p), p);
|
|
614
|
+
} else {
|
|
615
|
+
l = mod((Q[1] - P[1]) * inv(Q[0] - P[0], p), p);
|
|
616
|
+
}
|
|
617
|
+
const x = mod(l * l - P[0] - Q[0], p);
|
|
618
|
+
return [x, mod(l * (P[0] - x) - P[1], p)];
|
|
619
|
+
}
|
|
620
|
+
function ptMul(k, P) {
|
|
621
|
+
let R = null;
|
|
622
|
+
for (let b = P; k > 0n; k >>= 1n, b = ptAdd(b, b)) if (k & 1n) R = ptAdd(R, b);
|
|
623
|
+
return R;
|
|
624
|
+
}
|
|
625
|
+
function derInts(sig) {
|
|
626
|
+
if (sig[0] !== 48 || sig[1] !== sig.length - 2) return null;
|
|
627
|
+
let i = 2;
|
|
628
|
+
const out = [];
|
|
629
|
+
for (let k = 0; k < 2; k++) {
|
|
630
|
+
if (sig[i] !== 2) return null;
|
|
631
|
+
const len = sig[i + 1] ?? 0;
|
|
632
|
+
out.push(BigInt("0x" + (sig.subarray(i + 2, i + 2 + len).toString("hex") || "0")));
|
|
633
|
+
i += 2 + len;
|
|
634
|
+
}
|
|
635
|
+
return i === sig.length ? [out[0], out[1]] : null;
|
|
636
|
+
}
|
|
637
|
+
function ecdsaP256VerifyPrehashed(digest, sig, key) {
|
|
638
|
+
if (key.asymmetricKeyDetails?.namedCurve !== "prime256v1") return false;
|
|
639
|
+
const jwk = key.export({ format: "jwk" });
|
|
640
|
+
if (!jwk.x || !jwk.y) return false;
|
|
641
|
+
const Q = [BigInt("0x" + Buffer.from(jwk.x, "base64url").toString("hex")), BigInt("0x" + Buffer.from(jwk.y, "base64url").toString("hex"))];
|
|
642
|
+
const rs = derInts(sig);
|
|
643
|
+
if (!rs) return false;
|
|
644
|
+
const [r, sv] = rs;
|
|
645
|
+
const { n } = P256;
|
|
646
|
+
if (r <= 0n || r >= n || sv <= 0n || sv >= n) return false;
|
|
647
|
+
const z = BigInt("0x" + digest.subarray(0, 32).toString("hex"));
|
|
648
|
+
const w = inv(sv, n);
|
|
649
|
+
const R = ptAdd(ptMul(mod(z * w, n), [P256.gx, P256.gy]), ptMul(mod(r * w, n), Q));
|
|
650
|
+
return R !== null && mod(R[0], n) === r;
|
|
651
|
+
}
|
|
652
|
+
var DIGEST_INFO = {
|
|
653
|
+
"SHA-256": Buffer.from("3031300d060960864801650304020105000420", "hex"),
|
|
654
|
+
"SHA-384": Buffer.from("3041300d060960864801650304020205000430", "hex"),
|
|
655
|
+
"SHA-512": Buffer.from("3051300d060960864801650304020305000440", "hex")
|
|
656
|
+
};
|
|
657
|
+
function mgf1(hash, seed, length) {
|
|
658
|
+
const out = [];
|
|
659
|
+
for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
|
|
660
|
+
const c = Buffer.alloc(4);
|
|
661
|
+
c.writeUInt32BE(i, 0);
|
|
662
|
+
out.push((0, import_node_crypto2.createHash)(nodeHash(hash)).update(seed).update(c).digest());
|
|
663
|
+
}
|
|
664
|
+
return Buffer.concat(out).subarray(0, length);
|
|
665
|
+
}
|
|
666
|
+
function emsaPssVerify(mHash, em, emBits, hash) {
|
|
667
|
+
const hLen = hashLen(hash);
|
|
668
|
+
const sLen = hLen;
|
|
669
|
+
const emLen = Math.ceil(emBits / 8);
|
|
670
|
+
if (em.length > emLen) {
|
|
671
|
+
if (!em.subarray(0, em.length - emLen).every((b) => b === 0)) return false;
|
|
672
|
+
em = em.subarray(em.length - emLen);
|
|
673
|
+
}
|
|
674
|
+
if (emLen < hLen + sLen + 2 || em[emLen - 1] !== 188) return false;
|
|
675
|
+
const maskedDB = em.subarray(0, emLen - hLen - 1);
|
|
676
|
+
const h = em.subarray(emLen - hLen - 1, emLen - 1);
|
|
677
|
+
const topBits = 8 * emLen - emBits;
|
|
678
|
+
if (topBits > 0 && (maskedDB[0] ?? 0) >> 8 - topBits !== 0) return false;
|
|
679
|
+
const db = Buffer.from(maskedDB);
|
|
680
|
+
const mask = mgf1(hash, h, db.length);
|
|
681
|
+
for (let i = 0; i < db.length; i++) db[i] = (db[i] ?? 0) ^ (mask[i] ?? 0);
|
|
682
|
+
if (topBits > 0) db[0] = (db[0] ?? 0) & 255 >> topBits;
|
|
683
|
+
const psLen = emLen - hLen - sLen - 2;
|
|
684
|
+
if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
|
|
685
|
+
const salt = db.subarray(psLen + 1);
|
|
686
|
+
const h2 = (0, import_node_crypto2.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
|
|
687
|
+
return h.equals(h2);
|
|
688
|
+
}
|
|
689
|
+
function isCa(cert) {
|
|
690
|
+
return cert.ca === true;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/client.ts
|
|
694
|
+
var EidClient = class {
|
|
695
|
+
auth;
|
|
696
|
+
sign;
|
|
697
|
+
session;
|
|
698
|
+
validator;
|
|
699
|
+
constructor(cfg) {
|
|
700
|
+
if (!cfg.baseUrl) throw new Error("EidClient: baseUrl \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
701
|
+
if (!cfg.credentials?.apiSecret) throw new Error("EidClient: credentials.apiSecret \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
702
|
+
const base = cfg.baseUrl.replace(/\/+$/, "") + "/v3";
|
|
703
|
+
const http = new Http({
|
|
704
|
+
baseUrl: base,
|
|
705
|
+
apiSecret: cfg.credentials.apiSecret,
|
|
706
|
+
timeoutMs: cfg.timeoutMs,
|
|
707
|
+
dispatcher: cfg.dispatcher,
|
|
708
|
+
fetchImpl: cfg.fetchImpl
|
|
709
|
+
});
|
|
710
|
+
const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
|
|
711
|
+
this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
|
|
712
|
+
this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
|
|
713
|
+
this.session = new SessionApi(http);
|
|
714
|
+
this.validator = new ResponseValidator(cfg.trust ?? {});
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
718
|
+
0 && (module.exports = {
|
|
719
|
+
ApiError,
|
|
720
|
+
AuthApi,
|
|
721
|
+
AuthenticationError,
|
|
722
|
+
EidClient,
|
|
723
|
+
EidError,
|
|
724
|
+
ForbiddenError,
|
|
725
|
+
Http,
|
|
726
|
+
NetworkError,
|
|
727
|
+
ResponseValidator,
|
|
728
|
+
SessionApi,
|
|
729
|
+
SessionFailedError,
|
|
730
|
+
SignApi,
|
|
731
|
+
ValidationError,
|
|
732
|
+
acspV2Digest,
|
|
733
|
+
buildAcspV2Payload,
|
|
734
|
+
interactionsToBase64,
|
|
735
|
+
parseSessionResult,
|
|
736
|
+
randomChallenge,
|
|
737
|
+
sha256Base64,
|
|
738
|
+
verifyPayloadSignature,
|
|
739
|
+
verifyPrehashedSignature
|
|
740
|
+
});
|