@gerege-systems/eid-mongolia-sdk 0.4.1 → 0.5.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 CHANGED
@@ -91,9 +91,28 @@ button.onclick = () => {
91
91
  const how = openOrShowQR(session, { showQR: (link) => renderQr(link) }); // "opened" | "qr"
92
92
  showVc(session.vc); // иргэний утсан дээрх кодтой тулгуулна (ПИН биш)
93
93
  };
94
- // дараа нь backend: eid.session.waitForResult(sessionId) → eid.validator.validateAuth(result, s.acsp)
94
+ // дараа нь backend: eid.session.waitForResult(sessionId) → eid.validator.validateAuth(result, s.acsp, {...})
95
95
  ```
96
96
 
97
+ **Эхлүүлсэн урсгалаа заавал тулга (relay хаалт).** RP аль урсгалыг эхлүүлснээ мэддэг — гарын үсэг зурагдсан
98
+ `flowType` түүнтэй таарах ёстой, same-device үед callback-ийн `userChallengeVerifier` заавал:
99
+
100
+ ```ts
101
+ // desktop дээр QR харуулсан
102
+ eid.validator.validateAuth(result, s.acsp, { expectedFlowType: "QR" });
103
+
104
+ // гар утасны браузераас апп нээсэн (callbackUrl-тай session) — апп буцахдаа
105
+ // `?userChallengeVerifier=...`-ийг callback URL-д нэмдэг
106
+ eid.validator.validateAuth(result, s.acsp, {
107
+ expectedFlowType: "Web2App", // өөр аппаас бол "App2App"
108
+ userChallengeVerifier: callbackQuery.get("userChallengeVerifier") ?? undefined,
109
+ });
110
+ ```
111
+
112
+ > eID Mongolia апп бодит сувгийг 2.2.3 (build 58)-аас мэдээлнэ. Өмнөх build-ууд серверийн хүлээлтийг хуулдаг
113
+ > (callback-тай session → `App2App`, үгүй → `QR`) тул серверт min_version тавигдтал энэ шалгалт relay-ээс бүрэн
114
+ > хамгаалахгүй — гэхдээ QR урсгалд хуучин апптай ч эвдрэхгүй.
115
+
97
116
  - Гар утас (iOS/Android, desktop UA илгээдэг iPadOS ч) бол `location.assign(link)` — Universal Link / App Link
98
117
  аппыг нээнэ; бусад үед `showQR(link)`. Илрүүлэгчийг `isMobile: boolean | () => boolean`-оор солино
99
118
  (анхдагч `isMobileUserAgent()`).
@@ -113,7 +132,8 @@ OK ирж болзошгүй. Иймд `ResponseValidator`:
113
132
  (Root → National Issuing CA → Gerege Issuing CA → eID Mongolia Issuing CA → иргэн)
114
133
  3. Гэрчилгээ хүчинтэй хугацаанд + шаардсан `certificateLevel` (default `QUALIFIED`)
115
134
  4. Гарын үсгийг иргэний public key-ээр шалгана:
116
- - **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-ийг мөн шалгана
135
+ - **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-ийг мөн шалгана;
136
+ `expectedFlowType` өгвөл `flowType` эхлүүлсэн урсгалтай ЯГ таарах, Web2App/App2App-д `userChallengeVerifier` заавал
117
137
  - **sign** → RP-ийн өгсөн digest дээр
118
138
 
119
139
  Аль нэг алхам бүтэлгүйтвэл `ValidationError` шиднэ — хариунд **итгэхгүй**.
package/dist/index.cjs CHANGED
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  randomChallenge: () => randomChallenge,
46
46
  resolveBaseUrl: () => resolveBaseUrl,
47
47
  sha256Base64: () => sha256Base64,
48
+ userChallengeOf: () => userChallengeOf,
48
49
  verifyPayloadSignature: () => verifyPayloadSignature,
49
50
  verifyPrehashedSignature: () => verifyPrehashedSignature
50
51
  });
@@ -415,7 +416,7 @@ var ResponseValidator = class {
415
416
  * Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
416
417
  * @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
417
418
  */
418
- validateAuth(result, acsp) {
419
+ validateAuth(result, acsp, opts = {}) {
419
420
  const leaf = this.precheck(result, "ACSP_V2");
420
421
  const { serverRandom, userChallenge, flowType, interactionTypeUsed } = result;
421
422
  if (!serverRandom || Buffer.from(serverRandom, "base64").length < 18) throw new ValidationError("serverRandom \u0430\u043B\u0433\u0430/\u0431\u043E\u0433\u0438\u043D\u043E");
@@ -423,6 +424,16 @@ var ResponseValidator = class {
423
424
  if (!flowType || !acsp.flowTypes.includes(flowType)) {
424
425
  throw new ValidationError(`flowType ${flowType} \u043D\u044C \u0441\u0430\u043D\u0430\u043B \u0431\u043E\u043B\u0433\u043E\u0441\u043E\u043D \u0443\u0440\u0441\u0433\u0430\u043B\u0434 \u0431\u0430\u0439\u0445\u0433\u04AF\u0439`);
425
426
  }
427
+ if (opts.expectedFlowType && flowType !== opts.expectedFlowType) {
428
+ throw new ValidationError(`flowType ${flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${opts.expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
429
+ }
430
+ const sameDevice = opts.expectedFlowType === "Web2App" || opts.expectedFlowType === "App2App";
431
+ if (sameDevice && !opts.userChallengeVerifier) {
432
+ throw new ValidationError(`${opts.expectedFlowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 callback-\u0438\u0439\u043D userChallengeVerifier \u0437\u0430\u0430\u0432\u0430\u043B`);
433
+ }
434
+ if (opts.userChallengeVerifier !== void 0 && userChallengeOf(opts.userChallengeVerifier) !== userChallenge) {
435
+ throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
436
+ }
426
437
  if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
427
438
  const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
428
439
  const payload = buildAcspV2Payload({
@@ -570,6 +581,9 @@ var ResponseValidator = class {
570
581
  }
571
582
  }
572
583
  };
584
+ function userChallengeOf(verifier) {
585
+ return (0, import_node_crypto2.createHash)("sha256").update(verifier, "utf8").digest("base64url");
586
+ }
573
587
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
574
588
  const sig = Buffer.from(sigB64, "base64");
575
589
  const h = nodeHash(hash);
@@ -809,6 +823,7 @@ function openOrShowQR(session, opts) {
809
823
  randomChallenge,
810
824
  resolveBaseUrl,
811
825
  sha256Base64,
826
+ userChallengeOf,
812
827
  verifyPayloadSignature,
813
828
  verifyPrehashedSignature
814
829
  });
