@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/README.en.md +5 -1
- package/README.ja.md +5 -1
- package/README.md +5 -1
- package/dist/index.d.ts +146 -55
- package/dist/index.js +159 -44
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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: "
|
|
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: "\
|
|
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
|
|
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
|
|
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 <=
|
|
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:
|
|
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:
|
|
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 >
|
|
1138
|
-
throw new
|
|
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
|
|
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
|
|
1184
|
-
payload = JSON.parse(envelope ?? await readTransportBody(response.body, { maxBytes:
|
|
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
|
-
|
|
1195
|
-
|
|
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
|
|
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
|
-
|
|
1330
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
1505
|
-
|
|
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
|