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

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,7 @@ 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_transportServerKeyRingInvalid: "Transport server key ring is missing or invalid",
38
+ crypto_transportServerKeyRingInvalid: "The server transport key ring is missing or invalid. Check that private HAI_CRYPTO_TRANSPORT_SERVERKEYRING contains complete valid JSON and distribute the same ring to every node.",
39
39
  crypto_transportServerKeyUnknown: "Transport server key version is unknown or revoked",
40
40
  crypto_transportKeyStoreRequired: "Transport encryption requires an explicit client public key store",
41
41
  crypto_transportKeyStoreTtlInvalid: "Public-key store TTL must be an integer between 1 and 2147483647 seconds",
@@ -55,7 +55,10 @@ var en_US_default = {
55
55
  crypto_transportResponseEncryptFailed: "Failed to encrypt transport response body",
56
56
  crypto_transportResponseDecryptFailed: "Failed to decrypt transport response body",
57
57
  crypto_transportSessionKeyEncryptFailed: "Failed to encrypt transport session key",
58
- crypto_transportSessionKeyDecryptFailed: "Failed to decrypt transport session key"
58
+ crypto_transportSessionKeyDecryptFailed: "Failed to decrypt transport session key",
59
+ crypto_transportBodyLimitInvalid: "Transport plaintext limit must be an integer between 1024 and 67108864 bytes",
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."
59
62
  };
60
63
 
61
64
  // messages/ja-JP.json
