@tinycloud/sdk-services 2.2.0-beta.13 → 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 +1321 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -5
- package/dist/index.d.ts +26 -5
- package/dist/index.js +1280 -4
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +1 -1
- package/dist/kv/index.d.ts +1 -1
- 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,12 +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,
|
|
24
29
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS: () => DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
25
30
|
DataVaultService: () => DataVaultService,
|
|
26
31
|
DatabaseHandle: () => DatabaseHandle,
|
|
27
32
|
DuckDbAction: () => DuckDbAction,
|
|
28
33
|
DuckDbDatabaseHandle: () => DuckDbDatabaseHandle,
|
|
29
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,
|
|
30
40
|
ErrorCodes: () => ErrorCodes,
|
|
31
41
|
GenericKVResponseSchema: () => GenericKVResponseSchema,
|
|
32
42
|
GenericResultSchema: () => GenericResultSchema,
|
|
@@ -36,6 +46,8 @@ __export(index_exports, {
|
|
|
36
46
|
KVListResultSchema: () => KVListResultSchema,
|
|
37
47
|
KVResponseHeadersSchema: () => KVResponseHeadersSchema,
|
|
38
48
|
KVService: () => KVService,
|
|
49
|
+
NETWORK_NAME_PATTERN: () => NETWORK_NAME_PATTERN,
|
|
50
|
+
NetworkIdError: () => NetworkIdError,
|
|
39
51
|
PrefixedKVService: () => PrefixedKVService,
|
|
40
52
|
RetryPolicySchema: () => RetryPolicySchema,
|
|
41
53
|
SECRET_NAME_RE: () => SECRET_NAME_RE,
|
|
@@ -57,23 +69,51 @@ __export(index_exports, {
|
|
|
57
69
|
authExpiredError: () => authExpiredError,
|
|
58
70
|
authRequiredError: () => authRequiredError,
|
|
59
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,
|
|
60
82
|
canonicalizeSecretScope: () => canonicalizeSecretScope,
|
|
83
|
+
checkDecryptInvocationInput: () => checkDecryptInvocationInput,
|
|
61
84
|
createKVResponseSchema: () => createKVResponseSchema,
|
|
62
85
|
createResultSchema: () => createResultSchema,
|
|
63
86
|
createVaultCrypto: () => createVaultCrypto,
|
|
87
|
+
decryptEnvelopeWithKey: () => decryptEnvelopeWithKey,
|
|
64
88
|
defaultRetryPolicy: () => defaultRetryPolicy,
|
|
89
|
+
deriveSignedReceiverKey: () => deriveSignedReceiverKey,
|
|
90
|
+
discoverNetwork: () => discoverNetwork,
|
|
91
|
+
encryptToNetwork: () => encryptToNetwork,
|
|
92
|
+
encryptionError: () => encryptionError,
|
|
93
|
+
ensureNetworkUsableForDecrypt: () => ensureNetworkUsableForDecrypt,
|
|
65
94
|
err: () => err,
|
|
66
95
|
errorResult: () => errorResult,
|
|
96
|
+
generateRandomReceiverKey: () => generateRandomReceiverKey,
|
|
97
|
+
hexDecode: () => hexDecode,
|
|
98
|
+
hexEncode: () => hexEncode2,
|
|
99
|
+
isNetworkId: () => isNetworkId,
|
|
100
|
+
networkDiscoveryKey: () => networkDiscoveryKey,
|
|
67
101
|
networkError: () => networkError,
|
|
68
102
|
notFoundError: () => notFoundError,
|
|
69
103
|
ok: () => ok,
|
|
104
|
+
openWrappedKey: () => openWrappedKey,
|
|
70
105
|
parseAuthError: () => parseAuthError,
|
|
106
|
+
parseNetworkId: () => parseNetworkId,
|
|
71
107
|
permissionDeniedError: () => permissionDeniedError,
|
|
108
|
+
resolveSecretListPrefix: () => resolveSecretListPrefix,
|
|
72
109
|
resolveSecretPath: () => resolveSecretPath,
|
|
73
110
|
serviceError: () => serviceError,
|
|
74
111
|
storageLimitReachedError: () => storageLimitReachedError,
|
|
75
112
|
storageQuotaExceededError: () => storageQuotaExceededError,
|
|
76
113
|
timeoutError: () => timeoutError,
|
|
114
|
+
utf8Decode: () => utf8Decode,
|
|
115
|
+
utf8Encode: () => utf8Encode,
|
|
116
|
+
validateEnvelope: () => validateEnvelope,
|
|
77
117
|
validateKVListResponse: () => validateKVListResponse,
|
|
78
118
|
validateKVResponseHeaders: () => validateKVResponseHeaders,
|
|
79
119
|
validateRetryPolicy: () => validateRetryPolicy,
|
|
@@ -81,6 +121,7 @@ __export(index_exports, {
|
|
|
81
121
|
validateServiceRequestEvent: () => validateServiceRequestEvent,
|
|
82
122
|
validateServiceResponseEvent: () => validateServiceResponseEvent,
|
|
83
123
|
validateServiceSession: () => validateServiceSession,
|
|
124
|
+
verifyDecryptResponse: () => verifyDecryptResponse,
|
|
84
125
|
wrapError: () => wrapError
|
|
85
126
|
});
|
|
86
127
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -3024,6 +3065,12 @@ function base64Decode(str) {
|
|
|
3024
3065
|
}
|
|
3025
3066
|
return bytes;
|
|
3026
3067
|
}
|
|
3068
|
+
function unwrapKVData(value) {
|
|
3069
|
+
if (value !== null && typeof value === "object" && "data" in value) {
|
|
3070
|
+
return value.data;
|
|
3071
|
+
}
|
|
3072
|
+
return value;
|
|
3073
|
+
}
|
|
3027
3074
|
function isUnlockSigner(signer) {
|
|
3028
3075
|
return typeof signer === "object" && signer !== null && typeof signer.signMessage === "function";
|
|
3029
3076
|
}
|
|
@@ -3078,13 +3125,16 @@ var DataVaultService = class extends BaseService {
|
|
|
3078
3125
|
* Whether the vault is currently unlocked.
|
|
3079
3126
|
*/
|
|
3080
3127
|
get isUnlocked() {
|
|
3081
|
-
return this._isUnlocked;
|
|
3128
|
+
return this.usesNetworkEncryption || this._isUnlocked;
|
|
3082
3129
|
}
|
|
3083
3130
|
/**
|
|
3084
3131
|
* The vault's public encryption key (X25519).
|
|
3085
3132
|
* Throws if vault is locked.
|
|
3086
3133
|
*/
|
|
3087
3134
|
get publicKey() {
|
|
3135
|
+
if (this.usesNetworkEncryption) {
|
|
3136
|
+
throw new Error("Network-encrypted vaults do not expose a local public key");
|
|
3137
|
+
}
|
|
3088
3138
|
if (!this.encryptionIdentity) {
|
|
3089
3139
|
throw new Error("Vault is locked");
|
|
3090
3140
|
}
|
|
@@ -3102,12 +3152,51 @@ var DataVaultService = class extends BaseService {
|
|
|
3102
3152
|
get tc() {
|
|
3103
3153
|
return this.vaultConfig.tc;
|
|
3104
3154
|
}
|
|
3155
|
+
get networkEncryption() {
|
|
3156
|
+
return this.vaultConfig.encryption;
|
|
3157
|
+
}
|
|
3158
|
+
get usesNetworkEncryption() {
|
|
3159
|
+
return this.networkEncryption !== void 0;
|
|
3160
|
+
}
|
|
3105
3161
|
/**
|
|
3106
3162
|
* Get the host URL.
|
|
3107
3163
|
*/
|
|
3108
3164
|
get host() {
|
|
3109
3165
|
return this.tc.hosts[0];
|
|
3110
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
|
+
}
|
|
3111
3200
|
// =========================================================================
|
|
3112
3201
|
// Phase 1: Core Operations
|
|
3113
3202
|
// =========================================================================
|
|
@@ -3124,6 +3213,10 @@ var DataVaultService = class extends BaseService {
|
|
|
3124
3213
|
* signatures exist (browser only).
|
|
3125
3214
|
*/
|
|
3126
3215
|
async unlock(signer) {
|
|
3216
|
+
if (this.usesNetworkEncryption) {
|
|
3217
|
+
this._isUnlocked = true;
|
|
3218
|
+
return { ok: true, data: void 0 };
|
|
3219
|
+
}
|
|
3127
3220
|
const unlockSigner = isUnlockSigner(signer) ? signer : void 0;
|
|
3128
3221
|
if (this._isUnlocked && this.masterKey && (this.encryptionIdentity || !unlockSigner)) {
|
|
3129
3222
|
return { ok: true, data: void 0 };
|
|
@@ -3250,6 +3343,131 @@ var DataVaultService = class extends BaseService {
|
|
|
3250
3343
|
this.lock();
|
|
3251
3344
|
super.onSignOut();
|
|
3252
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
|
+
}
|
|
3253
3471
|
/**
|
|
3254
3472
|
* Encrypt and store a value at the given key.
|
|
3255
3473
|
*
|
|
@@ -3259,6 +3477,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3259
3477
|
*/
|
|
3260
3478
|
async put(key, value, options) {
|
|
3261
3479
|
return this.withTelemetry("put", key, async () => {
|
|
3480
|
+
if (this.usesNetworkEncryption) {
|
|
3481
|
+
return this.putNetworkEncrypted(key, value, options);
|
|
3482
|
+
}
|
|
3262
3483
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3263
3484
|
return vaultError({
|
|
3264
3485
|
code: "VAULT_LOCKED",
|
|
@@ -3351,6 +3572,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3351
3572
|
*/
|
|
3352
3573
|
async get(key, options) {
|
|
3353
3574
|
return this.withTelemetry("get", key, async () => {
|
|
3575
|
+
if (this.usesNetworkEncryption) {
|
|
3576
|
+
return this.getNetworkEncrypted(key, options);
|
|
3577
|
+
}
|
|
3354
3578
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3355
3579
|
return vaultError({
|
|
3356
3580
|
code: "VAULT_LOCKED",
|
|
@@ -3418,7 +3642,7 @@ var DataVaultService = class extends BaseService {
|
|
|
3418
3642
|
*/
|
|
3419
3643
|
async delete(key) {
|
|
3420
3644
|
return this.withTelemetry("delete", key, async () => {
|
|
3421
|
-
if (!this.
|
|
3645
|
+
if (!this.isUnlocked) {
|
|
3422
3646
|
return vaultError({
|
|
3423
3647
|
code: "VAULT_LOCKED",
|
|
3424
3648
|
message: "Vault must be unlocked before deleting data"
|
|
@@ -3431,6 +3655,13 @@ var DataVaultService = class extends BaseService {
|
|
|
3431
3655
|
});
|
|
3432
3656
|
}
|
|
3433
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
|
+
}
|
|
3434
3665
|
const [keyDelResult, valueDelResult] = await Promise.all([
|
|
3435
3666
|
this.tc.kv.delete(`keys/${key}`),
|
|
3436
3667
|
this.tc.kv.delete(`vault/${key}`)
|
|
@@ -3455,7 +3686,7 @@ var DataVaultService = class extends BaseService {
|
|
|
3455
3686
|
*/
|
|
3456
3687
|
async list(options) {
|
|
3457
3688
|
return this.withTelemetry("list", options?.prefix, async () => {
|
|
3458
|
-
if (!this.
|
|
3689
|
+
if (!this.isUnlocked) {
|
|
3459
3690
|
return vaultError({
|
|
3460
3691
|
code: "VAULT_LOCKED",
|
|
3461
3692
|
message: "Vault must be unlocked before listing data"
|
|
@@ -3505,6 +3736,9 @@ var DataVaultService = class extends BaseService {
|
|
|
3505
3736
|
*/
|
|
3506
3737
|
async head(key) {
|
|
3507
3738
|
return this.withTelemetry("head", key, async () => {
|
|
3739
|
+
if (this.usesNetworkEncryption) {
|
|
3740
|
+
return this.headNetworkEncrypted(key);
|
|
3741
|
+
}
|
|
3508
3742
|
if (!this._isUnlocked) {
|
|
3509
3743
|
return vaultError({
|
|
3510
3744
|
code: "VAULT_LOCKED",
|
|
@@ -3572,6 +3806,16 @@ var DataVaultService = class extends BaseService {
|
|
|
3572
3806
|
*/
|
|
3573
3807
|
async reencrypt(key, recipientDID, options) {
|
|
3574
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
|
+
}
|
|
3575
3819
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3576
3820
|
return vaultError({
|
|
3577
3821
|
code: "VAULT_LOCKED",
|
|
@@ -3661,6 +3905,66 @@ var DataVaultService = class extends BaseService {
|
|
|
3661
3905
|
*/
|
|
3662
3906
|
async getShared(grantorDID, key, options) {
|
|
3663
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
|
+
}
|
|
3664
3968
|
if (!this._isUnlocked || !this.masterKey || !this.encryptionIdentity) {
|
|
3665
3969
|
return vaultError({
|
|
3666
3970
|
code: "VAULT_LOCKED",
|
|
@@ -3786,6 +4090,10 @@ var DataVaultService = class extends BaseService {
|
|
|
3786
4090
|
*/
|
|
3787
4091
|
async listGrants(key) {
|
|
3788
4092
|
return this.withTelemetry("listGrants", key, async () => {
|
|
4093
|
+
if (this.usesNetworkEncryption) {
|
|
4094
|
+
void key;
|
|
4095
|
+
return { ok: true, data: [] };
|
|
4096
|
+
}
|
|
3789
4097
|
if (!this._isUnlocked) {
|
|
3790
4098
|
return vaultError({
|
|
3791
4099
|
code: "VAULT_LOCKED",
|
|
@@ -3849,6 +4157,15 @@ var DataVaultService = class extends BaseService {
|
|
|
3849
4157
|
*/
|
|
3850
4158
|
async revoke(key, recipientDID) {
|
|
3851
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
|
+
}
|
|
3852
4169
|
if (!this._isUnlocked || !this.masterKey) {
|
|
3853
4170
|
return vaultError({
|
|
3854
4171
|
code: "VAULT_LOCKED",
|
|
@@ -4022,11 +4339,14 @@ function resolveSecretPath(name, options = {}) {
|
|
|
4022
4339
|
...scope !== void 0 ? { scope } : {},
|
|
4023
4340
|
vaultKey,
|
|
4024
4341
|
permissionPaths: {
|
|
4025
|
-
keys: `keys/${vaultKey}`,
|
|
4026
4342
|
vault: `vault/${vaultKey}`
|
|
4027
4343
|
}
|
|
4028
4344
|
};
|
|
4029
4345
|
}
|
|
4346
|
+
function resolveSecretListPrefix(options = {}) {
|
|
4347
|
+
const scope = canonicalizeSecretScope(options.scope);
|
|
4348
|
+
return scope === void 0 ? "vault/secrets/" : `vault/secrets/scoped/${scope}/`;
|
|
4349
|
+
}
|
|
4030
4350
|
|
|
4031
4351
|
// src/secrets/SecretsService.ts
|
|
4032
4352
|
function invalidSecretInput(message) {
|
|
@@ -4104,15 +4424,981 @@ var SecretsService = class {
|
|
|
4104
4424
|
};
|
|
4105
4425
|
}
|
|
4106
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";
|
|
4107
5383
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4108
5384
|
0 && (module.exports = {
|
|
4109
5385
|
BaseService,
|
|
5386
|
+
DECRYPT_ACTION,
|
|
5387
|
+
DECRYPT_FACT_TYPE,
|
|
5388
|
+
DECRYPT_RESULT_TYPE,
|
|
5389
|
+
DEFAULT_ENCRYPTION_ALG,
|
|
5390
|
+
DEFAULT_KEY_VERSION,
|
|
4110
5391
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
4111
5392
|
DataVaultService,
|
|
4112
5393
|
DatabaseHandle,
|
|
4113
5394
|
DuckDbAction,
|
|
4114
5395
|
DuckDbDatabaseHandle,
|
|
4115
5396
|
DuckDbService,
|
|
5397
|
+
ENCRYPTION_NETWORK_URN_PREFIX,
|
|
5398
|
+
ENCRYPTION_SERVICE,
|
|
5399
|
+
ENCRYPTION_SERVICE_SHORT,
|
|
5400
|
+
ENVELOPE_VERSION,
|
|
5401
|
+
EncryptionService,
|
|
4116
5402
|
ErrorCodes,
|
|
4117
5403
|
GenericKVResponseSchema,
|
|
4118
5404
|
GenericResultSchema,
|
|
@@ -4122,6 +5408,8 @@ var SecretsService = class {
|
|
|
4122
5408
|
KVListResultSchema,
|
|
4123
5409
|
KVResponseHeadersSchema,
|
|
4124
5410
|
KVService,
|
|
5411
|
+
NETWORK_NAME_PATTERN,
|
|
5412
|
+
NetworkIdError,
|
|
4125
5413
|
PrefixedKVService,
|
|
4126
5414
|
RetryPolicySchema,
|
|
4127
5415
|
SECRET_NAME_RE,
|
|
@@ -4143,23 +5431,51 @@ var SecretsService = class {
|
|
|
4143
5431
|
authExpiredError,
|
|
4144
5432
|
authRequiredError,
|
|
4145
5433
|
authUnauthorizedError,
|
|
5434
|
+
base64Decode,
|
|
5435
|
+
base64Encode,
|
|
5436
|
+
buildCanonicalDecryptRequest,
|
|
5437
|
+
buildDecryptAttenuation,
|
|
5438
|
+
buildDecryptFacts,
|
|
5439
|
+
buildDecryptInvocation,
|
|
5440
|
+
buildNetworkId,
|
|
5441
|
+
canonicalHashHex,
|
|
5442
|
+
canonicalSignedResponse,
|
|
5443
|
+
canonicalizeEncryptionJson,
|
|
4146
5444
|
canonicalizeSecretScope,
|
|
5445
|
+
checkDecryptInvocationInput,
|
|
4147
5446
|
createKVResponseSchema,
|
|
4148
5447
|
createResultSchema,
|
|
4149
5448
|
createVaultCrypto,
|
|
5449
|
+
decryptEnvelopeWithKey,
|
|
4150
5450
|
defaultRetryPolicy,
|
|
5451
|
+
deriveSignedReceiverKey,
|
|
5452
|
+
discoverNetwork,
|
|
5453
|
+
encryptToNetwork,
|
|
5454
|
+
encryptionError,
|
|
5455
|
+
ensureNetworkUsableForDecrypt,
|
|
4151
5456
|
err,
|
|
4152
5457
|
errorResult,
|
|
5458
|
+
generateRandomReceiverKey,
|
|
5459
|
+
hexDecode,
|
|
5460
|
+
hexEncode,
|
|
5461
|
+
isNetworkId,
|
|
5462
|
+
networkDiscoveryKey,
|
|
4153
5463
|
networkError,
|
|
4154
5464
|
notFoundError,
|
|
4155
5465
|
ok,
|
|
5466
|
+
openWrappedKey,
|
|
4156
5467
|
parseAuthError,
|
|
5468
|
+
parseNetworkId,
|
|
4157
5469
|
permissionDeniedError,
|
|
5470
|
+
resolveSecretListPrefix,
|
|
4158
5471
|
resolveSecretPath,
|
|
4159
5472
|
serviceError,
|
|
4160
5473
|
storageLimitReachedError,
|
|
4161
5474
|
storageQuotaExceededError,
|
|
4162
5475
|
timeoutError,
|
|
5476
|
+
utf8Decode,
|
|
5477
|
+
utf8Encode,
|
|
5478
|
+
validateEnvelope,
|
|
4163
5479
|
validateKVListResponse,
|
|
4164
5480
|
validateKVResponseHeaders,
|
|
4165
5481
|
validateRetryPolicy,
|
|
@@ -4167,6 +5483,7 @@ var SecretsService = class {
|
|
|
4167
5483
|
validateServiceRequestEvent,
|
|
4168
5484
|
validateServiceResponseEvent,
|
|
4169
5485
|
validateServiceSession,
|
|
5486
|
+
verifyDecryptResponse,
|
|
4170
5487
|
wrapError
|
|
4171
5488
|
});
|
|
4172
5489
|
//# sourceMappingURL=index.cjs.map
|