@gerege-systems/eid-mongolia-sdk 0.10.0 → 0.11.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 +18 -2
- package/dist/{browser-BWCDj8xO.d.cts → browser-BVR6gtrx.d.cts} +12 -1
- package/dist/{browser-BWCDj8xO.d.ts → browser-BVR6gtrx.d.ts} +12 -1
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/{chunk-YDVPZMAL.js → chunk-QPJDZLHR.js} +10 -0
- package/dist/index.cjs +246 -28
- package/dist/index.d.cts +74 -6
- package/dist/index.d.ts +74 -6
- package/dist/index.js +237 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -254,7 +254,9 @@ try {
|
|
|
254
254
|
Түр алдаа — `ApiError.retryable` үнэн, `retryAfterMs`-ийн (CA-ийн `Retry-After`, секунд эсвэл огноо) дараа давтана:
|
|
255
255
|
prepare 429 `PDF_PREPARE_BUSY`, 503 `LTV_UNAVAILABLE` / `TSA_UNAVAILABLE`; document 503 `DOCUMENT_PENDING`,
|
|
256
256
|
`TSA_UNAVAILABLE`, `LTV_UNAVAILABLE` (`RETRYABLE_ERROR_CODES`). `headers` — хариуны header. PDF дуудлагын хугацаа
|
|
257
|
-
`pdfTimeoutMs` (анхдагч 80000).
|
|
257
|
+
`pdfTimeoutMs` (анхдагч 80000). `pdf.document` санах ойд `pdfMaxBytes` (анхдагч `DEFAULT_PDF_MAX_BYTES` = 64 MiB)
|
|
258
|
+
хүртэл уншина; хэтэрвэл `ResponseTooLargeError`. JSON хариу 4 MiB-ээс хэтэрвэл мөн `ResponseTooLargeError` (таслахгүй).
|
|
259
|
+
`getRepresentations`-ийн `raw` — серверийн түүхий JSON.
|
|
258
260
|
|
|
259
261
|
Олон талт гэрээ: А байгууллагын гаралтыг өөрчлөлтгүй Б-ийн `prepare`-д (`docID`, өөр `onBehalfOf`) илгээнэ.
|
|
260
262
|
Session-ий `onBehalfOf` блок (`role`/`rightType`) нь хариу угсрах үеийн бүртгэл; `pdf.signer.onBehalfOf`/`claimedRole`
|
|
@@ -281,8 +283,16 @@ verifyTokenCertificates(res, csrAuth, csrSign); // гэрчилгээ бүр CSR
|
|
|
281
283
|
const ch = await eid.token.authChallenge();
|
|
282
284
|
const digest = tokenAuthDigest(ch.rpUUID, ch.challenge); // токен CKM_ECDSA → түүхий r||s
|
|
283
285
|
const who = await eid.token.authVerify(ch.challengeId, authCertDer, rs);
|
|
286
|
+
|
|
287
|
+
// токеноор PDF (PAdES, SIGN эрх) — session/утасгүй; байгууллагын токенд onBehalfOf = тухайн байгууллага
|
|
288
|
+
const p = await eid.pdf.tokenPrepare({ pdf, fileName: "Гэрээ.pdf", signerEtsi, certificate: signCertDer });
|
|
289
|
+
const rs = /* токен (PKCS#11): CKM_ECDSA(p.hashToSign) → түүхий r||s, 64 байт — p.expiresAt хүртэл */ new Uint8Array(64);
|
|
290
|
+
const blk = await eid.pdf.tokenFinalize(p.signingId, rs); // READY; 503 DOCUMENT_PENDING → ижил rs-ийг дахин
|
|
291
|
+
const signed = await eid.pdf.tokenDocument(p.signingId); // SHA-256 = blk.outSha256 тулга
|
|
284
292
|
```
|
|
285
293
|
|
|
294
|
+
`/token/auth/*`, `/pdf/token/*` нь CA-д байгаа ч `/.well-known/eid`-д хараахан жагсаагдаагүй (гэрээний тест WARN).
|
|
295
|
+
|
|
286
296
|
## 4. QR ба ижил төхөөрөмж — device link v3
|
|
287
297
|
|
|
288
298
|
Иргэн өөр төхөөрөмжийн дэлгэц дээрх **QR**-ийг утсаараа уншина, эсвэл RP-ийн сайт/аппыг eID апптай **ижил
|
|
@@ -371,7 +381,12 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
371
381
|
|
|
372
382
|
1. `state === COMPLETE && endResult === OK`
|
|
373
383
|
2. Иргэний гэрчилгээг **trust anchor (Mongolian National Root CA)** хүртэл chain-аар баталгаажуулна
|
|
374
|
-
(Root → National Issuing CA → Gerege Issuing CA → eID Mongolia Issuing CA → иргэн)
|
|
384
|
+
(Root → National Issuing CA → Gerege Issuing CA → eID Mongolia Issuing CA → иргэн) — RFC 5280 замын шалгалт
|
|
385
|
+
(Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): issuer нэр + AKI/SKI + гарын үсэг, CA basicConstraints +
|
|
386
|
+
**pathLenConstraint** (04 нь `pathlen:0`), CA-ийн keyUsage keyCertSign, CA бүрийн хугацаа, **EKU** (auth →
|
|
387
|
+
clientAuth, sign → emailProtection — CA-ийн профайл; CA-д EKU байвал түүнд багтах), үл таних **critical
|
|
388
|
+
extension** → татгалзана. **Name constraints**-тэй CA гинжинд байвал fail-closed (CA-ийн бодит гинжинд алга).
|
|
389
|
+
`intermediatesPem`-д 02, 03, 04-ийг бүгдийг өгнө (хариунд гинж ирдэггүй). certificatePolicies-ийг шалгахгүй.
|
|
375
390
|
3. Гэрчилгээ хүчинтэй хугацаанд + шаардсан `certificateLevel` (default `QUALIFIED`)
|
|
376
391
|
4. Гарын үсгийг иргэний public key-ээр шалгана:
|
|
377
392
|
- **auth** → ACSP_V2 payload (`smart-id|ACSP_V2|serverRandom|rpChallenge|userChallenge|B64(rpName)|B64(brokeredRpName)|B64(SHA-256(interactions))|interactionTypeUsed|initialCallbackUrl|flowType`, hash = хүсэлтийн `hashAlgorithm`) дээр — `flowType`, `userChallenge`, `serverRandom` ба алгоритмын downgrade-ийг мөн шалгана;
|
|
@@ -404,6 +419,7 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
|
|
|
404
419
|
| `SessionFailedError` | endResult ≠ OK (TIMEOUT, USER_REFUSED…) — `.endResult`-оор салга |
|
|
405
420
|
| `ValidationError` | cert chain/signature/level шалгалт бүтэлгүйтсэн |
|
|
406
421
|
| `ApiError` | бусад HTTP алдаа (`.status`, `.body`, серверийн `.code`, `.retryAfterMs`, `.headers`, `.retryable` — ж: 404 `ORG_NOT_FOUND`) |
|
|
422
|
+
| `ResponseTooLargeError` | хариу SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — `.op`, `.limit` |
|
|
407
423
|
| `NetworkError` | timeout/сүлжээ (`.op` — таних тэмдэггүй зам, `.timeout`) |
|
|
408
424
|
| `EidOidcError` | «eID-ээр нэвтрэх» (OIDC) — `.code`: `access_denied`, `login_required`, `state_mismatch`, `invalid_grant`, `invalid_id_token`, `server_error` … |
|
|
409
425
|
| `DeviceLinkError` | device-link холбоос угсрах боломжгүй (`deviceLinkBase` https биш, token/secret/lang хэлбэр, төрөлд хэрэгтэй талбар дутуу) |
|
|
@@ -225,6 +225,8 @@ interface Representation {
|
|
|
225
225
|
interface RepresentationsResponse {
|
|
226
226
|
personEtsi: string;
|
|
227
227
|
representations: Representation[];
|
|
228
|
+
/** Серверийн түүхий JSON (SDK задлаагүй шинэ талбарт). */
|
|
229
|
+
raw?: unknown;
|
|
228
230
|
}
|
|
229
231
|
/** Session-ий байгууллагын блок — байгууллага + тухайн иргэний эрх (бүртгэлээс real-time). */
|
|
230
232
|
interface OrgResult {
|
|
@@ -395,6 +397,15 @@ declare class NetworkError extends EidError {
|
|
|
395
397
|
timeout?: boolean;
|
|
396
398
|
});
|
|
397
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Хариуны бие SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — бие таслагдаж задлагдахгүй. `op` — method +
|
|
402
|
+
* таних тэмдэггүй зам.
|
|
403
|
+
*/
|
|
404
|
+
declare class ResponseTooLargeError extends EidError {
|
|
405
|
+
readonly op: string;
|
|
406
|
+
readonly limit: number;
|
|
407
|
+
constructor(op: string, limit: number);
|
|
408
|
+
}
|
|
398
409
|
/**
|
|
399
410
|
* Session амжилтгүй дууссан (endResult ≠ OK): TIMEOUT, USER_REFUSED*, DOCUMENT_UNUSABLE гм.
|
|
400
411
|
* endResult-оор салгаж RP UI-д тохирох мессеж харуулна.
|
|
@@ -417,4 +428,4 @@ declare class ValidationError extends EidError {
|
|
|
417
428
|
declare class DeviceLinkError extends EidError {
|
|
418
429
|
}
|
|
419
430
|
|
|
420
|
-
export { type AcspContext as A,
|
|
431
|
+
export { type AcspContext as A, deviceLink as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, isMobileUserAgent as G, type HashAlgorithm as H, type Interaction as I, openOrShowQR as J, parseRetryAfter as K, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, ValidationError as V, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, DeviceLinkError as f, type DeviceLinkInput as g, type EndResult as h, ForbiddenError as i, type HttpErrorInit as j, type NavigatorLike as k, NetworkError as l, type OnBehalfErrorCode as m, type OpenOrShowQROptions as n, type OrgResult as o, type PdfDocumentStatus as p, type PdfSigner as q, RETRYABLE_ERROR_CODES as r, type Representation as s, ResponseTooLargeError as t, type RetryableErrorCode as u, SessionFailedError as v, type SessionState as w, type SignatureAlgorithmParameters as x, type SignatureProtocol as y, type TokenCertStatus as z };
|
|
@@ -225,6 +225,8 @@ interface Representation {
|
|
|
225
225
|
interface RepresentationsResponse {
|
|
226
226
|
personEtsi: string;
|
|
227
227
|
representations: Representation[];
|
|
228
|
+
/** Серверийн түүхий JSON (SDK задлаагүй шинэ талбарт). */
|
|
229
|
+
raw?: unknown;
|
|
228
230
|
}
|
|
229
231
|
/** Session-ий байгууллагын блок — байгууллага + тухайн иргэний эрх (бүртгэлээс real-time). */
|
|
230
232
|
interface OrgResult {
|
|
@@ -395,6 +397,15 @@ declare class NetworkError extends EidError {
|
|
|
395
397
|
timeout?: boolean;
|
|
396
398
|
});
|
|
397
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Хариуны бие SDK-ийн хязгаараас их (JSON 4 MiB, PDF `pdfMaxBytes`) — бие таслагдаж задлагдахгүй. `op` — method +
|
|
402
|
+
* таних тэмдэггүй зам.
|
|
403
|
+
*/
|
|
404
|
+
declare class ResponseTooLargeError extends EidError {
|
|
405
|
+
readonly op: string;
|
|
406
|
+
readonly limit: number;
|
|
407
|
+
constructor(op: string, limit: number);
|
|
408
|
+
}
|
|
398
409
|
/**
|
|
399
410
|
* Session амжилтгүй дууссан (endResult ≠ OK): TIMEOUT, USER_REFUSED*, DOCUMENT_UNUSABLE гм.
|
|
400
411
|
* endResult-оор салгаж RP UI-д тохирох мессеж харуулна.
|
|
@@ -417,4 +428,4 @@ declare class ValidationError extends EidError {
|
|
|
417
428
|
declare class DeviceLinkError extends EidError {
|
|
418
429
|
}
|
|
419
430
|
|
|
420
|
-
export { type AcspContext as A,
|
|
431
|
+
export { type AcspContext as A, deviceLink as B, type CertificateLevel as C, type DeviceLinkSession as D, EidError as E, type FlowType as F, isMobileUserAgent as G, type HashAlgorithm as H, type Interaction as I, openOrShowQR as J, parseRetryAfter as K, type NotificationSession as N, ON_BEHALF_ERROR_CODES as O, type PersonBlock as P, type RpCredentials as R, type SignatureAlgorithm as S, type TokenCertSessionBlock as T, ValidationError as V, type SessionResult as a, type PdfSessionBlock as b, type RepresentationsResponse as c, ApiError as d, AuthenticationError as e, DeviceLinkError as f, type DeviceLinkInput as g, type EndResult as h, ForbiddenError as i, type HttpErrorInit as j, type NavigatorLike as k, NetworkError as l, type OnBehalfErrorCode as m, type OpenOrShowQROptions as n, type OrgResult as o, type PdfDocumentStatus as p, type PdfSigner as q, RETRYABLE_ERROR_CODES as r, type Representation as s, ResponseTooLargeError as t, type RetryableErrorCode as u, SessionFailedError as v, type SessionState as w, type SignatureAlgorithmParameters as x, type SignatureProtocol as y, type TokenCertStatus as z };
|
package/dist/browser.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions,
|
|
1
|
+
export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR } from './browser-BVR6gtrx.cjs';
|
package/dist/browser.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions,
|
|
1
|
+
export { f as DeviceLinkError, g as DeviceLinkInput, D as DeviceLinkSession, E as EidError, k as NavigatorLike, n as OpenOrShowQROptions, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR } from './browser-BVR6gtrx.js';
|
package/dist/browser.js
CHANGED
|
@@ -99,6 +99,15 @@ var NetworkError = class extends EidError {
|
|
|
99
99
|
this.timeout = init?.timeout;
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
|
+
var ResponseTooLargeError = class extends EidError {
|
|
103
|
+
constructor(op, limit) {
|
|
104
|
+
super(`${op}: \u0445\u0430\u0440\u0438\u0443 ${limit} \u0431\u0430\u0439\u0442\u0430\u0430\u0441 \u0438\u0445`);
|
|
105
|
+
this.op = op;
|
|
106
|
+
this.limit = limit;
|
|
107
|
+
}
|
|
108
|
+
op;
|
|
109
|
+
limit;
|
|
110
|
+
};
|
|
102
111
|
function maskPath(path) {
|
|
103
112
|
return path.split(/[?#]/)[0].replace(/\/(etsi|document)\/[^/?#\s]+/g, "/$1/\u2026");
|
|
104
113
|
}
|
|
@@ -165,6 +174,7 @@ export {
|
|
|
165
174
|
parseRetryAfter,
|
|
166
175
|
ON_BEHALF_ERROR_CODES,
|
|
167
176
|
NetworkError,
|
|
177
|
+
ResponseTooLargeError,
|
|
168
178
|
maskPath,
|
|
169
179
|
SessionFailedError,
|
|
170
180
|
ValidationError,
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
AuthenticationError: () => AuthenticationError,
|
|
26
26
|
CITIZEN_ISSUING_CA_SKI: () => CITIZEN_ISSUING_CA_SKI,
|
|
27
27
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
28
|
+
DEFAULT_PDF_MAX_BYTES: () => DEFAULT_PDF_MAX_BYTES,
|
|
28
29
|
DEFAULT_PDF_TIMEOUT_MS: () => DEFAULT_PDF_TIMEOUT_MS,
|
|
29
30
|
DeviceLinkError: () => DeviceLinkError,
|
|
30
31
|
EID_ACR_ADVANCED: () => EID_ACR_ADVANCED,
|
|
@@ -42,6 +43,7 @@ __export(index_exports, {
|
|
|
42
43
|
PdfApi: () => PdfApi,
|
|
43
44
|
PersonApi: () => PersonApi,
|
|
44
45
|
RETRYABLE_ERROR_CODES: () => RETRYABLE_ERROR_CODES,
|
|
46
|
+
ResponseTooLargeError: () => ResponseTooLargeError,
|
|
45
47
|
ResponseValidator: () => ResponseValidator,
|
|
46
48
|
SessionApi: () => SessionApi,
|
|
47
49
|
SessionFailedError: () => SessionFailedError,
|
|
@@ -174,6 +176,15 @@ var NetworkError = class extends EidError {
|
|
|
174
176
|
this.timeout = init?.timeout;
|
|
175
177
|
}
|
|
176
178
|
};
|
|
179
|
+
var ResponseTooLargeError = class extends EidError {
|
|
180
|
+
constructor(op, limit) {
|
|
181
|
+
super(`${op}: \u0445\u0430\u0440\u0438\u0443 ${limit} \u0431\u0430\u0439\u0442\u0430\u0430\u0441 \u0438\u0445`);
|
|
182
|
+
this.op = op;
|
|
183
|
+
this.limit = limit;
|
|
184
|
+
}
|
|
185
|
+
op;
|
|
186
|
+
limit;
|
|
187
|
+
};
|
|
177
188
|
function maskPath(path) {
|
|
178
189
|
return path.split(/[?#]/)[0].replace(/\/(etsi|document)\/[^/?#\s]+/g, "/$1/\u2026");
|
|
179
190
|
}
|
|
@@ -191,6 +202,7 @@ var DeviceLinkError = class extends EidError {
|
|
|
191
202
|
|
|
192
203
|
// src/http.ts
|
|
193
204
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
205
|
+
var MAX_JSON_BYTES = 4 << 20;
|
|
194
206
|
var Http = class {
|
|
195
207
|
constructor(cfg) {
|
|
196
208
|
this.cfg = cfg;
|
|
@@ -206,14 +218,14 @@ var Http = class {
|
|
|
206
218
|
postMultipart(path, form, timeoutMs) {
|
|
207
219
|
return this.json("POST", path, form, void 0, timeoutMs);
|
|
208
220
|
}
|
|
209
|
-
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
|
|
210
|
-
async getBytes(path, timeoutMs) {
|
|
221
|
+
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
|
|
222
|
+
async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
|
|
211
223
|
const res = await this.send("GET", path, void 0, void 0, timeoutMs);
|
|
212
|
-
return
|
|
224
|
+
return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
|
|
213
225
|
}
|
|
214
226
|
async json(method, path, body, contentType, timeoutMs) {
|
|
215
227
|
const res = await this.send(method, path, body, contentType, timeoutMs);
|
|
216
|
-
const text = await res
|
|
228
|
+
const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
|
|
217
229
|
return text ? JSON.parse(text) : {};
|
|
218
230
|
}
|
|
219
231
|
async send(method, path, body, contentType, timeoutMs) {
|
|
@@ -258,6 +270,34 @@ var Http = class {
|
|
|
258
270
|
throw new ApiError(res.status, text, code, { headers, retryAfterMs });
|
|
259
271
|
}
|
|
260
272
|
};
|
|
273
|
+
async function readLimited(res, limit, op) {
|
|
274
|
+
const declared = Number(res.headers.get("content-length") ?? NaN);
|
|
275
|
+
if (declared > limit) {
|
|
276
|
+
await res.body?.cancel().catch(() => void 0);
|
|
277
|
+
throw new ResponseTooLargeError(op, limit);
|
|
278
|
+
}
|
|
279
|
+
if (!res.body) return new Uint8Array(0);
|
|
280
|
+
const reader = res.body.getReader();
|
|
281
|
+
const chunks = [];
|
|
282
|
+
let total = 0;
|
|
283
|
+
for (; ; ) {
|
|
284
|
+
const { done, value } = await reader.read();
|
|
285
|
+
if (done) break;
|
|
286
|
+
total += value.byteLength;
|
|
287
|
+
if (total > limit) {
|
|
288
|
+
await reader.cancel().catch(() => void 0);
|
|
289
|
+
throw new ResponseTooLargeError(op, limit);
|
|
290
|
+
}
|
|
291
|
+
chunks.push(value);
|
|
292
|
+
}
|
|
293
|
+
const out = new Uint8Array(total);
|
|
294
|
+
let off = 0;
|
|
295
|
+
for (const c of chunks) {
|
|
296
|
+
out.set(c, off);
|
|
297
|
+
off += c.byteLength;
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
261
301
|
function errorBody(text) {
|
|
262
302
|
try {
|
|
263
303
|
const v = JSON.parse(text);
|
|
@@ -294,7 +334,10 @@ var RP_ROUTES = {
|
|
|
294
334
|
};
|
|
295
335
|
var RP_ROUTES_UNDISCOVERED = {
|
|
296
336
|
tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
|
|
297
|
-
tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
|
|
337
|
+
tokenAuthVerify: { method: "POST", path: "/token/auth/verify" },
|
|
338
|
+
pdfTokenPrepare: { method: "POST", path: "/pdf/token/prepare" },
|
|
339
|
+
pdfTokenFinalize: { method: "POST", path: "/pdf/token/{signingID}/finalize" },
|
|
340
|
+
pdfTokenDocument: { method: "GET", path: "/pdf/token/{signingID}/document" }
|
|
298
341
|
};
|
|
299
342
|
function routePath(route, ...args) {
|
|
300
343
|
let i = 0;
|
|
@@ -642,6 +685,7 @@ var SessionApi = class {
|
|
|
642
685
|
|
|
643
686
|
// src/pdf.ts
|
|
644
687
|
var import_node_crypto2 = require("crypto");
|
|
688
|
+
var DEFAULT_PDF_MAX_BYTES = 64 << 20;
|
|
645
689
|
var DEFAULT_PDF_TIMEOUT_MS = 8e4;
|
|
646
690
|
function rec3(v) {
|
|
647
691
|
return v && typeof v === "object" ? v : {};
|
|
@@ -674,7 +718,7 @@ var PdfApi = class {
|
|
|
674
718
|
const form = new FormData();
|
|
675
719
|
form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
|
|
676
720
|
form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
|
|
677
|
-
const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.
|
|
721
|
+
const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
|
|
678
722
|
const receivedAt = Date.now();
|
|
679
723
|
const vc = rec3(r.vc).value;
|
|
680
724
|
const dl = rec3(r.deviceLink);
|
|
@@ -701,7 +745,75 @@ var PdfApi = class {
|
|
|
701
745
|
* дараа дахин татна (`retryable`).
|
|
702
746
|
*/
|
|
703
747
|
document(sessionId) {
|
|
704
|
-
return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.
|
|
748
|
+
return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.timeout, this.maxBytes);
|
|
749
|
+
}
|
|
750
|
+
get timeout() {
|
|
751
|
+
return this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS;
|
|
752
|
+
}
|
|
753
|
+
get maxBytes() {
|
|
754
|
+
return this.defaults.maxBytes ?? DEFAULT_PDF_MAX_BYTES;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
|
|
758
|
+
* (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
|
|
759
|
+
* зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
|
|
760
|
+
* LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
|
|
761
|
+
* TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
|
|
762
|
+
*/
|
|
763
|
+
async tokenPrepare(input) {
|
|
764
|
+
if (!input.pdf?.length || !input.certificate?.length || !input.signerEtsi?.trim() || !input.fileName?.trim()) {
|
|
765
|
+
throw new Error("eid: pdf token: pdf, certificate, signerEtsi, fileName \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
766
|
+
}
|
|
767
|
+
const request = {
|
|
768
|
+
relyingPartyUUID: this.creds.rpUUID,
|
|
769
|
+
relyingPartyName: this.creds.rpName,
|
|
770
|
+
signer: { etsi: input.signerEtsi.trim() },
|
|
771
|
+
certificate: Buffer.from(input.certificate).toString("base64"),
|
|
772
|
+
fileName: input.fileName
|
|
773
|
+
};
|
|
774
|
+
if (input.docID?.trim()) request.docID = input.docID.trim();
|
|
775
|
+
if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
|
|
776
|
+
const form = new FormData();
|
|
777
|
+
form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
|
|
778
|
+
form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
|
|
779
|
+
const raw = await this.http.postMultipart(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenPrepare), form, this.timeout);
|
|
780
|
+
const r = rec3(raw);
|
|
781
|
+
const hashToSign = new Uint8Array(Buffer.from(String(r.hashToSign ?? ""), "base64"));
|
|
782
|
+
if (!r.signingID || hashToSign.length !== 32) {
|
|
783
|
+
throw new ValidationError("pdf token prepare: signingID / hashToSign (32 \u0431\u0430\u0439\u0442) \u0431\u0443\u0440\u0443\u0443");
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
signingId: String(r.signingID),
|
|
787
|
+
docId: String(r.docID ?? ""),
|
|
788
|
+
hashToSign,
|
|
789
|
+
hashAlgorithm: String(r.hashAlgorithm ?? ""),
|
|
790
|
+
signatureAlgorithm: String(r.signatureAlgorithm ?? ""),
|
|
791
|
+
signatureFormat: String(r.signatureFormat ?? ""),
|
|
792
|
+
expiresAt: String(r.expiresAt ?? ""),
|
|
793
|
+
raw
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
|
|
798
|
+
* Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
|
|
799
|
+
* Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
|
|
800
|
+
* TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
|
|
801
|
+
*/
|
|
802
|
+
async tokenFinalize(signingId, signature) {
|
|
803
|
+
if (signature?.length !== 64) throw new Error("eid: pdf token: signature \u043D\u044C \u0442\u04AF\u04AF\u0445\u0438\u0439 r||s 64 \u0431\u0430\u0439\u0442 \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439 (P-256, DER \u0431\u0438\u0448)");
|
|
804
|
+
const r = await this.http.post(
|
|
805
|
+
routePath(RP_ROUTES_UNDISCOVERED.pdfTokenFinalize, signingId),
|
|
806
|
+
{ signature: Buffer.from(signature).toString("base64") },
|
|
807
|
+
this.timeout
|
|
808
|
+
);
|
|
809
|
+
return r;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
|
|
813
|
+
* хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
|
|
814
|
+
*/
|
|
815
|
+
tokenDocument(signingId) {
|
|
816
|
+
return this.http.getBytes(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenDocument, signingId), this.timeout, this.maxBytes);
|
|
705
817
|
}
|
|
706
818
|
/**
|
|
707
819
|
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
@@ -735,7 +847,7 @@ var OrganizationApi = class {
|
|
|
735
847
|
async getRepresentations(personEtsi) {
|
|
736
848
|
const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
|
|
737
849
|
const list = Array.isArray(raw.representations) ? raw.representations : [];
|
|
738
|
-
return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
|
|
850
|
+
return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list, raw };
|
|
739
851
|
}
|
|
740
852
|
};
|
|
741
853
|
|
|
@@ -887,6 +999,10 @@ function parseSubject(cert) {
|
|
|
887
999
|
return out;
|
|
888
1000
|
}
|
|
889
1001
|
function certExtension(der, oid) {
|
|
1002
|
+
return certExtensions(der).find((e) => e.oid === oid)?.value;
|
|
1003
|
+
}
|
|
1004
|
+
function certExtensions(der) {
|
|
1005
|
+
const out = [];
|
|
890
1006
|
const top = tlv(der, 0, der.length);
|
|
891
1007
|
const tbs = tlv(der, top.start, top.end);
|
|
892
1008
|
let f = tlv(der, tbs.start, tbs.end);
|
|
@@ -900,16 +1016,20 @@ function certExtension(der, oid) {
|
|
|
900
1016
|
const ext = tlv(der, ep, seq.end);
|
|
901
1017
|
const id = tlv(der, ext.start, ext.end);
|
|
902
1018
|
let v = tlv(der, id.end, ext.end);
|
|
903
|
-
|
|
1019
|
+
let critical = false;
|
|
1020
|
+
if (v.tag === 1) {
|
|
1021
|
+
critical = v.end > v.start && der[v.start] !== 0;
|
|
1022
|
+
v = tlv(der, v.end, ext.end);
|
|
1023
|
+
}
|
|
904
1024
|
if (v.tag !== 4) throw new ValidationError("extension: extnValue OCTET STRING \u0431\u0438\u0448");
|
|
905
|
-
|
|
1025
|
+
out.push({ oid: oidOf(der, id), critical, value: der.subarray(v.start, v.end) });
|
|
906
1026
|
ep = ext.end;
|
|
907
1027
|
}
|
|
908
|
-
return
|
|
1028
|
+
return out;
|
|
909
1029
|
}
|
|
910
1030
|
pos = t.end;
|
|
911
1031
|
}
|
|
912
|
-
return
|
|
1032
|
+
return out;
|
|
913
1033
|
}
|
|
914
1034
|
|
|
915
1035
|
// src/token.ts
|
|
@@ -1259,7 +1379,7 @@ var ResponseValidator = class {
|
|
|
1259
1379
|
);
|
|
1260
1380
|
}
|
|
1261
1381
|
} else {
|
|
1262
|
-
this.verifyChain(leaf);
|
|
1382
|
+
this.verifyChain(leaf, protocol === "ACSP_V2" ? EKU_CLIENT_AUTH : EKU_EMAIL_PROTECTION);
|
|
1263
1383
|
}
|
|
1264
1384
|
this.checkIssuer(leaf);
|
|
1265
1385
|
return leaf;
|
|
@@ -1303,21 +1423,40 @@ var ResponseValidator = class {
|
|
|
1303
1423
|
const need = rank[this.requiredLevel] ?? 2;
|
|
1304
1424
|
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}`);
|
|
1305
1425
|
}
|
|
1306
|
-
/**
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1426
|
+
/**
|
|
1427
|
+
* RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
|
|
1428
|
+
* замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
|
|
1429
|
+
* гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
|
|
1430
|
+
* завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
|
|
1431
|
+
* бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
|
|
1432
|
+
* fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
|
|
1433
|
+
* хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
|
|
1434
|
+
*/
|
|
1435
|
+
verifyChain(leaf, usage) {
|
|
1436
|
+
const now = Math.min(Math.max(Date.now(), Date.parse(leaf.validFrom)), Date.parse(leaf.validTo));
|
|
1437
|
+
const leafReason = criticalReason(leaf) ?? ekuReason(leaf, usage);
|
|
1438
|
+
if (leafReason) throw new ValidationError(`\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0433\u0438\u043D\u0436 \u0445\u04AF\u0447\u0438\u043D\u0433\u04AF\u0439: ${leafReason}`);
|
|
1439
|
+
let reason;
|
|
1440
|
+
const extend = (cur, cas) => {
|
|
1441
|
+
if (cas.length >= 8) return false;
|
|
1442
|
+
for (const [cand, isAnchor] of [
|
|
1443
|
+
...this.anchors.map((c) => [c, true]),
|
|
1444
|
+
...this.intermediates.map((c) => [c, false])
|
|
1445
|
+
]) {
|
|
1446
|
+
if (cas.includes(cand) || !signedBy(cur, cand)) continue;
|
|
1447
|
+
const bad = caReason(cand, cas.length, now, usage);
|
|
1448
|
+
if (bad) {
|
|
1449
|
+
reason ??= bad;
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
if (isAnchor || extend(cand, [...cas, cand])) return true;
|
|
1313
1453
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
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");
|
|
1454
|
+
return false;
|
|
1455
|
+
};
|
|
1456
|
+
if (extend(leaf, [])) return;
|
|
1457
|
+
throw new ValidationError(
|
|
1458
|
+
reason ? `\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0433\u0438\u043D\u0436 \u0445\u04AF\u0447\u0438\u043D\u0433\u04AF\u0439: ${reason}` : "\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"
|
|
1459
|
+
);
|
|
1321
1460
|
}
|
|
1322
1461
|
/** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
|
|
1323
1462
|
findIssuer(leaf) {
|
|
@@ -1528,6 +1667,79 @@ function authorityKeyId(cert) {
|
|
|
1528
1667
|
function isCa(cert) {
|
|
1529
1668
|
return cert.ca === true;
|
|
1530
1669
|
}
|
|
1670
|
+
var EKU_ANY = "2.5.29.37.0";
|
|
1671
|
+
var EKU_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2";
|
|
1672
|
+
var EKU_EMAIL_PROTECTION = "1.3.6.1.5.5.7.3.4";
|
|
1673
|
+
var OID_NAME_CONSTRAINTS = "2.5.29.30";
|
|
1674
|
+
var HANDLED_CRITICAL = /* @__PURE__ */ new Set([
|
|
1675
|
+
"2.5.29.14",
|
|
1676
|
+
// subjectKeyIdentifier
|
|
1677
|
+
"2.5.29.15",
|
|
1678
|
+
// keyUsage
|
|
1679
|
+
"2.5.29.17",
|
|
1680
|
+
// subjectAltName
|
|
1681
|
+
"2.5.29.19",
|
|
1682
|
+
// basicConstraints
|
|
1683
|
+
"2.5.29.30",
|
|
1684
|
+
// nameConstraints (CA-д — caReason fail-closed)
|
|
1685
|
+
"2.5.29.31",
|
|
1686
|
+
// cRLDistributionPoints
|
|
1687
|
+
"2.5.29.32",
|
|
1688
|
+
// certificatePolicies
|
|
1689
|
+
"2.5.29.35",
|
|
1690
|
+
// authorityKeyIdentifier
|
|
1691
|
+
"2.5.29.37",
|
|
1692
|
+
// extKeyUsage
|
|
1693
|
+
"1.3.6.1.5.5.7.1.1"
|
|
1694
|
+
// authorityInfoAccess
|
|
1695
|
+
]);
|
|
1696
|
+
function signedBy(child, cand) {
|
|
1697
|
+
if (child.issuer !== cand.subject) return false;
|
|
1698
|
+
const aki = authorityKeyId(child);
|
|
1699
|
+
const ski = subjectKeyId(cand);
|
|
1700
|
+
if (aki && ski && aki !== ski) return false;
|
|
1701
|
+
try {
|
|
1702
|
+
return child.verify(cand.publicKey);
|
|
1703
|
+
} catch {
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
function criticalReason(cert) {
|
|
1708
|
+
return certExtensions(cert.raw).some((e) => e.critical && !HANDLED_CRITICAL.has(e.oid)) ? "\u04AF\u043B \u0442\u0430\u043D\u0438\u0445 critical extension" : void 0;
|
|
1709
|
+
}
|
|
1710
|
+
function ekuReason(cert, usage) {
|
|
1711
|
+
const eku = extKeyUsage(cert);
|
|
1712
|
+
return eku.length && !eku.includes(usage) && !eku.includes(EKU_ANY) ? "\u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 ExtKeyUsage \u044D\u043D\u044D \u04AF\u0439\u043B\u0434\u044D\u043B\u0434 \u0442\u043E\u0445\u0438\u0440\u043E\u0445\u0433\u04AF\u0439 (\u0433\u0438\u043D\u0436\u0438\u0439\u043D EKU)" : void 0;
|
|
1713
|
+
}
|
|
1714
|
+
function caReason(ca, below, now, usage) {
|
|
1715
|
+
if (!isCa(ca)) return "\u0437\u0430\u0432\u0441\u0440\u044B\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D CA \u0431\u0438\u0448 \u044D\u0441\u0432\u044D\u043B keyCertSign \u044D\u0440\u0445\u0433\u04AF\u0439";
|
|
1716
|
+
if (certExtension(ca.raw, "2.5.29.15") && !(keyUsageBits(ca) & 4)) return "CA-\u0438\u0439\u043D KeyUsage-\u0434 keyCertSign \u0430\u043B\u0433\u0430";
|
|
1717
|
+
const from = Date.parse(ca.validFrom);
|
|
1718
|
+
const to = Date.parse(ca.validTo);
|
|
1719
|
+
if (Number.isNaN(from) || Number.isNaN(to) || now < from || now > to) {
|
|
1720
|
+
return "\u0433\u0438\u043D\u0436\u0438\u0439\u043D \u0433\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 \u044D\u0441\u0432\u044D\u043B \u0445\u04AF\u0447\u0438\u043D\u0442\u044D\u0439 \u0431\u043E\u043B\u043E\u043E\u0433\u04AF\u0439";
|
|
1721
|
+
}
|
|
1722
|
+
const pathLen = basicConstraintsPathLen(ca);
|
|
1723
|
+
if (pathLen !== void 0 && below > pathLen) return "pathLenConstraint \u0437\u04E9\u0440\u0447\u0441\u04E9\u043D (\u0437\u0430\u0432\u0441\u0440\u044B\u043D CA \u0445\u044D\u0442 \u043E\u043B\u043E\u043D)";
|
|
1724
|
+
if (certExtension(ca.raw, OID_NAME_CONSTRAINTS)) return "CA-\u0434 name constraints \u0431\u0430\u0439\u043D\u0430 (SDK \u04AF\u043D\u044D\u043B\u0434\u044D\u0433\u0433\u04AF\u0439 \u2014 fail-closed)";
|
|
1725
|
+
return criticalReason(ca) ?? ekuReason(ca, usage);
|
|
1726
|
+
}
|
|
1727
|
+
function basicConstraintsPathLen(cert) {
|
|
1728
|
+
const v = certExtension(cert.raw, "2.5.29.19");
|
|
1729
|
+
if (!v) return void 0;
|
|
1730
|
+
const seq = tlv(v, 0, v.length);
|
|
1731
|
+
for (let p = seq.start; p < seq.end; ) {
|
|
1732
|
+
const t = tlv(v, p, seq.end);
|
|
1733
|
+
if (t.tag === 2) {
|
|
1734
|
+
if (t.end - t.start > 4 || (v[t.start] & 128) !== 0) throw new ValidationError("basicConstraints: pathLen \u0431\u0443\u0440\u0443\u0443");
|
|
1735
|
+
let n = 0;
|
|
1736
|
+
for (let i = t.start; i < t.end; i++) n = n * 256 + v[i];
|
|
1737
|
+
return n;
|
|
1738
|
+
}
|
|
1739
|
+
p = t.end;
|
|
1740
|
+
}
|
|
1741
|
+
return void 0;
|
|
1742
|
+
}
|
|
1531
1743
|
|
|
1532
1744
|
// src/client.ts
|
|
1533
1745
|
var DEFAULT_BASE_URL = "https://rp.eidmongolia.mn";
|
|
@@ -1569,7 +1781,11 @@ var EidClient = class {
|
|
|
1569
1781
|
const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
|
|
1570
1782
|
this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
|
|
1571
1783
|
this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
|
|
1572
|
-
this.pdf = new PdfApi(http, cfg.credentials, {
|
|
1784
|
+
this.pdf = new PdfApi(http, cfg.credentials, {
|
|
1785
|
+
certificateLevel: level,
|
|
1786
|
+
timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
|
|
1787
|
+
maxBytes: cfg.pdfMaxBytes && cfg.pdfMaxBytes > 0 ? cfg.pdfMaxBytes : DEFAULT_PDF_MAX_BYTES
|
|
1788
|
+
});
|
|
1573
1789
|
this.organization = new OrganizationApi(http);
|
|
1574
1790
|
this.person = new PersonApi(http);
|
|
1575
1791
|
this.token = new TokenApi(http, cfg.credentials);
|
|
@@ -1973,6 +2189,7 @@ function callbackParams(input) {
|
|
|
1973
2189
|
AuthenticationError,
|
|
1974
2190
|
CITIZEN_ISSUING_CA_SKI,
|
|
1975
2191
|
DEFAULT_BASE_URL,
|
|
2192
|
+
DEFAULT_PDF_MAX_BYTES,
|
|
1976
2193
|
DEFAULT_PDF_TIMEOUT_MS,
|
|
1977
2194
|
DeviceLinkError,
|
|
1978
2195
|
EID_ACR_ADVANCED,
|
|
@@ -1990,6 +2207,7 @@ function callbackParams(input) {
|
|
|
1990
2207
|
PdfApi,
|
|
1991
2208
|
PersonApi,
|
|
1992
2209
|
RETRYABLE_ERROR_CODES,
|
|
2210
|
+
ResponseTooLargeError,
|
|
1993
2211
|
ResponseValidator,
|
|
1994
2212
|
SessionApi,
|
|
1995
2213
|
SessionFailedError,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-
|
|
2
|
-
export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as
|
|
1
|
+
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-BVR6gtrx.cjs';
|
|
2
|
+
export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as ResponseTooLargeError, u as RetryableErrorCode, v as SessionFailedError, w as SessionState, x as SignatureAlgorithmParameters, y as SignatureProtocol, T as TokenCertSessionBlock, z as TokenCertStatus, V as ValidationError, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR, K as parseRetryAfter } from './browser-BVR6gtrx.cjs';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -25,8 +25,8 @@ declare class Http {
|
|
|
25
25
|
get(path: string, timeoutMs?: number): Promise<unknown>;
|
|
26
26
|
/** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
|
|
27
27
|
postMultipart(path: string, form: FormData, timeoutMs?: number): Promise<unknown>;
|
|
28
|
-
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
|
|
29
|
-
getBytes(path: string, timeoutMs?: number): Promise<Uint8Array>;
|
|
28
|
+
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
|
|
29
|
+
getBytes(path: string, timeoutMs?: number, maxBytes?: number): Promise<Uint8Array>;
|
|
30
30
|
private json;
|
|
31
31
|
private send;
|
|
32
32
|
}
|
|
@@ -153,7 +153,11 @@ interface PdfDefaults {
|
|
|
153
153
|
certificateLevel: CertificateLevel;
|
|
154
154
|
/** prepare / document дуудлагын хугацаа (мс); анхдагч {@link DEFAULT_PDF_TIMEOUT_MS}. */
|
|
155
155
|
timeoutMs?: number;
|
|
156
|
+
/** document / tokenDocument-ийн санах ойд унших дээд хэмжээ (байт); анхдагч {@link DEFAULT_PDF_MAX_BYTES}. */
|
|
157
|
+
maxBytes?: number;
|
|
156
158
|
}
|
|
159
|
+
/** Гарын үсэгтэй PDF татахад санах ойд уншиж болох анхдагч дээд хэмжээ (64 MiB) — `ClientConfig.pdfMaxBytes`. */
|
|
160
|
+
declare const DEFAULT_PDF_MAX_BYTES: number;
|
|
157
161
|
/**
|
|
158
162
|
* PAdES prepare / баримт татах нэг дуудлагын анхдагч хугацаа (мс). CA prepare нь ≤ 10 MiB уншиж, өмнөх гарын үсэг +
|
|
159
163
|
* LTV өгөгдөл шалгаад push эхлүүлдэг; баримт татахад угсралтыг ≤ 30с хүлээдэг (PADES_SIGNING §3.4). 80с нь хоёуланд
|
|
@@ -209,6 +213,35 @@ interface PdfPrepareSession {
|
|
|
209
213
|
/** SDK хариу хүлээн авсан мөч (ms epoch) — QR-ийн `receivedAt` ({@link buildDeviceLink}). */
|
|
210
214
|
receivedAt: number;
|
|
211
215
|
}
|
|
216
|
+
/** USB токены PAdES — POST /pdf/token/prepare-ийн оролт. */
|
|
217
|
+
interface PdfTokenPrepareInput {
|
|
218
|
+
/** Эх PDF (≤ 10 223 616 байт). */
|
|
219
|
+
pdf: Uint8Array;
|
|
220
|
+
/** Баримтын нэр (≤ 120 тэмдэгт, заавал). */
|
|
221
|
+
fileName: string;
|
|
222
|
+
/** Иргэний ETSI (`PNOMN-…`) — токены гэрчилгээний SERIALNUMBER-тэй таарна. */
|
|
223
|
+
signerEtsi: string;
|
|
224
|
+
/** Токены SIGN гэрчилгээ (DER): eID Mongolia Issuing CA-аас, ACTIVE токенд холбогдсон. */
|
|
225
|
+
certificate: Uint8Array;
|
|
226
|
+
/** Олон гарын үсэгтэй баримтын docID (заавал биш, UUID). */
|
|
227
|
+
docID?: string;
|
|
228
|
+
/** Байгууллагын токенд ЗААВАЛ тухайн байгууллага (`NTRMN-…`); хувийн токенд хоосон. Зөрвөл 403 TOKEN_ORG_MISMATCH. */
|
|
229
|
+
onBehalfOf?: string;
|
|
230
|
+
}
|
|
231
|
+
/** POST /pdf/token/prepare-ийн хариу. */
|
|
232
|
+
interface PdfTokenPrepareSession {
|
|
233
|
+
signingId: string;
|
|
234
|
+
docId: string;
|
|
235
|
+
/** Токеноор зурах 32 байт (SHA-256 DTBS): CKM_ECDSA (prehashed) → түүхий r||s (64 байт). */
|
|
236
|
+
hashToSign: Uint8Array;
|
|
237
|
+
hashAlgorithm: string;
|
|
238
|
+
signatureAlgorithm: string;
|
|
239
|
+
/** "RAW_RS". */
|
|
240
|
+
signatureFormat: string;
|
|
241
|
+
/** tokenFinalize-ийн эцсийн хугацаа (prepare-ээс 10 мин), ISO-8601. */
|
|
242
|
+
expiresAt: string;
|
|
243
|
+
raw: unknown;
|
|
244
|
+
}
|
|
212
245
|
declare class PdfApi {
|
|
213
246
|
private readonly http;
|
|
214
247
|
private readonly creds;
|
|
@@ -226,6 +259,28 @@ declare class PdfApi {
|
|
|
226
259
|
* дараа дахин татна (`retryable`).
|
|
227
260
|
*/
|
|
228
261
|
document(sessionId: string): Promise<Uint8Array>;
|
|
262
|
+
private get timeout();
|
|
263
|
+
private get maxBytes();
|
|
264
|
+
/**
|
|
265
|
+
* USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
|
|
266
|
+
* (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
|
|
267
|
+
* зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
|
|
268
|
+
* LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
|
|
269
|
+
* TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
|
|
270
|
+
*/
|
|
271
|
+
tokenPrepare(input: PdfTokenPrepareInput): Promise<PdfTokenPrepareSession>;
|
|
272
|
+
/**
|
|
273
|
+
* POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
|
|
274
|
+
* Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
|
|
275
|
+
* Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
|
|
276
|
+
* TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
|
|
277
|
+
*/
|
|
278
|
+
tokenFinalize(signingId: string, signature: Uint8Array): Promise<PdfSessionBlock>;
|
|
279
|
+
/**
|
|
280
|
+
* GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
|
|
281
|
+
* хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
|
|
282
|
+
*/
|
|
283
|
+
tokenDocument(signingId: string): Promise<Uint8Array>;
|
|
229
284
|
/**
|
|
230
285
|
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
231
286
|
* `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
|
|
@@ -696,7 +751,15 @@ declare class ResponseValidator {
|
|
|
696
751
|
private checkIssuer;
|
|
697
752
|
private checkValidity;
|
|
698
753
|
private checkLevel;
|
|
699
|
-
/**
|
|
754
|
+
/**
|
|
755
|
+
* RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
|
|
756
|
+
* замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
|
|
757
|
+
* гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
|
|
758
|
+
* завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
|
|
759
|
+
* бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
|
|
760
|
+
* fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
|
|
761
|
+
* хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
|
|
762
|
+
*/
|
|
700
763
|
private verifyChain;
|
|
701
764
|
/** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
|
|
702
765
|
private findIssuer;
|
|
@@ -734,6 +797,11 @@ interface ClientConfig {
|
|
|
734
797
|
timeoutMs?: number;
|
|
735
798
|
/** PAdES prepare / баримт татах дуудлагын хугацаа (мс). Анхдагч 80000 ({@link DEFAULT_PDF_TIMEOUT_MS}). */
|
|
736
799
|
pdfTimeoutMs?: number;
|
|
800
|
+
/**
|
|
801
|
+
* `pdf.document` / `pdf.tokenDocument`-ийн санах ойд унших дээд хэмжээ (байт). Анхдагч 64 MiB
|
|
802
|
+
* ({@link DEFAULT_PDF_MAX_BYTES}); хэтэрвэл `ResponseTooLargeError`.
|
|
803
|
+
*/
|
|
804
|
+
pdfMaxBytes?: number;
|
|
737
805
|
/** undici Dispatcher (mTLS client cert). */
|
|
738
806
|
dispatcher?: unknown;
|
|
739
807
|
/** fetch орлуулагч (тест). */
|
|
@@ -989,4 +1057,4 @@ declare class EidOidcClient {
|
|
|
989
1057
|
/** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
|
|
990
1058
|
declare function pkceChallenge(codeVerifier: string): Promise<string>;
|
|
991
1059
|
|
|
992
|
-
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
|
|
1060
|
+
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_MAX_BYTES, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PdfTokenPrepareInput, type PdfTokenPrepareSession, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-
|
|
2
|
-
export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as
|
|
1
|
+
import { R as RpCredentials, C as CertificateLevel, I as Interaction, S as SignatureAlgorithm, H as HashAlgorithm, N as NotificationSession, D as DeviceLinkSession, a as SessionResult, P as PersonBlock, b as PdfSessionBlock, c as RepresentationsResponse, A as AcspContext, F as FlowType, E as EidError } from './browser-BVR6gtrx.js';
|
|
2
|
+
export { d as ApiError, e as AuthenticationError, f as DeviceLinkError, g as DeviceLinkInput, h as EndResult, i as ForbiddenError, j as HttpErrorInit, k as NavigatorLike, l as NetworkError, O as ON_BEHALF_ERROR_CODES, m as OnBehalfErrorCode, n as OpenOrShowQROptions, o as OrgResult, p as PdfDocumentStatus, q as PdfSigner, r as RETRYABLE_ERROR_CODES, s as Representation, t as ResponseTooLargeError, u as RetryableErrorCode, v as SessionFailedError, w as SessionState, x as SignatureAlgorithmParameters, y as SignatureProtocol, T as TokenCertSessionBlock, z as TokenCertStatus, V as ValidationError, B as deviceLink, G as isMobileUserAgent, J as openOrShowQR, K as parseRetryAfter } from './browser-BVR6gtrx.js';
|
|
3
3
|
import { X509Certificate, KeyObject } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
interface HttpConfig {
|
|
@@ -25,8 +25,8 @@ declare class Http {
|
|
|
25
25
|
get(path: string, timeoutMs?: number): Promise<unknown>;
|
|
26
26
|
/** multipart/form-data POST (Content-Type-ийг boundary-тай нь fetch тавина). JSON хариу. */
|
|
27
27
|
postMultipart(path: string, form: FormData, timeoutMs?: number): Promise<unknown>;
|
|
28
|
-
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
|
|
29
|
-
getBytes(path: string, timeoutMs?: number): Promise<Uint8Array>;
|
|
28
|
+
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
|
|
29
|
+
getBytes(path: string, timeoutMs?: number, maxBytes?: number): Promise<Uint8Array>;
|
|
30
30
|
private json;
|
|
31
31
|
private send;
|
|
32
32
|
}
|
|
@@ -153,7 +153,11 @@ interface PdfDefaults {
|
|
|
153
153
|
certificateLevel: CertificateLevel;
|
|
154
154
|
/** prepare / document дуудлагын хугацаа (мс); анхдагч {@link DEFAULT_PDF_TIMEOUT_MS}. */
|
|
155
155
|
timeoutMs?: number;
|
|
156
|
+
/** document / tokenDocument-ийн санах ойд унших дээд хэмжээ (байт); анхдагч {@link DEFAULT_PDF_MAX_BYTES}. */
|
|
157
|
+
maxBytes?: number;
|
|
156
158
|
}
|
|
159
|
+
/** Гарын үсэгтэй PDF татахад санах ойд уншиж болох анхдагч дээд хэмжээ (64 MiB) — `ClientConfig.pdfMaxBytes`. */
|
|
160
|
+
declare const DEFAULT_PDF_MAX_BYTES: number;
|
|
157
161
|
/**
|
|
158
162
|
* PAdES prepare / баримт татах нэг дуудлагын анхдагч хугацаа (мс). CA prepare нь ≤ 10 MiB уншиж, өмнөх гарын үсэг +
|
|
159
163
|
* LTV өгөгдөл шалгаад push эхлүүлдэг; баримт татахад угсралтыг ≤ 30с хүлээдэг (PADES_SIGNING §3.4). 80с нь хоёуланд
|
|
@@ -209,6 +213,35 @@ interface PdfPrepareSession {
|
|
|
209
213
|
/** SDK хариу хүлээн авсан мөч (ms epoch) — QR-ийн `receivedAt` ({@link buildDeviceLink}). */
|
|
210
214
|
receivedAt: number;
|
|
211
215
|
}
|
|
216
|
+
/** USB токены PAdES — POST /pdf/token/prepare-ийн оролт. */
|
|
217
|
+
interface PdfTokenPrepareInput {
|
|
218
|
+
/** Эх PDF (≤ 10 223 616 байт). */
|
|
219
|
+
pdf: Uint8Array;
|
|
220
|
+
/** Баримтын нэр (≤ 120 тэмдэгт, заавал). */
|
|
221
|
+
fileName: string;
|
|
222
|
+
/** Иргэний ETSI (`PNOMN-…`) — токены гэрчилгээний SERIALNUMBER-тэй таарна. */
|
|
223
|
+
signerEtsi: string;
|
|
224
|
+
/** Токены SIGN гэрчилгээ (DER): eID Mongolia Issuing CA-аас, ACTIVE токенд холбогдсон. */
|
|
225
|
+
certificate: Uint8Array;
|
|
226
|
+
/** Олон гарын үсэгтэй баримтын docID (заавал биш, UUID). */
|
|
227
|
+
docID?: string;
|
|
228
|
+
/** Байгууллагын токенд ЗААВАЛ тухайн байгууллага (`NTRMN-…`); хувийн токенд хоосон. Зөрвөл 403 TOKEN_ORG_MISMATCH. */
|
|
229
|
+
onBehalfOf?: string;
|
|
230
|
+
}
|
|
231
|
+
/** POST /pdf/token/prepare-ийн хариу. */
|
|
232
|
+
interface PdfTokenPrepareSession {
|
|
233
|
+
signingId: string;
|
|
234
|
+
docId: string;
|
|
235
|
+
/** Токеноор зурах 32 байт (SHA-256 DTBS): CKM_ECDSA (prehashed) → түүхий r||s (64 байт). */
|
|
236
|
+
hashToSign: Uint8Array;
|
|
237
|
+
hashAlgorithm: string;
|
|
238
|
+
signatureAlgorithm: string;
|
|
239
|
+
/** "RAW_RS". */
|
|
240
|
+
signatureFormat: string;
|
|
241
|
+
/** tokenFinalize-ийн эцсийн хугацаа (prepare-ээс 10 мин), ISO-8601. */
|
|
242
|
+
expiresAt: string;
|
|
243
|
+
raw: unknown;
|
|
244
|
+
}
|
|
212
245
|
declare class PdfApi {
|
|
213
246
|
private readonly http;
|
|
214
247
|
private readonly creds;
|
|
@@ -226,6 +259,28 @@ declare class PdfApi {
|
|
|
226
259
|
* дараа дахин татна (`retryable`).
|
|
227
260
|
*/
|
|
228
261
|
document(sessionId: string): Promise<Uint8Array>;
|
|
262
|
+
private get timeout();
|
|
263
|
+
private get maxBytes();
|
|
264
|
+
/**
|
|
265
|
+
* USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
|
|
266
|
+
* (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
|
|
267
|
+
* зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
|
|
268
|
+
* LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
|
|
269
|
+
* TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
|
|
270
|
+
*/
|
|
271
|
+
tokenPrepare(input: PdfTokenPrepareInput): Promise<PdfTokenPrepareSession>;
|
|
272
|
+
/**
|
|
273
|
+
* POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
|
|
274
|
+
* Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
|
|
275
|
+
* Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
|
|
276
|
+
* TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
|
|
277
|
+
*/
|
|
278
|
+
tokenFinalize(signingId: string, signature: Uint8Array): Promise<PdfSessionBlock>;
|
|
279
|
+
/**
|
|
280
|
+
* GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
|
|
281
|
+
* хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
|
|
282
|
+
*/
|
|
283
|
+
tokenDocument(signingId: string): Promise<Uint8Array>;
|
|
229
284
|
/**
|
|
230
285
|
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
231
286
|
* `expected` — poll-ийн `result.pdf` блок (`documentStatus` READY + `outSha256` заавал) эсвэл SHA-256 hex.
|
|
@@ -696,7 +751,15 @@ declare class ResponseValidator {
|
|
|
696
751
|
private checkIssuer;
|
|
697
752
|
private checkValidity;
|
|
698
753
|
private checkLevel;
|
|
699
|
-
/**
|
|
754
|
+
/**
|
|
755
|
+
* RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
|
|
756
|
+
* замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
|
|
757
|
+
* гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
|
|
758
|
+
* завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
|
|
759
|
+
* бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
|
|
760
|
+
* fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
|
|
761
|
+
* хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
|
|
762
|
+
*/
|
|
700
763
|
private verifyChain;
|
|
701
764
|
/** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
|
|
702
765
|
private findIssuer;
|
|
@@ -734,6 +797,11 @@ interface ClientConfig {
|
|
|
734
797
|
timeoutMs?: number;
|
|
735
798
|
/** PAdES prepare / баримт татах дуудлагын хугацаа (мс). Анхдагч 80000 ({@link DEFAULT_PDF_TIMEOUT_MS}). */
|
|
736
799
|
pdfTimeoutMs?: number;
|
|
800
|
+
/**
|
|
801
|
+
* `pdf.document` / `pdf.tokenDocument`-ийн санах ойд унших дээд хэмжээ (байт). Анхдагч 64 MiB
|
|
802
|
+
* ({@link DEFAULT_PDF_MAX_BYTES}); хэтэрвэл `ResponseTooLargeError`.
|
|
803
|
+
*/
|
|
804
|
+
pdfMaxBytes?: number;
|
|
737
805
|
/** undici Dispatcher (mTLS client cert). */
|
|
738
806
|
dispatcher?: unknown;
|
|
739
807
|
/** fetch орлуулагч (тест). */
|
|
@@ -989,4 +1057,4 @@ declare class EidOidcClient {
|
|
|
989
1057
|
/** PKCE S256: `BASE64URL(SHA-256(ASCII(code_verifier)))` (RFC 7636 §4.2). */
|
|
990
1058
|
declare function pkceChallenge(codeVerifier: string): Promise<string>;
|
|
991
1059
|
|
|
992
|
-
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
|
|
1060
|
+
export { AcspContext, type AcspV2Params, type ActivityCounts, type ActivityItem, type ActivityQuery, AuthApi, type AuthDefaults, type AuthOptions, type BuildDeviceLinkInput, CITIZEN_ISSUING_CA_SKI, type CertCounts, type CertSubject, CertificateLevel, type ClientConfig, type CreateAuthorizationOptions, DEFAULT_BASE_URL, DEFAULT_PDF_MAX_BYTES, DEFAULT_PDF_TIMEOUT_MS, DeviceLinkSession, type DeviceLinkSessionType, type DeviceLinkType, EID_ACR_ADVANCED, EID_ACR_QUALIFIED, EID_OIDC_ISSUER, type EidAuthorization, type EidCallbackChecks, type EidCallbackResult, EidClient, EidError, type EidIdTokenClaims, EidOidcClient, type EidOidcClientConfig, type EidOidcDiscovery, EidOidcError, type EidOidcErrorCode, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, OrganizationApi, PdfApi, type PdfDefaults, type PdfPrepareInput, type PdfPrepareSession, PdfSessionBlock, type PdfSignerRef, type PdfTokenPrepareInput, type PdfTokenPrepareSession, type PersonActivity, PersonApi, PersonBlock, type PersonCertificate, type PersonCertificates, type PersonChild, type PersonChildren, type PersonDevice, type PersonDevices, type PersonSummary, type QrDeviceLinkTickerOptions, RepresentationsResponse, ResponseValidator, type RevocationChecker, type RevocationOutcome, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, TokenApi, type TokenAuthChallenge, type TokenAuthResult, type TokenCertInput, type TokenCertItem, type TokenCertResult, type TokenCertStart, type TokenIdentity, type TokenLimit, type TokenOrganization, type TrustConfig, type ValidateAuthOptions, type ValidateSignOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, buildDeviceLink, interactionsToBase64, parseSessionResult, parseSubject, personMatchesCertificate, pkceChallenge, qrDeviceLinkTicker, randomChallenge, resolveBaseUrl, sha256Base64, tokenAuthDigest, tokenLimitOf, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature, verifyTokenCertificates };
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
NetworkError,
|
|
8
8
|
ON_BEHALF_ERROR_CODES,
|
|
9
9
|
RETRYABLE_ERROR_CODES,
|
|
10
|
+
ResponseTooLargeError,
|
|
10
11
|
SessionFailedError,
|
|
11
12
|
ValidationError,
|
|
12
13
|
deviceLink,
|
|
@@ -14,10 +15,11 @@ import {
|
|
|
14
15
|
maskPath,
|
|
15
16
|
openOrShowQR,
|
|
16
17
|
parseRetryAfter
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-QPJDZLHR.js";
|
|
18
19
|
|
|
19
20
|
// src/http.ts
|
|
20
21
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
22
|
+
var MAX_JSON_BYTES = 4 << 20;
|
|
21
23
|
var Http = class {
|
|
22
24
|
constructor(cfg) {
|
|
23
25
|
this.cfg = cfg;
|
|
@@ -33,14 +35,14 @@ var Http = class {
|
|
|
33
35
|
postMultipart(path, form, timeoutMs) {
|
|
34
36
|
return this.json("POST", path, form, void 0, timeoutMs);
|
|
35
37
|
}
|
|
36
|
-
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF). */
|
|
37
|
-
async getBytes(path, timeoutMs) {
|
|
38
|
+
/** GET — хариуг байтаар (ж: гарын үсэгтэй PDF), `maxBytes` хүртэл; хэтэрвэл {@link ResponseTooLargeError}. */
|
|
39
|
+
async getBytes(path, timeoutMs, maxBytes = 64 << 20) {
|
|
38
40
|
const res = await this.send("GET", path, void 0, void 0, timeoutMs);
|
|
39
|
-
return
|
|
41
|
+
return readLimited(res, maxBytes, `GET ${maskPath(path)}`);
|
|
40
42
|
}
|
|
41
43
|
async json(method, path, body, contentType, timeoutMs) {
|
|
42
44
|
const res = await this.send(method, path, body, contentType, timeoutMs);
|
|
43
|
-
const text = await res
|
|
45
|
+
const text = new TextDecoder().decode(await readLimited(res, MAX_JSON_BYTES, `${method} ${maskPath(path)}`));
|
|
44
46
|
return text ? JSON.parse(text) : {};
|
|
45
47
|
}
|
|
46
48
|
async send(method, path, body, contentType, timeoutMs) {
|
|
@@ -85,6 +87,34 @@ var Http = class {
|
|
|
85
87
|
throw new ApiError(res.status, text, code, { headers, retryAfterMs });
|
|
86
88
|
}
|
|
87
89
|
};
|
|
90
|
+
async function readLimited(res, limit, op) {
|
|
91
|
+
const declared = Number(res.headers.get("content-length") ?? NaN);
|
|
92
|
+
if (declared > limit) {
|
|
93
|
+
await res.body?.cancel().catch(() => void 0);
|
|
94
|
+
throw new ResponseTooLargeError(op, limit);
|
|
95
|
+
}
|
|
96
|
+
if (!res.body) return new Uint8Array(0);
|
|
97
|
+
const reader = res.body.getReader();
|
|
98
|
+
const chunks = [];
|
|
99
|
+
let total = 0;
|
|
100
|
+
for (; ; ) {
|
|
101
|
+
const { done, value } = await reader.read();
|
|
102
|
+
if (done) break;
|
|
103
|
+
total += value.byteLength;
|
|
104
|
+
if (total > limit) {
|
|
105
|
+
await reader.cancel().catch(() => void 0);
|
|
106
|
+
throw new ResponseTooLargeError(op, limit);
|
|
107
|
+
}
|
|
108
|
+
chunks.push(value);
|
|
109
|
+
}
|
|
110
|
+
const out = new Uint8Array(total);
|
|
111
|
+
let off = 0;
|
|
112
|
+
for (const c of chunks) {
|
|
113
|
+
out.set(c, off);
|
|
114
|
+
off += c.byteLength;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
88
118
|
function errorBody(text) {
|
|
89
119
|
try {
|
|
90
120
|
const v = JSON.parse(text);
|
|
@@ -121,7 +151,10 @@ var RP_ROUTES = {
|
|
|
121
151
|
};
|
|
122
152
|
var RP_ROUTES_UNDISCOVERED = {
|
|
123
153
|
tokenAuthChallenge: { method: "POST", path: "/token/auth/challenge" },
|
|
124
|
-
tokenAuthVerify: { method: "POST", path: "/token/auth/verify" }
|
|
154
|
+
tokenAuthVerify: { method: "POST", path: "/token/auth/verify" },
|
|
155
|
+
pdfTokenPrepare: { method: "POST", path: "/pdf/token/prepare" },
|
|
156
|
+
pdfTokenFinalize: { method: "POST", path: "/pdf/token/{signingID}/finalize" },
|
|
157
|
+
pdfTokenDocument: { method: "GET", path: "/pdf/token/{signingID}/document" }
|
|
125
158
|
};
|
|
126
159
|
function routePath(route, ...args) {
|
|
127
160
|
let i = 0;
|
|
@@ -469,6 +502,7 @@ var SessionApi = class {
|
|
|
469
502
|
|
|
470
503
|
// src/pdf.ts
|
|
471
504
|
import { createHash as createHash2 } from "crypto";
|
|
505
|
+
var DEFAULT_PDF_MAX_BYTES = 64 << 20;
|
|
472
506
|
var DEFAULT_PDF_TIMEOUT_MS = 8e4;
|
|
473
507
|
function rec3(v) {
|
|
474
508
|
return v && typeof v === "object" ? v : {};
|
|
@@ -501,7 +535,7 @@ var PdfApi = class {
|
|
|
501
535
|
const form = new FormData();
|
|
502
536
|
form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
|
|
503
537
|
form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
|
|
504
|
-
const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.
|
|
538
|
+
const r = rec3(await this.http.postMultipart(routePath(RP_ROUTES.pdfPrepare), form, this.timeout));
|
|
505
539
|
const receivedAt = Date.now();
|
|
506
540
|
const vc = rec3(r.vc).value;
|
|
507
541
|
const dl = rec3(r.deviceLink);
|
|
@@ -528,7 +562,75 @@ var PdfApi = class {
|
|
|
528
562
|
* дараа дахин татна (`retryable`).
|
|
529
563
|
*/
|
|
530
564
|
document(sessionId) {
|
|
531
|
-
return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.
|
|
565
|
+
return this.http.getBytes(routePath(RP_ROUTES.pdfDocument, sessionId), this.timeout, this.maxBytes);
|
|
566
|
+
}
|
|
567
|
+
get timeout() {
|
|
568
|
+
return this.defaults.timeoutMs ?? DEFAULT_PDF_TIMEOUT_MS;
|
|
569
|
+
}
|
|
570
|
+
get maxBytes() {
|
|
571
|
+
return this.defaults.maxBytes ?? DEFAULT_PDF_MAX_BYTES;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* USB токены PAdES (CA docs/PADES_SIGNING.md §16.5, TOKEN_CERT.md) — session / утас оролцохгүй. POST /pdf/token/prepare
|
|
575
|
+
* (multipart: `request` JSON + `pdf`), SIGN эрх. Дараа нь токеноор `hashToSign`-ийг CKM_ECDSA (prehashed, P-256)-аар
|
|
576
|
+
* зурж {@link tokenFinalize}. Түр алдаа: `ApiError` 429 PDF_PREPARE_BUSY / RATE_LIMITED, 503 TSA_UNAVAILABLE /
|
|
577
|
+
* LTV_UNAVAILABLE / STATE_UNAVAILABLE (`retryable`). Гэрчилгээ токенд холбогдоогүй / цуцлагдсан → 409
|
|
578
|
+
* TOKEN_CERT_INVALID. Зам нь CA-д бий ч `/.well-known/eid`-д хараахан алга (гэрээний тест WARN).
|
|
579
|
+
*/
|
|
580
|
+
async tokenPrepare(input) {
|
|
581
|
+
if (!input.pdf?.length || !input.certificate?.length || !input.signerEtsi?.trim() || !input.fileName?.trim()) {
|
|
582
|
+
throw new Error("eid: pdf token: pdf, certificate, signerEtsi, fileName \u0448\u0430\u0430\u0440\u0434\u043B\u0430\u0433\u0430\u0442\u0430\u0439");
|
|
583
|
+
}
|
|
584
|
+
const request = {
|
|
585
|
+
relyingPartyUUID: this.creds.rpUUID,
|
|
586
|
+
relyingPartyName: this.creds.rpName,
|
|
587
|
+
signer: { etsi: input.signerEtsi.trim() },
|
|
588
|
+
certificate: Buffer.from(input.certificate).toString("base64"),
|
|
589
|
+
fileName: input.fileName
|
|
590
|
+
};
|
|
591
|
+
if (input.docID?.trim()) request.docID = input.docID.trim();
|
|
592
|
+
if (input.onBehalfOf?.trim()) request.onBehalfOf = input.onBehalfOf.trim();
|
|
593
|
+
const form = new FormData();
|
|
594
|
+
form.append("request", new Blob([JSON.stringify(request)], { type: "application/json" }));
|
|
595
|
+
form.append("pdf", new Blob([input.pdf], { type: "application/pdf" }), input.fileName);
|
|
596
|
+
const raw = await this.http.postMultipart(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenPrepare), form, this.timeout);
|
|
597
|
+
const r = rec3(raw);
|
|
598
|
+
const hashToSign = new Uint8Array(Buffer.from(String(r.hashToSign ?? ""), "base64"));
|
|
599
|
+
if (!r.signingID || hashToSign.length !== 32) {
|
|
600
|
+
throw new ValidationError("pdf token prepare: signingID / hashToSign (32 \u0431\u0430\u0439\u0442) \u0431\u0443\u0440\u0443\u0443");
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
signingId: String(r.signingID),
|
|
604
|
+
docId: String(r.docID ?? ""),
|
|
605
|
+
hashToSign,
|
|
606
|
+
hashAlgorithm: String(r.hashAlgorithm ?? ""),
|
|
607
|
+
signatureAlgorithm: String(r.signatureAlgorithm ?? ""),
|
|
608
|
+
signatureFormat: String(r.signatureFormat ?? ""),
|
|
609
|
+
expiresAt: String(r.expiresAt ?? ""),
|
|
610
|
+
raw
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* POST /pdf/token/{signingID}/finalize `{signature: base64 түүхий r||s}` — `signature` 64 байт (P-256 r||s, DER БИШ).
|
|
615
|
+
* Амжвал READY баримтын блок (`outSha256` — {@link tokenDocument}-оор татсан файлтай тулга). READY-ээс хойш идэмпотент.
|
|
616
|
+
* Алдаа: 400 SIGNATURE_INVALID (3 удаа → FAILED), 409 DOCUMENT_FAILED, 410 DOCUMENT_EXPIRED, 503 DOCUMENT_PENDING /
|
|
617
|
+
* TSA_UNAVAILABLE / LTV_UNAVAILABLE — ижил гарын үсгийг `retryAfterMs`-ийн дараа дахин илгээнэ.
|
|
618
|
+
*/
|
|
619
|
+
async tokenFinalize(signingId, signature) {
|
|
620
|
+
if (signature?.length !== 64) throw new Error("eid: pdf token: signature \u043D\u044C \u0442\u04AF\u04AF\u0445\u0438\u0439 r||s 64 \u0431\u0430\u0439\u0442 \u0431\u0430\u0439\u0445 \u0451\u0441\u0442\u043E\u0439 (P-256, DER \u0431\u0438\u0448)");
|
|
621
|
+
const r = await this.http.post(
|
|
622
|
+
routePath(RP_ROUTES_UNDISCOVERED.pdfTokenFinalize, signingId),
|
|
623
|
+
{ signature: Buffer.from(signature).toString("base64") },
|
|
624
|
+
this.timeout
|
|
625
|
+
);
|
|
626
|
+
return r;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* GET /pdf/token/{signingID}/document — гарын үсэгтэй PDF (зөвхөн үүсгэсэн RP, expiresAt хүртэл), `pdfMaxBytes`
|
|
630
|
+
* хүртэл. SHA-256-ийг {@link tokenFinalize}-ийн `outSha256`-тай заавал тулга.
|
|
631
|
+
*/
|
|
632
|
+
tokenDocument(signingId) {
|
|
633
|
+
return this.http.getBytes(routePath(RP_ROUTES_UNDISCOVERED.pdfTokenDocument, signingId), this.timeout, this.maxBytes);
|
|
532
634
|
}
|
|
533
635
|
/**
|
|
534
636
|
* {@link document} + тулгалт: татсан байтын SHA-256 нь session poll-ийн `pdf.outSha256`-тай тэнцүү байх ёстой.
|
|
@@ -562,7 +664,7 @@ var OrganizationApi = class {
|
|
|
562
664
|
async getRepresentations(personEtsi) {
|
|
563
665
|
const raw = await this.http.get(routePath(RP_ROUTES.orgRepresentations, personEtsi));
|
|
564
666
|
const list = Array.isArray(raw.representations) ? raw.representations : [];
|
|
565
|
-
return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list };
|
|
667
|
+
return { personEtsi: String(raw.personEtsi ?? personEtsi), representations: list, raw };
|
|
566
668
|
}
|
|
567
669
|
};
|
|
568
670
|
|
|
@@ -714,6 +816,10 @@ function parseSubject(cert) {
|
|
|
714
816
|
return out;
|
|
715
817
|
}
|
|
716
818
|
function certExtension(der, oid) {
|
|
819
|
+
return certExtensions(der).find((e) => e.oid === oid)?.value;
|
|
820
|
+
}
|
|
821
|
+
function certExtensions(der) {
|
|
822
|
+
const out = [];
|
|
717
823
|
const top = tlv(der, 0, der.length);
|
|
718
824
|
const tbs = tlv(der, top.start, top.end);
|
|
719
825
|
let f = tlv(der, tbs.start, tbs.end);
|
|
@@ -727,16 +833,20 @@ function certExtension(der, oid) {
|
|
|
727
833
|
const ext = tlv(der, ep, seq.end);
|
|
728
834
|
const id = tlv(der, ext.start, ext.end);
|
|
729
835
|
let v = tlv(der, id.end, ext.end);
|
|
730
|
-
|
|
836
|
+
let critical = false;
|
|
837
|
+
if (v.tag === 1) {
|
|
838
|
+
critical = v.end > v.start && der[v.start] !== 0;
|
|
839
|
+
v = tlv(der, v.end, ext.end);
|
|
840
|
+
}
|
|
731
841
|
if (v.tag !== 4) throw new ValidationError("extension: extnValue OCTET STRING \u0431\u0438\u0448");
|
|
732
|
-
|
|
842
|
+
out.push({ oid: oidOf(der, id), critical, value: der.subarray(v.start, v.end) });
|
|
733
843
|
ep = ext.end;
|
|
734
844
|
}
|
|
735
|
-
return
|
|
845
|
+
return out;
|
|
736
846
|
}
|
|
737
847
|
pos = t.end;
|
|
738
848
|
}
|
|
739
|
-
return
|
|
849
|
+
return out;
|
|
740
850
|
}
|
|
741
851
|
|
|
742
852
|
// src/token.ts
|
|
@@ -1086,7 +1196,7 @@ var ResponseValidator = class {
|
|
|
1086
1196
|
);
|
|
1087
1197
|
}
|
|
1088
1198
|
} else {
|
|
1089
|
-
this.verifyChain(leaf);
|
|
1199
|
+
this.verifyChain(leaf, protocol === "ACSP_V2" ? EKU_CLIENT_AUTH : EKU_EMAIL_PROTECTION);
|
|
1090
1200
|
}
|
|
1091
1201
|
this.checkIssuer(leaf);
|
|
1092
1202
|
return leaf;
|
|
@@ -1130,21 +1240,40 @@ var ResponseValidator = class {
|
|
|
1130
1240
|
const need = rank[this.requiredLevel] ?? 2;
|
|
1131
1241
|
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}`);
|
|
1132
1242
|
}
|
|
1133
|
-
/**
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1243
|
+
/**
|
|
1244
|
+
* RFC 5280 замын шалгалт (Go SDK-ийн `x509.Certificate.Verify`-тэй ижил дүрэм): leaf-ээс trust anchor хүртэлх
|
|
1245
|
+
* замыг intermediates/anchors-оос (backtracking-тай) угсарна. Issuer бүр: subject = child.issuer, AKI/SKI таарах,
|
|
1246
|
+
* гарын үсэг, CA (basicConstraints CA:TRUE), KeyUsage тавьсан бол keyCertSign, **pathLenConstraint** (доорх
|
|
1247
|
+
* завсрын CA-ийн тоо), хүчинтэй хугацаа, EKU тавьсан бол `usage` / anyExtendedKeyUsage агуулах. Leaf болон CA
|
|
1248
|
+
* бүрийн үл таних **critical extension** → татгалзана. **Name constraints**-ийг SDK үнэлдэггүй — гинжинд байвал
|
|
1249
|
+
* fail-closed (CA-ийн бодит гинж 01–04-т алга). Цаг = now, leaf-ийн хүчинтэй хугацаанд хавчсан (`clockSkewMs`-ийн
|
|
1250
|
+
* хүрээнд; leaf-ийн хугацааг checkValidity шалгасан). certificatePolicies шалгахгүй (Go-той ижил).
|
|
1251
|
+
*/
|
|
1252
|
+
verifyChain(leaf, usage) {
|
|
1253
|
+
const now = Math.min(Math.max(Date.now(), Date.parse(leaf.validFrom)), Date.parse(leaf.validTo));
|
|
1254
|
+
const leafReason = criticalReason(leaf) ?? ekuReason(leaf, usage);
|
|
1255
|
+
if (leafReason) throw new ValidationError(`\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0433\u0438\u043D\u0436 \u0445\u04AF\u0447\u0438\u043D\u0433\u04AF\u0439: ${leafReason}`);
|
|
1256
|
+
let reason;
|
|
1257
|
+
const extend = (cur, cas) => {
|
|
1258
|
+
if (cas.length >= 8) return false;
|
|
1259
|
+
for (const [cand, isAnchor] of [
|
|
1260
|
+
...this.anchors.map((c) => [c, true]),
|
|
1261
|
+
...this.intermediates.map((c) => [c, false])
|
|
1262
|
+
]) {
|
|
1263
|
+
if (cas.includes(cand) || !signedBy(cur, cand)) continue;
|
|
1264
|
+
const bad = caReason(cand, cas.length, now, usage);
|
|
1265
|
+
if (bad) {
|
|
1266
|
+
reason ??= bad;
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
if (isAnchor || extend(cand, [...cas, cand])) return true;
|
|
1140
1270
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
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");
|
|
1271
|
+
return false;
|
|
1272
|
+
};
|
|
1273
|
+
if (extend(leaf, [])) return;
|
|
1274
|
+
throw new ValidationError(
|
|
1275
|
+
reason ? `\u0413\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 \u0433\u0438\u043D\u0436 \u0445\u04AF\u0447\u0438\u043D\u0433\u04AF\u0439: ${reason}` : "\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"
|
|
1276
|
+
);
|
|
1148
1277
|
}
|
|
1149
1278
|
/** leaf-ийн шууд issuer-ийг trust pool-оос буцаана (revocation шалгагчид issuer хэрэгтэй). */
|
|
1150
1279
|
findIssuer(leaf) {
|
|
@@ -1355,6 +1484,79 @@ function authorityKeyId(cert) {
|
|
|
1355
1484
|
function isCa(cert) {
|
|
1356
1485
|
return cert.ca === true;
|
|
1357
1486
|
}
|
|
1487
|
+
var EKU_ANY = "2.5.29.37.0";
|
|
1488
|
+
var EKU_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2";
|
|
1489
|
+
var EKU_EMAIL_PROTECTION = "1.3.6.1.5.5.7.3.4";
|
|
1490
|
+
var OID_NAME_CONSTRAINTS = "2.5.29.30";
|
|
1491
|
+
var HANDLED_CRITICAL = /* @__PURE__ */ new Set([
|
|
1492
|
+
"2.5.29.14",
|
|
1493
|
+
// subjectKeyIdentifier
|
|
1494
|
+
"2.5.29.15",
|
|
1495
|
+
// keyUsage
|
|
1496
|
+
"2.5.29.17",
|
|
1497
|
+
// subjectAltName
|
|
1498
|
+
"2.5.29.19",
|
|
1499
|
+
// basicConstraints
|
|
1500
|
+
"2.5.29.30",
|
|
1501
|
+
// nameConstraints (CA-д — caReason fail-closed)
|
|
1502
|
+
"2.5.29.31",
|
|
1503
|
+
// cRLDistributionPoints
|
|
1504
|
+
"2.5.29.32",
|
|
1505
|
+
// certificatePolicies
|
|
1506
|
+
"2.5.29.35",
|
|
1507
|
+
// authorityKeyIdentifier
|
|
1508
|
+
"2.5.29.37",
|
|
1509
|
+
// extKeyUsage
|
|
1510
|
+
"1.3.6.1.5.5.7.1.1"
|
|
1511
|
+
// authorityInfoAccess
|
|
1512
|
+
]);
|
|
1513
|
+
function signedBy(child, cand) {
|
|
1514
|
+
if (child.issuer !== cand.subject) return false;
|
|
1515
|
+
const aki = authorityKeyId(child);
|
|
1516
|
+
const ski = subjectKeyId(cand);
|
|
1517
|
+
if (aki && ski && aki !== ski) return false;
|
|
1518
|
+
try {
|
|
1519
|
+
return child.verify(cand.publicKey);
|
|
1520
|
+
} catch {
|
|
1521
|
+
return false;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function criticalReason(cert) {
|
|
1525
|
+
return certExtensions(cert.raw).some((e) => e.critical && !HANDLED_CRITICAL.has(e.oid)) ? "\u04AF\u043B \u0442\u0430\u043D\u0438\u0445 critical extension" : void 0;
|
|
1526
|
+
}
|
|
1527
|
+
function ekuReason(cert, usage) {
|
|
1528
|
+
const eku = extKeyUsage(cert);
|
|
1529
|
+
return eku.length && !eku.includes(usage) && !eku.includes(EKU_ANY) ? "\u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D\u043D\u0438\u0439 ExtKeyUsage \u044D\u043D\u044D \u04AF\u0439\u043B\u0434\u044D\u043B\u0434 \u0442\u043E\u0445\u0438\u0440\u043E\u0445\u0433\u04AF\u0439 (\u0433\u0438\u043D\u0436\u0438\u0439\u043D EKU)" : void 0;
|
|
1530
|
+
}
|
|
1531
|
+
function caReason(ca, below, now, usage) {
|
|
1532
|
+
if (!isCa(ca)) return "\u0437\u0430\u0432\u0441\u0440\u044B\u043D \u0433\u044D\u0440\u0447\u0438\u043B\u0433\u044D\u044D CA \u0431\u0438\u0448 \u044D\u0441\u0432\u044D\u043B keyCertSign \u044D\u0440\u0445\u0433\u04AF\u0439";
|
|
1533
|
+
if (certExtension(ca.raw, "2.5.29.15") && !(keyUsageBits(ca) & 4)) return "CA-\u0438\u0439\u043D KeyUsage-\u0434 keyCertSign \u0430\u043B\u0433\u0430";
|
|
1534
|
+
const from = Date.parse(ca.validFrom);
|
|
1535
|
+
const to = Date.parse(ca.validTo);
|
|
1536
|
+
if (Number.isNaN(from) || Number.isNaN(to) || now < from || now > to) {
|
|
1537
|
+
return "\u0433\u0438\u043D\u0436\u0438\u0439\u043D \u0433\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 \u044D\u0441\u0432\u044D\u043B \u0445\u04AF\u0447\u0438\u043D\u0442\u044D\u0439 \u0431\u043E\u043B\u043E\u043E\u0433\u04AF\u0439";
|
|
1538
|
+
}
|
|
1539
|
+
const pathLen = basicConstraintsPathLen(ca);
|
|
1540
|
+
if (pathLen !== void 0 && below > pathLen) return "pathLenConstraint \u0437\u04E9\u0440\u0447\u0441\u04E9\u043D (\u0437\u0430\u0432\u0441\u0440\u044B\u043D CA \u0445\u044D\u0442 \u043E\u043B\u043E\u043D)";
|
|
1541
|
+
if (certExtension(ca.raw, OID_NAME_CONSTRAINTS)) return "CA-\u0434 name constraints \u0431\u0430\u0439\u043D\u0430 (SDK \u04AF\u043D\u044D\u043B\u0434\u044D\u0433\u0433\u04AF\u0439 \u2014 fail-closed)";
|
|
1542
|
+
return criticalReason(ca) ?? ekuReason(ca, usage);
|
|
1543
|
+
}
|
|
1544
|
+
function basicConstraintsPathLen(cert) {
|
|
1545
|
+
const v = certExtension(cert.raw, "2.5.29.19");
|
|
1546
|
+
if (!v) return void 0;
|
|
1547
|
+
const seq = tlv(v, 0, v.length);
|
|
1548
|
+
for (let p = seq.start; p < seq.end; ) {
|
|
1549
|
+
const t = tlv(v, p, seq.end);
|
|
1550
|
+
if (t.tag === 2) {
|
|
1551
|
+
if (t.end - t.start > 4 || (v[t.start] & 128) !== 0) throw new ValidationError("basicConstraints: pathLen \u0431\u0443\u0440\u0443\u0443");
|
|
1552
|
+
let n = 0;
|
|
1553
|
+
for (let i = t.start; i < t.end; i++) n = n * 256 + v[i];
|
|
1554
|
+
return n;
|
|
1555
|
+
}
|
|
1556
|
+
p = t.end;
|
|
1557
|
+
}
|
|
1558
|
+
return void 0;
|
|
1559
|
+
}
|
|
1358
1560
|
|
|
1359
1561
|
// src/client.ts
|
|
1360
1562
|
var DEFAULT_BASE_URL = "https://rp.eidmongolia.mn";
|
|
@@ -1396,7 +1598,11 @@ var EidClient = class {
|
|
|
1396
1598
|
const level = cfg.defaultCertificateLevel ?? "QUALIFIED";
|
|
1397
1599
|
this.auth = new AuthApi(http, cfg.credentials, { certificateLevel: level });
|
|
1398
1600
|
this.sign = new SignApi(http, cfg.credentials, { certificateLevel: level });
|
|
1399
|
-
this.pdf = new PdfApi(http, cfg.credentials, {
|
|
1601
|
+
this.pdf = new PdfApi(http, cfg.credentials, {
|
|
1602
|
+
certificateLevel: level,
|
|
1603
|
+
timeoutMs: cfg.pdfTimeoutMs ?? DEFAULT_PDF_TIMEOUT_MS,
|
|
1604
|
+
maxBytes: cfg.pdfMaxBytes && cfg.pdfMaxBytes > 0 ? cfg.pdfMaxBytes : DEFAULT_PDF_MAX_BYTES
|
|
1605
|
+
});
|
|
1400
1606
|
this.organization = new OrganizationApi(http);
|
|
1401
1607
|
this.person = new PersonApi(http);
|
|
1402
1608
|
this.token = new TokenApi(http, cfg.credentials);
|
|
@@ -1757,6 +1963,7 @@ export {
|
|
|
1757
1963
|
AuthenticationError,
|
|
1758
1964
|
CITIZEN_ISSUING_CA_SKI,
|
|
1759
1965
|
DEFAULT_BASE_URL,
|
|
1966
|
+
DEFAULT_PDF_MAX_BYTES,
|
|
1760
1967
|
DEFAULT_PDF_TIMEOUT_MS,
|
|
1761
1968
|
DeviceLinkError,
|
|
1762
1969
|
EID_ACR_ADVANCED,
|
|
@@ -1774,6 +1981,7 @@ export {
|
|
|
1774
1981
|
PdfApi,
|
|
1775
1982
|
PersonApi,
|
|
1776
1983
|
RETRYABLE_ERROR_CODES,
|
|
1984
|
+
ResponseTooLargeError,
|
|
1777
1985
|
ResponseValidator,
|
|
1778
1986
|
SessionApi,
|
|
1779
1987
|
SessionFailedError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gerege-systems/eid-mongolia-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsup src/index.ts src/browser.ts --format esm,cjs --dts --clean",
|
|
32
|
-
"test": "npm run build && node --test --experimental-strip-types test/sdk.test.ts test/oidc.test.ts test/portal-gaps.test.ts",
|
|
32
|
+
"test": "npm run build && node --test --experimental-strip-types test/sdk.test.ts test/oidc.test.ts test/portal-gaps.test.ts test/chain.test.ts",
|
|
33
33
|
"test:contract": "npm run build && node --experimental-strip-types test/contract.test.ts",
|
|
34
34
|
"lint": "tsc --noEmit"
|
|
35
35
|
},
|