@storacha/encrypt-upload-client 0.0.39 → 1.0.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.
Files changed (55) hide show
  1. package/dist/config/env.d.ts.map +1 -1
  2. package/dist/config/env.js +19 -6
  3. package/dist/core/client.d.ts +8 -12
  4. package/dist/core/client.d.ts.map +1 -1
  5. package/dist/core/client.js +12 -21
  6. package/dist/core/metadata/encrypted-metadata.d.ts +8 -0
  7. package/dist/core/metadata/encrypted-metadata.d.ts.map +1 -0
  8. package/dist/core/metadata/encrypted-metadata.js +69 -0
  9. package/dist/core/metadata/kms-metadata.d.ts +36 -0
  10. package/dist/core/metadata/kms-metadata.d.ts.map +1 -0
  11. package/dist/core/metadata/kms-metadata.js +156 -0
  12. package/dist/core/{encrypted-metadata.d.ts → metadata/lit-metadata.d.ts} +11 -11
  13. package/dist/core/metadata/lit-metadata.d.ts.map +1 -0
  14. package/dist/core/{encrypted-metadata.js → metadata/lit-metadata.js} +32 -42
  15. package/dist/crypto/adapters/kms-crypto-adapter.d.ts +148 -0
  16. package/dist/crypto/adapters/kms-crypto-adapter.d.ts.map +1 -0
  17. package/dist/crypto/adapters/kms-crypto-adapter.js +321 -0
  18. package/dist/crypto/adapters/lit-crypto-adapter.d.ts +96 -0
  19. package/dist/crypto/adapters/lit-crypto-adapter.d.ts.map +1 -0
  20. package/dist/crypto/adapters/lit-crypto-adapter.js +210 -0
  21. package/dist/crypto/factories.browser.d.ts +11 -0
  22. package/dist/crypto/factories.browser.d.ts.map +1 -0
  23. package/dist/crypto/factories.browser.js +16 -0
  24. package/dist/crypto/factories.node.d.ts +26 -0
  25. package/dist/crypto/factories.node.d.ts.map +1 -0
  26. package/dist/crypto/factories.node.js +38 -0
  27. package/dist/crypto/index.d.ts +5 -0
  28. package/dist/crypto/index.d.ts.map +1 -0
  29. package/dist/crypto/index.js +7 -0
  30. package/dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.d.ts +76 -0
  31. package/dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.d.ts.map +1 -0
  32. package/dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.js +177 -0
  33. package/dist/crypto/symmetric/node-aes-cbc-crypto.d.ts +43 -0
  34. package/dist/crypto/symmetric/node-aes-cbc-crypto.d.ts.map +1 -0
  35. package/dist/crypto/symmetric/node-aes-cbc-crypto.js +110 -0
  36. package/dist/handlers/decrypt-handler.d.ts +9 -4
  37. package/dist/handlers/decrypt-handler.d.ts.map +1 -1
  38. package/dist/handlers/decrypt-handler.js +62 -93
  39. package/dist/handlers/encrypt-handler.d.ts +1 -1
  40. package/dist/handlers/encrypt-handler.d.ts.map +1 -1
  41. package/dist/handlers/encrypt-handler.js +31 -41
  42. package/dist/index.d.ts +0 -1
  43. package/dist/index.js +0 -1
  44. package/dist/protocols/lit.d.ts +1 -3
  45. package/dist/protocols/lit.d.ts.map +1 -1
  46. package/dist/types.d.ts +135 -20
  47. package/dist/types.d.ts.map +1 -1
  48. package/package.json +27 -18
  49. package/dist/core/encrypted-metadata.d.ts.map +0 -1
  50. package/dist/crypto-adapters/browser-crypto-adapter.d.ts +0 -42
  51. package/dist/crypto-adapters/browser-crypto-adapter.d.ts.map +0 -1
  52. package/dist/crypto-adapters/browser-crypto-adapter.js +0 -109
  53. package/dist/crypto-adapters/node-crypto-adapter.d.ts +0 -17
  54. package/dist/crypto-adapters/node-crypto-adapter.d.ts.map +0 -1
  55. package/dist/crypto-adapters/node-crypto-adapter.js +0 -66