package/dist/index.d.cts CHANGED
@@ -131,6 +131,28 @@ declare class SessionApi {
131
131
  }): Promise<SessionResult>;
132
132
  }
133
133
 
134
+ /**
135
+ * validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
136
+ * (device-link relay хаалт).
137
+ */
138
+ interface ValidateAuthOptions {
139
+ /**
140
+ * RP-ийн эхлүүлсэн урсгал. Өгвөл гарын үсэг зурагдсан ACSP_V2 `flowType` ЯГ ИЖИЛ байх ёстой:
141
+ * desktop QR → "QR"; браузераас холбоосоор апп нээсэн → "Web2App"; өөр аппаас → "App2App";
142
+ * push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
143
+ *
144
+ * ⚠️ Апп нь бодит сувгийг eID Mongolia 2.2.3 (build 58)-аас мэдээлнэ. Түүнээс өмнөх build-ууд
145
+ * серверийн хүлээлтийг хуулдаг тул серверт min_version тавигдтал энэ шалгалт relay-ээс
146
+ * бүрэн хамгаалахгүй (гэхдээ хуучин апптай ч эвдрэхгүй — QR урсгалд "QR" ирнэ).
147
+ */
148
+ expectedFlowType?: FlowType;
149
+ /**
150
+ * Callback URL-ийн `userChallengeVerifier` параметр (same-device урсгалд апп RP руу буцаахдаа
151
+ * нэмдэг). Өгвөл `BASE64URL(SHA-256(UTF-8(verifier)))` нь гарын үсэг зурагдсан `userChallenge`-тэй
152
+ * таарах ёстой.
153
+ */
154
+ userChallengeVerifier?: string;
155
+ }
134
156
  /**
135
157
  * RevocationChecker — иргэний cert revoke хийгдсэн эсэхийг шалгах hook (OCSP/CRL).
136
158
  * Сүлжээний I/O шаардсан тул async. RP нь cert доторх AIA (OCSP) / CDP (CRL) URL-аар
@@ -197,7 +219,7 @@ declare class ResponseValidator {
197
219
  * Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
198
220
  * @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
199
221
  */
