@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
package/dist/encryption/index.js
CHANGED
|
@@ -10,6 +10,10 @@ var ErrorCodes = {
|
|
|
10
10
|
/** HTTP 401: a delegation the cited one rests on was revoked. */
|
|
11
11
|
AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
|
|
12
12
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
13
|
+
NODE_NOT_RESPONDING: "NODE_NOT_RESPONDING",
|
|
14
|
+
NODE_IDENTITY_MISMATCH: "NODE_IDENTITY_MISMATCH",
|
|
15
|
+
PARTIAL_NODE_WRITE: "PARTIAL_NODE_WRITE",
|
|
16
|
+
RESOURCE_NOT_ROUTED: "RESOURCE_NOT_ROUTED",
|
|
13
17
|
TIMEOUT: "TIMEOUT",
|
|
14
18
|
ABORTED: "ABORTED",
|
|
15
19
|
INVALID_INPUT: "INVALID_INPUT",
|
|
@@ -206,6 +210,34 @@ function networkDiscoveryKey(name) {
|
|
|
206
210
|
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
207
211
|
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
208
212
|
|
|
213
|
+
// src/resourceRouting.ts
|
|
214
|
+
var ROUTING_FAILURE_CODES = {
|
|
215
|
+
NODE_NOT_RESPONDING: true,
|
|
216
|
+
PARTIAL_NODE_WRITE: true,
|
|
217
|
+
NODE_IDENTITY_MISMATCH: true,
|
|
218
|
+
RESOURCE_NOT_ROUTED: true,
|
|
219
|
+
DECRYPTION_DEPENDENCY_UNAVAILABLE: true,
|
|
220
|
+
registry_unavailable: true,
|
|
221
|
+
node_not_found: true,
|
|
222
|
+
node_registry_entry_exists: true
|
|
223
|
+
};
|
|
224
|
+
function isRoutingFailureCode(code) {
|
|
225
|
+
return Object.prototype.hasOwnProperty.call(ROUTING_FAILURE_CODES, code);
|
|
226
|
+
}
|
|
227
|
+
function routingFailure(error) {
|
|
228
|
+
if (typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && typeof error.message === "string" && isRoutingFailureCode(error.code)) {
|
|
229
|
+
const candidate = "meta" in error ? error.meta : void 0;
|
|
230
|
+
let meta = typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : void 0;
|
|
231
|
+
if (error.code === "NODE_NOT_RESPONDING" && "hosts" in error && Array.isArray(error.hosts)) {
|
|
232
|
+
meta = { ...meta, hosts: error.hosts };
|
|
233
|
+
} else if (error.code === "PARTIAL_NODE_WRITE" && "succeeded" in error && Array.isArray(error.succeeded) && "failed" in error && Array.isArray(error.failed)) {
|
|
234
|
+
meta = { ...meta, succeeded: error.succeeded, failed: error.failed };
|
|
235
|
+
}
|
|
236
|
+
return { code: error.code, message: error.message, ...error instanceof Error ? { cause: error } : {}, ...meta ? { meta } : {} };
|
|
237
|
+
}
|
|
238
|
+
return void 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
209
241
|
// src/errors.ts
|
|
210
242
|
var RequestTimeoutError = class extends Error {
|
|
211
243
|
constructor(timeoutMs) {
|
|
@@ -248,6 +280,8 @@ var capabilityServices = new Set(
|
|
|
248
280
|
CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
|
|
249
281
|
);
|
|
250
282
|
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
283
|
+
const routing = routingFailure(error);
|
|
284
|
+
if (routing) return { ...routing, service };
|
|
251
285
|
if (error instanceof Error) {
|
|
252
286
|
if (isRequestTimeoutError(error)) {
|
|
253
287
|
return timeoutError(service, error.timeoutMs, error);
|
|
@@ -659,9 +693,9 @@ function utf8Encode(s) {
|
|
|
659
693
|
function utf8Decode(b) {
|
|
660
694
|
return new TextDecoder().decode(b);
|
|
661
695
|
}
|
|
662
|
-
function canonicalHashHex(
|
|
696
|
+
function canonicalHashHex(sha2562, value) {
|
|
663
697
|
const canonical = canonicalize(value);
|
|
664
|
-
return hexEncode(
|
|
698
|
+
return hexEncode(sha2562(utf8Encode(canonical)));
|
|
665
699
|
}
|
|
666
700
|
|
|
667
701
|
// src/encryption/types.ts
|
|
@@ -695,6 +729,8 @@ function defaultEncryptionMessage(input) {
|
|
|
695
729
|
return input.message ?? input.cause.message;
|
|
696
730
|
case "INVALID_INPUT":
|
|
697
731
|
return input.message;
|
|
732
|
+
default:
|
|
733
|
+
return input.message ?? input.code;
|
|
698
734
|
}
|
|
699
735
|
}
|
|
700
736
|
function encryptionError(input) {
|
|
@@ -759,6 +795,8 @@ async function discoverNetwork(input) {
|
|
|
759
795
|
return { ok: true, data: { descriptor: validated.data, source: "node" } };
|
|
760
796
|
}
|
|
761
797
|
} catch (err2) {
|
|
798
|
+
const routing = routingFailure(err2);
|
|
799
|
+
if (routing) return { ok: false, error: encryptionError(routing) };
|
|
762
800
|
}
|
|
763
801
|
}
|
|
764
802
|
if (input.wellKnown !== void 0) {
|
|
@@ -776,6 +814,8 @@ async function discoverNetwork(input) {
|
|
|
776
814
|
};
|
|
777
815
|
}
|
|
778
816
|
} catch (err2) {
|
|
817
|
+
const routing = routingFailure(err2);
|
|
818
|
+
if (routing) return { ok: false, error: encryptionError(routing) };
|
|
779
819
|
}
|
|
780
820
|
}
|
|
781
821
|
return {
|
|
@@ -865,6 +905,160 @@ function ensureNetworkUsableForDecrypt(descriptor) {
|
|
|
865
905
|
};
|
|
866
906
|
}
|
|
867
907
|
|
|
908
|
+
// src/encryption/keyWrap.ts
|
|
909
|
+
import { gcm } from "@noble/ciphers/aes";
|
|
910
|
+
import { x25519 } from "@noble/curves/ed25519";
|
|
911
|
+
import { hkdf } from "@noble/hashes/hkdf";
|
|
912
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
913
|
+
import { randomBytes } from "@noble/hashes/utils";
|
|
914
|
+
var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
|
|
915
|
+
var MAGIC = utf8Encode("TCW2");
|
|
916
|
+
var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
|
|
917
|
+
var MAX_HEADER = 65536;
|
|
918
|
+
function concat(...parts) {
|
|
919
|
+
const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
|
|
920
|
+
let offset = 0;
|
|
921
|
+
for (const part of parts) {
|
|
922
|
+
result.set(part, offset);
|
|
923
|
+
offset += part.length;
|
|
924
|
+
}
|
|
925
|
+
return result;
|
|
926
|
+
}
|
|
927
|
+
function equal(a, b) {
|
|
928
|
+
if (a.length !== b.length) return false;
|
|
929
|
+
let difference = 0;
|
|
930
|
+
for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
|
|
931
|
+
return difference === 0;
|
|
932
|
+
}
|
|
933
|
+
function decode32(value) {
|
|
934
|
+
if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
|
|
935
|
+
const decoded = base64Decode(value);
|
|
936
|
+
if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
|
|
937
|
+
return decoded;
|
|
938
|
+
}
|
|
939
|
+
function isWellFormed(value) {
|
|
940
|
+
for (let i = 0; i < value.length; i++) {
|
|
941
|
+
const unit = value.charCodeAt(i);
|
|
942
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
943
|
+
const next = value.charCodeAt(++i);
|
|
944
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
945
|
+
} else if (unit >= 56320 && unit <= 57343) return false;
|
|
946
|
+
}
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
function validateContext(context) {
|
|
950
|
+
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))) {
|
|
951
|
+
throw new Error("Invalid key wrap context");
|
|
952
|
+
}
|
|
953
|
+
if (context.networkId !== "") {
|
|
954
|
+
const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
|
|
955
|
+
if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
|
|
956
|
+
throw new Error("Invalid key wrap resource");
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
function isKeyWrapV2(wrapped) {
|
|
961
|
+
return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
|
|
962
|
+
}
|
|
963
|
+
function readKeyWrapV2(wrapped) {
|
|
964
|
+
if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
|
|
965
|
+
const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
|
|
966
|
+
if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
|
|
967
|
+
const bytes = wrapped.subarray(8, 8 + length);
|
|
968
|
+
const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
969
|
+
const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
|
|
970
|
+
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") {
|
|
971
|
+
throw new Error("Invalid v2 key wrap header");
|
|
972
|
+
}
|
|
973
|
+
validateContext(header);
|
|
974
|
+
decode32(header.salt);
|
|
975
|
+
decode32(header.ephemeralPublicKey);
|
|
976
|
+
decode32(header.recipientPublicKey);
|
|
977
|
+
if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
|
|
978
|
+
const end = 8 + length;
|
|
979
|
+
return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
|
|
980
|
+
}
|
|
981
|
+
function assertKeyWrapContext(header, expected) {
|
|
982
|
+
validateContext(expected);
|
|
983
|
+
for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
|
|
984
|
+
if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
|
|
985
|
+
}
|
|
986
|
+
if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
|
|
987
|
+
}
|
|
988
|
+
function deriveKey(secret, ephemeral, recipient, salt) {
|
|
989
|
+
const shared = x25519.getSharedSecret(secret, ephemeral);
|
|
990
|
+
if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
|
|
991
|
+
try {
|
|
992
|
+
return hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
|
|
993
|
+
} finally {
|
|
994
|
+
shared.fill(0);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function sealKeyV2(recipientPublicKey, plaintext, context) {
|
|
998
|
+
validateContext(context);
|
|
999
|
+
if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
|
|
1000
|
+
const secret = randomBytes(32);
|
|
1001
|
+
const ephemeral = x25519.getPublicKey(secret);
|
|
1002
|
+
const salt = randomBytes(32);
|
|
1003
|
+
const header = {
|
|
1004
|
+
...context,
|
|
1005
|
+
v: 2,
|
|
1006
|
+
alg: KEY_WRAP_V2_ALG,
|
|
1007
|
+
kdf: "hkdf-sha256",
|
|
1008
|
+
aead: "aes-256-gcm",
|
|
1009
|
+
salt: base64Encode(salt),
|
|
1010
|
+
ephemeralPublicKey: base64Encode(ephemeral),
|
|
1011
|
+
recipientPublicKey: base64Encode(recipientPublicKey)
|
|
1012
|
+
};
|
|
1013
|
+
const bytes = utf8Encode(canonicalize({ ...header }));
|
|
1014
|
+
if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
|
|
1015
|
+
const prefix = new Uint8Array(8);
|
|
1016
|
+
prefix.set(MAGIC);
|
|
1017
|
+
new DataView(prefix.buffer).setUint32(4, bytes.length);
|
|
1018
|
+
const aad = concat(prefix, bytes);
|
|
1019
|
+
const nonce = randomBytes(12);
|
|
1020
|
+
let key;
|
|
1021
|
+
try {
|
|
1022
|
+
const shared = x25519.getSharedSecret(secret, recipientPublicKey);
|
|
1023
|
+
try {
|
|
1024
|
+
if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
|
|
1025
|
+
key = hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
|
|
1026
|
+
} finally {
|
|
1027
|
+
shared.fill(0);
|
|
1028
|
+
}
|
|
1029
|
+
return concat(aad, nonce, gcm(key, nonce, aad).encrypt(plaintext));
|
|
1030
|
+
} finally {
|
|
1031
|
+
secret.fill(0);
|
|
1032
|
+
key?.fill(0);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function openKeyV2(recipientPrivateKey, wrapped, expected) {
|
|
1036
|
+
const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
|
|
1037
|
+
assertKeyWrapContext(header, expected);
|
|
1038
|
+
const recipient = x25519.getPublicKey(recipientPrivateKey);
|
|
1039
|
+
if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
|
|
1040
|
+
const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
|
|
1041
|
+
try {
|
|
1042
|
+
return gcm(key, nonce, aad).decrypt(ciphertext);
|
|
1043
|
+
} finally {
|
|
1044
|
+
key.fill(0);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
|
|
1048
|
+
if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
|
|
1049
|
+
if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
|
|
1050
|
+
if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
|
|
1051
|
+
if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
|
|
1052
|
+
}
|
|
1053
|
+
function decryptColumn(decrypt, key, blob, expectedFormat, strictEncryption = false) {
|
|
1054
|
+
if (expectedFormat === "legacy-plaintext") {
|
|
1055
|
+
if (strictEncryption) throw new Error("Legacy plaintext is disabled");
|
|
1056
|
+
return blob;
|
|
1057
|
+
}
|
|
1058
|
+
if (expectedFormat !== "encrypted" || blob.length < 1 + 12 + 16 || blob[0] !== 1) throw new Error("Invalid encrypted column frame");
|
|
1059
|
+
return decrypt(key, blob.subarray(1));
|
|
1060
|
+
}
|
|
1061
|
+
|
|
868
1062
|
// src/encryption/envelope.ts
|
|
869
1063
|
function encryptToNetwork(crypto, input) {
|
|
870
1064
|
const parsedResource = parseEncryptionResource(
|
|
@@ -873,11 +1067,20 @@ function encryptToNetwork(crypto, input) {
|
|
|
873
1067
|
if (parsedResource.keyPath !== input.keyPath || input.keyPath?.endsWith("/")) {
|
|
874
1068
|
throw new NetworkIdError("keyPath must name an exact encryption key");
|
|
875
1069
|
}
|
|
876
|
-
const alg = input.
|
|
1070
|
+
const alg = input.keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG;
|
|
1071
|
+
if (input.alg !== void 0 && input.alg !== alg) {
|
|
1072
|
+
throw new Error("Algorithm does not match keyWrapV2");
|
|
1073
|
+
}
|
|
877
1074
|
const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
|
|
878
1075
|
const symmetricKey = crypto.randomBytes(32);
|
|
879
1076
|
const ciphertext = crypto.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
880
|
-
const wrapped =
|
|
1077
|
+
const wrapped = input.keyWrapV2 ? sealKeyV2(input.networkPublicKey, symmetricKey, {
|
|
1078
|
+
purpose: "network-key",
|
|
1079
|
+
networkId: input.networkId,
|
|
1080
|
+
keyPath: input.keyPath ?? "",
|
|
1081
|
+
keyVersion,
|
|
1082
|
+
metadata: input.metadata ?? {}
|
|
1083
|
+
}) : crypto.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
881
1084
|
const encryptedSymmetricKey = base64Encode(wrapped);
|
|
882
1085
|
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
883
1086
|
crypto.sha256,
|
|
@@ -897,7 +1100,7 @@ function encryptToNetwork(crypto, input) {
|
|
|
897
1100
|
};
|
|
898
1101
|
return { envelope, symmetricKey };
|
|
899
1102
|
}
|
|
900
|
-
function validateEnvelope(crypto, envelope) {
|
|
1103
|
+
function validateEnvelope(crypto, envelope, requireKeyWrapV2 = false) {
|
|
901
1104
|
if (envelope === null || typeof envelope !== "object") {
|
|
902
1105
|
return {
|
|
903
1106
|
ok: false,
|
|
@@ -974,9 +1177,28 @@ function validateEnvelope(crypto, envelope) {
|
|
|
974
1177
|
})
|
|
975
1178
|
};
|
|
976
1179
|
}
|
|
1180
|
+
try {
|
|
1181
|
+
validateEnvelopeKeyWrap(e, requireKeyWrapV2);
|
|
1182
|
+
} catch {
|
|
1183
|
+
return { ok: false, error: encryptionError({ code: "INVALID_ENVELOPE", message: "Envelope key wrap context is invalid" }) };
|
|
1184
|
+
}
|
|
977
1185
|
return { ok: true, data: e };
|
|
978
1186
|
}
|
|
1187
|
+
function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
|
|
1188
|
+
const wrapped = base64Decode(envelope.encryptedSymmetricKey);
|
|
1189
|
+
assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
|
|
1190
|
+
if (envelope.alg === KEY_WRAP_V2_ALG) {
|
|
1191
|
+
assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
|
|
1192
|
+
purpose: "network-key",
|
|
1193
|
+
networkId: envelope.networkId,
|
|
1194
|
+
keyPath: envelope.keyPath ?? "",
|
|
1195
|
+
keyVersion: envelope.keyVersion,
|
|
1196
|
+
metadata: envelope.metadata ?? {}
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
979
1200
|
function decryptEnvelopeWithKey(crypto, envelope, symmetricKey) {
|
|
1201
|
+
validateEnvelopeKeyWrap(envelope);
|
|
980
1202
|
const ciphertext = base64Decode(envelope.ciphertext);
|
|
981
1203
|
const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
|
|
982
1204
|
return crypto.authDecrypt(symmetricKey, ciphertext, aad);
|
|
@@ -1266,7 +1488,7 @@ function verifyDecryptResponse(input) {
|
|
|
1266
1488
|
})
|
|
1267
1489
|
};
|
|
1268
1490
|
}
|
|
1269
|
-
if (response.alg !==
|
|
1491
|
+
if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
|
|
1270
1492
|
return {
|
|
1271
1493
|
ok: false,
|
|
1272
1494
|
error: encryptionError({
|
|
@@ -1323,7 +1545,7 @@ function verifyDecryptResponse(input) {
|
|
|
1323
1545
|
})
|
|
1324
1546
|
};
|
|
1325
1547
|
}
|
|
1326
|
-
if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !==
|
|
1548
|
+
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) {
|
|
1327
1549
|
return {
|
|
1328
1550
|
ok: false,
|
|
1329
1551
|
error: encryptionError({
|
|
@@ -1346,8 +1568,13 @@ function verifyDecryptResponse(input) {
|
|
|
1346
1568
|
}
|
|
1347
1569
|
return { ok: true, data: response };
|
|
1348
1570
|
}
|
|
1349
|
-
function openWrappedKey(crypto, receiverPrivateKey, response) {
|
|
1571
|
+
function openWrappedKey(crypto, receiverPrivateKey, response, expected) {
|
|
1350
1572
|
const wrapped = base64Decode(response.wrappedKey);
|
|
1573
|
+
assertKeyWrapAlgorithm(wrapped, response.alg);
|
|
1574
|
+
if (response.alg === KEY_WRAP_V2_ALG) {
|
|
1575
|
+
if (!expected) throw new Error("V2 response requires expected key context");
|
|
1576
|
+
return openKeyV2(receiverPrivateKey, wrapped, expected);
|
|
1577
|
+
}
|
|
1351
1578
|
return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
1352
1579
|
}
|
|
1353
1580
|
|
|
@@ -1360,6 +1587,14 @@ function encOk(data) {
|
|
|
1360
1587
|
function encErr(error) {
|
|
1361
1588
|
return { ok: false, error };
|
|
1362
1589
|
}
|
|
1590
|
+
function decryptionError(input) {
|
|
1591
|
+
const unavailable = input.code === "NODE_NOT_RESPONDING" || input.code === "TRANSPORT_ERROR" && input.cause.name !== "AbortError";
|
|
1592
|
+
return encryptionError(unavailable ? {
|
|
1593
|
+
...input,
|
|
1594
|
+
code: "DECRYPTION_DEPENDENCY_UNAVAILABLE",
|
|
1595
|
+
message: "The decryption service isn't responding right now. Please try again."
|
|
1596
|
+
} : input);
|
|
1597
|
+
}
|
|
1363
1598
|
var EncryptionService = class extends BaseService {
|
|
1364
1599
|
constructor(config) {
|
|
1365
1600
|
super();
|
|
@@ -1401,12 +1636,15 @@ var EncryptionService = class extends BaseService {
|
|
|
1401
1636
|
networkPublicKey,
|
|
1402
1637
|
plaintext,
|
|
1403
1638
|
...options?.aad !== void 0 ? { aad: options.aad } : {},
|
|
1404
|
-
|
|
1639
|
+
keyWrapV2: options?.keyWrapV2 ?? this._config.keyWrapV2 ?? false,
|
|
1640
|
+
...options?.alg !== void 0 ? { alg: options.alg } : {},
|
|
1405
1641
|
keyVersion: options?.keyVersion ?? descriptor.keyVersion,
|
|
1406
1642
|
...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
|
|
1407
1643
|
});
|
|
1408
1644
|
return encOk(result.envelope);
|
|
1409
1645
|
} catch (error) {
|
|
1646
|
+
const routing = routingFailure(error);
|
|
1647
|
+
if (routing) return encErr(encryptionError(routing));
|
|
1410
1648
|
return encErr(
|
|
1411
1649
|
encryptionError({
|
|
1412
1650
|
code: "TRANSPORT_ERROR",
|
|
@@ -1418,7 +1656,7 @@ var EncryptionService = class extends BaseService {
|
|
|
1418
1656
|
async decryptEnvelope(envelope, capabilityProof, options) {
|
|
1419
1657
|
try {
|
|
1420
1658
|
this.assertActive();
|
|
1421
|
-
const validated = validateEnvelope(this.crypto, envelope);
|
|
1659
|
+
const validated = validateEnvelope(this.crypto, envelope, this._config.requireKeyWrapV2 ?? false);
|
|
1422
1660
|
if (!validated.ok) return validated;
|
|
1423
1661
|
if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
|
|
1424
1662
|
return encErr(
|
|
@@ -1433,12 +1671,12 @@ var EncryptionService = class extends BaseService {
|
|
|
1433
1671
|
descriptor = options.descriptor;
|
|
1434
1672
|
} else {
|
|
1435
1673
|
const discovered = await this.discoverNetwork(envelope.networkId);
|
|
1436
|
-
if (!discovered.ok) return discovered;
|
|
1674
|
+
if (!discovered.ok) return encErr(decryptionError(discovered.error));
|
|
1437
1675
|
descriptor = discovered.data;
|
|
1438
1676
|
}
|
|
1439
1677
|
const usable = ensureNetworkUsableForDecrypt(descriptor);
|
|
1440
1678
|
if (!usable.ok) return usable;
|
|
1441
|
-
const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
1679
|
+
const targetNode = this._config.resolveTargetNode ? await this._config.resolveTargetNode(envelope.networkId) : options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
1442
1680
|
if (targetNode === void 0) {
|
|
1443
1681
|
return encErr(
|
|
1444
1682
|
encryptionError({
|
|
@@ -1447,6 +1685,9 @@ var EncryptionService = class extends BaseService {
|
|
|
1447
1685
|
})
|
|
1448
1686
|
);
|
|
1449
1687
|
}
|
|
1688
|
+
if (this._config.resolveTargetNode && (options?.targetNode !== void 0 && options.targetNode !== targetNode || !descriptor.members.some((member) => member.nodeId === targetNode))) {
|
|
1689
|
+
return encErr(encryptionError({ code: "INVALID_INPUT", message: "Encryption destination does not match the registry" }));
|
|
1690
|
+
}
|
|
1450
1691
|
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
1451
1692
|
const receiverPublicKey = base64Encode(receiverKey.publicKey);
|
|
1452
1693
|
const receiverPublicKeyHash = canonicalHashHex(
|
|
@@ -1505,6 +1746,8 @@ var EncryptionService = class extends BaseService {
|
|
|
1505
1746
|
});
|
|
1506
1747
|
this.assertActive();
|
|
1507
1748
|
} catch (error) {
|
|
1749
|
+
const routing = routingFailure(error);
|
|
1750
|
+
if (routing) return encErr(decryptionError(routing));
|
|
1508
1751
|
if (error instanceof DecryptTransportResponseError) {
|
|
1509
1752
|
return encErr(
|
|
1510
1753
|
encryptionError(
|
|
@@ -1521,7 +1764,7 @@ var EncryptionService = class extends BaseService {
|
|
|
1521
1764
|
);
|
|
1522
1765
|
}
|
|
1523
1766
|
return encErr(
|
|
1524
|
-
|
|
1767
|
+
decryptionError({
|
|
1525
1768
|
code: "TRANSPORT_ERROR",
|
|
1526
1769
|
cause: toError(error)
|
|
1527
1770
|
})
|
|
@@ -1539,7 +1782,8 @@ var EncryptionService = class extends BaseService {
|
|
|
1539
1782
|
const symmetricKey = openWrappedKey(
|
|
1540
1783
|
this.crypto,
|
|
1541
1784
|
receiverKey.privateKey,
|
|
1542
|
-
verified.data
|
|
1785
|
+
verified.data,
|
|
1786
|
+
{ purpose: "receiver-key", networkId: body.networkId, keyPath: body.keyPath ?? "", keyVersion: body.keyVersion, metadata: {} }
|
|
1543
1787
|
);
|
|
1544
1788
|
const plaintext = decryptEnvelopeWithKey(
|
|
1545
1789
|
this.crypto,
|
|
@@ -1548,6 +1792,8 @@ var EncryptionService = class extends BaseService {
|
|
|
1548
1792
|
);
|
|
1549
1793
|
return encOk(plaintext);
|
|
1550
1794
|
} catch (error) {
|
|
1795
|
+
const routing = routingFailure(error);
|
|
1796
|
+
if (routing) return encErr(decryptionError(routing));
|
|
1551
1797
|
return encErr(
|
|
1552
1798
|
encryptionError({
|
|
1553
1799
|
code: "INVALID_RESPONSE",
|
|
@@ -1576,8 +1822,11 @@ export {
|
|
|
1576
1822
|
ENCRYPTION_SERVICE_SHORT,
|
|
1577
1823
|
ENVELOPE_VERSION,
|
|
1578
1824
|
EncryptionService,
|
|
1825
|
+
KEY_WRAP_V2_ALG,
|
|
1579
1826
|
NETWORK_NAME_PATTERN,
|
|
1580
1827
|
NetworkIdError,
|
|
1828
|
+
assertKeyWrapAlgorithm,
|
|
1829
|
+
assertKeyWrapContext,
|
|
1581
1830
|
base64Decode,
|
|
1582
1831
|
base64Encode,
|
|
1583
1832
|
buildCanonicalDecryptRequest,
|
|
@@ -1589,6 +1838,7 @@ export {
|
|
|
1589
1838
|
canonicalSignedResponse,
|
|
1590
1839
|
canonicalize,
|
|
1591
1840
|
checkDecryptInvocationInput,
|
|
1841
|
+
decryptColumn,
|
|
1592
1842
|
decryptEnvelopeWithKey,
|
|
1593
1843
|
deriveSignedReceiverKey,
|
|
1594
1844
|
discoverNetwork,
|
|
@@ -1598,11 +1848,15 @@ export {
|
|
|
1598
1848
|
generateRandomReceiverKey,
|
|
1599
1849
|
hexDecode,
|
|
1600
1850
|
hexEncode,
|
|
1851
|
+
isKeyWrapV2,
|
|
1601
1852
|
isNetworkId,
|
|
1602
1853
|
networkDiscoveryKey,
|
|
1854
|
+
openKeyV2,
|
|
1603
1855
|
openWrappedKey,
|
|
1604
1856
|
parseEncryptionResource,
|
|
1605
1857
|
parseNetworkId,
|
|
1858
|
+
readKeyWrapV2,
|
|
1859
|
+
sealKeyV2,
|
|
1606
1860
|
utf8Decode,
|
|
1607
1861
|
utf8Encode,
|
|
1608
1862
|
validateEnvelope,
|