@gerege-systems/eid-mongolia-sdk 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -4
- package/dist/{browser-B_XtyuFG.d.cts → browser-BWCDj8xO.d.cts} +104 -5
- package/dist/{browser-B_XtyuFG.d.ts → browser-BWCDj8xO.d.ts} +104 -5
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/{chunk-NXDKQZIW.js → chunk-YDVPZMAL.js} +71 -2
- package/dist/index.cjs +547 -43
- package/dist/index.d.cts +331 -5
- package/dist/index.d.ts +331 -5
- package/dist/index.js +468 -37
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,9 @@ __export(index_exports, {
|
|
|
23
23
|
ApiError: () => ApiError,
|
|
24
24
|
AuthApi: () => AuthApi,
|
|
25
25
|
AuthenticationError: () => AuthenticationError,
|
|
26
|
+
CITIZEN_ISSUING_CA_SKI: () => CITIZEN_ISSUING_CA_SKI,
|
|
26
27
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
28
|
+
DEFAULT_PDF_TIMEOUT_MS: () => DEFAULT_PDF_TIMEOUT_MS,
|
|
27
29
|
DeviceLinkError: () => DeviceLinkError,
|
|
28
30
|
EID_ACR_ADVANCED: () => EID_ACR_ADVANCED,
|
|
29
31
|
EID_ACR_QUALIFIED: () => EID_ACR_QUALIFIED,
|
|
@@ -38,10 +40,13 @@ __export(index_exports, {
|
|
|
38
40
|
ON_BEHALF_ERROR_CODES: () => ON_BEHALF_ERROR_CODES,
|
|
39
41
|
OrganizationApi: () => OrganizationApi,
|
|
40
42
|
PdfApi: () => PdfApi,
|
|
43
|
+
PersonApi: () => PersonApi,
|
|
44
|
+
RETRYABLE_ERROR_CODES: () => RETRYABLE_ERROR_CODES,
|
|
41
45
|
ResponseValidator: () => ResponseValidator,
|
|
42
46
|
SessionApi: () => SessionApi,
|
|
43
47
|
SessionFailedError: () => SessionFailedError,
|
|
44
48
|
SignApi: () => SignApi,
|
|
49
|
+
TokenApi: () => TokenApi,
|
|
45
50
|
ValidationError: () => ValidationError,
|
|
46
51
|
acspV2Digest: () => acspV2Digest,
|
|
47
52
|
buildAcspV2Payload: () => buildAcspV2Payload,
|
|
@@ -50,16 +55,21 @@ __export(index_exports, {
|
|
|
50
55
|
interactionsToBase64: () => interactionsToBase64,
|
|
51
56
|
isMobileUserAgent: () => isMobileUserAgent,
|
|
52
57
|
openOrShowQR: () => openOrShowQR,
|
|
58
|
+
parseRetryAfter: () => parseRetryAfter,
|
|
53
59
|
parseSessionResult: () => parseSessionResult,
|
|
54
60
|
parseSubject: () => parseSubject,
|
|
61
|
+
personMatchesCertificate: () => personMatchesCertificate,
|
|
55
62
|
pkceChallenge: () => pkceChallenge,
|
|
56
63
|
qrDeviceLinkTicker: () => qrDeviceLinkTicker,
|
|
57
64
|
randomChallenge: () => randomChallenge,
|
|
58
65
|
resolveBaseUrl: () => resolveBaseUrl,
|
|
59
66
|
sha256Base64: () => sha256Base64,
|
|
67
|
+
tokenAuthDigest: () => tokenAuthDigest,
|
|
68
|
+
tokenLimitOf: () => tokenLimitOf,
|
|
60
69
|
userChallengeOf: () => userChallengeOf,
|
|
61
70
|
verifyPayloadSignature: () => verifyPayloadSignature,
|
|
62
|
-
verifyPrehashedSignature: () => verifyPrehashedSignature
|
|
71
|
+
verifyPrehashedSignature: () => verifyPrehashedSignature,
|
|
72
|
+
verifyTokenCertificates: () => verifyTokenCertificates
|
|
63
73
|
});
|
|
64
74
|
module.exports = __toCommonJS(index_exports);
|
|
65
75
|
|
|
@@ -71,25 +81,79 @@ var EidError = class extends Error {
|
|
|
71
81
|
}
|
|
72
82
|
};
|
|
73
83
|
var AuthenticationError = class extends EidError {
|
|
84
|
+
/** Серверийн `{"code"}`; байхгүй / JSON биш (proxy) бол undefined. */
|
|
85
|
+
code;
|
|
86
|
+
/** Серверийн `{"error"}` текст (логт); JSON биш (proxy) бол undefined. */
|
|
87
|
+
serverMessage;
|
|
88
|
+
/** Хариуны бие (таслагдаагүй текст). */
|
|
89
|
+
body;
|
|
90
|
+
headers;
|
|
91
|
+
constructor(message, init) {
|
|
92
|
+
super(message);
|
|
93
|
+
this.code = init?.code;
|
|
94
|
+
this.serverMessage = init?.serverMessage;
|
|
95
|
+
this.body = init?.body;
|
|
96
|
+
this.headers = init?.headers;
|
|
97
|
+
}
|
|
74
98
|
};
|
|
75
99
|
var ForbiddenError = class extends EidError {
|
|
76
|
-
constructor(message, code) {
|
|
100
|
+
constructor(message, code, init) {
|
|
77
101
|
super(message);
|
|
78
102
|
this.code = code;
|
|
103
|
+
this.headers = init?.headers;
|
|
104
|
+
this.retryAfterMs = init?.retryAfterMs;
|
|
79
105
|
}
|
|
80
106
|
code;
|
|
107
|
+
headers;
|
|
108
|
+
/** Retry-After (мс), байхгүй бол undefined. */
|
|
109
|
+
retryAfterMs;
|
|
81
110
|
};
|
|
82
111
|
var ApiError = class extends EidError {
|
|
83
|
-
constructor(status, body, code) {
|
|
112
|
+
constructor(status, body, code, init) {
|
|
84
113
|
super(`RP-API ${status}: ${body}`);
|
|
85
114
|
this.status = status;
|
|
86
115
|
this.body = body;
|
|
87
116
|
this.code = code;
|
|
117
|
+
this.headers = init?.headers;
|
|
118
|
+
this.retryAfterMs = init?.retryAfterMs;
|
|
88
119
|
}
|
|
89
120
|
status;
|
|
90
121
|
body;
|
|
91
122
|
code;
|
|
123
|
+
headers;
|
|
124
|
+
/**
|
|
125
|
+
* Retry-After (мс, секунд эсвэл HTTP-огноо задалсан); байхгүй/буруу бол undefined. CA түр алдаанд (429
|
|
126
|
+
* PDF_PREPARE_BUSY / RATE_LIMITED, 503 DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE г.м.) тавина.
|
|
127
|
+
*/
|
|
128
|
+
retryAfterMs;
|
|
129
|
+
/**
|
|
130
|
+
* CA-ийн түр алдаа ({@link RETRYABLE_ERROR_CODES}-ийн нэг, эсвэл 429/503 + Retry-After): `retryAfterMs`-ийн дараа
|
|
131
|
+
* ижил хүсэлтийг давтаж болно.
|
|
132
|
+
*/
|
|
133
|
+
get retryable() {
|
|
134
|
+
if (this.code && RETRYABLE_ERROR_CODES.includes(this.code)) return true;
|
|
135
|
+
return (this.status === 429 || this.status === 503) && (this.retryAfterMs ?? 0) > 0;
|
|
136
|
+
}
|
|
92
137
|
};
|
|
138
|
+
var RETRYABLE_ERROR_CODES = [
|
|
139
|
+
"PDF_PREPARE_BUSY",
|
|
140
|
+
"DOCUMENT_PENDING",
|
|
141
|
+
"TSA_UNAVAILABLE",
|
|
142
|
+
"LTV_UNAVAILABLE",
|
|
143
|
+
"RATE_LIMITED",
|
|
144
|
+
"STATE_UNAVAILABLE",
|
|
145
|
+
"ISSUANCE_PENDING"
|
|
146
|
+
];
|
|
147
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
148
|
+
const v = (value ?? "").trim();
|
|
149
|
+
if (!v) return void 0;
|
|
150
|
+
if (/^-?\d+$/.test(v)) {
|
|
151
|
+
const n = Number(v);
|
|
152
|
+
return n > 0 && n <= 86400 ? n * 1e3 : void 0;
|
|
153
|
+
}
|
|
154
|
+
const t = Date.parse(v);
|
|
155
|
+
return Number.isNaN(t) || t <= now ? void 0 : t - now;
|
|
156
|
+
}
|
|
93
157
|
var ON_BEHALF_ERROR_CODES = [
|
|
94
158
|
"REPRESENTATION_DENIED",
|
|
95
159
|
"REPRESENTATION_PENDING",
|
|
@@ -100,7 +164,19 @@ var ON_BEHALF_ERROR_CODES = [
|
|
|
100
164
|
"REPRESENTATION_REVOKED"
|
|
101
165
|
];
|
|
102
166
|
var NetworkError = class extends EidError {
|
|
167
|
+
/** "GET /session/…" (таних тэмдэг хасагдсан зам). */
|
|
168
|
+
op;
|
|
169
|
+
/** Хүсэлт хугацаа хэтэрсэн. */
|
|
170
|
+
timeout;
|
|
171
|
+
constructor(message, init) {
|
|
172
|
+
super(message);
|
|
173
|
+
this.op = init?.op;
|
|
174
|
+
this.timeout = init?.timeout;
|
|
175
|
+
}
|
|
103
176
|
};
|
|
177
|
+
function maskPath(path) {
|
|
178
|
+
return path.split(/[?#]/)[0].replace(/\/(etsi|document)\/[^/?#\s]+/g, "/$1/\u2026");
|
|
179
|
+
}
|
|
104
180
|
var SessionFailedError = class extends EidError {
|
|
105
181
|
constructor(endResult) {
|
|
106
182
|
super(`Session \u0430\u043C\u0436\u0438\u043B\u0442\u0433\u04AF\u0439: ${endResult}`);
|
|
@@ -154,26 +230,32 @@ var Http = class {
|
|
|
154
230
|
};
|
|
155
231
|
if (this.cfg.dispatcher) init.dispatcher = this.cfg.dispatcher;
|
|
156
232
|
let res;
|
|
233
|
+
const op = `${method} ${maskPath(path)}`;
|
|
157
234
|
try {
|
|
158
235
|
res = await f(url, init);
|
|
159
236
|
} catch (e) {
|
|
160
237
|
const err = e;
|
|
161
238
|
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
162
|
-
throw new NetworkError(`\u0425\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${
|
|
239
|
+
throw new NetworkError(`\u0425\u04AF\u0441\u044D\u043B\u0442 \u0445\u0443\u0433\u0430\u0446\u0430\u0430 \u0445\u044D\u0442\u044D\u0440\u043B\u044D\u044D: ${op}`, { op, timeout: true });
|
|
163
240
|
}
|
|
164
|
-
throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${
|
|
241
|
+
throw new NetworkError(`\u0421\u04AF\u043B\u0436\u044D\u044D\u043D\u0438\u0439 \u0430\u043B\u0434\u0430\u0430 (${op}): ${err.message.split(url).join(op)}`, { op, timeout: false });
|
|
165
242
|
}
|
|
166
243
|
if (res.status < 300) return res;
|
|
167
244
|
const text = await res.text();
|
|
168
245
|
const { error, code } = errorBody(text);
|
|
169
|
-
|
|
246
|
+
const headers = res.headers;
|
|
247
|
+
const retryAfterMs = parseRetryAfter(headers.get("Retry-After"));
|
|
248
|
+
if (res.status === 401) {
|
|
249
|
+
throw new AuthenticationError(`API secret \u0431\u0443\u0440\u0443\u0443 \u044D\u0441\u0432\u044D\u043B \u0431\u0430\u0439\u0445\u0433\u04AF\u0439 (401${code ? ` ${code}` : ""})`, { code, serverMessage: error, body: text, headers });
|
|
250
|
+
}
|
|
170
251
|
if (res.status === 403) {
|
|
171
252
|
throw new ForbiddenError(
|
|
172
253
|
error ? `RP-API 403${code ? ` ${code}` : ""}: ${error}` : "IP allowlist \u044D\u0441\u0432\u044D\u043B mTLS \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u04E9\u0433\u04AF\u0439 (403)",
|
|
173
|
-
code
|
|
254
|
+
code,
|
|
255
|
+
{ headers, retryAfterMs }
|
|
174
256
|
);
|
|
175
257
|
}
|
|
176
|
-
throw new ApiError(res.status, text, code);
|
|
258
|
+
throw new ApiError(res.status, text, code, { headers, retryAfterMs });
|
|
177
259
|
}
|
|
178
260
|
};
|
|
179
261
|
function errorBody(text) {
|
|
@@ -190,6 +272,48 @@ function errorBody(text) {
|
|
|
190
272
|
}
|
|
191
273
|
}
|
|
192
274
|
|
|
275
|
+
// src/contract.ts
|
|
276
|
+
var RP_ROUTES = {
|
|
277
|
+
authNotificationEtsi: { method: "POST", path: "/authentication/notification/etsi/{semanticsIdentifier}" },
|
|
278
|
+
authNotificationDocument: { method: "POST", path: "/authentication/notification/document/{documentNumber}" },
|
|
279
|
+
authDeviceLinkAnonymous: { method: "POST", path: "/authentication/device-link/anonymous" },
|
|
280
|
+
authDeviceLinkEtsi: { method: "POST", path: "/authentication/device-link/etsi/{semanticsIdentifier}" },
|
|
281
|
+
signNotificationEtsi: { method: "POST", path: "/signature/notification/etsi/{semanticsIdentifier}" },
|
|
282
|
+
signNotificationDocument: { method: "POST", path: "/signature/notification/document/{documentNumber}" },
|
|
283
|
+
pdfPrepare: { method: "POST", path: "/pdf/sign/prepare" },
|
|
284
|
+
pdfDocument: { method: "GET", path: "/pdf/sign/{sessionID}/document" },
|
|
285
|
+
orgRepresentations: { method: "GET", path: "/organization/representations/etsi/{personEtsi}" },
|
|
286
|
+
session: { method: "GET", path: "/session/{sessionId}" },
|
|
287
|
+
personCertificates: { method: "GET", path: "/certificates/etsi/{personEtsi}" },
|
|
288
|
+
personDevices: { method: "GET", path: "/devices/etsi/{personEtsi}" },
|
|
289
|
+
personActivity: { method: "GET", path: "/rp/activity/etsi/{personEtsi}" },
|
|
290
|
+
personSummary: { method: "GET", path: "/person/summary/etsi/{personEtsi}" },
|
|
291
|
+
personChildren: { method: "GET", path: "/person/children/etsi/{personEtsi}" },
|
|
292
|
+
tokenCertificate: { method: "POST", path: "/token/certificate" },
|
|
293
|
+
tokenCertificateResult: { method: "GET", path: "/token/certificate/{sessionID}" }
|
|
294
|
+
};
|
|
295
|
+
var RP_ROUTES_UNDISCOVERED = {
|
|
296
|
+
tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
|
|
297
|
+
tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
|
|
298
|
+
};
|
|
299
|
+
function routePath(route, ...args) {
|
|
300
|
+
let i = 0;
|
|
301
|
+
const out = route.path.replace(/\{[^}]*\}/g, () => {
|
|
302
|
+
const a = args[i++];
|
|
303
|
+
if (a === void 0) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0434\u0443\u0442\u0443\u0443`);
|
|
304
|
+
return encodeURIComponent(a);
|
|
305
|
+
});
|
|
306
|
+
if (i !== args.length) throw new Error(`eid: route ${route.path}: \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 \u0438\u043B\u04AF\u04AF`);
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
var OIDC_EXPECT = {
|
|
310
|
+
defaultScope: "openid",
|
|
311
|
+
pkceMethod: "S256",
|
|
312
|
+
idTokenAlg: "ES256",
|
|
313
|
+
/** `Authorization: Basic` (RFC 6749 §2.3.1). */
|
|
314
|
+
tokenEndpointAuthMethod: "client_secret_basic"
|
|
315
|
+
};
|
|
316
|
+
|
|
193
317
|
// src/crypto.ts
|
|
194
318
|
var import_node_crypto = require("crypto");
|
|
195
319
|
function sha256Base64(data) {
|
|
@@ -292,25 +416,25 @@ var AuthApi = class {
|
|
|
292
416
|
/** Push — РД/иргэний дугаар/ETSI-ээр (сервер төрлийг таьна). Иргэний утас руу мэдэгдэл очно. */
|
|
293
417
|
async notificationByEtsi(id, interactions, opts) {
|
|
294
418
|
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
|
|
295
|
-
const raw = await this.http.post(
|
|
419
|
+
const raw = await this.http.post(routePath(RP_ROUTES.authNotificationEtsi, id), body);
|
|
296
420
|
return this.parseNotification(raw, acsp);
|
|
297
421
|
}
|
|
298
422
|
/** Push — баримтын дугаараар (тодорхой төхөөрөмж рүү). */
|
|
299
423
|
async notificationByDocument(documentNumber, interactions, opts) {
|
|
300
424
|
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["Notification"], opts);
|
|
301
|
-
const raw = await this.http.post(
|
|
425
|
+
const raw = await this.http.post(routePath(RP_ROUTES.authNotificationDocument, documentNumber), body);
|
|
302
426
|
return this.parseNotification(raw, acsp);
|
|
303
427
|
}
|
|
304
428
|
/** QR/App2App — нэр томьёололгүй (иргэн хэн болохыг урьдчилж мэдэхгүй). QR код үүсгэхэд. */
|
|
305
429
|
async deviceLinkAnonymous(interactions, opts) {
|
|
306
430
|
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
|
|
307
|
-
const raw = await this.http.post(
|
|
431
|
+
const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkAnonymous), body);
|
|
308
432
|
return this.parseDeviceLink(raw, acsp);
|
|
309
433
|
}
|
|
310
434
|
/** QR/App2App — тодорхой иргэн (РД/civil_id/ETSI). */
|
|
311
435
|
async deviceLinkByEtsi(id, interactions, opts) {
|
|
312
436
|
const { body, acsp } = this.baseBody(randomChallenge(), interactions, ["QR"], opts);
|
|
313
|
-
const raw = await this.http.post(
|
|
437
|
+
const raw = await this.http.post(routePath(RP_ROUTES.authDeviceLinkEtsi, id), body);
|
|
314
438
|
return this.parseDeviceLink(raw, acsp);
|
|
315
439
|
}
|
|
316
440
|
};
|
|
@@ -343,6 +467,7 @@ var SignApi = class {
|
|
|
343
467
|
};
|
|
344
468
|
if (opts?.callbackUrl?.trim()) body.initialCallbackUrl = opts.callbackUrl.trim();
|
|
345
469
|
if (opts?.onBehalfOf?.trim()) body.onBehalfOf = opts.onBehalfOf.trim();
|
|
470
|
+
if (opts?.fileName?.trim()) body.fileName = opts.fileName.trim();
|
|
346
471
|
return body;
|
|
347
472
|
}
|
|
348
473
|
parse(raw) {
|
|
@@ -355,12 +480,12 @@ var SignApi = class {
|
|
|
355
480
|
* digest-ийг гаднаас бэлдэж өгнө — текст бус бинар баримтад тохиромжтой.
|
|
356
481
|
*/
|
|
357
482
|
async digestByEtsi(id, digestB64, interactions, opts) {
|
|
358
|
-
const raw = await this.http.post(
|
|
483
|
+
const raw = await this.http.post(routePath(RP_ROUTES.signNotificationEtsi, id), this.body(digestB64, interactions, opts));
|
|
359
484
|
return this.parse(raw);
|
|
360
485
|
}
|
|
361
486
|
/** Бэлэн digest-ээр push sign — баримтын дугаараар (auth-аас гарсан documentNumber дээр). */
|
|
362
487
|
async digestByDocument(documentNumber, digestB64, interactions, opts) {
|
|
363
|
-
const raw = await this.http.post(
|
|
488
|
+
const raw = await this.http.post(routePath(RP_ROUTES.signNotificationDocument, documentNumber), this.body(digestB64, interactions, opts));
|
|
364
489
|
return this.parse(raw);
|
|
365
490
|
}
|
|
366
491
|
/** Текстийн SHA-256 digest-ийг дотроо тооцоод push sign (богино текст баримтад тохиромжтой). */
|
|
@@ -401,9 +526,44 @@ function parseSessionResult(raw) {
|
|
|
401
526
|
signatureAlgorithmParameters: parseSigParams(sig.signatureAlgorithmParameters),
|
|
402
527
|
interactionTypeUsed: str(r.interactionTypeUsed),
|
|
403
528
|
onBehalfOf: parseOrg(r.onBehalfOf),
|
|
404
|
-
pdf: parsePdf(r.pdf)
|
|
529
|
+
pdf: parsePdf(r.pdf),
|
|
530
|
+
person: parsePerson(r.person),
|
|
531
|
+
tokenCertificate: parseTokenCert(r.tokenCertificate),
|
|
532
|
+
raw
|
|
405
533
|
};
|
|
406
534
|
}
|
|
535
|
+
var PERSON_STRINGS = [
|
|
536
|
+
"firstName",
|
|
537
|
+
"lastName",
|
|
538
|
+
"familyName",
|
|
539
|
+
"firstNameEn",
|
|
540
|
+
"lastNameEn",
|
|
541
|
+
"civilId",
|
|
542
|
+
"regNo",
|
|
543
|
+
"birthDate",
|
|
544
|
+
"gender"
|
|
545
|
+
];
|
|
546
|
+
function parsePerson(raw) {
|
|
547
|
+
const p = asRecord(raw);
|
|
548
|
+
const out = {};
|
|
549
|
+
for (const k of PERSON_STRINGS) if (typeof p[k] === "string" && p[k]) out[k] = p[k];
|
|
550
|
+
if (typeof p.geID === "number" && p.geID !== 0) out.geID = p.geID;
|
|
551
|
+
return Object.keys(out).length ? out : null;
|
|
552
|
+
}
|
|
553
|
+
function parseTokenCert(raw) {
|
|
554
|
+
const t = asRecord(raw);
|
|
555
|
+
const status = str(t.status);
|
|
556
|
+
if (!status) return null;
|
|
557
|
+
const out = { status };
|
|
558
|
+
if (t.errorCode) out.errorCode = String(t.errorCode);
|
|
559
|
+
if (t.authSerialHex) out.authSerialHex = String(t.authSerialHex);
|
|
560
|
+
if (t.signSerialHex) out.signSerialHex = String(t.signSerialHex);
|
|
561
|
+
return out;
|
|
562
|
+
}
|
|
563
|
+
function personMatchesCertificate(person, subject) {
|
|
564
|
+
const serial = typeof subject === "string" ? subject : subject?.serialNumber;
|
|
565
|
+
return !!person?.civilId && !!serial && serial === `PNOMN-${person.civilId}`;
|
|
566
|
+
}
|
|
407
567
|
function parsePdf(raw) {
|
|
408
568
|
const p = asRecord(raw);
|
|
409
569
|
const docID = str(p.docID);
|
|
@@ -457,7 +617,7 @@ var SessionApi = class {
|
|
|
457
617
|
/** Нэг удаагийн long-poll. serverPollMs хүртэл хүлээнэ. */
|
|
458
618
|
async poll(sessionId, serverPollMs = SERVER_POLL_MS) {
|
|
459
619
|
const raw = await this.http.get(
|
|
460
|
-
|
|
620
|
+
`${routePath(RP_ROUTES.session, sessionId)}?timeoutMs=${serverPollMs}`,
|
|
461
621
|
serverPollMs + 1e4
|
|
462
622
|
// HTTP timeout нь серверийн poll-оос урт
|
|
463
623
|
);
|
|
@@ -482,6 +642,7 @@ var SessionApi = class {
|
|
|
482
642
|
|
|
483
643
|
// src/pdf.ts
|
|
484
644
|
var import_node_crypto2 = require("crypto");
|
|
645
|
+
var DEFAULT_PDF_TIMEOUT_MS = 8e4;
|
|
485
646
|
function rec3(v) {
|
|
486
647
|
return v && typeof v === "object" ? v : {};
|
|
487
648
|
}
|
|
@@ -494,7 +655,10 @@ var PdfApi = class {
|
|
|
494
655
|
http;
|
|
495
656
|
creds;
|
|
496
657
|
defaults;
|
|
497
|
-
/**
|
|
658
|
+
/**
|
|
659
|
+
* POST /pdf/sign/prepare — multipart (`request` JSON + `pdf`). SIGN эрх шаардана. Түр алдаа: `ApiError` 429
|
|
660
|
+
* PDF_PREPARE_BUSY, 503 LTV_UNAVAILABLE / TSA_UNAVAILABLE — `retryAfterMs`-ийн дараа дахин (`retryable`).
|
|
661
|
+
*/
|
|
498
662
|
async prepare(input) {
|
|
499
663
|
const request = {
|
|
500
664
|
relyingPartyUUID: this.creds.rpUUID,
|
|
@@ -510,7 +674,7 @@ var PdfApi = class {
|
|
|
510
674
|
const form = new FormData();
|
|
511
675
|
form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
|
|
512
676
|
form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
|
|
513
|
-
const r = rec3(await this.http.postMultipart(
|
|
677
|
+
const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS));
|
|
514
678
|
const receivedAt = Date.now();
|
|
515
679
|
const vc = rec3(r.vc).value;
|
|
516
680
|
const dl = rec3(r.deviceLink);
|
|
@@ -532,10 +696,12 @@ var PdfApi = class {
|
|
|
532
696
|
}
|
|
533
697
|
/**
|
|
534
698
|
* GET /pdf/sign/{sessionID}/document — гарын үсэгтэй PDF (READY-ээс хойш 30 мин). Татсан байтын SHA-256-ийг
|
|
535
|
-
* session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`.
|
|
699
|
+
* session-ий `pdf.outSha256`-тай тулга. Эрх хасагдсан бол `ForbiddenError` `.code = "REPRESENTATION_REVOKED"`. Баримт
|
|
700
|
+
* бэлэн болоогүй бол `ApiError` 503 `.code` DOCUMENT_PENDING / TSA_UNAVAILABLE / LTV_UNAVAILABLE — `retryAfterMs`-ийн
|
|
701
|
+
* дараа дахин татна (`retryable`).
|
|
536
702
|
*/
|
|
537
703
|
document(sessionId) {
|
|
538
|
-
return this.http.getBytes(
|
|
704
|
+
return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS);
|
|
539
705
|
}
|
|
540
706
|
/**
|
|
541
707
|
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
@@ -567,13 +733,53 @@ var OrganizationApi = class {
|
|
|
567
733
|
http;
|
|
568
734
|
/** GET /organization/representations/etsi/{personEtsi}. Сонголт нь итгэх эх биш — сервер дахин шалгана. */
|
|
569
735
|
async getRepresentations(personEtsi) {
|
|
570
|
-
const raw = await this.http.get(
|
|
736
|
+
const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
|
|
571
737
|
const list = Array.isArray(raw.representations) ? raw.representations : [];
|
|
572
738
|
return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
|
|
573
739
|
}
|
|
574
740
|
};
|
|
575
741
|
|
|
576
|
-
// src/
|
|
742
|
+
// src/person.ts
|
|
743
|
+
function rec4(v) {
|
|
744
|
+
return v && typeof v === "object" ? v : {};
|
|
745
|
+
}
|
|
746
|
+
var PersonApi = class {
|
|
747
|
+
constructor(http) {
|
|
748
|
+
this.http = http;
|
|
749
|
+
}
|
|
750
|
+
http;
|
|
751
|
+
async get(path) {
|
|
752
|
+
const raw = await this.http.get(path);
|
|
753
|
+
return { ...rec4(raw), raw };
|
|
754
|
+
}
|
|
755
|
+
/** Иргэний бүх гэрчилгээ + статусын тоолол. */
|
|
756
|
+
certificates(personEtsi) {
|
|
757
|
+
return this.get(routePath(RP_ROUTES.personCertificates, personEtsi));
|
|
758
|
+
}
|
|
759
|
+
/** Иргэний холбоотой төхөөрөмжүүд (идэвхтэй + идэвхгүй). */
|
|
760
|
+
devices(personEtsi) {
|
|
761
|
+
return this.get(routePath(RP_ROUTES.personDevices, personEtsi));
|
|
762
|
+
}
|
|
763
|
+
/** Иргэний үйл ажиллагааны түүх. */
|
|
764
|
+
activity(personEtsi, query) {
|
|
765
|
+
const q = new URLSearchParams();
|
|
766
|
+
if (query?.flow) q.set("flow", query.flow);
|
|
767
|
+
if (query?.limit && query.limit > 0) q.set("limit", String(query.limit));
|
|
768
|
+
if (query?.offset && query.offset > 0) q.set("offset", String(query.offset));
|
|
769
|
+
const qs = q.toString();
|
|
770
|
+
return this.get(routePath(RP_ROUTES.personActivity, personEtsi) + (qs ? `?${qs}` : ""));
|
|
771
|
+
}
|
|
772
|
+
/** Самбарын нэгдсэн тоо. */
|
|
773
|
+
summary(personEtsi) {
|
|
774
|
+
return this.get(routePath(RP_ROUTES.personSummary, personEtsi));
|
|
775
|
+
}
|
|
776
|
+
/** Асран хамгаалж буй хүүхдүүд (зөвхөн унших). */
|
|
777
|
+
children(personEtsi) {
|
|
778
|
+
return this.get(routePath(RP_ROUTES.personChildren, personEtsi));
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
// src/token.ts
|
|
577
783
|
var import_node_crypto4 = require("crypto");
|
|
578
784
|
|
|
579
785
|
// src/subject.ts
|
|
@@ -680,8 +886,207 @@ function parseSubject(cert) {
|
|
|
680
886
|
out.name = full || out.commonName || null;
|
|
681
887
|
return out;
|
|
682
888
|
}
|
|
889
|
+
function certExtension(der, oid) {
|
|
890
|
+
const top = tlv(der, 0, der.length);
|
|
891
|
+
const tbs = tlv(der, top.start, top.end);
|
|
892
|
+
let f = tlv(der, tbs.start, tbs.end);
|
|
893
|
+
if (f.tag === 160) f = tlv(der, f.end, tbs.end);
|
|
894
|
+
for (let k = 0; k < 5; k++) f = tlv(der, f.end, tbs.end);
|
|
895
|
+
for (let pos = f.end; pos < tbs.end; ) {
|
|
896
|
+
const t = tlv(der, pos, tbs.end);
|
|
897
|
+
if (t.tag === 163) {
|
|
898
|
+
const seq = tlv(der, t.start, t.end);
|
|
899
|
+
for (let ep = seq.start; ep < seq.end; ) {
|
|
900
|
+
const ext = tlv(der, ep, seq.end);
|
|
901
|
+
const id = tlv(der, ext.start, ext.end);
|
|
902
|
+
let v = tlv(der, id.end, ext.end);
|
|
903
|
+
if (v.tag === 1) v = tlv(der, v.end, ext.end);
|
|
904
|
+
if (v.tag !== 4) throw new ValidationError("extension: extnValue OCTET STRING \u0431\u0438\u0448");
|
|
905
|
+
if (oidOf(der, id) === oid) return der.subarray(v.start, v.end);
|
|
906
|
+
ep = ext.end;
|
|
907
|
+
}
|
|
908
|
+
return void 0;
|
|
909
|
+
}
|
|
910
|
+
pos = t.end;
|
|
911
|
+
}
|
|
912
|
+
return void 0;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/token.ts
|
|
916
|
+
function rec5(v) {
|
|
917
|
+
return v && typeof v === "object" ? v : {};
|
|
918
|
+
}
|
|
919
|
+
function hexSha256Equal(hex, data) {
|
|
920
|
+
if (typeof hex !== "string" || !/^[0-9a-fA-F]{64}$/.test(hex)) return false;
|
|
921
|
+
return (0, import_node_crypto4.timingSafeEqual)((0, import_node_crypto4.createHash)("sha256").update(data).digest(), Buffer.from(hex, "hex"));
|
|
922
|
+
}
|
|
923
|
+
var TOKEN_RESULT_TIMEOUT_MS = 3e4;
|
|
924
|
+
var TokenApi = class {
|
|
925
|
+
constructor(http, creds) {
|
|
926
|
+
this.http = http;
|
|
927
|
+
this.creds = creds;
|
|
928
|
+
}
|
|
929
|
+
http;
|
|
930
|
+
creds;
|
|
931
|
+
/**
|
|
932
|
+
* POST /token/certificate — токены AUTH + SIGN гэрчилгээ олголт эхлүүлнэ (иргэн утсаараа батална). CA-ийн буцаасан
|
|
933
|
+
* CSR hash-ууд илгээсэнтэй таарахгүй бол ValidationError.
|
|
934
|
+
*/
|
|
935
|
+
async issueCertificate(input) {
|
|
936
|
+
if (!input.signerEtsi?.trim() || !input.csrAuth?.length || !input.csrSign?.length || !input.tokenSerial?.trim()) {
|
|
937
|
+
throw new Error("token: signerEtsi, csrAuth, csrSign, tokenSerial \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
938
|
+
}
|
|
939
|
+
const body = {
|
|
940
|
+
relyingPartyUUID: this.creds.rpUUID,
|
|
941
|
+
relyingPartyName: this.creds.rpName,
|
|
942
|
+
signer: { etsi: input.signerEtsi.trim() },
|
|
943
|
+
csrAuth: Buffer.from(input.csrAuth).toString("base64"),
|
|
944
|
+
csrSign: Buffer.from(input.csrSign).toString("base64"),
|
|
945
|
+
tokenSerial: input.tokenSerial.trim(),
|
|
946
|
+
onBehalfOf: input.onBehalfOf?.trim() ?? "",
|
|
947
|
+
flow: "notification"
|
|
948
|
+
};
|
|
949
|
+
if (input.tokenLabel) body.tokenLabel = input.tokenLabel;
|
|
950
|
+
const r = rec5(await this.http.post(routePath(RP_ROUTES.tokenCertificate), body));
|
|
951
|
+
if (!hexSha256Equal(r.csrAuthSha256, input.csrAuth) || !hexSha256Equal(r.csrSignSha256, input.csrSign)) {
|
|
952
|
+
throw new ValidationError("token: CA-\u0438\u0439\u043D csrAuthSha256/csrSignSha256 \u0438\u043B\u0433\u044D\u044D\u0441\u044D\u043D CSR-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
953
|
+
}
|
|
954
|
+
const vc = rec5(r.vc).value ?? (typeof r.vc === "string" ? r.vc : void 0);
|
|
955
|
+
return {
|
|
956
|
+
sessionId: String(r.sessionID ?? ""),
|
|
957
|
+
vc: vc != null ? String(vc) : null,
|
|
958
|
+
csrAuthSha256: String(r.csrAuthSha256).toLowerCase(),
|
|
959
|
+
csrSignSha256: String(r.csrSignSha256).toLowerCase(),
|
|
960
|
+
...typeof r.expiresAt === "string" ? { expiresAt: r.expiresAt } : {}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* GET /token/certificate/{sessionID} — олгосон AUTH + SIGN гэрчилгээ + гинж. Токенд бичихээс өмнө
|
|
965
|
+
* {@link verifyTokenCertificates}-ээр CSR-ийн түлхүүртэй тулга.
|
|
966
|
+
*/
|
|
967
|
+
async certificateResult(sessionId) {
|
|
968
|
+
const raw = await this.http.get(routePath(RP_ROUTES.tokenCertificateResult, sessionId), TOKEN_RESULT_TIMEOUT_MS);
|
|
969
|
+
const r = rec5(raw);
|
|
970
|
+
const item = (v) => v && typeof v === "object" ? v : null;
|
|
971
|
+
return {
|
|
972
|
+
status: String(r.status ?? ""),
|
|
973
|
+
auth: item(r.auth),
|
|
974
|
+
sign: item(r.sign),
|
|
975
|
+
chain: Array.isArray(r.chain) ? r.chain.map(String) : [],
|
|
976
|
+
chainPem: String(r.chainPem ?? ""),
|
|
977
|
+
organization: r.organization && typeof r.organization === "object" ? r.organization : null,
|
|
978
|
+
keyMedium: String(r.keyMedium ?? ""),
|
|
979
|
+
certificateLevel: String(r.certificateLevel ?? ""),
|
|
980
|
+
raw
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
/** POST /token/auth/challenge — энэ RP-д холбогдсон нэг удаагийн challenge (2 мин). */
|
|
984
|
+
async authChallenge() {
|
|
985
|
+
const r = rec5(
|
|
986
|
+
await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthChallenge), {
|
|
987
|
+
relyingPartyUUID: this.creds.rpUUID,
|
|
988
|
+
relyingPartyName: this.creds.rpName
|
|
989
|
+
})
|
|
990
|
+
);
|
|
991
|
+
return {
|
|
992
|
+
challengeId: String(r.challengeId ?? ""),
|
|
993
|
+
challenge: String(r.challenge ?? ""),
|
|
994
|
+
rpUUID: String(r.rpUUID ?? ""),
|
|
995
|
+
expiresAt: String(r.expiresAt ?? "")
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* POST /token/auth/verify — токены AUTH гэрчилгээ (DER) + challenge дээрх түүхий r||s. CA гинж, төлөв, цуцлалт, гарын
|
|
1000
|
+
* үсгийг шалгана; SDK нэмж endResult === OK ба identity.etsi === гэрчилгээний SERIALNUMBER-ийг тулгана (эс бөгөөс
|
|
1001
|
+
* ValidationError). Challenge нэг удаагийн — алдааны дараа шинээр авна.
|
|
1002
|
+
*/
|
|
1003
|
+
async authVerify(challengeId, certDer, signature) {
|
|
1004
|
+
let serial;
|
|
1005
|
+
try {
|
|
1006
|
+
serial = parseSubject(new import_node_crypto4.X509Certificate(certDer)).serialNumber;
|
|
1007
|
+
} catch {
|
|
1008
|
+
throw new Error("token auth: \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
|
|
1009
|
+
}
|
|
1010
|
+
if (!challengeId || !signature?.length) throw new Error("token auth: challengeId \u0431\u0430 signature \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
1011
|
+
const raw = await this.http.post(routePath(RP_ROUTES_UNDISCOVERED.tokenAuthVerify), {
|
|
1012
|
+
challengeId,
|
|
1013
|
+
certificate: Buffer.from(certDer).toString("base64"),
|
|
1014
|
+
signature: Buffer.from(signature).toString("base64")
|
|
1015
|
+
});
|
|
1016
|
+
const r = rec5(raw);
|
|
1017
|
+
if (rec5(r.result).endResult !== "OK") throw new ValidationError("token auth: endResult OK \u0431\u0438\u0448");
|
|
1018
|
+
const id = rec5(r.identity);
|
|
1019
|
+
if (!id.etsi || id.etsi !== serial) throw new ValidationError("token auth: identity.etsi \u043D\u044C \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 SERIALNUMBER \u0431\u0438\u0448");
|
|
1020
|
+
const org = rec5(r.organization);
|
|
1021
|
+
const cert = rec5(r.cert);
|
|
1022
|
+
return {
|
|
1023
|
+
identity: { etsi: String(id.etsi), givenName: String(id.givenName ?? ""), surname: String(id.surname ?? "") },
|
|
1024
|
+
serialHex: String(cert.serialHex ?? ""),
|
|
1025
|
+
notAfter: String(cert.notAfter ?? ""),
|
|
1026
|
+
organization: org.etsi ? { etsi: String(org.etsi), name: String(org.name ?? "") } : null,
|
|
1027
|
+
raw
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
function csrSpki(csr) {
|
|
1032
|
+
const top = tlv(csr, 0, csr.length);
|
|
1033
|
+
const info = tlv(csr, top.start, top.end);
|
|
1034
|
+
const version = tlv(csr, info.start, info.end);
|
|
1035
|
+
const subject = tlv(csr, version.end, info.end);
|
|
1036
|
+
const spki = tlv(csr, subject.end, info.end);
|
|
1037
|
+
return (0, import_node_crypto4.createPublicKey)({ key: Buffer.from(csr.subarray(subject.end, spki.end)), format: "der", type: "spki" }).export({
|
|
1038
|
+
format: "der",
|
|
1039
|
+
type: "spki"
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
function verifyTokenCertificates(result, csrAuth, csrSign) {
|
|
1043
|
+
if (!result?.auth || !result.sign) throw new ValidationError("token: auth/sign \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0430\u043B\u0433\u0430");
|
|
1044
|
+
for (const [name, item, csr] of [
|
|
1045
|
+
["auth", result.auth, csrAuth],
|
|
1046
|
+
["sign", result.sign, csrSign]
|
|
1047
|
+
]) {
|
|
1048
|
+
let want;
|
|
1049
|
+
try {
|
|
1050
|
+
want = csrSpki(csr);
|
|
1051
|
+
} catch {
|
|
1052
|
+
throw new Error(`token: ${name} CSR \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
|
|
1053
|
+
}
|
|
1054
|
+
let got;
|
|
1055
|
+
try {
|
|
1056
|
+
got = new import_node_crypto4.X509Certificate(Buffer.from(item.certificate, "base64")).publicKey.export({ format: "der", type: "spki" });
|
|
1057
|
+
} catch {
|
|
1058
|
+
throw new ValidationError(`token: ${name} \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0437\u0430\u0434\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
|
|
1059
|
+
}
|
|
1060
|
+
if (!got.equals(want)) throw new ValidationError(`token: ${name} \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0442\u04AF\u043B\u0445\u04AF\u04AF\u0440 CSR-\u0438\u0439\u043D\u0445\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function tokenAuthDigest(rpUUID, challengeB64) {
|
|
1064
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(rpUUID)) {
|
|
1065
|
+
throw new Error("token auth: rpUUID \u0436\u0438\u0436\u0438\u0433 \u04AF\u0441\u0433\u0438\u0439\u043D 36 \u0442\u044D\u043C\u0434\u044D\u0433\u0442 UUID \u0431\u0438\u0448");
|
|
1066
|
+
}
|
|
1067
|
+
const ch = Buffer.from(challengeB64, "base64");
|
|
1068
|
+
if (ch.length !== 32 || ch.toString("base64") !== challengeB64) throw new Error("token auth: challenge 32 \u0431\u0430\u0439\u0442 base64 \u0431\u0438\u0448");
|
|
1069
|
+
return (0, import_node_crypto4.createHash)("sha256").update("eID-TOKEN-AUTH", "ascii").update(rpUUID, "ascii").update(ch).digest();
|
|
1070
|
+
}
|
|
1071
|
+
function tokenLimitOf(err) {
|
|
1072
|
+
if (!(err instanceof ApiError) || err.code !== "TOKEN_LIMIT_REACHED") return void 0;
|
|
1073
|
+
let b;
|
|
1074
|
+
try {
|
|
1075
|
+
b = rec5(JSON.parse(err.body));
|
|
1076
|
+
} catch {
|
|
1077
|
+
return void 0;
|
|
1078
|
+
}
|
|
1079
|
+
if (typeof b.limit !== "number" || !Number.isInteger(b.limit) || b.limit < 0) return void 0;
|
|
1080
|
+
if (b.kind === "PERSONAL") return { kind: "PERSONAL", limit: b.limit };
|
|
1081
|
+
if (b.kind === "ORGANIZATION" && typeof b.organization === "string" && b.organization.startsWith("NTRMN-")) {
|
|
1082
|
+
return { kind: "ORGANIZATION", limit: b.limit, organization: b.organization };
|
|
1083
|
+
}
|
|
1084
|
+
return void 0;
|
|
1085
|
+
}
|
|
683
1086
|
|
|
684
1087
|
// src/validator.ts
|
|
1088
|
+
var import_node_crypto5 = require("crypto");
|
|
1089
|
+
var CITIZEN_ISSUING_CA_SKI = "b8a37f2c64ad677912db7d9e34f246358a2fb909";
|
|
685
1090
|
var ResponseValidator = class {
|
|
686
1091
|
anchors;
|
|
687
1092
|
intermediates;
|
|
@@ -690,14 +1095,28 @@ var ResponseValidator = class {
|
|
|
690
1095
|
allowUntrusted;
|
|
691
1096
|
revocation;
|
|
692
1097
|
revocationMode;
|
|
1098
|
+
allowedIssuers;
|
|
1099
|
+
checkKeyUsage;
|
|
1100
|
+
schemeName;
|
|
693
1101
|
constructor(trust = {}) {
|
|
694
|
-
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new
|
|
695
|
-
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new
|
|
1102
|
+
this.anchors = (trust.trustAnchorsPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
|
|
1103
|
+
this.intermediates = (trust.intermediatesPem ?? []).map((p) => new import_node_crypto5.X509Certificate(p));
|
|
696
1104
|
this.requiredLevel = trust.requiredLevel ?? "QUALIFIED";
|
|
697
1105
|
this.skew = trust.clockSkewMs ?? 0;
|
|
698
1106
|
this.allowUntrusted = trust.allowUntrusted ?? false;
|
|
699
1107
|
this.revocation = trust.revocation;
|
|
700
1108
|
this.revocationMode = trust.revocationMode ?? "hard-fail";
|
|
1109
|
+
if (trust.allowedIssuerSkis?.length) {
|
|
1110
|
+
this.allowedIssuers = new Set(
|
|
1111
|
+
trust.allowedIssuerSkis.map((s) => {
|
|
1112
|
+
const ski = s.trim().replace(/:/g, "").toLowerCase();
|
|
1113
|
+
if (!/^(?:[0-9a-f]{2})+$/.test(ski)) throw new Error(`ResponseValidator: allowedIssuerSkis: "${s}" hex key identifier \u0431\u0438\u0448`);
|
|
1114
|
+
return ski;
|
|
1115
|
+
})
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
this.checkKeyUsage = trust.checkKeyUsage ?? false;
|
|
1119
|
+
this.schemeName = trust.schemeName;
|
|
701
1120
|
}
|
|
702
1121
|
// ── нийтийн API ──
|
|
703
1122
|
/**
|
|
@@ -725,6 +1144,7 @@ var ResponseValidator = class {
|
|
|
725
1144
|
if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
|
|
726
1145
|
const { alg, hash } = this.algorithmOf(result, leaf, acsp.signatureAlgorithm, acsp.hashAlgorithm);
|
|
727
1146
|
const payload = buildAcspV2Payload({
|
|
1147
|
+
...this.schemeName ? { schemeName: this.schemeName } : {},
|
|
728
1148
|
serverRandom,
|
|
729
1149
|
rpChallenge: acsp.rpChallenge,
|
|
730
1150
|
userChallenge,
|
|
@@ -828,9 +1248,10 @@ var ResponseValidator = class {
|
|
|
828
1248
|
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");
|
|
829
1249
|
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");
|
|
830
1250
|
if (!result.documentNumber) throw new ValidationError("\u0425\u0430\u0440\u0438\u0443\u043D\u0434 documentNumber \u0430\u043B\u0433\u0430");
|
|
831
|
-
const leaf = new
|
|
1251
|
+
const leaf = new import_node_crypto5.X509Certificate(Buffer.from(result.certificateDerB64, "base64"));
|
|
832
1252
|
this.checkValidity(leaf);
|
|
833
1253
|
this.checkLevel(String(result.certificateLevel ?? ""));
|
|
1254
|
+
this.checkPurpose(leaf, protocol);
|
|
834
1255
|
if (this.anchors.length === 0) {
|
|
835
1256
|
if (!this.allowUntrusted) {
|
|
836
1257
|
throw new ValidationError(
|
|
@@ -840,8 +1261,35 @@ var ResponseValidator = class {
|
|
|
840
1261
|
} else {
|
|
841
1262
|
this.verifyChain(leaf);
|
|
842
1263
|
}
|
|
1264
|
+
this.checkIssuer(leaf);
|
|
843
1265
|
return leaf;
|
|
844
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* checkKeyUsage үед гэрчилгээний зориулалт протоколд тохирох эсэх (CA crypto/ca_profile.go): ACSP_V2 (auth) → KU
|
|
1269
|
+
* digitalSignature + EKU clientAuth (эсвэл anyExtendedKeyUsage); RAW_DIGEST_SIGNATURE (sign) → KU contentCommitment.
|
|
1270
|
+
*/
|
|
1271
|
+
checkPurpose(leaf, protocol) {
|
|
1272
|
+
if (!this.checkKeyUsage) return;
|
|
1273
|
+
const ku = keyUsageBits(leaf);
|
|
1274
|
+
if (protocol === "ACSP_V2") {
|
|
1275
|
+
if (!(ku & 128)) throw new ValidationError("\u041D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 KeyUsage digitalSignature \u0430\u043B\u0433\u0430");
|
|
1276
|
+
const eku = extKeyUsage(leaf);
|
|
1277
|
+
if (!eku.includes("1.3.6.1.5.5.7.3.2") && !eku.includes("2.5.29.37.0")) {
|
|
1278
|
+
throw new ValidationError("\u041D\u044D\u0432\u0442\u0440\u044D\u043B\u0442\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 ExtKeyUsage clientAuth \u0430\u043B\u0433\u0430 (\u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u0433\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)");
|
|
1279
|
+
}
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (!(ku & 64)) throw new ValidationError("\u0413\u0430\u0440\u044B\u043D \u04AF\u0441\u0433\u0438\u0439\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0434 KeyUsage nonRepudiation (contentCommitment) \u0430\u043B\u0433\u0430");
|
|
1283
|
+
}
|
|
1284
|
+
/** allowedIssuerSkis үед leaf-ийн шууд issuer-ийн SKI (issuer-т алга бол leaf-ийн AKI) жагсаалтад байх ёстой. */
|
|
1285
|
+
checkIssuer(leaf) {
|
|
1286
|
+
if (!this.allowedIssuers) return;
|
|
1287
|
+
const issuer = this.findIssuer(leaf);
|
|
1288
|
+
const ski = (issuer && subjectKeyId(issuer)) ?? authorityKeyId(leaf);
|
|
1289
|
+
if (!ski || !this.allowedIssuers.has(ski)) {
|
|
1290
|
+
throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u0433 \u0433\u0430\u0440\u0433\u0430\u0441\u0430\u043D CA \u0437\u04E9\u0432\u0448\u04E9\u04E9\u0440\u04E9\u0433\u0434\u0441\u04E9\u043D \u0438\u0440\u0433\u044D\u043D\u0438\u0439 issuer \u0431\u0438\u0448 (allowedIssuerSkis)");
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
845
1293
|
checkValidity(cert) {
|
|
846
1294
|
const now = Date.now();
|
|
847
1295
|
const from = Date.parse(cert.validFrom);
|
|
@@ -884,14 +1332,23 @@ var ResponseValidator = class {
|
|
|
884
1332
|
* дуудна (тэдгээр нь sync; revocation нь сүлжээ I/O тул async).
|
|
885
1333
|
*/
|
|
886
1334
|
async checkRevocation(cert) {
|
|
887
|
-
|
|
1335
|
+
await this.checkRevocationStatus(cert);
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* {@link checkRevocation} + юу шийдсэнийг буцаана (лог/аудитад): "good" | "unchecked" (unknown, soft-fail) |
|
|
1339
|
+
* "skipped" (hook алга). "revoked", мөн hard-fail горимд "unknown" → ValidationError; hook-ийн алдаа дамжина.
|
|
1340
|
+
*/
|
|
1341
|
+
async checkRevocationStatus(cert) {
|
|
1342
|
+
if (!this.revocation) return "skipped";
|
|
888
1343
|
const status = await this.revocation(cert, this.findIssuer(cert));
|
|
1344
|
+
if (status === "good") return "good";
|
|
889
1345
|
if (status === "revoked") {
|
|
890
1346
|
throw new ValidationError("\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D \u0446\u0443\u0446\u043B\u0430\u0433\u0434\u0441\u0430\u043D (revoked)");
|
|
891
1347
|
}
|
|
892
|
-
if (
|
|
1348
|
+
if (this.revocationMode === "hard-fail") {
|
|
893
1349
|
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)");
|
|
894
1350
|
}
|
|
1351
|
+
return "unchecked";
|
|
895
1352
|
}
|
|
896
1353
|
};
|
|
897
1354
|
function signedCallbackUrl(fromResponse, rpCallback, flowType) {
|
|
@@ -906,16 +1363,16 @@ function signedCallbackUrl(fromResponse, rpCallback, flowType) {
|
|
|
906
1363
|
return fromResponse;
|
|
907
1364
|
}
|
|
908
1365
|
function userChallengeOf(verifier) {
|
|
909
|
-
return (0,
|
|
1366
|
+
return (0, import_node_crypto5.createHash)("sha256").update(verifier, "utf8").digest("base64url");
|
|
910
1367
|
}
|
|
911
1368
|
function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
|
|
912
1369
|
const sig = Buffer.from(sigB64, "base64");
|
|
913
1370
|
const h = nodeHash(hash);
|
|
914
1371
|
if (alg === "rsassa-pss") {
|
|
915
|
-
return (0,
|
|
1372
|
+
return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PSS_PADDING, saltLength: hashLen(hash) }, sig);
|
|
916
1373
|
}
|
|
917
|
-
if (alg === "ecdsa-with-SHA256") return (0,
|
|
918
|
-
return (0,
|
|
1374
|
+
if (alg === "ecdsa-with-SHA256") return (0, import_node_crypto5.verify)(h, payload, { key, dsaEncoding: "der" }, sig);
|
|
1375
|
+
return (0, import_node_crypto5.verify)(h, payload, { key, padding: import_node_crypto5.constants.RSA_PKCS1_PADDING }, sig);
|
|
919
1376
|
}
|
|
920
1377
|
function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
921
1378
|
const sig = Buffer.from(sigB64, "base64");
|
|
@@ -924,7 +1381,7 @@ function verifyPrehashedSignature(digest, sigB64, key, alg, hash) {
|
|
|
924
1381
|
if (!modBits || sig.length !== Math.ceil(modBits / 8)) return false;
|
|
925
1382
|
let em;
|
|
926
1383
|
try {
|
|
927
|
-
em = (0,
|
|
1384
|
+
em = (0, import_node_crypto5.publicDecrypt)({ key, padding: import_node_crypto5.constants.RSA_NO_PADDING }, sig);
|
|
928
1385
|
} catch {
|
|
929
1386
|
return false;
|
|
930
1387
|
}
|
|
@@ -1008,7 +1465,7 @@ function mgf1(hash, seed, length) {
|
|
|
1008
1465
|
for (let i = 0, n = 0; n < length; i++, n += hashLen(hash)) {
|
|
1009
1466
|
const c = Buffer.alloc(4);
|
|
1010
1467
|
c.writeUInt32BE(i, 0);
|
|
1011
|
-
out.push((0,
|
|
1468
|
+
out.push((0, import_node_crypto5.createHash)(nodeHash(hash)).update(seed).update(c).digest());
|
|
1012
1469
|
}
|
|
1013
1470
|
return Buffer.concat(out).subarray(0, length);
|
|
1014
1471
|
}
|
|
@@ -1032,9 +1489,42 @@ function emsaPssVerify(mHash, em, emBits, hash) {
|
|
|
1032
1489
|
const psLen = emLen - hLen - sLen - 2;
|
|
1033
1490
|
if (!db.subarray(0, psLen).every((b) => b === 0) || db[psLen] !== 1) return false;
|
|
1034
1491
|
const salt = db.subarray(psLen + 1);
|
|
1035
|
-
const h2 = (0,
|
|
1492
|
+
const h2 = (0, import_node_crypto5.createHash)(nodeHash(hash)).update(Buffer.alloc(8)).update(mHash).update(salt).digest();
|
|
1036
1493
|
return h.equals(h2);
|
|
1037
1494
|
}
|
|
1495
|
+
function keyUsageBits(cert) {
|
|
1496
|
+
const v = certExtension(cert.raw, "2.5.29.15");
|
|
1497
|
+
if (!v) return 0;
|
|
1498
|
+
const bs = tlv(v, 0, v.length);
|
|
1499
|
+
if (bs.tag !== 3 || bs.end - bs.start < 2) return 0;
|
|
1500
|
+
return v[bs.start + 1];
|
|
1501
|
+
}
|
|
1502
|
+
function extKeyUsage(cert) {
|
|
1503
|
+
const v = certExtension(cert.raw, "2.5.29.37");
|
|
1504
|
+
if (!v) return [];
|
|
1505
|
+
const seq = tlv(v, 0, v.length);
|
|
1506
|
+
const out = [];
|
|
1507
|
+
for (let p = seq.start; p < seq.end; ) {
|
|
1508
|
+
const t = tlv(v, p, seq.end);
|
|
1509
|
+
out.push(oidOf(v, t));
|
|
1510
|
+
p = t.end;
|
|
1511
|
+
}
|
|
1512
|
+
return out;
|
|
1513
|
+
}
|
|
1514
|
+
function subjectKeyId(cert) {
|
|
1515
|
+
const v = certExtension(cert.raw, "2.5.29.14");
|
|
1516
|
+
if (!v) return void 0;
|
|
1517
|
+
const o = tlv(v, 0, v.length);
|
|
1518
|
+
return o.tag === 4 && o.end > o.start ? Buffer.from(v.subarray(o.start, o.end)).toString("hex") : void 0;
|
|
1519
|
+
}
|
|
1520
|
+
function authorityKeyId(cert) {
|
|
1521
|
+
const v = certExtension(cert.raw, "2.5.29.35");
|
|
1522
|
+
if (!v) return void 0;
|
|
1523
|
+
const seq = tlv(v, 0, v.length);
|
|
1524
|
+
if (seq.start >= seq.end) return void 0;
|
|
1525
|
+
const k = tlv(v, seq.start, seq.end);
|
|
1526
|
+
return k.tag === 128 && k.end > k.start ? Buffer.from(v.subarray(k.start, k.end)).toString("hex") : void 0;
|
|
1527
|
+
}
|
|
1038
1528
|
function isCa(cert) {
|
|
1039
1529
|
return cert.ca === true;
|
|
1040
1530
|
}
|
|
@@ -1062,6 +1552,8 @@ var EidClient = class {
|
|
|
1062
1552
|
sign;
|
|
1063
1553
|
pdf;
|
|
1064
1554
|
organization;
|
|
1555
|
+
person;
|
|
1556
|
+
token;
|
|
1065
1557
|
session;
|
|
1066
1558
|
validator;
|
|
1067
1559
|
constructor(cfg) {
|
|
@@ -1077,8 +1569,10 @@ var EidClient = class {
|
|
|
1077
1569
|
const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
|
|
1078
1570
|
this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
|
|
1079
1571
|
this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
|
|
1080
|
-
this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level });
|
|
1572
|
+
this.pdf = new PdfApi(http, cfg.credentials, { certificateLevel: level, timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS });
|
|
1081
1573
|
this.organization = new OrganizationApi(http);
|
|
1574
|
+
this.person = new PersonApi(http);
|
|
1575
|
+
this.token = new TokenApi(http, cfg.credentials);
|
|
1082
1576
|
this.session = new SessionApi(http);
|
|
1083
1577
|
this.validator = new ResponseValidator(cfg.trust ?? {});
|
|
1084
1578
|
}
|
|
@@ -1127,7 +1621,7 @@ function openOrShowQR(session, opts) {
|
|
|
1127
1621
|
}
|
|
1128
1622
|
|
|
1129
1623
|
// src/devicelinkv3.ts
|
|
1130
|
-
var
|
|
1624
|
+
var import_node_crypto6 = require("crypto");
|
|
1131
1625
|
var TYPES = ["QR", "Web2App", "App2App"];
|
|
1132
1626
|
var PROTOCOL = { auth: "ACSP_V2", sign: "RAW_DIGEST_SIGNATURE", cert: "" };
|
|
1133
1627
|
var TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
@@ -1184,7 +1678,7 @@ function buildDeviceLink(p) {
|
|
|
1184
1678
|
callback,
|
|
1185
1679
|
unprotected
|
|
1186
1680
|
].join("|");
|
|
1187
|
-
const authCode = (0,
|
|
1681
|
+
const authCode = (0, import_node_crypto6.createHmac)("sha256", Buffer.from(p.sessionSecret, "base64")).update(payload, "utf8").digest("base64url");
|
|
1188
1682
|
return `${unprotected}&authCode=${authCode}`;
|
|
1189
1683
|
}
|
|
1190
1684
|
function qrDeviceLinkTicker(input, onLink, opts) {
|
|
@@ -1197,7 +1691,7 @@ function qrDeviceLinkTicker(input, onLink, opts) {
|
|
|
1197
1691
|
}
|
|
1198
1692
|
|
|
1199
1693
|
// src/oidc.ts
|
|
1200
|
-
var
|
|
1694
|
+
var import_node_crypto7 = require("crypto");
|
|
1201
1695
|
var EID_OIDC_ISSUER = "https://login.eidmongolia.mn";
|
|
1202
1696
|
var EID_ACR_QUALIFIED = "urn:eidmongolia:loa:qualified";
|
|
1203
1697
|
var EID_ACR_ADVANCED = "urn:eidmongolia:loa:advanced";
|
|
@@ -1234,7 +1728,7 @@ var EidOidcClient = class {
|
|
|
1234
1728
|
if (!cfg.clientSecret) throw new EidError("EidOidcClient: clientSecret \u0445\u043E\u043E\u0441\u043E\u043D");
|
|
1235
1729
|
if (!/^https:\/\//.test(cfg.redirectUri ?? "")) throw new EidError("EidOidcClient: redirectUri https:// \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439");
|
|
1236
1730
|
this.issuer = (cfg.issuer ?? EID_OIDC_ISSUER).replace(/\/+$/, "");
|
|
1237
|
-
this.scope = cfg.scope ??
|
|
1731
|
+
this.scope = cfg.scope ?? OIDC_EXPECT.defaultScope;
|
|
1238
1732
|
}
|
|
1239
1733
|
cfg;
|
|
1240
1734
|
issuer;
|
|
@@ -1282,7 +1776,7 @@ var EidOidcClient = class {
|
|
|
1282
1776
|
state,
|
|
1283
1777
|
nonce,
|
|
1284
1778
|
code_challenge: await pkceChallenge(codeVerifier),
|
|
1285
|
-
code_challenge_method:
|
|
1779
|
+
code_challenge_method: OIDC_EXPECT.pkceMethod
|
|
1286
1780
|
});
|
|
1287
1781
|
if (opts.display) p.set("display", opts.display);
|
|
1288
1782
|
u.search = p.toString();
|
|
@@ -1361,7 +1855,7 @@ var EidOidcClient = class {
|
|
|
1361
1855
|
const header = decodeJsonPart(parts[0]);
|
|
1362
1856
|
const payload = decodeJsonPart(parts[1]);
|
|
1363
1857
|
if (!header || !payload) throw bad("\u0445\u044D\u043B\u0431\u044D\u0440 \u0431\u0443\u0440\u0443\u0443");
|
|
1364
|
-
if (header.alg !==
|
|
1858
|
+
if (header.alg !== OIDC_EXPECT.idTokenAlg) throw bad("alg ES256 \u0431\u0438\u0448");
|
|
1365
1859
|
const kid = typeof header.kid === "string" ? header.kid : "";
|
|
1366
1860
|
if (!kid) throw bad("kid \u0430\u043B\u0433\u0430");
|
|
1367
1861
|
const pick = (ks) => ks.find(
|
|
@@ -1454,7 +1948,7 @@ function formEncode(s) {
|
|
|
1454
1948
|
function timingSafeEqualStr(a, b) {
|
|
1455
1949
|
const x = Buffer.from(a, "utf8");
|
|
1456
1950
|
const y = Buffer.from(b, "utf8");
|
|
1457
|
-
return x.length === y.length && (0,
|
|
1951
|
+
return x.length === y.length && (0, import_node_crypto7.timingSafeEqual)(x, y);
|
|
1458
1952
|
}
|
|
1459
1953
|
function decodeJsonPart(s) {
|
|
1460
1954
|
try {
|
|
@@ -1477,7 +1971,9 @@ function callbackParams(input) {
|
|
|
1477
1971
|
ApiError,
|
|
1478
1972
|
AuthApi,
|
|
1479
1973
|
AuthenticationError,
|
|
1974
|
+
CITIZEN_ISSUING_CA_SKI,
|
|
1480
1975
|
DEFAULT_BASE_URL,
|
|
1976
|
+
DEFAULT_PDF_TIMEOUT_MS,
|
|
1481
1977
|
DeviceLinkError,
|
|
1482
1978
|
EID_ACR_ADVANCED,
|
|
1483
1979
|
EID_ACR_QUALIFIED,
|
|
@@ -1492,10 +1988,13 @@ function callbackParams(input) {
|
|
|
1492
1988
|
ON_BEHALF_ERROR_CODES,
|
|
1493
1989
|
OrganizationApi,
|
|
1494
1990
|
PdfApi,
|
|
1991
|
+
PersonApi,
|
|
1992
|
+
RETRYABLE_ERROR_CODES,
|
|
1495
1993
|
ResponseValidator,
|
|
1496
1994
|
SessionApi,
|
|
1497
1995
|
SessionFailedError,
|
|
1498
1996
|
SignApi,
|
|
1997
|
+
TokenApi,
|
|
1499
1998
|
ValidationError,
|
|
1500
1999
|
acspV2Digest,
|
|
1501
2000
|
buildAcspV2Payload,
|
|
@@ -1504,14 +2003,19 @@ function callbackParams(input) {
|
|
|
1504
2003
|
interactionsToBase64,
|
|
1505
2004
|
isMobileUserAgent,
|
|
1506
2005
|
openOrShowQR,
|
|
2006
|
+
parseRetryAfter,
|
|
1507
2007
|
parseSessionResult,
|
|
1508
2008
|
parseSubject,
|
|
2009
|
+
personMatchesCertificate,
|
|
1509
2010
|
pkceChallenge,
|
|
1510
2011
|
qrDeviceLinkTicker,
|
|
1511
2012
|
randomChallenge,
|
|
1512
2013
|
resolveBaseUrl,
|
|
1513
2014
|
sha256Base64,
|
|
2015
|
+
tokenAuthDigest,
|
|
2016
|
+
tokenLimitOf,
|
|
1514
2017
|
userChallengeOf,
|
|
1515
2018
|
verifyPayloadSignature,
|
|
1516
|
-
verifyPrehashedSignature
|
|
2019
|
+
verifyPrehashedSignature,
|
|
2020
|
+
verifyTokenCertificates
|
|
1517
2021
|
});
|