@tinycloud/sdk-services 3.11.0-beta.2 → 3.12.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseService-Ma2MwJAd.d.ts → BaseService-BRDG69KR.d.ts} +1 -1
- package/dist/{BaseService-O0iyuFLk.d.cts → BaseService-CdBkfPGE.d.cts} +1 -1
- package/dist/{IHooksService-4QNODVy6.d.cts → IHooksService-C9p3stsB.d.cts} +1 -1
- package/dist/{IHooksService-CvTpJbQD.d.ts → IHooksService-D6eyZat1.d.ts} +1 -1
- package/dist/{ISQLService-Aly1CBsW.d.ts → ISQLService-B_4KcGA-.d.ts} +1 -1
- package/dist/{ISQLService-BOgWzHLT.d.cts → ISQLService-C6nNpcls.d.cts} +1 -1
- package/dist/context.cjs +4 -0
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.cts +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.js +4 -0
- package/dist/context.js.map +1 -1
- package/dist/encryption/index.cjs +276 -14
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +26 -28
- package/dist/encryption/index.d.ts +26 -28
- package/dist/encryption/index.js +268 -14
- package/dist/encryption/index.js.map +1 -1
- package/dist/encryption/named-keys.cjs +225 -14
- package/dist/encryption/named-keys.cjs.map +1 -1
- package/dist/encryption/named-keys.d.cts +7 -2
- package/dist/encryption/named-keys.d.ts +7 -2
- package/dist/encryption/named-keys.js +225 -14
- package/dist/encryption/named-keys.js.map +1 -1
- package/dist/encryption/resource.cjs.map +1 -1
- package/dist/encryption/resource.js.map +1 -1
- package/dist/feed/index.cjs +4 -0
- package/dist/feed/index.cjs.map +1 -1
- package/dist/feed/index.d.cts +3 -3
- package/dist/feed/index.d.ts +3 -3
- package/dist/feed/index.js +4 -0
- package/dist/feed/index.js.map +1 -1
- package/dist/index.cjs +509 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +499 -18
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +34 -0
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +4 -4
- package/dist/kv/index.d.ts +4 -4
- package/dist/kv/index.js +34 -0
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +39 -8
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -3
- package/dist/kv/replication/index.d.ts +3 -3
- package/dist/kv/replication/index.js +38 -7
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/{scope-DEgc9f-W.d.cts → scope-DtcbyRKY.d.cts} +1 -1
- package/dist/{scope-BZr0QwxX.d.ts → scope-J20PQP3u.d.ts} +1 -1
- package/dist/sql/index.cjs +36 -0
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +4 -4
- package/dist/sql/index.d.ts +4 -4
- package/dist/sql/index.js +36 -0
- package/dist/sql/index.js.map +1 -1
- package/dist/telemetry/requestLog.cjs +4 -0
- package/dist/telemetry/requestLog.cjs.map +1 -1
- package/dist/telemetry/requestLog.d.cts +1 -1
- package/dist/telemetry/requestLog.d.ts +1 -1
- package/dist/telemetry/requestLog.js +4 -0
- package/dist/telemetry/requestLog.js.map +1 -1
- package/dist/{types-D4XbIfFJ.d.cts → types-DfqNz9Nu.d.cts} +135 -6
- package/dist/{types-CSd0RYir.d.ts → types-DjAP8dve.d.cts} +4 -0
- package/dist/{types-CSd0RYir.d.cts → types-DjAP8dve.d.ts} +4 -0
- package/dist/{types-Bf91gup6.d.ts → types-Nvi_RckJ.d.ts} +135 -6
- package/package.json +4 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { E as EncryptionCrypto, I as InlineEncryptedEnvelope, J as Json } from '../types-
|
|
1
|
+
import { E as EncryptionCrypto, I as InlineEncryptedEnvelope, J as Json } from '../types-Nvi_RckJ.js';
|
|
2
2
|
import { ParsedKeyResource } from './resource.js';
|
|
3
|
-
import '../types-
|
|
3
|
+
import '../types-DjAP8dve.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Named-key client: key admin, descriptor reads, `ensureKey` and key-scoped
|
|
@@ -103,6 +103,10 @@ interface NamedKeyInvocationInput {
|
|
|
103
103
|
interface NamedKeyClientConfig {
|
|
104
104
|
host: string;
|
|
105
105
|
crypto: EncryptionCrypto;
|
|
106
|
+
/** Write TCW2 wraps; disabled by default. */
|
|
107
|
+
keyWrapV2?: boolean;
|
|
108
|
+
/** Require TCW2 incoming network-key wraps, not signed receiver responses. Disabled by default. */
|
|
109
|
+
requireKeyWrapV2?: boolean;
|
|
106
110
|
fetch: typeof fetch;
|
|
107
111
|
/** Network used when a call does not name one. */
|
|
108
112
|
defaultNetworkId: () => string;
|
|
@@ -130,6 +134,7 @@ interface KeyCallOptions {
|
|
|
130
134
|
networkId?: string;
|
|
131
135
|
}
|
|
132
136
|
interface KeyEncryptOptions extends KeyCallOptions {
|
|
137
|
+
keyWrapV2?: boolean;
|
|
133
138
|
aad?: Uint8Array;
|
|
134
139
|
metadata?: Record<string, string>;
|
|
135
140
|
}
|
|
@@ -85,9 +85,9 @@ function base64Decode(s) {
|
|
|
85
85
|
function utf8Encode(s) {
|
|
86
86
|
return new TextEncoder().encode(s);
|
|
87
87
|
}
|
|
88
|
-
function canonicalHashHex(
|
|
88
|
+
function canonicalHashHex(sha2562, value) {
|
|
89
89
|
const canonical = canonicalize(value);
|
|
90
|
-
return hexEncode(
|
|
90
|
+
return hexEncode(sha2562(utf8Encode(canonical)));
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// src/encryption/networkId.ts
|
|
@@ -135,6 +135,26 @@ function parseNetworkId(networkId) {
|
|
|
135
135
|
}
|
|
136
136
|
return { networkId, ownerDid, name };
|
|
137
137
|
}
|
|
138
|
+
function parseEncryptionResource(resource) {
|
|
139
|
+
const networkSeparator = resource.lastIndexOf(":");
|
|
140
|
+
const nameAndPath = resource.slice(networkSeparator + 1);
|
|
141
|
+
const pathSeparator = nameAndPath.indexOf("/");
|
|
142
|
+
const name = pathSeparator === -1 ? nameAndPath : nameAndPath.slice(0, pathSeparator);
|
|
143
|
+
const networkId = pathSeparator === -1 ? resource : `${resource.slice(0, networkSeparator + 1)}${name}`;
|
|
144
|
+
const parsed = parseNetworkId(networkId);
|
|
145
|
+
if (pathSeparator === -1) return parsed;
|
|
146
|
+
const keyPath = nameAndPath.slice(pathSeparator + 1);
|
|
147
|
+
const segments = keyPath.split("/");
|
|
148
|
+
const prefix = keyPath.endsWith("/");
|
|
149
|
+
if (keyPath.length === 0 || /[\u0000-\u001f\u007f\\]/.test(keyPath) || segments.some(
|
|
150
|
+
(segment, index) => segment === "." || segment === ".." || segment.length === 0 && !(prefix && index === segments.length - 1)
|
|
151
|
+
)) {
|
|
152
|
+
throw new NetworkIdError(
|
|
153
|
+
`encryption resource has an invalid keyPath (got ${JSON.stringify(resource)})`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return { ...parsed, keyPath };
|
|
157
|
+
}
|
|
138
158
|
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
139
159
|
|
|
140
160
|
// src/encryption/receiverKey.ts
|
|
@@ -170,6 +190,8 @@ function defaultEncryptionMessage(input) {
|
|
|
170
190
|
return input.message ?? input.cause.message;
|
|
171
191
|
case "INVALID_INPUT":
|
|
172
192
|
return input.message;
|
|
193
|
+
default:
|
|
194
|
+
return input.message ?? input.code;
|
|
173
195
|
}
|
|
174
196
|
}
|
|
175
197
|
function encryptionError(input) {
|
|
@@ -180,6 +202,152 @@ function encryptionError(input) {
|
|
|
180
202
|
};
|
|
181
203
|
}
|
|
182
204
|
|
|
205
|
+
// src/encryption/keyWrap.ts
|
|
206
|
+
import { gcm } from "@noble/ciphers/aes";
|
|
207
|
+
import { x25519 } from "@noble/curves/ed25519";
|
|
208
|
+
import { hkdf } from "@noble/hashes/hkdf";
|
|
209
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
210
|
+
import { randomBytes } from "@noble/hashes/utils";
|
|
211
|
+
var KEY_WRAP_V2_ALG = "x25519-hkdf-aes256gcm/v2";
|
|
212
|
+
var MAGIC = utf8Encode("TCW2");
|
|
213
|
+
var INFO = utf8Encode("tinycloud.encryption.key-wrap/v2");
|
|
214
|
+
var MAX_HEADER = 65536;
|
|
215
|
+
function concat(...parts) {
|
|
216
|
+
const result = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
|
|
217
|
+
let offset = 0;
|
|
218
|
+
for (const part of parts) {
|
|
219
|
+
result.set(part, offset);
|
|
220
|
+
offset += part.length;
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
function equal(a, b) {
|
|
225
|
+
if (a.length !== b.length) return false;
|
|
226
|
+
let difference = 0;
|
|
227
|
+
for (let i = 0; i < a.length; i++) difference |= a[i] ^ b[i];
|
|
228
|
+
return difference === 0;
|
|
229
|
+
}
|
|
230
|
+
function decode32(value) {
|
|
231
|
+
if (typeof value !== "string") throw new Error("Invalid key wrap encoding");
|
|
232
|
+
const decoded = base64Decode(value);
|
|
233
|
+
if (decoded.length !== 32 || base64Encode(decoded) !== value) throw new Error("Invalid key wrap encoding");
|
|
234
|
+
return decoded;
|
|
235
|
+
}
|
|
236
|
+
function isWellFormed(value) {
|
|
237
|
+
for (let i = 0; i < value.length; i++) {
|
|
238
|
+
const unit = value.charCodeAt(i);
|
|
239
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
240
|
+
const next = value.charCodeAt(++i);
|
|
241
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
242
|
+
} else if (unit >= 56320 && unit <= 57343) return false;
|
|
243
|
+
}
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
function validateContext(context) {
|
|
247
|
+
if (context.purpose !== "network-key" && context.purpose !== "receiver-key" || typeof context.networkId !== "string" || typeof context.keyPath !== "string" || !isWellFormed(context.networkId) || !isWellFormed(context.keyPath) || !Number.isSafeInteger(context.keyVersion) || context.keyVersion <= 0 || !context.metadata || typeof context.metadata !== "object" || Array.isArray(context.metadata) || Object.entries(context.metadata).some(([key, value]) => !isWellFormed(key) || typeof value !== "string" || !isWellFormed(value))) {
|
|
248
|
+
throw new Error("Invalid key wrap context");
|
|
249
|
+
}
|
|
250
|
+
if (context.networkId !== "") {
|
|
251
|
+
const resource = parseEncryptionResource(context.keyPath === "" ? context.networkId : `${context.networkId}/${context.keyPath}`);
|
|
252
|
+
if (resource.networkId !== context.networkId || (resource.keyPath ?? "") !== context.keyPath || context.keyPath.endsWith("/")) {
|
|
253
|
+
throw new Error("Invalid key wrap resource");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function isKeyWrapV2(wrapped) {
|
|
258
|
+
return wrapped.length >= 4 && MAGIC.every((byte, index) => wrapped[index] === byte);
|
|
259
|
+
}
|
|
260
|
+
function readKeyWrapV2(wrapped) {
|
|
261
|
+
if (!isKeyWrapV2(wrapped) || wrapped.length < 8) throw new Error("Invalid v2 key wrap frame");
|
|
262
|
+
const length = new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength).getUint32(4);
|
|
263
|
+
if (length === 0 || length > MAX_HEADER || wrapped.length < 8 + length + 12 + 16) throw new Error("Invalid v2 key wrap length");
|
|
264
|
+
const bytes = wrapped.subarray(8, 8 + length);
|
|
265
|
+
const header = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
266
|
+
const fields = ["v", "alg", "kdf", "aead", "salt", "ephemeralPublicKey", "recipientPublicKey", "purpose", "networkId", "keyPath", "keyVersion", "metadata"].sort();
|
|
267
|
+
if (!header || typeof header !== "object" || Array.isArray(header) || Object.keys(header).sort().join(",") !== fields.join(",") || header.v !== 2 || header.alg !== KEY_WRAP_V2_ALG || header.kdf !== "hkdf-sha256" || header.aead !== "aes-256-gcm") {
|
|
268
|
+
throw new Error("Invalid v2 key wrap header");
|
|
269
|
+
}
|
|
270
|
+
validateContext(header);
|
|
271
|
+
decode32(header.salt);
|
|
272
|
+
decode32(header.ephemeralPublicKey);
|
|
273
|
+
decode32(header.recipientPublicKey);
|
|
274
|
+
if (!equal(bytes, utf8Encode(canonicalize({ ...header })))) throw new Error("Non-canonical v2 key wrap header");
|
|
275
|
+
const end = 8 + length;
|
|
276
|
+
return { header, aad: wrapped.subarray(0, end), nonce: wrapped.subarray(end, end + 12), ciphertext: wrapped.subarray(end + 12) };
|
|
277
|
+
}
|
|
278
|
+
function assertKeyWrapContext(header, expected) {
|
|
279
|
+
validateContext(expected);
|
|
280
|
+
for (const field of ["purpose", "networkId", "keyPath", "keyVersion"]) {
|
|
281
|
+
if (header[field] !== expected[field]) throw new Error("Key wrap context mismatch");
|
|
282
|
+
}
|
|
283
|
+
if (canonicalize(header.metadata) !== canonicalize(expected.metadata)) throw new Error("Key wrap metadata mismatch");
|
|
284
|
+
}
|
|
285
|
+
function deriveKey(secret, ephemeral, recipient, salt) {
|
|
286
|
+
const shared = x25519.getSharedSecret(secret, ephemeral);
|
|
287
|
+
if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
|
|
288
|
+
try {
|
|
289
|
+
return hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipient), 32);
|
|
290
|
+
} finally {
|
|
291
|
+
shared.fill(0);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function sealKeyV2(recipientPublicKey, plaintext, context) {
|
|
295
|
+
validateContext(context);
|
|
296
|
+
if (recipientPublicKey.length !== 32) throw new Error("Invalid recipient public key");
|
|
297
|
+
const secret = randomBytes(32);
|
|
298
|
+
const ephemeral = x25519.getPublicKey(secret);
|
|
299
|
+
const salt = randomBytes(32);
|
|
300
|
+
const header = {
|
|
301
|
+
...context,
|
|
302
|
+
v: 2,
|
|
303
|
+
alg: KEY_WRAP_V2_ALG,
|
|
304
|
+
kdf: "hkdf-sha256",
|
|
305
|
+
aead: "aes-256-gcm",
|
|
306
|
+
salt: base64Encode(salt),
|
|
307
|
+
ephemeralPublicKey: base64Encode(ephemeral),
|
|
308
|
+
recipientPublicKey: base64Encode(recipientPublicKey)
|
|
309
|
+
};
|
|
310
|
+
const bytes = utf8Encode(canonicalize({ ...header }));
|
|
311
|
+
if (bytes.length > MAX_HEADER) throw new Error("Key wrap header too large");
|
|
312
|
+
const prefix = new Uint8Array(8);
|
|
313
|
+
prefix.set(MAGIC);
|
|
314
|
+
new DataView(prefix.buffer).setUint32(4, bytes.length);
|
|
315
|
+
const aad = concat(prefix, bytes);
|
|
316
|
+
const nonce = randomBytes(12);
|
|
317
|
+
let key;
|
|
318
|
+
try {
|
|
319
|
+
const shared = x25519.getSharedSecret(secret, recipientPublicKey);
|
|
320
|
+
try {
|
|
321
|
+
if (shared.every((byte) => byte === 0)) throw new Error("Invalid key agreement");
|
|
322
|
+
key = hkdf(sha256, shared, salt, concat(INFO, ephemeral, recipientPublicKey), 32);
|
|
323
|
+
} finally {
|
|
324
|
+
shared.fill(0);
|
|
325
|
+
}
|
|
326
|
+
return concat(aad, nonce, gcm(key, nonce, aad).encrypt(plaintext));
|
|
327
|
+
} finally {
|
|
328
|
+
secret.fill(0);
|
|
329
|
+
key?.fill(0);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function openKeyV2(recipientPrivateKey, wrapped, expected) {
|
|
333
|
+
const { header, aad, nonce, ciphertext } = readKeyWrapV2(wrapped);
|
|
334
|
+
assertKeyWrapContext(header, expected);
|
|
335
|
+
const recipient = x25519.getPublicKey(recipientPrivateKey);
|
|
336
|
+
if (!equal(recipient, decode32(header.recipientPublicKey))) throw new Error("Key wrap recipient mismatch");
|
|
337
|
+
const key = deriveKey(recipientPrivateKey, decode32(header.ephemeralPublicKey), recipient, decode32(header.salt));
|
|
338
|
+
try {
|
|
339
|
+
return gcm(key, nonce, aad).decrypt(ciphertext);
|
|
340
|
+
} finally {
|
|
341
|
+
key.fill(0);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function assertKeyWrapAlgorithm(wrapped, alg, requireV2 = false) {
|
|
345
|
+
if (alg !== "x25519-aes256gcm/v1" && alg !== KEY_WRAP_V2_ALG) throw new Error("Unsupported key wrap algorithm");
|
|
346
|
+
if (requireV2 && alg !== KEY_WRAP_V2_ALG) throw new Error("V2 network key wrap required");
|
|
347
|
+
if (isKeyWrapV2(wrapped) !== (alg === KEY_WRAP_V2_ALG)) throw new Error("Key wrap algorithm mismatch");
|
|
348
|
+
if (alg === KEY_WRAP_V2_ALG) readKeyWrapV2(wrapped);
|
|
349
|
+
}
|
|
350
|
+
|
|
183
351
|
// src/encryption/response.ts
|
|
184
352
|
function canonicalSignedResponse(response) {
|
|
185
353
|
const { nodeSignature: _drop, ...rest } = response;
|
|
@@ -223,7 +391,7 @@ function verifyDecryptResponse(input) {
|
|
|
223
391
|
})
|
|
224
392
|
};
|
|
225
393
|
}
|
|
226
|
-
if (response.alg !==
|
|
394
|
+
if (response.alg !== "x25519-aes256gcm/v1" && response.alg !== KEY_WRAP_V2_ALG) {
|
|
227
395
|
return {
|
|
228
396
|
ok: false,
|
|
229
397
|
error: encryptionError({
|
|
@@ -280,7 +448,7 @@ function verifyDecryptResponse(input) {
|
|
|
280
448
|
})
|
|
281
449
|
};
|
|
282
450
|
}
|
|
283
|
-
if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !==
|
|
451
|
+
if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== request.alg || facts.keyVersion !== response.keyVersion) {
|
|
284
452
|
return {
|
|
285
453
|
ok: false,
|
|
286
454
|
error: encryptionError({
|
|
@@ -303,11 +471,31 @@ function verifyDecryptResponse(input) {
|
|
|
303
471
|
}
|
|
304
472
|
return { ok: true, data: response };
|
|
305
473
|
}
|
|
306
|
-
function openWrappedKey(crypto, receiverPrivateKey, response) {
|
|
474
|
+
function openWrappedKey(crypto, receiverPrivateKey, response, expected) {
|
|
307
475
|
const wrapped = base64Decode(response.wrappedKey);
|
|
476
|
+
assertKeyWrapAlgorithm(wrapped, response.alg);
|
|
477
|
+
if (response.alg === KEY_WRAP_V2_ALG) {
|
|
478
|
+
if (!expected) throw new Error("V2 response requires expected key context");
|
|
479
|
+
return openKeyV2(receiverPrivateKey, wrapped, expected);
|
|
480
|
+
}
|
|
308
481
|
return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
309
482
|
}
|
|
310
483
|
|
|
484
|
+
// src/encryption/envelope.ts
|
|
485
|
+
function validateEnvelopeKeyWrap(envelope, requireKeyWrapV2 = false) {
|
|
486
|
+
const wrapped = base64Decode(envelope.encryptedSymmetricKey);
|
|
487
|
+
assertKeyWrapAlgorithm(wrapped, envelope.alg, requireKeyWrapV2);
|
|
488
|
+
if (envelope.alg === KEY_WRAP_V2_ALG) {
|
|
489
|
+
assertKeyWrapContext(readKeyWrapV2(wrapped).header, {
|
|
490
|
+
purpose: "network-key",
|
|
491
|
+
networkId: envelope.networkId,
|
|
492
|
+
keyPath: envelope.keyPath ?? "",
|
|
493
|
+
keyVersion: envelope.keyVersion,
|
|
494
|
+
metadata: envelope.metadata ?? {}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
311
499
|
// src/encryption/resource.ts
|
|
312
500
|
import { pkhDid } from "@tinycloud/bootstrap";
|
|
313
501
|
var SEGMENT_SOURCE = "[a-z0-9][a-z0-9._-]{0,63}";
|
|
@@ -390,7 +578,7 @@ function assertPath(path, what) {
|
|
|
390
578
|
);
|
|
391
579
|
}
|
|
392
580
|
}
|
|
393
|
-
function
|
|
581
|
+
function parseEncryptionResource2(uri) {
|
|
394
582
|
if (typeof uri !== "string" || !uri.startsWith(ENCRYPTION_NETWORK_URN_PREFIX)) {
|
|
395
583
|
throw new EncryptionResourceError(
|
|
396
584
|
"not_encryption_urn",
|
|
@@ -451,7 +639,7 @@ function canonicalNetworkId(networkId) {
|
|
|
451
639
|
return parsed.networkId;
|
|
452
640
|
}
|
|
453
641
|
function parseBuilt(resource, networkId) {
|
|
454
|
-
if (
|
|
642
|
+
if (parseEncryptionResource2(resource).networkId !== networkId) {
|
|
455
643
|
throw new EncryptionResourceError(
|
|
456
644
|
"malformed",
|
|
457
645
|
`built resource does not round-trip (got ${JSON.stringify(resource)})`
|
|
@@ -550,7 +738,7 @@ function mapNodeKeyError(status, text, context) {
|
|
|
550
738
|
function requireKeyTarget(resource) {
|
|
551
739
|
let parsed;
|
|
552
740
|
try {
|
|
553
|
-
parsed =
|
|
741
|
+
parsed = parseEncryptionResource2(resource);
|
|
554
742
|
} catch (error) {
|
|
555
743
|
throw new NamedKeyError(
|
|
556
744
|
"INVALID_KEY_TARGET",
|
|
@@ -852,16 +1040,21 @@ var NamedKeyClient = class {
|
|
|
852
1040
|
const { crypto } = this.config;
|
|
853
1041
|
const symmetricKey = crypto.randomBytes(32);
|
|
854
1042
|
const ciphertext = crypto.authEncrypt(symmetricKey, plaintext, options?.aad);
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1043
|
+
const keyWrapV2 = options?.keyWrapV2 ?? this.config.keyWrapV2 ?? false;
|
|
1044
|
+
const publicKey = base64Decode(descriptor.publicEncryptionKey);
|
|
1045
|
+
const wrapped = keyWrapV2 ? sealKeyV2(publicKey, symmetricKey, {
|
|
1046
|
+
purpose: "network-key",
|
|
1047
|
+
networkId: descriptor.networkId,
|
|
1048
|
+
keyPath: descriptor.keyPath,
|
|
1049
|
+
keyVersion: descriptor.keyVersion,
|
|
1050
|
+
metadata: options?.metadata ?? {}
|
|
1051
|
+
}) : crypto.sealToNetworkKey(publicKey, symmetricKey);
|
|
859
1052
|
const encryptedSymmetricKey = base64Encode(wrapped);
|
|
860
1053
|
return {
|
|
861
1054
|
v: NAMED_KEY_ENVELOPE_VERSION,
|
|
862
1055
|
networkId: descriptor.networkId,
|
|
863
1056
|
keyPath: descriptor.keyPath,
|
|
864
|
-
alg:
|
|
1057
|
+
alg: keyWrapV2 ? KEY_WRAP_V2_ALG : DEFAULT_ENCRYPTION_ALG,
|
|
865
1058
|
keyVersion: descriptor.keyVersion,
|
|
866
1059
|
encryptedSymmetricKey,
|
|
867
1060
|
encryptedSymmetricKeyHash: canonicalHashHex(crypto.sha256, encryptedSymmetricKey),
|
|
@@ -906,6 +1099,13 @@ var NamedKeyClient = class {
|
|
|
906
1099
|
return results;
|
|
907
1100
|
}
|
|
908
1101
|
async decryptRoot(envelope) {
|
|
1102
|
+
if (this.config.requireKeyWrapV2) {
|
|
1103
|
+
try {
|
|
1104
|
+
validateEnvelopeKeyWrap(envelope, true);
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
throw new NamedKeyError("INVALID_ENVELOPE", "Envelope key wrap context is invalid", { cause: error });
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
909
1109
|
if (this.config.decryptRoot === void 0) {
|
|
910
1110
|
throw new NamedKeyError("DECRYPT_FAILED", "no root-key decrypt path is configured");
|
|
911
1111
|
}
|
|
@@ -942,6 +1142,11 @@ var NamedKeyClient = class {
|
|
|
942
1142
|
"envelope.encryptedSymmetricKeyHash does not match encryptedSymmetricKey"
|
|
943
1143
|
);
|
|
944
1144
|
}
|
|
1145
|
+
try {
|
|
1146
|
+
validateEnvelopeKeyWrap(e, this.config.requireKeyWrapV2 ?? false);
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
throw new NamedKeyError("INVALID_ENVELOPE", "Envelope key wrap context is invalid", { cause: error });
|
|
1149
|
+
}
|
|
945
1150
|
return e;
|
|
946
1151
|
}
|
|
947
1152
|
async decryptKey(envelope) {
|
|
@@ -1003,7 +1208,13 @@ var NamedKeyClient = class {
|
|
|
1003
1208
|
if (!verified.ok) {
|
|
1004
1209
|
throw new NamedKeyError("INVALID_RESPONSE", safeEcho(verified.error.message), { cause: verified.error });
|
|
1005
1210
|
}
|
|
1006
|
-
const symmetricKey = openWrappedKey(crypto, receiverKey.privateKey, verified.data
|
|
1211
|
+
const symmetricKey = openWrappedKey(crypto, receiverKey.privateKey, verified.data, {
|
|
1212
|
+
purpose: "receiver-key",
|
|
1213
|
+
networkId: body.networkId,
|
|
1214
|
+
keyPath: body.keyPath,
|
|
1215
|
+
keyVersion: body.keyVersion,
|
|
1216
|
+
metadata: {}
|
|
1217
|
+
});
|
|
1007
1218
|
try {
|
|
1008
1219
|
return crypto.authDecrypt(
|
|
1009
1220
|
symmetricKey,
|