@tinycloud/sdk-services 3.11.0-beta.2 → 3.12.0-beta.10
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/{BaseService-Ma2MwJAd.d.ts → BaseService-BRDG69KR.d.ts} +1 -1
- package/dist/{BaseService-O0iyuFLk.d.cts → BaseService-CdBkfPGE.d.cts} +1 -1
- package/dist/{IHooksService-4QNODVy6.d.cts → IHooksService-C9p3stsB.d.cts} +1 -1
- package/dist/{IHooksService-CvTpJbQD.d.ts → IHooksService-D6eyZat1.d.ts} +1 -1
- package/dist/{ISQLService-Aly1CBsW.d.ts → ISQLService-B_4KcGA-.d.ts} +1 -1
- package/dist/{ISQLService-BOgWzHLT.d.cts → ISQLService-C6nNpcls.d.cts} +1 -1
- package/dist/context.cjs +4 -0
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.cts +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.js +4 -0
- package/dist/context.js.map +1 -1
- package/dist/encryption/index.cjs +276 -14
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +26 -28
- package/dist/encryption/index.d.ts +26 -28
- package/dist/encryption/index.js +268 -14
- package/dist/encryption/index.js.map +1 -1
- package/dist/encryption/named-keys.cjs +225 -14
- package/dist/encryption/named-keys.cjs.map +1 -1
- package/dist/encryption/named-keys.d.cts +7 -2
- package/dist/encryption/named-keys.d.ts +7 -2
- package/dist/encryption/named-keys.js +225 -14
- package/dist/encryption/named-keys.js.map +1 -1
- package/dist/encryption/resource.cjs.map +1 -1
- package/dist/encryption/resource.js.map +1 -1
- package/dist/feed/index.cjs +4 -0
- package/dist/feed/index.cjs.map +1 -1
- package/dist/feed/index.d.cts +3 -3
- package/dist/feed/index.d.ts +3 -3
- package/dist/feed/index.js +4 -0
- package/dist/feed/index.js.map +1 -1
- package/dist/index.cjs +509 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +499 -18
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +34 -0
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +4 -4
- package/dist/kv/index.d.ts +4 -4
- package/dist/kv/index.js +34 -0
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +39 -8
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -3
- package/dist/kv/replication/index.d.ts +3 -3
- package/dist/kv/replication/index.js +38 -7
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/{scope-DEgc9f-W.d.cts → scope-DtcbyRKY.d.cts} +1 -1
- package/dist/{scope-BZr0QwxX.d.ts → scope-J20PQP3u.d.ts} +1 -1
- package/dist/sql/index.cjs +36 -0
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +4 -4
- package/dist/sql/index.d.ts +4 -4
- package/dist/sql/index.js +36 -0
- package/dist/sql/index.js.map +1 -1
- package/dist/telemetry/requestLog.cjs +4 -0
- package/dist/telemetry/requestLog.cjs.map +1 -1
- package/dist/telemetry/requestLog.d.cts +1 -1
- package/dist/telemetry/requestLog.d.ts +1 -1
- package/dist/telemetry/requestLog.js +4 -0
- package/dist/telemetry/requestLog.js.map +1 -1
- package/dist/{types-D4XbIfFJ.d.cts → types-DfqNz9Nu.d.cts} +135 -6
- package/dist/{types-CSd0RYir.d.ts → types-DjAP8dve.d.cts} +4 -0
- package/dist/{types-CSd0RYir.d.cts → types-DjAP8dve.d.ts} +4 -0
- package/dist/{types-Bf91gup6.d.ts → types-Nvi_RckJ.d.ts} +135 -6
- package/package.json +4 -1
|
@@ -31,8 +31,11 @@ __export(encryption_exports, {
|
|
|
31
31
|
ENCRYPTION_SERVICE_SHORT: () => ENCRYPTION_SERVICE_SHORT,
|
|
32
32
|
ENVELOPE_VERSION: () => ENVELOPE_VERSION,
|
|
33
33
|
EncryptionService: () => EncryptionService,
|
|
34
|
+
KEY_WRAP_V2_ALG: () => KEY_WRAP_V2_ALG,
|
|
34
35
|
NETWORK_NAME_PATTERN: () => NETWORK_NAME_PATTERN,
|
|
35
36
|
NetworkIdError: () => NetworkIdError,
|
|
37
|
+
assertKeyWrapAlgorithm: () => assertKeyWrapAlgorithm,
|
|
38
|
+
assertKeyWrapContext: () => assertKeyWrapContext,
|
|
36
39
|
base64Decode: () => base64Decode,
|
|
37
40
|
base64Encode: () => base64Encode,
|
|
38
41
|
buildCanonicalDecryptRequest: () => buildCanonicalDecryptRequest,
|
|
@@ -44,6 +47,7 @@ __export(encryption_exports, {
|
|
|
44
47
|
canonicalSignedResponse: () => canonicalSignedResponse,
|
|
45
48
|
canonicalize: () => canonicalize,
|
|
46
49
|
checkDecryptInvocationInput: () => checkDecryptInvocationInput,
|
|
50
|
+
decryptColumn: () => decryptColumn,
|
|
47
51
|
decryptEnvelopeWithKey: () => decryptEnvelopeWithKey,
|
|
48
52
|
deriveSignedReceiverKey: () => deriveSignedReceiverKey,
|
|
49
53
|
discoverNetwork: () => discoverNetwork,
|
|
@@ -53,11 +57,15 @@ __export(encryption_exports, {
|
|
|
53
57
|
generateRandomReceiverKey: () => generateRandomReceiverKey,
|
|
54
58
|
hexDecode: () => hexDecode,
|
|
55
59
|
hexEncode: () => hexEncode,
|
|
60
|
+
isKeyWrapV2: () => isKeyWrapV2,
|
|
56
61
|
isNetworkId: () => isNetworkId,
|
|
57
62
|
networkDiscoveryKey: () => networkDiscoveryKey,
|
|
63
|
+
openKeyV2: () => openKeyV2,
|
|
58
64
|
openWrappedKey: () => openWrappedKey,
|
|
59
65
|
parseEncryptionResource: () => parseEncryptionResource,
|
|
60
66
|
parseNetworkId: () => parseNetworkId,
|
|
67
|
+
readKeyWrapV2: () => readKeyWrapV2,
|
|
68
|
+
sealKeyV2: () => sealKeyV2,
|
|
61
69
|
utf8Decode: () => utf8Decode,
|
|
62
70
|
utf8Encode: () => utf8Encode,
|
|
63
71
|
validateEnvelope: () => validateEnvelope,
|
|
@@ -77,6 +85,10 @@ var ErrorCodes = {
|
|
|
77
85
|
/** HTTP 401: a delegation the cited one rests on was revoked. */
|
|
78
86
|
AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
|
|
79
87
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
88
|
+
NODE_NOT_RESPONDING: "NODE_NOT_RESPONDING",
|
|
89
|
+
NODE_IDENTITY_MISMATCH: "NODE_IDENTITY_MISMATCH",
|
|
90
|
+
PARTIAL_NODE_WRITE: "PARTIAL_NODE_WRITE",
|
|
91
|
+
RESOURCE_NOT_ROUTED: "RESOURCE_NOT_ROUTED",
|
|
80
92
|
TIMEOUT: "TIMEOUT",
|
|
81
93
|
ABORTED: "ABORTED",
|
|
82
94
|
INVALID_INPUT: "INVALID_INPUT",
|
|
@@ -273,6 +285,34 @@ function networkDiscoveryKey(name) {
|
|
|
273
285
|
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
274
286
|
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
275
287
|
|
|
288
|
+
// src/resourceRouting.ts
|
|
289
|
+
var ROUTING_FAILURE_CODES = {
|
|
290
|
+
NODE_NOT_RESPONDING: true,
|
|
291
|
+
PARTIAL_NODE_WRITE: true,
|
|
292
|
+
NODE_IDENTITY_MISMATCH: true,
|
|
293
|
+
RESOURCE_NOT_ROUTED: true,
|
|
294
|
+
DECRYPTION_DEPENDENCY_UNAVAILABLE: true,
|
|
295
|
+
registry_unavailable: true,
|
|
296
|
+
node_not_found: true,
|
|
297
|
+
node_registry_entry_exists: true
|
|
298
|
+
};
|
|
299
|
+
function isRoutingFailureCode(code) {
|
|
300
|
+
return Object.prototype.hasOwnProperty.call(ROUTING_FAILURE_CODES, code);
|
|
301
|
+
}
|
|
302
|
+
function routingFailure(error) {
|
|
303
|
+
if (typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && typeof error.message === "string" && isRoutingFailureCode(error.code)) {
|
|
304
|
+
const candidate = "meta" in error ? error.meta : void 0;
|
|
305
|
+
let meta = typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : void 0;
|
|
306
|
+
if (error.code === "NODE_NOT_RESPONDING" && "hosts" in error && Array.isArray(error.hosts)) {
|
|
307
|
+
meta = { ...meta, hosts: error.hosts };
|
|
308
|
+
} else if (error.code === "PARTIAL_NODE_WRITE" && "succeeded" in error && Array.isArray(error.succeeded) && "failed" in error && Array.isArray(error.failed)) {
|
|
309
|
+
meta = { ...meta, succeeded: error.succeeded, failed: error.failed };
|
|
310
|
+
}
|
|
311
|
+
return { code: error.code, message: error.message, ...error instanceof Error ? { cause: error } : {}, ...meta ? { meta } : {} };
|
|
312
|
+
}
|
|
313
|
+
return void 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
276
316
|
// src/errors.ts
|
|
277
317
|
var RequestTimeoutError = class extends Error {
|
|
278
318
|
constructor(timeoutMs) {
|
|
@@ -315,6 +355,8 @@ var capabilityServices = new Set(
|
|
|
315
355
|
import_bootstrap.CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
|
|
316
356
|
);
|
|
317
357
|
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
358
|
+
const routing = routingFailure(error);
|
|
359
|
+
if (routing) return { ...routing, service };
|
|
318
360
|
if (error instanceof Error) {
|
|
319
361
|
if (isRequestTimeoutError(error)) {
|
|
320
362
|
return timeoutError(service, error.timeoutMs, error);
|
|
@@ -726,9 +768,9 @@ function utf8Encode(s) {
|
|
|
726
768
|
function utf8Decode(b) {
|
|
727
769
|
return new TextDecoder().decode(b);
|
|
728
770
|
}
|
|
729
|
-
function canonicalHashHex(
|
|
771
|
+
function canonicalHashHex(sha2562, value) {
|
|
730
772
|
const canonical = canonicalize(value);
|
|
731
|
-
return hexEncode(
|
|
773
|
+
return hexEncode(sha2562(utf8Encode(canonical)));
|
|
732
774
|
}
|
|
733
775
|
|
|
734
776
|
// src/encryption/types.ts
|
|
@@ -762,6 +804,8 @@ function defaultEncryptionMessage(input) {
|
|
|
762
804
|
return input.message ?? input.cause.message;
|
|
763
805
|
case "INVALID_INPUT":
|
|
764
806
|
return input.message;
|
|
807
|
+
default:
|
|
808
|
+
return input.message ?? input.code;
|
|
765
809
|
}
|
|
766
810
|
}
|
|
767
811
|
function encryptionError(input) {
|
|
@@ -826,6 +870,8 @@ async function discoverNetwork(input) {
|
|
|
826
870
|
return { ok: true, data: { descriptor: validated.data, source: "node" } };
|
|
827
871
|
}
|
|
828
872
|
} catch (err2) {
|
|
873
|
+
const routing = routingFailure(err2);
|
|
874
|
+
if (routing) return { ok: false, error: encryptionError(routing) };
|
|
829
875
|
}
|
|
830
876
|
}
|
|
831
877
|
if (input.wellKnown !== void 0) {
|
|
@@ -843,6 +889,8 @@ async function discoverNetwork(input) {
|
|
|
843
889
|
};
|
|
844
890
|
}
|
|
845
891
|
} catch (err2) {
|
|
892
|
+
const routing = routingFailure(err2);
|
|
893
|
+
if (routing) return { ok: false, error: encryptionError(routing) };
|
|
846
894
|
}
|
|
847
895
|
}
|
|
848
896
|
return {
|
|
@@ -932,6 +980,160 @@ function ensureNetworkUsableForDecrypt(descriptor) {
|
|
|
932
980
|
};
|
|
933
981
|
}
|
|
934
982
|
|
|
983
|
+
// src/encryption/keyWrap.ts
|
|
984
|
+
var import_aes = require("@noble/ciphers/aes");
|
|
985
|
+
var import_ed25519 = require("@noble/curves/ed25519");
|
|
986
|
+
var import_hkdf = require("@noble/hashes/hkdf");
|
|
987
|
+
var import_sha256 = require("@noble/hashes/sha256");
|
|
988
|
+
var import_utils = require("@noble/hashes/utils");
|
|
989
|
+
var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
|
|
990
|
+
var MAGIC = utf8Encode("TCW2");
|
|
991
|
+
var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
|
|
992
|
+
var MAX_HEADER = 65536;
|
|
993
|
+
function concat(...parts) {
|
|
994
|
+
const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
|
|
995
|
+
let offset = 0;
|
|
996
|
+
for (const part of parts) {
|
|
997
|
+
result.set(part, offset);
|
|
998
|
+
offset += part.length;
|
|
999
|
+
}
|
|
1000
|
+
return result;
|
|
1001
|
+
}
|
|
1002
|
+
function equal(a, b) {
|
|
1003
|
+
if (a.length !== b.length) return false;
|
|
1004
|
+
let difference = 0;
|
|
1005
|
+
for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
|
|
1006
|
+
return difference === 0;
|
|
1007
|
+
}
|
|
1008
|
+
function decode32(value) {
|
|
1009
|
+
if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
|
|
1010
|
+
const decoded = base64Decode(value);
|
|
1011
|
+
if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
|
|
1012
|
+
return decoded;
|
|
1013
|
+
}
|
|
1014
|
+
function isWellFormed(value) {
|
|
1015
|
+
for (let i = 0; i < value.length; i++) {
|
|
1016
|
+
const unit = value.charCodeAt(i);
|
|
1017
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
1018
|
+
const next = value.charCodeAt(++i);
|
|
1019
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
1020
|
+
} else if (unit >= 56320 && unit <= 57343) return false;
|
|
1021
|
+
}
|
|
1022
|
+
return true;
|
|
1023
|
+
}
|
|
1024
|
+
function validateContext(context) {
|
|
1025
|
+
if (context.purpose !== "network-key" && context.purpose !== "receiver-key" || typeof context.networkId !== "string" || typeof context.keyPath !== "string" || !isWellFormed(context.networkId) || !isWellFormed(context.keyPath) || !Number.isSafeInteger(context.keyVersion) || context.keyVersion <= 0 || !context.metadata || typeof context.metadata !== "object" || Array.isArray(context.metadata) || Object.entries(context.metadata).some(([key, value]) => !isWellFormed(key) || typeof value !== "string" || !isWellFormed(value))) {
|
|
1026
|
+
throw new Error("Invalid key wrap context");
|
|
1027
|
+
}
|
|
1028
|
+
if (context.networkId !== "") {
|
|
1029
|
+
const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
|
|
1030
|
+
if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
|
|
1031
|
+
throw new Error("Invalid key wrap resource");
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function isKeyWrapV2(wrapped) {
|
|
1036
|
+
return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
|
|
1037
|
+
}
|
|
1038
|
+
function readKeyWrapV2(wrapped) {
|
|
1039
|
+
if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
|
|
1040
|
+
const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
|
|
1041
|
+
if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
|
|
1042
|
+
const bytes = wrapped.subarray(8, 8 + length);
|
|
1043
|
+
const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
1044
|
+
const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
|
|
1045
|
+
if (!header || typeof header !== "object" || Array.isArray(header) || Object.keys(header).sort().join(",") !== fields.join(",") || header.v !== 2 || header.alg !== KEY_WRAP_V2_ALG || header.kdf !== "hkdf-sha256" || header.aead !== "aes-256-gcm") {
|
|
1046
|
+
throw new Error("Invalid v2 key wrap header");
|
|
1047
|
+
}
|
|
1048
|
+
validateContext(header);
|
|
1049
|
+
decode32(header.salt);
|
|
1050
|
+
decode32(header.ephemeralPublicKey);
|
|
1051
|
+
decode32(header.recipientPublicKey);
|
|
1052
|
+
if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
|
|
1053
|
+
const end = 8 + length;
|
|
1054
|
+
return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
|
|
1055
|
+
}
|
|
1056
|
+
function assertKeyWrapContext(header, expected) {
|
|
1057
|
+
validateContext(expected);
|
|
1058
|
+
for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
|
|
1059
|
+
if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
|
|
1060
|
+
}
|
|
1061
|
+
if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
|
|
1062
|
+
}
|
|
1063
|
+
function deriveKey(secret, ephemeral, recipient, salt) {
|
|
1064
|
+
const shared = import_ed25519.x25519.getSharedSecret(secret, ephemeral);
|
|
1065
|
+
if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
|
|
1066
|
+
try {
|
|
1067
|
+
return (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
|
|
1068
|
+
} finally {
|
|
1069
|
+
shared.fill(0);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function sealKeyV2(recipientPublicKey, plaintext, context) {
|
|
1073
|
+
validateContext(context);
|
|
1074
|
+
if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
|
|
1075
|
+
const secret = (0, import_utils.randomBytes)(32);
|
|
1076
|
+
const ephemeral = import_ed25519.x25519.getPublicKey(secret);
|
|
1077
|
+
const salt = (0, import_utils.randomBytes)(32);
|
|
1078
|
+
const header = {
|
|
1079
|
+
...context,
|
|
1080
|
+
v: 2,
|
|
1081
|
+
alg: KEY_WRAP_V2_ALG,
|
|
1082
|
+
kdf: "hkdf-sha256",
|
|
1083
|
+
aead: "aes-256-gcm",
|
|
1084
|
+
salt: base64Encode(salt),
|
|
1085
|
+
ephemeralPublicKey: base64Encode(ephemeral),
|
|
1086
|
+
recipientPublicKey: base64Encode(recipientPublicKey)
|
|
1087
|
+
};
|
|
1088
|
+
const bytes = utf8Encode(canonicalize({ ...header }));
|
|
1089
|
+
if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
|
|
1090
|
+
const prefix = new Uint8Array(8);
|
|
1091
|
+
prefix.set(MAGIC);
|
|
1092
|
+
new DataView(prefix.buffer).setUint32(4, bytes.length);
|
|
1093
|
+
const aad = concat(prefix, bytes);
|
|
1094
|
+
const nonce = (0, import_utils.randomBytes)(12);
|
|
1095
|
+
let key;
|
|
1096
|
+
try {
|
|
1097
|
+
const shared = import_ed25519.x25519.getSharedSecret(secret, recipientPublicKey);
|
|
1098
|
+
try {
|
|
1099
|
+
if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
|
|
1100
|
+
key = (0, import_hkdf.hkdf)(import_sha256.sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
|
|
1101
|
+
} finally {
|
|
1102
|
+
shared.fill(0);
|
|
1103
|
+
}
|
|
1104
|
+
return concat(aad, nonce, (0, import_aes.gcm)(key, nonce, aad).encrypt(plaintext));
|
|
1105
|
+
} finally {
|
|
1106
|
+
secret.fill(0);
|
|
1107
|
+
key?.fill(0);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function openKeyV2(recipientPrivateKey, wrapped, expected) {
|
|
1111
|
+
const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
|
|
1112
|
+
assertKeyWrapContext(header, expected);
|
|
1113
|
+
const recipient = import_ed25519.x25519.getPublicKey(recipientPrivateKey);
|
|
1114
|
+
if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
|
|
1115
|
+
const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
|
|
1116
|
+
try {
|
|
1117
|
+
return (0, import_aes.gcm)(key, nonce, aad).decrypt(ciphertext);
|
|
1118
|
+
} finally {
|
|
1119
|
+
key.fill(0);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
|
|
1123
|
+
if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
|
|
1124
|
+
if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
|
|
1125
|
+
if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
|
|
1126
|
+
if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
|
|
1127
|
+
}
|
|
1128
|
+
function decryptColumn(decrypt, key, blob, expectedFormat, strictEncryption = false) {
|
|
1129
|
+
if (expectedFormat === "legacy-plaintext") {
|
|
1130
|
+
if (strictEncryption) throw new Error("Legacy plaintext is disabled");
|
|
1131
|
+
return blob;
|
|
1132
|
+
}
|
|
1133
|
+
if (expectedFormat !== "encrypted" || blob.length < 1 + 12 + 16 || blob[0] !== 1) throw new Error("Invalid encrypted column frame");
|
|
1134
|
+
return decrypt(key, blob.subarray(1));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
935
1137
|
// src/encryption/envelope.ts
|
|
936
1138
|
function encryptToNetwork(crypto, input) {
|
|
937
1139
|
const parsedResource = parseEncryptionResource(
|
|
@@ -940,11 +1142,20 @@ function encryptToNetwork(crypto, input) {
|
|
|
940
1142
|
if (parsedResource.keyPath !== input.keyPath || input.keyPath?.endsWith("/")) {
|
|
941
1143
|
throw new NetworkIdError("keyPath must name an exact encryption key");
|
|
942
1144
|
}
|
|
943
|
-
const alg = input.
|
|
1145
|
+
const alg = input.keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG;
|
|
1146
|
+
if (input.alg !== void 0 && input.alg !== alg) {
|
|
1147
|
+
throw new Error("Algorithm does not match keyWrapV2");
|
|
1148
|
+
}
|
|
944
1149
|
const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
|
|
945
1150
|
const symmetricKey = crypto.randomBytes(32);
|
|
946
1151
|
const ciphertext = crypto.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
947
|
-
const wrapped =
|
|
1152
|
+
const wrapped = input.keyWrapV2 ? sealKeyV2(input.networkPublicKey, symmetricKey, {
|
|
1153
|
+
purpose: "network-key",
|
|
1154
|
+
networkId: input.networkId,
|
|
1155
|
+
keyPath: input.keyPath ?? "",
|
|
1156
|
+
keyVersion,
|
|
1157
|
+
metadata: input.metadata ?? {}
|
|
1158
|
+
}) : crypto.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
948
1159
|
const encryptedSymmetricKey = base64Encode(wrapped);
|
|
949
1160
|
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
950
1161
|
crypto.sha256,
|
|
@@ -964,7 +1175,7 @@ function encryptToNetwork(crypto, input) {
|
|
|
964
1175
|
};
|
|
965
1176
|
return { envelope, symmetricKey };
|
|
966
1177
|
}
|
|
967
|
-
function validateEnvelope(crypto, envelope) {
|
|
1178
|
+
function validateEnvelope(crypto, envelope, requireKeyWrapV2 = false) {
|
|
968
1179
|
if (envelope === null || typeof envelope !== "object") {
|
|
969
1180
|
return {
|
|
970
1181
|
ok: false,
|
|
@@ -1041,9 +1252,28 @@ function validateEnvelope(crypto, envelope) {
|
|
|
1041
1252
|
})
|
|
1042
1253
|
};
|
|
1043
1254
|
}
|
|
1255
|
+
try {
|
|
1256
|
+
validateEnvelopeKeyWrap(e, requireKeyWrapV2);
|
|
1257
|
+
} catch {
|
|
1258
|
+
return { ok: false, error: encryptionError({ code: "INVALID_ENVELOPE", message: "Envelope key wrap context is invalid" }) };
|
|
1259
|
+
}
|
|
1044
1260
|
return { ok: true, data: e };
|
|
1045
1261
|
}
|
|
1262
|
+
function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
|
|
1263
|
+
const wrapped = base64Decode(envelope.encryptedSymmetricKey);
|
|
1264
|
+
assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
|
|
1265
|
+
if (envelope.alg === KEY_WRAP_V2_ALG) {
|
|
1266
|
+
assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
|
|
1267
|
+
purpose: "network-key",
|
|
1268
|
+
networkId: envelope.networkId,
|
|
1269
|
+
keyPath: envelope.keyPath ?? "",
|
|
1270
|
+
keyVersion: envelope.keyVersion,
|
|
1271
|
+
metadata: envelope.metadata ?? {}
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1046
1275
|
function decryptEnvelopeWithKey(crypto, envelope, symmetricKey) {
|
|
1276
|
+
validateEnvelopeKeyWrap(envelope);
|
|
1047
1277
|
const ciphertext = base64Decode(envelope.ciphertext);
|
|
1048
1278
|
const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
|
|
1049
1279
|
return crypto.authDecrypt(symmetricKey, ciphertext, aad);
|
|
@@ -1333,7 +1563,7 @@ function verifyDecryptResponse(input) {
|
|
|
1333
1563
|
})
|
|
1334
1564
|
};
|
|
1335
1565
|
}
|
|
1336
|
-
if (response.alg !==
|
|
1566
|
+
if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
|
|
1337
1567
|
return {
|
|
1338
1568
|
ok: false,
|
|
1339
1569
|
error: encryptionError({
|
|
@@ -1390,7 +1620,7 @@ function verifyDecryptResponse(input) {
|
|
|
1390
1620
|
})
|
|
1391
1621
|
};
|
|
1392
1622
|
}
|
|
1393
|
-
if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !==
|
|
1623
|
+
if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== request.alg || facts.keyVersion !== response.keyVersion) {
|
|
1394
1624
|
return {
|
|
1395
1625
|
ok: false,
|
|
1396
1626
|
error: encryptionError({
|
|
@@ -1413,8 +1643,13 @@ function verifyDecryptResponse(input) {
|
|
|
1413
1643
|
}
|
|
1414
1644
|
return { ok: true, data: response };
|
|
1415
1645
|
}
|
|
1416
|
-
function openWrappedKey(crypto, receiverPrivateKey, response) {
|
|
1646
|
+
function openWrappedKey(crypto, receiverPrivateKey, response, expected) {
|
|
1417
1647
|
const wrapped = base64Decode(response.wrappedKey);
|
|
1648
|
+
assertKeyWrapAlgorithm(wrapped, response.alg);
|
|
1649
|
+
if (response.alg === KEY_WRAP_V2_ALG) {
|
|
1650
|
+
if (!expected) throw new Error("V2 response requires expected key context");
|
|
1651
|
+
return openKeyV2(receiverPrivateKey, wrapped, expected);
|
|
1652
|
+
}
|
|
1418
1653
|
return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
1419
1654
|
}
|
|
1420
1655
|
|
|
@@ -1427,6 +1662,14 @@ function encOk(data) {
|
|
|
1427
1662
|
function encErr(error) {
|
|
1428
1663
|
return { ok: false, error };
|
|
1429
1664
|
}
|
|
1665
|
+
function decryptionError(input) {
|
|
1666
|
+
const unavailable = input.code === "NODE_NOT_RESPONDING" || input.code === "TRANSPORT_ERROR" && input.cause.name !== "AbortError";
|
|
1667
|
+
return encryptionError(unavailable ? {
|
|
1668
|
+
...input,
|
|
1669
|
+
code: "DECRYPTION_DEPENDENCY_UNAVAILABLE",
|
|
1670
|
+
message: "The decryption service isn't responding right now. Please try again."
|
|
1671
|
+
} : input);
|
|
1672
|
+
}
|
|
1430
1673
|
var EncryptionService = class extends BaseService {
|
|
1431
1674
|
constructor(config) {
|
|
1432
1675
|
super();
|
|
@@ -1468,12 +1711,15 @@ var EncryptionService = class extends BaseService {
|
|
|
1468
1711
|
networkPublicKey,
|
|
1469
1712
|
plaintext,
|
|
1470
1713
|
...options?.aad !== void 0 ? { aad: options.aad } : {},
|
|
1471
|
-
|
|
1714
|
+
keyWrapV2: options?.keyWrapV2 ?? this._config.keyWrapV2 ?? false,
|
|
1715
|
+
...options?.alg !== void 0 ? { alg: options.alg } : {},
|
|
1472
1716
|
keyVersion: options?.keyVersion ?? descriptor.keyVersion,
|
|
1473
1717
|
...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
|
|
1474
1718
|
});
|
|
1475
1719
|
return encOk(result.envelope);
|
|
1476
1720
|
} catch (error) {
|
|
1721
|
+
const routing = routingFailure(error);
|
|
1722
|
+
if (routing) return encErr(encryptionError(routing));
|
|
1477
1723
|
return encErr(
|
|
1478
1724
|
encryptionError({
|
|
1479
1725
|
code: "TRANSPORT_ERROR",
|
|
@@ -1485,7 +1731,7 @@ var EncryptionService = class extends BaseService {
|
|
|
1485
1731
|
async decryptEnvelope(envelope, capabilityProof, options) {
|
|
1486
1732
|
try {
|
|
1487
1733
|
this.assertActive();
|
|
1488
|
-
const validated = validateEnvelope(this.crypto, envelope);
|
|
1734
|
+
const validated = validateEnvelope(this.crypto, envelope, this._config.requireKeyWrapV2 ?? false);
|
|
1489
1735
|
if (!validated.ok) return validated;
|
|
1490
1736
|
if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
|
|
1491
1737
|
return encErr(
|
|
@@ -1500,12 +1746,12 @@ var EncryptionService = class extends BaseService {
|
|
|
1500
1746
|
descriptor = options.descriptor;
|
|
1501
1747
|
} else {
|
|
1502
1748
|
const discovered = await this.discoverNetwork(envelope.networkId);
|
|
1503
|
-
if (!discovered.ok) return discovered;
|
|
1749
|
+
if (!discovered.ok) return encErr(decryptionError(discovered.error));
|
|
1504
1750
|
descriptor = discovered.data;
|
|
1505
1751
|
}
|
|
1506
1752
|
const usable = ensureNetworkUsableForDecrypt(descriptor);
|
|
1507
1753
|
if (!usable.ok) return usable;
|
|
1508
|
-
const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
1754
|
+
const targetNode = this._config.resolveTargetNode ? await this._config.resolveTargetNode(envelope.networkId) : options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
1509
1755
|
if (targetNode === void 0) {
|
|
1510
1756
|
return encErr(
|
|
1511
1757
|
encryptionError({
|
|
@@ -1514,6 +1760,9 @@ var EncryptionService = class extends BaseService {
|
|
|
1514
1760
|
})
|
|
1515
1761
|
);
|
|
1516
1762
|
}
|
|
1763
|
+
if (this._config.resolveTargetNode && (options?.targetNode !== void 0 && options.targetNode !== targetNode || !descriptor.members.some((member) => member.nodeId === targetNode))) {
|
|
1764
|
+
return encErr(encryptionError({ code: "INVALID_INPUT", message: "Encryption destination does not match the registry" }));
|
|
1765
|
+
}
|
|
1517
1766
|
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
1518
1767
|
const receiverPublicKey = base64Encode(receiverKey.publicKey);
|
|
1519
1768
|
const receiverPublicKeyHash = canonicalHashHex(
|
|
@@ -1572,6 +1821,8 @@ var EncryptionService = class extends BaseService {
|
|
|
1572
1821
|
});
|
|
1573
1822
|
this.assertActive();
|
|
1574
1823
|
} catch (error) {
|
|
1824
|
+
const routing = routingFailure(error);
|
|
1825
|
+
if (routing) return encErr(decryptionError(routing));
|
|
1575
1826
|
if (error instanceof import_decrypt_transport_response_error.DecryptTransportResponseError) {
|
|
1576
1827
|
return encErr(
|
|
1577
1828
|
encryptionError(
|
|
@@ -1588,7 +1839,7 @@ var EncryptionService = class extends BaseService {
|
|
|
1588
1839
|
);
|
|
1589
1840
|
}
|
|
1590
1841
|
return encErr(
|
|
1591
|
-
|
|
1842
|
+
decryptionError({
|
|
1592
1843
|
code: "TRANSPORT_ERROR",
|
|
1593
1844
|
cause: toError(error)
|
|
1594
1845
|
})
|
|
@@ -1606,7 +1857,8 @@ var EncryptionService = class extends BaseService {
|
|
|
1606
1857
|
const symmetricKey = openWrappedKey(
|
|
1607
1858
|
this.crypto,
|
|
1608
1859
|
receiverKey.privateKey,
|
|
1609
|
-
verified.data
|
|
1860
|
+
verified.data,
|
|
1861
|
+
{ purpose: "receiver-key", networkId: body.networkId, keyPath: body.keyPath ?? "", keyVersion: body.keyVersion, metadata: {} }
|
|
1610
1862
|
);
|
|
1611
1863
|
const plaintext = decryptEnvelopeWithKey(
|
|
1612
1864
|
this.crypto,
|
|
@@ -1615,6 +1867,8 @@ var EncryptionService = class extends BaseService {
|
|
|
1615
1867
|
);
|
|
1616
1868
|
return encOk(plaintext);
|
|
1617
1869
|
} catch (error) {
|
|
1870
|
+
const routing = routingFailure(error);
|
|
1871
|
+
if (routing) return encErr(decryptionError(routing));
|
|
1618
1872
|
return encErr(
|
|
1619
1873
|
encryptionError({
|
|
1620
1874
|
code: "INVALID_RESPONSE",
|
|
@@ -1644,8 +1898,11 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
1644
1898
|
ENCRYPTION_SERVICE_SHORT,
|
|
1645
1899
|
ENVELOPE_VERSION,
|
|
1646
1900
|
EncryptionService,
|
|
1901
|
+
KEY_WRAP_V2_ALG,
|
|
1647
1902
|
NETWORK_NAME_PATTERN,
|
|
1648
1903
|
NetworkIdError,
|
|
1904
|
+
assertKeyWrapAlgorithm,
|
|
1905
|
+
assertKeyWrapContext,
|
|
1649
1906
|
base64Decode,
|
|
1650
1907
|
base64Encode,
|
|
1651
1908
|
buildCanonicalDecryptRequest,
|
|
@@ -1657,6 +1914,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
1657
1914
|
canonicalSignedResponse,
|
|
1658
1915
|
canonicalize,
|
|
1659
1916
|
checkDecryptInvocationInput,
|
|
1917
|
+
decryptColumn,
|
|
1660
1918
|
decryptEnvelopeWithKey,
|
|
1661
1919
|
deriveSignedReceiverKey,
|
|
1662
1920
|
discoverNetwork,
|
|
@@ -1666,11 +1924,15 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
1666
1924
|
generateRandomReceiverKey,
|
|
1667
1925
|
hexDecode,
|
|
1668
1926
|
hexEncode,
|
|
1927
|
+
isKeyWrapV2,
|
|
1669
1928
|
isNetworkId,
|
|
1670
1929
|
networkDiscoveryKey,
|
|
1930
|
+
openKeyV2,
|
|
1671
1931
|
openWrappedKey,
|
|
1672
1932
|
parseEncryptionResource,
|
|
1673
1933
|
parseNetworkId,
|
|
1934
|
+
readKeyWrapV2,
|
|
1935
|
+
sealKeyV2,
|
|
1674
1936
|
utf8Decode,
|
|
1675
1937
|
utf8Encode,
|
|
1676
1938
|
validateEnvelope,
|