@h-ai/crypto 1.0.0-beta.2 → 1.0.0-beta.21

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/dist/index.js CHANGED
@@ -58,7 +58,9 @@ var en_US_default = {
58
58
  crypto_transportSessionKeyDecryptFailed: "Failed to decrypt transport session key",
59
59
  crypto_transportBodyLimitInvalid: "Transport plaintext limit must be an integer between 1024 and 67108864 bytes",
60
60
  crypto_transportBodyTooLarge: "Encrypted transport payload exceeds the configured byte limit",
61
- crypto_transportTimeInvalid: "Your device and server clocks may be out of sync, or the request has expired. Enable automatic time synchronization and refresh to retry. If this continues, ask an administrator to check the server clock.",
61
+ crypto_transportTimeInvalid: "Your device and server clocks may be out of sync, or the request has expired. Enable automatic time synchronization or set your device time manually, then refresh to retry. If this continues, ask an administrator to check the server clock.",
62
+ crypto_transportTimeInvalidWithServerTime: "Your device and server clocks may be out of sync, or the request has expired. Server time is {serverTime}. Enable automatic time synchronization or set your device time manually, then refresh to retry. If this continues, ask an administrator to check the server clock.",
63
+ crypto_transportTimeConfigInvalid: "Invalid transport time settings. Future clock skew must be an integer from 0 to 300 seconds and request lifetime from 1 to 600 seconds.",
62
64
  crypto_transportBodyLimitConflict: "Explicit transport limit {limit} bytes is below the enabled module requirement of {required} bytes; adjust the module budget or explicit limit",
63
65
  crypto_transportBusy: "Material processing is busy. Please retry shortly."
64
66
  };
@@ -118,7 +120,9 @@ var ja_JP_default = {
118
120
  crypto_transportSessionKeyDecryptFailed: "\u901A\u4FE1\u30BB\u30C3\u30B7\u30E7\u30F3\u9375\u306E\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
119
121
  crypto_transportBodyLimitInvalid: "\u901A\u4FE1\u5E73\u6587\u306E\u4E0A\u9650\u306F 1024 \u304B\u3089 67108864 \u30D0\u30A4\u30C8\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
120
122
  crypto_transportBodyTooLarge: "\u6697\u53F7\u5316\u901A\u4FE1\u306E\u30C7\u30FC\u30BF\u304C\u8A2D\u5B9A\u3055\u308C\u305F\u30D0\u30A4\u30C8\u4E0A\u9650\u3092\u8D85\u3048\u3066\u3044\u307E\u3059",
121
- crypto_transportTimeInvalid: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3057\u3066\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
123
+ crypto_transportTimeInvalid: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u7AEF\u672B\u306E\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3059\u308B\u304B\u3001\u624B\u52D5\u3067\u6642\u523B\u3092\u8A2D\u5B9A\u3057\u3066\u304B\u3089\u3001\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
124
+ crypto_transportTimeInvalidWithServerTime: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u30B5\u30FC\u30D0\u30FC\u6642\u523B\u306F {serverTime} \u3067\u3059\u3002\u7AEF\u672B\u306E\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3059\u308B\u304B\u3001\u624B\u52D5\u3067\u6642\u523B\u3092\u8A2D\u5B9A\u3057\u3066\u304B\u3089\u3001\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
125
+ crypto_transportTimeConfigInvalid: "\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u306E\u6642\u523B\u8A2D\u5B9A\u304C\u7121\u52B9\u3067\u3059\u3002\u672A\u6765\u65B9\u5411\u306E\u6642\u523B\u5DEE\u306F 0\uFF5E300 \u79D2\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9593\u306F 1\uFF5E600 \u79D2\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
122
126
  crypto_transportBodyLimitConflict: "\u660E\u793A\u3055\u308C\u305F\u8EE2\u9001\u4E0A\u9650 {limit} \u30D0\u30A4\u30C8\u306F\u6709\u52B9\u306A\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u5FC5\u8981\u91CF {required} \u30D0\u30A4\u30C8\u672A\u6E80\u3067\u3059\u3002\u4E88\u7B97\u307E\u305F\u306F\u4E0A\u9650\u3092\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044",
123
127
  crypto_transportBusy: "\u8CC7\u6599\u306E\u51E6\u7406\u304C\u6DF7\u307F\u5408\u3063\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u3057\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
124
128
  };