@@ -0,0 +1,177 @@
1
+ import * as Type from '../../types.js';
2
+ const ENCRYPTION_ALGORITHM = 'AES-CTR';
3
+ const KEY_LENGTH = 256; // bits
4
+ const IV_LENGTH = 16; // bytes (128 bits, used as counter)
5
+ const COUNTER_LENGTH = 64; // bits (Web Crypto API default for AES-CTR)
6
+ /**
7
+ * GenericAesCtrStreamingCrypto implements TRUE streaming AES-CTR encryption for any JavaScript environment.
8
+ *
9
+ * This implementation:
10
+ * - Uses Web Crypto API (available in both Node.js 16+ and modern browsers)
11
+ * - Emits encrypted chunks immediately without buffering
12
+ * - Supports files of any size with bounded memory usage
13
+ * - Uses TransformStream for clean, standardized streaming
14
+ * - Provides identical results across Node.js and browser environments
15
+ *
16
+ * Key features:
17
+ * - Memory usage: O(1) - constant memory regardless of file size
18
+ * - Supports unlimited file sizes (1TB+)
19
+ * - Cross-platform compatibility (Node.js 16+ and modern browsers)
20
+ * - Clean streaming implementation with automatic resource management
21
+ * - Built-in error handling via TransformStream
22
+ *
23
+ * @class
24
+ * @implements {Type.SymmetricCrypto}
25
+ */
26
+ export class GenericAesCtrStreamingCrypto {
27
+ constructor() {
28
+ if (typeof globalThis.crypto === 'undefined') {
29
+ throw new Error('Web Crypto API is not available.');
30
+ }
31
+ }
32
+ /**
33
+ * Generate a random AES key
34
+ *
35
+ * @returns {Promise<Uint8Array>} A random AES key
36
+ */
37
+ async generateKey() {
38
+ return globalThis.crypto.getRandomValues(new Uint8Array(KEY_LENGTH / 8));
39
+ }
40
+ /**
41
+ * Properly increment AES-CTR counter with 128-bit arithmetic
42
+ *
43
+ * @param {Uint8Array} counter - The base counter (16 bytes)
44
+ * @param {number} increment - The value to add
45
+ * @returns {Uint8Array} - New counter with proper carry propagation
46
+ */
47
+ incrementCounter(counter, increment) {
48
+ const result = new Uint8Array(counter);
49
+ let carry = increment;
50
+ // Implement proper 128-bit arithmetic with carry propagation
51
+ // Start from the least significant byte (rightmost) and propagate carry
52
+ for (let i = result.length - 1; i >= 0 && carry > 0; i--) {
53
+ const sum = result[i] + carry;
54
+ result[i] = sum & 0xff; // Keep only the low 8 bits
55
+ carry = sum >> 8; // Carry the high bits to next position
56
+ }
57
+ // Check for counter overflow (extremely unlikely with 128-bit counter)
58
+ if (carry > 0) {
59
+ throw new Error('Counter overflow: exceeded 128-bit limit. This should never happen in practice.');
60
+ }
61
+ return result;
62
+ }
63
+ /**
64
+ * Encrypt a stream of data using AES-CTR with TRUE streaming (no buffering).
65
+ *
66
+ * @param {Blob} data The data to encrypt.
67
+ * @returns {Promise<{ key: Uint8Array, iv: Uint8Array, encryptedStream: ReadableStream }>}
68
+ */
69
+ async encryptStream(data) {
70
+ const key = await this.generateKey();
71
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(IV_LENGTH));
72
+ // Pre-import the crypto key for reuse across chunks
73
+ const cryptoKey = await globalThis.crypto.subtle.importKey('raw', key, { name: ENCRYPTION_ALGORITHM }, false, ['encrypt', 'decrypt']);
74
+ // State for AES-CTR counter management
75
+ let counter = new Uint8Array(iv); // Copy the IV for counter
76
+ let totalBlocks = 0; // Track total blocks processed
77
+ // Create TransformStream (inspired by Node.js approach)
78
+ const encryptTransform = new TransformStream({
79
+ transform: async (chunk, controller) => {
80
+ try {
81
+ // SECURITY: Calculate counter based on total blocks, not chunks
82
+ const chunkCounter = this.incrementCounter(counter, totalBlocks);
83
+ // SECURITY: Increment by blocks in this chunk (16 bytes per block)
84
+ const blocksInChunk = Math.ceil(chunk.length / 16);
85
+ totalBlocks += blocksInChunk;
86
+ // Encrypt chunk using Web Crypto API
87
+ const encrypted = new Uint8Array(await globalThis.crypto.subtle.encrypt({
88
+ name: ENCRYPTION_ALGORITHM,
89
+ counter: chunkCounter,
90
+ length: COUNTER_LENGTH,
91
+ }, cryptoKey, chunk));
92
+ controller.enqueue(encrypted);
93
+ }
94
+ catch (error) {
95
+ controller.error(error);
96
+ }
97
+ },
98
+ // Note: No flush needed for AES-CTR (unlike CBC which needs final padding)
99
+ });
100
+ const encryptedStream = data.stream().pipeThrough(encryptTransform);
101
+ return { key, iv, encryptedStream };
102
+ }
103
+ /**
104
+ * Decrypt a stream of data using AES-CTR with TRUE streaming (no buffering).
105
+ *
106
+ * @param {ReadableStream} encryptedData The encrypted data stream.
107
+ * @param {Uint8Array} key The encryption key.
108
+ * @param {Uint8Array} iv The initialization vector (counter).
109
+ * @returns {Promise<ReadableStream>} A stream of decrypted data.
110
+ */
111
+ async decryptStream(encryptedData, key, iv) {
112
+ // Pre-import the crypto key for reuse across chunks
113
+ const cryptoKey = await globalThis.crypto.subtle.importKey('raw', key, { name: ENCRYPTION_ALGORITHM }, false, ['encrypt', 'decrypt']);
114
+ // State for AES-CTR counter management
115
+ let counter = new Uint8Array(iv);
116
+ let totalBlocks = 0; // Track total blocks processed (CRITICAL for security)
117
+ // Create TransformStream (inspired by Node.js approach)
118
+ const decryptTransform = new TransformStream({
119
+ transform: async (chunk, controller) => {
120
+ try {
121
+ // SECURITY: Calculate counter based on total blocks, not chunks
122
+ const chunkCounter = this.incrementCounter(counter, totalBlocks);
123
+ // SECURITY: Increment by blocks in this chunk (16 bytes per block)
124
+ const blocksInChunk = Math.ceil(chunk.length / 16);
125
+ totalBlocks += blocksInChunk;
126
+ // Decrypt chunk using Web Crypto API
127
+ const decrypted = new Uint8Array(await globalThis.crypto.subtle.decrypt({
128
+ name: ENCRYPTION_ALGORITHM,
129
+ counter: chunkCounter,
130
+ length: COUNTER_LENGTH,
131
+ }, cryptoKey, chunk));
132
+ controller.enqueue(decrypted);
133
+ }
134
+ catch (error) {
135
+ controller.error(error);
136
+ }
137
+ },
138
+ // Note: No flush needed for AES-CTR (unlike CBC which needs final padding)
139
+ });
140
+ return encryptedData.pipeThrough(decryptTransform);
141
+ }
142
+ /**
143
+ * Combine key and IV into a single array for AES-CTR
144
+ *
145
+ * @param {Uint8Array} key - The AES key (KEY_LENGTH/8 bytes)
146
+ * @param {Uint8Array} iv - The AES-CTR IV (IV_LENGTH bytes)
147
+ * @returns {Uint8Array} Combined key and IV (KEY_LENGTH/8 + IV_LENGTH bytes)
148
+ */
149
+ combineKeyAndIV(key, iv) {
150
+ const keyBytes = KEY_LENGTH / 8;
151
+ if (key.length !== keyBytes) {
152
+ throw new Error(`AES-${KEY_LENGTH} key must be ${keyBytes} bytes, got ${key.length}`);
153
+ }
154
+ if (iv.length !== IV_LENGTH) {
155
+ throw new Error(`AES-CTR IV must be ${IV_LENGTH} bytes, got ${iv.length}`);
156
+ }
157
+ return new Uint8Array([...key, ...iv]);
158
+ }
159
+ /**
160
+ * Split combined key and IV for AES-CTR
161
+ *
162
+ * @param {Uint8Array} combined - Combined key and IV (KEY_LENGTH/8 + IV_LENGTH bytes)
163
+ * @returns {{ key: Uint8Array, iv: Uint8Array }} Separated key and IV
164
+ */
165
+ splitKeyAndIV(combined) {
166
+ const keyBytes = KEY_LENGTH / 8;
167
+ const expectedLength = keyBytes + IV_LENGTH;
168
+ if (combined.length !== expectedLength) {
169
+ throw new Error(`AES-${KEY_LENGTH}-CTR combined key+IV must be ${expectedLength} bytes, got ${combined.length}`);
170
+ }
171
+ return {
172
+ key: combined.subarray(0, keyBytes),
173
+ iv: combined.subarray(keyBytes, keyBytes + IV_LENGTH),
174
+ };
175
+ }
176
+ }
177
+ //# sourceMappingURL=generic-aes-ctr-streaming-crypto.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * NodeAesCbcCrypto implements AES-CBC symmetric encryption for Node.js environments.
3
+ * It uses AES-CBC mode for encryption via the Node.js crypto module.
4
+ * If you already encrypted a file with this class, you still need to use this class to decrypt it.
5
+ *
6
+ * @deprecated Use GenericAesCtrStreamingCrypto instead for new uploads
7
+ * @class
8
+ * @implements {Type.SymmetricCrypto}
9
+ */
10
+ export class NodeAesCbcCrypto implements Type.SymmetricCrypto {
11
+ /** @param {Type.BlobLike} data */
12
+ encryptStream(data: Type.BlobLike): Promise<{
13
+ key: Buffer<ArrayBufferLike>;
14
+ iv: Buffer<ArrayBufferLike>;
15
+ encryptedStream: ReadableStream<any>;
16
+ }>;
17
+ /**
18
+ * @param {ReadableStream} encryptedData
19
+ * @param {Uint8Array} key
20
+ * @param {Uint8Array} iv
21
+ */
22
+ decryptStream(encryptedData: ReadableStream, key: Uint8Array, iv: Uint8Array): Promise<ReadableStream<any>>;
23
+ /**
24
+ * Combine key and IV into a single array for AES-CBC
25
+ *
26
+ * @param {Uint8Array} key - The AES key (KEY_LENGTH/8 bytes)
27
+ * @param {Uint8Array} iv - The AES-CBC IV (IV_LENGTH bytes)
28
+ * @returns {Uint8Array} Combined key and IV (KEY_LENGTH/8 + IV_LENGTH bytes)
29
+ */
30
+ combineKeyAndIV(key: Uint8Array, iv: Uint8Array): Uint8Array;
31
+ /**
32
+ * Split combined key and IV for AES-CBC
33
+ *
34
+ * @param {Uint8Array} combined - Combined key and IV (KEY_LENGTH/8 + IV_LENGTH bytes)
35
+ * @returns {{ key: Uint8Array, iv: Uint8Array }} Separated key and IV
36
+ */
37
+ splitKeyAndIV(combined: Uint8Array): {
38
+ key: Uint8Array;
39
+ iv: Uint8Array;
40
+ };
41
+ }
42
+ import * as Type from '../../types.js';
43
+ //# sourceMappingURL=node-aes-cbc-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-aes-cbc-crypto.d.ts","sourceRoot":"","sources":["../../../src/crypto/symmetric/node-aes-cbc-crypto.js"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,yCAFgB,IAAI,CAAC,eAAe;IAGlC,mCAAmC;IACnC,oBADY,IAAI,CAAC,QAAQ;;;;OA+BxB;IAED;;;;OAIG;IACH,6BAJW,cAAc,OACd,UAAU,MACV,UAAU,gCA8BpB;IAED;;;;;;OAMG;IACH,qBAJW,UAAU,MACV,UAAU,GACR,UAAU,CAatB;IAED;;;;;OAKG;IACH,wBAHW,UAAU,GACR;QAAE,GAAG,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,UAAU,CAAA;KAAE,CAc/C;CACF;sBA3HqB,gBAAgB"}
@@ -0,0 +1,110 @@
1
+ import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
2
+ import * as Type from '../../types.js';
3
+ const ENCRYPTION_ALGORITHM = 'aes-256-cbc';
4
+ const KEY_LENGTH = 256; // bits
5
+ const IV_LENGTH = 16; // bytes (128 bits, used as initialization vector)
6
+ /**
7
+ * NodeAesCbcCrypto implements AES-CBC symmetric encryption for Node.js environments.
8
+ * It uses AES-CBC mode for encryption via the Node.js crypto module.
9
+ * If you already encrypted a file with this class, you still need to use this class to decrypt it.
10
+ *
11
+ * @deprecated Use GenericAesCtrStreamingCrypto instead for new uploads
12
+ * @class
13
+ * @implements {Type.SymmetricCrypto}
14
+ */
15
+ export class NodeAesCbcCrypto {
16
+ /** @param {Type.BlobLike} data */
17
+ async encryptStream(data) {
18
+ const symmetricKey = randomBytes(KEY_LENGTH / 8); // KEY_LENGTH bits for AES
19
+ const initializationVector = randomBytes(IV_LENGTH); // IV_LENGTH bytes for AES
20
+ const cipher = createCipheriv(ENCRYPTION_ALGORITHM, symmetricKey, initializationVector);
21
+ const encryptStream = new TransformStream({
22
+ transform: async (chunk, controller) => {
23
+ const encryptedChunk = cipher.update(chunk);
24
+ if (encryptedChunk.length) {
25
+ controller.enqueue(encryptedChunk);
26
+ }
27
+ },
28
+ flush: (controller) => {
29
+ const final = cipher.final();
30
+ if (final.length) {
31
+ controller.enqueue(final);
32
+ }
33
+ },
34
+ });
35
+ return Promise.resolve({
36
+ key: symmetricKey,
37
+ iv: initializationVector,
38
+ encryptedStream: data.stream().pipeThrough(encryptStream),
39
+ });
40
+ }
41
+ /**
42
+ * @param {ReadableStream} encryptedData
43
+ * @param {Uint8Array} key
44
+ * @param {Uint8Array} iv
45
+ */
46
+ async decryptStream(encryptedData, key, iv) {
47
+ const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
48
+ const decryptor = new TransformStream({
49
+ async transform(chunk, controller) {
50
+ try {
51
+ const decryptedChunk = decipher.update(chunk);
52
+ if (decryptedChunk.length > 0) {
53
+ controller.enqueue(decryptedChunk);
54
+ }
55
+ }
56
+ catch (err) {
57
+ controller.error(err);
58
+ }
59
+ },
60
+ flush(controller) {
61
+ try {
62
+ const finalChunk = decipher.final();
63
+ if (finalChunk.length > 0) {
64
+ controller.enqueue(finalChunk);
65
+ }
66
+ controller.terminate();
67
+ }
68
+ catch (err) {
69
+ controller.error(err);
70
+ }
71
+ },
72
+ });
73
+ return Promise.resolve(encryptedData.pipeThrough(decryptor));
74
+ }
75
+ /**
76
+ * Combine key and IV into a single array for AES-CBC
77
+ *
78
+ * @param {Uint8Array} key - The AES key (KEY_LENGTH/8 bytes)
79
+ * @param {Uint8Array} iv - The AES-CBC IV (IV_LENGTH bytes)
80
+ * @returns {Uint8Array} Combined key and IV (KEY_LENGTH/8 + IV_LENGTH bytes)
81
+ */
82
+ combineKeyAndIV(key, iv) {
83
+ const keyBytes = KEY_LENGTH / 8;
84
+ if (key.length !== keyBytes) {
85
+ throw new Error(`AES-${KEY_LENGTH} key must be ${keyBytes} bytes, got ${key.length}`);
86
+ }
87
+ if (iv.length !== IV_LENGTH) {
88
+ throw new Error(`AES-CBC IV must be ${IV_LENGTH} bytes, got ${iv.length}`);
89
+ }
90
+ return new Uint8Array([...key, ...iv]);
91
+ }
92
+ /**
93
+ * Split combined key and IV for AES-CBC
94
+ *
95
+ * @param {Uint8Array} combined - Combined key and IV (KEY_LENGTH/8 + IV_LENGTH bytes)
96
+ * @returns {{ key: Uint8Array, iv: Uint8Array }} Separated key and IV
97
+ */
98
+ splitKeyAndIV(combined) {
99
+ const keyBytes = KEY_LENGTH / 8;
100
+ const expectedLength = keyBytes + IV_LENGTH;
101
+ if (combined.length !== expectedLength) {
102
+ throw new Error(`AES-${KEY_LENGTH}-CBC combined key+IV must be ${expectedLength} bytes, got ${combined.length}`);
103
+ }
104
+ return {
105
+ key: combined.subarray(0, keyBytes),
106
+ iv: combined.subarray(keyBytes, keyBytes + IV_LENGTH),
107
+ };
108
+ }
109
+ }
110
+ //# sourceMappingURL=node-aes-cbc-crypto.js.map
@@ -1,10 +1,15 @@
1
1
  /**
2
+ * Decrypt file content using the decrypted symmetric key and IV.
3
+ *
2
4
  * @param {Type.CryptoAdapter} cryptoAdapter - The crypto adapter responsible for performing
3
5
  * encryption and decryption operations.
4
- * @param {string} combinedKey
5
- * @param {Uint8Array} content
6
+ * @param {Uint8Array} key - The symmetric key
7
+ * @param {Uint8Array} iv - The initialization vector
8
+ * @param {Uint8Array} content - The encrypted file content
9
+ * @returns {Promise<ReadableStream>} The decrypted file stream
6
10
  */
