@h-ai/crypto 1.0.0-beta.21 → 1.0.0-beta.23
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 +6 -6
- package/README.ja.md +6 -6
- package/README.md +6 -6
- package/dist/index.d.ts +26 -14
- package/dist/index.js +98 -77
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ var en_US_default = {
|
|
|
59
59
|
crypto_transportBodyLimitInvalid: "Transport plaintext limit must be an integer between 1024 and 67108864 bytes",
|
|
60
60
|
crypto_transportBodyTooLarge: "Encrypted transport payload exceeds the configured byte limit",
|
|
61
61
|
crypto_transportTimeInvalid: "Your device and server clocks may be out of sync, or the request has expired. Enable automatic time synchronization or set your device time manually, then refresh to retry. If this continues, ask an administrator to check the server clock.",
|
|
62
|
+
crypto_transportTimeResponseInvalid: "Transport time validation response is missing a valid server timestamp",
|
|
62
63
|
crypto_transportTimeInvalidWithServerTime: "Your device and server clocks may be out of sync, or the request has expired. Server time is {serverTime}. Enable automatic time synchronization or set your device time manually, then refresh to retry. If this continues, ask an administrator to check the server clock.",
|
|
63
64
|
crypto_transportTimeConfigInvalid: "Invalid transport time settings. Future clock skew must be an integer from 0 to 300 seconds and request lifetime from 1 to 600 seconds.",
|
|
64
65
|
crypto_transportBodyLimitConflict: "Explicit transport limit {limit} bytes is below the enabled module requirement of {required} bytes; adjust the module budget or explicit limit",
|
|
@@ -121,6 +122,7 @@ var ja_JP_default = {
|
|
|
121
122
|
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",
|
|
122
123
|
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",
|
|
123
124
|
crypto_transportTimeInvalid: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u7AEF\u672B\u306E\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3059\u308B\u304B\u3001\u624B\u52D5\u3067\u6642\u523B\u3092\u8A2D\u5B9A\u3057\u3066\u304B\u3089\u3001\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
125
|
+
crypto_transportTimeResponseInvalid: "\u6697\u53F7\u5316\u901A\u4FE1\u306E\u6642\u523B\u691C\u8A3C\u5FDC\u7B54\u306B\u6709\u52B9\u306A\u30B5\u30FC\u30D0\u30FC\u6642\u523B\u304C\u3042\u308A\u307E\u305B\u3093",
|
|
124
126
|
crypto_transportTimeInvalidWithServerTime: "\u7AEF\u672B\u3068\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u304C\u540C\u671F\u3057\u3066\u3044\u306A\u3044\u304B\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u3066\u3044\u307E\u3059\u3002\u30B5\u30FC\u30D0\u30FC\u6642\u523B\u306F {serverTime} \u3067\u3059\u3002\u7AEF\u672B\u306E\u81EA\u52D5\u6642\u523B\u540C\u671F\u3092\u6709\u52B9\u306B\u3059\u308B\u304B\u3001\u624B\u52D5\u3067\u6642\u523B\u3092\u8A2D\u5B9A\u3057\u3066\u304B\u3089\u3001\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6D88\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u30B5\u30FC\u30D0\u30FC\u306E\u6642\u523B\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
125
127
|
crypto_transportTimeConfigInvalid: "\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u306E\u6642\u523B\u8A2D\u5B9A\u304C\u7121\u52B9\u3067\u3059\u3002\u672A\u6765\u65B9\u5411\u306E\u6642\u523B\u5DEE\u306F 0\uFF5E300 \u79D2\u3001\u8981\u6C42\u306E\u6709\u52B9\u671F\u9593\u306F 1\uFF5E600 \u79D2\u306E\u6574\u6570\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
126
128
|
crypto_transportBodyLimitConflict: "\u660E\u793A\u3055\u308C\u305F\u8EE2\u9001\u4E0A\u9650 {limit} \u30D0\u30A4\u30C8\u306F\u6709\u52B9\u306A\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u5FC5\u8981\u91CF {required} \u30D0\u30A4\u30C8\u672A\u6E80\u3067\u3059\u3002\u4E88\u7B97\u307E\u305F\u306F\u4E0A\u9650\u3092\u5909\u66F4\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
@@ -183,6 +185,7 @@ var zh_CN_default = {
|
|
|
183
185
|
crypto_transportBodyLimitInvalid: "\u4F20\u8F93\u660E\u6587\u5BB9\u91CF\u5FC5\u987B\u662F 1024 \u5230 67108864 \u5B57\u8282\u4E4B\u95F4\u7684\u6574\u6570",
|
|
184
186
|
crypto_transportBodyTooLarge: "\u52A0\u5BC6\u4F20\u8F93\u62A5\u6587\u8D85\u8FC7\u914D\u7F6E\u7684\u5B57\u8282\u4E0A\u9650",
|
|
185
187
|
crypto_transportTimeInvalid: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u8BF7\u5F00\u542F\u8BBE\u5907\u7684\u81EA\u52A8\u6821\u65F6\u6216\u624B\u5DE5\u4FEE\u6539\u65F6\u95F4\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
|
|
188
|
+
crypto_transportTimeResponseInvalid: "\u4F20\u8F93\u52A0\u5BC6\u65F6\u95F4\u6821\u9A8C\u54CD\u5E94\u7F3A\u5C11\u6709\u6548\u7684\u670D\u52A1\u5668\u65F6\u95F4",
|
|
186
189
|
crypto_transportTimeInvalidWithServerTime: "\u8BBE\u5907\u4E0E\u670D\u52A1\u5668\u65F6\u95F4\u53EF\u80FD\u4E0D\u540C\u6B65\uFF0C\u6216\u8BF7\u6C42\u5DF2\u8FC7\u671F\u3002\u670D\u52A1\u5668\u65F6\u95F4\u4E3A {serverTime}\u3002\u8BF7\u5F00\u542F\u8BBE\u5907\u7684\u81EA\u52A8\u6821\u65F6\u6216\u624B\u5DE5\u4FEE\u6539\u65F6\u95F4\uFF0C\u7136\u540E\u5237\u65B0\u91CD\u8BD5\u3002\u82E5\u4ECD\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u68C0\u67E5\u670D\u52A1\u5668\u65F6\u95F4\u3002",
|
|
187
190
|
crypto_transportTimeConfigInvalid: "\u4F20\u8F93\u65F6\u95F4\u914D\u7F6E\u65E0\u6548\u3002\u5141\u8BB8\u7684\u672A\u6765\u504F\u5DEE\u987B\u4E3A 0 \u81F3 300 \u79D2\u7684\u6574\u6570\uFF0C\u8BF7\u6C42\u6709\u6548\u671F\u987B\u4E3A 1 \u81F3 600 \u79D2\u7684\u6574\u6570\u3002",
|
|
188
191
|
crypto_transportBodyLimitConflict: "\u663E\u5F0F\u4F20\u8F93\u4E0A\u9650 {limit} \u5B57\u8282\u5C0F\u4E8E\u5DF2\u542F\u7528\u6A21\u5757\u6240\u9700\u7684 {required} \u5B57\u8282\uFF1B\u8BF7\u8C03\u6574\u6A21\u5757\u9884\u7B97\u6216\u663E\u5F0F\u4E0A\u9650",
|
|
@@ -1058,6 +1061,9 @@ var TRANSPORT_TIME_LIMITS = Object.freeze({
|
|
|
1058
1061
|
});
|
|
1059
1062
|
|
|
1060
1063
|
// src/transport/crypto-transport-codec.ts
|
|
1064
|
+
function isValidTransportKeyTtlSeconds(value) {
|
|
1065
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 2147483647;
|
|
1066
|
+
}
|
|
1061
1067
|
function authenticatedText(payload) {
|
|
1062
1068
|
const c = payload.context;
|
|
1063
1069
|
return JSON.stringify([
|
|
@@ -1164,20 +1170,20 @@ function sameTransportContext(a, b) {
|
|
|
1164
1170
|
// src/transport/crypto-transport-error.ts
|
|
1165
1171
|
var TransportTimeError = class extends Error {
|
|
1166
1172
|
constructor(context, serverTime) {
|
|
1167
|
-
|
|
1168
|
-
|
|
1173
|
+
if (!Number.isSafeInteger(serverTime) || serverTime < 0 || serverTime > 864e13)
|
|
1174
|
+
throw new RangeError(cryptoM("crypto_transportTimeResponseInvalid"));
|
|
1175
|
+
super(cryptoM("crypto_transportTimeInvalidWithServerTime", {
|
|
1169
1176
|
params: {
|
|
1170
|
-
serverTime: new Date(
|
|
1177
|
+
serverTime: new Date(serverTime).toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC")
|
|
1171
1178
|
}
|
|
1172
|
-
});
|
|
1173
|
-
super(message);
|
|
1179
|
+
}));
|
|
1174
1180
|
this.context = context;
|
|
1181
|
+
this.serverTime = serverTime;
|
|
1175
1182
|
this.name = "TransportTimeError";
|
|
1176
|
-
this.serverTime = validTime;
|
|
1177
1183
|
}
|
|
1178
1184
|
context;
|
|
1179
|
-
code = HaiCryptoError.TRANSPORT_TIME_INVALID.code;
|
|
1180
1185
|
serverTime;
|
|
1186
|
+
code = HaiCryptoError.TRANSPORT_TIME_INVALID.code;
|
|
1181
1187
|
};
|
|
1182
1188
|
async function createTransportErrorResponse(error, authenticated) {
|
|
1183
1189
|
const isTimeError = error instanceof TransportTimeError;
|
|
@@ -1194,7 +1200,7 @@ async function createTransportErrorResponse(error, authenticated) {
|
|
|
1194
1200
|
}
|
|
1195
1201
|
};
|
|
1196
1202
|
if (authenticated) {
|
|
1197
|
-
const encryptedError = isTimeError
|
|
1203
|
+
const encryptedError = isTimeError ? {
|
|
1198
1204
|
...publicError,
|
|
1199
1205
|
message: error.message,
|
|
1200
1206
|
details: { serverTime: error.serverTime }
|
|
@@ -1217,15 +1223,14 @@ async function createTransportErrorResponse(error, authenticated) {
|
|
|
1217
1223
|
}
|
|
1218
1224
|
|
|
1219
1225
|
// src/transport/crypto-transport-client.ts
|
|
1226
|
+
function isSessionUsable(session) {
|
|
1227
|
+
return Date.now() < session.renewAt && performance.now() < session.monotonicRenewAt;
|
|
1228
|
+
}
|
|
1220
1229
|
function createTransportClient(options) {
|
|
1221
1230
|
const baseFetch = options.fetch ?? fetch;
|
|
1222
1231
|
const localLimits = options.maxPlaintextBytes === void 0 ? void 0 : resolveTransportBodyLimits(options.maxPlaintextBytes);
|
|
1223
|
-
let
|
|
1224
|
-
let
|
|
1225
|
-
let serverPublicKey = null;
|
|
1226
|
-
let serverKeyId = null;
|
|
1227
|
-
let clientId = null;
|
|
1228
|
-
let initPromise = null;
|
|
1232
|
+
let session;
|
|
1233
|
+
let initPromise;
|
|
1229
1234
|
let generation = 0;
|
|
1230
1235
|
let handshakeController;
|
|
1231
1236
|
async function doInit(attemptGeneration) {
|
|
@@ -1237,6 +1242,8 @@ function createTransportClient(options) {
|
|
|
1237
1242
|
return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientKeyGenerateFailed"));
|
|
1238
1243
|
const controller = new AbortController();
|
|
1239
1244
|
handshakeController = controller;
|
|
1245
|
+
const exchangeStartedAt = Date.now();
|
|
1246
|
+
const monotonicStart = performance.now();
|
|
1240
1247
|
const timeout = setTimeout(() => controller.abort(new DOMException("Key exchange timed out", "TimeoutError")), 3e4);
|
|
1241
1248
|
try {
|
|
1242
1249
|
let response;
|
|
@@ -1269,6 +1276,7 @@ function createTransportClient(options) {
|
|
|
1269
1276
|
if (!isKeyExchangeResponse(body)) {
|
|
1270
1277
|
return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeMissingFields"));
|
|
1271
1278
|
}
|
|
1279
|
+
let bodyLimits;
|
|
1272
1280
|
try {
|
|
1273
1281
|
const serverLimits = resolveTransportBodyLimits(body.maxPlaintextBytes);
|
|
1274
1282
|
bodyLimits = resolveTransportBodyLimits(Math.min(localLimits?.plaintextBytes ?? serverLimits.plaintextBytes, serverLimits.plaintextBytes));
|
|
@@ -1277,11 +1285,21 @@ function createTransportClient(options) {
|
|
|
1277
1285
|
}
|
|
1278
1286
|
if (attemptGeneration !== generation || controller.signal.aborted)
|
|
1279
1287
|
return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1288
|
+
const ttlMs = body.clientKeyTtlSeconds === void 0 ? Number.POSITIVE_INFINITY : body.clientKeyTtlSeconds * 1e3;
|
|
1289
|
+
const usableMs = ttlMs - Math.min(ttlMs / 10, 3e4);
|
|
1290
|
+
const nextSession = {
|
|
1291
|
+
keyPair: localKeyPair,
|
|
1292
|
+
serverPublicKey: body.serverPublicKey,
|
|
1293
|
+
serverKeyId: body.serverKeyId,
|
|
1294
|
+
clientId: body.clientId,
|
|
1295
|
+
bodyLimits,
|
|
1296
|
+
renewAt: exchangeStartedAt + usableMs,
|
|
1297
|
+
monotonicRenewAt: monotonicStart + usableMs
|
|
1298
|
+
};
|
|
1299
|
+
if (!isSessionUsable(nextSession))
|
|
1300
|
+
return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportKeyExchangeRequestFailed"));
|
|
1301
|
+
session = nextSession;
|
|
1302
|
+
return ok(nextSession);
|
|
1285
1303
|
} finally {
|
|
1286
1304
|
clearTimeout(timeout);
|
|
1287
1305
|
if (handshakeController === controller)
|
|
@@ -1289,39 +1307,23 @@ function createTransportClient(options) {
|
|
|
1289
1307
|
}
|
|
1290
1308
|
}
|
|
1291
1309
|
async function ensureReady() {
|
|
1292
|
-
if (
|
|
1293
|
-
return ok(
|
|
1310
|
+
if (session && isSessionUsable(session))
|
|
1311
|
+
return ok(session);
|
|
1294
1312
|
if (!initPromise) {
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1313
|
+
session = void 0;
|
|
1314
|
+
const pending = doInit(generation).finally(() => {
|
|
1315
|
+
if (initPromise === pending)
|
|
1316
|
+
initPromise = void 0;
|
|
1299
1317
|
});
|
|
1300
1318
|
initPromise = pending;
|
|
1301
1319
|
}
|
|
1302
1320
|
return initPromise;
|
|
1303
1321
|
}
|
|
1304
|
-
function
|
|
1305
|
-
if (!serverPublicKey || !serverKeyId || !keyPair)
|
|
1306
|
-
return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
|
|
1307
|
-
return sealTransportPayload(options.crypto, plaintext, context, "request", 0, serverPublicKey, keyPair.privateKey, bodyLimits);
|
|
1308
|
-
}
|
|
1309
|
-
function decryptPayload(payload) {
|
|
1310
|
-
if (!keyPair || !serverPublicKey)
|
|
1311
|
-
return err(HaiCommonError.INTERNAL_ERROR, cryptoM("crypto_transportClientNotInitialized"));
|
|
1312
|
-
return openTransportPayload(options.crypto, payload, keyPair.privateKey, serverPublicKey, bodyLimits);
|
|
1313
|
-
}
|
|
1314
|
-
const encryptedFetch = async (input, init) => {
|
|
1315
|
-
const requestInput = isRequestLike(input) ? input : void 0;
|
|
1316
|
-
const signal = init?.signal ?? requestInput?.signal ?? void 0;
|
|
1322
|
+
async function waitForSession(signal) {
|
|
1317
1323
|
signal?.throwIfAborted();
|
|
1318
|
-
const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : requestInput?.url ?? String(input);
|
|
1319
|
-
if (targetUrl === options.keyExchangeUrl)
|
|
1320
|
-
return baseFetch(input, init);
|
|
1321
1324
|
let onAbort;
|
|
1322
|
-
let ready;
|
|
1323
1325
|
try {
|
|
1324
|
-
ready = await (signal ? Promise.race([
|
|
1326
|
+
const ready = await (signal ? Promise.race([
|
|
1325
1327
|
ensureReady(),
|
|
1326
1328
|
new Promise((_resolve, reject) => {
|
|
1327
1329
|
onAbort = () => reject(signal.reason);
|
|
@@ -1330,24 +1332,36 @@ function createTransportClient(options) {
|
|
|
1330
1332
|
onAbort();
|
|
1331
1333
|
})
|
|
1332
1334
|
]) : ensureReady());
|
|
1335
|
+
if (!ready.success)
|
|
1336
|
+
throw new Error(ready.error.message);
|
|
1337
|
+
return ready.data;
|
|
1333
1338
|
} finally {
|
|
1334
1339
|
if (onAbort)
|
|
1335
1340
|
signal?.removeEventListener("abort", onAbort);
|
|
1336
1341
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1342
|
+
}
|
|
1343
|
+
const encryptedFetch = async (input, init) => {
|
|
1344
|
+
const requestInput = isRequestLike(input) ? input : void 0;
|
|
1345
|
+
const signal = init?.signal ?? requestInput?.signal ?? void 0;
|
|
1346
|
+
signal?.throwIfAborted();
|
|
1347
|
+
const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : requestInput?.url ?? String(input);
|
|
1348
|
+
if (targetUrl === options.keyExchangeUrl)
|
|
1349
|
+
return baseFetch(input, init);
|
|
1350
|
+
const requestGeneration = generation;
|
|
1351
|
+
let requestSession = await waitForSession(signal);
|
|
1352
|
+
if (requestGeneration !== generation)
|
|
1353
|
+
throw new Error(cryptoM("crypto_transportClientNotInitialized"));
|
|
1354
|
+
const method = (init?.method ?? requestInput?.method ?? "GET").toUpperCase();
|
|
1355
|
+
const requestBody = init?.body != null ? new Response(init.body).body : method === "GET" || method === "HEAD" ? null : requestInput?.body ?? null;
|
|
1356
|
+
const plaintext = await readTransportBody(requestBody, { maxBytes: requestSession.bodyLimits.plaintextBytes, signal });
|
|
1357
|
+
if (requestGeneration === generation && (requestSession !== session || !isSessionUsable(requestSession)))
|
|
1358
|
+
requestSession = await waitForSession(signal);
|
|
1359
|
+
signal?.throwIfAborted();
|
|
1360
|
+
if (requestGeneration !== generation || !isSessionUsable(requestSession))
|
|
1361
|
+
throw new Error(cryptoM("crypto_transportClientNotInitialized"));
|
|
1362
|
+
const { bodyLimits, clientId, keyPair, serverKeyId, serverPublicKey } = requestSession;
|
|
1339
1363
|
const headers = new Headers(init?.headers ?? requestInput?.headers);
|
|
1340
1364
|
headers.set(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER, clientId);
|
|
1341
|
-
let rawBody = init?.body;
|
|
1342
|
-
if (rawBody == null && requestInput) {
|
|
1343
|
-
const method2 = (init?.method ?? requestInput.method).toUpperCase();
|
|
1344
|
-
if (method2 !== "GET" && method2 !== "HEAD") {
|
|
1345
|
-
const text = await readTransportBody(requestInput.body, { maxBytes: bodyLimits.plaintextBytes, signal });
|
|
1346
|
-
if (text)
|
|
1347
|
-
rawBody = text;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
const method = (init?.method ?? requestInput?.method ?? "GET").toUpperCase();
|
|
1351
1365
|
const base = new URL(options.keyExchangeUrl, globalThis.location?.href ?? "http://localhost");
|
|
1352
1366
|
const url = new URL(targetUrl, base);
|
|
1353
1367
|
const pathHash = hashTransportPath(options.crypto, url.pathname + url.search);
|
|
@@ -1362,10 +1376,12 @@ function createTransportClient(options) {
|
|
|
1362
1376
|
method,
|
|
1363
1377
|
pathHash: pathHash.data
|
|
1364
1378
|
};
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
|
|
1379
|
+
const encResult = sealTransportPayload(options.crypto, plaintext, context, "request", 0, serverPublicKey, keyPair.privateKey, bodyLimits);
|
|
1380
|
+
if (!encResult.success) {
|
|
1381
|
+
if (encResult.error.cause instanceof TransportBodyTooLargeError)
|
|
1382
|
+
throw encResult.error.cause;
|
|
1368
1383
|
throw new Error(encResult.error.message);
|
|
1384
|
+
}
|
|
1369
1385
|
const serialized = JSON.stringify(encResult.data);
|
|
1370
1386
|
if (new TextEncoder().encode(serialized).byteLength > bodyLimits.encryptedPayloadBytes)
|
|
1371
1387
|
throw new TransportBodyTooLargeError(bodyLimits.encryptedPayloadBytes, new TextEncoder().encode(serialized).byteLength, { stage: "encrypted" });
|
|
@@ -1432,7 +1448,9 @@ function createTransportClient(options) {
|
|
|
1432
1448
|
if (!isValidEncryptedPayload(payload, bodyLimits) || payload.direction !== "response" || payload.status !== response.status || !sameTransportContext(payload.context, context)) {
|
|
1433
1449
|
throw new Error(cryptoM("crypto_transportResponseDecryptFailed"));
|
|
1434
1450
|
}
|
|
1435
|
-
|
|
1451
|
+
if (requestGeneration !== generation)
|
|
1452
|
+
throw new Error(cryptoM("crypto_transportClientNotInitialized"));
|
|
1453
|
+
const decResult = openTransportPayload(options.crypto, payload, keyPair.privateKey, serverPublicKey, bodyLimits);
|
|
1436
1454
|
if (!decResult.success) {
|
|
1437
1455
|
if (decResult.error.cause instanceof TransportBodyTooLargeError)
|
|
1438
1456
|
throw decResult.error.cause;
|
|
@@ -1448,7 +1466,9 @@ function createTransportClient(options) {
|
|
|
1448
1466
|
}
|
|
1449
1467
|
if (failure?.success === false && failure.error?.code === HaiCryptoError.TRANSPORT_TIME_INVALID.code) {
|
|
1450
1468
|
const serverTime = failure.error.details?.serverTime;
|
|
1451
|
-
|
|
1469
|
+
if (typeof serverTime !== "number")
|
|
1470
|
+
throw new TypeError(cryptoM("crypto_transportTimeResponseInvalid"));
|
|
1471
|
+
throw new TransportTimeError(void 0, serverTime);
|
|
1452
1472
|
}
|
|
1453
1473
|
}
|
|
1454
1474
|
if (response.status === 500) {
|
|
@@ -1474,18 +1494,18 @@ function createTransportClient(options) {
|
|
|
1474
1494
|
});
|
|
1475
1495
|
};
|
|
1476
1496
|
return {
|
|
1477
|
-
init
|
|
1497
|
+
async init() {
|
|
1498
|
+
const ready = await ensureReady();
|
|
1499
|
+
return ready.success ? ok(void 0) : ready;
|
|
1500
|
+
},
|
|
1478
1501
|
encryptedFetch,
|
|
1479
|
-
ready: () =>
|
|
1502
|
+
ready: () => session !== void 0 && isSessionUsable(session),
|
|
1480
1503
|
destroy() {
|
|
1481
1504
|
generation++;
|
|
1482
1505
|
handshakeController?.abort();
|
|
1483
1506
|
handshakeController = void 0;
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
serverKeyId = null;
|
|
1487
|
-
clientId = null;
|
|
1488
|
-
initPromise = null;
|
|
1507
|
+
session = void 0;
|
|
1508
|
+
initPromise = void 0;
|
|
1489
1509
|
}
|
|
1490
1510
|
};
|
|
1491
1511
|
}
|
|
@@ -1500,7 +1520,7 @@ function isKeyExchangeResponse(body) {
|
|
|
1500
1520
|
if (!body || typeof body !== "object")
|
|
1501
1521
|
return false;
|
|
1502
1522
|
const fields = body;
|
|
1503
|
-
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);
|
|
1523
|
+
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) && typeof fields.maxPlaintextBytes === "number" && (fields.clientKeyTtlSeconds === void 0 || isValidTransportKeyTtlSeconds(fields.clientKeyTtlSeconds));
|
|
1504
1524
|
}
|
|
1505
1525
|
function parseTransportServerKeyRing(serialized) {
|
|
1506
1526
|
try {
|
|
@@ -1554,6 +1574,9 @@ function createTransportEncryption(cryptoService, options) {
|
|
|
1554
1574
|
}
|
|
1555
1575
|
if (core.module.requiresSharedState(globalThis.process?.env ?? {}) && options.keyStore.stateScope !== "shared")
|
|
1556
1576
|
return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportKeyStoreRequired"));
|
|
1577
|
+
const { clientKeyTtlSeconds } = options.keyStore;
|
|
1578
|
+
if (clientKeyTtlSeconds !== void 0 && !isValidTransportKeyTtlSeconds(clientKeyTtlSeconds))
|
|
1579
|
+
return err(HaiCryptoError.INVALID_INPUT, cryptoM("crypto_transportKeyStoreTtlInvalid"));
|
|
1557
1580
|
const serverKeys = /* @__PURE__ */ new Map();
|
|
1558
1581
|
for (const [id, key] of Object.entries(ring.data.keys)) {
|
|
1559
1582
|
const probe = cryptoService.asymmetric.encrypt("hai-transport-key-check", key.publicKey);
|
|
@@ -1569,6 +1592,7 @@ function createTransportEncryption(cryptoService, options) {
|
|
|
1569
1592
|
const keyStore = options.keyStore;
|
|
1570
1593
|
const manager = {
|
|
1571
1594
|
bodyLimits,
|
|
1595
|
+
clientKeyTtlSeconds,
|
|
1572
1596
|
getServerKeyId() {
|
|
1573
1597
|
return activeKeyId;
|
|
1574
1598
|
},
|
|
@@ -1886,13 +1910,6 @@ function createInMemoryKeyStore(maxClients = DEFAULT_MAX_CLIENTS) {
|
|
|
1886
1910
|
|
|
1887
1911
|
// src/transport/store-provider/crypto-transport-store-redis.ts
|
|
1888
1912
|
var TRANSPORT_KEY_CACHE_PREFIX = "hai:crypto:transport:client";
|
|
1889
|
-
function normalizeTtlSeconds(ttlSeconds) {
|
|
1890
|
-
if (ttlSeconds === void 0)
|
|
1891
|
-
return void 0;
|
|
1892
|
-
if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds <= 0 || ttlSeconds > 2147483647)
|
|
1893
|
-
throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
|
|
1894
|
-
return ttlSeconds;
|
|
1895
|
-
}
|
|
1896
1913
|
function createClientId2() {
|
|
1897
1914
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
|
|
1898
1915
|
return `c_${crypto.randomUUID()}`;
|
|
@@ -1909,11 +1926,14 @@ function unwrapResult(operation, result) {
|
|
|
1909
1926
|
throw toOperationError(operation, result.error);
|
|
1910
1927
|
}
|
|
1911
1928
|
function createRedisTransportKeyStore(options) {
|
|
1912
|
-
const ttlSeconds =
|
|
1929
|
+
const { ttlSeconds } = options;
|
|
1930
|
+
if (ttlSeconds !== void 0 && !isValidTransportKeyTtlSeconds(ttlSeconds))
|
|
1931
|
+
throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
|
|
1913
1932
|
function buildCacheKey(clientId) {
|
|
1914
1933
|
return `${TRANSPORT_KEY_CACHE_PREFIX}:${clientId}`;
|
|
1915
1934
|
}
|
|
1916
1935
|
return {
|
|
1936
|
+
clientKeyTtlSeconds: ttlSeconds,
|
|
1917
1937
|
get stateScope() {
|
|
1918
1938
|
return options.cache.config?.type === "redis" ? "shared" : "local";
|
|
1919
1939
|
},
|
|
@@ -1972,7 +1992,7 @@ function unwrapResult2(operation, result) {
|
|
|
1972
1992
|
}
|
|
1973
1993
|
function createReldbTransportKeyStore(options) {
|
|
1974
1994
|
const { ttlSeconds } = options;
|
|
1975
|
-
if (ttlSeconds !== void 0 &&
|
|
1995
|
+
if (ttlSeconds !== void 0 && !isValidTransportKeyTtlSeconds(ttlSeconds))
|
|
1976
1996
|
throw new TypeError(cryptoM("crypto_transportKeyStoreTtlInvalid"));
|
|
1977
1997
|
const maxClients = options.maxClients ?? 1e4;
|
|
1978
1998
|
if (!Number.isSafeInteger(maxClients) || maxClients <= 0)
|
|
@@ -2072,6 +2092,7 @@ function createReldbTransportKeyStore(options) {
|
|
|
2072
2092
|
return withCapacityLock(pruneRows);
|
|
2073
2093
|
}
|
|
2074
2094
|
return {
|
|
2095
|
+
clientKeyTtlSeconds: ttlSeconds,
|
|
2075
2096
|
get stateScope() {
|
|
2076
2097
|
return options.reldb.config?.type && options.reldb.config.type !== "sqlite" ? "shared" : "local";
|
|
2077
2098
|
},
|