@@ -178,7 +182,9 @@ var zh_CN_default = {
178
182
  crypto_transportSessionKeyDecryptFailed: "\u4F20\u8F93\u52A0\u5BC6\u4F1A\u8BDD\u5BC6\u94A5\u89E3\u5BC6\u5931\u8D25",
179
183
  crypto_transportBodyLimitInvalid: "\u4F20\u8F93\u660E\u6587\u5BB9\u91CF\u5FC5\u987B\u662F 1024 \u5230 67108864 \u5B57\u8282\u4E4B\u95F4\u7684\u6574\u6570",
180
184
  crypto_transportBodyTooLarge: "\u52A0\u5BC6\u4F20\u8F93\u62A5\u6587\u8D85\u8FC7\u914D\u7F6E\u7684\u5B57\u8282\u4E0A\u9650",
181
- crypto_transportTimeInvalid: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u8BF7\u5F00\u542F\u81EA\u52A8\u6821\u65F6\u5E76\u5237\u65B0\u91CD\u8BD5\uFF1B\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
185
+ crypto_transportTimeInvalid: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u8BF7\u5F00\u542F\u8BBE\u5907\u7684\u81EA\u52A8\u6821\u65F6\u6216\u624B\u5DE5\u4FEE\u6539\u65F6\u95F4\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
186
+ crypto_transportTimeInvalidWithServerTime: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u670D\u52A1\u5668\u65F6\u95F4\u4E3A {serverTime}\u3002\u8BF7\u5F00\u542F\u8BBE\u5907\u7684\u81EA\u52A8\u6821\u65F6\u6216\u624B\u5DE5\u4FEE\u6539\u65F6\u95F4\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
187
+ crypto_transportTimeConfigInvalid: "\u4F20\u8F93\u65F6\u95F4\u914D\u7F6E\u65E0\u6548\u3002\u5141\u8BB8\u7684\u672A\u6765\u504F\u5DEE\u987B\u4E3A 0 \u81F3 300 \u79D2\u7684\u6574\u6570\uFF0C\u8BF7\u6C42\u6709\u6548\u671F\u987B\u4E3A 1 \u81F3 600 \u79D2\u7684\u6574\u6570\u3002",
182
188
  crypto_transportBodyLimitConflict: "\u663E\u5F0F\u4F20\u8F93\u4E0A\u9650 {limit} \u5B57\u8282\u5C0F\u4E8E\u5DF2\u542F\u7528\u6A21\u5757\u6240\u9700\u7684 {required} \u5B57\u8282\uFF1B\u8BF7\u8C03\u6574\u6A21\u5757\u9884\u7B97\u6216\u663E\u5F0F\u4E0A\u9650",
183
189
  crypto_transportBusy: "\u6750\u6599\u5904\u7406\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
184
190
  };