7
- export function decryptFileWithKey(cryptoAdapter: Type.CryptoAdapter, combinedKey: string, content: Uint8Array): Promise<ReadableStream<any>>;
8
- export function retrieveAndDecrypt(storachaClient: import("@storacha/client").Client, litClient: import("@lit-protocol/lit-node-client").LitNodeClient, cryptoAdapter: Type.CryptoAdapter, gatewayURL: URL, signer: Type.LitWalletSigner | Type.LitPkpSigner, cid: Type.AnyLink, delegationCAR: Uint8Array): Promise<ReadableStream<any>>;
11
+ export function decryptFileWithKey(cryptoAdapter: Type.CryptoAdapter, key: Uint8Array, iv: Uint8Array, content: Uint8Array): Promise<ReadableStream>;
12
+ export function retrieveAndDecrypt(storachaClient: import("@storacha/client").Client, cryptoAdapter: Type.CryptoAdapter, gatewayURL: URL, cid: Type.AnyLink, delegationCAR: Uint8Array, decryptionOptions: Type.DecryptionOptions): Promise<ReadableStream>;
13
+ export function getCarFileFromPublicGateway(gatewayURL: URL, cid: string): Promise<Uint8Array>;
9
14
  import * as Type from '../types.js';
10
15
  //# sourceMappingURL=decrypt-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"decrypt-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/decrypt-handler.js"],"names":[],"mappings":"AAuGA;;;;;GAKG;AACH,kDALW,IAAI,CAAC,aAAa,eAElB,MAAM,WACN,UAAU,gCAuBpB;AA3GM,mDATI,OAAO,kBAAkB,EAAE,MAAM,aACjC,OAAO,+BAA+B,EAAE,aAAa,iBACrD,IAAI,CAAC,aAAa,cAElB,GAAG,UACH,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,OACxC,IAAI,CAAC,OAAO,iBACZ,UAAU,gCAgFpB;sBA9FqB,aAAa"}
