@tinycloud/sdk-services 2.2.0-beta.7 → 2.2.0
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-BiS6HRwE.d.cts → BaseService-C_iXlTeN.d.cts} +6 -1
- package/dist/{BaseService-BiS6HRwE.d.ts → BaseService-C_iXlTeN.d.ts} +6 -1
- package/dist/encryption/index.cjs +1340 -0
- package/dist/encryption/index.cjs.map +1 -0
- package/dist/encryption/index.d.cts +802 -0
- package/dist/encryption/index.d.ts +802 -0
- package/dist/encryption/index.js +1274 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/index.cjs +1555 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -12
- package/dist/index.d.ts +55 -12
- package/dist/index.js +1510 -46
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs +116 -0
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +100 -2
- package/dist/kv/index.d.ts +100 -2
- package/dist/kv/index.js +115 -0
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +1 -1
- package/dist/sql/index.d.ts +1 -1
- package/dist/sql/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -818,6 +818,13 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
818
818
|
const fullKey = this.getFullKey(key);
|
|
819
819
|
return this._kv.head(fullKey, { ...options, prefix: "" });
|
|
820
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Create a short-lived signed URL for reading a KV object.
|
|
823
|
+
*/
|
|
824
|
+
async createSignedReadUrl(key, options) {
|
|
825
|
+
const fullKey = this.getFullKey(key);
|
|
826
|
+
return this._kv.createSignedReadUrl(fullKey, { ...options, prefix: "" });
|
|
827
|
+
}
|
|
821
828
|
/**
|
|
822
829
|
* Create a nested prefix-scoped view.
|
|
823
830
|
*/
|
|
@@ -829,6 +836,7 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
829
836
|
};
|
|
830
837
|
|
|
831
838
|
// src/kv/types.ts
|
|
839
|
+
var DEFAULT_SIGNED_READ_URL_EXPIRY_MS = 5 * 60 * 1e3;
|
|
832
840
|
var KVAction = {
|
|
833
841
|
GET: "tinycloud.kv/get",
|
|
834
842
|
PUT: "tinycloud.kv/put",
|
|
@@ -913,6 +921,15 @@ var KVService = class extends BaseService {
|
|
|
913
921
|
get host() {
|
|
914
922
|
return this.context.hosts[0];
|
|
915
923
|
}
|
|
924
|
+
withJsonContentType(headers) {
|
|
925
|
+
if (Array.isArray(headers)) {
|
|
926
|
+
return [...headers, ["content-type", "application/json"]];
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
...headers,
|
|
930
|
+
"content-type": "application/json"
|
|
931
|
+
};
|
|
932
|
+
}
|
|
916
933
|
/**
|
|
917
934
|
* Execute an invoke operation.
|
|
918
935
|
*
|
|
@@ -982,6 +999,48 @@ var KVService = class extends BaseService {
|
|
|
982
999
|
return text;
|
|
983
1000
|
}
|
|
984
1001
|
}
|
|
1002
|
+
async createSignedReadUrlError(response, key) {
|
|
1003
|
+
let errorText = response.statusText;
|
|
1004
|
+
try {
|
|
1005
|
+
const text = await response.text();
|
|
1006
|
+
if (text) {
|
|
1007
|
+
errorText = text;
|
|
1008
|
+
}
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
if (response.status === 401 || response.status === 403) {
|
|
1012
|
+
const { resource, action } = parseAuthError(errorText);
|
|
1013
|
+
return err(authUnauthorizedError("kv", errorText, {
|
|
1014
|
+
status: response.status,
|
|
1015
|
+
...action && { requiredAction: action },
|
|
1016
|
+
...resource && { resource }
|
|
1017
|
+
}));
|
|
1018
|
+
}
|
|
1019
|
+
const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
1020
|
+
return err(
|
|
1021
|
+
serviceError(
|
|
1022
|
+
code,
|
|
1023
|
+
`Failed to create signed read URL for key "${key}": ${response.status} - ${errorText}`,
|
|
1024
|
+
"kv",
|
|
1025
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1026
|
+
)
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
normalizeSignedReadUrlResponse(data) {
|
|
1030
|
+
if (!data || typeof data !== "object") {
|
|
1031
|
+
return void 0;
|
|
1032
|
+
}
|
|
1033
|
+
const response = data;
|
|
1034
|
+
if (typeof response.url !== "string" || typeof response.ticketId !== "string" || typeof response.expiresAt !== "string") {
|
|
1035
|
+
return void 0;
|
|
1036
|
+
}
|
|
1037
|
+
return {
|
|
1038
|
+
url: new URL(response.url, this.host).toString(),
|
|
1039
|
+
relativeUrl: response.url,
|
|
1040
|
+
ticketId: response.ticketId,
|
|
1041
|
+
expiresAt: response.expiresAt
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
985
1044
|
/**
|
|
986
1045
|
* Get a value by key.
|
|
987
1046
|
*/
|
|
@@ -1254,6 +1313,61 @@ var KVService = class extends BaseService {
|
|
|
1254
1313
|
}
|
|
1255
1314
|
});
|
|
1256
1315
|
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Create a short-lived signed URL for reading a KV object.
|
|
1318
|
+
*/
|
|
1319
|
+
async createSignedReadUrl(key, options) {
|
|
1320
|
+
return this.withTelemetry("createSignedReadUrl", key, async () => {
|
|
1321
|
+
if (!this.requireAuth()) {
|
|
1322
|
+
return err(authRequiredError("kv"));
|
|
1323
|
+
}
|
|
1324
|
+
const path = this.getFullPath(key, options?.prefix);
|
|
1325
|
+
const session = this.context.session;
|
|
1326
|
+
const headers = this.context.invoke(
|
|
1327
|
+
session,
|
|
1328
|
+
"kv",
|
|
1329
|
+
path,
|
|
1330
|
+
KVAction.GET
|
|
1331
|
+
);
|
|
1332
|
+
const body = {
|
|
1333
|
+
space: session.spaceId,
|
|
1334
|
+
path,
|
|
1335
|
+
ttl_seconds: options?.expiresInSeconds ?? Math.ceil(DEFAULT_SIGNED_READ_URL_EXPIRY_MS / 1e3)
|
|
1336
|
+
};
|
|
1337
|
+
if (options?.contentHash !== void 0) {
|
|
1338
|
+
body.content_hash = options.contentHash;
|
|
1339
|
+
}
|
|
1340
|
+
if (options?.etag !== void 0) {
|
|
1341
|
+
body.etag = options.etag;
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
const response = await this.context.fetch(`${this.host}/signed/kv`, {
|
|
1345
|
+
method: "POST",
|
|
1346
|
+
headers: this.withJsonContentType(headers),
|
|
1347
|
+
body: JSON.stringify(body),
|
|
1348
|
+
signal: this.combineSignals(options?.signal)
|
|
1349
|
+
});
|
|
1350
|
+
if (!response.ok) {
|
|
1351
|
+
return this.createSignedReadUrlError(response, key);
|
|
1352
|
+
}
|
|
1353
|
+
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
1354
|
+
await response.json()
|
|
1355
|
+
);
|
|
1356
|
+
if (!signedUrl) {
|
|
1357
|
+
return err(
|
|
1358
|
+
serviceError(
|
|
1359
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1360
|
+
"Signed read URL response did not include url, ticketId, and expiresAt",
|
|
1361
|
+
"kv"
|
|
1362
|
+
)
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
return ok(signedUrl);
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
return err(wrapError("kv", error));
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1257
1371
|
/**
|
|
1258
1372
|
* Create a prefix-scoped view of this KV service.
|
|
1259
1373
|
*
|
|
@@ -2823,6 +2937,15 @@ function base64Decode(str) {
|
|
|
2823
2937
|
}
|
|
2824
2938
|
return bytes;
|
|
2825
2939
|
}
|
|
2940
|
+
function unwrapKVData(value) {
|
|
2941
|
+
if (value !== null && typeof value === "object" && "data" in value) {
|
|
2942
|
+
return value.data;
|
|
2943
|
+
}
|
|
2944
|
+
return value;
|
|
2945
|
+
}
|
|
2946
|
+
function isUnlockSigner(signer) {
|
|
2947
|
+
return typeof signer === "object" && signer !== null && typeof signer.signMessage === "function";
|
|
2948
|
+
}
|
|
2826
2949
|
function defaultVaultMessage(input) {
|
|
2827
2950
|
switch (input.code) {
|
|
2828
2951
|
case "DECRYPTION_FAILED":
|
|
@@ -2860,6 +2983,7 @@ var DataVaultService = class extends BaseService {
|
|
|
2860
2983
|
this.masterKey = null;
|
|
2861
2984
|
this.encryptionIdentity = null;
|
|
2862
2985
|
this._isUnlocked = false;
|
|
2986
|
+
this.unlockInFlight = null;
|
|
2863
2987
|
this.vaultConfig = config;
|
|
2864
2988
|
this._config = config;
|
|
2865
2989
|
}
|
|
@@ -2873,13 +2997,16 @@ var DataVaultService = class extends BaseService {
|
|
|
2873
2997
|
* Whether the vault is currently unlocked.
|
|
2874
2998
|
*/
|
|
2875
2999
|
get isUnlocked() {
|
|
2876
|
-
return this._isUnlocked;
|
|
3000
|
+
return this.usesNetworkEncryption || this._isUnlocked;
|
|
2877
3001
|
}
|
|
2878
3002
|
/**
|
|
2879
3003
|
* The vault's public encryption key (X25519).
|
|
2880
3004
|
* Throws if vault is locked.
|
|
2881
3005
|
*/
|
|
2882
3006
|
get publicKey() {
|
|
3007
|
+
if (this.usesNetworkEncryption) {
|
|
3008
|
+
throw new Error("Network-encrypted vaults do not expose a local public key");
|
|
3009
|
+
}
|
|
2883
3010
|
if (!this.encryptionIdentity) {
|
|
2884
3011
|
throw new Error("Vault is locked");
|
|
2885
3012
|
}
|
|
@@ -2897,12 +3024,51 @@ var DataVaultService = class extends BaseService {
|
|
|
2897
3024
|
get tc() {
|
|
2898
3025
|
return this.vaultConfig.tc;
|
|
2899
3026
|
}
|
|
3027
|
+
get networkEncryption() {
|
|
3028
|
+
return this.vaultConfig.encryption;
|
|
3029
|
+
}
|
|
3030
|
+
get usesNetworkEncryption() {
|
|
3031
|
+
return this.networkEncryption !== void 0;
|
|
3032
|
+
}
|
|
2900
3033
|
/**
|
|
2901
3034
|
* Get the host URL.
|
|
2902
3035
|
*/
|
|
2903
3036
|
get host() {
|
|
2904
3037
|
return this.tc.hosts[0];
|
|
2905
3038
|
}
|
|
3039
|
+
async decryptCapabilityProof() {
|
|
3040
|
+
const proof = this.networkEncryption?.decryptCapabilityProof;
|
|
3041
|
+
if (typeof proof === "function") {
|
|
3042
|
+
return await proof();
|
|
3043
|
+
}
|
|
3044
|
+
return proof ?? { proofs: [] };
|
|
3045
|
+
}
|
|
3046
|
+
serializeValue(value, options) {
|
|
3047
|
+
let plaintext;
|
|
3048
|
+
if (value instanceof Uint8Array) {
|
|
3049
|
+
plaintext = value;
|
|
3050
|
+
} else if (options?.serialize) {
|
|
3051
|
+
plaintext = options.serialize(value);
|
|
3052
|
+
} else if (typeof value === "string") {
|
|
3053
|
+
plaintext = toBytes(value);
|
|
3054
|
+
} else {
|
|
3055
|
+
plaintext = toBytes(JSON.stringify(value));
|
|
3056
|
+
}
|
|
3057
|
+
const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
|
|
3058
|
+
return { plaintext, contentType };
|
|
3059
|
+
}
|
|
3060
|
+
deserializeValue(plaintext, contentType, options) {
|
|
3061
|
+
if (options?.raw) {
|
|
3062
|
+
return plaintext;
|
|
3063
|
+
}
|
|
3064
|
+
if (options?.deserialize) {
|
|
3065
|
+
return options.deserialize(plaintext);
|
|
3066
|
+
}
|
|
3067
|
+
if (contentType === "application/json") {
|
|
3068
|
+
return JSON.parse(fromBytes(plaintext));
|
|
3069
|
+
}
|
|
3070
|
+
return plaintext;
|
|
3071
|
+
}
|
|
2906
3072
|
// =========================================================================
|
|
2907
3073
|
// Phase 1: Core Operations
|
|
2908
3074
|
// =========================================================================
|
|
@@ -2919,30 +3085,44 @@ var DataVaultService = class extends BaseService {
|
|
|
2919
3085
|
* signatures exist (browser only).
|
|
2920
3086
|
*/
|
|
2921
3087
|
async unlock(signer) {
|
|
2922
|
-
|
|
3088
|
+
if (this.usesNetworkEncryption) {
|
|
3089
|
+
this._isUnlocked = true;
|
|
3090
|
+
return { ok: true, data: void 0 };
|
|
3091
|
+
}
|
|
3092
|
+
const unlockSigner = isUnlockSigner(signer) ? signer : void 0;
|
|
3093
|
+
if (this._isUnlocked && this.masterKey && (this.encryptionIdentity || !unlockSigner)) {
|
|
3094
|
+
return { ok: true, data: void 0 };
|
|
3095
|
+
}
|
|
3096
|
+
if (this.unlockInFlight) {
|
|
3097
|
+
return this.unlockInFlight;
|
|
3098
|
+
}
|
|
3099
|
+
this.unlockInFlight = this.withTelemetry("unlock", void 0, async () => {
|
|
2923
3100
|
const spaceId = this.vaultConfig.spaceId;
|
|
2924
3101
|
const versionConfig = VaultVersionConfig[CURRENT_VAULT_VERSION];
|
|
2925
3102
|
const masterCacheKey = `vault-master:${spaceId}`;
|
|
2926
3103
|
const identityCacheKey = `vault-identity:${this.tc.address}`;
|
|
2927
3104
|
try {
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
if (!
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3105
|
+
if (!this.masterKey) {
|
|
3106
|
+
let masterSigBytes = await loadCachedSignature(masterCacheKey);
|
|
3107
|
+
if (!masterSigBytes) {
|
|
3108
|
+
if (!unlockSigner) {
|
|
3109
|
+
return vaultError({
|
|
3110
|
+
code: "VAULT_LOCKED",
|
|
3111
|
+
message: "Signer is required when no cached master signature exists"
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
const sig = await unlockSigner.signMessage(
|
|
3115
|
+
versionConfig.masterMessage(spaceId)
|
|
3116
|
+
);
|
|
3117
|
+
masterSigBytes = toBytes(sig);
|
|
3118
|
+
await cacheSignature(masterCacheKey, masterSigBytes);
|
|
2935
3119
|
}
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
masterSigBytes,
|
|
2943
|
-
this.crypto.sha256(toBytes(spaceId)),
|
|
2944
|
-
toBytes("vault-master")
|
|
2945
|
-
);
|
|
3120
|
+
this.masterKey = this.crypto.deriveKey(
|
|
3121
|
+
masterSigBytes,
|
|
3122
|
+
this.crypto.sha256(toBytes(spaceId)),
|
|
3123
|
+
toBytes("vault-master")
|
|
3124
|
+
);
|
|
3125
|
+
}
|
|
2946
3126
|
const publicSpaceId = this.tc.makePublicSpaceId(this.tc.address, this.tc.chainId);
|
|
2947
3127
|
let existingPubKey = null;
|
|
2948
3128
|
try {
|
|
@@ -2965,13 +3145,14 @@ var DataVaultService = class extends BaseService {
|
|
|
2965
3145
|
} else {
|
|
2966
3146
|
let identitySigBytes = await loadCachedSignature(identityCacheKey);
|
|
2967
3147
|
if (!identitySigBytes) {
|
|
2968
|
-
if (!
|
|
3148
|
+
if (!unlockSigner) {
|
|
2969
3149
|
this.encryptionIdentity = null;
|
|
2970
3150
|
this._isUnlocked = true;
|
|
2971
3151
|
return ok(void 0);
|
|
2972
3152
|
}
|
|
2973
|
-
const
|
|
2974
|
-
|
|
3153
|
+
const sig = await unlockSigner.signMessage(
|
|
3154
|
+
versionConfig.identityMessage
|
|
3155
|
+
);
|
|
2975
3156
|
identitySigBytes = toBytes(sig);
|
|
2976
3157
|
await cacheSignature(identityCacheKey, identitySigBytes);
|
|
2977
3158
|
}
|
|
@@ -3001,6 +3182,11 @@ var DataVaultService = class extends BaseService {
|
|
|
3001
3182
|
});
|
|
3002
3183
|
}
|
|
3003
3184
|
});
|
|
3185
|
+
try {
|
|
3186
|
+
return await this.unlockInFlight;
|
|
3187
|
+
} finally {
|
|
3188
|
+
this.unlockInFlight = null;
|
|
3189
|
+
}
|
|
3004
3190
|
}
|
|
3005
3191
|
/**
|
|
3006
3192
|
* Clear the cached vault signatures.
|
|
@@ -3029,6 +3215,131 @@ var DataVaultService = class extends BaseService {
|
|
|
3029
3215
|
this.lock();
|
|
3030
3216
|
super.onSignOut();
|
|
3031
3217
|
}
|
|
3218
|
+
async putNetworkEncrypted(key, value, options) {
|
|
3219
|
+
const config = this.networkEncryption;
|
|
3220
|
+
if (!config) {
|
|
3221
|
+
return vaultError({
|
|
3222
|
+
code: "VAULT_LOCKED",
|
|
3223
|
+
message: "Network encryption is not configured"
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
if (!this.requireAuth()) {
|
|
3227
|
+
return vaultError({
|
|
3228
|
+
code: "VAULT_LOCKED",
|
|
3229
|
+
message: "Authentication required"
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
try {
|
|
3233
|
+
const { plaintext, contentType } = this.serializeValue(value, options);
|
|
3234
|
+
const metadata = {
|
|
3235
|
+
[VaultHeaders.VERSION]: "2",
|
|
3236
|
+
[VaultHeaders.CIPHER]: "tinycloud-network-envelope",
|
|
3237
|
+
[VaultHeaders.CONTENT_TYPE]: contentType,
|
|
3238
|
+
...options?.metadata ?? {}
|
|
3239
|
+
};
|
|
3240
|
+
const aad = toBytes(`tinycloud.vault:${this.vaultConfig.spaceId}:${key}`);
|
|
3241
|
+
const envelopeResult = await config.service.encryptToNetwork(
|
|
3242
|
+
config.networkId,
|
|
3243
|
+
plaintext,
|
|
3244
|
+
{ aad, metadata }
|
|
3245
|
+
);
|
|
3246
|
+
if (!envelopeResult.ok) {
|
|
3247
|
+
return vaultError({
|
|
3248
|
+
code: "STORAGE_ERROR",
|
|
3249
|
+
cause: new Error(envelopeResult.error.message)
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
const valuePutResult = await this.tc.kv.put(
|
|
3253
|
+
`vault/${key}`,
|
|
3254
|
+
JSON.stringify(envelopeResult.data)
|
|
3255
|
+
);
|
|
3256
|
+
if (!valuePutResult.ok) {
|
|
3257
|
+
return vaultError({
|
|
3258
|
+
code: "STORAGE_ERROR",
|
|
3259
|
+
cause: new Error(
|
|
3260
|
+
`Failed to store encrypted value: ${valuePutResult.error.message}`
|
|
3261
|
+
)
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
3264
|
+
return { ok: true, data: void 0 };
|
|
3265
|
+
} catch (error) {
|
|
3266
|
+
return vaultError({
|
|
3267
|
+
code: "STORAGE_ERROR",
|
|
3268
|
+
cause: toError(error)
|
|
3269
|
+
});
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
async getNetworkEncrypted(key, options) {
|
|
3273
|
+
const config = this.networkEncryption;
|
|
3274
|
+
if (!config) {
|
|
3275
|
+
return vaultError({
|
|
3276
|
+
code: "VAULT_LOCKED",
|
|
3277
|
+
message: "Network encryption is not configured"
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
if (!this.requireAuth()) {
|
|
3281
|
+
return vaultError({
|
|
3282
|
+
code: "VAULT_LOCKED",
|
|
3283
|
+
message: "Authentication required"
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
try {
|
|
3287
|
+
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
3288
|
+
raw: true
|
|
3289
|
+
});
|
|
3290
|
+
if (!valueResult.ok) {
|
|
3291
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3292
|
+
}
|
|
3293
|
+
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
3294
|
+
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
3295
|
+
const proof = await this.decryptCapabilityProof();
|
|
3296
|
+
const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
3297
|
+
if (!plaintextResult.ok) {
|
|
3298
|
+
return vaultError({
|
|
3299
|
+
code: "DECRYPTION_FAILED",
|
|
3300
|
+
message: plaintextResult.error.message
|
|
3301
|
+
});
|
|
3302
|
+
}
|
|
3303
|
+
const metadata = envelope.metadata ?? {};
|
|
3304
|
+
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
3305
|
+
const keyId = metadata[VaultHeaders.KEY_ID] ?? envelope.encryptedSymmetricKeyHash.slice(0, 16);
|
|
3306
|
+
const value = this.deserializeValue(
|
|
3307
|
+
plaintextResult.data,
|
|
3308
|
+
contentType,
|
|
3309
|
+
options
|
|
3310
|
+
);
|
|
3311
|
+
return { ok: true, data: { value, metadata, keyId } };
|
|
3312
|
+
} catch (error) {
|
|
3313
|
+
return vaultError({
|
|
3314
|
+
code: "STORAGE_ERROR",
|
|
3315
|
+
cause: toError(error)
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
async headNetworkEncrypted(key) {
|
|
3320
|
+
if (!this.requireAuth()) {
|
|
3321
|
+
return vaultError({
|
|
3322
|
+
code: "VAULT_LOCKED",
|
|
3323
|
+
message: "Authentication required"
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3326
|
+
try {
|
|
3327
|
+
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
3328
|
+
raw: true
|
|
3329
|
+
});
|
|
3330
|
+
if (!valueResult.ok) {
|
|
3331
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3332
|
+
}
|
|
3333
|
+
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
3334
|
+
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
3335
|
+
return { ok: true, data: envelope.metadata ?? {} };
|
|
3336
|
+
} catch (error) {
|
|
3337
|
+
return vaultError({
|
|
3338
|
+
code: "STORAGE_ERROR",
|
|
3339
|
+
cause: toError(error)
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3032
3343
|
/**
|
|
3033
3344
|
* Encrypt and store a value at the given key.
|
|
3034
3345
|
*
|
|
@@ -3038,6 +3349,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3038
3349
|
*/
|
|
3039
3350
|
async put(key, value, options) {
|
|
3040
3351
|
return this.withTelemetry("put", key, async () => {
|
|
3352
|
+
if (this.usesNetworkEncryption) {
|
|
3353
|
+
return this.putNetworkEncrypted(key, value, options);
|
|
3354
|
+
}
|
|
3041
3355
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3042
3356
|
return vaultError({
|
|
3043
3357
|
code: "VAULT_LOCKED",
|
|
@@ -3130,6 +3444,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3130
3444
|
*/
|
|
3131
3445
|
async get(key, options) {
|
|
3132
3446
|
return this.withTelemetry("get", key, async () => {
|
|
3447
|
+
if (this.usesNetworkEncryption) {
|
|
3448
|
+
return this.getNetworkEncrypted(key, options);
|
|
3449
|
+
}
|
|
3133
3450
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3134
3451
|
return vaultError({
|
|
3135
3452
|
code: "VAULT_LOCKED",
|
|
@@ -3197,7 +3514,7 @@ var DataVaultService = class extends BaseService {
|
|
|
3197
3514
|
*/
|
|
3198
3515
|
async delete(key) {
|
|
3199
3516
|
return this.withTelemetry("delete", key, async () => {
|
|
3200
|
-
if (!this.
|
|
3517
|
+
if (!this.isUnlocked) {
|
|
3201
3518
|
return vaultError({
|
|
3202
3519
|
code: "VAULT_LOCKED",
|
|
3203
3520
|
message: "Vault must be unlocked before deleting data"
|
|
@@ -3210,6 +3527,13 @@ var DataVaultService = class extends BaseService {
|
|
|
3210
3527
|
});
|
|
3211
3528
|
}
|
|
3212
3529
|
try {
|
|
3530
|
+
if (this.usesNetworkEncryption) {
|
|
3531
|
+
const valueDelResult2 = await this.tc.kv.delete(`vault/${key}`);
|
|
3532
|
+
if (!valueDelResult2.ok) {
|
|
3533
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3534
|
+
}
|
|
3535
|
+
return ok(void 0);
|
|
3536
|
+
}
|
|
3213
3537
|
const [keyDelResult, valueDelResult] = await Promise.all([
|
|
3214
3538
|
this.tc.kv.delete(`keys/${key}`),
|
|
3215
3539
|
this.tc.kv.delete(`vault/${key}`)
|
|
@@ -3234,7 +3558,7 @@ var DataVaultService = class extends BaseService {
|
|
|
3234
3558
|
*/
|
|
3235
3559
|
async list(options) {
|
|
3236
3560
|
return this.withTelemetry("list", options?.prefix, async () => {
|
|
3237
|
-
if (!this.
|
|
3561
|
+
if (!this.isUnlocked) {
|
|
3238
3562
|
return vaultError({
|
|
3239
3563
|
code: "VAULT_LOCKED",
|
|
3240
3564
|
message: "Vault must be unlocked before listing data"
|
|
@@ -3284,6 +3608,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3284
3608
|
*/
|
|
3285
3609
|
async head(key) {
|
|
3286
3610
|
return this.withTelemetry("head", key, async () => {
|
|
3611
|
+
if (this.usesNetworkEncryption) {
|
|
3612
|
+
return this.headNetworkEncrypted(key);
|
|
3613
|
+
}
|
|
3287
3614
|
if (!this._isUnlocked) {
|
|
3288
3615
|
return vaultError({
|
|
3289
3616
|
code: "VAULT_LOCKED",
|
|
@@ -3351,6 +3678,16 @@ var DataVaultService = class extends BaseService {
|
|
|
3351
3678
|
*/
|
|
3352
3679
|
async reencrypt(key, recipientDID, options) {
|
|
3353
3680
|
return this.withTelemetry("reencrypt", key, async () => {
|
|
3681
|
+
if (this.usesNetworkEncryption) {
|
|
3682
|
+
void recipientDID;
|
|
3683
|
+
void options;
|
|
3684
|
+
return vaultError({
|
|
3685
|
+
code: "STORAGE_ERROR",
|
|
3686
|
+
cause: new Error(
|
|
3687
|
+
"Vault key grants are deprecated for network-encrypted vaults; grant tinycloud.encryption/decrypt on the network plus KV access to vault data."
|
|
3688
|
+
)
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3354
3691
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3355
3692
|
return vaultError({
|
|
3356
3693
|
code: "VAULT_LOCKED",
|
|
@@ -3440,6 +3777,66 @@ var DataVaultService = class extends BaseService {
|
|
|
3440
3777
|
*/
|
|
3441
3778
|
async getShared(grantorDID, key, options) {
|
|
3442
3779
|
return this.withTelemetry("getShared", key, async () => {
|
|
3780
|
+
if (this.usesNetworkEncryption) {
|
|
3781
|
+
const grantorKV = options?.kv;
|
|
3782
|
+
if (!grantorKV) {
|
|
3783
|
+
return vaultError({
|
|
3784
|
+
code: "STORAGE_ERROR",
|
|
3785
|
+
cause: new Error(
|
|
3786
|
+
"getShared requires a delegated KV service via options.kv."
|
|
3787
|
+
)
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
const config = this.networkEncryption;
|
|
3791
|
+
if (!config) {
|
|
3792
|
+
return vaultError({
|
|
3793
|
+
code: "VAULT_LOCKED",
|
|
3794
|
+
message: "Network encryption is not configured"
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
if (!this.requireAuth()) {
|
|
3798
|
+
return vaultError({
|
|
3799
|
+
code: "VAULT_LOCKED",
|
|
3800
|
+
message: "Authentication required"
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3803
|
+
try {
|
|
3804
|
+
const valueResult = await grantorKV.get(`vault/${key}`, {
|
|
3805
|
+
raw: true
|
|
3806
|
+
});
|
|
3807
|
+
if (!valueResult.ok) {
|
|
3808
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3809
|
+
}
|
|
3810
|
+
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
3811
|
+
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
3812
|
+
const proof = await this.decryptCapabilityProof();
|
|
3813
|
+
const plaintextResult = await config.service.decryptEnvelope(
|
|
3814
|
+
envelope,
|
|
3815
|
+
proof
|
|
3816
|
+
);
|
|
3817
|
+
if (!plaintextResult.ok) {
|
|
3818
|
+
return vaultError({
|
|
3819
|
+
code: "DECRYPTION_FAILED",
|
|
3820
|
+
message: plaintextResult.error.message
|
|
3821
|
+
});
|
|
3822
|
+
}
|
|
3823
|
+
const metadata = envelope.metadata ?? {};
|
|
3824
|
+
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
3825
|
+
const keyId = metadata[VaultHeaders.KEY_ID] ?? envelope.encryptedSymmetricKeyHash.slice(0, 16);
|
|
3826
|
+
const value = this.deserializeValue(
|
|
3827
|
+
plaintextResult.data,
|
|
3828
|
+
contentType,
|
|
3829
|
+
options
|
|
3830
|
+
);
|
|
3831
|
+
void grantorDID;
|
|
3832
|
+
return { ok: true, data: { value, metadata, keyId } };
|
|
3833
|
+
} catch (error) {
|
|
3834
|
+
return vaultError({
|
|
3835
|
+
code: "STORAGE_ERROR",
|
|
3836
|
+
cause: toError(error)
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3443
3840
|
if (!this._isUnlocked || !this.masterKey || !this.encryptionIdentity) {
|
|
3444
3841
|
return vaultError({
|
|
3445
3842
|
code: "VAULT_LOCKED",
|
|
@@ -3565,6 +3962,10 @@ var DataVaultService = class extends BaseService {
|
|
|
3565
3962
|
*/
|
|
3566
3963
|
async listGrants(key) {
|
|
3567
3964
|
return this.withTelemetry("listGrants", key, async () => {
|
|
3965
|
+
if (this.usesNetworkEncryption) {
|
|
3966
|
+
void key;
|
|
3967
|
+
return { ok: true, data: [] };
|
|
3968
|
+
}
|
|
3568
3969
|
if (!this._isUnlocked) {
|
|
3569
3970
|
return vaultError({
|
|
3570
3971
|
code: "VAULT_LOCKED",
|
|
@@ -3628,6 +4029,15 @@ var DataVaultService = class extends BaseService {
|
|
|
3628
4029
|
*/
|
|
3629
4030
|
async revoke(key, recipientDID) {
|
|
3630
4031
|
return this.withTelemetry("revoke", key, async () => {
|
|
4032
|
+
if (this.usesNetworkEncryption) {
|
|
4033
|
+
void recipientDID;
|
|
4034
|
+
return vaultError({
|
|
4035
|
+
code: "STORAGE_ERROR",
|
|
4036
|
+
cause: new Error(
|
|
4037
|
+
"Vault key grants are deprecated for network-encrypted vaults; revoke KV and tinycloud.encryption/decrypt grants instead."
|
|
4038
|
+
)
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
3631
4041
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3632
4042
|
return vaultError({
|
|
3633
4043
|
code: "VAULT_LOCKED",
|
|
@@ -3763,18 +4173,67 @@ function createVaultCrypto(wasm) {
|
|
|
3763
4173
|
};
|
|
3764
4174
|
}
|
|
3765
4175
|
|
|
3766
|
-
// src/secrets/
|
|
4176
|
+
// src/secrets/paths.ts
|
|
3767
4177
|
var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
3768
4178
|
var SECRET_PREFIX = "secrets/";
|
|
3769
|
-
|
|
4179
|
+
var SCOPED_SECRET_PREFIX = "secrets/scoped/";
|
|
4180
|
+
var RESERVED_SECRET_SCOPES = /* @__PURE__ */ new Set(["default", "global"]);
|
|
4181
|
+
function canonicalizeSecretScope(scope) {
|
|
4182
|
+
if (scope === void 0) {
|
|
4183
|
+
return void 0;
|
|
4184
|
+
}
|
|
4185
|
+
const trimmed = scope.trim();
|
|
4186
|
+
if (trimmed === "") {
|
|
4187
|
+
throw new Error("Secret scope must be non-empty; omit scope for global secrets.");
|
|
4188
|
+
}
|
|
4189
|
+
const canonical = trimmed.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
4190
|
+
if (canonical === "") {
|
|
4191
|
+
throw new Error("Secret scope must contain at least one letter or number.");
|
|
4192
|
+
}
|
|
4193
|
+
if (RESERVED_SECRET_SCOPES.has(canonical)) {
|
|
4194
|
+
throw new Error(
|
|
4195
|
+
`Secret scope ${JSON.stringify(scope)} is reserved; omit scope for global secrets.`
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
return canonical;
|
|
4199
|
+
}
|
|
4200
|
+
function resolveSecretPath(name, options = {}) {
|
|
4201
|
+
const normalizedName = name.trim();
|
|
4202
|
+
if (!SECRET_NAME_RE.test(normalizedName)) {
|
|
4203
|
+
throw new Error(
|
|
4204
|
+
`Invalid secret name ${JSON.stringify(name)}. Secret names must match ${SECRET_NAME_RE.source}.`
|
|
4205
|
+
);
|
|
4206
|
+
}
|
|
4207
|
+
const scope = canonicalizeSecretScope(options.scope);
|
|
4208
|
+
const vaultKey = scope === void 0 ? `${SECRET_PREFIX}${normalizedName}` : `${SCOPED_SECRET_PREFIX}${scope}/${normalizedName}`;
|
|
4209
|
+
return {
|
|
4210
|
+
name: normalizedName,
|
|
4211
|
+
...scope !== void 0 ? { scope } : {},
|
|
4212
|
+
vaultKey,
|
|
4213
|
+
permissionPaths: {
|
|
4214
|
+
vault: `vault/${vaultKey}`
|
|
4215
|
+
}
|
|
4216
|
+
};
|
|
4217
|
+
}
|
|
4218
|
+
function resolveSecretListPrefix(options = {}) {
|
|
4219
|
+
const scope = canonicalizeSecretScope(options.scope);
|
|
4220
|
+
return scope === void 0 ? "vault/secrets/" : `vault/secrets/scoped/${scope}/`;
|
|
4221
|
+
}
|
|
4222
|
+
|
|
4223
|
+
// src/secrets/SecretsService.ts
|
|
4224
|
+
function invalidSecretInput(message) {
|
|
3770
4225
|
return err({
|
|
3771
4226
|
code: ErrorCodes.INVALID_INPUT,
|
|
3772
4227
|
service: "secrets",
|
|
3773
|
-
message
|
|
4228
|
+
message
|
|
3774
4229
|
});
|
|
3775
4230
|
}
|
|
3776
|
-
function
|
|
3777
|
-
|
|
4231
|
+
function resolveSecretPathResult(name, options) {
|
|
4232
|
+
try {
|
|
4233
|
+
return resolveSecretPath(name, options);
|
|
4234
|
+
} catch (error) {
|
|
4235
|
+
return invalidSecretInput(error instanceof Error ? error.message : String(error));
|
|
4236
|
+
}
|
|
3778
4237
|
}
|
|
3779
4238
|
var SecretsService = class {
|
|
3780
4239
|
constructor(vault) {
|
|
@@ -3792,36 +4251,40 @@ var SecretsService = class {
|
|
|
3792
4251
|
lock() {
|
|
3793
4252
|
this.vault.lock();
|
|
3794
4253
|
}
|
|
3795
|
-
async get(name) {
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
const result = await this.vault.get(secretKey(name));
|
|
4254
|
+
async get(name, options) {
|
|
4255
|
+
const secretPath = resolveSecretPathResult(name, options);
|
|
4256
|
+
if ("ok" in secretPath) return secretPath;
|
|
4257
|
+
const result = await this.vault.get(secretPath.vaultKey);
|
|
3800
4258
|
if (!result.ok) {
|
|
3801
4259
|
return result;
|
|
3802
4260
|
}
|
|
3803
4261
|
return { ok: true, data: result.data.value.value };
|
|
3804
4262
|
}
|
|
3805
|
-
async put(name, value) {
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
}
|
|
4263
|
+
async put(name, value, options) {
|
|
4264
|
+
const secretPath = resolveSecretPathResult(name, options);
|
|
4265
|
+
if ("ok" in secretPath) return secretPath;
|
|
3809
4266
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3810
|
-
return this.vault.put(
|
|
4267
|
+
return this.vault.put(secretPath.vaultKey, {
|
|
3811
4268
|
value,
|
|
3812
4269
|
createdAt: now,
|
|
3813
4270
|
updatedAt: now
|
|
3814
4271
|
});
|
|
3815
4272
|
}
|
|
3816
|
-
async delete(name) {
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
return this.vault.delete(secretKey(name));
|
|
4273
|
+
async delete(name, options) {
|
|
4274
|
+
const secretPath = resolveSecretPathResult(name, options);
|
|
4275
|
+
if ("ok" in secretPath) return secretPath;
|
|
4276
|
+
return this.vault.delete(secretPath.vaultKey);
|
|
3821
4277
|
}
|
|
3822
|
-
async list() {
|
|
4278
|
+
async list(options) {
|
|
4279
|
+
let prefix;
|
|
4280
|
+
try {
|
|
4281
|
+
const scope = canonicalizeSecretScope(options?.scope);
|
|
4282
|
+
prefix = scope === void 0 ? "secrets/" : `secrets/scoped/${scope}/`;
|
|
4283
|
+
} catch (error) {
|
|
4284
|
+
return invalidSecretInput(error instanceof Error ? error.message : String(error));
|
|
4285
|
+
}
|
|
3823
4286
|
const result = await this.vault.list({
|
|
3824
|
-
prefix
|
|
4287
|
+
prefix,
|
|
3825
4288
|
removePrefix: true
|
|
3826
4289
|
});
|
|
3827
4290
|
if (!result.ok) {
|
|
@@ -3833,13 +4296,980 @@ var SecretsService = class {
|
|
|
3833
4296
|
};
|
|
3834
4297
|
}
|
|
3835
4298
|
};
|
|
4299
|
+
|
|
4300
|
+
// src/encryption/canonical.ts
|
|
4301
|
+
function canonicalize(value) {
|
|
4302
|
+
if (value === void 0) {
|
|
4303
|
+
return "";
|
|
4304
|
+
}
|
|
4305
|
+
return stringify(value);
|
|
4306
|
+
}
|
|
4307
|
+
function stringify(value) {
|
|
4308
|
+
if (value === null) return "null";
|
|
4309
|
+
switch (typeof value) {
|
|
4310
|
+
case "boolean":
|
|
4311
|
+
case "number":
|
|
4312
|
+
return JSON.stringify(value);
|
|
4313
|
+
case "string":
|
|
4314
|
+
return JSON.stringify(value);
|
|
4315
|
+
case "object": {
|
|
4316
|
+
if (Array.isArray(value)) {
|
|
4317
|
+
return `[${value.map(stringify).join(",")}]`;
|
|
4318
|
+
}
|
|
4319
|
+
const keys = Object.keys(value).sort();
|
|
4320
|
+
const parts = [];
|
|
4321
|
+
for (const k of keys) {
|
|
4322
|
+
const v = value[k];
|
|
4323
|
+
if (v === void 0) continue;
|
|
4324
|
+
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
4325
|
+
}
|
|
4326
|
+
return `{${parts.join(",")}}`;
|
|
4327
|
+
}
|
|
4328
|
+
default:
|
|
4329
|
+
throw new TypeError(
|
|
4330
|
+
`canonicalize: unsupported value type ${typeof value}`
|
|
4331
|
+
);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
var HEX = "0123456789abcdef";
|
|
4335
|
+
function hexEncode2(bytes) {
|
|
4336
|
+
let out = "";
|
|
4337
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
4338
|
+
const b = bytes[i];
|
|
4339
|
+
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
4340
|
+
}
|
|
4341
|
+
return out;
|
|
4342
|
+
}
|
|
4343
|
+
function hexDecode(hex) {
|
|
4344
|
+
if (hex.length % 2 !== 0) {
|
|
4345
|
+
throw new Error("hex string must have even length");
|
|
4346
|
+
}
|
|
4347
|
+
const out = new Uint8Array(hex.length / 2);
|
|
4348
|
+
for (let i = 0; i < out.length; i++) {
|
|
4349
|
+
const hi = parseInt(hex[i * 2], 16);
|
|
4350
|
+
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
4351
|
+
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
4352
|
+
throw new Error("invalid hex character");
|
|
4353
|
+
}
|
|
4354
|
+
out[i] = hi << 4 | lo;
|
|
4355
|
+
}
|
|
4356
|
+
return out;
|
|
4357
|
+
}
|
|
4358
|
+
function base64Encode2(bytes) {
|
|
4359
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4360
|
+
let out = "";
|
|
4361
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
4362
|
+
const b0 = bytes[i];
|
|
4363
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
4364
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
4365
|
+
out += chars[b0 >> 2 & 63];
|
|
4366
|
+
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
4367
|
+
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
4368
|
+
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
4369
|
+
}
|
|
4370
|
+
return out;
|
|
4371
|
+
}
|
|
4372
|
+
function base64Decode2(s) {
|
|
4373
|
+
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
4374
|
+
const len = clean.length;
|
|
4375
|
+
if (len % 4 !== 0) {
|
|
4376
|
+
throw new Error("invalid base64 input");
|
|
4377
|
+
}
|
|
4378
|
+
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
4379
|
+
const outLen = len / 4 * 3 - padding;
|
|
4380
|
+
const out = new Uint8Array(outLen);
|
|
4381
|
+
const lookup = {};
|
|
4382
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4383
|
+
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
4384
|
+
let outIdx = 0;
|
|
4385
|
+
for (let i = 0; i < len; i += 4) {
|
|
4386
|
+
const v0 = lookup[clean[i]] ?? 0;
|
|
4387
|
+
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
4388
|
+
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
4389
|
+
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
4390
|
+
const b0 = v0 << 2 | v1 >> 4;
|
|
4391
|
+
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
4392
|
+
const b2 = (v2 & 3) << 6 | v3;
|
|
4393
|
+
if (outIdx < outLen) out[outIdx++] = b0;
|
|
4394
|
+
if (outIdx < outLen) out[outIdx++] = b1;
|
|
4395
|
+
if (outIdx < outLen) out[outIdx++] = b2;
|
|
4396
|
+
}
|
|
4397
|
+
return out;
|
|
4398
|
+
}
|
|
4399
|
+
function utf8Encode(s) {
|
|
4400
|
+
return new TextEncoder().encode(s);
|
|
4401
|
+
}
|
|
4402
|
+
function utf8Decode(b) {
|
|
4403
|
+
return new TextDecoder().decode(b);
|
|
4404
|
+
}
|
|
4405
|
+
function canonicalHashHex(sha256, value) {
|
|
4406
|
+
const canonical = canonicalize(value);
|
|
4407
|
+
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
// src/encryption/networkId.ts
|
|
4411
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
4412
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
4413
|
+
var NetworkIdError = class extends Error {
|
|
4414
|
+
constructor(message) {
|
|
4415
|
+
super(message);
|
|
4416
|
+
this.name = "NetworkIdError";
|
|
4417
|
+
}
|
|
4418
|
+
};
|
|
4419
|
+
function parseNetworkId(networkId) {
|
|
4420
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
4421
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
4422
|
+
}
|
|
4423
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
4424
|
+
throw new NetworkIdError(
|
|
4425
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
4426
|
+
);
|
|
4427
|
+
}
|
|
4428
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
4429
|
+
const lastColon = body.lastIndexOf(":");
|
|
4430
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
4431
|
+
throw new NetworkIdError(
|
|
4432
|
+
`networkId missing principal or name segment (got ${JSON.stringify(networkId)})`
|
|
4433
|
+
);
|
|
4434
|
+
}
|
|
4435
|
+
const principal = body.slice(0, lastColon);
|
|
4436
|
+
const name = body.slice(lastColon + 1);
|
|
4437
|
+
if (!principal.startsWith("did:")) {
|
|
4438
|
+
throw new NetworkIdError(
|
|
4439
|
+
`networkId principal must be a DID (got ${JSON.stringify(principal)})`
|
|
4440
|
+
);
|
|
4441
|
+
}
|
|
4442
|
+
const didParts = principal.split(":");
|
|
4443
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
4444
|
+
throw new NetworkIdError(
|
|
4445
|
+
`networkId principal is not a well-formed DID (got ${JSON.stringify(principal)})`
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
4449
|
+
throw new NetworkIdError(
|
|
4450
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
return { networkId, principal, name };
|
|
4454
|
+
}
|
|
4455
|
+
function buildNetworkId(principal, name) {
|
|
4456
|
+
if (typeof principal !== "string" || !principal.startsWith("did:")) {
|
|
4457
|
+
throw new NetworkIdError("principal must be a DID");
|
|
4458
|
+
}
|
|
4459
|
+
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
4460
|
+
throw new NetworkIdError(
|
|
4461
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
4462
|
+
);
|
|
4463
|
+
}
|
|
4464
|
+
const networkId = `${URN_PREFIX}${principal}:${name}`;
|
|
4465
|
+
parseNetworkId(networkId);
|
|
4466
|
+
return networkId;
|
|
4467
|
+
}
|
|
4468
|
+
function isNetworkId(networkId) {
|
|
4469
|
+
if (typeof networkId !== "string") {
|
|
4470
|
+
return false;
|
|
4471
|
+
}
|
|
4472
|
+
try {
|
|
4473
|
+
parseNetworkId(networkId);
|
|
4474
|
+
return true;
|
|
4475
|
+
} catch {
|
|
4476
|
+
return false;
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
function networkDiscoveryKey(name) {
|
|
4480
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
4481
|
+
throw new NetworkIdError(
|
|
4482
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
4483
|
+
);
|
|
4484
|
+
}
|
|
4485
|
+
return `.well-known/encryption/network/${name}`;
|
|
4486
|
+
}
|
|
4487
|
+
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
4488
|
+
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
4489
|
+
|
|
4490
|
+
// src/encryption/types.ts
|
|
4491
|
+
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
4492
|
+
var ENVELOPE_VERSION = 1;
|
|
4493
|
+
var DEFAULT_KEY_VERSION = 1;
|
|
4494
|
+
var DECRYPT_FACT_TYPE = "tinycloud.encryption.decrypt/v1";
|
|
4495
|
+
var DECRYPT_RESULT_TYPE = "tinycloud.encryption.decrypt-result/v1";
|
|
4496
|
+
var ENCRYPTION_SERVICE = "tinycloud.encryption";
|
|
4497
|
+
var ENCRYPTION_SERVICE_SHORT = "encryption";
|
|
4498
|
+
var DECRYPT_ACTION = "tinycloud.encryption/decrypt";
|
|
4499
|
+
function defaultEncryptionMessage(input) {
|
|
4500
|
+
switch (input.code) {
|
|
4501
|
+
case "NETWORK_NOT_FOUND":
|
|
4502
|
+
return input.message ?? `Network not found: ${input.networkId ?? input.name ?? "<unknown>"}`;
|
|
4503
|
+
case "NETWORK_NOT_ACTIVE":
|
|
4504
|
+
return input.message ?? `Network not active (state=${input.state})`;
|
|
4505
|
+
case "INVALID_NETWORK_ID":
|
|
4506
|
+
return input.message;
|
|
4507
|
+
case "INVALID_ENVELOPE":
|
|
4508
|
+
return input.message;
|
|
4509
|
+
case "DECRYPT_DENIED":
|
|
4510
|
+
return input.message;
|
|
4511
|
+
case "INVALID_RESPONSE":
|
|
4512
|
+
return input.message;
|
|
4513
|
+
case "RESPONSE_SIGNATURE_INVALID":
|
|
4514
|
+
return input.message ?? "Node response signature failed to verify";
|
|
4515
|
+
case "RESPONSE_BINDING_MISMATCH":
|
|
4516
|
+
return input.message ?? `Node response binding mismatch on field ${JSON.stringify(input.field)}`;
|
|
4517
|
+
case "TRANSPORT_ERROR":
|
|
4518
|
+
return input.message ?? input.cause.message;
|
|
4519
|
+
case "INVALID_INPUT":
|
|
4520
|
+
return input.message;
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
function encryptionError(input) {
|
|
4524
|
+
return {
|
|
4525
|
+
...input,
|
|
4526
|
+
service: "encryption",
|
|
4527
|
+
message: defaultEncryptionMessage(input)
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4530
|
+
function toError2(error) {
|
|
4531
|
+
if (error instanceof Error) return error;
|
|
4532
|
+
if (typeof error === "object" && error !== null) {
|
|
4533
|
+
return new Error(JSON.stringify(error));
|
|
4534
|
+
}
|
|
4535
|
+
return new Error(String(error));
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
// src/encryption/discovery.ts
|
|
4539
|
+
async function discoverNetwork(input) {
|
|
4540
|
+
let networkId;
|
|
4541
|
+
let principal;
|
|
4542
|
+
let name;
|
|
4543
|
+
try {
|
|
4544
|
+
if (input.identifier.startsWith("urn:tinycloud:encryption:")) {
|
|
4545
|
+
const parsed = parseNetworkId(input.identifier);
|
|
4546
|
+
networkId = parsed.networkId;
|
|
4547
|
+
principal = parsed.principal;
|
|
4548
|
+
name = parsed.name;
|
|
4549
|
+
} else {
|
|
4550
|
+
if (input.principal === void 0) {
|
|
4551
|
+
return {
|
|
4552
|
+
ok: false,
|
|
4553
|
+
error: encryptionError({
|
|
4554
|
+
code: "INVALID_INPUT",
|
|
4555
|
+
message: "discoverNetwork requires `principal` when identifier is a bare network name"
|
|
4556
|
+
})
|
|
4557
|
+
};
|
|
4558
|
+
}
|
|
4559
|
+
networkId = `urn:tinycloud:encryption:${input.principal}:${input.identifier}`;
|
|
4560
|
+
const parsed = parseNetworkId(networkId);
|
|
4561
|
+
principal = parsed.principal;
|
|
4562
|
+
name = parsed.name;
|
|
4563
|
+
}
|
|
4564
|
+
} catch (err3) {
|
|
4565
|
+
if (err3 instanceof NetworkIdError) {
|
|
4566
|
+
return {
|
|
4567
|
+
ok: false,
|
|
4568
|
+
error: encryptionError({
|
|
4569
|
+
code: "INVALID_NETWORK_ID",
|
|
4570
|
+
message: err3.message
|
|
4571
|
+
})
|
|
4572
|
+
};
|
|
4573
|
+
}
|
|
4574
|
+
throw err3;
|
|
4575
|
+
}
|
|
4576
|
+
if (input.node !== void 0) {
|
|
4577
|
+
try {
|
|
4578
|
+
const descriptor = await input.node.fetchByNetworkId(networkId);
|
|
4579
|
+
if (descriptor !== null) {
|
|
4580
|
+
const validated = validateDescriptor(descriptor, networkId, principal, name);
|
|
4581
|
+
if (!validated.ok) return validated;
|
|
4582
|
+
return { ok: true, data: { descriptor: validated.data, source: "node" } };
|
|
4583
|
+
}
|
|
4584
|
+
} catch (err3) {
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
if (input.wellKnown !== void 0) {
|
|
4588
|
+
try {
|
|
4589
|
+
const descriptor = await input.wellKnown.fetchWellKnown(
|
|
4590
|
+
principal,
|
|
4591
|
+
networkDiscoveryKey(name)
|
|
4592
|
+
);
|
|
4593
|
+
if (descriptor !== null) {
|
|
4594
|
+
const validated = validateDescriptor(descriptor, networkId, principal, name);
|
|
4595
|
+
if (!validated.ok) return validated;
|
|
4596
|
+
return {
|
|
4597
|
+
ok: true,
|
|
4598
|
+
data: { descriptor: validated.data, source: "well-known" }
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4601
|
+
} catch (err3) {
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
return {
|
|
4605
|
+
ok: false,
|
|
4606
|
+
error: encryptionError({
|
|
4607
|
+
code: "NETWORK_NOT_FOUND",
|
|
4608
|
+
networkId,
|
|
4609
|
+
name
|
|
4610
|
+
})
|
|
4611
|
+
};
|
|
4612
|
+
}
|
|
4613
|
+
function validateDescriptor(descriptor, networkId, principal, name) {
|
|
4614
|
+
if (descriptor.networkId !== networkId) {
|
|
4615
|
+
return {
|
|
4616
|
+
ok: false,
|
|
4617
|
+
error: encryptionError({
|
|
4618
|
+
code: "INVALID_NETWORK_ID",
|
|
4619
|
+
message: `descriptor networkId ${JSON.stringify(descriptor.networkId)} does not match expected ${JSON.stringify(networkId)}`
|
|
4620
|
+
})
|
|
4621
|
+
};
|
|
4622
|
+
}
|
|
4623
|
+
if (descriptor.principal !== principal) {
|
|
4624
|
+
return {
|
|
4625
|
+
ok: false,
|
|
4626
|
+
error: encryptionError({
|
|
4627
|
+
code: "INVALID_NETWORK_ID",
|
|
4628
|
+
message: "descriptor principal does not match networkId principal"
|
|
4629
|
+
})
|
|
4630
|
+
};
|
|
4631
|
+
}
|
|
4632
|
+
if (descriptor.name !== name) {
|
|
4633
|
+
return {
|
|
4634
|
+
ok: false,
|
|
4635
|
+
error: encryptionError({
|
|
4636
|
+
code: "INVALID_NETWORK_ID",
|
|
4637
|
+
message: "descriptor name does not match networkId name"
|
|
4638
|
+
})
|
|
4639
|
+
};
|
|
4640
|
+
}
|
|
4641
|
+
if (typeof descriptor.publicEncryptionKey !== "string" || descriptor.publicEncryptionKey.length === 0) {
|
|
4642
|
+
return {
|
|
4643
|
+
ok: false,
|
|
4644
|
+
error: encryptionError({
|
|
4645
|
+
code: "INVALID_NETWORK_ID",
|
|
4646
|
+
message: "descriptor publicEncryptionKey must be a non-empty string"
|
|
4647
|
+
})
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
return { ok: true, data: descriptor };
|
|
4651
|
+
}
|
|
4652
|
+
function ensureNetworkUsableForDecrypt(descriptor) {
|
|
4653
|
+
if (descriptor.state === "active" || descriptor.state === "rotating") {
|
|
4654
|
+
return { ok: true, data: descriptor };
|
|
4655
|
+
}
|
|
4656
|
+
return {
|
|
4657
|
+
ok: false,
|
|
4658
|
+
error: encryptionError({
|
|
4659
|
+
code: "NETWORK_NOT_ACTIVE",
|
|
4660
|
+
state: descriptor.state
|
|
4661
|
+
})
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
// src/encryption/envelope.ts
|
|
4666
|
+
function encryptToNetwork(crypto2, input) {
|
|
4667
|
+
parseNetworkId(input.networkId);
|
|
4668
|
+
const alg = input.alg ?? DEFAULT_ENCRYPTION_ALG;
|
|
4669
|
+
const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
|
|
4670
|
+
const symmetricKey = crypto2.randomBytes(32);
|
|
4671
|
+
const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
4672
|
+
const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
4673
|
+
const encryptedSymmetricKey = base64Encode2(wrapped);
|
|
4674
|
+
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
4675
|
+
crypto2.sha256,
|
|
4676
|
+
encryptedSymmetricKey
|
|
4677
|
+
);
|
|
4678
|
+
const envelope = {
|
|
4679
|
+
v: ENVELOPE_VERSION,
|
|
4680
|
+
networkId: input.networkId,
|
|
4681
|
+
alg,
|
|
4682
|
+
keyVersion,
|
|
4683
|
+
encryptedSymmetricKey,
|
|
4684
|
+
encryptedSymmetricKeyHash,
|
|
4685
|
+
ciphertext: base64Encode2(ciphertext),
|
|
4686
|
+
...input.aad !== void 0 ? { aad: base64Encode2(input.aad) } : {},
|
|
4687
|
+
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
4688
|
+
};
|
|
4689
|
+
return { envelope, symmetricKey };
|
|
4690
|
+
}
|
|
4691
|
+
function validateEnvelope(crypto2, envelope) {
|
|
4692
|
+
if (envelope === null || typeof envelope !== "object") {
|
|
4693
|
+
return {
|
|
4694
|
+
ok: false,
|
|
4695
|
+
error: encryptionError({
|
|
4696
|
+
code: "INVALID_ENVELOPE",
|
|
4697
|
+
message: "envelope must be an object"
|
|
4698
|
+
})
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
const e = envelope;
|
|
4702
|
+
if (e.v !== ENVELOPE_VERSION) {
|
|
4703
|
+
return {
|
|
4704
|
+
ok: false,
|
|
4705
|
+
error: encryptionError({
|
|
4706
|
+
code: "INVALID_ENVELOPE",
|
|
4707
|
+
message: `envelope.v must be ${ENVELOPE_VERSION} (got ${e.v})`
|
|
4708
|
+
})
|
|
4709
|
+
};
|
|
4710
|
+
}
|
|
4711
|
+
try {
|
|
4712
|
+
parseNetworkId(e.networkId);
|
|
4713
|
+
} catch (err3) {
|
|
4714
|
+
return {
|
|
4715
|
+
ok: false,
|
|
4716
|
+
error: encryptionError({
|
|
4717
|
+
code: "INVALID_ENVELOPE",
|
|
4718
|
+
message: `envelope.networkId is malformed: ${err3 instanceof Error ? err3.message : String(err3)}`
|
|
4719
|
+
})
|
|
4720
|
+
};
|
|
4721
|
+
}
|
|
4722
|
+
for (const field of [
|
|
4723
|
+
"alg",
|
|
4724
|
+
"encryptedSymmetricKey",
|
|
4725
|
+
"encryptedSymmetricKeyHash",
|
|
4726
|
+
"ciphertext"
|
|
4727
|
+
]) {
|
|
4728
|
+
if (typeof e[field] !== "string" || e[field].length === 0) {
|
|
4729
|
+
return {
|
|
4730
|
+
ok: false,
|
|
4731
|
+
error: encryptionError({
|
|
4732
|
+
code: "INVALID_ENVELOPE",
|
|
4733
|
+
message: `envelope.${field} must be a non-empty string`
|
|
4734
|
+
})
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
if (typeof e.keyVersion !== "number" || !Number.isInteger(e.keyVersion)) {
|
|
4739
|
+
return {
|
|
4740
|
+
ok: false,
|
|
4741
|
+
error: encryptionError({
|
|
4742
|
+
code: "INVALID_ENVELOPE",
|
|
4743
|
+
message: "envelope.keyVersion must be an integer"
|
|
4744
|
+
})
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
const expectedHash = canonicalHashHex(crypto2.sha256, e.encryptedSymmetricKey);
|
|
4748
|
+
if (expectedHash !== e.encryptedSymmetricKeyHash) {
|
|
4749
|
+
return {
|
|
4750
|
+
ok: false,
|
|
4751
|
+
error: encryptionError({
|
|
4752
|
+
code: "INVALID_ENVELOPE",
|
|
4753
|
+
message: "envelope.encryptedSymmetricKeyHash does not match canonical hash of encryptedSymmetricKey"
|
|
4754
|
+
})
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
return { ok: true, data: e };
|
|
4758
|
+
}
|
|
4759
|
+
function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
|
|
4760
|
+
const ciphertext = base64Decode2(envelope.ciphertext);
|
|
4761
|
+
const aad = envelope.aad !== void 0 ? base64Decode2(envelope.aad) : void 0;
|
|
4762
|
+
return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4765
|
+
// src/encryption/invocation.ts
|
|
4766
|
+
function buildCanonicalDecryptRequest(input) {
|
|
4767
|
+
const canonicalBody = canonicalize(input.body);
|
|
4768
|
+
const bodyHash = canonicalHashHex(
|
|
4769
|
+
input.crypto.sha256,
|
|
4770
|
+
input.body
|
|
4771
|
+
);
|
|
4772
|
+
const receiverPublicKeyHash = canonicalHashHex(
|
|
4773
|
+
input.crypto.sha256,
|
|
4774
|
+
input.body.receiverPublicKey
|
|
4775
|
+
);
|
|
4776
|
+
return { canonicalBody, bodyHash, receiverPublicKeyHash };
|
|
4777
|
+
}
|
|
4778
|
+
function buildDecryptFacts(input) {
|
|
4779
|
+
const bodyHash = input.canonicalBody !== void 0 ? hexEncode2(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
|
|
4780
|
+
input.crypto.sha256,
|
|
4781
|
+
input.body
|
|
4782
|
+
);
|
|
4783
|
+
const receiverPublicKeyHash = canonicalHashHex(
|
|
4784
|
+
input.crypto.sha256,
|
|
4785
|
+
input.body.receiverPublicKey
|
|
4786
|
+
);
|
|
4787
|
+
return {
|
|
4788
|
+
type: DECRYPT_FACT_TYPE,
|
|
4789
|
+
targetNode: input.body.targetNode,
|
|
4790
|
+
networkId: input.body.networkId,
|
|
4791
|
+
bodyHash,
|
|
4792
|
+
encryptedSymmetricKeyHash: input.encryptedSymmetricKeyHash,
|
|
4793
|
+
receiverPublicKeyHash,
|
|
4794
|
+
alg: input.body.alg,
|
|
4795
|
+
keyVersion: input.body.keyVersion
|
|
4796
|
+
};
|
|
4797
|
+
}
|
|
4798
|
+
function buildDecryptAttenuation(networkId) {
|
|
4799
|
+
parseNetworkId(networkId);
|
|
4800
|
+
return {
|
|
4801
|
+
[networkId]: {
|
|
4802
|
+
[DECRYPT_ACTION]: {}
|
|
4803
|
+
}
|
|
4804
|
+
};
|
|
4805
|
+
}
|
|
4806
|
+
function checkDecryptInvocationInput(crypto2, input) {
|
|
4807
|
+
if (input.body.type !== DECRYPT_FACT_TYPE) {
|
|
4808
|
+
return {
|
|
4809
|
+
ok: false,
|
|
4810
|
+
error: encryptionError({
|
|
4811
|
+
code: "INVALID_INPUT",
|
|
4812
|
+
message: `body.type must be ${DECRYPT_FACT_TYPE}`
|
|
4813
|
+
})
|
|
4814
|
+
};
|
|
4815
|
+
}
|
|
4816
|
+
if (input.facts.type !== DECRYPT_FACT_TYPE) {
|
|
4817
|
+
return {
|
|
4818
|
+
ok: false,
|
|
4819
|
+
error: encryptionError({
|
|
4820
|
+
code: "INVALID_INPUT",
|
|
4821
|
+
message: `facts.type must be ${DECRYPT_FACT_TYPE}`
|
|
4822
|
+
})
|
|
4823
|
+
};
|
|
4824
|
+
}
|
|
4825
|
+
if (input.facts.targetNode !== input.targetNode) {
|
|
4826
|
+
return {
|
|
4827
|
+
ok: false,
|
|
4828
|
+
error: encryptionError({
|
|
4829
|
+
code: "INVALID_INPUT",
|
|
4830
|
+
message: "facts.targetNode must equal targetNode \u2014 the UCAN audience binds the request to a single node"
|
|
4831
|
+
})
|
|
4832
|
+
};
|
|
4833
|
+
}
|
|
4834
|
+
if (input.body.targetNode !== input.targetNode) {
|
|
4835
|
+
return {
|
|
4836
|
+
ok: false,
|
|
4837
|
+
error: encryptionError({
|
|
4838
|
+
code: "INVALID_INPUT",
|
|
4839
|
+
message: "body.targetNode must equal targetNode"
|
|
4840
|
+
})
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
if (input.facts.networkId !== input.networkId) {
|
|
4844
|
+
return {
|
|
4845
|
+
ok: false,
|
|
4846
|
+
error: encryptionError({
|
|
4847
|
+
code: "INVALID_INPUT",
|
|
4848
|
+
message: "facts.networkId must equal networkId"
|
|
4849
|
+
})
|
|
4850
|
+
};
|
|
4851
|
+
}
|
|
4852
|
+
if (input.body.networkId !== input.networkId) {
|
|
4853
|
+
return {
|
|
4854
|
+
ok: false,
|
|
4855
|
+
error: encryptionError({
|
|
4856
|
+
code: "INVALID_INPUT",
|
|
4857
|
+
message: "body.networkId must equal networkId"
|
|
4858
|
+
})
|
|
4859
|
+
};
|
|
4860
|
+
}
|
|
4861
|
+
if (input.facts.alg !== input.body.alg) {
|
|
4862
|
+
return {
|
|
4863
|
+
ok: false,
|
|
4864
|
+
error: encryptionError({
|
|
4865
|
+
code: "INVALID_INPUT",
|
|
4866
|
+
message: "facts.alg must equal body.alg"
|
|
4867
|
+
})
|
|
4868
|
+
};
|
|
4869
|
+
}
|
|
4870
|
+
if (input.facts.keyVersion !== input.body.keyVersion) {
|
|
4871
|
+
return {
|
|
4872
|
+
ok: false,
|
|
4873
|
+
error: encryptionError({
|
|
4874
|
+
code: "INVALID_INPUT",
|
|
4875
|
+
message: "facts.keyVersion must equal body.keyVersion"
|
|
4876
|
+
})
|
|
4877
|
+
};
|
|
4878
|
+
}
|
|
4879
|
+
if (input.facts.encryptedSymmetricKeyHash !== input.body.encryptedSymmetricKeyHash) {
|
|
4880
|
+
return {
|
|
4881
|
+
ok: false,
|
|
4882
|
+
error: encryptionError({
|
|
4883
|
+
code: "INVALID_INPUT",
|
|
4884
|
+
message: "facts.encryptedSymmetricKeyHash must equal body.encryptedSymmetricKeyHash"
|
|
4885
|
+
})
|
|
4886
|
+
};
|
|
4887
|
+
}
|
|
4888
|
+
if (input.facts.receiverPublicKeyHash !== input.body.receiverPublicKeyHash) {
|
|
4889
|
+
return {
|
|
4890
|
+
ok: false,
|
|
4891
|
+
error: encryptionError({
|
|
4892
|
+
code: "INVALID_INPUT",
|
|
4893
|
+
message: "facts.receiverPublicKeyHash must equal body.receiverPublicKeyHash"
|
|
4894
|
+
})
|
|
4895
|
+
};
|
|
4896
|
+
}
|
|
4897
|
+
try {
|
|
4898
|
+
parseNetworkId(input.networkId);
|
|
4899
|
+
} catch (err3) {
|
|
4900
|
+
return {
|
|
4901
|
+
ok: false,
|
|
4902
|
+
error: encryptionError({
|
|
4903
|
+
code: "INVALID_NETWORK_ID",
|
|
4904
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
4905
|
+
})
|
|
4906
|
+
};
|
|
4907
|
+
}
|
|
4908
|
+
const canonicalBody = canonicalize(
|
|
4909
|
+
input.body
|
|
4910
|
+
);
|
|
4911
|
+
const expectedBodyHash = canonicalHashHex(crypto2.sha256, input.body);
|
|
4912
|
+
if (expectedBodyHash !== input.facts.bodyHash) {
|
|
4913
|
+
return {
|
|
4914
|
+
ok: false,
|
|
4915
|
+
error: encryptionError({
|
|
4916
|
+
code: "INVALID_INPUT",
|
|
4917
|
+
message: "facts.bodyHash does not match the canonical body hash"
|
|
4918
|
+
})
|
|
4919
|
+
};
|
|
4920
|
+
}
|
|
4921
|
+
return { ok: true, data: input, canonicalBody };
|
|
4922
|
+
}
|
|
4923
|
+
async function buildDecryptInvocation(crypto2, signer, input) {
|
|
4924
|
+
const checked = checkDecryptInvocationInput(crypto2, input);
|
|
4925
|
+
if (!checked.ok) {
|
|
4926
|
+
return checked;
|
|
4927
|
+
}
|
|
4928
|
+
try {
|
|
4929
|
+
const built = await signer.signDecryptInvocation(checked.data);
|
|
4930
|
+
if (!built.authorization || !built.invocationCid) {
|
|
4931
|
+
return {
|
|
4932
|
+
ok: false,
|
|
4933
|
+
error: encryptionError({
|
|
4934
|
+
code: "INVALID_INPUT",
|
|
4935
|
+
message: "decrypt-invocation signer returned an empty authorization or invocationCid"
|
|
4936
|
+
})
|
|
4937
|
+
};
|
|
4938
|
+
}
|
|
4939
|
+
if (built.canonicalBody !== checked.canonicalBody) {
|
|
4940
|
+
return {
|
|
4941
|
+
ok: false,
|
|
4942
|
+
error: encryptionError({
|
|
4943
|
+
code: "INVALID_INPUT",
|
|
4944
|
+
message: "decrypt-invocation signer returned a canonicalBody that does not match the SDK's canonicalization \u2014 signer must use the SDK-provided body"
|
|
4945
|
+
})
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
return { ok: true, data: built };
|
|
4949
|
+
} catch (err3) {
|
|
4950
|
+
return {
|
|
4951
|
+
ok: false,
|
|
4952
|
+
error: encryptionError({
|
|
4953
|
+
code: "TRANSPORT_ERROR",
|
|
4954
|
+
cause: err3 instanceof Error ? err3 : new Error(String(err3)),
|
|
4955
|
+
message: `failed to sign decrypt invocation: ${err3 instanceof Error ? err3.message : String(err3)}`
|
|
4956
|
+
})
|
|
4957
|
+
};
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
|
|
4961
|
+
// src/encryption/receiverKey.ts
|
|
4962
|
+
function generateRandomReceiverKey(input) {
|
|
4963
|
+
const seed = input.crypto.randomBytes(32);
|
|
4964
|
+
return input.crypto.x25519FromSeed(seed);
|
|
4965
|
+
}
|
|
4966
|
+
async function deriveSignedReceiverKey(input) {
|
|
4967
|
+
const message = `tinycloud.encryption.receiver-key/v1:${input.networkId}:${input.context ?? ""}`;
|
|
4968
|
+
const sig = await input.signer.signMessage(message);
|
|
4969
|
+
const sigBytes = utf8Encode(sig);
|
|
4970
|
+
const seed = input.crypto.sha256(sigBytes);
|
|
4971
|
+
return input.crypto.x25519FromSeed(seed);
|
|
4972
|
+
}
|
|
4973
|
+
|
|
4974
|
+
// src/encryption/response.ts
|
|
4975
|
+
function canonicalSignedResponse(response) {
|
|
4976
|
+
const { nodeSignature: _drop, ...rest } = response;
|
|
4977
|
+
return canonicalize(rest);
|
|
4978
|
+
}
|
|
4979
|
+
function verifyDecryptResponse(input) {
|
|
4980
|
+
const { crypto: crypto2, request, facts, invocationCid, requestBodyHash, response } = input;
|
|
4981
|
+
if (response.type !== DECRYPT_RESULT_TYPE) {
|
|
4982
|
+
return {
|
|
4983
|
+
ok: false,
|
|
4984
|
+
error: encryptionError({
|
|
4985
|
+
code: "INVALID_RESPONSE",
|
|
4986
|
+
message: `response.type must be ${DECRYPT_RESULT_TYPE}`
|
|
4987
|
+
})
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
if (response.targetNode !== request.targetNode) {
|
|
4991
|
+
return {
|
|
4992
|
+
ok: false,
|
|
4993
|
+
error: encryptionError({
|
|
4994
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
4995
|
+
field: "targetNode"
|
|
4996
|
+
})
|
|
4997
|
+
};
|
|
4998
|
+
}
|
|
4999
|
+
if (response.networkId !== request.networkId) {
|
|
5000
|
+
return {
|
|
5001
|
+
ok: false,
|
|
5002
|
+
error: encryptionError({
|
|
5003
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5004
|
+
field: "networkId"
|
|
5005
|
+
})
|
|
5006
|
+
};
|
|
5007
|
+
}
|
|
5008
|
+
if (response.alg !== request.alg) {
|
|
5009
|
+
return {
|
|
5010
|
+
ok: false,
|
|
5011
|
+
error: encryptionError({
|
|
5012
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5013
|
+
field: "alg"
|
|
5014
|
+
})
|
|
5015
|
+
};
|
|
5016
|
+
}
|
|
5017
|
+
if (response.keyVersion !== request.keyVersion) {
|
|
5018
|
+
return {
|
|
5019
|
+
ok: false,
|
|
5020
|
+
error: encryptionError({
|
|
5021
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5022
|
+
field: "keyVersion"
|
|
5023
|
+
})
|
|
5024
|
+
};
|
|
5025
|
+
}
|
|
5026
|
+
if (response.encryptedSymmetricKeyHash !== request.encryptedSymmetricKeyHash) {
|
|
5027
|
+
return {
|
|
5028
|
+
ok: false,
|
|
5029
|
+
error: encryptionError({
|
|
5030
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5031
|
+
field: "encryptedSymmetricKeyHash"
|
|
5032
|
+
})
|
|
5033
|
+
};
|
|
5034
|
+
}
|
|
5035
|
+
if (response.receiverPublicKeyHash !== request.receiverPublicKeyHash) {
|
|
5036
|
+
return {
|
|
5037
|
+
ok: false,
|
|
5038
|
+
error: encryptionError({
|
|
5039
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5040
|
+
field: "receiverPublicKeyHash"
|
|
5041
|
+
})
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
if (response.invocationCid !== invocationCid) {
|
|
5045
|
+
return {
|
|
5046
|
+
ok: false,
|
|
5047
|
+
error: encryptionError({
|
|
5048
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5049
|
+
field: "invocationCid"
|
|
5050
|
+
})
|
|
5051
|
+
};
|
|
5052
|
+
}
|
|
5053
|
+
const expectedRequestHash = hexEncode2(
|
|
5054
|
+
crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
|
|
5055
|
+
);
|
|
5056
|
+
if (response.requestHash !== expectedRequestHash) {
|
|
5057
|
+
return {
|
|
5058
|
+
ok: false,
|
|
5059
|
+
error: encryptionError({
|
|
5060
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5061
|
+
field: "requestHash"
|
|
5062
|
+
})
|
|
5063
|
+
};
|
|
5064
|
+
}
|
|
5065
|
+
if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== response.alg || facts.keyVersion !== response.keyVersion) {
|
|
5066
|
+
return {
|
|
5067
|
+
ok: false,
|
|
5068
|
+
error: encryptionError({
|
|
5069
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5070
|
+
field: "facts"
|
|
5071
|
+
})
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
5074
|
+
const signedBytes = new TextEncoder().encode(
|
|
5075
|
+
canonicalSignedResponse(response)
|
|
5076
|
+
);
|
|
5077
|
+
const signatureBytes = base64Decode2(response.nodeSignature);
|
|
5078
|
+
if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
|
|
5079
|
+
return {
|
|
5080
|
+
ok: false,
|
|
5081
|
+
error: encryptionError({
|
|
5082
|
+
code: "RESPONSE_SIGNATURE_INVALID"
|
|
5083
|
+
})
|
|
5084
|
+
};
|
|
5085
|
+
}
|
|
5086
|
+
return { ok: true, data: response };
|
|
5087
|
+
}
|
|
5088
|
+
function openWrappedKey(crypto2, receiverPrivateKey, response) {
|
|
5089
|
+
const wrapped = base64Decode2(response.wrappedKey);
|
|
5090
|
+
return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
5091
|
+
}
|
|
5092
|
+
|
|
5093
|
+
// src/encryption/EncryptionService.ts
|
|
5094
|
+
function encOk(data) {
|
|
5095
|
+
return { ok: true, data };
|
|
5096
|
+
}
|
|
5097
|
+
function encErr(error) {
|
|
5098
|
+
return { ok: false, error };
|
|
5099
|
+
}
|
|
5100
|
+
var EncryptionService = class extends BaseService {
|
|
5101
|
+
constructor(config) {
|
|
5102
|
+
super();
|
|
5103
|
+
this._config = config;
|
|
5104
|
+
}
|
|
5105
|
+
get config() {
|
|
5106
|
+
return this._config;
|
|
5107
|
+
}
|
|
5108
|
+
get crypto() {
|
|
5109
|
+
return this._config.crypto;
|
|
5110
|
+
}
|
|
5111
|
+
async discoverNetwork(identifier, principal) {
|
|
5112
|
+
const result = await discoverNetwork({
|
|
5113
|
+
identifier,
|
|
5114
|
+
...principal !== void 0 ? { principal } : {},
|
|
5115
|
+
...this._config.node !== void 0 ? { node: this._config.node } : {},
|
|
5116
|
+
...this._config.wellKnown !== void 0 ? { wellKnown: this._config.wellKnown } : {}
|
|
5117
|
+
});
|
|
5118
|
+
if (!result.ok) return result;
|
|
5119
|
+
return encOk(result.data.descriptor);
|
|
5120
|
+
}
|
|
5121
|
+
async encryptToNetwork(networkId, plaintext, options) {
|
|
5122
|
+
try {
|
|
5123
|
+
const discovered = await this.discoverNetwork(networkId);
|
|
5124
|
+
if (!discovered.ok) return discovered;
|
|
5125
|
+
const usable = ensureNetworkUsableForDecrypt(discovered.data);
|
|
5126
|
+
if (!usable.ok) return usable;
|
|
5127
|
+
const descriptor = usable.data;
|
|
5128
|
+
const networkPublicKey = base64Decode2(descriptor.publicEncryptionKey);
|
|
5129
|
+
const result = encryptToNetwork(this.crypto, {
|
|
5130
|
+
networkId,
|
|
5131
|
+
networkPublicKey,
|
|
5132
|
+
plaintext,
|
|
5133
|
+
...options?.aad !== void 0 ? { aad: options.aad } : {},
|
|
5134
|
+
alg: options?.alg ?? descriptor.alg,
|
|
5135
|
+
keyVersion: options?.keyVersion ?? descriptor.keyVersion,
|
|
5136
|
+
...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
|
|
5137
|
+
});
|
|
5138
|
+
return encOk(result.envelope);
|
|
5139
|
+
} catch (error) {
|
|
5140
|
+
return encErr(
|
|
5141
|
+
encryptionError({
|
|
5142
|
+
code: "TRANSPORT_ERROR",
|
|
5143
|
+
cause: toError2(error)
|
|
5144
|
+
})
|
|
5145
|
+
);
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
async decryptEnvelope(envelope, capabilityProof, options) {
|
|
5149
|
+
try {
|
|
5150
|
+
const validated = validateEnvelope(this.crypto, envelope);
|
|
5151
|
+
if (!validated.ok) return validated;
|
|
5152
|
+
let descriptor;
|
|
5153
|
+
if (options?.descriptor !== void 0) {
|
|
5154
|
+
descriptor = options.descriptor;
|
|
5155
|
+
} else {
|
|
5156
|
+
const discovered = await this.discoverNetwork(envelope.networkId);
|
|
5157
|
+
if (!discovered.ok) return discovered;
|
|
5158
|
+
descriptor = discovered.data;
|
|
5159
|
+
}
|
|
5160
|
+
const usable = ensureNetworkUsableForDecrypt(descriptor);
|
|
5161
|
+
if (!usable.ok) return usable;
|
|
5162
|
+
const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
5163
|
+
if (targetNode === void 0) {
|
|
5164
|
+
return encErr(
|
|
5165
|
+
encryptionError({
|
|
5166
|
+
code: "INVALID_INPUT",
|
|
5167
|
+
message: "no target node available from descriptor"
|
|
5168
|
+
})
|
|
5169
|
+
);
|
|
5170
|
+
}
|
|
5171
|
+
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
5172
|
+
const receiverPublicKey = base64Encode2(receiverKey.publicKey);
|
|
5173
|
+
const receiverPublicKeyHash = canonicalHashHex(
|
|
5174
|
+
this.crypto.sha256,
|
|
5175
|
+
receiverPublicKey
|
|
5176
|
+
);
|
|
5177
|
+
const body = {
|
|
5178
|
+
type: DECRYPT_FACT_TYPE,
|
|
5179
|
+
targetNode,
|
|
5180
|
+
networkId: envelope.networkId,
|
|
5181
|
+
alg: envelope.alg,
|
|
5182
|
+
keyVersion: envelope.keyVersion,
|
|
5183
|
+
encryptedSymmetricKey: envelope.encryptedSymmetricKey,
|
|
5184
|
+
encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,
|
|
5185
|
+
receiverPublicKey,
|
|
5186
|
+
receiverPublicKeyHash
|
|
5187
|
+
};
|
|
5188
|
+
const canonicalRequest = buildCanonicalDecryptRequest({
|
|
5189
|
+
crypto: this.crypto,
|
|
5190
|
+
body,
|
|
5191
|
+
receiverPublicKey: receiverKey.publicKey
|
|
5192
|
+
});
|
|
5193
|
+
const facts = buildDecryptFacts({
|
|
5194
|
+
crypto: this.crypto,
|
|
5195
|
+
body,
|
|
5196
|
+
encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,
|
|
5197
|
+
receiverPublicKey: receiverKey.publicKey,
|
|
5198
|
+
canonicalBody: canonicalRequest.canonicalBody
|
|
5199
|
+
});
|
|
5200
|
+
const built = await buildDecryptInvocation(this.crypto, this._config.signer, {
|
|
5201
|
+
targetNode,
|
|
5202
|
+
networkId: envelope.networkId,
|
|
5203
|
+
body,
|
|
5204
|
+
facts,
|
|
5205
|
+
proof: capabilityProof
|
|
5206
|
+
});
|
|
5207
|
+
if (!built.ok) return built;
|
|
5208
|
+
let response;
|
|
5209
|
+
try {
|
|
5210
|
+
response = await this._config.transport.postDecrypt({
|
|
5211
|
+
targetNode,
|
|
5212
|
+
networkId: envelope.networkId,
|
|
5213
|
+
authorization: built.data.authorization,
|
|
5214
|
+
canonicalBody: built.data.canonicalBody
|
|
5215
|
+
});
|
|
5216
|
+
} catch (error) {
|
|
5217
|
+
return encErr(
|
|
5218
|
+
encryptionError({
|
|
5219
|
+
code: "TRANSPORT_ERROR",
|
|
5220
|
+
cause: toError2(error)
|
|
5221
|
+
})
|
|
5222
|
+
);
|
|
5223
|
+
}
|
|
5224
|
+
const verified = verifyDecryptResponse({
|
|
5225
|
+
crypto: this.crypto,
|
|
5226
|
+
request: body,
|
|
5227
|
+
facts,
|
|
5228
|
+
invocationCid: built.data.invocationCid,
|
|
5229
|
+
requestBodyHash: facts.bodyHash,
|
|
5230
|
+
response
|
|
5231
|
+
});
|
|
5232
|
+
if (!verified.ok) return verified;
|
|
5233
|
+
const symmetricKey = openWrappedKey(
|
|
5234
|
+
this.crypto,
|
|
5235
|
+
receiverKey.privateKey,
|
|
5236
|
+
verified.data
|
|
5237
|
+
);
|
|
5238
|
+
const plaintext = decryptEnvelopeWithKey(
|
|
5239
|
+
this.crypto,
|
|
5240
|
+
envelope,
|
|
5241
|
+
symmetricKey
|
|
5242
|
+
);
|
|
5243
|
+
return encOk(plaintext);
|
|
5244
|
+
} catch (error) {
|
|
5245
|
+
return encErr(
|
|
5246
|
+
encryptionError({
|
|
5247
|
+
code: "TRANSPORT_ERROR",
|
|
5248
|
+
cause: toError2(error)
|
|
5249
|
+
})
|
|
5250
|
+
);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
};
|
|
5254
|
+
EncryptionService.serviceName = "encryption";
|
|
3836
5255
|
export {
|
|
3837
5256
|
BaseService,
|
|
5257
|
+
DECRYPT_ACTION,
|
|
5258
|
+
DECRYPT_FACT_TYPE,
|
|
5259
|
+
DECRYPT_RESULT_TYPE,
|
|
5260
|
+
DEFAULT_ENCRYPTION_ALG,
|
|
5261
|
+
DEFAULT_KEY_VERSION,
|
|
5262
|
+
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
3838
5263
|
DataVaultService,
|
|
3839
5264
|
DatabaseHandle,
|
|
3840
5265
|
DuckDbAction,
|
|
3841
5266
|
DuckDbDatabaseHandle,
|
|
3842
5267
|
DuckDbService,
|
|
5268
|
+
ENCRYPTION_NETWORK_URN_PREFIX,
|
|
5269
|
+
ENCRYPTION_SERVICE,
|
|
5270
|
+
ENCRYPTION_SERVICE_SHORT,
|
|
5271
|
+
ENVELOPE_VERSION,
|
|
5272
|
+
EncryptionService,
|
|
3843
5273
|
ErrorCodes,
|
|
3844
5274
|
GenericKVResponseSchema,
|
|
3845
5275
|
GenericResultSchema,
|
|
@@ -3849,8 +5279,11 @@ export {
|
|
|
3849
5279
|
KVListResultSchema,
|
|
3850
5280
|
KVResponseHeadersSchema,
|
|
3851
5281
|
KVService,
|
|
5282
|
+
NETWORK_NAME_PATTERN,
|
|
5283
|
+
NetworkIdError,
|
|
3852
5284
|
PrefixedKVService,
|
|
3853
5285
|
RetryPolicySchema,
|
|
5286
|
+
SECRET_NAME_RE,
|
|
3854
5287
|
SQLAction,
|
|
3855
5288
|
SQLService,
|
|
3856
5289
|
SecretsService,
|
|
@@ -3869,21 +5302,51 @@ export {
|
|
|
3869
5302
|
authExpiredError,
|
|
3870
5303
|
authRequiredError,
|
|
3871
5304
|
authUnauthorizedError,
|
|
5305
|
+
base64Decode2 as base64Decode,
|
|
5306
|
+
base64Encode2 as base64Encode,
|
|
5307
|
+
buildCanonicalDecryptRequest,
|
|
5308
|
+
buildDecryptAttenuation,
|
|
5309
|
+
buildDecryptFacts,
|
|
5310
|
+
buildDecryptInvocation,
|
|
5311
|
+
buildNetworkId,
|
|
5312
|
+
canonicalHashHex,
|
|
5313
|
+
canonicalSignedResponse,
|
|
5314
|
+
canonicalize as canonicalizeEncryptionJson,
|
|
5315
|
+
canonicalizeSecretScope,
|
|
5316
|
+
checkDecryptInvocationInput,
|
|
3872
5317
|
createKVResponseSchema,
|
|
3873
5318
|
createResultSchema,
|
|
3874
5319
|
createVaultCrypto,
|
|
5320
|
+
decryptEnvelopeWithKey,
|
|
3875
5321
|
defaultRetryPolicy,
|
|
5322
|
+
deriveSignedReceiverKey,
|
|
5323
|
+
discoverNetwork,
|
|
5324
|
+
encryptToNetwork,
|
|
5325
|
+
encryptionError,
|
|
5326
|
+
ensureNetworkUsableForDecrypt,
|
|
3876
5327
|
err,
|
|
3877
5328
|
errorResult,
|
|
5329
|
+
generateRandomReceiverKey,
|
|
5330
|
+
hexDecode,
|
|
5331
|
+
hexEncode2 as hexEncode,
|
|
5332
|
+
isNetworkId,
|
|
5333
|
+
networkDiscoveryKey,
|
|
3878
5334
|
networkError,
|
|
3879
5335
|
notFoundError,
|
|
3880
5336
|
ok,
|
|
5337
|
+
openWrappedKey,
|
|
3881
5338
|
parseAuthError,
|
|
5339
|
+
parseNetworkId,
|
|
3882
5340
|
permissionDeniedError,
|
|
5341
|
+
resolveSecretListPrefix,
|
|
5342
|
+
resolveSecretPath,
|
|
3883
5343
|
serviceError,
|
|
3884
5344
|
storageLimitReachedError,
|
|
3885
5345
|
storageQuotaExceededError,
|
|
3886
5346
|
timeoutError,
|
|
5347
|
+
utf8Decode,
|
|
5348
|
+
utf8Encode,
|
|
5349
|
+
validateEnvelope,
|
|
3887
5350
|
validateKVListResponse,
|
|
3888
5351
|
validateKVResponseHeaders,
|
|
3889
5352
|
validateRetryPolicy,
|
|
@@ -3891,6 +5354,7 @@ export {
|
|
|
3891
5354
|
validateServiceRequestEvent,
|
|
3892
5355
|
validateServiceResponseEvent,
|
|
3893
5356
|
validateServiceSession,
|
|
5357
|
+
verifyDecryptResponse,
|
|
3894
5358
|
wrapError
|
|
3895
5359
|
};
|
|
3896
5360
|
//# sourceMappingURL=index.js.map
|