200
- validateAuth(result: SessionResult, acsp: AcspContext): VerifiedIdentity;
222
+ validateAuth(result: SessionResult, acsp: AcspContext, opts?: ValidateAuthOptions): VerifiedIdentity;
201
223
  /**
202
224
  * Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
203
225
  * @param hashAlgorithm — digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256).
@@ -225,6 +247,8 @@ declare class ResponseValidator {
225
247
  checkRevocation(cert: X509Certificate): Promise<void>;
226
248
  }
227
249
  /** Payload (auth) дээрх гарын үсэг — алгоритмаар салаалж node:crypto verify. */
250
+ /** Smart-ID: userChallenge = BASE64URL(SHA-256(UTF-8(userChallengeVerifier))), padding-гүй. */
251
+ declare function userChallengeOf(verifier: string): string;
228
252
  declare function verifyPayloadSignature(payload: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
229
253
  /** Prehashed digest (sign) дээрх гарын үсэг. RSA: raw public op + EMSA-PSS-VERIFY / PKCS#1 DigestInfo. */
230
254
  declare function verifyPrehashedSignature(digest: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
@@ -288,4 +312,4 @@ declare function buildAcspV2Payload(p: AcspV2Params): Buffer;
288
312
  /** HASH_{hashAlgorithm}(payload). */
289
313
  declare function acspV2Digest(p: AcspV2Params, hashAlgorithm: HashAlgorithm | string): Buffer;
290
314
 
291
- export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, EidClient, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, interactionsToBase64, parseSessionResult, randomChallenge, resolveBaseUrl, sha256Base64, verifyPayloadSignature, verifyPrehashedSignature };
315
+ export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, EidClient, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, interactionsToBase64, parseSessionResult, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
package/dist/index.d.ts CHANGED
@@ -131,6 +131,28 @@ declare class SessionApi {
131
131
  }): Promise<SessionResult>;
132
132
  }
133
133
 
134
+ /**
135
+ * validateAuth-ийн нэмэлт шалгалт — RP ЯМАР урсгал эхлүүлснээ мэддэг тул түүнийг тулгана
136
+ * (device-link relay хаалт).
137
+ */
138
+ interface ValidateAuthOptions {
139
+ /**
140
+ * RP-ийн эхлүүлсэн урсгал. Өгвөл гарын үсэг зурагдсан ACSP_V2 `flowType` ЯГ ИЖИЛ байх ёстой:
141
+ * desktop QR → "QR"; браузераас холбоосоор апп нээсэн → "Web2App"; өөр аппаас → "App2App";
142
+ * push → "Notification". "Web2App"/"App2App" үед `userChallengeVerifier` ЗААВАЛ.
143
+ *
144
+ * ⚠️ Апп нь бодит сувгийг eID Mongolia 2.2.3 (build 58)-аас мэдээлнэ. Түүнээс өмнөх build-ууд
145
+ * серверийн хүлээлтийг хуулдаг тул серверт min_version тавигдтал энэ шалгалт relay-ээс
146
+ * бүрэн хамгаалахгүй (гэхдээ хуучин апптай ч эвдрэхгүй — QR урсгалд "QR" ирнэ).
147
+ */
148
+ expectedFlowType?: FlowType;
149
+ /**
150
+ * Callback URL-ийн `userChallengeVerifier` параметр (same-device урсгалд апп RP руу буцаахдаа
151
+ * нэмдэг). Өгвөл `BASE64URL(SHA-256(UTF-8(verifier)))` нь гарын үсэг зурагдсан `userChallenge`-тэй
152
+ * таарах ёстой.
153
+ */
154
+ userChallengeVerifier?: string;
155
+ }
134
156
  /**
135
157
  * RevocationChecker — иргэний cert revoke хийгдсэн эсэхийг шалгах hook (OCSP/CRL).
136
158
  * Сүлжээний I/O шаардсан тул async. RP нь cert доторх AIA (OCSP) / CDP (CRL) URL-аар
@@ -197,7 +219,7 @@ declare class ResponseValidator {
197
219
  * Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
198
220
  * @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
199
221
  */