1
+ {"version":3,"file":"decrypt-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/decrypt-handler.js"],"names":[],"mappings":"AA4DA;;;;;;;;;GASG;AACH,kDAPW,IAAI,CAAC,aAAa,OAElB,UAAU,MACV,UAAU,WACV,UAAU,GACR,OAAO,CAAC,cAAc,CAAC,CAanC;AA9DM,mDATI,OAAO,kBAAkB,EAAE,MAAM,iBACjC,IAAI,CAAC,aAAa,cAElB,GAAG,OACH,IAAI,CAAC,OAAO,iBACZ,UAAU,qBACV,IAAI,CAAC,iBAAiB,GACpB,OAAO,CAAC,cAAc,CAAC,CAyCnC;AAoCM,wDAJI,GAAG,OACH,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CA+B/B;sBAtHqB,aAAa"}
@@ -1,144 +1,113 @@
1
1
  import { CID } from 'multiformats';
2
- import { CarIndexer } from '@ipld/car';
2
+ import { CarIndexer, CarReader } from '@ipld/car';
3
3
  import { exporter } from 'ipfs-unixfs-exporter';
4
4
  import { MemoryBlockstore } from 'blockstore-core';
5
- import { base64 } from 'multiformats/bases/base64';
6
- import * as Lit from '../protocols/lit.js';
7
5
  import * as Type from '../types.js';
