@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.cjs
CHANGED
|
@@ -21,11 +21,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BaseService: () => BaseService,
|
|
24
|
+
DECRYPT_ACTION: () => DECRYPT_ACTION,
|
|
25
|
+
DECRYPT_FACT_TYPE: () => DECRYPT_FACT_TYPE,
|
|
26
|
+
DECRYPT_RESULT_TYPE: () => DECRYPT_RESULT_TYPE,
|
|
27
|
+
DEFAULT_ENCRYPTION_ALG: () => DEFAULT_ENCRYPTION_ALG,
|
|
28
|
+
DEFAULT_KEY_VERSION: () => DEFAULT_KEY_VERSION,
|
|
29
|
+
DEFAULT_SIGNED_READ_URL_EXPIRY_MS: () => DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
24
30
|
DataVaultService: () => DataVaultService,
|
|
25
31
|
DatabaseHandle: () => DatabaseHandle,
|
|
26
32
|
DuckDbAction: () => DuckDbAction,
|
|
27
33
|
DuckDbDatabaseHandle: () => DuckDbDatabaseHandle,
|
|
28
34
|
DuckDbService: () => DuckDbService,
|
|
35
|
+
ENCRYPTION_NETWORK_URN_PREFIX: () => ENCRYPTION_NETWORK_URN_PREFIX,
|
|
36
|
+
ENCRYPTION_SERVICE: () => ENCRYPTION_SERVICE,
|
|
37
|
+
ENCRYPTION_SERVICE_SHORT: () => ENCRYPTION_SERVICE_SHORT,
|
|
38
|
+
ENVELOPE_VERSION: () => ENVELOPE_VERSION,
|
|
39
|
+
EncryptionService: () => EncryptionService,
|
|
29
40
|
ErrorCodes: () => ErrorCodes,
|
|
30
41
|
GenericKVResponseSchema: () => GenericKVResponseSchema,
|
|
31
42
|
GenericResultSchema: () => GenericResultSchema,
|
|
@@ -35,8 +46,11 @@ __export(index_exports, {
|
|
|
35
46
|
KVListResultSchema: () => KVListResultSchema,
|
|
36
47
|
KVResponseHeadersSchema: () => KVResponseHeadersSchema,
|
|
37
48
|
KVService: () => KVService,
|
|
49
|
+
NETWORK_NAME_PATTERN: () => NETWORK_NAME_PATTERN,
|
|
50
|
+
NetworkIdError: () => NetworkIdError,
|
|
38
51
|
PrefixedKVService: () => PrefixedKVService,
|
|
39
52
|
RetryPolicySchema: () => RetryPolicySchema,
|
|
53
|
+
SECRET_NAME_RE: () => SECRET_NAME_RE,
|
|
40
54
|
SQLAction: () => SQLAction,
|
|
41
55
|
SQLService: () => SQLService,
|
|
42
56
|
SecretsService: () => SecretsService,
|
|
@@ -55,21 +69,51 @@ __export(index_exports, {
|
|
|
55
69
|
authExpiredError: () => authExpiredError,
|
|
56
70
|
authRequiredError: () => authRequiredError,
|
|
57
71
|
authUnauthorizedError: () => authUnauthorizedError,
|
|
72
|
+
base64Decode: () => base64Decode2,
|
|
73
|
+
base64Encode: () => base64Encode2,
|
|
74
|
+
buildCanonicalDecryptRequest: () => buildCanonicalDecryptRequest,
|
|
75
|
+
buildDecryptAttenuation: () => buildDecryptAttenuation,
|
|
76
|
+
buildDecryptFacts: () => buildDecryptFacts,
|
|
77
|
+
buildDecryptInvocation: () => buildDecryptInvocation,
|
|
78
|
+
buildNetworkId: () => buildNetworkId,
|
|
79
|
+
canonicalHashHex: () => canonicalHashHex,
|
|
80
|
+
canonicalSignedResponse: () => canonicalSignedResponse,
|
|
81
|
+
canonicalizeEncryptionJson: () => canonicalize,
|
|
82
|
+
canonicalizeSecretScope: () => canonicalizeSecretScope,
|
|
83
|
+
checkDecryptInvocationInput: () => checkDecryptInvocationInput,
|
|
58
84
|
createKVResponseSchema: () => createKVResponseSchema,
|
|
59
85
|
createResultSchema: () => createResultSchema,
|
|
60
86
|
createVaultCrypto: () => createVaultCrypto,
|
|
87
|
+
decryptEnvelopeWithKey: () => decryptEnvelopeWithKey,
|
|
61
88
|
defaultRetryPolicy: () => defaultRetryPolicy,
|
|
89
|
+
deriveSignedReceiverKey: () => deriveSignedReceiverKey,
|
|
90
|
+
discoverNetwork: () => discoverNetwork,
|
|
91
|
+
encryptToNetwork: () => encryptToNetwork,
|
|
92
|
+
encryptionError: () => encryptionError,
|
|
93
|
+
ensureNetworkUsableForDecrypt: () => ensureNetworkUsableForDecrypt,
|
|
62
94
|
err: () => err,
|
|
63
95
|
errorResult: () => errorResult,
|
|
96
|
+
generateRandomReceiverKey: () => generateRandomReceiverKey,
|
|
97
|
+
hexDecode: () => hexDecode,
|
|
98
|
+
hexEncode: () => hexEncode2,
|
|
99
|
+
isNetworkId: () => isNetworkId,
|
|
100
|
+
networkDiscoveryKey: () => networkDiscoveryKey,
|
|
64
101
|
networkError: () => networkError,
|
|
65
102
|
notFoundError: () => notFoundError,
|
|
66
103
|
ok: () => ok,
|
|
104
|
+
openWrappedKey: () => openWrappedKey,
|
|
67
105
|
parseAuthError: () => parseAuthError,
|
|
106
|
+
parseNetworkId: () => parseNetworkId,
|
|
68
107
|
permissionDeniedError: () => permissionDeniedError,
|
|
108
|
+
resolveSecretListPrefix: () => resolveSecretListPrefix,
|
|
109
|
+
resolveSecretPath: () => resolveSecretPath,
|
|
69
110
|
serviceError: () => serviceError,
|
|
70
111
|
storageLimitReachedError: () => storageLimitReachedError,
|
|
71
112
|
storageQuotaExceededError: () => storageQuotaExceededError,
|
|
72
113
|
timeoutError: () => timeoutError,
|
|
114
|
+
utf8Decode: () => utf8Decode,
|
|
115
|
+
utf8Encode: () => utf8Encode,
|
|
116
|
+
validateEnvelope: () => validateEnvelope,
|
|
73
117
|
validateKVListResponse: () => validateKVListResponse,
|
|
74
118
|
validateKVResponseHeaders: () => validateKVResponseHeaders,
|
|
75
119
|
validateRetryPolicy: () => validateRetryPolicy,
|
|
@@ -77,6 +121,7 @@ __export(index_exports, {
|
|
|
77
121
|
validateServiceRequestEvent: () => validateServiceRequestEvent,
|
|
78
122
|
validateServiceResponseEvent: () => validateServiceResponseEvent,
|
|
79
123
|
validateServiceSession: () => validateServiceSession,
|
|
124
|
+
verifyDecryptResponse: () => verifyDecryptResponse,
|
|
80
125
|
wrapError: () => wrapError
|
|
81
126
|
});
|
|
82
127
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -901,6 +946,13 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
901
946
|
const fullKey = this.getFullKey(key);
|
|
902
947
|
return this._kv.head(fullKey, { ...options, prefix: "" });
|
|
903
948
|
}
|
|
949
|
+
/**
|
|
950
|
+
* Create a short-lived signed URL for reading a KV object.
|
|
951
|
+
*/
|
|
952
|
+
async createSignedReadUrl(key, options) {
|
|
953
|
+
const fullKey = this.getFullKey(key);
|
|
954
|
+
return this._kv.createSignedReadUrl(fullKey, { ...options, prefix: "" });
|
|
955
|
+
}
|
|
904
956
|
/**
|
|
905
957
|
* Create a nested prefix-scoped view.
|
|
906
958
|
*/
|
|
@@ -912,6 +964,7 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
912
964
|
};
|
|
913
965
|
|
|
914
966
|
// src/kv/types.ts
|
|
967
|
+
var DEFAULT_SIGNED_READ_URL_EXPIRY_MS = 5 * 60 * 1e3;
|
|
915
968
|
var KVAction = {
|
|
916
969
|
GET: "tinycloud.kv/get",
|
|
917
970
|
PUT: "tinycloud.kv/put",
|
|
@@ -996,6 +1049,15 @@ var KVService = class extends BaseService {
|
|
|
996
1049
|
get host() {
|
|
997
1050
|
return this.context.hosts[0];
|
|
998
1051
|
}
|
|
1052
|
+
withJsonContentType(headers) {
|
|
1053
|
+
if (Array.isArray(headers)) {
|
|
1054
|
+
return [...headers, ["content-type", "application/json"]];
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
...headers,
|
|
1058
|
+
"content-type": "application/json"
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
999
1061
|
/**
|
|
1000
1062
|
* Execute an invoke operation.
|
|
1001
1063
|
*
|
|
@@ -1065,6 +1127,48 @@ var KVService = class extends BaseService {
|
|
|
1065
1127
|
return text;
|
|
1066
1128
|
}
|
|
1067
1129
|
}
|
|
1130
|
+
async createSignedReadUrlError(response, key) {
|
|
1131
|
+
let errorText = response.statusText;
|
|
1132
|
+
try {
|
|
1133
|
+
const text = await response.text();
|
|
1134
|
+
if (text) {
|
|
1135
|
+
errorText = text;
|
|
1136
|
+
}
|
|
1137
|
+
} catch {
|
|
1138
|
+
}
|
|
1139
|
+
if (response.status === 401 || response.status === 403) {
|
|
1140
|
+
const { resource, action } = parseAuthError(errorText);
|
|
1141
|
+
return err(authUnauthorizedError("kv", errorText, {
|
|
1142
|
+
status: response.status,
|
|
1143
|
+
...action && { requiredAction: action },
|
|
1144
|
+
...resource && { resource }
|
|
1145
|
+
}));
|
|
1146
|
+
}
|
|
1147
|
+
const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
1148
|
+
return err(
|
|
1149
|
+
serviceError(
|
|
1150
|
+
code,
|
|
1151
|
+
`Failed to create signed read URL for key "${key}": ${response.status} - ${errorText}`,
|
|
1152
|
+
"kv",
|
|
1153
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
normalizeSignedReadUrlResponse(data) {
|
|
1158
|
+
if (!data || typeof data !== "object") {
|
|
1159
|
+
return void 0;
|
|
1160
|
+
}
|
|
1161
|
+
const response = data;
|
|
1162
|
+
if (typeof response.url !== "string" || typeof response.ticketId !== "string" || typeof response.expiresAt !== "string") {
|
|
1163
|
+
return void 0;
|
|
1164
|
+
}
|
|
1165
|
+
return {
|
|
1166
|
+
url: new URL(response.url, this.host).toString(),
|
|
1167
|
+
relativeUrl: response.url,
|
|
1168
|
+
ticketId: response.ticketId,
|
|
1169
|
+
expiresAt: response.expiresAt
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1068
1172
|
/**
|
|
1069
1173
|
* Get a value by key.
|
|
1070
1174
|
*/
|
|
@@ -1337,6 +1441,61 @@ var KVService = class extends BaseService {
|
|
|
1337
1441
|
}
|
|
1338
1442
|
});
|
|
1339
1443
|
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Create a short-lived signed URL for reading a KV object.
|
|
1446
|
+
*/
|
|
1447
|
+
async createSignedReadUrl(key, options) {
|
|
1448
|
+
return this.withTelemetry("createSignedReadUrl", key, async () => {
|
|
1449
|
+
if (!this.requireAuth()) {
|
|
1450
|
+
return err(authRequiredError("kv"));
|
|
1451
|
+
}
|
|
1452
|
+
const path = this.getFullPath(key, options?.prefix);
|
|
1453
|
+
const session = this.context.session;
|
|
1454
|
+
const headers = this.context.invoke(
|
|
1455
|
+
session,
|
|
1456
|
+
"kv",
|
|
1457
|
+
path,
|
|
1458
|
+
KVAction.GET
|
|
1459
|
+
);
|
|
1460
|
+
const body = {
|
|
1461
|
+
space: session.spaceId,
|
|
1462
|
+
path,
|
|
1463
|
+
ttl_seconds: options?.expiresInSeconds ?? Math.ceil(DEFAULT_SIGNED_READ_URL_EXPIRY_MS / 1e3)
|
|
1464
|
+
};
|
|
1465
|
+
if (options?.contentHash !== void 0) {
|
|
1466
|
+
body.content_hash = options.contentHash;
|
|
1467
|
+
}
|
|
1468
|
+
if (options?.etag !== void 0) {
|
|
1469
|
+
body.etag = options.etag;
|
|
1470
|
+
}
|
|
1471
|
+
try {
|
|
1472
|
+
const response = await this.context.fetch(`${this.host}/signed/kv`, {
|
|
1473
|
+
method: "POST",
|
|
1474
|
+
headers: this.withJsonContentType(headers),
|
|
1475
|
+
body: JSON.stringify(body),
|
|
1476
|
+
signal: this.combineSignals(options?.signal)
|
|
1477
|
+
});
|
|
1478
|
+
if (!response.ok) {
|
|
1479
|
+
return this.createSignedReadUrlError(response, key);
|
|
1480
|
+
}
|
|
1481
|
+
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
1482
|
+
await response.json()
|
|
1483
|
+
);
|
|
1484
|
+
if (!signedUrl) {
|
|
1485
|
+
return err(
|
|
1486
|
+
serviceError(
|
|
1487
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1488
|
+
"Signed read URL response did not include url, ticketId, and expiresAt",
|
|
1489
|
+
"kv"
|
|
1490
|
+
)
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
return ok(signedUrl);
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
return err(wrapError("kv", error));
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1340
1499
|
/**
|
|
1341
1500
|
* Create a prefix-scoped view of this KV service.
|
|
1342
1501
|
*
|
|
@@ -2906,6 +3065,15 @@ function base64Decode(str) {
|
|
|
2906
3065
|
}
|
|
2907
3066
|
return bytes;
|
|
2908
3067
|
}
|
|
3068
|
+
function unwrapKVData(value) {
|
|
3069
|
+
if (value !== null && typeof value === "object" && "data" in value) {
|
|
3070
|
+
return value.data;
|
|
3071
|
+
}
|
|
3072
|
+
return value;
|
|
3073
|
+
}
|
|
3074
|
+
function isUnlockSigner(signer) {
|
|
3075
|
+
return typeof signer === "object" && signer !== null && typeof signer.signMessage === "function";
|
|
3076
|
+
}
|
|
2909
3077
|
function defaultVaultMessage(input) {
|
|
2910
3078
|
switch (input.code) {
|
|
2911
3079
|
case "DECRYPTION_FAILED":
|
|
@@ -2943,6 +3111,7 @@ var DataVaultService = class extends BaseService {
|
|
|
2943
3111
|
this.masterKey = null;
|
|
2944
3112
|
this.encryptionIdentity = null;
|
|
2945
3113
|
this._isUnlocked = false;
|
|
3114
|
+
this.unlockInFlight = null;
|
|
2946
3115
|
this.vaultConfig = config;
|
|
2947
3116
|
this._config = config;
|
|
2948
3117
|
}
|
|
@@ -2956,13 +3125,16 @@ var DataVaultService = class extends BaseService {
|
|
|
2956
3125
|
* Whether the vault is currently unlocked.
|
|
2957
3126
|
*/
|
|
2958
3127
|
get isUnlocked() {
|
|
2959
|
-
return this._isUnlocked;
|
|
3128
|
+
return this.usesNetworkEncryption || this._isUnlocked;
|
|
2960
3129
|
}
|
|
2961
3130
|
/**
|
|
2962
3131
|
* The vault's public encryption key (X25519).
|
|
2963
3132
|
* Throws if vault is locked.
|
|
2964
3133
|
*/
|
|
2965
3134
|
get publicKey() {
|
|
3135
|
+
if (this.usesNetworkEncryption) {
|
|
3136
|
+
throw new Error("Network-encrypted vaults do not expose a local public key");
|
|
3137
|
+
}
|
|
2966
3138
|
if (!this.encryptionIdentity) {
|
|
2967
3139
|
throw new Error("Vault is locked");
|
|
2968
3140
|
}
|
|
@@ -2980,12 +3152,51 @@ var DataVaultService = class extends BaseService {
|
|
|
2980
3152
|
get tc() {
|
|
2981
3153
|
return this.vaultConfig.tc;
|
|
2982
3154
|
}
|
|
3155
|
+
get networkEncryption() {
|
|
3156
|
+
return this.vaultConfig.encryption;
|
|
3157
|
+
}
|
|
3158
|
+
get usesNetworkEncryption() {
|
|
3159
|
+
return this.networkEncryption !== void 0;
|
|
3160
|
+
}
|
|
2983
3161
|
/**
|
|
2984
3162
|
* Get the host URL.
|
|
2985
3163
|
*/
|
|
2986
3164
|
get host() {
|
|
2987
3165
|
return this.tc.hosts[0];
|
|
2988
3166
|
}
|
|
3167
|
+
async decryptCapabilityProof() {
|
|
3168
|
+
const proof = this.networkEncryption?.decryptCapabilityProof;
|
|
3169
|
+
if (typeof proof === "function") {
|
|
3170
|
+
return await proof();
|
|
3171
|
+
}
|
|
3172
|
+
return proof ?? { proofs: [] };
|
|
3173
|
+
}
|
|
3174
|
+
serializeValue(value, options) {
|
|
3175
|
+
let plaintext;
|
|
3176
|
+
if (value instanceof Uint8Array) {
|
|
3177
|
+
plaintext = value;
|
|
3178
|
+
} else if (options?.serialize) {
|
|
3179
|
+
plaintext = options.serialize(value);
|
|
3180
|
+
} else if (typeof value === "string") {
|
|
3181
|
+
plaintext = toBytes(value);
|
|
3182
|
+
} else {
|
|
3183
|
+
plaintext = toBytes(JSON.stringify(value));
|
|
3184
|
+
}
|
|
3185
|
+
const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
|
|
3186
|
+
return { plaintext, contentType };
|
|
3187
|
+
}
|
|
3188
|
+
deserializeValue(plaintext, contentType, options) {
|
|
3189
|
+
if (options?.raw) {
|
|
3190
|
+
return plaintext;
|
|
3191
|
+
}
|
|
3192
|
+
if (options?.deserialize) {
|
|
3193
|
+
return options.deserialize(plaintext);
|
|
3194
|
+
}
|
|
3195
|
+
if (contentType === "application/json") {
|
|
3196
|
+
return JSON.parse(fromBytes(plaintext));
|
|
3197
|
+
}
|
|
3198
|
+
return plaintext;
|
|
3199
|
+
}
|
|
2989
3200
|
// =========================================================================
|
|
2990
3201
|
// Phase 1: Core Operations
|
|
2991
3202
|
// =========================================================================
|
|
@@ -3002,30 +3213,44 @@ var DataVaultService = class extends BaseService {
|
|
|
3002
3213
|
* signatures exist (browser only).
|
|
3003
3214
|
*/
|
|
3004
3215
|
async unlock(signer) {
|
|
3005
|
-
|
|
3216
|
+
if (this.usesNetworkEncryption) {
|
|
3217
|
+
this._isUnlocked = true;
|
|
3218
|
+
return { ok: true, data: void 0 };
|
|
3219
|
+
}
|
|
3220
|
+
const unlockSigner = isUnlockSigner(signer) ? signer : void 0;
|
|
3221
|
+
if (this._isUnlocked && this.masterKey && (this.encryptionIdentity || !unlockSigner)) {
|
|
3222
|
+
return { ok: true, data: void 0 };
|
|
3223
|
+
}
|
|
3224
|
+
if (this.unlockInFlight) {
|
|
3225
|
+
return this.unlockInFlight;
|
|
3226
|
+
}
|
|
3227
|
+
this.unlockInFlight = this.withTelemetry("unlock", void 0, async () => {
|
|
3006
3228
|
const spaceId = this.vaultConfig.spaceId;
|
|
3007
3229
|
const versionConfig = VaultVersionConfig[CURRENT_VAULT_VERSION];
|
|
3008
3230
|
const masterCacheKey = `vault-master:${spaceId}`;
|
|
3009
3231
|
const identityCacheKey = `vault-identity:${this.tc.address}`;
|
|
3010
3232
|
try {
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
if (!
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3233
|
+
if (!this.masterKey) {
|
|
3234
|
+
let masterSigBytes = await loadCachedSignature(masterCacheKey);
|
|
3235
|
+
if (!masterSigBytes) {
|
|
3236
|
+
if (!unlockSigner) {
|
|
3237
|
+
return vaultError({
|
|
3238
|
+
code: "VAULT_LOCKED",
|
|
3239
|
+
message: "Signer is required when no cached master signature exists"
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
const sig = await unlockSigner.signMessage(
|
|
3243
|
+
versionConfig.masterMessage(spaceId)
|
|
3244
|
+
);
|
|
3245
|
+
masterSigBytes = toBytes(sig);
|
|
3246
|
+
await cacheSignature(masterCacheKey, masterSigBytes);
|
|
3018
3247
|
}
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
masterSigBytes,
|
|
3026
|
-
this.crypto.sha256(toBytes(spaceId)),
|
|
3027
|
-
toBytes("vault-master")
|
|
3028
|
-
);
|
|
3248
|
+
this.masterKey = this.crypto.deriveKey(
|
|
3249
|
+
masterSigBytes,
|
|
3250
|
+
this.crypto.sha256(toBytes(spaceId)),
|
|
3251
|
+
toBytes("vault-master")
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3029
3254
|
const publicSpaceId = this.tc.makePublicSpaceId(this.tc.address, this.tc.chainId);
|
|
3030
3255
|
let existingPubKey = null;
|
|
3031
3256
|
try {
|
|
@@ -3048,13 +3273,14 @@ var DataVaultService = class extends BaseService {
|
|
|
3048
3273
|
} else {
|
|
3049
3274
|
let identitySigBytes = await loadCachedSignature(identityCacheKey);
|
|
3050
3275
|
if (!identitySigBytes) {
|
|
3051
|
-
if (!
|
|
3276
|
+
if (!unlockSigner) {
|
|
3052
3277
|
this.encryptionIdentity = null;
|
|
3053
3278
|
this._isUnlocked = true;
|
|
3054
3279
|
return ok(void 0);
|
|
3055
3280
|
}
|
|
3056
|
-
const
|
|
3057
|
-
|
|
3281
|
+
const sig = await unlockSigner.signMessage(
|
|
3282
|
+
versionConfig.identityMessage
|
|
3283
|
+
);
|
|
3058
3284
|
identitySigBytes = toBytes(sig);
|
|
3059
3285
|
await cacheSignature(identityCacheKey, identitySigBytes);
|
|
3060
3286
|
}
|
|
@@ -3084,6 +3310,11 @@ var DataVaultService = class extends BaseService {
|
|
|
3084
3310
|
});
|
|
3085
3311
|
}
|
|
3086
3312
|
});
|
|
3313
|
+
try {
|
|
3314
|
+
return await this.unlockInFlight;
|
|
3315
|
+
} finally {
|
|
3316
|
+
this.unlockInFlight = null;
|
|
3317
|
+
}
|
|
3087
3318
|
}
|
|
3088
3319
|
/**
|
|
3089
3320
|
* Clear the cached vault signatures.
|
|
@@ -3112,6 +3343,131 @@ var DataVaultService = class extends BaseService {
|
|
|
3112
3343
|
this.lock();
|
|
3113
3344
|
super.onSignOut();
|
|
3114
3345
|
}
|
|
3346
|
+
async putNetworkEncrypted(key, value, options) {
|
|
3347
|
+
const config = this.networkEncryption;
|
|
3348
|
+
if (!config) {
|
|
3349
|
+
return vaultError({
|
|
3350
|
+
code: "VAULT_LOCKED",
|
|
3351
|
+
message: "Network encryption is not configured"
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
if (!this.requireAuth()) {
|
|
3355
|
+
return vaultError({
|
|
3356
|
+
code: "VAULT_LOCKED",
|
|
3357
|
+
message: "Authentication required"
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
try {
|
|
3361
|
+
const { plaintext, contentType } = this.serializeValue(value, options);
|
|
3362
|
+
const metadata = {
|
|
3363
|
+
[VaultHeaders.VERSION]: "2",
|
|
3364
|
+
[VaultHeaders.CIPHER]: "tinycloud-network-envelope",
|
|
3365
|
+
[VaultHeaders.CONTENT_TYPE]: contentType,
|
|
3366
|
+
...options?.metadata ?? {}
|
|
3367
|
+
};
|
|
3368
|
+
const aad = toBytes(`tinycloud.vault:${this.vaultConfig.spaceId}:${key}`);
|
|
3369
|
+
const envelopeResult = await config.service.encryptToNetwork(
|
|
3370
|
+
config.networkId,
|
|
3371
|
+
plaintext,
|
|
3372
|
+
{ aad, metadata }
|
|
3373
|
+
);
|
|
3374
|
+
if (!envelopeResult.ok) {
|
|
3375
|
+
return vaultError({
|
|
3376
|
+
code: "STORAGE_ERROR",
|
|
3377
|
+
cause: new Error(envelopeResult.error.message)
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
const valuePutResult = await this.tc.kv.put(
|
|
3381
|
+
`vault/${key}`,
|
|
3382
|
+
JSON.stringify(envelopeResult.data)
|
|
3383
|
+
);
|
|
3384
|
+
if (!valuePutResult.ok) {
|
|
3385
|
+
return vaultError({
|
|
3386
|
+
code: "STORAGE_ERROR",
|
|
3387
|
+
cause: new Error(
|
|
3388
|
+
`Failed to store encrypted value: ${valuePutResult.error.message}`
|
|
3389
|
+
)
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
return { ok: true, data: void 0 };
|
|
3393
|
+
} catch (error) {
|
|
3394
|
+
return vaultError({
|
|
3395
|
+
code: "STORAGE_ERROR",
|
|
3396
|
+
cause: toError(error)
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
async getNetworkEncrypted(key, options) {
|
|
3401
|
+
const config = this.networkEncryption;
|
|
3402
|
+
if (!config) {
|
|
3403
|
+
return vaultError({
|
|
3404
|
+
code: "VAULT_LOCKED",
|
|
3405
|
+
message: "Network encryption is not configured"
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
if (!this.requireAuth()) {
|
|
3409
|
+
return vaultError({
|
|
3410
|
+
code: "VAULT_LOCKED",
|
|
3411
|
+
message: "Authentication required"
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
try {
|
|
3415
|
+
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
3416
|
+
raw: true
|
|
3417
|
+
});
|
|
3418
|
+
if (!valueResult.ok) {
|
|
3419
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3420
|
+
}
|
|
3421
|
+
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
3422
|
+
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
3423
|
+
const proof = await this.decryptCapabilityProof();
|
|
3424
|
+
const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
3425
|
+
if (!plaintextResult.ok) {
|
|
3426
|
+
return vaultError({
|
|
3427
|
+
code: "DECRYPTION_FAILED",
|
|
3428
|
+
message: plaintextResult.error.message
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
const metadata = envelope.metadata ?? {};
|
|
3432
|
+
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
3433
|
+
const keyId = metadata[VaultHeaders.KEY_ID] ?? envelope.encryptedSymmetricKeyHash.slice(0, 16);
|
|
3434
|
+
const value = this.deserializeValue(
|
|
3435
|
+
plaintextResult.data,
|
|
3436
|
+
contentType,
|
|
3437
|
+
options
|
|
3438
|
+
);
|
|
3439
|
+
return { ok: true, data: { value, metadata, keyId } };
|
|
3440
|
+
} catch (error) {
|
|
3441
|
+
return vaultError({
|
|
3442
|
+
code: "STORAGE_ERROR",
|
|
3443
|
+
cause: toError(error)
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
async headNetworkEncrypted(key) {
|
|
3448
|
+
if (!this.requireAuth()) {
|
|
3449
|
+
return vaultError({
|
|
3450
|
+
code: "VAULT_LOCKED",
|
|
3451
|
+
message: "Authentication required"
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
try {
|
|
3455
|
+
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
3456
|
+
raw: true
|
|
3457
|
+
});
|
|
3458
|
+
if (!valueResult.ok) {
|
|
3459
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3460
|
+
}
|
|
3461
|
+
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
3462
|
+
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
3463
|
+
return { ok: true, data: envelope.metadata ?? {} };
|
|
3464
|
+
} catch (error) {
|
|
3465
|
+
return vaultError({
|
|
3466
|
+
code: "STORAGE_ERROR",
|
|
3467
|
+
cause: toError(error)
|
|
3468
|
+
});
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3115
3471
|
/**
|
|
3116
3472
|
* Encrypt and store a value at the given key.
|
|
3117
3473
|
*
|
|
@@ -3121,6 +3477,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3121
3477
|
*/
|
|
3122
3478
|
async put(key, value, options) {
|
|
3123
3479
|
return this.withTelemetry("put", key, async () => {
|
|
3480
|
+
if (this.usesNetworkEncryption) {
|
|
3481
|
+
return this.putNetworkEncrypted(key, value, options);
|
|
3482
|
+
}
|
|
3124
3483
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3125
3484
|
return vaultError({
|
|
3126
3485
|
code: "VAULT_LOCKED",
|
|
@@ -3213,6 +3572,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3213
3572
|
*/
|
|
3214
3573
|
async get(key, options) {
|
|
3215
3574
|
return this.withTelemetry("get", key, async () => {
|
|
3575
|
+
if (this.usesNetworkEncryption) {
|
|
3576
|
+
return this.getNetworkEncrypted(key, options);
|
|
3577
|
+
}
|
|
3216
3578
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3217
3579
|
return vaultError({
|
|
3218
3580
|
code: "VAULT_LOCKED",
|
|
@@ -3280,7 +3642,7 @@ var DataVaultService = class extends BaseService {
|
|
|
3280
3642
|
*/
|
|
3281
3643
|
async delete(key) {
|
|
3282
3644
|
return this.withTelemetry("delete", key, async () => {
|
|
3283
|
-
if (!this.
|
|
3645
|
+
if (!this.isUnlocked) {
|
|
3284
3646
|
return vaultError({
|
|
3285
3647
|
code: "VAULT_LOCKED",
|
|
3286
3648
|
message: "Vault must be unlocked before deleting data"
|
|
@@ -3293,6 +3655,13 @@ var DataVaultService = class extends BaseService {
|
|
|
3293
3655
|
});
|
|
3294
3656
|
}
|
|
3295
3657
|
try {
|
|
3658
|
+
if (this.usesNetworkEncryption) {
|
|
3659
|
+
const valueDelResult2 = await this.tc.kv.delete(`vault/${key}`);
|
|
3660
|
+
if (!valueDelResult2.ok) {
|
|
3661
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3662
|
+
}
|
|
3663
|
+
return ok(void 0);
|
|
3664
|
+
}
|
|
3296
3665
|
const [keyDelResult, valueDelResult] = await Promise.all([
|
|
3297
3666
|
this.tc.kv.delete(`keys/${key}`),
|
|
3298
3667
|
this.tc.kv.delete(`vault/${key}`)
|
|
@@ -3317,7 +3686,7 @@ var DataVaultService = class extends BaseService {
|
|
|
3317
3686
|
*/
|
|
3318
3687
|
async list(options) {
|
|
3319
3688
|
return this.withTelemetry("list", options?.prefix, async () => {
|
|
3320
|
-
if (!this.
|
|
3689
|
+
if (!this.isUnlocked) {
|
|
3321
3690
|
return vaultError({
|
|
3322
3691
|
code: "VAULT_LOCKED",
|
|
3323
3692
|
message: "Vault must be unlocked before listing data"
|
|
@@ -3367,6 +3736,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3367
3736
|
*/
|
|
3368
3737
|
async head(key) {
|
|
3369
3738
|
return this.withTelemetry("head", key, async () => {
|
|
3739
|
+
if (this.usesNetworkEncryption) {
|
|
3740
|
+
return this.headNetworkEncrypted(key);
|
|
3741
|
+
}
|
|
3370
3742
|
if (!this._isUnlocked) {
|
|
3371
3743
|
return vaultError({
|
|
3372
3744
|
code: "VAULT_LOCKED",
|
|
@@ -3434,6 +3806,16 @@ var DataVaultService = class extends BaseService {
|
|
|
3434
3806
|
*/
|
|
3435
3807
|
async reencrypt(key, recipientDID, options) {
|
|
3436
3808
|
return this.withTelemetry("reencrypt", key, async () => {
|
|
3809
|
+
if (this.usesNetworkEncryption) {
|
|
3810
|
+
void recipientDID;
|
|
3811
|
+
void options;
|
|
3812
|
+
return vaultError({
|
|
3813
|
+
code: "STORAGE_ERROR",
|
|
3814
|
+
cause: new Error(
|
|
3815
|
+
"Vault key grants are deprecated for network-encrypted vaults; grant tinycloud.encryption/decrypt on the network plus KV access to vault data."
|
|
3816
|
+
)
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3437
3819
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3438
3820
|
return vaultError({
|
|
3439
3821
|
code: "VAULT_LOCKED",
|
|
@@ -3523,6 +3905,66 @@ var DataVaultService = class extends BaseService {
|
|
|
3523
3905
|
*/
|
|
3524
3906
|
async getShared(grantorDID, key, options) {
|
|
3525
3907
|
return this.withTelemetry("getShared", key, async () => {
|
|
3908
|
+
if (this.usesNetworkEncryption) {
|
|
3909
|
+
const grantorKV = options?.kv;
|
|
3910
|
+
if (!grantorKV) {
|
|
3911
|
+
return vaultError({
|
|
3912
|
+
code: "STORAGE_ERROR",
|
|
3913
|
+
cause: new Error(
|
|
3914
|
+
"getShared requires a delegated KV service via options.kv."
|
|
3915
|
+
)
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3918
|
+
const config = this.networkEncryption;
|
|
3919
|
+
if (!config) {
|
|
3920
|
+
return vaultError({
|
|
3921
|
+
code: "VAULT_LOCKED",
|
|
3922
|
+
message: "Network encryption is not configured"
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
if (!this.requireAuth()) {
|
|
3926
|
+
return vaultError({
|
|
3927
|
+
code: "VAULT_LOCKED",
|
|
3928
|
+
message: "Authentication required"
|
|
3929
|
+
});
|
|
3930
|
+
}
|
|
3931
|
+
try {
|
|
3932
|
+
const valueResult = await grantorKV.get(`vault/${key}`, {
|
|
3933
|
+
raw: true
|
|
3934
|
+
});
|
|
3935
|
+
if (!valueResult.ok) {
|
|
3936
|
+
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
3937
|
+
}
|
|
3938
|
+
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
3939
|
+
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
3940
|
+
const proof = await this.decryptCapabilityProof();
|
|
3941
|
+
const plaintextResult = await config.service.decryptEnvelope(
|
|
3942
|
+
envelope,
|
|
3943
|
+
proof
|
|
3944
|
+
);
|
|
3945
|
+
if (!plaintextResult.ok) {
|
|
3946
|
+
return vaultError({
|
|
3947
|
+
code: "DECRYPTION_FAILED",
|
|
3948
|
+
message: plaintextResult.error.message
|
|
3949
|
+
});
|
|
3950
|
+
}
|
|
3951
|
+
const metadata = envelope.metadata ?? {};
|
|
3952
|
+
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
3953
|
+
const keyId = metadata[VaultHeaders.KEY_ID] ?? envelope.encryptedSymmetricKeyHash.slice(0, 16);
|
|
3954
|
+
const value = this.deserializeValue(
|
|
3955
|
+
plaintextResult.data,
|
|
3956
|
+
contentType,
|
|
3957
|
+
options
|
|
3958
|
+
);
|
|
3959
|
+
void grantorDID;
|
|
3960
|
+
return { ok: true, data: { value, metadata, keyId } };
|
|
3961
|
+
} catch (error) {
|
|
3962
|
+
return vaultError({
|
|
3963
|
+
code: "STORAGE_ERROR",
|
|
3964
|
+
cause: toError(error)
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3526
3968
|
if (!this._isUnlocked || !this.masterKey || !this.encryptionIdentity) {
|
|
3527
3969
|
return vaultError({
|
|
3528
3970
|
code: "VAULT_LOCKED",
|
|
@@ -3648,6 +4090,10 @@ var DataVaultService = class extends BaseService {
|
|
|
3648
4090
|
*/
|
|
3649
4091
|
async listGrants(key) {
|
|
3650
4092
|
return this.withTelemetry("listGrants", key, async () => {
|
|
4093
|
+
if (this.usesNetworkEncryption) {
|
|
4094
|
+
void key;
|
|
4095
|
+
return { ok: true, data: [] };
|
|
4096
|
+
}
|
|
3651
4097
|
if (!this._isUnlocked) {
|
|
3652
4098
|
return vaultError({
|
|
3653
4099
|
code: "VAULT_LOCKED",
|
|
@@ -3711,6 +4157,15 @@ var DataVaultService = class extends BaseService {
|
|
|
3711
4157
|
*/
|
|
3712
4158
|
async revoke(key, recipientDID) {
|
|
3713
4159
|
return this.withTelemetry("revoke", key, async () => {
|
|
4160
|
+
if (this.usesNetworkEncryption) {
|
|
4161
|
+
void recipientDID;
|
|
4162
|
+
return vaultError({
|
|
4163
|
+
code: "STORAGE_ERROR",
|
|
4164
|
+
cause: new Error(
|
|
4165
|
+
"Vault key grants are deprecated for network-encrypted vaults; revoke KV and tinycloud.encryption/decrypt grants instead."
|
|
4166
|
+
)
|
|
4167
|
+
});
|
|
4168
|
+
}
|
|
3714
4169
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3715
4170
|
return vaultError({
|
|
3716
4171
|
code: "VAULT_LOCKED",
|
|
@@ -3846,18 +4301,67 @@ function createVaultCrypto(wasm) {
|
|
|
3846
4301
|
};
|
|
3847
4302
|
}
|
|
3848
4303
|
|
|
3849
|
-
// src/secrets/
|
|
4304
|
+
// src/secrets/paths.ts
|
|
3850
4305
|
var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
3851
4306
|
var SECRET_PREFIX = "secrets/";
|
|
3852
|
-
|
|
4307
|
+
var SCOPED_SECRET_PREFIX = "secrets/scoped/";
|
|
4308
|
+
var RESERVED_SECRET_SCOPES = /* @__PURE__ */ new Set(["default", "global"]);
|
|
4309
|
+
function canonicalizeSecretScope(scope) {
|
|
4310
|
+
if (scope === void 0) {
|
|
4311
|
+
return void 0;
|
|
4312
|
+
}
|
|
4313
|
+
const trimmed = scope.trim();
|
|
4314
|
+
if (trimmed === "") {
|
|
4315
|
+
throw new Error("Secret scope must be non-empty; omit scope for global secrets.");
|
|
4316
|
+
}
|
|
4317
|
+
const canonical = trimmed.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
4318
|
+
if (canonical === "") {
|
|
4319
|
+
throw new Error("Secret scope must contain at least one letter or number.");
|
|
4320
|
+
}
|
|
4321
|
+
if (RESERVED_SECRET_SCOPES.has(canonical)) {
|
|
4322
|
+
throw new Error(
|
|
4323
|
+
`Secret scope ${JSON.stringify(scope)} is reserved; omit scope for global secrets.`
|
|
4324
|
+
);
|
|
4325
|
+
}
|
|
4326
|
+
return canonical;
|
|
4327
|
+
}
|
|
4328
|
+
function resolveSecretPath(name, options = {}) {
|
|
4329
|
+
const normalizedName = name.trim();
|
|
4330
|
+
if (!SECRET_NAME_RE.test(normalizedName)) {
|
|
4331
|
+
throw new Error(
|
|
4332
|
+
`Invalid secret name ${JSON.stringify(name)}. Secret names must match ${SECRET_NAME_RE.source}.`
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
const scope = canonicalizeSecretScope(options.scope);
|
|
4336
|
+
const vaultKey = scope === void 0 ? `${SECRET_PREFIX}${normalizedName}` : `${SCOPED_SECRET_PREFIX}${scope}/${normalizedName}`;
|
|
4337
|
+
return {
|
|
4338
|
+
name: normalizedName,
|
|
4339
|
+
...scope !== void 0 ? { scope } : {},
|
|
4340
|
+
vaultKey,
|
|
4341
|
+
permissionPaths: {
|
|
4342
|
+
vault: `vault/${vaultKey}`
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4345
|
+
}
|
|
4346
|
+
function resolveSecretListPrefix(options = {}) {
|
|
4347
|
+
const scope = canonicalizeSecretScope(options.scope);
|
|
4348
|
+
return scope === void 0 ? "vault/secrets/" : `vault/secrets/scoped/${scope}/`;
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
// src/secrets/SecretsService.ts
|
|
4352
|
+
function invalidSecretInput(message) {
|
|
3853
4353
|
return err({
|
|
3854
4354
|
code: ErrorCodes.INVALID_INPUT,
|
|
3855
4355
|
service: "secrets",
|
|
3856
|
-
message
|
|
4356
|
+
message
|
|
3857
4357
|
});
|
|
3858
4358
|
}
|
|
3859
|
-
function
|
|
3860
|
-
|
|
4359
|
+
function resolveSecretPathResult(name, options) {
|
|
4360
|
+
try {
|
|
4361
|
+
return resolveSecretPath(name, options);
|
|
4362
|
+
} catch (error) {
|
|
4363
|
+
return invalidSecretInput(error instanceof Error ? error.message : String(error));
|
|
4364
|
+
}
|
|
3861
4365
|
}
|
|
3862
4366
|
var SecretsService = class {
|
|
3863
4367
|
constructor(vault) {
|
|
@@ -3875,36 +4379,40 @@ var SecretsService = class {
|
|
|
3875
4379
|
lock() {
|
|
3876
4380
|
this.vault.lock();
|
|
3877
4381
|
}
|
|
3878
|
-
async get(name) {
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
const result = await this.vault.get(secretKey(name));
|
|
4382
|
+
async get(name, options) {
|
|
4383
|
+
const secretPath = resolveSecretPathResult(name, options);
|
|
4384
|
+
if ("ok" in secretPath) return secretPath;
|
|
4385
|
+
const result = await this.vault.get(secretPath.vaultKey);
|
|
3883
4386
|
if (!result.ok) {
|
|
3884
4387
|
return result;
|
|
3885
4388
|
}
|
|
3886
4389
|
return { ok: true, data: result.data.value.value };
|
|
3887
4390
|
}
|
|
3888
|
-
async put(name, value) {
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
}
|
|
4391
|
+
async put(name, value, options) {
|
|
4392
|
+
const secretPath = resolveSecretPathResult(name, options);
|
|
4393
|
+
if ("ok" in secretPath) return secretPath;
|
|
3892
4394
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3893
|
-
return this.vault.put(
|
|
4395
|
+
return this.vault.put(secretPath.vaultKey, {
|
|
3894
4396
|
value,
|
|
3895
4397
|
createdAt: now,
|
|
3896
4398
|
updatedAt: now
|
|
3897
4399
|
});
|
|
3898
4400
|
}
|
|
3899
|
-
async delete(name) {
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
return this.vault.delete(secretKey(name));
|
|
4401
|
+
async delete(name, options) {
|
|
4402
|
+
const secretPath = resolveSecretPathResult(name, options);
|
|
4403
|
+
if ("ok" in secretPath) return secretPath;
|
|
4404
|
+
return this.vault.delete(secretPath.vaultKey);
|
|
3904
4405
|
}
|
|
3905
|
-
async list() {
|
|
4406
|
+
async list(options) {
|
|
4407
|
+
let prefix;
|
|
4408
|
+
try {
|
|
4409
|
+
const scope = canonicalizeSecretScope(options?.scope);
|
|
4410
|
+
prefix = scope === void 0 ? "secrets/" : `secrets/scoped/${scope}/`;
|
|
4411
|
+
} catch (error) {
|
|
4412
|
+
return invalidSecretInput(error instanceof Error ? error.message : String(error));
|
|
4413
|
+
}
|
|
3906
4414
|
const result = await this.vault.list({
|
|
3907
|
-
prefix
|
|
4415
|
+
prefix,
|
|
3908
4416
|
removePrefix: true
|
|
3909
4417
|
});
|
|
3910
4418
|
if (!result.ok) {
|
|
@@ -3916,14 +4424,981 @@ var SecretsService = class {
|
|
|
3916
4424
|
};
|
|
3917
4425
|
}
|
|
3918
4426
|
};
|
|
4427
|
+
|
|
4428
|
+
// src/encryption/canonical.ts
|
|
4429
|
+
function canonicalize(value) {
|
|
4430
|
+
if (value === void 0) {
|
|
4431
|
+
return "";
|
|
4432
|
+
}
|
|
4433
|
+
return stringify(value);
|
|
4434
|
+
}
|
|
4435
|
+
function stringify(value) {
|
|
4436
|
+
if (value === null) return "null";
|
|
4437
|
+
switch (typeof value) {
|
|
4438
|
+
case "boolean":
|
|
4439
|
+
case "number":
|
|
4440
|
+
return JSON.stringify(value);
|
|
4441
|
+
case "string":
|
|
4442
|
+
return JSON.stringify(value);
|
|
4443
|
+
case "object": {
|
|
4444
|
+
if (Array.isArray(value)) {
|
|
4445
|
+
return `[${value.map(stringify).join(",")}]`;
|
|
4446
|
+
}
|
|
4447
|
+
const keys = Object.keys(value).sort();
|
|
4448
|
+
const parts = [];
|
|
4449
|
+
for (const k of keys) {
|
|
4450
|
+
const v = value[k];
|
|
4451
|
+
if (v === void 0) continue;
|
|
4452
|
+
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
4453
|
+
}
|
|
4454
|
+
return `{${parts.join(",")}}`;
|
|
4455
|
+
}
|
|
4456
|
+
default:
|
|
4457
|
+
throw new TypeError(
|
|
4458
|
+
`canonicalize: unsupported value type ${typeof value}`
|
|
4459
|
+
);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
var HEX = "0123456789abcdef";
|
|
4463
|
+
function hexEncode2(bytes) {
|
|
4464
|
+
let out = "";
|
|
4465
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
4466
|
+
const b = bytes[i];
|
|
4467
|
+
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
4468
|
+
}
|
|
4469
|
+
return out;
|
|
4470
|
+
}
|
|
4471
|
+
function hexDecode(hex) {
|
|
4472
|
+
if (hex.length % 2 !== 0) {
|
|
4473
|
+
throw new Error("hex string must have even length");
|
|
4474
|
+
}
|
|
4475
|
+
const out = new Uint8Array(hex.length / 2);
|
|
4476
|
+
for (let i = 0; i < out.length; i++) {
|
|
4477
|
+
const hi = parseInt(hex[i * 2], 16);
|
|
4478
|
+
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
4479
|
+
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
4480
|
+
throw new Error("invalid hex character");
|
|
4481
|
+
}
|
|
4482
|
+
out[i] = hi << 4 | lo;
|
|
4483
|
+
}
|
|
4484
|
+
return out;
|
|
4485
|
+
}
|
|
4486
|
+
function base64Encode2(bytes) {
|
|
4487
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4488
|
+
let out = "";
|
|
4489
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
4490
|
+
const b0 = bytes[i];
|
|
4491
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
4492
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
4493
|
+
out += chars[b0 >> 2 & 63];
|
|
4494
|
+
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
4495
|
+
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
4496
|
+
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
4497
|
+
}
|
|
4498
|
+
return out;
|
|
4499
|
+
}
|
|
4500
|
+
function base64Decode2(s) {
|
|
4501
|
+
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
4502
|
+
const len = clean.length;
|
|
4503
|
+
if (len % 4 !== 0) {
|
|
4504
|
+
throw new Error("invalid base64 input");
|
|
4505
|
+
}
|
|
4506
|
+
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
4507
|
+
const outLen = len / 4 * 3 - padding;
|
|
4508
|
+
const out = new Uint8Array(outLen);
|
|
4509
|
+
const lookup = {};
|
|
4510
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4511
|
+
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
4512
|
+
let outIdx = 0;
|
|
4513
|
+
for (let i = 0; i < len; i += 4) {
|
|
4514
|
+
const v0 = lookup[clean[i]] ?? 0;
|
|
4515
|
+
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
4516
|
+
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
4517
|
+
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
4518
|
+
const b0 = v0 << 2 | v1 >> 4;
|
|
4519
|
+
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
4520
|
+
const b2 = (v2 & 3) << 6 | v3;
|
|
4521
|
+
if (outIdx < outLen) out[outIdx++] = b0;
|
|
4522
|
+
if (outIdx < outLen) out[outIdx++] = b1;
|
|
4523
|
+
if (outIdx < outLen) out[outIdx++] = b2;
|
|
4524
|
+
}
|
|
4525
|
+
return out;
|
|
4526
|
+
}
|
|
4527
|
+
function utf8Encode(s) {
|
|
4528
|
+
return new TextEncoder().encode(s);
|
|
4529
|
+
}
|
|
4530
|
+
function utf8Decode(b) {
|
|
4531
|
+
return new TextDecoder().decode(b);
|
|
4532
|
+
}
|
|
4533
|
+
function canonicalHashHex(sha256, value) {
|
|
4534
|
+
const canonical = canonicalize(value);
|
|
4535
|
+
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
// src/encryption/networkId.ts
|
|
4539
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
4540
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
4541
|
+
var NetworkIdError = class extends Error {
|
|
4542
|
+
constructor(message) {
|
|
4543
|
+
super(message);
|
|
4544
|
+
this.name = "NetworkIdError";
|
|
4545
|
+
}
|
|
4546
|
+
};
|
|
4547
|
+
function parseNetworkId(networkId) {
|
|
4548
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
4549
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
4550
|
+
}
|
|
4551
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
4552
|
+
throw new NetworkIdError(
|
|
4553
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
4554
|
+
);
|
|
4555
|
+
}
|
|
4556
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
4557
|
+
const lastColon = body.lastIndexOf(":");
|
|
4558
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
4559
|
+
throw new NetworkIdError(
|
|
4560
|
+
`networkId missing principal or name segment (got ${JSON.stringify(networkId)})`
|
|
4561
|
+
);
|
|
4562
|
+
}
|
|
4563
|
+
const principal = body.slice(0, lastColon);
|
|
4564
|
+
const name = body.slice(lastColon + 1);
|
|
4565
|
+
if (!principal.startsWith("did:")) {
|
|
4566
|
+
throw new NetworkIdError(
|
|
4567
|
+
`networkId principal must be a DID (got ${JSON.stringify(principal)})`
|
|
4568
|
+
);
|
|
4569
|
+
}
|
|
4570
|
+
const didParts = principal.split(":");
|
|
4571
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
4572
|
+
throw new NetworkIdError(
|
|
4573
|
+
`networkId principal is not a well-formed DID (got ${JSON.stringify(principal)})`
|
|
4574
|
+
);
|
|
4575
|
+
}
|
|
4576
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
4577
|
+
throw new NetworkIdError(
|
|
4578
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
return { networkId, principal, name };
|
|
4582
|
+
}
|
|
4583
|
+
function buildNetworkId(principal, name) {
|
|
4584
|
+
if (typeof principal !== "string" || !principal.startsWith("did:")) {
|
|
4585
|
+
throw new NetworkIdError("principal must be a DID");
|
|
4586
|
+
}
|
|
4587
|
+
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
4588
|
+
throw new NetworkIdError(
|
|
4589
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
4590
|
+
);
|
|
4591
|
+
}
|
|
4592
|
+
const networkId = `${URN_PREFIX}${principal}:${name}`;
|
|
4593
|
+
parseNetworkId(networkId);
|
|
4594
|
+
return networkId;
|
|
4595
|
+
}
|
|
4596
|
+
function isNetworkId(networkId) {
|
|
4597
|
+
if (typeof networkId !== "string") {
|
|
4598
|
+
return false;
|
|
4599
|
+
}
|
|
4600
|
+
try {
|
|
4601
|
+
parseNetworkId(networkId);
|
|
4602
|
+
return true;
|
|
4603
|
+
} catch {
|
|
4604
|
+
return false;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
function networkDiscoveryKey(name) {
|
|
4608
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
4609
|
+
throw new NetworkIdError(
|
|
4610
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
return `.well-known/encryption/network/${name}`;
|
|
4614
|
+
}
|
|
4615
|
+
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
4616
|
+
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
4617
|
+
|
|
4618
|
+
// src/encryption/types.ts
|
|
4619
|
+
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
4620
|
+
var ENVELOPE_VERSION = 1;
|
|
4621
|
+
var DEFAULT_KEY_VERSION = 1;
|
|
4622
|
+
var DECRYPT_FACT_TYPE = "tinycloud.encryption.decrypt/v1";
|
|
4623
|
+
var DECRYPT_RESULT_TYPE = "tinycloud.encryption.decrypt-result/v1";
|
|
4624
|
+
var ENCRYPTION_SERVICE = "tinycloud.encryption";
|
|
4625
|
+
var ENCRYPTION_SERVICE_SHORT = "encryption";
|
|
4626
|
+
var DECRYPT_ACTION = "tinycloud.encryption/decrypt";
|
|
4627
|
+
function defaultEncryptionMessage(input) {
|
|
4628
|
+
switch (input.code) {
|
|
4629
|
+
case "NETWORK_NOT_FOUND":
|
|
4630
|
+
return input.message ?? `Network not found: ${input.networkId ?? input.name ?? "<unknown>"}`;
|
|
4631
|
+
case "NETWORK_NOT_ACTIVE":
|
|
4632
|
+
return input.message ?? `Network not active (state=${input.state})`;
|
|
4633
|
+
case "INVALID_NETWORK_ID":
|
|
4634
|
+
return input.message;
|
|
4635
|
+
case "INVALID_ENVELOPE":
|
|
4636
|
+
return input.message;
|
|
4637
|
+
case "DECRYPT_DENIED":
|
|
4638
|
+
return input.message;
|
|
4639
|
+
case "INVALID_RESPONSE":
|
|
4640
|
+
return input.message;
|
|
4641
|
+
case "RESPONSE_SIGNATURE_INVALID":
|
|
4642
|
+
return input.message ?? "Node response signature failed to verify";
|
|
4643
|
+
case "RESPONSE_BINDING_MISMATCH":
|
|
4644
|
+
return input.message ?? `Node response binding mismatch on field ${JSON.stringify(input.field)}`;
|
|
4645
|
+
case "TRANSPORT_ERROR":
|
|
4646
|
+
return input.message ?? input.cause.message;
|
|
4647
|
+
case "INVALID_INPUT":
|
|
4648
|
+
return input.message;
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
function encryptionError(input) {
|
|
4652
|
+
return {
|
|
4653
|
+
...input,
|
|
4654
|
+
service: "encryption",
|
|
4655
|
+
message: defaultEncryptionMessage(input)
|
|
4656
|
+
};
|
|
4657
|
+
}
|
|
4658
|
+
function toError2(error) {
|
|
4659
|
+
if (error instanceof Error) return error;
|
|
4660
|
+
if (typeof error === "object" && error !== null) {
|
|
4661
|
+
return new Error(JSON.stringify(error));
|
|
4662
|
+
}
|
|
4663
|
+
return new Error(String(error));
|
|
4664
|
+
}
|
|
4665
|
+
|
|
4666
|
+
// src/encryption/discovery.ts
|
|
4667
|
+
async function discoverNetwork(input) {
|
|
4668
|
+
let networkId;
|
|
4669
|
+
let principal;
|
|
4670
|
+
let name;
|
|
4671
|
+
try {
|
|
4672
|
+
if (input.identifier.startsWith("urn:tinycloud:encryption:")) {
|
|
4673
|
+
const parsed = parseNetworkId(input.identifier);
|
|
4674
|
+
networkId = parsed.networkId;
|
|
4675
|
+
principal = parsed.principal;
|
|
4676
|
+
name = parsed.name;
|
|
4677
|
+
} else {
|
|
4678
|
+
if (input.principal === void 0) {
|
|
4679
|
+
return {
|
|
4680
|
+
ok: false,
|
|
4681
|
+
error: encryptionError({
|
|
4682
|
+
code: "INVALID_INPUT",
|
|
4683
|
+
message: "discoverNetwork requires `principal` when identifier is a bare network name"
|
|
4684
|
+
})
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
networkId = `urn:tinycloud:encryption:${input.principal}:${input.identifier}`;
|
|
4688
|
+
const parsed = parseNetworkId(networkId);
|
|
4689
|
+
principal = parsed.principal;
|
|
4690
|
+
name = parsed.name;
|
|
4691
|
+
}
|
|
4692
|
+
} catch (err3) {
|
|
4693
|
+
if (err3 instanceof NetworkIdError) {
|
|
4694
|
+
return {
|
|
4695
|
+
ok: false,
|
|
4696
|
+
error: encryptionError({
|
|
4697
|
+
code: "INVALID_NETWORK_ID",
|
|
4698
|
+
message: err3.message
|
|
4699
|
+
})
|
|
4700
|
+
};
|
|
4701
|
+
}
|
|
4702
|
+
throw err3;
|
|
4703
|
+
}
|
|
4704
|
+
if (input.node !== void 0) {
|
|
4705
|
+
try {
|
|
4706
|
+
const descriptor = await input.node.fetchByNetworkId(networkId);
|
|
4707
|
+
if (descriptor !== null) {
|
|
4708
|
+
const validated = validateDescriptor(descriptor, networkId, principal, name);
|
|
4709
|
+
if (!validated.ok) return validated;
|
|
4710
|
+
return { ok: true, data: { descriptor: validated.data, source: "node" } };
|
|
4711
|
+
}
|
|
4712
|
+
} catch (err3) {
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
if (input.wellKnown !== void 0) {
|
|
4716
|
+
try {
|
|
4717
|
+
const descriptor = await input.wellKnown.fetchWellKnown(
|
|
4718
|
+
principal,
|
|
4719
|
+
networkDiscoveryKey(name)
|
|
4720
|
+
);
|
|
4721
|
+
if (descriptor !== null) {
|
|
4722
|
+
const validated = validateDescriptor(descriptor, networkId, principal, name);
|
|
4723
|
+
if (!validated.ok) return validated;
|
|
4724
|
+
return {
|
|
4725
|
+
ok: true,
|
|
4726
|
+
data: { descriptor: validated.data, source: "well-known" }
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
} catch (err3) {
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
return {
|
|
4733
|
+
ok: false,
|
|
4734
|
+
error: encryptionError({
|
|
4735
|
+
code: "NETWORK_NOT_FOUND",
|
|
4736
|
+
networkId,
|
|
4737
|
+
name
|
|
4738
|
+
})
|
|
4739
|
+
};
|
|
4740
|
+
}
|
|
4741
|
+
function validateDescriptor(descriptor, networkId, principal, name) {
|
|
4742
|
+
if (descriptor.networkId !== networkId) {
|
|
4743
|
+
return {
|
|
4744
|
+
ok: false,
|
|
4745
|
+
error: encryptionError({
|
|
4746
|
+
code: "INVALID_NETWORK_ID",
|
|
4747
|
+
message: `descriptor networkId ${JSON.stringify(descriptor.networkId)} does not match expected ${JSON.stringify(networkId)}`
|
|
4748
|
+
})
|
|
4749
|
+
};
|
|
4750
|
+
}
|
|
4751
|
+
if (descriptor.principal !== principal) {
|
|
4752
|
+
return {
|
|
4753
|
+
ok: false,
|
|
4754
|
+
error: encryptionError({
|
|
4755
|
+
code: "INVALID_NETWORK_ID",
|
|
4756
|
+
message: "descriptor principal does not match networkId principal"
|
|
4757
|
+
})
|
|
4758
|
+
};
|
|
4759
|
+
}
|
|
4760
|
+
if (descriptor.name !== name) {
|
|
4761
|
+
return {
|
|
4762
|
+
ok: false,
|
|
4763
|
+
error: encryptionError({
|
|
4764
|
+
code: "INVALID_NETWORK_ID",
|
|
4765
|
+
message: "descriptor name does not match networkId name"
|
|
4766
|
+
})
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
if (typeof descriptor.publicEncryptionKey !== "string" || descriptor.publicEncryptionKey.length === 0) {
|
|
4770
|
+
return {
|
|
4771
|
+
ok: false,
|
|
4772
|
+
error: encryptionError({
|
|
4773
|
+
code: "INVALID_NETWORK_ID",
|
|
4774
|
+
message: "descriptor publicEncryptionKey must be a non-empty string"
|
|
4775
|
+
})
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
return { ok: true, data: descriptor };
|
|
4779
|
+
}
|
|
4780
|
+
function ensureNetworkUsableForDecrypt(descriptor) {
|
|
4781
|
+
if (descriptor.state === "active" || descriptor.state === "rotating") {
|
|
4782
|
+
return { ok: true, data: descriptor };
|
|
4783
|
+
}
|
|
4784
|
+
return {
|
|
4785
|
+
ok: false,
|
|
4786
|
+
error: encryptionError({
|
|
4787
|
+
code: "NETWORK_NOT_ACTIVE",
|
|
4788
|
+
state: descriptor.state
|
|
4789
|
+
})
|
|
4790
|
+
};
|
|
4791
|
+
}
|
|
4792
|
+
|
|
4793
|
+
// src/encryption/envelope.ts
|
|
4794
|
+
function encryptToNetwork(crypto2, input) {
|
|
4795
|
+
parseNetworkId(input.networkId);
|
|
4796
|
+
const alg = input.alg ?? DEFAULT_ENCRYPTION_ALG;
|
|
4797
|
+
const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
|
|
4798
|
+
const symmetricKey = crypto2.randomBytes(32);
|
|
4799
|
+
const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
4800
|
+
const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
4801
|
+
const encryptedSymmetricKey = base64Encode2(wrapped);
|
|
4802
|
+
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
4803
|
+
crypto2.sha256,
|
|
4804
|
+
encryptedSymmetricKey
|
|
4805
|
+
);
|
|
4806
|
+
const envelope = {
|
|
4807
|
+
v: ENVELOPE_VERSION,
|
|
4808
|
+
networkId: input.networkId,
|
|
4809
|
+
alg,
|
|
4810
|
+
keyVersion,
|
|
4811
|
+
encryptedSymmetricKey,
|
|
4812
|
+
encryptedSymmetricKeyHash,
|
|
4813
|
+
ciphertext: base64Encode2(ciphertext),
|
|
4814
|
+
...input.aad !== void 0 ? { aad: base64Encode2(input.aad) } : {},
|
|
4815
|
+
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
4816
|
+
};
|
|
4817
|
+
return { envelope, symmetricKey };
|
|
4818
|
+
}
|
|
4819
|
+
function validateEnvelope(crypto2, envelope) {
|
|
4820
|
+
if (envelope === null || typeof envelope !== "object") {
|
|
4821
|
+
return {
|
|
4822
|
+
ok: false,
|
|
4823
|
+
error: encryptionError({
|
|
4824
|
+
code: "INVALID_ENVELOPE",
|
|
4825
|
+
message: "envelope must be an object"
|
|
4826
|
+
})
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
const e = envelope;
|
|
4830
|
+
if (e.v !== ENVELOPE_VERSION) {
|
|
4831
|
+
return {
|
|
4832
|
+
ok: false,
|
|
4833
|
+
error: encryptionError({
|
|
4834
|
+
code: "INVALID_ENVELOPE",
|
|
4835
|
+
message: `envelope.v must be ${ENVELOPE_VERSION} (got ${e.v})`
|
|
4836
|
+
})
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4839
|
+
try {
|
|
4840
|
+
parseNetworkId(e.networkId);
|
|
4841
|
+
} catch (err3) {
|
|
4842
|
+
return {
|
|
4843
|
+
ok: false,
|
|
4844
|
+
error: encryptionError({
|
|
4845
|
+
code: "INVALID_ENVELOPE",
|
|
4846
|
+
message: `envelope.networkId is malformed: ${err3 instanceof Error ? err3.message : String(err3)}`
|
|
4847
|
+
})
|
|
4848
|
+
};
|
|
4849
|
+
}
|
|
4850
|
+
for (const field of [
|
|
4851
|
+
"alg",
|
|
4852
|
+
"encryptedSymmetricKey",
|
|
4853
|
+
"encryptedSymmetricKeyHash",
|
|
4854
|
+
"ciphertext"
|
|
4855
|
+
]) {
|
|
4856
|
+
if (typeof e[field] !== "string" || e[field].length === 0) {
|
|
4857
|
+
return {
|
|
4858
|
+
ok: false,
|
|
4859
|
+
error: encryptionError({
|
|
4860
|
+
code: "INVALID_ENVELOPE",
|
|
4861
|
+
message: `envelope.${field} must be a non-empty string`
|
|
4862
|
+
})
|
|
4863
|
+
};
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
if (typeof e.keyVersion !== "number" || !Number.isInteger(e.keyVersion)) {
|
|
4867
|
+
return {
|
|
4868
|
+
ok: false,
|
|
4869
|
+
error: encryptionError({
|
|
4870
|
+
code: "INVALID_ENVELOPE",
|
|
4871
|
+
message: "envelope.keyVersion must be an integer"
|
|
4872
|
+
})
|
|
4873
|
+
};
|
|
4874
|
+
}
|
|
4875
|
+
const expectedHash = canonicalHashHex(crypto2.sha256, e.encryptedSymmetricKey);
|
|
4876
|
+
if (expectedHash !== e.encryptedSymmetricKeyHash) {
|
|
4877
|
+
return {
|
|
4878
|
+
ok: false,
|
|
4879
|
+
error: encryptionError({
|
|
4880
|
+
code: "INVALID_ENVELOPE",
|
|
4881
|
+
message: "envelope.encryptedSymmetricKeyHash does not match canonical hash of encryptedSymmetricKey"
|
|
4882
|
+
})
|
|
4883
|
+
};
|
|
4884
|
+
}
|
|
4885
|
+
return { ok: true, data: e };
|
|
4886
|
+
}
|
|
4887
|
+
function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
|
|
4888
|
+
const ciphertext = base64Decode2(envelope.ciphertext);
|
|
4889
|
+
const aad = envelope.aad !== void 0 ? base64Decode2(envelope.aad) : void 0;
|
|
4890
|
+
return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
|
|
4891
|
+
}
|
|
4892
|
+
|
|
4893
|
+
// src/encryption/invocation.ts
|
|
4894
|
+
function buildCanonicalDecryptRequest(input) {
|
|
4895
|
+
const canonicalBody = canonicalize(input.body);
|
|
4896
|
+
const bodyHash = canonicalHashHex(
|
|
4897
|
+
input.crypto.sha256,
|
|
4898
|
+
input.body
|
|
4899
|
+
);
|
|
4900
|
+
const receiverPublicKeyHash = canonicalHashHex(
|
|
4901
|
+
input.crypto.sha256,
|
|
4902
|
+
input.body.receiverPublicKey
|
|
4903
|
+
);
|
|
4904
|
+
return { canonicalBody, bodyHash, receiverPublicKeyHash };
|
|
4905
|
+
}
|
|
4906
|
+
function buildDecryptFacts(input) {
|
|
4907
|
+
const bodyHash = input.canonicalBody !== void 0 ? hexEncode2(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
|
|
4908
|
+
input.crypto.sha256,
|
|
4909
|
+
input.body
|
|
4910
|
+
);
|
|
4911
|
+
const receiverPublicKeyHash = canonicalHashHex(
|
|
4912
|
+
input.crypto.sha256,
|
|
4913
|
+
input.body.receiverPublicKey
|
|
4914
|
+
);
|
|
4915
|
+
return {
|
|
4916
|
+
type: DECRYPT_FACT_TYPE,
|
|
4917
|
+
targetNode: input.body.targetNode,
|
|
4918
|
+
networkId: input.body.networkId,
|
|
4919
|
+
bodyHash,
|
|
4920
|
+
encryptedSymmetricKeyHash: input.encryptedSymmetricKeyHash,
|
|
4921
|
+
receiverPublicKeyHash,
|
|
4922
|
+
alg: input.body.alg,
|
|
4923
|
+
keyVersion: input.body.keyVersion
|
|
4924
|
+
};
|
|
4925
|
+
}
|
|
4926
|
+
function buildDecryptAttenuation(networkId) {
|
|
4927
|
+
parseNetworkId(networkId);
|
|
4928
|
+
return {
|
|
4929
|
+
[networkId]: {
|
|
4930
|
+
[DECRYPT_ACTION]: {}
|
|
4931
|
+
}
|
|
4932
|
+
};
|
|
4933
|
+
}
|
|
4934
|
+
function checkDecryptInvocationInput(crypto2, input) {
|
|
4935
|
+
if (input.body.type !== DECRYPT_FACT_TYPE) {
|
|
4936
|
+
return {
|
|
4937
|
+
ok: false,
|
|
4938
|
+
error: encryptionError({
|
|
4939
|
+
code: "INVALID_INPUT",
|
|
4940
|
+
message: `body.type must be ${DECRYPT_FACT_TYPE}`
|
|
4941
|
+
})
|
|
4942
|
+
};
|
|
4943
|
+
}
|
|
4944
|
+
if (input.facts.type !== DECRYPT_FACT_TYPE) {
|
|
4945
|
+
return {
|
|
4946
|
+
ok: false,
|
|
4947
|
+
error: encryptionError({
|
|
4948
|
+
code: "INVALID_INPUT",
|
|
4949
|
+
message: `facts.type must be ${DECRYPT_FACT_TYPE}`
|
|
4950
|
+
})
|
|
4951
|
+
};
|
|
4952
|
+
}
|
|
4953
|
+
if (input.facts.targetNode !== input.targetNode) {
|
|
4954
|
+
return {
|
|
4955
|
+
ok: false,
|
|
4956
|
+
error: encryptionError({
|
|
4957
|
+
code: "INVALID_INPUT",
|
|
4958
|
+
message: "facts.targetNode must equal targetNode \u2014 the UCAN audience binds the request to a single node"
|
|
4959
|
+
})
|
|
4960
|
+
};
|
|
4961
|
+
}
|
|
4962
|
+
if (input.body.targetNode !== input.targetNode) {
|
|
4963
|
+
return {
|
|
4964
|
+
ok: false,
|
|
4965
|
+
error: encryptionError({
|
|
4966
|
+
code: "INVALID_INPUT",
|
|
4967
|
+
message: "body.targetNode must equal targetNode"
|
|
4968
|
+
})
|
|
4969
|
+
};
|
|
4970
|
+
}
|
|
4971
|
+
if (input.facts.networkId !== input.networkId) {
|
|
4972
|
+
return {
|
|
4973
|
+
ok: false,
|
|
4974
|
+
error: encryptionError({
|
|
4975
|
+
code: "INVALID_INPUT",
|
|
4976
|
+
message: "facts.networkId must equal networkId"
|
|
4977
|
+
})
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4980
|
+
if (input.body.networkId !== input.networkId) {
|
|
4981
|
+
return {
|
|
4982
|
+
ok: false,
|
|
4983
|
+
error: encryptionError({
|
|
4984
|
+
code: "INVALID_INPUT",
|
|
4985
|
+
message: "body.networkId must equal networkId"
|
|
4986
|
+
})
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4989
|
+
if (input.facts.alg !== input.body.alg) {
|
|
4990
|
+
return {
|
|
4991
|
+
ok: false,
|
|
4992
|
+
error: encryptionError({
|
|
4993
|
+
code: "INVALID_INPUT",
|
|
4994
|
+
message: "facts.alg must equal body.alg"
|
|
4995
|
+
})
|
|
4996
|
+
};
|
|
4997
|
+
}
|
|
4998
|
+
if (input.facts.keyVersion !== input.body.keyVersion) {
|
|
4999
|
+
return {
|
|
5000
|
+
ok: false,
|
|
5001
|
+
error: encryptionError({
|
|
5002
|
+
code: "INVALID_INPUT",
|
|
5003
|
+
message: "facts.keyVersion must equal body.keyVersion"
|
|
5004
|
+
})
|
|
5005
|
+
};
|
|
5006
|
+
}
|
|
5007
|
+
if (input.facts.encryptedSymmetricKeyHash !== input.body.encryptedSymmetricKeyHash) {
|
|
5008
|
+
return {
|
|
5009
|
+
ok: false,
|
|
5010
|
+
error: encryptionError({
|
|
5011
|
+
code: "INVALID_INPUT",
|
|
5012
|
+
message: "facts.encryptedSymmetricKeyHash must equal body.encryptedSymmetricKeyHash"
|
|
5013
|
+
})
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
if (input.facts.receiverPublicKeyHash !== input.body.receiverPublicKeyHash) {
|
|
5017
|
+
return {
|
|
5018
|
+
ok: false,
|
|
5019
|
+
error: encryptionError({
|
|
5020
|
+
code: "INVALID_INPUT",
|
|
5021
|
+
message: "facts.receiverPublicKeyHash must equal body.receiverPublicKeyHash"
|
|
5022
|
+
})
|
|
5023
|
+
};
|
|
5024
|
+
}
|
|
5025
|
+
try {
|
|
5026
|
+
parseNetworkId(input.networkId);
|
|
5027
|
+
} catch (err3) {
|
|
5028
|
+
return {
|
|
5029
|
+
ok: false,
|
|
5030
|
+
error: encryptionError({
|
|
5031
|
+
code: "INVALID_NETWORK_ID",
|
|
5032
|
+
message: err3 instanceof Error ? err3.message : String(err3)
|
|
5033
|
+
})
|
|
5034
|
+
};
|
|
5035
|
+
}
|
|
5036
|
+
const canonicalBody = canonicalize(
|
|
5037
|
+
input.body
|
|
5038
|
+
);
|
|
5039
|
+
const expectedBodyHash = canonicalHashHex(crypto2.sha256, input.body);
|
|
5040
|
+
if (expectedBodyHash !== input.facts.bodyHash) {
|
|
5041
|
+
return {
|
|
5042
|
+
ok: false,
|
|
5043
|
+
error: encryptionError({
|
|
5044
|
+
code: "INVALID_INPUT",
|
|
5045
|
+
message: "facts.bodyHash does not match the canonical body hash"
|
|
5046
|
+
})
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
5049
|
+
return { ok: true, data: input, canonicalBody };
|
|
5050
|
+
}
|
|
5051
|
+
async function buildDecryptInvocation(crypto2, signer, input) {
|
|
5052
|
+
const checked = checkDecryptInvocationInput(crypto2, input);
|
|
5053
|
+
if (!checked.ok) {
|
|
5054
|
+
return checked;
|
|
5055
|
+
}
|
|
5056
|
+
try {
|
|
5057
|
+
const built = await signer.signDecryptInvocation(checked.data);
|
|
5058
|
+
if (!built.authorization || !built.invocationCid) {
|
|
5059
|
+
return {
|
|
5060
|
+
ok: false,
|
|
5061
|
+
error: encryptionError({
|
|
5062
|
+
code: "INVALID_INPUT",
|
|
5063
|
+
message: "decrypt-invocation signer returned an empty authorization or invocationCid"
|
|
5064
|
+
})
|
|
5065
|
+
};
|
|
5066
|
+
}
|
|
5067
|
+
if (built.canonicalBody !== checked.canonicalBody) {
|
|
5068
|
+
return {
|
|
5069
|
+
ok: false,
|
|
5070
|
+
error: encryptionError({
|
|
5071
|
+
code: "INVALID_INPUT",
|
|
5072
|
+
message: "decrypt-invocation signer returned a canonicalBody that does not match the SDK's canonicalization \u2014 signer must use the SDK-provided body"
|
|
5073
|
+
})
|
|
5074
|
+
};
|
|
5075
|
+
}
|
|
5076
|
+
return { ok: true, data: built };
|
|
5077
|
+
} catch (err3) {
|
|
5078
|
+
return {
|
|
5079
|
+
ok: false,
|
|
5080
|
+
error: encryptionError({
|
|
5081
|
+
code: "TRANSPORT_ERROR",
|
|
5082
|
+
cause: err3 instanceof Error ? err3 : new Error(String(err3)),
|
|
5083
|
+
message: `failed to sign decrypt invocation: ${err3 instanceof Error ? err3.message : String(err3)}`
|
|
5084
|
+
})
|
|
5085
|
+
};
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
|
|
5089
|
+
// src/encryption/receiverKey.ts
|
|
5090
|
+
function generateRandomReceiverKey(input) {
|
|
5091
|
+
const seed = input.crypto.randomBytes(32);
|
|
5092
|
+
return input.crypto.x25519FromSeed(seed);
|
|
5093
|
+
}
|
|
5094
|
+
async function deriveSignedReceiverKey(input) {
|
|
5095
|
+
const message = `tinycloud.encryption.receiver-key/v1:${input.networkId}:${input.context ?? ""}`;
|
|
5096
|
+
const sig = await input.signer.signMessage(message);
|
|
5097
|
+
const sigBytes = utf8Encode(sig);
|
|
5098
|
+
const seed = input.crypto.sha256(sigBytes);
|
|
5099
|
+
return input.crypto.x25519FromSeed(seed);
|
|
5100
|
+
}
|
|
5101
|
+
|
|
5102
|
+
// src/encryption/response.ts
|
|
5103
|
+
function canonicalSignedResponse(response) {
|
|
5104
|
+
const { nodeSignature: _drop, ...rest } = response;
|
|
5105
|
+
return canonicalize(rest);
|
|
5106
|
+
}
|
|
5107
|
+
function verifyDecryptResponse(input) {
|
|
5108
|
+
const { crypto: crypto2, request, facts, invocationCid, requestBodyHash, response } = input;
|
|
5109
|
+
if (response.type !== DECRYPT_RESULT_TYPE) {
|
|
5110
|
+
return {
|
|
5111
|
+
ok: false,
|
|
5112
|
+
error: encryptionError({
|
|
5113
|
+
code: "INVALID_RESPONSE",
|
|
5114
|
+
message: `response.type must be ${DECRYPT_RESULT_TYPE}`
|
|
5115
|
+
})
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
if (response.targetNode !== request.targetNode) {
|
|
5119
|
+
return {
|
|
5120
|
+
ok: false,
|
|
5121
|
+
error: encryptionError({
|
|
5122
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5123
|
+
field: "targetNode"
|
|
5124
|
+
})
|
|
5125
|
+
};
|
|
5126
|
+
}
|
|
5127
|
+
if (response.networkId !== request.networkId) {
|
|
5128
|
+
return {
|
|
5129
|
+
ok: false,
|
|
5130
|
+
error: encryptionError({
|
|
5131
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5132
|
+
field: "networkId"
|
|
5133
|
+
})
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
if (response.alg !== request.alg) {
|
|
5137
|
+
return {
|
|
5138
|
+
ok: false,
|
|
5139
|
+
error: encryptionError({
|
|
5140
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5141
|
+
field: "alg"
|
|
5142
|
+
})
|
|
5143
|
+
};
|
|
5144
|
+
}
|
|
5145
|
+
if (response.keyVersion !== request.keyVersion) {
|
|
5146
|
+
return {
|
|
5147
|
+
ok: false,
|
|
5148
|
+
error: encryptionError({
|
|
5149
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5150
|
+
field: "keyVersion"
|
|
5151
|
+
})
|
|
5152
|
+
};
|
|
5153
|
+
}
|
|
5154
|
+
if (response.encryptedSymmetricKeyHash !== request.encryptedSymmetricKeyHash) {
|
|
5155
|
+
return {
|
|
5156
|
+
ok: false,
|
|
5157
|
+
error: encryptionError({
|
|
5158
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5159
|
+
field: "encryptedSymmetricKeyHash"
|
|
5160
|
+
})
|
|
5161
|
+
};
|
|
5162
|
+
}
|
|
5163
|
+
if (response.receiverPublicKeyHash !== request.receiverPublicKeyHash) {
|
|
5164
|
+
return {
|
|
5165
|
+
ok: false,
|
|
5166
|
+
error: encryptionError({
|
|
5167
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5168
|
+
field: "receiverPublicKeyHash"
|
|
5169
|
+
})
|
|
5170
|
+
};
|
|
5171
|
+
}
|
|
5172
|
+
if (response.invocationCid !== invocationCid) {
|
|
5173
|
+
return {
|
|
5174
|
+
ok: false,
|
|
5175
|
+
error: encryptionError({
|
|
5176
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5177
|
+
field: "invocationCid"
|
|
5178
|
+
})
|
|
5179
|
+
};
|
|
5180
|
+
}
|
|
5181
|
+
const expectedRequestHash = hexEncode2(
|
|
5182
|
+
crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
|
|
5183
|
+
);
|
|
5184
|
+
if (response.requestHash !== expectedRequestHash) {
|
|
5185
|
+
return {
|
|
5186
|
+
ok: false,
|
|
5187
|
+
error: encryptionError({
|
|
5188
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5189
|
+
field: "requestHash"
|
|
5190
|
+
})
|
|
5191
|
+
};
|
|
5192
|
+
}
|
|
5193
|
+
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) {
|
|
5194
|
+
return {
|
|
5195
|
+
ok: false,
|
|
5196
|
+
error: encryptionError({
|
|
5197
|
+
code: "RESPONSE_BINDING_MISMATCH",
|
|
5198
|
+
field: "facts"
|
|
5199
|
+
})
|
|
5200
|
+
};
|
|
5201
|
+
}
|
|
5202
|
+
const signedBytes = new TextEncoder().encode(
|
|
5203
|
+
canonicalSignedResponse(response)
|
|
5204
|
+
);
|
|
5205
|
+
const signatureBytes = base64Decode2(response.nodeSignature);
|
|
5206
|
+
if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
|
|
5207
|
+
return {
|
|
5208
|
+
ok: false,
|
|
5209
|
+
error: encryptionError({
|
|
5210
|
+
code: "RESPONSE_SIGNATURE_INVALID"
|
|
5211
|
+
})
|
|
5212
|
+
};
|
|
5213
|
+
}
|
|
5214
|
+
return { ok: true, data: response };
|
|
5215
|
+
}
|
|
5216
|
+
function openWrappedKey(crypto2, receiverPrivateKey, response) {
|
|
5217
|
+
const wrapped = base64Decode2(response.wrappedKey);
|
|
5218
|
+
return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
// src/encryption/EncryptionService.ts
|
|
5222
|
+
function encOk(data) {
|
|
5223
|
+
return { ok: true, data };
|
|
5224
|
+
}
|
|
5225
|
+
function encErr(error) {
|
|
5226
|
+
return { ok: false, error };
|
|
5227
|
+
}
|
|
5228
|
+
var EncryptionService = class extends BaseService {
|
|
5229
|
+
constructor(config) {
|
|
5230
|
+
super();
|
|
5231
|
+
this._config = config;
|
|
5232
|
+
}
|
|
5233
|
+
get config() {
|
|
5234
|
+
return this._config;
|
|
5235
|
+
}
|
|
5236
|
+
get crypto() {
|
|
5237
|
+
return this._config.crypto;
|
|
5238
|
+
}
|
|
5239
|
+
async discoverNetwork(identifier, principal) {
|
|
5240
|
+
const result = await discoverNetwork({
|
|
5241
|
+
identifier,
|
|
5242
|
+
...principal !== void 0 ? { principal } : {},
|
|
5243
|
+
...this._config.node !== void 0 ? { node: this._config.node } : {},
|
|
5244
|
+
...this._config.wellKnown !== void 0 ? { wellKnown: this._config.wellKnown } : {}
|
|
5245
|
+
});
|
|
5246
|
+
if (!result.ok) return result;
|
|
5247
|
+
return encOk(result.data.descriptor);
|
|
5248
|
+
}
|
|
5249
|
+
async encryptToNetwork(networkId, plaintext, options) {
|
|
5250
|
+
try {
|
|
5251
|
+
const discovered = await this.discoverNetwork(networkId);
|
|
5252
|
+
if (!discovered.ok) return discovered;
|
|
5253
|
+
const usable = ensureNetworkUsableForDecrypt(discovered.data);
|
|
5254
|
+
if (!usable.ok) return usable;
|
|
5255
|
+
const descriptor = usable.data;
|
|
5256
|
+
const networkPublicKey = base64Decode2(descriptor.publicEncryptionKey);
|
|
5257
|
+
const result = encryptToNetwork(this.crypto, {
|
|
5258
|
+
networkId,
|
|
5259
|
+
networkPublicKey,
|
|
5260
|
+
plaintext,
|
|
5261
|
+
...options?.aad !== void 0 ? { aad: options.aad } : {},
|
|
5262
|
+
alg: options?.alg ?? descriptor.alg,
|
|
5263
|
+
keyVersion: options?.keyVersion ?? descriptor.keyVersion,
|
|
5264
|
+
...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
|
|
5265
|
+
});
|
|
5266
|
+
return encOk(result.envelope);
|
|
5267
|
+
} catch (error) {
|
|
5268
|
+
return encErr(
|
|
5269
|
+
encryptionError({
|
|
5270
|
+
code: "TRANSPORT_ERROR",
|
|
5271
|
+
cause: toError2(error)
|
|
5272
|
+
})
|
|
5273
|
+
);
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
async decryptEnvelope(envelope, capabilityProof, options) {
|
|
5277
|
+
try {
|
|
5278
|
+
const validated = validateEnvelope(this.crypto, envelope);
|
|
5279
|
+
if (!validated.ok) return validated;
|
|
5280
|
+
let descriptor;
|
|
5281
|
+
if (options?.descriptor !== void 0) {
|
|
5282
|
+
descriptor = options.descriptor;
|
|
5283
|
+
} else {
|
|
5284
|
+
const discovered = await this.discoverNetwork(envelope.networkId);
|
|
5285
|
+
if (!discovered.ok) return discovered;
|
|
5286
|
+
descriptor = discovered.data;
|
|
5287
|
+
}
|
|
5288
|
+
const usable = ensureNetworkUsableForDecrypt(descriptor);
|
|
5289
|
+
if (!usable.ok) return usable;
|
|
5290
|
+
const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
5291
|
+
if (targetNode === void 0) {
|
|
5292
|
+
return encErr(
|
|
5293
|
+
encryptionError({
|
|
5294
|
+
code: "INVALID_INPUT",
|
|
5295
|
+
message: "no target node available from descriptor"
|
|
5296
|
+
})
|
|
5297
|
+
);
|
|
5298
|
+
}
|
|
5299
|
+
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
5300
|
+
const receiverPublicKey = base64Encode2(receiverKey.publicKey);
|
|
5301
|
+
const receiverPublicKeyHash = canonicalHashHex(
|
|
5302
|
+
this.crypto.sha256,
|
|
5303
|
+
receiverPublicKey
|
|
5304
|
+
);
|
|
5305
|
+
const body = {
|
|
5306
|
+
type: DECRYPT_FACT_TYPE,
|
|
5307
|
+
targetNode,
|
|
5308
|
+
networkId: envelope.networkId,
|
|
5309
|
+
alg: envelope.alg,
|
|
5310
|
+
keyVersion: envelope.keyVersion,
|
|
5311
|
+
encryptedSymmetricKey: envelope.encryptedSymmetricKey,
|
|
5312
|
+
encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,
|
|
5313
|
+
receiverPublicKey,
|
|
5314
|
+
receiverPublicKeyHash
|
|
5315
|
+
};
|
|
5316
|
+
const canonicalRequest = buildCanonicalDecryptRequest({
|
|
5317
|
+
crypto: this.crypto,
|
|
5318
|
+
body,
|
|
5319
|
+
receiverPublicKey: receiverKey.publicKey
|
|
5320
|
+
});
|
|
5321
|
+
const facts = buildDecryptFacts({
|
|
5322
|
+
crypto: this.crypto,
|
|
5323
|
+
body,
|
|
5324
|
+
encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,
|
|
5325
|
+
receiverPublicKey: receiverKey.publicKey,
|
|
5326
|
+
canonicalBody: canonicalRequest.canonicalBody
|
|
5327
|
+
});
|
|
5328
|
+
const built = await buildDecryptInvocation(this.crypto, this._config.signer, {
|
|
5329
|
+
targetNode,
|
|
5330
|
+
networkId: envelope.networkId,
|
|
5331
|
+
body,
|
|
5332
|
+
facts,
|
|
5333
|
+
proof: capabilityProof
|
|
5334
|
+
});
|
|
5335
|
+
if (!built.ok) return built;
|
|
5336
|
+
let response;
|
|
5337
|
+
try {
|
|
5338
|
+
response = await this._config.transport.postDecrypt({
|
|
5339
|
+
targetNode,
|
|
5340
|
+
networkId: envelope.networkId,
|
|
5341
|
+
authorization: built.data.authorization,
|
|
5342
|
+
canonicalBody: built.data.canonicalBody
|
|
5343
|
+
});
|
|
5344
|
+
} catch (error) {
|
|
5345
|
+
return encErr(
|
|
5346
|
+
encryptionError({
|
|
5347
|
+
code: "TRANSPORT_ERROR",
|
|
5348
|
+
cause: toError2(error)
|
|
5349
|
+
})
|
|
5350
|
+
);
|
|
5351
|
+
}
|
|
5352
|
+
const verified = verifyDecryptResponse({
|
|
5353
|
+
crypto: this.crypto,
|
|
5354
|
+
request: body,
|
|
5355
|
+
facts,
|
|
5356
|
+
invocationCid: built.data.invocationCid,
|
|
5357
|
+
requestBodyHash: facts.bodyHash,
|
|
5358
|
+
response
|
|
5359
|
+
});
|
|
5360
|
+
if (!verified.ok) return verified;
|
|
5361
|
+
const symmetricKey = openWrappedKey(
|
|
5362
|
+
this.crypto,
|
|
5363
|
+
receiverKey.privateKey,
|
|
5364
|
+
verified.data
|
|
5365
|
+
);
|
|
5366
|
+
const plaintext = decryptEnvelopeWithKey(
|
|
5367
|
+
this.crypto,
|
|
5368
|
+
envelope,
|
|
5369
|
+
symmetricKey
|
|
5370
|
+
);
|
|
5371
|
+
return encOk(plaintext);
|
|
5372
|
+
} catch (error) {
|
|
5373
|
+
return encErr(
|
|
5374
|
+
encryptionError({
|
|
5375
|
+
code: "TRANSPORT_ERROR",
|
|
5376
|
+
cause: toError2(error)
|
|
5377
|
+
})
|
|
5378
|
+
);
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
};
|
|
5382
|
+
EncryptionService.serviceName = "encryption";
|
|
3919
5383
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3920
5384
|
0 && (module.exports = {
|
|
3921
5385
|
BaseService,
|
|
5386
|
+
DECRYPT_ACTION,
|
|
5387
|
+
DECRYPT_FACT_TYPE,
|
|
5388
|
+
DECRYPT_RESULT_TYPE,
|
|
5389
|
+
DEFAULT_ENCRYPTION_ALG,
|
|
5390
|
+
DEFAULT_KEY_VERSION,
|
|
5391
|
+
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
3922
5392
|
DataVaultService,
|
|
3923
5393
|
DatabaseHandle,
|
|
3924
5394
|
DuckDbAction,
|
|
3925
5395
|
DuckDbDatabaseHandle,
|
|
3926
5396
|
DuckDbService,
|
|
5397
|
+
ENCRYPTION_NETWORK_URN_PREFIX,
|
|
5398
|
+
ENCRYPTION_SERVICE,
|
|
5399
|
+
ENCRYPTION_SERVICE_SHORT,
|
|
5400
|
+
ENVELOPE_VERSION,
|
|
5401
|
+
EncryptionService,
|
|
3927
5402
|
ErrorCodes,
|
|
3928
5403
|
GenericKVResponseSchema,
|
|
3929
5404
|
GenericResultSchema,
|
|
@@ -3933,8 +5408,11 @@ var SecretsService = class {
|
|
|
3933
5408
|
KVListResultSchema,
|
|
3934
5409
|
KVResponseHeadersSchema,
|
|
3935
5410
|
KVService,
|
|
5411
|
+
NETWORK_NAME_PATTERN,
|
|
5412
|
+
NetworkIdError,
|
|
3936
5413
|
PrefixedKVService,
|
|
3937
5414
|
RetryPolicySchema,
|
|
5415
|
+
SECRET_NAME_RE,
|
|
3938
5416
|
SQLAction,
|
|
3939
5417
|
SQLService,
|
|
3940
5418
|
SecretsService,
|
|
@@ -3953,21 +5431,51 @@ var SecretsService = class {
|
|
|
3953
5431
|
authExpiredError,
|
|
3954
5432
|
authRequiredError,
|
|
3955
5433
|
authUnauthorizedError,
|
|
5434
|
+
base64Decode,
|
|
5435
|
+
base64Encode,
|
|
5436
|
+
buildCanonicalDecryptRequest,
|
|
5437
|
+
buildDecryptAttenuation,
|
|
5438
|
+
buildDecryptFacts,
|
|
5439
|
+
buildDecryptInvocation,
|
|
5440
|
+
buildNetworkId,
|
|
5441
|
+
canonicalHashHex,
|
|
5442
|
+
canonicalSignedResponse,
|
|
5443
|
+
canonicalizeEncryptionJson,
|
|
5444
|
+
canonicalizeSecretScope,
|
|
5445
|
+
checkDecryptInvocationInput,
|
|
3956
5446
|
createKVResponseSchema,
|
|
3957
5447
|
createResultSchema,
|
|
3958
5448
|
createVaultCrypto,
|
|
5449
|
+
decryptEnvelopeWithKey,
|
|
3959
5450
|
defaultRetryPolicy,
|
|
5451
|
+
deriveSignedReceiverKey,
|
|
5452
|
+
discoverNetwork,
|
|
5453
|
+
encryptToNetwork,
|
|
5454
|
+
encryptionError,
|
|
5455
|
+
ensureNetworkUsableForDecrypt,
|
|
3960
5456
|
err,
|
|
3961
5457
|
errorResult,
|
|
5458
|
+
generateRandomReceiverKey,
|
|
5459
|
+
hexDecode,
|
|
5460
|
+
hexEncode,
|
|
5461
|
+
isNetworkId,
|
|
5462
|
+
networkDiscoveryKey,
|
|
3962
5463
|
networkError,
|
|
3963
5464
|
notFoundError,
|
|
3964
5465
|
ok,
|
|
5466
|
+
openWrappedKey,
|
|
3965
5467
|
parseAuthError,
|
|
5468
|
+
parseNetworkId,
|
|
3966
5469
|
permissionDeniedError,
|
|
5470
|
+
resolveSecretListPrefix,
|
|
5471
|
+
resolveSecretPath,
|
|
3967
5472
|
serviceError,
|
|
3968
5473
|
storageLimitReachedError,
|
|
3969
5474
|
storageQuotaExceededError,
|
|
3970
5475
|
timeoutError,
|
|
5476
|
+
utf8Decode,
|
|
5477
|
+
utf8Encode,
|
|
5478
|
+
validateEnvelope,
|
|
3971
5479
|
validateKVListResponse,
|
|
3972
5480
|
validateKVResponseHeaders,
|
|
3973
5481
|
validateRetryPolicy,
|
|
@@ -3975,6 +5483,7 @@ var SecretsService = class {
|
|
|
3975
5483
|
validateServiceRequestEvent,
|
|
3976
5484
|
validateServiceResponseEvent,
|
|
3977
5485
|
validateServiceSession,
|
|
5486
|
+
verifyDecryptResponse,
|
|
3978
5487
|
wrapError
|
|
3979
5488
|
});
|
|
3980
5489
|
//# sourceMappingURL=index.cjs.map
|