200
- validateAuth(result: SessionResult, acsp: AcspContext): VerifiedIdentity;
222
+ validateAuth(result: SessionResult, acsp: AcspContext, opts?: ValidateAuthOptions): VerifiedIdentity;
201
223
  /**
202
224
  * Sign session-ийн хариуг бүрэн шалгаж, баталгаажсан гарын үсгийг буцаана.
203
225
  * @param hashAlgorithm — digest-ийг үүсгэсэн hash (хүсэлтэд илгээсэнтэй ижил; default SHA-256).
@@ -225,6 +247,8 @@ declare class ResponseValidator {
225
247
  checkRevocation(cert: X509Certificate): Promise<void>;
226
248
  }
227
249
  /** Payload (auth) дээрх гарын үсэг — алгоритмаар салаалж node:crypto verify. */
250
+ /** Smart-ID: userChallenge = BASE64URL(SHA-256(UTF-8(userChallengeVerifier))), padding-гүй. */
251
+ declare function userChallengeOf(verifier: string): string;
228
252
  declare function verifyPayloadSignature(payload: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
229
253
  /** Prehashed digest (sign) дээрх гарын үсэг. RSA: raw public op + EMSA-PSS-VERIFY / PKCS#1 DigestInfo. */
230
254
  declare function verifyPrehashedSignature(digest: Buffer, sigB64: string, key: KeyObject, alg: string, hash: string): boolean;
@@ -288,4 +312,4 @@ declare function buildAcspV2Payload(p: AcspV2Params): Buffer;
288
312
  /** HASH_{hashAlgorithm}(payload). */
289
313
  declare function acspV2Digest(p: AcspV2Params, hashAlgorithm: HashAlgorithm | string): Buffer;
290
314
 
291
- export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, EidClient, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, interactionsToBase64, parseSessionResult, randomChallenge, resolveBaseUrl, sha256Base64, verifyPayloadSignature, verifyPrehashedSignature };
315
+ export { AcspContext, type AcspV2Params, AuthApi, type AuthDefaults, type AuthOptions, CertificateLevel, type ClientConfig, DEFAULT_BASE_URL, DeviceLinkSession, EidClient, FlowType, HashAlgorithm, Http, type HttpConfig, Interaction, NotificationSession, ResponseValidator, type RevocationChecker, type RevocationStatus, RpCredentials, SessionApi, SessionResult, SignApi, type SignDefaults, type SignOptions, SignatureAlgorithm, type TrustConfig, type ValidateAuthOptions, type VerifiedIdentity, type VerifiedSignature, acspV2Digest, buildAcspV2Payload, interactionsToBase64, parseSessionResult, randomChallenge, resolveBaseUrl, sha256Base64, userChallengeOf, verifyPayloadSignature, verifyPrehashedSignature };
package/dist/index.js CHANGED
@@ -343,7 +343,7 @@ var ResponseValidator = class {
343
343
  * Auth session-ийн хариуг бүрэн шалгаж, баталгаажсан иргэнийг буцаана.
344
344
  * @param acsp — session эхлүүлэхэд SDK-ийн буцаасан `acsp` контекст (RP хадгалсан).
345
345
  */
346
- validateAuth(result, acsp) {
346
+ validateAuth(result, acsp, opts = {}) {
347
347
  const leaf = this.precheck(result, "ACSP_V2");
348
348
  const { serverRandom, userChallenge, flowType, interactionTypeUsed } = result;
349
349
  if (!serverRandom || Buffer.from(serverRandom, "base64").length < 18) throw new ValidationError("serverRandom \u0430\u043B\u0433\u0430/\u0431\u043E\u0433\u0438\u043D\u043E");
@@ -351,6 +351,16 @@ var ResponseValidator = class {
351
351
  if (!flowType || !acsp.flowTypes.includes(flowType)) {
352
352
  throw new ValidationError(`flowType ${flowType} \u043D\u044C \u0441\u0430\u043D\u0430\u043B \u0431\u043E\u043B\u0433\u043E\u0441\u043E\u043D \u0443\u0440\u0441\u0433\u0430\u043B\u0434 \u0431\u0430\u0439\u0445\u0433\u04AF\u0439`);
353
353
  }
354
+ if (opts.expectedFlowType && flowType !== opts.expectedFlowType) {
355
+ throw new ValidationError(`flowType ${flowType} \u2260 \u044D\u0445\u043B\u04AF\u04AF\u043B\u0441\u044D\u043D \u0443\u0440\u0441\u0433\u0430\u043B ${opts.expectedFlowType} (relay \u0431\u0430\u0439\u0436 \u0431\u043E\u043B\u0437\u043E\u0448\u0433\u04AF\u0439)`);
356
+ }
357
+ const sameDevice = opts.expectedFlowType === "Web2App" || opts.expectedFlowType === "App2App";
358
+ if (sameDevice && !opts.userChallengeVerifier) {
359
+ throw new ValidationError(`${opts.expectedFlowType} \u0443\u0440\u0441\u0433\u0430\u043B\u0434 callback-\u0438\u0439\u043D userChallengeVerifier \u0437\u0430\u0430\u0432\u0430\u043B`);
360
+ }
361
+ if (opts.userChallengeVerifier !== void 0 && userChallengeOf(opts.userChallengeVerifier) !== userChallenge) {
362
+ throw new ValidationError("userChallengeVerifier \u043D\u044C \u0433\u0430\u0440\u044B\u043D \u04AF\u0441\u044D\u0433 \u0437\u0443\u0440\u0430\u0433\u0434\u0441\u0430\u043D userChallenge-\u0442\u044D\u0439 \u0442\u0430\u0430\u0440\u0441\u0430\u043D\u0433\u04AF\u0439");
363
+ }
354
364
  if (!interactionTypeUsed) throw new ValidationError("interactionTypeUsed \u0430\u043B\u0433\u0430");
355
365
  const { alg, hash } = this.algorithmOf(result, acsp.signatureAlgorithm, acsp.hashAlgorithm);
356
366
  const payload = buildAcspV2Payload({
@@ -498,6 +508,9 @@ var ResponseValidator = class {
498
508
  }
499
509
  }
500
510
  };
511
+ function userChallengeOf(verifier) {
512
+ return createHash2("sha256").update(verifier, "utf8").digest("base64url");
513
+ }
501
514
  function verifyPayloadSignature(payload, sigB64, key, alg, hash) {
502
515
  const sig = Buffer.from(sigB64, "base64");
503
516
  const h = nodeHash(hash);
@@ -694,6 +707,7 @@ export {
694
707
  randomChallenge,
695
708
  resolveBaseUrl,
696
709
  sha256Base64,
710
+ userChallengeOf,
697
711
  verifyPayloadSignature,
698
712
  verifyPrehashedSignature
699
713
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gerege-systems/eid-mongolia-sdk",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "e-ID Mongolia Relying Party SDK — иргэнээр утсаар нь нэвтрэлт ба хууль ёсны цахим гарын үсэг (RP-API v3, Smart-ID нийцтэй).",
5
5
  "license": "MIT",
6
6
  "type": "module",