8
- import * as EncryptedMetadata from '../core/encrypted-metadata.js';
9
- import { createDecryptWrappedInvocation } from '../utils.js';
10
6
  /**
11
- * Retrieve and decrypt a file from the IPFS gateway.
7
+ * Retrieve and decrypt a file from the IPFS gateway using any supported encryption strategy.
12
8
  *
13
9
  * @param {import('@storacha/client').Client} storachaClient - The Storacha client
14
- * @param {import('@lit-protocol/lit-node-client').LitNodeClient} litClient - The Lit client
15
10
  * @param {Type.CryptoAdapter} cryptoAdapter - The crypto adapter responsible for performing
16
11
  * encryption and decryption operations.
17
12
  * @param {URL} gatewayURL - The IPFS gateway URL
18
- * @param {Type.LitWalletSigner | Type.LitPkpSigner} signer - The wallet or PKP key signer to decrypt the file
19
13
  * @param {Type.AnyLink} cid - The link to the file to retrieve
20
- * @param {Uint8Array} delegationCAR - The delegation that gives permission to decrypt the file
14
+ * @param {Uint8Array} delegationCAR - The delegation that gives permission to decrypt (required for both strategies)
15
+ * @param {Type.DecryptionOptions} decryptionOptions - User-provided decryption options
16
+ * @returns {Promise<ReadableStream>} The decrypted file stream
21
17
  */