@@ -379,7 +385,11 @@ function createSM2() {
379
385
  } catch (error) {
380
386
  return err(
381
387
  HaiCryptoError.KEY_GENERATION_FAILED,
382
- cryptoM("crypto_sm2KeyPairGenerateFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
388
+ cryptoM("crypto_sm2KeyPairGenerateFailed", {
389
+ params: {
390
+ error: error instanceof Error ? error.message : String(error)
391
+ }
392
+ }),
383
393
  error
384
394
  );
385
395
  }
@@ -418,7 +428,11 @@ function createSM2() {
418
428
  } catch (error) {
419
429
  return err(
420
430
  HaiCryptoError.ENCRYPTION_FAILED,
421
- cryptoM("crypto_sm2EncryptFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
431
+ cryptoM("crypto_sm2EncryptFailed", {
432
+ params: {
433
+ error: error instanceof Error ? error.message : String(error)
434
+ }
435
+ }),
422
436
  error
423
437
  );
424
438
  }
@@ -457,7 +471,11 @@ function createSM2() {
457
471
  } catch (error) {
458
472
  return err(
459
473
  HaiCryptoError.DECRYPTION_FAILED,
460
- cryptoM("crypto_sm2DecryptFailedWithError", { params: { error: error instanceof Error ? error.message : String(error) } }),
474
+ cryptoM("crypto_sm2DecryptFailedWithError", {
475
+ params: {
476
+ error: error instanceof Error ? error.message : String(error)
477
+ }
478
+ }),
461
479
  error
462
480
  );
463
481
  }
@@ -492,7 +510,11 @@ function createSM2() {
492
510
  } catch (error) {
493
511
  return err(
494
512
  HaiCryptoError.SIGN_FAILED,
495
- cryptoM("crypto_sm2SignFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
513
+ cryptoM("crypto_sm2SignFailed", {
514
+ params: {
515
+ error: error instanceof Error ? error.message : String(error)
516
+ }
517
+ }),
496
518
  error
497
519
  );
498
520
  }
@@ -523,7 +545,11 @@ function createSM2() {
523
545
  } catch (error) {
524
546
  return err(
525
547
  HaiCryptoError.VERIFY_FAILED,
526
- cryptoM("crypto_sm2VerifyFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
548
+ cryptoM("crypto_sm2VerifyFailed", {
549
+ params: {
550
+ error: error instanceof Error ? error.message : String(error)
551
+ }
552
+ }),
527
553
  error
528
554
  );
529
555
  }
@@ -589,7 +615,11 @@ function createSM3() {
589
615
  } catch (error) {
590
616
  return err(
591
617
  HaiCryptoError.HASH_FAILED,
592
- cryptoM("crypto_sm3HashFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
618
+ cryptoM("crypto_sm3HashFailed", {
619
+ params: {
620
+ error: error instanceof Error ? error.message : String(error)
621
+ }
622
+ }),
593
623
  error
594
624
  );
595
625
  }
@@ -628,7 +658,11 @@ function createSM3() {
628
658
  } catch (error) {
629
659
  return err(
630
660
  HaiCryptoError.HMAC_FAILED,
631
- cryptoM("crypto_sm3HmacFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
661
+ cryptoM("crypto_sm3HmacFailed", {
662
+ params: {
663
+ error: error instanceof Error ? error.message : String(error)
664
+ }
665
+ }),
632
666
  error
633
667
  );
634
668
  }
@@ -649,7 +683,11 @@ function createSM3() {
649
683
  } catch (error) {
650
684
  return err(
651
685
  HaiCryptoError.HASH_FAILED,
652
- cryptoM("crypto_sm3VerifyFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
686
+ cryptoM("crypto_sm3VerifyFailed", {
687
+ params: {
688
+ error: error instanceof Error ? error.message : String(error)
689
+ }
690
+ }),
653
691
  error
654
692
  );
655
693
  }
@@ -737,7 +775,11 @@ function createSM4() {
737
775
  } catch (error) {
738
776
  return err(
739
777
  HaiCryptoError.ENCRYPTION_FAILED,
740
- cryptoM("crypto_sm4EncryptFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
778
+ cryptoM("crypto_sm4EncryptFailed", {
779
+ params: {
780
+ error: error instanceof Error ? error.message : String(error)
781
+ }
782
+ }),
741
783
  error
742
784
  );
743
785
  }
@@ -785,7 +827,11 @@ function createSM4() {
785
827
  } catch (error) {
786
828
  return err(
787
829
  HaiCryptoError.DECRYPTION_FAILED,
788
- cryptoM("crypto_sm4DecryptFailedWithError", { params: { error: error instanceof Error ? error.message : String(error) } }),
830
+ cryptoM("crypto_sm4DecryptFailedWithError", {
831
+ params: {
832
+ error: error instanceof Error ? error.message : String(error)
833
+ }
834
+ }),
789
835
  error
790
836
  );
791
837
  }
@@ -821,7 +867,12 @@ function createSM4() {
821
867
  * @returns 成功时返回明文;失败时同 decrypt
822
868
  */
823
869
  decryptWithIV(ciphertext, key, iv) {
824
- return this.decrypt({ mode: "cbc", ciphertext, iv, encoding: "hex" }, key);
870
+ return this.decrypt({
871
+ mode: "cbc",
872
+ ciphertext,
873
+ iv,
874
+ encoding: "hex"
875
+ }, key);
825
876
  },
826
877
  /** 校验密钥格式是否合法(32 字符十六进制) */
827
878
  isValidKey(key) {
@@ -867,8 +918,11 @@ function resolveTransportBodyLimits(maxPlaintextBytes, options = {}) {
867
918
  maxPlaintextBytes = required ?? TRANSPORT_BODY_LIMITS.plaintextBytes;
868
919
  if (!Number.isSafeInteger(maxPlaintextBytes) || maxPlaintextBytes < TRANSPORT_PLAINTEXT_RANGE.min || maxPlaintextBytes > TRANSPORT_PLAINTEXT_RANGE.max)
869
920
  throw new RangeError(cryptoM("crypto_transportBodyLimitInvalid"));
870
- if (required !== void 0 && maxPlaintextBytes < required)
871
- throw new RangeError(cryptoM("crypto_transportBodyLimitConflict", { params: { limit: maxPlaintextBytes, required } }));
921
+ if (required !== void 0 && maxPlaintextBytes < required) {
922
+ throw new RangeError(cryptoM("crypto_transportBodyLimitConflict", {
923
+ params: { limit: maxPlaintextBytes, required }
924
+ }));
925
+ }
872
926
  return Object.freeze({ ...TRANSPORT_BODY_LIMITS, plaintextBytes: maxPlaintextBytes, encryptedPayloadBytes: 2 * maxPlaintextBytes + 32768 });
873
927
  }
874
928
  var TransportBodyTooLargeError = class extends RangeError {
@@ -940,7 +994,10 @@ function createTransportBodyPolicy(maxPlaintextBytes, routes = {}) {
940
994
  const entries = Object.entries(routes);
941
995
  if (entries.length > 100 || entries.some(([path]) => !path.startsWith("/") || path.includes("?") || path.includes("#") || path.split("/").some((part) => part.includes("*") && part !== "*")))
942
996
  throw new RangeError(cryptoM("crypto_transportBodyLimitInvalid"));
943
- const configured = entries.map(([path, bytes]) => ({ parts: path.split("/"), limits: resolveTransportBodyLimits(bytes) }));
997
+ const configured = entries.map(([path, bytes]) => ({
998
+ parts: path.split("/"),
999
+ limits: resolveTransportBodyLimits(bytes)
1000
+ }));
944
1001
  const maximum = resolveTransportBodyLimits(Math.max(ordinary.plaintextBytes, ...configured.map((route) => route.limits.plaintextBytes)));
945
1002
  let small = 0;
946
1003
  let large = 0;
@@ -951,22 +1008,26 @@ function createTransportBodyPolicy(maxPlaintextBytes, routes = {}) {
951
1008
  const matching = configured.filter((route) => route.parts.length === parts.length && route.parts.every((part, index) => part === "*" || part === parts[index]));
952
1009
  const limits = matching.length ? matching.reduce((left, right) => left.limits.plaintextBytes < right.limits.plaintextBytes ? left : right).limits : ordinary;
953
1010
  const heavy = matching.length > 0 && limits.plaintextBytes > TRANSPORT_BODY_LIMITS.plaintextBytes;
954
- if (heavy ? large >= 2 : small >= 16)
1011
+ if (heavy ? large >= 2 : small >= 16) {
955
1012
  return null;
1013
+ }
956
1014
  if (heavy)
957
1015
  large++;
958
1016
  else
959
1017
  small++;
960
1018
  let released = false;
961
- return { limits, release() {
962
- if (released)
963
- return;
964
- released = true;
965
- if (heavy)
966
- large--;
967
- else
968
- small--;
969
- } };
1019
+ return {
1020
+ limits,
1021
+ release() {
1022
+ if (released)
1023
+ return;
1024
+ released = true;
1025
+ if (heavy)
1026
+ large--;
1027
+ else
1028
+ small--;
1029
+ }
1030
+ };
970
1031
  }
971
1032
  };
972
1033
  }
@@ -979,9 +1040,9 @@ var TRANSPORT_PROTOCOL = {
979
1040
  ENVELOPE_HEADER: "X-Hai-Transport",
980
1041
  /** 唯一支持的认证协议版本;旧未认证格式拒绝。 */
981
1042
  VERSION: 2,
982
- /** 请求允许的过去时间窗口(毫秒)。 */
1043
+ /** 默认请求有效期(毫秒)。 */
983
1044
  REQUEST_WINDOW_MS: 3e5,
984
- /** 节点时钟允许的最大未来偏差(毫秒)。 */
1045
+ /** 默认允许的客户端未来偏差(毫秒),也是部署约定的节点对时误差上限。 */
985
1046
  CLOCK_SKEW_MS: 3e4,
986
1047
  /** 标识响应体已加密的响应头名。 */
987
1048
  ENCRYPTED_HEADER: "X-Encrypted",
@@ -990,6 +1051,11 @@ var TRANSPORT_PROTOCOL = {
990
1051
  /** 密钥协商端点的默认子路径(相对于挂载前缀)。 */
991
1052
  DEFAULT_KEY_EXCHANGE_PATH: "/_hai/key-exchange"
992
1053
  };
1054
+ var TRANSPORT_TIME_LIMITS = Object.freeze({
1055
+ maxFutureSkewSeconds: 300,
1056
+ minRequestAgeSeconds: 1,
1057
+ maxRequestAgeSeconds: 600
1058
+ });
993
1059
 
994
1060
  // src/transport/crypto-transport-codec.ts
995
1061
  function authenticatedText(payload) {
@@ -1034,7 +1100,14 @@ function sealTransportPayload(service, plaintext, context, direction, status, re
1034
1100
  const wrapped = service.asymmetric.encrypt(JSON.stringify([encryptionKey, authenticationKey]), recipientPublicKey);
1035
1101
  if (!encrypted.success || !wrapped.success)
1036
1102
  return transportAuthenticationFailure();
1037
- const payload = { context: { ...context }, direction, status, encryptedKey: wrapped.data, ciphertext: encrypted.data.ciphertext, iv: encrypted.data.iv };
1103
+ const payload = {
1104
+ context: { ...context },
1105
+ direction,
1106
+ status,
1107
+ encryptedKey: wrapped.data,
1108
+ ciphertext: encrypted.data.ciphertext,
1109
+ iv: encrypted.data.iv
1110
+ };
1038
1111
  const text = authenticatedText(payload);
1039
1112
  const tag = service.hash.hmac(text, authenticationKey);
1040
1113
  if (!tag.success)
@@ -1090,29 +1163,57 @@ function sameTransportContext(a, b) {
1090
1163
 
1091
1164
  // src/transport/crypto-transport-error.ts
1092
1165
  var TransportTimeError = class extends Error {
1093
- constructor(context) {
1094
- super(cryptoM("crypto_transportTimeInvalid"));
1166
+ constructor(context, serverTime) {
1167
+ const validTime = typeof serverTime === "number" && Number.isSafeInteger(serverTime) && serverTime >= 0 && serverTime <= 864e13 ? serverTime : void 0;
1168
+ const message = validTime === void 0 ? cryptoM("crypto_transportTimeInvalid") : cryptoM("crypto_transportTimeInvalidWithServerTime", {
1169
+ params: {
1170
+ serverTime: new Date(validTime).toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC")
1171
+ }
1172
+ });
1173
+ super(message);
1095
1174
  this.context = context;
1096
1175
  this.name = "TransportTimeError";
1176
+ this.serverTime = validTime;
1097
1177
  }
1098
1178
  context;
1099
1179
  code = HaiCryptoError.TRANSPORT_TIME_INVALID.code;
1180
+ serverTime;
1100
1181
  };
1101
1182
  async function createTransportErrorResponse(error, authenticated) {
1102
- const status = error instanceof TransportTimeError ? 400 : error.direction === "request" ? 413 : 500;
1103
- const body = JSON.stringify({ success: false, error: { code: error.code, message: error.message, httpStatus: status, details: error instanceof TransportTimeError ? void 0 : {
1104
- direction: error.direction,
1105
- stage: error.stage,
1106
- limitBytes: error.limitBytes,
1107
- actualBytes: error.actualBytes
1108
- } } });
1183
+ const isTimeError = error instanceof TransportTimeError;
1184
+ const status = isTimeError ? 400 : error.direction === "request" ? 413 : 500;
1185
+ const publicError = {
1186
+ code: error.code,
1187
+ message: isTimeError ? cryptoM("crypto_transportTimeInvalid") : error.message,
1188
+ httpStatus: status,
1189
+ details: isTimeError ? void 0 : {
1190
+ direction: error.direction,
1191
+ stage: error.stage,
1192
+ limitBytes: error.limitBytes,
1193
+ actualBytes: error.actualBytes
1194
+ }
1195
+ };
1109
1196
  if (authenticated) {
1197
+ const encryptedError = isTimeError && error.serverTime !== void 0 ? {
1198
+ ...publicError,
1199
+ message: error.message,
1200
+ details: { serverTime: error.serverTime }
1201
+ } : publicError;
1202
+ const body = JSON.stringify({ success: false, error: encryptedError });
1110
1203
  const encrypted = await authenticated.manager.encryptResponse(authenticated.context, body, status);
1111
1204
  if (encrypted.success) {
1112
- return Response.json(encrypted.data, { status, headers: { [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER]: TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE } });
1205
+ const headers = new Headers({ [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER]: TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE });
1206
+ if (authenticated.context.method === "HEAD") {
1207
+ headers.set(TRANSPORT_PROTOCOL.ENVELOPE_HEADER, JSON.stringify(encrypted.data));
1208
+ return new Response(null, { status, headers });
1209
+ }
1210
+ return Response.json(encrypted.data, { status, headers });
1113
1211
  }
1114
1212
  }
1115
- return new Response(body, { status, headers: { "Content-Type": "application/json; charset=utf-8" } });
1213
+ return new Response(JSON.stringify({ success: false, error: publicError }), {
1214
+ status,
1215
+ headers: { "Content-Type": "application/json; charset=utf-8" }
1216
+ });
1116
1217
  }
1117
1218
 
1118
1219
  // src/transport/crypto-transport-client.ts
@@ -1125,7 +1226,9 @@ function createTransportClient(options) {
1125
1226
  let serverKeyId = null;
1126
1227
  let clientId = null;
1127
1228
  let initPromise = null;
1128
- async function doInit() {
1229
+ let generation = 0;
1230
+ let handshakeController;
1231
+ async function doInit(attemptGeneration) {
1129
1232
  const kpResult = options.crypto.asymmetric.generateKeyPair();
1130
1233
  if (!kpResult.success)
1131
1234
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientKeyGenerateFailed"), kpResult.error);
@@ -1133,6 +1236,7 @@ function createTransportClient(options) {
1133
1236
  if (new TextEncoder().encode(localKeyPair.publicKey).byteLength > TRANSPORT_BODY_LIMITS.publicKeyBytes)
1134
1237
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientKeyGenerateFailed"));
1135
1238
  const controller = new AbortController();
1239
+ handshakeController = controller;
1136
1240
  const timeout = setTimeout(() => controller.abort(new DOMException("Key exchange timed out", "TimeoutError")), 3e4);
1137
1241
  try {
1138
1242
  let response;
@@ -1149,7 +1253,9 @@ function createTransportClient(options) {
1149
1253
  if (!response.ok) {
1150
1254
  void response.body?.cancel().catch(() => {
1151
1255
  });
1152
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeHttpFailed", { params: { status: response.status } }));
1256
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeHttpFailed", {
1257
+ params: { status: response.status }
1258
+ }));
1153
1259
  }
1154
1260
  let body;
1155
1261
  try {
@@ -1169,6 +1275,8 @@ function createTransportClient(options) {
1169
1275
  } catch {
1170
1276
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportBodyLimitInvalid"));
1171
1277
  }
1278
+ if (attemptGeneration !== generation || controller.signal.aborted)
1279
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
1172
1280
  keyPair = localKeyPair;
1173
1281
  serverPublicKey = body.serverPublicKey;
1174
1282
  serverKeyId = body.serverKeyId;
@@ -1176,17 +1284,20 @@ function createTransportClient(options) {
1176
1284
  return ok(void 0);
1177
1285
  } finally {
1178
1286
  clearTimeout(timeout);
1287
+ if (handshakeController === controller)
1288
+ handshakeController = void 0;
1179
1289
  }
1180
1290
  }
1181
1291
  async function ensureReady() {
1182
1292
  if (clientId && serverPublicKey)
1183
1293
  return ok(void 0);
1184
1294
  if (!initPromise) {
1185
- initPromise = doInit().then((result) => {
1186
- if (!result.success)
1295
+ const pending = doInit(generation).then((result) => {
1296
+ if (!result.success && initPromise === pending)
1187
1297
  initPromise = null;
1188
1298
  return result;
1189
1299
  });
1300
+ initPromise = pending;
1190
1301
  }
1191
1302
  return initPromise;
1192
1303
  }
@@ -1307,7 +1418,12 @@ function createTransportClient(options) {
1307
1418
  }
1308
1419
  if (envelope && new TextEncoder().encode(envelope).byteLength > TRANSPORT_BODY_LIMITS.envelopeHeaderBytes)
1309
1420
  throw new TransportBodyTooLargeError(TRANSPORT_BODY_LIMITS.envelopeHeaderBytes, new TextEncoder().encode(envelope).byteLength, { direction: "response", stage: "header" });
1310
- payload = JSON.parse(envelope ?? await readTransportBody(response.body, { maxBytes: bodyLimits.encryptedPayloadBytes, direction: "response", stage: "encrypted", signal }));
1421
+ payload = JSON.parse(envelope ?? await readTransportBody(response.body, {
1422
+ maxBytes: bodyLimits.encryptedPayloadBytes,
1423
+ direction: "response",
1424
+ stage: "encrypted",
1425
+ signal
1426
+ }));
1311
1427
  } catch (error) {
1312
1428
  if (error instanceof TransportBodyTooLargeError)
1313
1429
  throw error;
@@ -1330,8 +1446,10 @@ function createTransportClient(options) {
1330
1446
  failure = JSON.parse(decResult.data);
1331
1447
  } catch {
1332
1448
  }
1333
- if (failure?.success === false && failure.error?.code === HaiCryptoError.TRANSPORT_TIME_INVALID.code)
1334
- throw new TransportTimeError();
1449
+ if (failure?.success === false && failure.error?.code === HaiCryptoError.TRANSPORT_TIME_INVALID.code) {
1450
+ const serverTime = failure.error.details?.serverTime;
1451
+ throw new TransportTimeError(void 0, typeof serverTime === "number" ? serverTime : void 0);
1452
+ }
1335
1453
  }
1336
1454
  if (response.status === 500) {
1337
1455
  let failure;
@@ -1360,6 +1478,9 @@ function createTransportClient(options) {
1360
1478
  encryptedFetch,
1361
1479
  ready: () => clientId !== null && serverPublicKey !== null,
1362
1480
  destroy() {
1481
+ generation++;
1482
+ handshakeController?.abort();
1483
+ handshakeController = void 0;
1363
1484
  keyPair = null;
1364
1485
  serverPublicKey = null;
1365
1486
  serverKeyId = null;
@@ -1409,6 +1530,16 @@ function isTransportServerKeyRing(value) {
1409
1530
 
1410
1531
  // src/transport/crypto-transport-server.ts
1411
1532
  function createTransportEncryption(cryptoService, options) {
1533
+ const {
1534
+ maxFutureSkewSeconds = TRANSPORT_PROTOCOL.CLOCK_SKEW_MS / 1e3,
1535
+ maxRequestAgeSeconds = TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS / 1e3
1536
+ } = options ?? {};
1537
+ if (!Number.isInteger(maxFutureSkewSeconds) || maxFutureSkewSeconds < 0 || maxFutureSkewSeconds > TRANSPORT_TIME_LIMITS.maxFutureSkewSeconds || !Number.isInteger(maxRequestAgeSeconds) || maxRequestAgeSeconds < TRANSPORT_TIME_LIMITS.minRequestAgeSeconds || maxRequestAgeSeconds > TRANSPORT_TIME_LIMITS.maxRequestAgeSeconds) {
1538
+ return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportTimeConfigInvalid"));
1539
+ }
1540
+ const futureSkewMs = maxFutureSkewSeconds * 1e3;
1541
+ const requestAgeMs = maxRequestAgeSeconds * 1e3;
1542
+ const nonceRetentionMs = TRANSPORT_TIME_LIMITS.maxRequestAgeSeconds * 1e3 + 2 * TRANSPORT_PROTOCOL.CLOCK_SKEW_MS;
1412
1543
  let bodyLimits;
1413
1544
  try {
1414
1545
  bodyLimits = resolveTransportBodyLimits(options?.maxPlaintextBytes);
@@ -1470,7 +1601,10 @@ function createTransportEncryption(cryptoService, options) {
1470
1601
  try {
1471
1602
  if (!isValidEncryptedPayload(payload, bodyLimits) || payload.direction !== "request" || payload.status !== 0)
1472
1603
  return transportAuthenticationFailure();
1473
- const authenticated = { ...payload, context: { ...payload.context } };
1604
+ const authenticated = {
1605
+ ...payload,
1606
+ context: { ...payload.context }
1607
+ };
1474
1608
  const c = authenticated.context;
1475
1609
  const pathHash = hashTransportPath(cryptoService, expected.path);
1476
1610
  if (!pathHash.success || c.clientId !== expected.clientId || c.method !== expected.method || c.pathHash !== pathHash.data) {
@@ -1483,18 +1617,20 @@ function createTransportEncryption(cryptoService, options) {
1483
1617
  const plaintext = openTransportPayload(cryptoService, authenticated, key.privateKey, clientPublicKey, bodyLimits);
1484
1618
  if (!plaintext.success)
1485
1619
  return plaintext;
1486
- const timeFailure = () => {
1487
- const error = new TransportTimeError(c);
1620
+ const timeFailure = (serverTime) => {
1621
+ const error = new TransportTimeError(c, serverTime);
1488
1622
  return err(HaiCryptoError.TRANSPORT_TIME_INVALID, error.message, error);
1489
1623
  };
1490
1624
  const now = Date.now();
1491
- if (c.issuedAt > now + TRANSPORT_PROTOCOL.CLOCK_SKEW_MS || now >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS)
1492
- return timeFailure();
1493
- if (!await keyStore.claimRequest(c.clientId, c.requestId, c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS + 2 * TRANSPORT_PROTOCOL.CLOCK_SKEW_MS)) {
1625
+ const expiresAt = c.issuedAt + requestAgeMs;
1626
+ if (c.issuedAt > now + futureSkewMs || now >= expiresAt)
1627
+ return timeFailure(now);
1628
+ if (!await keyStore.claimRequest(c.clientId, c.requestId, c.issuedAt + nonceRetentionMs)) {
1494
1629
  return transportAuthenticationFailure();
1495
1630
  }
1496
- if (Date.now() >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS)
1497
- return timeFailure();
1631
+ const claimedAt = Date.now();
1632
+ if (claimedAt >= expiresAt)
1633
+ return timeFailure(claimedAt);
1498
1634
  return plaintext;
1499
1635
  } catch {
1500
1636
  return transportAuthenticationFailure();
@@ -1511,6 +1647,7 @@ function createTransportEncryption(cryptoService, options) {
1511
1647
  var logger = core.logger.child({ module: "crypto", scope: "main" });
1512
1648
  var initialized = false;
1513
1649
  var initInProgress = false;
1650
+ var lifecycleGeneration = 0;
1514
1651
  var currentAsymmetric = null;
1515
1652
  var currentHash = null;
1516
1653
  var currentSymmetric = null;
@@ -1528,7 +1665,10 @@ function getCrypto() {
1528
1665
  }
1529
1666
  var transportOperations = {
1530
1667
  createServer: (options) => getCrypto().isInitialized ? createTransportEncryption(getCrypto(), options) : notInitialized.result(),
1531
- createClient: (options) => createTransportClient({ crypto: getCrypto(), ...options }),
1668
+ createClient: (options) => createTransportClient({
1669
+ crypto: getCrypto(),
1670
+ ...options
1671
+ }),
1532
1672
  protocol: TRANSPORT_PROTOCOL
1533
1673
  };
1534
1674
  var crypto2 = {
@@ -1545,15 +1685,24 @@ var crypto2 = {
1545
1685
  logger.warn("Crypto init already in progress, skipping concurrent call");
1546
1686
  return err(
1547
1687
  HaiCryptoError.INIT_FAILED,
1548
- cryptoM("crypto_initFailed", { params: { error: "Concurrent initialization detected" } })
1688
+ cryptoM("crypto_initFailed", {
1689
+ params: { error: "Concurrent initialization detected" }
1690
+ })
1549
1691
  );
1550
1692
  }
1551
1693
  initInProgress = true;
1694
+ const generation = ++lifecycleGeneration;
1552
1695
  try {
1553
1696
  await Promise.resolve();
1697
+ if (generation !== lifecycleGeneration)
1698
+ return notInitialized.result();
1554
1699
  if (initialized) {
1555
1700
  logger.warn("Crypto module is already initialized, reinitializing");
1556
- await crypto2.close();
1701
+ currentAsymmetric = null;
1702
+ currentHash = null;
1703
+ currentSymmetric = null;
1704
+ currentPassword = null;
1705
+ initialized = false;
1557
1706
  }
1558
1707
  logger.info("Initializing crypto module");
1559
1708
  currentAsymmetric = createSM2();
@@ -1573,7 +1722,9 @@ var crypto2 = {
1573
1722
  return err(
1574
1723
  HaiCryptoError.INIT_FAILED,
1575
1724
  cryptoM("crypto_initFailed", {
1576
- params: { error: error instanceof Error ? error.message : String(error) }
1725
+ params: {
1726
+ error: error instanceof Error ? error.message : String(error)
1727
+ }
1577
1728
  }),
1578
1729
  error
1579
1730
  );
@@ -1613,6 +1764,7 @@ var crypto2 = {
1613
1764
  * 关闭后访问 asymmetric/hash/symmetric/password 会返回 NOT_INITIALIZED 错误。
1614
1765
  */
1615
1766
  async close() {
1767
+ lifecycleGeneration++;
1616
1768
  if (!initialized) {
1617
1769
  logger.info("Crypto module already closed, skipping");
1618
1770
  return;
@@ -1906,7 +2058,10 @@ function createReldbTransportKeyStore(options) {
1906
2058
  }
1907
2059
  async function pruneRows(tx) {
1908
2060
  let removed = 0;
1909
- for (const [table, id, expiry] of [[TRANSPORT_KEY_TABLE, "client_id", "expires_at_ms"], [TRANSPORT_REQUEST_TABLE, "request_key", "expires_at"]]) {
2061
+ for (const [table, id, expiry] of [
2062
+ [TRANSPORT_KEY_TABLE, "client_id", "expires_at_ms"],
2063
+ [TRANSPORT_REQUEST_TABLE, "request_key", "expires_at"]
2064
+ ]) {
1910
2065
  removed += unwrapResult2("tx.execute", await tx.execute(
1911
2066
  `DELETE FROM ${table} WHERE ${expiry} <= ${nowSql()} AND ${id} IN (SELECT ${id} FROM (SELECT ${id} FROM ${table} WHERE ${expiry} <= ${nowSql()} LIMIT ${PRUNE_BATCH_SIZE}) AS expired)`
1912
2067
  )).changes;
@@ -1961,7 +2116,10 @@ function createReldbTransportKeyStore(options) {
1961
2116
  const insertSql = options.reldb.config?.type === "mysql" ? `INSERT IGNORE INTO ${TRANSPORT_REQUEST_TABLE} (request_key, expires_at) VALUES (?, ?)` : `INSERT INTO ${TRANSPORT_REQUEST_TABLE} (request_key, expires_at) VALUES (?, ?) ON CONFLICT (request_key) DO NOTHING`;
1962
2117
  const inserted = unwrapResult2("reldb.sql.execute", await options.reldb.sql.execute(
1963
2118
  insertSql,
1964
- [JSON.stringify([clientId, requestId]), expiresAt]
2119
+ [
2120
+ JSON.stringify([clientId, requestId]),
2121
+ expiresAt
2122
+ ]
1965
2123
  ));
1966
2124
  return inserted.changes === 1;
1967
2125
  },
@@ -1973,6 +2131,6 @@ function createReldbTransportKeyStore(options) {
1973
2131
  };
1974
2132
  }
1975
2133
 
1976
- export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, TransportBodyTooLargeError, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportBodyPolicy, createTransportErrorResponse, crypto2 as crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
2134
+ export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, TRANSPORT_TIME_LIMITS, TransportBodyTooLargeError, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportBodyPolicy, createTransportErrorResponse, crypto2 as crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
1977
2135
  //# sourceMappingURL=index.js.map
1978
2136
  //# sourceMappingURL=index.js.map