@@ -90,7 +93,7 @@ var ja_JP_default = {
90
93
  crypto_hashFormatInvalid: "\u30CF\u30C3\u30B7\u30E5\u306E\u5F62\u5F0F\u304C\u7121\u52B9\u3067\u3059",
91
94
  crypto_passwordVerifyFailed: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
92
95
  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",
96
+ crypto_transportServerKeyRingInvalid: "\u30B5\u30FC\u30D0\u30FC\u306E\u8EE2\u9001\u9375\u30EA\u30F3\u30B0\u304C\u4E0D\u8DB3\u307E\u305F\u306F\u4E0D\u6B63\u3067\u3059\u3002\u975E\u516C\u958B\u306E HAI_CRYPTO_TRANSPORT_SERVERKEYRING \u304C\u5B8C\u5168\u3067\u6709\u52B9\u306A JSON \u3067\u3042\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3001\u5168\u30CE\u30FC\u30C9\u306B\u540C\u3058\u30EA\u30F3\u30B0\u3092\u914D\u5E03\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
94
97
  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
98
  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
99
  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",
@@ -110,7 +113,10 @@ var ja_JP_default = {
110
113
  crypto_transportResponseEncryptFailed: "\u901A\u4FE1\u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u6587\u306E\u6697\u53F7\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
111
114
  crypto_transportResponseDecryptFailed: "\u901A\u4FE1\u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u6587\u306E\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
112
115
  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"
116
+ crypto_transportSessionKeyDecryptFailed: "\u901A\u4FE1\u30BB\u30C3\u30B7\u30E7\u30F3\u9375\u306E\u5FA9\u53F7\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
117
+ 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",
118
+ 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",
119
+ 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"
114
120
  };
115
121
 
116
122
  // messages/zh-CN.json
@@ -145,7 +151,7 @@ var zh_CN_default = {
145
151
  crypto_hashFormatInvalid: "\u65E0\u6548\u7684\u54C8\u5E0C\u683C\u5F0F",
146
152
  crypto_passwordVerifyFailed: "\u5BC6\u7801\u9A8C\u8BC1\u5931\u8D25",
147
153
  crypto_transportClientKeyGenerateFailed: "\u4F20\u8F93\u52A0\u5BC6\u5BA2\u6237\u7AEF\u5BC6\u94A5\u751F\u6210\u5931\u8D25",
148
- crypto_transportServerKeyRingInvalid: "\u670D\u52A1\u7AEF\u4F20\u8F93\u5BC6\u94A5\u73AF\u7F3A\u5931\u6216\u65E0\u6548",
154
+ crypto_transportServerKeyRingInvalid: "\u670D\u52A1\u7AEF\u4F20\u8F93\u5BC6\u94A5\u73AF\u7F3A\u5931\u6216\u65E0\u6548\u3002\u8BF7\u68C0\u67E5\u79C1\u6709\u73AF\u5883\u53D8\u91CF HAI_CRYPTO_TRANSPORT_SERVERKEYRING \u662F\u5426\u4E3A\u5B8C\u6574\u6709\u6548\u7684 JSON\uFF0C\u5E76\u5411\u6240\u6709\u8282\u70B9\u5206\u53D1\u540C\u4E00\u5BC6\u94A5\u73AF\u3002",
149
155
  crypto_transportServerKeyUnknown: "\u670D\u52A1\u7AEF\u4F20\u8F93\u5BC6\u94A5\u7248\u672C\u672A\u77E5\u6216\u5DF2\u64A4\u9500",
150
156
  crypto_transportKeyStoreRequired: "\u4F20\u8F93\u52A0\u5BC6\u5FC5\u987B\u663E\u5F0F\u63D0\u4F9B\u5BA2\u6237\u7AEF\u516C\u94A5\u5B58\u50A8",
151
157
  crypto_transportKeyStoreTtlInvalid: "\u516C\u94A5\u5B58\u50A8 TTL \u5FC5\u987B\u4E3A 1 \u5230 2147483647 \u4E4B\u95F4\u7684\u6574\u6570\u79D2",
@@ -165,7 +171,10 @@ var zh_CN_default = {
165
171
  crypto_transportResponseEncryptFailed: "\u4F20\u8F93\u52A0\u5BC6\u54CD\u5E94\u4F53\u52A0\u5BC6\u5931\u8D25",
166
172
  crypto_transportResponseDecryptFailed: "\u4F20\u8F93\u52A0\u5BC6\u54CD\u5E94\u4F53\u89E3\u5BC6\u5931\u8D25",
167
173
  crypto_transportSessionKeyEncryptFailed: "\u4F20\u8F93\u52A0\u5BC6\u4F1A\u8BDD\u5BC6\u94A5\u52A0\u5BC6\u5931\u8D25",
168
- crypto_transportSessionKeyDecryptFailed: "\u4F20\u8F93\u52A0\u5BC6\u4F1A\u8BDD\u5BC6\u94A5\u89E3\u5BC6\u5931\u8D25"
174
+ crypto_transportSessionKeyDecryptFailed: "\u4F20\u8F93\u52A0\u5BC6\u4F1A\u8BDD\u5BC6\u94A5\u89E3\u5BC6\u5931\u8D25",
175
+ crypto_transportBodyLimitInvalid: "\u4F20\u8F93\u660E\u6587\u5BB9\u91CF\u5FC5\u987B\u662F 1024 \u5230 67108864 \u5B57\u8282\u4E4B\u95F4\u7684\u6574\u6570",
176
+ crypto_transportBodyTooLarge: "\u52A0\u5BC6\u4F20\u8F93\u62A5\u6587\u8D85\u8FC7\u914D\u7F6E\u7684\u5B57\u8282\u4E0A\u9650",
177
+ 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"
169
178
  };
170
179
 
171
180
  // src/crypto-i18n.ts
@@ -186,7 +195,9 @@ var CryptoErrorInfo = {
186
195
  VERIFY_FAILED: "024:500",
187
196
  HASH_FAILED: "040:500",
188
197
  HMAC_FAILED: "041:500",
189
- INVALID_IV: "060:400"
198
+ INVALID_IV: "060:400",
199
+ TRANSPORT_PAYLOAD_TOO_LARGE: "070:413",
200
+ TRANSPORT_TIME_INVALID: "071:400"
190
201
  };
191
202
  var HaiCryptoError = core.error.buildHaiErrorsDef("crypto", CryptoErrorInfo);
192
203
 
@@ -828,7 +839,8 @@ function decodeCiphertext(payload) {
828
839
  }
829
840
 
830
841
  // src/transport/crypto-transport-body.ts
831
- var TRANSPORT_BODY_LIMITS = {
842
+ var TRANSPORT_PLAINTEXT_RANGE = Object.freeze({ min: 1024, max: 64 * 1024 * 1024 });
843
+ var TRANSPORT_BODY_LIMITS = Object.freeze({
832
844
  /** 密钥协商 JSON 的 UTF-8 总字节数。 */
833
845
  keyExchangeBytes: 16384,
834
846
  /** GET/HEAD 请求及无正文响应的认证头封装 UTF-8 上限。 */
@@ -839,6 +851,26 @@ var TRANSPORT_BODY_LIMITS = {
839
851
  plaintextBytes: 1048576,
840
852
  /** 密文 JSON 的 UTF-8 总字节数(2 MiB + 32 KiB,含 CBC 填充与全部认证字段)。 */
841
853
  encryptedPayloadBytes: 2129920
854
+ });
855
+ function resolveTransportBodyLimits(maxPlaintextBytes = TRANSPORT_BODY_LIMITS.plaintextBytes) {
856
+ if (!Number.isSafeInteger(maxPlaintextBytes) || maxPlaintextBytes < TRANSPORT_PLAINTEXT_RANGE.min || maxPlaintextBytes > TRANSPORT_PLAINTEXT_RANGE.max)
857
+ throw new RangeError(cryptoM("crypto_transportBodyLimitInvalid"));
858
+ return Object.freeze({ ...TRANSPORT_BODY_LIMITS, plaintextBytes: maxPlaintextBytes, encryptedPayloadBytes: 2 * maxPlaintextBytes + 32768 });
859
+ }
860
+ var TransportBodyTooLargeError = class extends RangeError {
861
+ code = HaiCryptoError.TRANSPORT_PAYLOAD_TOO_LARGE.code;
862
+ direction;
863
+ stage;
864
+ limitBytes;
865
+ actualBytes;
866
+ constructor(limitBytes, actualBytes, options = {}) {
867
+ super(cryptoM("crypto_transportBodyTooLarge"));
868
+ this.name = "TransportBodyTooLargeError";
869
+ this.limitBytes = limitBytes;
870
+ this.actualBytes = actualBytes;
871
+ this.direction = options.direction ?? "request";
872
+ this.stage = options.stage ?? "plaintext";
873
+ }
842
874
  };
843
875
  async function readTransportBody(stream, options) {
844
876
  if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0)
@@ -870,7 +902,7 @@ async function readTransportBody(stream, options) {
870
902
  }
871
903
  bytes += chunk.value.byteLength;
872
904
  if (bytes > options.maxBytes)
873
- throw new RangeError("Transport body byte limit exceeded");
905
+ throw new TransportBodyTooLargeError(options.maxBytes, bytes, options);
874
906
  text += decoder.decode(chunk.value, { stream: true });
875
907
  }
876
908
  } catch (error) {
@@ -942,8 +974,11 @@ function hashTransportPath(service, path) {
942
974
  return transportAuthenticationFailure();
943
975
  }
944
976
  }
945
- function sealTransportPayload(service, plaintext, context, direction, status, recipientPublicKey, senderPrivateKey) {
977
+ function sealTransportPayload(service, plaintext, context, direction, status, recipientPublicKey, senderPrivateKey, limits = TRANSPORT_BODY_LIMITS) {
946
978
  try {
979
+ const bytes = new TextEncoder().encode(plaintext).byteLength;
980
+ if (bytes > limits.plaintextBytes)
981
+ return err(HaiCryptoError.TRANSPORT_PAYLOAD_TOO_LARGE, cryptoM("crypto_transportBodyTooLarge"), new TransportBodyTooLargeError(limits.plaintextBytes, bytes, { direction }));
947
982
  const encryptionKey = service.symmetric.generateKey();
948
983
  const authenticationKey = service.symmetric.generateKey() + service.symmetric.generateKey();
949
984
  const encrypted = service.symmetric.encryptWithIV(plaintext, encryptionKey);
@@ -959,14 +994,14 @@ function sealTransportPayload(service, plaintext, context, direction, status, re
959
994
  if (!signature.success)
960
995
  return transportAuthenticationFailure();
961
996
  const authenticated = { ...payload, tag: tag.data, signature: signature.data };
962
- return isValidEncryptedPayload(authenticated) ? ok(authenticated) : transportAuthenticationFailure();
997
+ return isValidEncryptedPayload(authenticated, limits) ? ok(authenticated) : transportAuthenticationFailure();
963
998
  } catch {
964
999
  return transportAuthenticationFailure();
965
1000
  }
966
1001
  }
967
- function openTransportPayload(service, payload, recipientPrivateKey, senderPublicKey) {
1002
+ function openTransportPayload(service, payload, recipientPrivateKey, senderPublicKey, limits = TRANSPORT_BODY_LIMITS) {
968
1003
  try {
969
- if (!isValidEncryptedPayload(payload))
1004
+ if (!isValidEncryptedPayload(payload, limits))
970
1005
  return transportAuthenticationFailure();
971
1006
  const text = authenticatedText(payload);
972
1007
  const verified = service.asymmetric.verify(JSON.stringify([text, payload.tag]), payload.signature, senderPublicKey);
@@ -985,25 +1020,57 @@ function openTransportPayload(service, payload, recipientPrivateKey, senderPubli
985
1020
  const plaintext = service.symmetric.decryptWithIV(payload.ciphertext, keys[0], payload.iv);
986
1021
  if (!plaintext.success)
987
1022
  return transportAuthenticationFailure();
1023
+ const bytes = new TextEncoder().encode(plaintext.data).byteLength;
1024
+ if (bytes > limits.plaintextBytes)
1025
+ return err(HaiCryptoError.TRANSPORT_PAYLOAD_TOO_LARGE, cryptoM("crypto_transportBodyTooLarge"), new TransportBodyTooLargeError(limits.plaintextBytes, bytes, { direction: payload.direction }));
988
1026
  return plaintext;
989
1027
  } catch {
990
1028
  return transportAuthenticationFailure();
991
1029
  }
992
1030
  }
993
- function isValidEncryptedPayload(value) {
1031
+ function isValidEncryptedPayload(value, limits = TRANSPORT_BODY_LIMITS) {
994
1032
  if (!value || typeof value !== "object")
995
1033
  return false;
996
1034
  const p = value;
997
1035
  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);
1036
+ 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 <= 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
1037
  }
1000
1038
  function sameTransportContext(a, b) {
1001
1039
  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
1040
  }
1003
1041
 
1042
+ // src/transport/crypto-transport-error.ts
1043
+ var TransportTimeError = class extends Error {
1044
+ constructor(context) {
1045
+ super(cryptoM("crypto_transportTimeInvalid"));
1046
+ this.context = context;
1047
+ this.name = "TransportTimeError";
1048
+ }
1049
+ context;
1050
+ code = HaiCryptoError.TRANSPORT_TIME_INVALID.code;
1051
+ };
1052
+ async function createTransportErrorResponse(error, authenticated) {
1053
+ const status = error instanceof TransportTimeError ? 400 : error.direction === "request" ? 413 : 500;
1054
+ const body = JSON.stringify({ success: false, error: { code: error.code, message: error.message, httpStatus: status, details: error instanceof TransportTimeError ? void 0 : {
1055
+ direction: error.direction,
1056
+ stage: error.stage,
1057
+ limitBytes: error.limitBytes,
1058
+ actualBytes: error.actualBytes
1059
+ } } });
1060
+ if (authenticated) {
1061
+ const encrypted = await authenticated.manager.encryptResponse(authenticated.context, body, status);
1062
+ if (encrypted.success) {
1063
+ return Response.json(encrypted.data, { status, headers: { [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER]: TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE } });
1064
+ }
1065
+ }
1066
+ return new Response(body, { status, headers: { "Content-Type": "application/json; charset=utf-8" } });
1067
+ }
1068
+
1004
1069
  // src/transport/crypto-transport-client.ts
1005
1070
  function createTransportClient(options) {
1006
1071
  const baseFetch = options.fetch ?? fetch;
1072
+ const localLimits = options.maxPlaintextBytes === void 0 ? void 0 : resolveTransportBodyLimits(options.maxPlaintextBytes);
1073
+ let bodyLimits = localLimits ?? resolveTransportBodyLimits();
1007
1074
  let keyPair = null;
1008
1075
  let serverPublicKey = null;
1009
1076
  let serverKeyId = null;
@@ -1047,6 +1114,12 @@ function createTransportClient(options) {
1047
1114
  if (!isKeyExchangeResponse(body)) {
1048
1115
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeMissingFields"));
1049
1116
  }
1117
+ try {
1118
+ const serverLimits = resolveTransportBodyLimits(body.maxPlaintextBytes);
1119
+ bodyLimits = resolveTransportBodyLimits(Math.min(localLimits?.plaintextBytes ?? serverLimits.plaintextBytes, serverLimits.plaintextBytes));
1120
+ } catch {
1121
+ return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportBodyLimitInvalid"));
1122
+ }
1050
1123
  keyPair = localKeyPair;
1051
1124
  serverPublicKey = body.serverPublicKey;
1052
1125
  serverKeyId = body.serverKeyId;
@@ -1071,12 +1144,12 @@ function createTransportClient(options) {
1071
1144
  function encryptBody(plaintext, context) {
1072
1145
  if (!serverPublicKey || !serverKeyId || !keyPair)
1073
1146
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
1074
- return sealTransportPayload(options.crypto, plaintext, context, "request", 0, serverPublicKey, keyPair.privateKey);
1147
+ return sealTransportPayload(options.crypto, plaintext, context, "request", 0, serverPublicKey, keyPair.privateKey, bodyLimits);
1075
1148
  }
1076
1149
  function decryptPayload(payload) {
1077
1150
  if (!keyPair || !serverPublicKey)
1078
1151
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
1079
- return openTransportPayload(options.crypto, payload, keyPair.privateKey, serverPublicKey);
1152
+ return openTransportPayload(options.crypto, payload, keyPair.privateKey, serverPublicKey, bodyLimits);
1080
1153
  }
1081
1154
  const encryptedFetch = async (input, init) => {
1082
1155
  const requestInput = isRequestLike(input) ? input : void 0;
@@ -1109,7 +1182,7 @@ function createTransportClient(options) {
1109
1182
  if (rawBody == null && requestInput) {
1110
1183
  const method2 = (init?.method ?? requestInput.method).toUpperCase();
1111
1184
  if (method2 !== "GET" && method2 !== "HEAD") {
1112
- const text = await readTransportBody(requestInput.body, { maxBytes: TRANSPORT_BODY_LIMITS.plaintextBytes, signal });
1185
+ const text = await readTransportBody(requestInput.body, { maxBytes: bodyLimits.plaintextBytes, signal });
1113
1186
  if (text)
1114
1187
  rawBody = text;
1115
1188
  }
@@ -1129,13 +1202,13 @@ function createTransportClient(options) {
1129
1202
  method,
1130
1203
  pathHash: pathHash.data
1131
1204
  };
1132
- const plaintext = rawBody == null ? "" : await readTransportBody(new Response(rawBody).body, { maxBytes: TRANSPORT_BODY_LIMITS.plaintextBytes, signal });
1205
+ const plaintext = rawBody == null ? "" : await readTransportBody(new Response(rawBody).body, { maxBytes: bodyLimits.plaintextBytes, signal });
1133
1206
  const encResult = encryptBody(plaintext, context);
1134
1207
  if (!encResult.success)
1135
1208
  throw new Error(encResult.error.message);
1136
1209
  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");
1210
+ if (new TextEncoder().encode(serialized).byteLength > bodyLimits.encryptedPayloadBytes)
1211
+ throw new TransportBodyTooLargeError(bodyLimits.encryptedPayloadBytes, new TextEncoder().encode(serialized).byteLength, { stage: "encrypted" });
1139
1212
  let nextBody;
1140
1213
  headers.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
1141
1214
  headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE);
@@ -1143,7 +1216,7 @@ function createTransportClient(options) {
1143
1216
  headers.delete("Content-Encoding");
1144
1217
  if (method === "GET" || method === "HEAD") {
1145
1218
  if (new TextEncoder().encode(serialized).byteLength > TRANSPORT_BODY_LIMITS.envelopeHeaderBytes)
1146
- throw new RangeError("Transport request envelope header byte limit exceeded");
1219
+ throw new TransportBodyTooLargeError(TRANSPORT_BODY_LIMITS.envelopeHeaderBytes, new TextEncoder().encode(serialized).byteLength, { stage: "header" });
1147
1220
  headers.set(TRANSPORT_PROTOCOL.ENVELOPE_HEADER, serialized);
1148
1221
  } else {
1149
1222
  nextBody = serialized;
@@ -1169,6 +1242,8 @@ function createTransportClient(options) {
1169
1242
  if (response.headers.get(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER) !== TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE) {
1170
1243
  void response.body?.cancel().catch(() => {
1171
1244
  });
1245
+ if (response.status === 413)
1246
+ throw new TransportBodyTooLargeError(bodyLimits.encryptedPayloadBytes, void 0, { stage: "encrypted" });
1172
1247
  throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
1173
1248
  }
1174
1249
  let payload;
@@ -1180,19 +1255,44 @@ function createTransportClient(options) {
1180
1255
  throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
1181
1256
  }
1182
1257
  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 }));
1185
- } catch {
1258
+ throw new TransportBodyTooLargeError(TRANSPORT_BODY_LIMITS.envelopeHeaderBytes, new TextEncoder().encode(envelope).byteLength, { direction: "response", stage: "header" });
1259
+ payload = JSON.parse(envelope ?? await readTransportBody(response.body, { maxBytes: bodyLimits.encryptedPayloadBytes, direction: "response", stage: "encrypted", signal }));
1260
+ } catch (error) {
1261
+ if (error instanceof TransportBodyTooLargeError)
1262
+ throw error;
1186
1263
  throw new Error(cryptoM("crypto_transportResponseDecryptFailed"), signal?.aborted ? { cause: signal.reason } : void 0);
1187
1264
  }
1188
- if (!isValidEncryptedPayload(payload) || payload.direction !== "response" || payload.status !== response.status || !sameTransportContext(payload.context, context)) {
1265
+ if (!isValidEncryptedPayload(payload, bodyLimits) || payload.direction !== "response" || payload.status !== response.status || !sameTransportContext(payload.context, context)) {
1189
1266
  throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
1190
1267
  }
1191
1268
  const decResult = decryptPayload(payload);
1192
- if (!decResult.success)
1269
+ if (!decResult.success) {
1270
+ if (decResult.error.cause instanceof TransportBodyTooLargeError)
1271
+ throw decResult.error.cause;
1193
1272
  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");
1273
+ }
1274
+ if (new TextEncoder().encode(decResult.data).byteLength > bodyLimits.plaintextBytes)
1275
+ throw new TransportBodyTooLargeError(bodyLimits.plaintextBytes, new TextEncoder().encode(decResult.data).byteLength, { direction: "response" });
1276
+ if (response.status === 400) {
1277
+ let failure;
1278
+ try {
1279
+ failure = JSON.parse(decResult.data);
1280
+ } catch {
1281
+ }
1282
+ if (failure?.success === false && failure.error?.code === HaiCryptoError.TRANSPORT_TIME_INVALID.code)
1283
+ throw new TransportTimeError();
1284
+ }
1285
+ if (response.status === 500) {
1286
+ let failure;
1287
+ try {
1288
+ failure = JSON.parse(decResult.data);
1289
+ } catch {
1290
+ }
1291
+ const details = failure?.error?.details;
1292
+ if (failure?.success === false && failure.error?.code === "hai:crypto:070" && details?.direction === "response" && (details.stage === "plaintext" || details.stage === "encrypted" || details.stage === "header") && typeof details.limitBytes === "number" && Number.isSafeInteger(details.limitBytes) && details.limitBytes >= 0) {
1293
+ throw new TransportBodyTooLargeError(details.limitBytes, typeof details.actualBytes === "number" ? details.actualBytes : void 0, { direction: "response", stage: details.stage });
1294
+ }
1295
+ }
1196
1296
  const respHeaders = new Headers(response.headers);
1197
1297
  respHeaders.delete(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER);
1198
1298
  respHeaders.delete(TRANSPORT_PROTOCOL.ENVELOPE_HEADER);
@@ -1258,6 +1358,12 @@ function isTransportServerKeyRing(value) {
1258
1358
 
1259
1359
  // src/transport/crypto-transport-server.ts
1260
1360
  function createTransportEncryption(cryptoService, options) {
1361
+ let bodyLimits;
1362
+ try {
1363
+ bodyLimits = resolveTransportBodyLimits(options?.maxPlaintextBytes);
1364
+ } catch {
1365
+ return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportBodyLimitInvalid"));
1366
+ }
1261
1367
  const ring = parseTransportServerKeyRing(options?.serverKeyRing);
1262
1368
  if (!ring.success)
1263
1369
  return ring;
@@ -1280,6 +1386,7 @@ function createTransportEncryption(cryptoService, options) {
1280
1386
  const serverKeyPair = serverKeys.get(activeKeyId);
1281
1387
  const keyStore = options.keyStore;
1282
1388
  const manager = {
1389
+ bodyLimits,
1283
1390
  getServerKeyId() {
1284
1391
  return activeKeyId;
1285
1392
  },
@@ -1303,36 +1410,40 @@ function createTransportEncryption(cryptoService, options) {
1303
1410
  const key = serverKeys.get(context.serverKeyId);
1304
1411
  if (!key)
1305
1412
  return transportAuthenticationFailure();
1306
- return sealTransportPayload(cryptoService, data, context, "response", status, clientPublicKey, key.privateKey);
1413
+ return sealTransportPayload(cryptoService, data, context, "response", status, clientPublicKey, key.privateKey, bodyLimits);
1307
1414
  } catch {
1308
1415
  return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportResponseEncryptFailed"));
1309
1416
  }
1310
1417
  },
1311
1418
  async decryptRequest(payload, expected) {
1312
1419
  try {
1313
- if (!isValidEncryptedPayload(payload) || payload.direction !== "request" || payload.status !== 0)
1420
+ if (!isValidEncryptedPayload(payload, bodyLimits) || payload.direction !== "request" || payload.status !== 0)
1314
1421
  return transportAuthenticationFailure();
1315
1422
  const authenticated = { ...payload, context: { ...payload.context } };
1316
1423
  const c = authenticated.context;
1317
- const now = Date.now();
1318
1424
  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) {
1425
+ if (!pathHash.success || c.clientId !== expected.clientId || c.method !== expected.method || c.pathHash !== pathHash.data) {
1320
1426
  return transportAuthenticationFailure();
1321
1427
  }
1322
1428
  const key = serverKeys.get(c.serverKeyId);
1323
1429
  const clientPublicKey = await keyStore.get(c.clientId);
1324
1430
  if (!key || !clientPublicKey)
1325
1431
  return transportAuthenticationFailure();
1326
- const plaintext = openTransportPayload(cryptoService, authenticated, key.privateKey, clientPublicKey);
1432
+ const plaintext = openTransportPayload(cryptoService, authenticated, key.privateKey, clientPublicKey, bodyLimits);
1327
1433
  if (!plaintext.success)
1328
1434
  return plaintext;
1329
- if (Date.now() >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS)
1330
- return transportAuthenticationFailure();
1435
+ const timeFailure = () => {
1436
+ const error = new TransportTimeError(c);
1437
+ return err(HaiCryptoError.TRANSPORT_TIME_INVALID, error.message, error);
1438
+ };
1439
+ const now = Date.now();
1440
+ if (c.issuedAt > now + TRANSPORT_PROTOCOL.CLOCK_SKEW_MS || now >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS)
1441
+ return timeFailure();
1331
1442
  if (!await keyStore.claimRequest(c.clientId, c.requestId, c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS + 2 * TRANSPORT_PROTOCOL.CLOCK_SKEW_MS)) {
1332
1443
  return transportAuthenticationFailure();
1333
1444
  }
1334
1445
  if (Date.now() >= c.issuedAt + TRANSPORT_PROTOCOL.REQUEST_WINDOW_MS)
1335
- return transportAuthenticationFailure();
1446
+ return timeFailure();
1336
1447
  return plaintext;
1337
1448
  } catch {
1338
1449
  return transportAuthenticationFailure();
@@ -1480,9 +1591,10 @@ async function authenticateTransportRequest(manager, request) {
1480
1591
  if (bodyless && (!envelope || request.body !== null) || !bodyless && envelope !== null)
1481
1592
  return reject();
1482
1593
  if (envelope && new TextEncoder().encode(envelope).byteLength > TRANSPORT_BODY_LIMITS.envelopeHeaderBytes)
1483
- throw new RangeError("Transport request envelope byte limit exceeded");
1594
+ throw new TransportBodyTooLargeError(TRANSPORT_BODY_LIMITS.envelopeHeaderBytes, new TextEncoder().encode(envelope).byteLength, { stage: "header" });
1484
1595
  const text = envelope ?? await readTransportBody(request.body, {
1485
- maxBytes: TRANSPORT_BODY_LIMITS.encryptedPayloadBytes,
1596
+ maxBytes: manager.bodyLimits.encryptedPayloadBytes,
1597
+ stage: "encrypted",
1486
1598
  signal: request.signal
1487
1599
  });
1488
1600
  let payload;
@@ -1491,7 +1603,7 @@ async function authenticateTransportRequest(manager, request) {
1491
1603
  } catch {
1492
1604
  return transportAuthenticationFailure();
1493
1605
  }
1494
- if (!isValidEncryptedPayload(payload) || payload.direction !== "request" || payload.status !== 0)
1606
+ if (!isValidEncryptedPayload(payload, manager.bodyLimits) || payload.direction !== "request" || payload.status !== 0)
1495
1607
  return transportAuthenticationFailure();
1496
1608
  const url = new URL(request.url);
1497
1609
  const decrypted = await manager.decryptRequest(payload, {
@@ -1499,10 +1611,13 @@ async function authenticateTransportRequest(manager, request) {
1499
1611
  method: request.method,
1500
1612
  path: url.pathname + url.search
1501
1613
  });
1502
- if (!decrypted.success)
1614
+ if (!decrypted.success) {
1615
+ if (decrypted.error.cause instanceof TransportBodyTooLargeError)
1616
+ throw decrypted.error.cause;
1503
1617
  return decrypted;
1504
- if (new TextEncoder().encode(decrypted.data).byteLength > TRANSPORT_BODY_LIMITS.plaintextBytes)
1505
- throw new RangeError("Transport request plaintext byte limit exceeded");
1618
+ }
1619
+ if (new TextEncoder().encode(decrypted.data).byteLength > manager.bodyLimits.plaintextBytes)
1620
+ throw new TransportBodyTooLargeError(manager.bodyLimits.plaintextBytes, new TextEncoder().encode(decrypted.data).byteLength);
1506
1621
  if (bodyless && decrypted.data !== "")
1507
1622
  return transportAuthenticationFailure();
1508
1623
  return ok({ plaintext: decrypted.data, context: payload.context });
@@ -1807,6 +1922,6 @@ function createReldbTransportKeyStore(options) {
1807
1922
  };
1808
1923
  }
1809
1924
 
1810
- export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PROTOCOL, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, crypto2 as crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody };
1925
+ export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, TransportBodyTooLargeError, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportErrorResponse, crypto2 as crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
1811
1926
  //# sourceMappingURL=index.js.map
1812
1927
  //# sourceMappingURL=index.js.map