22
- export const retrieveAndDecrypt = async (storachaClient, litClient, cryptoAdapter, gatewayURL, signer, cid, delegationCAR) => {
23
- const encryptedMetadataCar = await getCarFileFromGateway(gatewayURL, cid.toString());
24
- const { encryptedDataCID, identityBoundCiphertext, plaintextKeyHash, accessControlConditions, } = extractEncryptedMetadata(encryptedMetadataCar);
25
- const spaceDID = /** @type {`did:key:${string}`} */ (accessControlConditions[0].parameters[1]);
26
- const encryptedData = await getEncryptedDataFromCar(encryptedMetadataCar, encryptedDataCID);
27
- if (!signer) {
28
- throw new Error('Signer is required');
29
- }
30
- /**
31
- * TODO: check if the wallet has capacity credits, if not get it
32
- */
33
- const acc =
34
- /** @type import('@lit-protocol/types').AccessControlConditions */ (
35
- /** @type {unknown} */ (accessControlConditions));
36
- const expiration = new Date(Date.now() + 1000 * 60 * 5).toISOString(); // 5 min
37
- // TODO: store the session signature (https://developer.litprotocol.com/intro/first-request/generating-session-sigs#nodejs)
38
- let sessionSigs;
39
- if ('wallet' in signer) {
40
- sessionSigs = await Lit.getSessionSigs(litClient, {
41
- wallet: signer.wallet,
42
- dataToEncryptHash: plaintextKeyHash,
43
- expiration,
44
- accessControlConditions: acc,
45
- });
46
- }
47
- else {
48
- sessionSigs = await Lit.getPkpSessionSigs(litClient, {
49
- pkpPublicKey: signer.pkpPublicKey,
50
- authMethod: signer.authMethod,
51
- dataToEncryptHash: plaintextKeyHash,
52
- expiration,
53
- accessControlConditions: acc,
54
- });
55
- }
56
- const wrappedInvocationJSON = await createDecryptWrappedInvocation({
18
+ export const retrieveAndDecrypt = async (storachaClient, cryptoAdapter, gatewayURL, cid, delegationCAR, decryptionOptions) => {
19
+ // Step 1: Get the encrypted metadata from the public gateway
20
+ const encryptedMetadataCar = await getCarFileFromPublicGateway(gatewayURL, cid.toString());
21
+ // Step 2: Extract encrypted metadata from the CAR file
22
+ const metadata = cryptoAdapter.extractEncryptedMetadata(encryptedMetadataCar);
23
+ // Step 3: Get the encrypted data from the CAR file
24
+ const encryptedData = await getEncryptedDataFromCar(encryptedMetadataCar, metadata.encryptedDataCID);
25
+ // Step 4: Decrypt the encrypted symmetric key
26
+ const encryptedSymmetricKey = cryptoAdapter.getEncryptedKey(metadata);
27
+ const { key, iv } = await cryptoAdapter.decryptSymmetricKey(encryptedSymmetricKey, {
28
+ decryptionOptions,
29
+ metadata,
57
30
  delegationCAR,
58
- spaceDID,
59
31
  resourceCID: cid,
60
32
  issuer: storachaClient.agent.issuer,
61
33
  audience: storachaClient.defaultProvider(),
62
- expiration: new Date(Date.now() + 1000 * 60 * 10).getTime(), // 10 min
63
34
  });
64
- const decryptKey = await Lit.executeUcanValidationAction(litClient, {
65
- sessionSigs,
66
- spaceDID,
67
- identityBoundCiphertext,
68
- plaintextKeyHash,
69
- accessControlConditions,
70
- wrappedInvocationJSON,
71
- });
72
- return decryptFileWithKey(cryptoAdapter, decryptKey, encryptedData);
35
+ // Step 5: Decrypt the encrypted file content using the decrypted symmetric key and IV
36
+ return decryptFileWithKey(cryptoAdapter, key, iv, encryptedData);
73
37
  };
74
38
  /**
39
+ * Decrypt file content using the decrypted symmetric key and IV.
40
+ *
75
41
  * @param {Type.CryptoAdapter} cryptoAdapter - The crypto adapter responsible for performing
76
42
  * encryption and decryption operations.
77
- * @param {string} combinedKey
78
- * @param {Uint8Array} content
43
+ * @param {Uint8Array} key - The symmetric key
44
+ * @param {Uint8Array} iv - The initialization vector
45
+ * @param {Uint8Array} content - The encrypted file content
46
+ * @returns {Promise<ReadableStream>} The decrypted file stream
79
47
  */
80
- export function decryptFileWithKey(cryptoAdapter, combinedKey, content) {
81
- // Split the decrypted data back into key and initializationVector
82
- const decryptedKeyData = base64.decode(combinedKey);
83
- const symmetricKey = decryptedKeyData.subarray(0, 32);
84
- const initializationVector = decryptedKeyData.subarray(32);
85
- // Create a ReadableStream from the Uint8Array
48
+ export function decryptFileWithKey(cryptoAdapter, key, iv, content) {
86
49
  const contentStream = new ReadableStream({
87
50
  start(controller) {
88
51
  controller.enqueue(content);
89
52
  controller.close();
90
53
  },
91
54
  });
92
- const decryptedStream = cryptoAdapter.decryptStream(contentStream, symmetricKey, initializationVector);
55
+ const decryptedStream = cryptoAdapter.decryptStream(contentStream, key, iv);
93
56
  return decryptedStream;
94
57
  }
95
58
  /**
59
+ * Fetch a CAR file from the public IPFS gateway with root CID verification.
96
60
  *
97
- * @param {URL} gatewayURL
98
- * @param {string} cid
61
+ * SECURITY: This function provides metadata integrity protection (P0.2).
62
+ * Verifies the returned CAR matches the requested CID to prevent metadata tampering.
63
+ * Content integrity (P2.2) is handled by existing IPFS tools in getEncryptedDataFromCar.
64
+ *
65
+ * @param {URL} gatewayURL - The IPFS gateway URL
66
+ * @param {string} cid - The CID to fetch
67
+ * @returns {Promise<Uint8Array>} The verified CAR file bytes
99
68
  */
100
- const getCarFileFromGateway = async (gatewayURL, cid) => {
69
+ export const getCarFileFromPublicGateway = async (gatewayURL, cid) => {
101
70
  const url = new URL(`/ipfs/${cid}?format=car`, gatewayURL);
102
71
  const response = await fetch(url);
103
72
  if (!response.ok) {
104
73
  throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
105
74
  }
106
75
  const car = new Uint8Array(await response.arrayBuffer());
107
- return car;
108
- };
109
- /**
110
- *
111
- * @param {Uint8Array} car
112
- */
113
- const extractEncryptedMetadata = (car) => {
114
- const encryptedContentResult = EncryptedMetadata.extract(car);
115
- if (encryptedContentResult.error) {
116
- throw encryptedContentResult.error;
76
+ // SECURITY: Verify the CAR's root CID matches what we requested
77
+ const reader = await CarReader.fromBytes(car);
78
+ const roots = await reader.getRoots();
79
+ const expectedCID = CID.parse(cid);
80
+ if (roots.length !== 1) {
81
+ throw new Error(`CAR file must have exactly one root CID, found ${roots.length}`);
82
+ }
83
+ if (!roots[0].equals(expectedCID)) {
84
+ throw new Error(`CID verification failed: expected ${expectedCID} but CAR contains ${roots[0]}`);
117
85
  }
118
- let encryptedContent = encryptedContentResult.ok.toJSON();
119
- return encryptedContent;
86
+ return car;
120
87
  };
121
88
  /**
89
+ * Extract encrypted data from a CAR file.
122
90
  *
123
- * @param {Uint8Array} car
124
- * @param {string} encryptedDataCID
91
+ * @param {Uint8Array} car - The CAR file bytes
92
+ * @param {string} encryptedDataCID - The CID of the encrypted data
93
+ * @returns {Promise<Uint8Array>} The encrypted data bytes
125
94
  */
126
95
  const getEncryptedDataFromCar = async (car, encryptedDataCID) => {
127
- // NOTE: convert CAR to a block store
96
+ // Step 1: Index the CAR file for efficient block lookup
128
97
  const iterable = await CarIndexer.fromBytes(car);
129
- const blockstore = new MemoryBlockstore();
98
+ const blockIndex = new Map();
130
99
  for await (const { cid, blockLength, blockOffset } of iterable) {
131
- const blockBytes = car.slice(blockOffset, blockOffset + blockLength);
132
- await blockstore.put(cid, blockBytes);
100
+ blockIndex.set(cid.toString(), { blockOffset, blockLength });
133
101
  }
134
- // NOTE: get the encrypted Data from the CAR file
102
+ // Step 2: Use the index to extract the encrypted data block bytes as needed
103
+ const { blockOffset, blockLength } = blockIndex.get(encryptedDataCID);
104
+ const blockBytes = car.subarray(blockOffset, blockOffset + blockLength);
105
+ // Step 3: Put the block in a blockstore for exporter compatibility
106
+ const blockstore = new MemoryBlockstore();
107
+ await blockstore.put(CID.parse(encryptedDataCID), blockBytes);
108
+ // Step 4: Get the encrypted data from the CAR file
135
109
  const encryptedDataEntry = await exporter(CID.parse(encryptedDataCID), blockstore);
136
- const encryptedDataBytes = new Uint8Array(Number(encryptedDataEntry.size));
137
- let offset = 0;
138
- for await (const chunk of encryptedDataEntry.content()) {
139
- encryptedDataBytes.set(chunk, offset);
140
- offset += chunk.length;
141
- }
142
- return encryptedDataBytes;
110
+ // Step 5: Return the async iterable (stream of chunks)
111
+ return encryptedDataEntry.content(); // async iterable of Uint8Array
143
112
  };
144
113
  //# sourceMappingURL=decrypt-handler.js.map
@@ -1,3 +1,3 @@
1
- export function encryptAndUpload(storachaClient: import("@storacha/client").Client, litClient: import("@lit-protocol/lit-node-client").LitNodeClient, cryptoAdapter: Type.CryptoAdapter, file: Type.BlobLike): Promise<Type.AnyLink>;
1
+ export function encryptAndUpload(storachaClient: import("@storacha/client").Client, cryptoAdapter: Type.CryptoAdapter, file: Type.BlobLike, encryptionConfig: Type.EncryptionConfig, uploadOptions?: Type.UploadOptions): Promise<Type.AnyLink>;
2
2
  import * as Type from '../types.js';
3
3
  //# sourceMappingURL=encrypt-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"encrypt-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/encrypt-handler.js"],"names":[],"mappings":"AAkBO,iDAPI,OAAO,kBAAkB,EAAE,MAAM,aACjC,OAAO,+BAA+B,EAAE,aAAa,iBACrD,IAAI,CAAC,aAAa,QAElB,IAAI,CAAC,QAAQ,GACX,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CA4BjC;sBAxCqB,aAAa"}
1
+ {"version":3,"file":"encrypt-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/encrypt-handler.js"],"names":[],"mappings":"AAgBO,iDARI,OAAO,kBAAkB,EAAE,MAAM,iBACjC,IAAI,CAAC,aAAa,QAElB,IAAI,CAAC,QAAQ,oBACb,IAAI,CAAC,gBAAgB,kBACrB,IAAI,CAAC,aAAa,GAChB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CA6BjC;sBAxCqB,aAAa"}