@h-ai/crypto 0.1.0-alpha.66 → 0.1.0-alpha.68

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
@@ -35,7 +35,15 @@ var en_US_default = {
35
35
  crypto_hashFormatInvalid: "Invalid hash format",
36
36
  crypto_passwordVerifyFailed: "Password verification failed",
37
37
  crypto_transportClientKeyGenerateFailed: "Failed to generate transport client key pair",
38
- crypto_transportServerKeyGenerateFailed: "Failed to generate transport server key pair",
38
+ crypto_transportServerKeyRingInvalid: "Transport server key ring is missing or invalid",
39
+ crypto_transportServerKeyUnknown: "Transport server key version is unknown or revoked",
40
+ crypto_transportKeyStoreRequired: "Transport encryption requires an explicit client public key store",
41
+ crypto_transportKeyStoreTtlInvalid: "Public-key store TTL must be an integer between 1 and 2147483647 seconds",
42
+ crypto_transportKeyStoreCapacityInvalid: "Public-key store capacity must be a positive safe integer",
43
+ crypto_transportKeyStoreCapacityMismatch: "Public-key store shared capacity settings do not match",
44
+ crypto_transportKeyStoreCapacityReached: "Public-key store capacity has been reached",
45
+ crypto_transportKeyStoreOperationFailed: "Public-key store operation failed",
46
+ crypto_transportAuthenticationFailed: "Transport authentication, request binding or replay validation failed",
39
47
  crypto_transportKeyExchangeRequestFailed: "Transport key exchange request failed",
40
48
  crypto_transportKeyExchangeHttpFailed: "Transport key exchange returned HTTP {status}",
41
49
  crypto_transportKeyExchangeInvalidJson: "Transport key exchange response is not valid JSON",
@@ -50,6 +58,61 @@ var en_US_default = {
50
58
  crypto_transportSessionKeyDecryptFailed: "Failed to decrypt transport session key"
51
59
  };
52
60
 
61
+ // messages/ja-JP.json
62
+ var ja_JP_default = {
63
+ $schema: "https://inlang.com/schema/inlang-message-format",
64
+ crypto_notInitialized: "\u6697\u53F7\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bcrypto.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
65
+ crypto_initFailed: "\u6697\u53F7\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
66
+ crypto_sm2PublicKeyInvalid: "SM2\u516C\u958B\u9375\u306E\u5F62\u5F0F\u304C\u7121\u52B9\u3067\u3059",
67
+ crypto_sm2PrivateKeyInvalid: "SM2\u79D8\u5BC6\u9375\u306E\u5F62\u5F0F\u304C\u7121\u52B9\u3067\u3059",
68
+ crypto_sm2EncryptEmpty: "SM2\u6697\u53F7\u5316\u306E\u7D50\u679C\u304C\u7A7A\u3067\u3059",
69
+ crypto_sm2DecryptFailed: "SM2\u5FA9\u53F7\u306B\u5931\u6557\u3057\u305F\u304B\u3001\u7D50\u679C\u304C\u7121\u52B9\u3067\u3059",
70
+ crypto_sm2SignEmpty: "SM2\u7F72\u540D\u306E\u7D50\u679C\u304C\u7A7A\u3067\u3059",
71
+ crypto_sm2KeyPairGenerateFailed: "SM2\u9375\u30DA\u30A2\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
72
+ crypto_sm2EncryptFailed: "SM2\u6697\u53F7\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
73
+ crypto_sm2DecryptFailedWithError: "SM2\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
74
+ crypto_sm2SignFailed: "SM2\u7F72\u540D\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
75
+ crypto_sm2VerifyFailed: "SM2\u7F72\u540D\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
76
+ crypto_sm3HashEmpty: "SM3\u30CF\u30C3\u30B7\u30E5\u306E\u7D50\u679C\u304C\u7A7A\u3067\u3059",
77
+ crypto_sm3HashFailed: "SM3\u30CF\u30C3\u30B7\u30E5\u306E\u8A08\u7B97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
78
+ crypto_sm3HmacFailed: "HMAC-SM3\u306E\u8A08\u7B97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
79
+ crypto_sm3VerifyFailed: "SM3\u30CF\u30C3\u30B7\u30E5\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
80
+ crypto_sm4KeyInvalid: "SM4\u9375\u306F16\u30D0\u30A4\u30C8\uFF0816\u9032\u657032\u6587\u5B57\uFF09\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059",
81
+ crypto_sm4CbcNeedIv: "CBC\u30E2\u30FC\u30C9\u306B\u306FIV\u304C\u5FC5\u8981\u3067\u3059",
82
+ crypto_sm4IvInvalid: "SM4\u306EIV\u306F16\u30D0\u30A4\u30C8\uFF0816\u9032\u657032\u6587\u5B57\uFF09\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059",
83
+ crypto_sm4EncryptEmpty: "SM4\u6697\u53F7\u5316\u306E\u7D50\u679C\u304C\u7A7A\u3067\u3059",
84
+ crypto_sm4DecryptFailed: "SM4\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
85
+ crypto_sm4EncryptFailed: "SM4\u6697\u53F7\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
86
+ crypto_sm4DecryptFailedWithError: "SM4\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
87
+ crypto_passwordEmpty: "\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u7A7A\u306B\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093",
88
+ crypto_passwordHashEmpty: "\u30D1\u30B9\u30EF\u30FC\u30C9\u3068\u30CF\u30C3\u30B7\u30E5\u3092\u7A7A\u306B\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093",
89
+ crypto_passwordHashFailed: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u30CF\u30C3\u30B7\u30E5\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
90
+ crypto_hashFormatInvalid: "\u30CF\u30C3\u30B7\u30E5\u306E\u5F62\u5F0F\u304C\u7121\u52B9\u3067\u3059",
91
+ crypto_passwordVerifyFailed: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
92
+ crypto_transportClientKeyGenerateFailed: "\u901A\u4FE1\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306E\u9375\u30DA\u30A2\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
93
+ crypto_transportServerKeyRingInvalid: "\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u306E\u30B5\u30FC\u30D0\u30FC\u30AD\u30FC\u30EA\u30F3\u30B0\u304C\u672A\u8A2D\u5B9A\u307E\u305F\u306F\u7121\u52B9\u3067\u3059",
94
+ crypto_transportServerKeyUnknown: "\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u306E\u30B5\u30FC\u30D0\u30FC\u30AD\u30FC\u30D0\u30FC\u30B8\u30E7\u30F3\u304C\u4E0D\u660E\u3001\u307E\u305F\u306F\u5931\u52B9\u3057\u3066\u3044\u307E\u3059",
95
+ crypto_transportKeyStoreRequired: "\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u6697\u53F7\u5316\u306B\u306F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u516C\u958B\u30AD\u30FC\u30B9\u30C8\u30A2\u306E\u660E\u793A\u7684\u306A\u6307\u5B9A\u304C\u5FC5\u8981\u3067\u3059",
96
+ crypto_transportKeyStoreTtlInvalid: "\u516C\u958B\u30AD\u30FC\u30B9\u30C8\u30A2\u306E TTL \u306F1\u301C2147483647\u79D2\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
97
+ crypto_transportKeyStoreCapacityInvalid: "\u516C\u958B\u30AD\u30FC\u30B9\u30C8\u30A2\u306E\u5BB9\u91CF\u306F\u6B63\u306E\u5B89\u5168\u306A\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
98
+ crypto_transportKeyStoreCapacityMismatch: "\u516C\u958B\u30AD\u30FC\u30B9\u30C8\u30A2\u306E\u5171\u6709\u5BB9\u91CF\u8A2D\u5B9A\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093",
99
+ crypto_transportKeyStoreCapacityReached: "\u516C\u958B\u30AD\u30FC\u30B9\u30C8\u30A2\u306E\u5BB9\u91CF\u4E0A\u9650\u306B\u9054\u3057\u307E\u3057\u305F",
100
+ crypto_transportKeyStoreOperationFailed: "\u516C\u958B\u30AD\u30FC\u30B9\u30C8\u30A2\u306E\u64CD\u4F5C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
101
+ crypto_transportAuthenticationFailed: "\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u8A8D\u8A3C\u3001\u30EA\u30AF\u30A8\u30B9\u30C8\u3068\u306E\u7D10\u4ED8\u3051\u3001\u307E\u305F\u306F\u30EA\u30D7\u30EC\u30A4\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
102
+ crypto_transportKeyExchangeRequestFailed: "\u901A\u4FE1\u306E\u9375\u4EA4\u63DB\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
103
+ crypto_transportKeyExchangeHttpFailed: "\u901A\u4FE1\u306E\u9375\u4EA4\u63DB\u3067HTTP {status}\u304C\u8FD4\u3055\u308C\u307E\u3057\u305F",
104
+ crypto_transportKeyExchangeInvalidJson: "\u901A\u4FE1\u306E\u9375\u4EA4\u63DB\u30EC\u30B9\u30DD\u30F3\u30B9\u304C\u6709\u52B9\u306AJSON\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
105
+ crypto_transportKeyExchangeMissingFields: "\u901A\u4FE1\u306E\u9375\u4EA4\u63DB\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u5FC5\u9808\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u305B\u3093",
106
+ crypto_transportClientNotInitialized: "\u901A\u4FE1\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306E\u9375\u4EA4\u63DB\u304C\u5B8C\u4E86\u3057\u3066\u3044\u307E\u305B\u3093",
107
+ crypto_transportClientNotRegistered: "\u901A\u4FE1\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u306A\u3044\u304B\u3001\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059",
108
+ crypto_transportRequestEncryptFailed: "\u901A\u4FE1\u30EA\u30AF\u30A8\u30B9\u30C8\u672C\u6587\u306E\u6697\u53F7\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
109
+ crypto_transportRequestDecryptFailed: "\u901A\u4FE1\u30EA\u30AF\u30A8\u30B9\u30C8\u672C\u6587\u306E\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
110
+ crypto_transportResponseEncryptFailed: "\u901A\u4FE1\u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u6587\u306E\u6697\u53F7\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
111
+ crypto_transportResponseDecryptFailed: "\u901A\u4FE1\u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u6587\u306E\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
112
+ crypto_transportSessionKeyEncryptFailed: "\u901A\u4FE1\u30BB\u30C3\u30B7\u30E7\u30F3\u9375\u306E\u6697\u53F7\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
113
+ crypto_transportSessionKeyDecryptFailed: "\u901A\u4FE1\u30BB\u30C3\u30B7\u30E7\u30F3\u9375\u306E\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
114
+ };
115
+
53
116
  // messages/zh-CN.json
54
117
  var zh_CN_default = {
55
118
  $schema: "https://inlang.com/schema/inlang-message-format",
@@ -82,7 +145,15 @@ var zh_CN_default = {
82
145
  crypto_hashFormatInvalid: "\u65E0\u6548\u7684\u54C8\u5E0C\u683C\u5F0F",
83
146
  crypto_passwordVerifyFailed: "\u5BC6\u7801\u9A8C\u8BC1\u5931\u8D25",
84
147
  crypto_transportClientKeyGenerateFailed: "\u4F20\u8F93\u52A0\u5BC6\u5BA2\u6237\u7AEF\u5BC6\u94A5\u751F\u6210\u5931\u8D25",
85
- crypto_transportServerKeyGenerateFailed: "\u4F20\u8F93\u52A0\u5BC6\u670D\u52A1\u7AEF\u5BC6\u94A5\u751F\u6210\u5931\u8D25",
148
+ crypto_transportServerKeyRingInvalid: "\u670D\u52A1\u7AEF\u4F20\u8F93\u5BC6\u94A5\u73AF\u7F3A\u5931\u6216\u65E0\u6548",
149
+ crypto_transportServerKeyUnknown: "\u670D\u52A1\u7AEF\u4F20\u8F93\u5BC6\u94A5\u7248\u672C\u672A\u77E5\u6216\u5DF2\u64A4\u9500",
150
+ crypto_transportKeyStoreRequired: "\u4F20\u8F93\u52A0\u5BC6\u5FC5\u987B\u663E\u5F0F\u63D0\u4F9B\u5BA2\u6237\u7AEF\u516C\u94A5\u5B58\u50A8",
151
+ crypto_transportKeyStoreTtlInvalid: "\u516C\u94A5\u5B58\u50A8 TTL \u5FC5\u987B\u4E3A 1 \u5230 2147483647 \u4E4B\u95F4\u7684\u6574\u6570\u79D2",
152
+ crypto_transportKeyStoreCapacityInvalid: "\u516C\u94A5\u5B58\u50A8\u5BB9\u91CF\u5FC5\u987B\u4E3A\u6B63\u5B89\u5168\u6574\u6570",
153
+ crypto_transportKeyStoreCapacityMismatch: "\u516C\u94A5\u5B58\u50A8\u7684\u5171\u4EAB\u5BB9\u91CF\u914D\u7F6E\u4E0D\u4E00\u81F4",
154
+ crypto_transportKeyStoreCapacityReached: "\u516C\u94A5\u5B58\u50A8\u5BB9\u91CF\u5DF2\u6EE1",
155
+ crypto_transportKeyStoreOperationFailed: "\u516C\u94A5\u5B58\u50A8\u64CD\u4F5C\u5931\u8D25",
156
+ crypto_transportAuthenticationFailed: "\u4F20\u8F93\u6D88\u606F\u8BA4\u8BC1\u3001\u8BF7\u6C42\u7ED1\u5B9A\u6216\u9632\u91CD\u653E\u6821\u9A8C\u5931\u8D25",
86
157
  crypto_transportKeyExchangeRequestFailed: "\u4F20\u8F93\u52A0\u5BC6\u5BC6\u94A5\u534F\u5546\u8BF7\u6C42\u5931\u8D25",
87
158
  crypto_transportKeyExchangeHttpFailed: "\u4F20\u8F93\u52A0\u5BC6\u5BC6\u94A5\u534F\u5546\u8FD4\u56DE HTTP {status}",
88
159
  crypto_transportKeyExchangeInvalidJson: "\u4F20\u8F93\u52A0\u5BC6\u5BC6\u94A5\u534F\u5546\u54CD\u5E94\u4E0D\u662F\u6709\u6548 JSON",
@@ -100,7 +171,8 @@ var zh_CN_default = {
100
171
  // src/crypto-i18n.ts
101
172
  var cryptoM = core.i18n.createMessageGetter({
102
173
  "zh-CN": zh_CN_default,
103
- "en-US": en_US_default
174
+ "en-US": en_US_default,
175
+ "ja-JP": ja_JP_default
104
176
  });
105
177
  var CryptoErrorInfo = {
106
178
  INVALID_INPUT: "002:400",
@@ -241,6 +313,13 @@ function createPasswordFunctions(deps) {
241
313
  }
242
314
  };
243
315
  }
316
+ var SM2_PUBLIC_KEY_REGEX = /^(?:04)?[0-9a-f]{128}$/i;
317
+ function isValidSM2PublicKey(publicKey) {
318
+ if (typeof publicKey !== "string" || publicKey.length !== 128 && publicKey.length !== 130 || !SM2_PUBLIC_KEY_REGEX.test(publicKey))
319
+ return false;
320
+ const encoded = publicKey.length === 128 ? `04${publicKey}` : publicKey;
321
+ return smCrypto.sm2.verifyPublicKey(encoded) === true;
322
+ }
244
323
 
245
324
  // src/crypto-utils.ts
246
325
  function isBase64(str) {
@@ -264,7 +343,6 @@ function base64ToHex(base64) {
264
343
 
265
344
  // src/crypto-sm2.ts
266
345
  var { sm2 } = smCrypto;
267
- var SM2_PUBLIC_KEY_REGEX = /^[0-9a-f]{128}$/i;
268
346
  var SM2_PRIVATE_KEY_REGEX = /^[0-9a-f]{64}$/i;
269
347
  function createSM2() {
270
348
  return {
@@ -307,7 +385,7 @@ function createSM2() {
307
385
  );
308
386
  }
309
387
  try {
310
- const key = publicKey.startsWith("04") ? publicKey : `04${publicKey}`;
388
+ const key = publicKey.length === 130 ? publicKey : `04${publicKey}`;
311
389
  const encrypted = sm2.doEncrypt(data, key, cipherMode);
312
390
  if (!encrypted) {
313
391
  return err(
@@ -421,7 +499,7 @@ function createSM2() {
421
499
  );
422
500
  }
423
501
  try {
424
- const key = publicKey.startsWith("04") ? publicKey : `04${publicKey}`;
502
+ const key = publicKey.length === 130 ? publicKey : `04${publicKey}`;
425
503
  const isValid = sm2.doVerifySignature(data, signature, key, { hash, userId });
426
504
  return ok(!!isValid);
427
505
  } catch (error) {
@@ -433,18 +511,16 @@ function createSM2() {
433
511
  }
434
512
  },
435
513
  /**
436
- * 校验公钥格式是否合法
514
+ * 校验公钥编码及曲线点是否合法
437
515
  *
438
516
  * 合法格式:128 字符十六进制(无前缀)或 130 字符(含 04 前缀)。
439
517
  *
440
518
  * @param key - 待校验公钥
441
- * @returns 格式合法返回 true
519
+ * @returns 编码有效且为 SM2 曲线上的非无穷远点时返回 true
520
+ * @example `crypto.asymmetric.isValidPublicKey(clientPublicKey)`
442
521
  */
443
522
  isValidPublicKey(key) {
444
- if (!key || typeof key !== "string")
445
- return false;
446
- const cleanKey = key.startsWith("04") ? key.slice(2) : key;
447
- return SM2_PUBLIC_KEY_REGEX.test(cleanKey);
523
+ return isValidSM2PublicKey(key);
448
524
  },
449
525
  /**
450
526
  * 校验私钥格式是否合法
@@ -573,7 +649,7 @@ function stringToBytes(str) {
573
649
  const encoder = new TextEncoder();
574
650
  return Array.from(encoder.encode(str));
575
651
  }
576
- var { sm3: sm32, sm4 } = smCrypto;
652
+ var { sm4 } = smCrypto;
577
653
  var SM4_HEX_32_REGEX = /^[0-9a-f]{32}$/i;
578
654
  function createSM4() {
579
655
  return {
@@ -591,7 +667,7 @@ function createSM4() {
591
667
  * 默认使用 CBC 并自动生成随机 IV,返回结构化字段。
592
668
  *
593
669
  * ⚠️ 安全警告:ECB 模式会让相同明文块产生相同密文块,泄漏结构信息。
594
- * 生产场景请使用默认 CBC,或显式传入 `{ mode: 'cbc', iv }`。
670
+ * 新数据使用默认 CBC,或每次显式传入新的随机 IV;CBC 仍需独立完整性认证,HTTP 使用 transport。
595
671
  *
596
672
  * @param data - 待加密明文
597
673
  * @param key - 密钥(32 字符十六进制)
@@ -701,7 +777,7 @@ function createSM4() {
701
777
  *
702
778
  * @param data - 待加密明文
703
779
  * @param key - 密钥(32 字符十六进制)
704
- * @returns 成功时返回 { ciphertext, iv };失败时同 encrypt
780
+ * @returns 成功时返回完整 { mode, ciphertext, iv, encoding };失败时同 encrypt
705
781
  */
706
782
  encryptWithIV(data, key) {
707
783
  const iv = this.generateIV();
@@ -719,6 +795,8 @@ function createSM4() {
719
795
  /**
720
796
  * 带 IV 解密(CBC 模式)
721
797
  *
798
+ * 这是不含认证的底层 primitive;传输数据必须经 transport 验签及 MAC 后再调用,禁止直接用于 HTTP 解密。
799
+ *
722
800
  * @param ciphertext - 密文
723
801
  * @param key - 密钥
724
802
  * @param iv - 加密时使用的 IV
@@ -727,26 +805,6 @@ function createSM4() {
727
805
  decryptWithIV(ciphertext, key, iv) {
728
806
  return this.decrypt({ mode: "cbc", ciphertext, iv, encoding: "hex" }, key);
729
807
  },
730
- /**
731
- * 从密码和盐值派生密钥
732
- *
733
- * 内部仅执行单次 SM3 哈希(password + salt),取前 32 字符作为密钥。
734
- *
735
- * ⚠️ 安全警告:**此实现不是标准 KDF**,不具备密码爆破抗性(无迭代、无内存硬化)。
736
- * - **禁止用于密码存储**:密码散列请用 `crypto.password.hash()`。
737
- * - **禁止用于高价值密钥派生**:如需从密码派生加密密钥,请在应用层自行实现 PBKDF2 / scrypt / Argon2。
738
- * - 仅适用于测试、迭代兼容或不敏感的场景。
739
- *
740
- * @deprecated 未来版本可能移除。密码散列请用 `crypto.password.hash`;密钥派生请使用标准 KDF。
741
- * @param password - 密码
742
- * @param salt - 盐值
743
- * @returns 32 字符十六进制密钥
744
- */
745
- deriveKey(password, salt) {
746
- const combined = password + salt;
747
- const hash = sm32(combined);
748
- return hash.slice(0, 32);
749
- },
750
808
  /** 校验密钥格式是否合法(32 字符十六进制) */
751
809
  isValidKey(key) {
752
810
  return SM4_HEX_32_REGEX.test(key);
@@ -769,10 +827,81 @@ function decodeCiphertext(payload) {
769
827
  return payload.encoding === "base64" ? base64ToHex(payload.ciphertext) : payload.ciphertext;
770
828
  }
771
829
 
830
+ // src/transport/crypto-transport-body.ts
831
+ var TRANSPORT_BODY_LIMITS = {
832
+ /** 密钥协商 JSON 的 UTF-8 总字节数。 */
833
+ keyExchangeBytes: 16384,
834
+ /** GET/HEAD 请求及无正文响应的认证头封装 UTF-8 上限。 */
835
+ envelopeHeaderBytes: 16384,
836
+ /** 单个协商公钥的 UTF-8 字节数。 */
837
+ publicKeyBytes: 4096,
838
+ /** 解密后的请求/加密前的响应明文字节数。 */
839
+ plaintextBytes: 1048576,
840
+ /** 密文 JSON 的 UTF-8 总字节数(2 MiB + 32 KiB,含 CBC 填充与全部认证字段)。 */
841
+ encryptedPayloadBytes: 2129920
842
+ };
843
+ async function readTransportBody(stream, options) {
844
+ if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0)
845
+ throw new RangeError("Invalid transport body byte limit");
846
+ if (!stream) {
847
+ options.signal?.throwIfAborted();
848
+ return "";
849
+ }
850
+ const reader = stream.getReader();
851
+ const signal = options.signal;
852
+ const decoder = new TextDecoder("utf-8", { fatal: true });
853
+ let completed = false;
854
+ let failure;
855
+ let onAbort;
856
+ try {
857
+ signal?.throwIfAborted();
858
+ const aborted = signal ? new Promise((_resolve, reject) => {
859
+ onAbort = () => reject(signal.reason);
860
+ signal.addEventListener("abort", onAbort, { once: true });
861
+ }) : void 0;
862
+ let bytes = 0;
863
+ let text = "";
864
+ while (true) {
865
+ const next = reader.read();
866
+ const chunk = await (aborted ? Promise.race([next, aborted]) : next);
867
+ if (chunk.done) {
868
+ completed = true;
869
+ return text + decoder.decode();
870
+ }
871
+ bytes += chunk.value.byteLength;
872
+ if (bytes > options.maxBytes)
873
+ throw new RangeError("Transport body byte limit exceeded");
874
+ text += decoder.decode(chunk.value, { stream: true });
875
+ }
876
+ } catch (error) {
877
+ failure = error;
878
+ throw error;
879
+ } finally {
880
+ if (onAbort)
881
+ signal?.removeEventListener("abort", onAbort);
882
+ if (!completed) {
883
+ try {
884
+ void reader.cancel(failure).catch(() => {
885
+ });
886
+ } catch {
887
+ }
888
+ }
889
+ reader.releaseLock();
890
+ }
891
+ }
892
+
772
893
  // src/transport/crypto-transport-types.ts
773
894
  var TRANSPORT_PROTOCOL = {
774
895
  /** 客户端 ID 请求头名。 */
775
896
  CLIENT_ID_HEADER: "X-Client-Id",
897
+ /** GET/HEAD 等无正文请求的认证封装;禁止与正文封装同时出现。 */
898
+ ENVELOPE_HEADER: "X-Hai-Transport",
899
+ /** 唯一支持的认证协议版本;旧未认证格式拒绝。 */
900
+ VERSION: 2,
901
+ /** 请求允许的过去时间窗口(毫秒)。 */
902
+ REQUEST_WINDOW_MS: 3e5,
903
+ /** 节点时钟允许的最大未来偏差(毫秒)。 */
904
+ CLOCK_SKEW_MS: 3e4,
776
905
  /** 标识响应体已加密的响应头名。 */
777
906
  ENCRYPTED_HEADER: "X-Encrypted",
778
907
  /** `X-Encrypted` 响应头的开启值。 */
@@ -781,11 +910,103 @@ var TRANSPORT_PROTOCOL = {
781
910
  DEFAULT_KEY_EXCHANGE_PATH: "/_hai/key-exchange"
782
911
  };
783
912
 
913
+ // src/transport/crypto-transport-codec.ts
914
+ function authenticatedText(payload) {
915
+ const c = payload.context;
916
+ return JSON.stringify([
917
+ "hai-transport",
918
+ c.version,
919
+ payload.direction,
920
+ c.serverKeyId,
921
+ c.clientId,
922
+ c.requestId,
923
+ c.issuedAt,
924
+ c.method,
925
+ c.pathHash,
926
+ payload.status,
927
+ payload.encryptedKey,
928
+ payload.ciphertext,
929
+ payload.iv
930
+ ]);
931
+ }
932
+ function transportAuthenticationFailure() {
933
+ return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportAuthenticationFailed"));
934
+ }
935
+ function hashTransportPath(service, path) {
936
+ try {
937
+ if (!path.startsWith("/") || new TextEncoder().encode(path).byteLength > 8192)
938
+ return transportAuthenticationFailure();
939
+ const digest = service.hash.hash(path);
940
+ return digest.success && /^[a-f0-9]{64}$/.test(digest.data) ? digest : transportAuthenticationFailure();
941
+ } catch {
942
+ return transportAuthenticationFailure();
943
+ }
944
+ }
945
+ function sealTransportPayload(service, plaintext, context, direction, status, recipientPublicKey, senderPrivateKey) {
946
+ try {
947
+ const encryptionKey = service.symmetric.generateKey();
948
+ const authenticationKey = service.symmetric.generateKey() + service.symmetric.generateKey();
949
+ const encrypted = service.symmetric.encryptWithIV(plaintext, encryptionKey);
950
+ const wrapped = service.asymmetric.encrypt(JSON.stringify([encryptionKey, authenticationKey]), recipientPublicKey);
951
+ if (!encrypted.success || !wrapped.success)
952
+ return transportAuthenticationFailure();
953
+ const payload = { context: { ...context }, direction, status, encryptedKey: wrapped.data, ciphertext: encrypted.data.ciphertext, iv: encrypted.data.iv };
954
+ const text = authenticatedText(payload);
955
+ const tag = service.hash.hmac(text, authenticationKey);
956
+ if (!tag.success)
957
+ return transportAuthenticationFailure();
958
+ const signature = service.asymmetric.sign(JSON.stringify([text, tag.data]), senderPrivateKey);
959
+ if (!signature.success)
960
+ return transportAuthenticationFailure();
961
+ const authenticated = { ...payload, tag: tag.data, signature: signature.data };
962
+ return isValidEncryptedPayload(authenticated) ? ok(authenticated) : transportAuthenticationFailure();
963
+ } catch {
964
+ return transportAuthenticationFailure();
965
+ }
966
+ }
967
+ function openTransportPayload(service, payload, recipientPrivateKey, senderPublicKey) {
968
+ try {
969
+ if (!isValidEncryptedPayload(payload))
970
+ return transportAuthenticationFailure();
971
+ const text = authenticatedText(payload);
972
+ const verified = service.asymmetric.verify(JSON.stringify([text, payload.tag]), payload.signature, senderPublicKey);
973
+ if (!verified.success || !verified.data)
974
+ return transportAuthenticationFailure();
975
+ const unwrapped = service.asymmetric.decrypt(payload.encryptedKey, recipientPrivateKey);
976
+ if (!unwrapped.success)
977
+ return transportAuthenticationFailure();
978
+ const keys = JSON.parse(unwrapped.data);
979
+ if (!Array.isArray(keys) || keys.length !== 2 || typeof keys[0] !== "string" || !/^[a-f0-9]{32}$/.test(keys[0]) || typeof keys[1] !== "string" || !/^[a-f0-9]{64}$/.test(keys[1])) {
980
+ return transportAuthenticationFailure();
981
+ }
982
+ const tag = service.hash.hmac(text, keys[1]);
983
+ if (!tag.success || !core.string.constantTimeEqual(tag.data, payload.tag))
984
+ return transportAuthenticationFailure();
985
+ const plaintext = service.symmetric.decryptWithIV(payload.ciphertext, keys[0], payload.iv);
986
+ if (!plaintext.success)
987
+ return transportAuthenticationFailure();
988
+ return plaintext;
989
+ } catch {
990
+ return transportAuthenticationFailure();
991
+ }
992
+ }
993
+ function isValidEncryptedPayload(value) {
994
+ if (!value || typeof value !== "object")
995
+ return false;
996
+ const p = value;
997
+ const c = p.context;
998
+ return Object.keys(p).length === 8 && !!c && typeof c === "object" && Object.keys(c).length === 7 && c.version === TRANSPORT_PROTOCOL.VERSION && typeof c.serverKeyId === "string" && /^[\w-]{1,64}$/.test(c.serverKeyId) && typeof c.clientId === "string" && /^[\w-]{1,128}$/.test(c.clientId) && typeof c.requestId === "string" && /^[a-f0-9]{32}$/.test(c.requestId) && Number.isSafeInteger(c.issuedAt) && c.issuedAt > 0 && typeof c.method === "string" && /^[A-Z]{1,16}$/.test(c.method) && typeof c.pathHash === "string" && /^[a-f0-9]{64}$/.test(c.pathHash) && (p.direction === "request" || p.direction === "response") && Number.isInteger(p.status) && (p.direction === "request" ? p.status === 0 : Number(p.status) >= 100 && Number(p.status) <= 599) && typeof p.encryptedKey === "string" && /^[a-f0-9]{1,8192}$/i.test(p.encryptedKey) && typeof p.ciphertext === "string" && p.ciphertext.length <= TRANSPORT_BODY_LIMITS.encryptedPayloadBytes && p.ciphertext.length > 0 && p.ciphertext.length % 32 === 0 && /^[a-f0-9]+$/i.test(p.ciphertext) && typeof p.iv === "string" && /^[a-f0-9]{32}$/i.test(p.iv) && typeof p.tag === "string" && /^[a-f0-9]{64}$/.test(p.tag) && typeof p.signature === "string" && /^[a-f0-9]{128}$/i.test(p.signature);
999
+ }
1000
+ function sameTransportContext(a, b) {
1001
+ return a.version === b.version && a.serverKeyId === b.serverKeyId && a.clientId === b.clientId && a.requestId === b.requestId && a.issuedAt === b.issuedAt && a.method === b.method && a.pathHash === b.pathHash;
1002
+ }
1003
+
784
1004
  // src/transport/crypto-transport-client.ts
785
1005
  function createTransportClient(options) {
786
1006
  const baseFetch = options.fetch ?? fetch;
787
1007
  let keyPair = null;
788
1008
  let serverPublicKey = null;
1009
+ let serverKeyId = null;
789
1010
  let clientId = null;
790
1011
  let initPromise = null;
791
1012
  async function doInit() {
@@ -793,30 +1014,47 @@ function createTransportClient(options) {
793
1014
  if (!kpResult.success)
794
1015
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientKeyGenerateFailed"), kpResult.error);
795
1016
  const localKeyPair = kpResult.data;
796
- let response;
797
- try {
798
- response = await baseFetch(options.keyExchangeUrl, {
799
- method: "POST",
800
- headers: { "Content-Type": "application/json" },
801
- body: JSON.stringify({ clientPublicKey: localKeyPair.publicKey })
802
- });
803
- } catch (cause) {
804
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeRequestFailed"), cause);
805
- }
806
- if (!response.ok)
807
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeHttpFailed", { params: { status: response.status } }));
808
- let body;
1017
+ if (new TextEncoder().encode(localKeyPair.publicKey).byteLength > TRANSPORT_BODY_LIMITS.publicKeyBytes)
1018
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientKeyGenerateFailed"));
1019
+ const controller = new AbortController();
1020
+ const timeout = setTimeout(() => controller.abort(new DOMException("Key exchange timed out", "TimeoutError")), 3e4);
809
1021
  try {
810
- body = await response.json();
811
- } catch (cause) {
812
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeInvalidJson"), cause);
1022
+ let response;
1023
+ try {
1024
+ response = await baseFetch(options.keyExchangeUrl, {
1025
+ method: "POST",
1026
+ headers: { "Content-Type": "application/json" },
1027
+ body: JSON.stringify({ clientPublicKey: localKeyPair.publicKey }),
1028
+ signal: controller.signal
1029
+ });
1030
+ } catch (cause) {
1031
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeRequestFailed"), cause);
1032
+ }
1033
+ if (!response.ok) {
1034
+ void response.body?.cancel().catch(() => {
1035
+ });
1036
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeHttpFailed", { params: { status: response.status } }));
1037
+ }
1038
+ let body;
1039
+ try {
1040
+ body = JSON.parse(await readTransportBody(response.body, {
1041
+ maxBytes: TRANSPORT_BODY_LIMITS.keyExchangeBytes,
1042
+ signal: controller.signal
1043
+ }));
1044
+ } catch {
1045
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeInvalidJson"));
1046
+ }
1047
+ if (!isKeyExchangeResponse(body)) {
1048
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeMissingFields"));
1049
+ }
1050
+ keyPair = localKeyPair;
1051
+ serverPublicKey = body.serverPublicKey;
1052
+ serverKeyId = body.serverKeyId;
1053
+ clientId = body.clientId;
1054
+ return ok(void 0);
1055
+ } finally {
1056
+ clearTimeout(timeout);
813
1057
  }
814
- if (!body.serverPublicKey || !body.clientId)
815
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeMissingFields"));
816
- keyPair = localKeyPair;
817
- serverPublicKey = body.serverPublicKey;
818
- clientId = body.clientId;
819
- return ok(void 0);
820
1058
  }
821
1059
  async function ensureReady() {
822
1060
  if (clientId && serverPublicKey)
@@ -830,66 +1068,91 @@ function createTransportClient(options) {
830
1068
  }
831
1069
  return initPromise;
832
1070
  }
833
- function encryptBody(plaintext) {
834
- if (!serverPublicKey)
1071
+ function encryptBody(plaintext, context) {
1072
+ if (!serverPublicKey || !serverKeyId || !keyPair)
835
1073
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
836
- const symKey = options.crypto.symmetric.generateKey();
837
- const encResult = options.crypto.symmetric.encryptWithIV(plaintext, symKey);
838
- if (!encResult.success)
839
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportRequestEncryptFailed"), encResult.error);
840
- const keyEncResult = options.crypto.asymmetric.encrypt(symKey, serverPublicKey);
841
- if (!keyEncResult.success)
842
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportSessionKeyEncryptFailed"), keyEncResult.error);
843
- return ok({
844
- encryptedKey: keyEncResult.data,
845
- ciphertext: encResult.data.ciphertext,
846
- iv: encResult.data.iv
847
- });
1074
+ return sealTransportPayload(options.crypto, plaintext, context, "request", 0, serverPublicKey, keyPair.privateKey);
848
1075
  }
849
1076
  function decryptPayload(payload) {
850
- if (!keyPair)
1077
+ if (!keyPair || !serverPublicKey)
851
1078
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
852
- const keyDec = options.crypto.asymmetric.decrypt(payload.encryptedKey, keyPair.privateKey);
853
- if (!keyDec.success)
854
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportSessionKeyDecryptFailed"), keyDec.error);
855
- const dec = options.crypto.symmetric.decryptWithIV(payload.ciphertext, keyDec.data, payload.iv);
856
- if (!dec.success)
857
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportResponseDecryptFailed"), dec.error);
858
- return ok(dec.data);
1079
+ return openTransportPayload(options.crypto, payload, keyPair.privateKey, serverPublicKey);
859
1080
  }
860
1081
  const encryptedFetch = async (input, init) => {
861
1082
  const requestInput = isRequestLike(input) ? input : void 0;
1083
+ const signal = init?.signal ?? requestInput?.signal ?? void 0;
1084
+ signal?.throwIfAborted();
862
1085
  const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : requestInput?.url ?? String(input);
863
1086
  if (targetUrl === options.keyExchangeUrl)
864
1087
  return baseFetch(input, init);
865
- const ready = await ensureReady();
1088
+ let onAbort;
1089
+ let ready;
1090
+ try {
1091
+ ready = await (signal ? Promise.race([
1092
+ ensureReady(),
1093
+ new Promise((_resolve, reject) => {
1094
+ onAbort = () => reject(signal.reason);
1095
+ signal.addEventListener("abort", onAbort, { once: true });
1096
+ if (signal.aborted)
1097
+ onAbort();
1098
+ })
1099
+ ]) : ensureReady());
1100
+ } finally {
1101
+ if (onAbort)
1102
+ signal?.removeEventListener("abort", onAbort);
1103
+ }
866
1104
  if (!ready.success)
867
1105
  throw new Error(ready.error.message);
868
1106
  const headers = new Headers(init?.headers ?? requestInput?.headers);
869
1107
  headers.set(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER, clientId);
870
1108
  let rawBody = init?.body;
871
1109
  if (rawBody == null && requestInput) {
872
- const method = (init?.method ?? requestInput.method).toUpperCase();
873
- if (method !== "GET" && method !== "HEAD") {
874
- const text = await requestInput.clone().text();
1110
+ const method2 = (init?.method ?? requestInput.method).toUpperCase();
1111
+ if (method2 !== "GET" && method2 !== "HEAD") {
1112
+ const text = await readTransportBody(requestInput.body, { maxBytes: TRANSPORT_BODY_LIMITS.plaintextBytes, signal });
875
1113
  if (text)
876
1114
  rawBody = text;
877
1115
  }
878
1116
  }
879
- let nextBody = rawBody;
880
- if (rawBody != null) {
881
- const plaintext = await bodyToText(rawBody);
882
- const encResult = encryptBody(plaintext);
883
- if (!encResult.success)
884
- throw new Error(encResult.error.message);
885
- nextBody = JSON.stringify(encResult.data);
1117
+ const method = (init?.method ?? requestInput?.method ?? "GET").toUpperCase();
1118
+ const base = new URL(options.keyExchangeUrl, globalThis.location?.href ?? "http://localhost");
1119
+ const url = new URL(targetUrl, base);
1120
+ const pathHash = hashTransportPath(options.crypto, url.pathname + url.search);
1121
+ if (!pathHash.success)
1122
+ throw new Error(pathHash.error.message);
1123
+ const context = {
1124
+ version: TRANSPORT_PROTOCOL.VERSION,
1125
+ serverKeyId,
1126
+ clientId,
1127
+ requestId: options.crypto.symmetric.generateKey(),
1128
+ issuedAt: Date.now(),
1129
+ method,
1130
+ pathHash: pathHash.data
1131
+ };
1132
+ const plaintext = rawBody == null ? "" : await readTransportBody(new Response(rawBody).body, { maxBytes: TRANSPORT_BODY_LIMITS.plaintextBytes, signal });
1133
+ const encResult = encryptBody(plaintext, context);
1134
+ if (!encResult.success)
1135
+ throw new Error(encResult.error.message);
1136
+ const serialized = JSON.stringify(encResult.data);
1137
+ if (new TextEncoder().encode(serialized).byteLength > TRANSPORT_BODY_LIMITS.encryptedPayloadBytes)
1138
+ throw new RangeError("Transport request envelope byte limit exceeded");
1139
+ let nextBody;
1140
+ headers.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
1141
+ headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE);
1142
+ headers.delete("Content-Length");
1143
+ headers.delete("Content-Encoding");
1144
+ if (method === "GET" || method === "HEAD") {
1145
+ if (new TextEncoder().encode(serialized).byteLength > TRANSPORT_BODY_LIMITS.envelopeHeaderBytes)
1146
+ throw new RangeError("Transport request envelope header byte limit exceeded");
1147
+ headers.set(TRANSPORT_PROTOCOL.ENVELOPE_HEADER, serialized);
1148
+ } else {
1149
+ nextBody = serialized;
886
1150
  headers.set("Content-Type", "application/json");
887
- headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE);
888
1151
  }
889
1152
  const finalInput = requestInput ? requestInput.url : input;
890
1153
  const finalInit = {
891
1154
  ...init,
892
- method: init?.method ?? requestInput?.method,
1155
+ method,
893
1156
  headers,
894
1157
  body: nextBody,
895
1158
  cache: init?.cache ?? requestInput?.cache,
@@ -903,24 +1166,39 @@ function createTransportClient(options) {
903
1166
  signal: init?.signal ?? requestInput?.signal
904
1167
  };
905
1168
  const response = await baseFetch(finalInput, finalInit);
906
- if (response.headers.get(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) !== TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)
907
- return response;
908
- const cloned = response.clone();
1169
+ if (response.headers.get(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) !== TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE) {
1170
+ void response.body?.cancel().catch(() => {
1171
+ });
1172
+ throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
1173
+ }
909
1174
  let payload;
910
1175
  try {
911
- payload = await cloned.json();
1176
+ const envelope = response.headers.get(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
1177
+ if (envelope && response.body !== null) {
1178
+ void response.body.cancel().catch(() => {
1179
+ });
1180
+ throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
1181
+ }
1182
+ if (envelope && new TextEncoder().encode(envelope).byteLength > TRANSPORT_BODY_LIMITS.envelopeHeaderBytes)
1183
+ throw new RangeError("Transport response envelope byte limit exceeded");
1184
+ payload = JSON.parse(envelope ?? await readTransportBody(response.body, { maxBytes: TRANSPORT_BODY_LIMITS.encryptedPayloadBytes, signal }));
912
1185
  } catch {
913
- return response;
1186
+ throw new Error(cryptoM("crypto_transportResponseDecryptFailed"), signal?.aborted ? { cause: signal.reason } : void 0);
1187
+ }
1188
+ if (!isValidEncryptedPayload(payload) || payload.direction !== "response" || payload.status !== response.status || !sameTransportContext(payload.context, context)) {
1189
+ throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
914
1190
  }
915
- if (!isEncryptedPayload(payload))
916
- return response;
917
1191
  const decResult = decryptPayload(payload);
918
1192
  if (!decResult.success)
919
1193
  throw new Error(decResult.error.message);
1194
+ if (new TextEncoder().encode(decResult.data).byteLength > TRANSPORT_BODY_LIMITS.plaintextBytes)
1195
+ throw new RangeError("Transport response plaintext byte limit exceeded");
920
1196
  const respHeaders = new Headers(response.headers);
921
1197
  respHeaders.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER);
1198
+ respHeaders.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
922
1199
  respHeaders.delete("Content-Length");
923
- return new Response(decResult.data, {
1200
+ respHeaders.delete("Content-Encoding");
1201
+ return new Response(method === "HEAD" || [204, 304].includes(response.status) ? null : decResult.data, {
924
1202
  status: response.status,
925
1203
  statusText: response.statusText,
926
1204
  headers: respHeaders
@@ -933,24 +1211,12 @@ function createTransportClient(options) {
933
1211
  destroy() {
934
1212
  keyPair = null;
935
1213
  serverPublicKey = null;
1214
+ serverKeyId = null;
936
1215
  clientId = null;
937
1216
  initPromise = null;
938
1217
  }
939
1218
  };
940
1219
  }
941
- async function bodyToText(body) {
942
- if (typeof body === "string")
943
- return body;
944
- if (body instanceof URLSearchParams)
945
- return body.toString();
946
- if (body instanceof Blob)
947
- return body.text();
948
- if (body instanceof ArrayBuffer)
949
- return new TextDecoder().decode(body);
950
- if (ArrayBuffer.isView(body))
951
- return new TextDecoder().decode(body.buffer);
952
- return new Response(body).text();
953
- }
954
1220
  function isRequestLike(input) {
955
1221
  if (typeof input !== "object" || input === null)
956
1222
  return false;
@@ -958,95 +1224,119 @@ function isRequestLike(input) {
958
1224
  const headers = candidate.headers;
959
1225
  return typeof candidate.url === "string" && typeof headers?.get === "function" && typeof candidate.method === "string" && typeof candidate.clone === "function";
960
1226
  }
961
- function isEncryptedPayload(payload) {
962
- if (!payload || typeof payload !== "object")
1227
+ function isKeyExchangeResponse(body) {
1228
+ if (!body || typeof body !== "object")
963
1229
  return false;
964
- const p = payload;
965
- return typeof p.encryptedKey === "string" && typeof p.ciphertext === "string" && typeof p.iv === "string";
966
- }
967
-
968
- // src/transport/store-provider/crypto-transport-store-memory.ts
969
- var DEFAULT_MAX_CLIENTS = 1e4;
970
- function normalizeMaxClients(maxClients) {
971
- if (!Number.isFinite(maxClients))
972
- return DEFAULT_MAX_CLIENTS;
973
- return Math.max(1, Math.floor(maxClients));
1230
+ const fields = body;
1231
+ return typeof fields.serverPublicKey === "string" && fields.serverPublicKey.length > 0 && new TextEncoder().encode(fields.serverPublicKey).byteLength <= TRANSPORT_BODY_LIMITS.publicKeyBytes && typeof fields.clientId === "string" && fields.clientId.length > 0 && typeof fields.serverKeyId === "string" && /^[\w-]{1,64}$/.test(fields.serverKeyId);
974
1232
  }
975
- function createClientId(counter) {
976
- const entropy = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "") : Math.random().toString(36).slice(2, 12);
977
- return `c_${counter}_${Date.now()}_${entropy}`;
1233
+ function parseTransportServerKeyRing(serialized) {
1234
+ try {
1235
+ const value = typeof serialized === "string" ? JSON.parse(serialized) : serialized;
1236
+ if (isTransportServerKeyRing(value))
1237
+ return ok(value);
1238
+ } catch {
1239
+ }
1240
+ return err(HaiCryptoError.INVALID_KEY, cryptoM("crypto_transportServerKeyRingInvalid"));
978
1241
  }
979
- function createInMemoryKeyStore(maxClients = DEFAULT_MAX_CLIENTS) {
980
- const clientKeys = /* @__PURE__ */ new Map();
981
- const capacity = normalizeMaxClients(maxClients);
982
- let counter = 0;
983
- return {
984
- async register(publicKey) {
985
- counter++;
986
- const clientId = createClientId(counter);
987
- if (clientKeys.size >= capacity) {
988
- const oldest = clientKeys.keys().next().value;
989
- if (oldest !== void 0)
990
- clientKeys.delete(oldest);
991
- }
992
- clientKeys.set(clientId, publicKey);
993
- return clientId;
994
- },
995
- async get(clientId) {
996
- return clientKeys.get(clientId);
997
- },
998
- async delete(clientId) {
999
- clientKeys.delete(clientId);
1000
- },
1001
- async close() {
1002
- clientKeys.clear();
1003
- }
1004
- };
1242
+ function isTransportServerKeyRing(value) {
1243
+ if (!value || typeof value !== "object")
1244
+ return false;
1245
+ const ring = value;
1246
+ if (typeof ring.activeKeyId !== "string" || !/^[\w-]{1,64}$/.test(ring.activeKeyId) || !ring.keys || typeof ring.keys !== "object" || Array.isArray(ring.keys)) {
1247
+ return false;
1248
+ }
1249
+ if (!Object.hasOwn(ring.keys, ring.activeKeyId))
1250
+ return false;
1251
+ return Object.entries(ring.keys).every(([id, pair]) => {
1252
+ if (!/^[\w-]{1,64}$/.test(id) || !pair || typeof pair !== "object")
1253
+ return false;
1254
+ const fields = pair;
1255
+ return typeof fields.publicKey === "string" && fields.publicKey.length > 0 && new TextEncoder().encode(fields.publicKey).byteLength <= TRANSPORT_BODY_LIMITS.publicKeyBytes && typeof fields.privateKey === "string" && fields.privateKey.length > 0 && fields.privateKey.length <= TRANSPORT_BODY_LIMITS.publicKeyBytes;
1256
+ });
1005
1257
  }
1006
1258
 
1007
1259
  // src/transport/crypto-transport-server.ts
1008
- function createTransportEncryption(cryptoService, options = {}) {
1009
- const keyPairResult = cryptoService.asymmetric.generateKeyPair();
1010
- if (!keyPairResult.success)
1011
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportServerKeyGenerateFailed"), keyPairResult.error);
1012
- const serverKeyPair = keyPairResult.data;
1013
- const keyStore = options.keyStore ?? createInMemoryKeyStore(options.maxClients);
1260
+ function createTransportEncryption(cryptoService, options) {
1261
+ const ring = parseTransportServerKeyRing(options?.serverKeyRing);
1262
+ if (!ring.success)
1263
+ return ring;
1264
+ if (!options.keyStore || typeof options.keyStore.register !== "function" || typeof options.keyStore.get !== "function" || typeof options.keyStore.claimRequest !== "function") {
1265
+ return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportKeyStoreRequired"));
1266
+ }
1267
+ if (core.module.requiresSharedState(globalThis.process?.env ?? {}) && options.keyStore.stateScope !== "shared")
1268
+ return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportKeyStoreRequired"));
1269
+ const serverKeys = /* @__PURE__ */ new Map();
1270
+ for (const [id, key] of Object.entries(ring.data.keys)) {
1271
+ const probe = cryptoService.asymmetric.encrypt("hai-transport-key-check", key.publicKey);
1272
+ if (!probe.success)
1273
+ return err(HaiCryptoError.INVALID_KEY, cryptoM("crypto_transportServerKeyRingInvalid"));
1274
+ const checked = cryptoService.asymmetric.decrypt(probe.data, key.privateKey);
1275
+ if (!checked.success || checked.data !== "hai-transport-key-check")
1276
+ return err(HaiCryptoError.INVALID_KEY, cryptoM("crypto_transportServerKeyRingInvalid"));
1277
+ serverKeys.set(id, { publicKey: key.publicKey, privateKey: key.privateKey });
1278
+ }
1279
+ const activeKeyId = ring.data.activeKeyId;
1280
+ const serverKeyPair = serverKeys.get(activeKeyId);
1281
+ const keyStore = options.keyStore;
1014
1282
  const manager = {
1283
+ getServerKeyId() {
1284
+ return activeKeyId;
1285
+ },
1015
1286
  getServerPublicKey() {
1016
1287
  return serverKeyPair.publicKey;
1017
1288
  },
1018
1289
  async registerClientKey(clientPublicKey) {
1290
+ if (!isValidSM2PublicKey(clientPublicKey))
1291
+ throw new TypeError(cryptoM("crypto_sm2PublicKeyInvalid"));
1019
1292
  return keyStore.register(clientPublicKey);
1020
1293
  },
1021
1294
  async getClientPublicKey(clientId) {
1022
1295
  return keyStore.get(clientId);
1023
1296
  },
1024
- async encryptResponse(clientId, data) {
1025
- const clientPublicKey = await keyStore.get(clientId);
1026
- if (!clientPublicKey)
1027
- return err(HaiCommonError.NOT_FOUND, cryptoM("crypto_transportClientNotRegistered"));
1028
- const symmetricKey = cryptoService.symmetric.generateKey();
1029
- const encResult = cryptoService.symmetric.encryptWithIV(data, symmetricKey);
1030
- if (!encResult.success)
1031
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportResponseEncryptFailed"), encResult.error);
1032
- const keyEncResult = cryptoService.asymmetric.encrypt(symmetricKey, clientPublicKey);
1033
- if (!keyEncResult.success)
1034
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportSessionKeyEncryptFailed"), keyEncResult.error);
1035
- const payload = {
1036
- encryptedKey: keyEncResult.data,
1037
- ciphertext: encResult.data.ciphertext,
1038
- iv: encResult.data.iv
1039
- };
1040
- return ok(payload);
1297
+ async encryptResponse(request, data, status) {
1298
+ try {
1299
+ const context = { ...request };
1300
+ const clientPublicKey = await keyStore.get(context.clientId);
1301
+ if (!clientPublicKey)
1302
+ return err(HaiCommonError.NOT_FOUND, cryptoM("crypto_transportClientNotRegistered"));
1303
+ const key = serverKeys.get(context.serverKeyId);
1304
+ if (!key)
1305
+ return transportAuthenticationFailure();
1306
+ return sealTransportPayload(cryptoService, data, context, "response", status, clientPublicKey, key.privateKey);
1307
+ } catch {
1308
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportResponseEncryptFailed"));
1309
+ }
1041
1310
  },
1042
- decryptRequest(payload) {
1043
- const keyDecResult = cryptoService.asymmetric.decrypt(payload.encryptedKey, serverKeyPair.privateKey);
1044
- if (!keyDecResult.success)
1045
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportSessionKeyDecryptFailed"), keyDecResult.error);
1046
- const decResult = cryptoService.symmetric.decryptWithIV(payload.ciphertext, keyDecResult.data, payload.iv);
1047
- if (!decResult.success)
1048
- return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportRequestDecryptFailed"), decResult.error);
1049
- return ok(decResult.data);
1311
+ async decryptRequest(payload, expected) {
1312
+ try {
1313
+ if (!isValidEncryptedPayload(payload) || payload.direction !== "request" || payload.status !== 0)
1314
+ return transportAuthenticationFailure();
1315
+ const authenticated = { ...payload, context: { ...payload.context } };
1316
+ const c = authenticated.context;
1317
+ const now = Date.now();
1318
+ const pathHash = hashTransportPath(cryptoService, expected.path);
1319
+ if (!pathHash.success || c.clientId !== expected.clientId || c.method !== expected.method || c.pathHash !== pathHash.data || c.issuedAt > now + TRANSPORT_PROTOCOL.CLOCK_SKEW_MS || now >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS) {
1320
+ return transportAuthenticationFailure();
1321
+ }
1322
+ const key = serverKeys.get(c.serverKeyId);
1323
+ const clientPublicKey = await keyStore.get(c.clientId);
1324
+ if (!key || !clientPublicKey)
1325
+ return transportAuthenticationFailure();
1326
+ const plaintext = openTransportPayload(cryptoService, authenticated, key.privateKey, clientPublicKey);
1327
+ if (!plaintext.success)
1328
+ return plaintext;
1329
+ if (Date.now() >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS)
1330
+ return transportAuthenticationFailure();
1331
+ if (!await keyStore.claimRequest(c.clientId, c.requestId, c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS + 2 * TRANSPORT_PROTOCOL.CLOCK_SKEW_MS)) {
1332
+ return transportAuthenticationFailure();
1333
+ }
1334
+ if (Date.now() >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS)
1335
+ return transportAuthenticationFailure();
1336
+ return plaintext;
1337
+ } catch {
1338
+ return transportAuthenticationFailure();
1339
+ }
1050
1340
  },
1051
1341
  async close() {
1052
1342
  await keyStore.close?.();
@@ -1075,7 +1365,7 @@ function getCrypto() {
1075
1365
  return crypto2;
1076
1366
  }
1077
1367
  var transportOperations = {
1078
- createServer: (options) => createTransportEncryption(getCrypto(), options),
1368
+ createServer: (options) => getCrypto().isInitialized ? createTransportEncryption(getCrypto(), options) : notInitialized.result(),
1079
1369
  createClient: (options) => createTransportClient({ crypto: getCrypto(), ...options }),
1080
1370
  protocol: TRANSPORT_PROTOCOL
1081
1371
  };
@@ -1148,8 +1438,7 @@ var crypto2 = {
1148
1438
  /**
1149
1439
  * 传输加密操作(端到端混合加密)
1150
1440
  *
1151
- * createServer 内部直接复用 asymmetric/symmetric,因此未初始化时
1152
- * 自动通过 asymmetric proxy 返回 NOT_INITIALIZED 错误。
1441
+ * createServer 检查初始化后复用 asymmetric/symmetric;未初始化返回 NOT_INITIALIZED。
1153
1442
  */
1154
1443
  transport: transportOperations,
1155
1444
  /** 是否已初始化 */
@@ -1175,13 +1464,116 @@ var crypto2 = {
1175
1464
  logger.info("Crypto module closed");
1176
1465
  }
1177
1466
  };
1467
+ async function authenticateTransportRequest(manager, request) {
1468
+ const reject = () => {
1469
+ void request.body?.cancel().catch(() => {
1470
+ });
1471
+ return transportAuthenticationFailure();
1472
+ };
1473
+ if (request.headers.get(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) !== TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)
1474
+ return reject();
1475
+ const clientId = request.headers.get(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER);
1476
+ if (!clientId)
1477
+ return reject();
1478
+ const envelope = request.headers.get(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
1479
+ const bodyless = request.method === "GET" || request.method === "HEAD";
1480
+ if (bodyless && (!envelope || request.body !== null) || !bodyless && envelope !== null)
1481
+ return reject();
1482
+ if (envelope && new TextEncoder().encode(envelope).byteLength > TRANSPORT_BODY_LIMITS.envelopeHeaderBytes)
1483
+ throw new RangeError("Transport request envelope byte limit exceeded");
1484
+ const text = envelope ?? await readTransportBody(request.body, {
1485
+ maxBytes: TRANSPORT_BODY_LIMITS.encryptedPayloadBytes,
1486
+ signal: request.signal
1487
+ });
1488
+ let payload;
1489
+ try {
1490
+ payload = JSON.parse(text);
1491
+ } catch {
1492
+ return transportAuthenticationFailure();
1493
+ }
1494
+ if (!isValidEncryptedPayload(payload) || payload.direction !== "request" || payload.status !== 0)
1495
+ return transportAuthenticationFailure();
1496
+ const url = new URL(request.url);
1497
+ const decrypted = await manager.decryptRequest(payload, {
1498
+ clientId,
1499
+ method: request.method,
1500
+ path: url.pathname + url.search
1501
+ });
1502
+ if (!decrypted.success)
1503
+ return decrypted;
1504
+ if (new TextEncoder().encode(decrypted.data).byteLength > TRANSPORT_BODY_LIMITS.plaintextBytes)
1505
+ throw new RangeError("Transport request plaintext byte limit exceeded");
1506
+ if (bodyless && decrypted.data !== "")
1507
+ return transportAuthenticationFailure();
1508
+ return ok({ plaintext: decrypted.data, context: payload.context });
1509
+ }
1510
+
1511
+ // src/transport/store-provider/crypto-transport-store-memory.ts
1512
+ var DEFAULT_MAX_CLIENTS = 1e4;
1513
+ function normalizeMaxClients(maxClients) {
1514
+ if (!Number.isFinite(maxClients))
1515
+ return DEFAULT_MAX_CLIENTS;
1516
+ return Math.max(1, Math.floor(maxClients));
1517
+ }
1518
+ function createClientId(counter) {
1519
+ const entropy = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "") : Math.random().toString(36).slice(2, 12);
1520
+ return `c_${counter}_${Date.now()}_${entropy}`;
1521
+ }
1522
+ function createInMemoryKeyStore(maxClients = DEFAULT_MAX_CLIENTS) {
1523
+ const clientKeys = /* @__PURE__ */ new Map();
1524
+ const requests = /* @__PURE__ */ new Map();
1525
+ const capacity = normalizeMaxClients(maxClients);
1526
+ let counter = 0;
1527
+ return {
1528
+ get stateScope() {
1529
+ return "local";
1530
+ },
1531
+ async register(publicKey) {
1532
+ if (!isValidSM2PublicKey(publicKey))
1533
+ throw new TypeError(cryptoM("crypto_sm2PublicKeyInvalid"));
1534
+ counter++;
1535
+ const clientId = createClientId(counter);
1536
+ if (clientKeys.size >= capacity) {
1537
+ const oldest = clientKeys.keys().next().value;
1538
+ if (oldest !== void 0)
1539
+ clientKeys.delete(oldest);
1540
+ }
1541
+ clientKeys.set(clientId, publicKey);
1542
+ return clientId;
1543
+ },
1544
+ async get(clientId) {
1545
+ return clientKeys.get(clientId);
1546
+ },
1547
+ async claimRequest(clientId, requestId, expiresAt) {
1548
+ const now = Date.now();
1549
+ for (const [id2, expiry] of requests) {
1550
+ if (expiry <= now)
1551
+ requests.delete(id2);
1552
+ }
1553
+ const id = JSON.stringify([clientId, requestId]);
1554
+ if (requests.has(id) || expiresAt <= now)
1555
+ return false;
1556
+ requests.set(id, expiresAt);
1557
+ return true;
1558
+ },
1559
+ async delete(clientId) {
1560
+ clientKeys.delete(clientId);
1561
+ },
1562
+ async close() {
1563
+ clientKeys.clear();
1564
+ requests.clear();
1565
+ }
1566
+ };
1567
+ }
1178
1568
 
1179
1569
  // src/transport/store-provider/crypto-transport-store-redis.ts
1180
1570
  var TRANSPORT_KEY_CACHE_PREFIX = "hai:crypto:transport:client";
1181
1571
  function normalizeTtlSeconds(ttlSeconds) {
1182
- if (ttlSeconds === void 0 || !Number.isFinite(ttlSeconds) || ttlSeconds <= 0)
1572
+ if (ttlSeconds === void 0)
1183
1573
  return void 0;
1184
- return Math.floor(ttlSeconds);
1574
+ if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds <= 0 || ttlSeconds > 2147483647)
1575
+ throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
1576
+ return ttlSeconds;
1185
1577
  }
1186
1578
  function createClientId2() {
1187
1579
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
@@ -1204,9 +1596,14 @@ function createRedisTransportKeyStore(options) {
1204
1596
  return `${TRANSPORT_KEY_CACHE_PREFIX}:${clientId}`;
1205
1597
  }
1206
1598
  return {
1599
+ get stateScope() {
1600
+ return options.cache.config?.type === "redis" ? "shared" : "local";
1601
+ },
1207
1602
  async register(publicKey) {
1603
+ if (!isValidSM2PublicKey(publicKey))
1604
+ throw new TypeError(cryptoM("crypto_sm2PublicKeyInvalid"));
1208
1605
  const clientId = createClientId2();
1209
- unwrapResult(
1606
+ const applied = unwrapResult(
1210
1607
  "cache.kv.set",
1211
1608
  await options.cache.kv.set(
1212
1609
  buildCacheKey(clientId),
@@ -1214,6 +1611,8 @@ function createRedisTransportKeyStore(options) {
1214
1611
  ttlSeconds === void 0 ? void 0 : { ex: ttlSeconds }
1215
1612
  )
1216
1613
  );
1614
+ if (!applied)
1615
+ throw new Error(cryptoM("crypto_transportClientNotRegistered"));
1217
1616
  return clientId;
1218
1617
  },
1219
1618
  async get(clientId) {
@@ -1223,6 +1622,17 @@ function createRedisTransportKeyStore(options) {
1223
1622
  );
1224
1623
  return publicKey ?? void 0;
1225
1624
  },
1625
+ async claimRequest(clientId, requestId, expiresAt) {
1626
+ const ttl = expiresAt - Date.now();
1627
+ if (ttl <= 0)
1628
+ return false;
1629
+ return unwrapResult("cache.kv.compareAndSwap", await options.cache.kv.compareAndSwap(
1630
+ `hai:crypto:transport:request:${clientId}:${requestId}`,
1631
+ void 0,
1632
+ true,
1633
+ { px: ttl }
1634
+ ));
1635
+ },
1226
1636
  async delete(clientId) {
1227
1637
  unwrapResult("cache.kv.del", await options.cache.kv.del(buildCacheKey(clientId)));
1228
1638
  }
@@ -1231,20 +1641,11 @@ function createRedisTransportKeyStore(options) {
1231
1641
 
1232
1642
  // src/transport/store-provider/crypto-transport-store-reldb.ts
1233
1643
  var TRANSPORT_KEY_TABLE = "hai_crypto_transport_client_keys";
1234
- function normalizeTtlSeconds2(ttlSeconds) {
1235
- if (ttlSeconds === void 0 || !Number.isFinite(ttlSeconds) || ttlSeconds <= 0)
1236
- return void 0;
1237
- return Math.floor(ttlSeconds);
1238
- }
1239
- function createClientId3() {
1240
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
1241
- return `c_${crypto.randomUUID()}`;
1242
- return `c_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`;
1243
- }
1644
+ var TRANSPORT_REQUEST_TABLE = "hai_crypto_transport_requests";
1645
+ var TRANSPORT_CAPACITY_TABLE = "hai_crypto_transport_capacity";
1646
+ var PRUNE_BATCH_SIZE = 100;
1244
1647
  function toOperationError2(operation, error) {
1245
- const wrapped = new Error(`${operation} failed: ${error.message}`, { cause: error });
1246
- wrapped.code = error.code;
1247
- return wrapped;
1648
+ return Object.assign(new Error(cryptoM("crypto_transportKeyStoreOperationFailed")), { operation, code: error.code });
1248
1649
  }
1249
1650
  function unwrapResult2(operation, result) {
1250
1651
  if (result.success)
@@ -1252,21 +1653,66 @@ function unwrapResult2(operation, result) {
1252
1653
  throw toOperationError2(operation, result.error);
1253
1654
  }
1254
1655
  function createReldbTransportKeyStore(options) {
1255
- const ttlSeconds = normalizeTtlSeconds2(options.ttlSeconds);
1656
+ const { ttlSeconds } = options;
1657
+ if (ttlSeconds !== void 0 && (!Number.isSafeInteger(ttlSeconds) || ttlSeconds <= 0 || ttlSeconds > 2147483647))
1658
+ throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
1659
+ const maxClients = options.maxClients ?? 1e4;
1660
+ if (!Number.isSafeInteger(maxClients) || maxClients <= 0)
1661
+ throw new TypeError(cryptoM("crypto_transportKeyStoreCapacityInvalid"));
1256
1662
  let ensureTablePromise = null;
1663
+ function nowSql() {
1664
+ switch (options.reldb.config?.type) {
1665
+ case "postgresql":
1666
+ return "CAST(FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) AS BIGINT)";
1667
+ case "mysql":
1668
+ return "CAST(FLOOR(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000) AS SIGNED)";
1669
+ default:
1670
+ return "(CAST(strftime('%s', 'now') AS INTEGER) * 1000 + CAST(substr(strftime('%f', 'now'), 4, 3) AS INTEGER))";
1671
+ }
1672
+ }
1673
+ function insertIfAbsent(table, columns, placeholders, key) {
1674
+ return options.reldb.config?.type === "mysql" ? `INSERT IGNORE INTO ${table} (${columns}) VALUES (${placeholders})` : `INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT (${key}) DO NOTHING`;
1675
+ }
1257
1676
  async function ensureTable() {
1258
1677
  if (!ensureTablePromise) {
1259
- ensureTablePromise = (async () => {
1678
+ const initialization = (async () => {
1260
1679
  unwrapResult2(
1261
1680
  "reldb.ddl.createTable",
1262
1681
  await options.reldb.ddl.createTable(TRANSPORT_KEY_TABLE, {
1263
1682
  client_id: { type: "TEXT", primaryKey: true },
1264
1683
  public_key: { type: "TEXT", notNull: true },
1265
- created_at: { type: "TIMESTAMP", notNull: true },
1266
- expires_at: { type: "TIMESTAMP" }
1684
+ created_at_ms: { type: "BIGINT", notNull: true },
1685
+ expires_at_ms: { type: "BIGINT" }
1267
1686
  }, true)
1268
1687
  );
1688
+ unwrapResult2("reldb.ddl.createTable", await options.reldb.ddl.createTable(TRANSPORT_REQUEST_TABLE, {
1689
+ request_key: { type: "TEXT", primaryKey: true },
1690
+ expires_at: { type: "BIGINT", notNull: true }
1691
+ }, true));
1692
+ unwrapResult2("reldb.ddl.createTable", await options.reldb.ddl.createTable(TRANSPORT_CAPACITY_TABLE, {
1693
+ id: { type: "INTEGER", primaryKey: true },
1694
+ max_clients: { type: "BIGINT", notNull: true },
1695
+ revision: { type: "BIGINT", notNull: true }
1696
+ }, true));
1697
+ unwrapResult2("reldb.sql.execute", await options.reldb.sql.execute(
1698
+ insertIfAbsent(TRANSPORT_CAPACITY_TABLE, "id, max_clients, revision", "?, ?, ?", "id"),
1699
+ [1, maxClients, 0]
1700
+ ));
1701
+ const capacity = unwrapResult2("reldb.sql.get", await options.reldb.sql.get(
1702
+ `SELECT max_clients FROM ${TRANSPORT_CAPACITY_TABLE} WHERE id = ?`,
1703
+ [1]
1704
+ ));
1705
+ if (!capacity || Number(capacity.max_clients) !== maxClients)
1706
+ throw new Error(cryptoM("crypto_transportKeyStoreCapacityMismatch"));
1269
1707
  })();
1708
+ ensureTablePromise = initialization;
1709
+ try {
1710
+ await initialization;
1711
+ } catch (error) {
1712
+ if (ensureTablePromise === initialization)
1713
+ ensureTablePromise = null;
1714
+ throw error;
1715
+ }
1270
1716
  }
1271
1717
  return ensureTablePromise;
1272
1718
  }
@@ -1279,45 +1725,88 @@ function createReldbTransportKeyStore(options) {
1279
1725
  )
1280
1726
  );
1281
1727
  }
1728
+ async function withCapacityLock(operation) {
1729
+ await ensureTable();
1730
+ const result = await options.reldb.tx.wrap(async (tx) => {
1731
+ const locked = unwrapResult2("tx.execute", await tx.execute(
1732
+ `UPDATE ${TRANSPORT_CAPACITY_TABLE} SET revision = revision + 1 WHERE id = ? AND max_clients = ?`,
1733
+ [1, maxClients]
1734
+ ));
1735
+ if (locked.changes !== 1)
1736
+ throw new Error(cryptoM("crypto_transportKeyStoreCapacityMismatch"));
1737
+ return operation(tx);
1738
+ });
1739
+ return unwrapResult2("reldb.tx.wrap", result);
1740
+ }
1741
+ async function pruneRows(tx) {
1742
+ let removed = 0;
1743
+ for (const [table, id, expiry] of [[TRANSPORT_KEY_TABLE, "client_id", "expires_at_ms"], [TRANSPORT_REQUEST_TABLE, "request_key", "expires_at"]]) {
1744
+ removed += unwrapResult2("tx.execute", await tx.execute(
1745
+ `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)`
1746
+ )).changes;
1747
+ }
1748
+ return removed;
1749
+ }
1750
+ async function pruneExpired() {
1751
+ return withCapacityLock(pruneRows);
1752
+ }
1282
1753
  return {
1754
+ get stateScope() {
1755
+ return options.reldb.config?.type && options.reldb.config.type !== "sqlite" ? "shared" : "local";
1756
+ },
1283
1757
  async register(publicKey) {
1284
- await ensureTable();
1285
- const clientId = createClientId3();
1286
- const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1287
- const expiresAt = ttlSeconds === void 0 ? null : new Date(Date.now() + ttlSeconds * 1e3).toISOString();
1288
- unwrapResult2(
1289
- "reldb.sql.execute",
1290
- await options.reldb.sql.execute(
1291
- `INSERT INTO ${TRANSPORT_KEY_TABLE} (client_id, public_key, created_at, expires_at) VALUES (?, ?, ?, ?)`,
1292
- [clientId, publicKey, createdAt, expiresAt]
1293
- )
1294
- );
1295
- return clientId;
1758
+ if (!isValidSM2PublicKey(publicKey))
1759
+ throw new TypeError(cryptoM("crypto_sm2PublicKeyInvalid"));
1760
+ const clientId = `c_${globalThis.crypto.randomUUID()}`;
1761
+ return withCapacityLock(async (tx) => {
1762
+ await pruneRows(tx);
1763
+ const count = unwrapResult2("tx.get", await tx.get(
1764
+ `SELECT COUNT(*) AS total FROM ${TRANSPORT_KEY_TABLE} WHERE expires_at_ms IS NULL OR expires_at_ms > ${nowSql()}`
1765
+ ));
1766
+ const total = Number(count?.total);
1767
+ if (!Number.isSafeInteger(total) || total < 0)
1768
+ throw new Error(cryptoM("crypto_transportKeyStoreOperationFailed"));
1769
+ if (total >= maxClients)
1770
+ throw new Error(cryptoM("crypto_transportKeyStoreCapacityReached"));
1771
+ const inserted = unwrapResult2("tx.execute", await tx.execute(
1772
+ `INSERT INTO ${TRANSPORT_KEY_TABLE} (client_id, public_key, created_at_ms, expires_at_ms) VALUES (?, ?, ${nowSql()}, ${ttlSeconds === void 0 ? "NULL" : `${nowSql()} + ?`})`,
1773
+ ttlSeconds === void 0 ? [clientId, publicKey] : [clientId, publicKey, ttlSeconds * 1e3]
1774
+ ));
1775
+ if (inserted.changes !== 1)
1776
+ throw new Error(cryptoM("crypto_transportClientNotRegistered"));
1777
+ return clientId;
1778
+ });
1296
1779
  },
1297
1780
  async get(clientId) {
1298
1781
  await ensureTable();
1299
1782
  const row = unwrapResult2(
1300
1783
  "reldb.sql.get",
1301
1784
  await options.reldb.sql.get(
1302
- `SELECT public_key, expires_at FROM ${TRANSPORT_KEY_TABLE} WHERE client_id = ?`,
1785
+ `SELECT public_key FROM ${TRANSPORT_KEY_TABLE} WHERE client_id = ? AND (expires_at_ms IS NULL OR expires_at_ms > ${nowSql()})`,
1303
1786
  [clientId]
1304
1787
  )
1305
1788
  );
1306
- if (!row)
1307
- return void 0;
1308
- if (row.expires_at && Date.parse(row.expires_at) <= Date.now()) {
1309
- await deleteClientKey(clientId);
1310
- return void 0;
1311
- }
1312
- return row.public_key;
1789
+ return row?.public_key;
1790
+ },
1791
+ async claimRequest(clientId, requestId, expiresAt) {
1792
+ await ensureTable();
1793
+ if (!Number.isSafeInteger(expiresAt) || expiresAt <= Date.now())
1794
+ return false;
1795
+ 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`;
1796
+ const inserted = unwrapResult2("reldb.sql.execute", await options.reldb.sql.execute(
1797
+ insertSql,
1798
+ [JSON.stringify([clientId, requestId]), expiresAt]
1799
+ ));
1800
+ return inserted.changes === 1;
1313
1801
  },
1314
1802
  async delete(clientId) {
1315
1803
  await ensureTable();
1316
1804
  await deleteClientKey(clientId);
1317
- }
1805
+ },
1806
+ pruneExpired
1318
1807
  };
1319
1808
  }
1320
1809
 
1321
- export { HaiCryptoError, TRANSPORT_PROTOCOL, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, crypto2 as crypto };
1810
+ export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PROTOCOL, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, crypto2 as crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody };
1322
1811
  //# sourceMappingURL=index.js.map
1323
1812
  //# sourceMappingURL=index.js.map