@storacha/encrypt-upload-client 1.1.80 → 1.1.82

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 (64) hide show
  1. package/dist/crypto/adapters/kms-crypto-adapter.d.ts +4 -10
  2. package/dist/crypto/adapters/kms-crypto-adapter.d.ts.map +1 -1
  3. package/dist/crypto/adapters/kms-crypto-adapter.js +4 -4
  4. package/dist/examples/decrypt-test.d.ts +2 -0
  5. package/dist/examples/decrypt-test.d.ts.map +1 -0
  6. package/dist/examples/decrypt-test.js +102 -0
  7. package/dist/examples/encrypt-test.d.ts +2 -0
  8. package/dist/examples/encrypt-test.d.ts.map +1 -0
  9. package/dist/examples/encrypt-test.js +93 -0
  10. package/dist/test/cid-verification.spec.d.ts +2 -0
  11. package/dist/test/cid-verification.spec.d.ts.map +1 -0
  12. package/dist/test/cid-verification.spec.js +314 -0
  13. package/dist/test/crypto-compatibility.spec.d.ts +2 -0
  14. package/dist/test/crypto-compatibility.spec.d.ts.map +1 -0
  15. package/dist/test/crypto-compatibility.spec.js +124 -0
  16. package/dist/test/crypto-counter-security.spec.d.ts +2 -0
  17. package/dist/test/crypto-counter-security.spec.d.ts.map +1 -0
  18. package/dist/test/crypto-counter-security.spec.js +147 -0
  19. package/dist/test/crypto-streaming.spec.d.ts +2 -0
  20. package/dist/test/crypto-streaming.spec.d.ts.map +1 -0
  21. package/dist/test/crypto-streaming.spec.js +129 -0
  22. package/dist/test/encrypted-metadata.spec.d.ts +2 -0
  23. package/dist/test/encrypted-metadata.spec.d.ts.map +1 -0
  24. package/dist/test/encrypted-metadata.spec.js +68 -0
  25. package/dist/test/factories.spec.d.ts +2 -0
  26. package/dist/test/factories.spec.d.ts.map +1 -0
  27. package/dist/test/factories.spec.js +129 -0
  28. package/dist/test/file-metadata.spec.d.ts +2 -0
  29. package/dist/test/file-metadata.spec.d.ts.map +1 -0
  30. package/dist/test/file-metadata.spec.js +433 -0
  31. package/dist/test/fixtures/test-fixtures.d.ts +28 -0
  32. package/dist/test/fixtures/test-fixtures.d.ts.map +1 -0
  33. package/dist/test/fixtures/test-fixtures.js +63 -0
  34. package/dist/test/helpers/test-file-utils.d.ts +60 -0
  35. package/dist/test/helpers/test-file-utils.d.ts.map +1 -0
  36. package/dist/test/helpers/test-file-utils.js +139 -0
  37. package/dist/test/https-enforcement.spec.d.ts +2 -0
  38. package/dist/test/https-enforcement.spec.d.ts.map +1 -0
  39. package/dist/test/https-enforcement.spec.js +125 -0
  40. package/dist/test/kms-crypto-adapter.spec.d.ts +2 -0
  41. package/dist/test/kms-crypto-adapter.spec.d.ts.map +1 -0
  42. package/dist/test/kms-crypto-adapter.spec.js +305 -0
  43. package/dist/test/lit-crypto-adapter.spec.d.ts +2 -0
  44. package/dist/test/lit-crypto-adapter.spec.d.ts.map +1 -0
  45. package/dist/test/lit-crypto-adapter.spec.js +76 -0
  46. package/dist/test/memory-efficiency.spec.d.ts +2 -0
  47. package/dist/test/memory-efficiency.spec.d.ts.map +1 -0
  48. package/dist/test/memory-efficiency.spec.js +93 -0
  49. package/dist/test/mocks/key-manager.d.ts +58 -0
  50. package/dist/test/mocks/key-manager.d.ts.map +1 -0
  51. package/dist/test/mocks/key-manager.js +137 -0
  52. package/dist/test/node-crypto-adapter.spec.d.ts +2 -0
  53. package/dist/test/node-crypto-adapter.spec.d.ts.map +1 -0
  54. package/dist/test/node-crypto-adapter.spec.js +103 -0
  55. package/dist/test/node-generic-crypto-adapter.spec.d.ts +2 -0
  56. package/dist/test/node-generic-crypto-adapter.spec.d.ts.map +1 -0
  57. package/dist/test/node-generic-crypto-adapter.spec.js +95 -0
  58. package/dist/test/setup.d.ts +2 -0
  59. package/dist/test/setup.d.ts.map +1 -0
  60. package/dist/test/setup.js +12 -0
  61. package/dist/tsconfig.spec.tsbuildinfo +1 -0
  62. package/dist/types.d.ts +1 -2
  63. package/dist/types.d.ts.map +1 -1
  64. package/package.json +4 -4
@@ -0,0 +1,139 @@
1
+ import { KMSMetadata } from '../../src/core/metadata/encrypted-metadata.js';
2
+ /**
3
+ * Create test data with specific patterns for easy verification
4
+ *
5
+ * @param {number} sizeMB - Size of the test file in megabytes
6
+ * @returns {Blob} A Blob containing test data with predictable patterns
7
+ */
8
+ export function createTestFile(sizeMB) {
9
+ const chunkSize = 64 * 1024; // 64KB chunks
10
+ const totalSize = sizeMB * 1024 * 1024;
11
+ const numChunks = Math.ceil(totalSize / chunkSize);
12
+ const chunks = [];
13
+ for (let i = 0; i < numChunks; i++) {
14
+ const isLastChunk = i === numChunks - 1;
15
+ const currentChunkSize = isLastChunk
16
+ ? totalSize % chunkSize || chunkSize
17
+ : chunkSize;
18
+ const chunk = new Uint8Array(currentChunkSize);
19
+ // Create pattern: chunk index in first byte, then sequence
20
+ chunk[0] = i % 256;
21
+ for (let j = 1; j < currentChunkSize; j++) {
22
+ chunk[j] = (i + j) % 256;
23
+ }
24
+ chunks.push(chunk);
25
+ }
26
+ return new Blob(chunks, { type: 'application/octet-stream' });
27
+ }
28
+ /**
29
+ * Convert ReadableStream to Uint8Array
30
+ *
31
+ * @param {ReadableStream} stream - The stream to convert
32
+ * @returns {Promise<Uint8Array>} The stream content as a Uint8Array
33
+ */
34
+ export async function streamToUint8Array(stream) {
35
+ const reader = stream.getReader();
36
+ const chunks = [];
37
+ // eslint-disable-next-line no-constant-condition
38
+ while (true) {
39
+ const { done, value } = await reader.read();
40
+ if (done)
41
+ break;
42
+ chunks.push(value);
43
+ }
44
+ const totalLength = chunks.reduce((acc, val) => acc + val.length, 0);
45
+ const result = new Uint8Array(totalLength);
46
+ let offset = 0;
47
+ for (const chunk of chunks) {
48
+ result.set(chunk, offset);
49
+ offset += chunk.length;
50
+ }
51
+ return result;
52
+ }
53
+ /**
54
+ * @param {Uint8Array} arr
55
+ * @returns {string}
56
+ */
57
+ export function uint8ArrayToString(arr) {
58
+ return new TextDecoder().decode(arr);
59
+ }
60
+ /**
61
+ * @param {string} str
62
+ * @returns {Uint8Array}
63
+ */
64
+ export function stringToUint8Array(str) {
65
+ return new TextEncoder().encode(str);
66
+ }
67
+ /**
68
+ * Check if an error is a memory-related error (out of heap space, etc.)
69
+ *
70
+ * @param {unknown} error - The error to check
71
+ * @returns {boolean} True if the error appears to be memory-related
72
+ */
73
+ export function isMemoryError(error) {
74
+ const errorMessage = error instanceof Error ? error.message : String(error);
75
+ return (errorMessage.includes('heap') ||
76
+ errorMessage.includes('memory') ||
77
+ errorMessage.includes('allocation failed') ||
78
+ errorMessage.includes('out of memory'));
79
+ }
80
+ /**
81
+ * Test an encryption operation and expect it might fail with memory errors
82
+ *
83
+ * @param {Function} encryptOperation - Function that performs encryption
84
+ * @param {string} operationName - Name of the operation for logging
85
+ * @returns {Promise<{success: boolean, error?: Error}>} Result of the operation
86
+ */
87
+ export async function testEncryptionWithMemoryHandling(encryptOperation, operationName) {
88
+ try {
89
+ await encryptOperation();
90
+ return { success: true };
91
+ }
92
+ catch (error) {
93
+ if (isMemoryError(error)) {
94
+ console.log(`✓ ${operationName} failed as expected: Out of memory`);
95
+ return {
96
+ success: false,
97
+ error: error instanceof Error ? error : new Error(String(error)),
98
+ };
99
+ }
100
+ else {
101
+ // Re-throw if it's not a memory error
102
+ throw error;
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * Create a CAR file with KMS metadata content
108
+ *
109
+ * @param {any} content - The KMS metadata content
110
+ * @returns {Promise<{car: Uint8Array, actualRootCID: import('multiformats').UnknownLink}>}
111
+ */
112
+ export async function createTestCar(content) {
113
+ // Create KMS metadata and archive it to get the CAR
114
+ const kmsMetadata = KMSMetadata.create(content);
115
+ const { cid, bytes } = await kmsMetadata.archiveBlock();
116
+ // Use UCANTO's CAR encoding to create a proper CAR file
117
+ const { CAR } = await import('@ucanto/core');
118
+ const car = CAR.encode({ roots: [{ cid, bytes }] });
119
+ return { car, actualRootCID: cid };
120
+ }
121
+ /**
122
+ * Create a mock BlobLike object for testing
123
+ *
124
+ * @param {Uint8Array} data
125
+ * @returns {import('../../src/types.js').BlobLike}
126
+ */
127
+ export function createMockBlob(data) {
128
+ return {
129
+ stream() {
130
+ return new ReadableStream({
131
+ start(controller) {
132
+ controller.enqueue(data);
133
+ controller.close();
134
+ },
135
+ });
136
+ },
137
+ };
138
+ }
139
+ //# sourceMappingURL=test-file-utils.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=https-enforcement.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"https-enforcement.spec.d.ts","sourceRoot":"","sources":["../../test/https-enforcement.spec.js"],"names":[],"mappings":""}
@@ -0,0 +1,125 @@
1
+ import assert from 'node:assert';
2
+ import { describe, test } from 'node:test';
3
+ import { KMSCryptoAdapter } from '../src/crypto/adapters/kms-crypto-adapter.js';
4
+ import { GenericAesCtrStreamingCrypto } from '../src/crypto/symmetric/generic-aes-ctr-streaming-crypto.js';
5
+ await describe('HTTPS Enforcement', async () => {
6
+ await describe('KMSCryptoAdapter Constructor', async () => {
7
+ await test('should accept valid HTTPS URL as string', async () => {
8
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
9
+ // Should not throw
10
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://private.storacha.link', 'did:web:private.storacha.link');
11
+ assert(adapter instanceof KMSCryptoAdapter, 'Should create adapter successfully');
12
+ assert.strictEqual(adapter.keyManagerServiceURL.protocol, 'https:', 'Should store HTTPS protocol');
13
+ assert.strictEqual(adapter.keyManagerServiceURL.toString(), 'https://private.storacha.link/', 'Should store correct URL');
14
+ });
15
+ await test('should accept valid HTTPS URL object', async () => {
16
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
17
+ const httpsURL = new URL('https://example.com:8443/path');
18
+ // Should not throw
19
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, httpsURL, 'did:web:example.com');
20
+ assert(adapter instanceof KMSCryptoAdapter, 'Should create adapter successfully');
21
+ assert.strictEqual(adapter.keyManagerServiceURL.protocol, 'https:', 'Should store HTTPS protocol');
22
+ assert.strictEqual(adapter.keyManagerServiceURL.toString(), 'https://example.com:8443/path', 'Should preserve URL structure');
23
+ });
24
+ await test('should reject HTTP URL string', async () => {
25
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
26
+ assert.throws(() => new KMSCryptoAdapter(symmetricCrypto, 'http://insecure.example.com', 'did:web:example.com'), /Key manager service must use HTTPS protocol for security/, 'Should reject HTTP protocol');
27
+ });
28
+ await test('should reject HTTP URL object', async () => {
29
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
30
+ const httpURL = new URL('http://insecure.example.com');
31
+ assert.throws(() => new KMSCryptoAdapter(symmetricCrypto, httpURL, 'did:web:example.com'), /Key manager service must use HTTPS protocol for security/, 'Should reject HTTP URL object');
32
+ });
33
+ await test('should reject other protocols', async () => {
34
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
35
+ const protocolTestCases = [
36
+ 'ftp://example.com',
37
+ 'ws://example.com',
38
+ 'file://example.com',
39
+ 'data:text/plain;base64,SGVsbG8=',
40
+ ];
41
+ for (const testURL of protocolTestCases) {
42
+ assert.throws(() => new KMSCryptoAdapter(symmetricCrypto, testURL, 'did:web:example.com'), /Key manager service must use HTTPS protocol for security/, `Should reject protocol: ${testURL}`);
43
+ }
44
+ });
45
+ await test('should provide helpful error message', async () => {
46
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
47
+ try {
48
+ new KMSCryptoAdapter(symmetricCrypto, 'http://example.com', 'did:web:example.com');
49
+ assert.fail('Should have thrown an error');
50
+ }
51
+ catch (error) {
52
+ assert(error instanceof Error, 'Should throw Error instance');
53
+ assert(error.message.includes('Key manager service must use HTTPS protocol'), 'Should include main error message');
54
+ assert(error.message.includes('Received: http:'), 'Should include received protocol');
55
+ assert(error.message.includes('https://your-key-manager-service.com'), 'Should include example of correct format');
56
+ }
57
+ });
58
+ await test('should handle localhost development URLs correctly', async () => {
59
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
60
+ // Even localhost should require HTTPS for consistency
61
+ assert.throws(() => new KMSCryptoAdapter(symmetricCrypto, 'http://localhost:3000', 'did:web:localhost'), /Key manager service must use HTTPS protocol for security/, 'Should reject HTTP even for localhost');
62
+ // But HTTPS localhost should work
63
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://localhost:3000', 'did:web:localhost');
64
+ assert(adapter instanceof KMSCryptoAdapter, 'Should accept HTTPS localhost');
65
+ });
66
+ await test('should handle invalid URL strings gracefully', async () => {
67
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
68
+ assert.throws(() => new KMSCryptoAdapter(symmetricCrypto, 'not-a-valid-url', 'did:web:example.com'), /Invalid URL/, 'Should throw URL parsing error for invalid URLs');
69
+ });
70
+ await test('should preserve all adapter functionality after HTTPS validation', async () => {
71
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
72
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://private.storacha.link', 'did:web:private.storacha.link');
73
+ // Verify all expected methods exist
74
+ assert.strictEqual(typeof adapter.encryptStream, 'function', 'Should have encryptStream method');
75
+ assert.strictEqual(typeof adapter.decryptStream, 'function', 'Should have decryptStream method');
76
+ assert.strictEqual(typeof adapter.encryptSymmetricKey, 'function', 'Should have encryptSymmetricKey method');
77
+ assert.strictEqual(typeof adapter.decryptSymmetricKey, 'function', 'Should have decryptSymmetricKey method');
78
+ assert.strictEqual(typeof adapter.extractEncryptedMetadata, 'function', 'Should have extractEncryptedMetadata method');
79
+ assert.strictEqual(typeof adapter.getEncryptedKey, 'function', 'Should have getEncryptedKey method');
80
+ assert.strictEqual(typeof adapter.encodeMetadata, 'function', 'Should have encodeMetadata method');
81
+ // Verify adapter properties are set correctly
82
+ assert(adapter.symmetricCrypto === symmetricCrypto, 'Should store symmetric crypto reference');
83
+ assert.strictEqual(adapter.keyManagerServiceDID.did(), 'did:web:private.storacha.link', 'Should store gateway DID');
84
+ });
85
+ });
86
+ await describe('Secure by Default Principle', async () => {
87
+ await test('should demonstrate secure by default - HTTPS is automatically used', async () => {
88
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
89
+ // All of these should work without any special configuration
90
+ const validHttpsUrls = [
91
+ 'https://gateway.example.com',
92
+ 'https://localhost:8080',
93
+ 'https://192.168.1.100:3000',
94
+ 'https://api.storacha.network:443/v1',
95
+ ];
96
+ for (const url of validHttpsUrls) {
97
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, url, 'did:web:example.com');
98
+ assert.strictEqual(adapter.keyManagerServiceURL.protocol, 'https:', `Should store HTTPS protocol for URL: ${url}`);
99
+ }
100
+ });
101
+ await test('should require explicit insecure configuration to bypass HTTPS', async () => {
102
+ // This demonstrates that HTTP is never accidentally allowed
103
+ // If someone really needs HTTP (like for testing), they would need to
104
+ // modify our security validation code intentionally
105
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
106
+ const httpUrls = [
107
+ 'http://example.com',
108
+ 'http://localhost:3000',
109
+ 'http://192.168.1.100:8080',
110
+ ];
111
+ for (const url of httpUrls) {
112
+ assert.throws(() => new KMSCryptoAdapter(symmetricCrypto, url, 'did:web:example.com'), /Key manager service must use HTTPS protocol for security/, `Should reject HTTP URL: ${url}`);
113
+ }
114
+ });
115
+ await test('should allow HTTP for testing when explicitly enabled', async () => {
116
+ // This demonstrates the testing escape hatch
117
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
118
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'http://localhost:8080', 'did:web:localhost', { allowInsecureHttp: true } // Explicit testing option
119
+ );
120
+ assert.strictEqual(adapter.keyManagerServiceURL.protocol, 'http:', 'Should allow HTTP when explicitly enabled for testing');
121
+ assert.strictEqual(adapter.keyManagerServiceURL.toString(), 'http://localhost:8080/', 'Should preserve HTTP URL when testing option is enabled');
122
+ });
123
+ });
124
+ });
125
+ //# sourceMappingURL=https-enforcement.spec.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=kms-crypto-adapter.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kms-crypto-adapter.spec.d.ts","sourceRoot":"","sources":["../../test/kms-crypto-adapter.spec.js"],"names":[],"mappings":""}
@@ -0,0 +1,305 @@
1
+ import './setup.js';
2
+ import { test, describe } from 'node:test';
3
+ import assert from 'node:assert';
4
+ import * as Server from '@ucanto/server';
5
+ import { base64 } from 'multiformats/bases/base64';
6
+ import * as Space from '@storacha/capabilities/space';
7
+ import { GenericAesCtrStreamingCrypto } from '../src/crypto/symmetric/generic-aes-ctr-streaming-crypto.js';
8
+ import { KMSCryptoAdapter } from '../src/crypto/adapters/kms-crypto-adapter.js';
9
+ import { createMockKeyManagerService, createMockKeyManagerServer, } from './mocks/key-manager.js';
10
+ import { createTestFixtures } from './fixtures/test-fixtures.js';
11
+ import { stringToUint8Array, streamToUint8Array, uint8ArrayToString, } from './helpers/test-file-utils.js';
12
+ await describe('KMSCryptoAdapter', async () => {
13
+ await describe('Unit Tests', async () => {
14
+ await test('should delegate symmetric crypto operations to the injected implementation', async () => {
15
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
16
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://private.storacha.link', 'did:web:private.storacha.link');
17
+ const originalText = 'Op, this is a test for KMS strategy-based encryption!';
18
+ const blob = new Blob([stringToUint8Array(originalText)]);
19
+ // Test that it delegates to the symmetric crypto implementation
20
+ const { key, iv, encryptedStream } = await adapter.encryptStream(blob);
21
+ assert(key instanceof Uint8Array, 'Key should be a Uint8Array');
22
+ assert(iv instanceof Uint8Array, 'IV should be a Uint8Array');
23
+ assert(encryptedStream instanceof ReadableStream, 'Encrypted stream should be a ReadableStream');
24
+ // Test decryption delegation
25
+ const decryptedStream = await adapter.decryptStream(encryptedStream, key, iv);
26
+ const decryptedBytes = await streamToUint8Array(decryptedStream);
27
+ const decryptedText = uint8ArrayToString(decryptedBytes);
28
+ assert.strictEqual(decryptedText, originalText, 'Decrypted text should match original');
29
+ });
30
+ await test('should initialize KMS adapter with correct configuration', async () => {
31
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
32
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://private.storacha.link', 'did:web:private.storacha.link');
33
+ // Test that the adapter can handle encryption options directly
34
+ assert(typeof adapter.encryptSymmetricKey === 'function', 'encryptSymmetricKey should be a function');
35
+ // Verify adapter constructor sets properties correctly
36
+ assert(typeof adapter.keyManagerServiceDID === 'object', 'Adapter should have gateway DID object');
37
+ assert.strictEqual(adapter.keyManagerServiceDID.did(), 'did:web:private.storacha.link', 'Adapter should have correct gateway DID');
38
+ assert(adapter.keyManagerServiceURL instanceof URL, 'Adapter should have gateway URL');
39
+ });
40
+ await test('should handle metadata extraction with invalid CAR data', async () => {
41
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
42
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://private.storacha.link', 'did:web:private.storacha.link');
43
+ // Test that the method exists
44
+ assert(typeof adapter.extractEncryptedMetadata === 'function', 'extractEncryptedMetadata should be a function');
45
+ assert(typeof adapter.getEncryptedKey === 'function', 'getEncryptedKey should be a function');
46
+ // Should throw error for invalid CAR data
47
+ const mockCar = new Uint8Array([1, 2, 3]);
48
+ assert.throws(() => {
49
+ adapter.extractEncryptedMetadata(mockCar);
50
+ }, /Invalid CAR header format/, 'Should throw error for invalid CAR data');
51
+ });
52
+ await test('should sanitize space DID for KMS key ID', async () => {
53
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
54
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://mock-gateway.example.com', 'did:web:mock');
55
+ const spaceDID = 'did:key:z6MkwDK3M4PxU1FqcSt6quBH1xRBSGnPRdQYP9B13h3Wq5X1';
56
+ const sanitized = adapter.sanitizeSpaceDIDForKMSKeyId(spaceDID);
57
+ assert.strictEqual(sanitized, 'z6MkwDK3M4PxU1FqcSt6quBH1xRBSGnPRdQYP9B13h3Wq5X1');
58
+ assert(!sanitized.includes('did:key:'));
59
+ });
60
+ });
61
+ await describe('Integration Tests', async () => {
62
+ await test('should complete full encryption workflow with mocked private gateway', async () => {
63
+ const fixtures = await createTestFixtures();
64
+ const { keyManagerServiceDID, spaceDID, issuer, publicKeyPem, keyPair, delegationProof, } = fixtures;
65
+ let setupCalled = false;
66
+ let decryptCalled = false;
67
+ let actualEncryptedKey = '';
68
+ // Create mock gateway service that performs real RSA encryption/decryption
69
+ const service = createMockKeyManagerService({
70
+ mockPublicKey: publicKeyPem,
71
+ onEncryptionSetup: (/** @type {any} */ input) => {
72
+ setupCalled = true;
73
+ assert.strictEqual(input.capability.with, spaceDID);
74
+ assert.strictEqual(input.capability.can, 'space/encryption/setup');
75
+ },
76
+ onKeyDecrypt: (/** @type {any} */ input) => {
77
+ decryptCalled = true;
78
+ assert.strictEqual(input.capability.with, spaceDID);
79
+ assert.strictEqual(input.capability.can, 'space/encryption/key/decrypt');
80
+ },
81
+ });
82
+ // Override the decrypt handler to actually decrypt with the private key
83
+ service.space.encryption.key.decrypt = Server.provide(Space.EncryptionKeyDecrypt, async (input) => {
84
+ decryptCalled = true;
85
+ assert.strictEqual(input.capability.with, spaceDID);
86
+ assert.strictEqual(input.capability.can, 'space/encryption/key/decrypt');
87
+ // Get the encrypted key from the request
88
+ const encryptedKeyBytes = input.capability.nb.key;
89
+ const encryptedSymmetricKey = base64.encode(encryptedKeyBytes);
90
+ actualEncryptedKey = encryptedSymmetricKey;
91
+ // Decrypt with RSA private key (simulate real KMS decryption)
92
+ const encryptedBytes = encryptedKeyBytes;
93
+ const decryptedBytes = await globalThis.crypto.subtle.decrypt({ name: 'RSA-OAEP' }, keyPair.privateKey, encryptedBytes);
94
+ // Return the decrypted symmetric key as base64
95
+ const decryptedKey = base64.encode(new Uint8Array(decryptedBytes));
96
+ return Server.ok({
97
+ decryptedSymmetricKey: decryptedKey,
98
+ });
99
+ });
100
+ // Create mock gateway server (HTTPS by default)
101
+ const keyManagerServiceServer = await createMockKeyManagerServer(service, keyManagerServiceDID, 5555);
102
+ // Create KMS adapter with HTTP allowed for testing
103
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
104
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, keyManagerServiceServer.url, keyManagerServiceDID.did(), { allowInsecureHttp: true } // Allow HTTP for testing
105
+ );
106
+ try {
107
+ // Create test file and encrypt it to get real symmetric keys
108
+ const testFile = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
109
+ const testBlob = new Blob([testFile], {
110
+ type: 'application/octet-stream',
111
+ });
112
+ // Encrypt the file to get real symmetric keys
113
+ const { key, iv } = await adapter.encryptStream(testBlob);
114
+ const encryptionConfig = {
115
+ issuer,
116
+ spaceDID,
117
+ location: 'us-central1',
118
+ keyring: 'test-keyring',
119
+ };
120
+ // Test key encryption with real symmetric keys - this will call the mock setup
121
+ const encryptResult = await adapter.encryptSymmetricKey(key, iv, encryptionConfig);
122
+ assert(setupCalled, 'EncryptionSetup should have been called');
123
+ assert.strictEqual(encryptResult.strategy, 'kms');
124
+ const kmsMetadata =
125
+ /** @type {import('../src/types.js').KMSKeyMetadata} */ (encryptResult.metadata);
126
+ assert.strictEqual(kmsMetadata.space, spaceDID);
127
+ assert.strictEqual(kmsMetadata.kms.provider, 'google-kms');
128
+ assert.strictEqual(kmsMetadata.kms.algorithm, 'RSA-OAEP-2048-SHA256');
129
+ assert(typeof encryptResult.encryptedKey === 'string');
130
+ // Test key decryption - this will call the mock decrypt
131
+ const decryptionConfig = {
132
+ spaceDID,
133
+ decryptDelegation: delegationProof,
134
+ proofs: [],
135
+ };
136
+ const mockMetadata = {
137
+ strategy: /** @type {'kms'} */ ('kms'),
138
+ encryptedDataCID: 'bafybeid',
139
+ encryptedSymmetricKey: encryptResult.encryptedKey,
140
+ space: spaceDID,
141
+ kms: kmsMetadata.kms,
142
+ };
143
+ const decryptResult = await adapter.decryptSymmetricKey(encryptResult.encryptedKey, {
144
+ decryptionConfig,
145
+ metadata: mockMetadata,
146
+ resourceCID:
147
+ /** @type {import('@storacha/upload-client/types').AnyLink} */ (
148
+ /** @type {any} */ ('bafybeid')),
149
+ issuer,
150
+ audience: keyManagerServiceDID.did(),
151
+ });
152
+ // Verify the round-trip worked
153
+ assert(setupCalled, 'EncryptionSetup should have been called');
154
+ assert(decryptCalled, 'KeyDecrypt should have been called');
155
+ assert(decryptResult.key instanceof Uint8Array, 'Decrypted key should be Uint8Array');
156
+ assert(decryptResult.iv instanceof Uint8Array, 'Decrypted IV should be Uint8Array');
157
+ // Most importantly: verify the decrypted keys match the original
158
+ assert.deepStrictEqual(decryptResult.key, key, 'Decrypted key should match original key');
159
+ assert.deepStrictEqual(decryptResult.iv, iv, 'Decrypted IV should match original IV');
160
+ // Verify the encrypted key was actually encrypted (different from original)
161
+ const originalCombined = adapter.symmetricCrypto.combineKeyAndIV(key, iv);
162
+ const originalBase64 = base64.encode(originalCombined);
163
+ assert.notStrictEqual(actualEncryptedKey, originalBase64, 'Encrypted key should be different from original');
164
+ }
165
+ catch (error) {
166
+ console.error('Test failed with error:', error);
167
+ throw error;
168
+ }
169
+ finally {
170
+ // Clean up server
171
+ await keyManagerServiceServer.close();
172
+ }
173
+ });
174
+ await test('should handle encryption setup errors gracefully', async () => {
175
+ const fixtures = await createTestFixtures();
176
+ const { keyManagerServiceDID, spaceDID, issuer } = fixtures;
177
+ // Create service that returns errors
178
+ const service = createMockKeyManagerService({
179
+ mockPublicKey: 'invalid',
180
+ onEncryptionSetup: () => {
181
+ // This will be called but service will return error
182
+ },
183
+ });
184
+ // Override service to return error
185
+ service.space.encryption.setup = Server.provide(Space.EncryptionSetup, async () => {
186
+ return Server.error({
187
+ name: 'SpaceNotProvisioned',
188
+ message: 'Space is not provisioned for encryption',
189
+ });
190
+ });
191
+ const keyManagerServiceServer = await createMockKeyManagerServer(service, keyManagerServiceDID, 5556);
192
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
193
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, keyManagerServiceServer.url, keyManagerServiceDID.did(), { allowInsecureHttp: true } // Allow HTTP for testing
194
+ );
195
+ try {
196
+ const testKey = new Uint8Array(32).fill(1);
197
+ const testIV = new Uint8Array(16).fill(2);
198
+ const encryptionConfig = {
199
+ issuer,
200
+ spaceDID,
201
+ };
202
+ // Should throw error
203
+ await assert.rejects(() => adapter.encryptSymmetricKey(testKey, testIV, encryptionConfig), /Space is not provisioned for encryption/);
204
+ }
205
+ finally {
206
+ await keyManagerServiceServer.close();
207
+ }
208
+ });
209
+ await test('should handle key decryption errors gracefully', async () => {
210
+ const fixtures = await createTestFixtures();
211
+ const { keyManagerServiceDID, spaceDID, issuer, delegationProof } = fixtures;
212
+ // Create service that returns errors for decrypt
213
+ const service = createMockKeyManagerService({
214
+ mockPublicKey: 'mock-key',
215
+ });
216
+ // Override decrypt service to return error
217
+ service.space.encryption.key.decrypt = Server.provide(Space.EncryptionKeyDecrypt, async () => {
218
+ return Server.error({
219
+ name: 'KeyNotFound',
220
+ message: 'KMS key not found',
221
+ });
222
+ });
223
+ const keyManagerServiceServer = await createMockKeyManagerServer(service, keyManagerServiceDID, 5557);
224
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
225
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, keyManagerServiceServer.url, keyManagerServiceDID.did(), { allowInsecureHttp: true } // Allow HTTP for testing
226
+ );
227
+ const decryptionOptions = {
228
+ spaceDID,
229
+ decryptDelegation: delegationProof,
230
+ };
231
+ const mockKey = new Uint8Array([1, 2, 3]); // test value as bytes
232
+ const mockKeyString = base64.encode(mockKey);
233
+ const mockMetadata = {
234
+ strategy: /** @type {'kms'} */ ('kms'),
235
+ encryptedDataCID: 'bafybeid',
236
+ key: mockKey, // use bytes, not string
237
+ space: spaceDID,
238
+ kms: {
239
+ provider: /** @type {'google-kms'} */ ('google-kms'),
240
+ keyId: 'test-key',
241
+ algorithm: /** @type {'RSA-OAEP-2048-SHA256'} */ ('RSA-OAEP-2048-SHA256'),
242
+ },
243
+ };
244
+ const decryptConfigs = /** @type {any} */ ({
245
+ decryptionConfig: decryptionOptions,
246
+ metadata: mockMetadata,
247
+ delegationCAR: new Uint8Array(),
248
+ resourceCID: 'bafybeid',
249
+ issuer,
250
+ audience: keyManagerServiceDID.did(),
251
+ });
252
+ try {
253
+ // Should throw error
254
+ await assert.rejects(() => adapter.decryptSymmetricKey(mockKeyString, decryptConfigs), /KMS key not found/);
255
+ }
256
+ finally {
257
+ await keyManagerServiceServer.close();
258
+ }
259
+ });
260
+ });
261
+ await describe('Validation Tests', async () => {
262
+ await test('should validate required decryption parameters', async () => {
263
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
264
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://mock-gateway.example.com', 'did:web:mock');
265
+ const invalidConfigs = /** @type {any} */ ({
266
+ decryptionConfig: {}, // Missing spaceDID and decryptDelegation
267
+ metadata: { strategy: 'kms' },
268
+ delegationCAR: new Uint8Array(),
269
+ resourceCID: 'bafybeid',
270
+ issuer: null,
271
+ audience: 'did:web:mock',
272
+ });
273
+ await assert.rejects(() => adapter.decryptSymmetricKey('key', invalidConfigs), /SpaceDID and decryptDelegation are required/);
274
+ });
275
+ await test('should validate issuer is provided', async () => {
276
+ const fixtures = await createTestFixtures();
277
+ const { spaceDID, delegationProof } = fixtures;
278
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
279
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://mock-gateway.example.com', 'did:web:mock');
280
+ const invalidConfigs = /** @type {any} */ ({
281
+ decryptionConfig: { spaceDID, decryptDelegation: delegationProof },
282
+ metadata: { strategy: 'kms' },
283
+ delegationCAR: new Uint8Array(),
284
+ resourceCID: 'bafybeid',
285
+ issuer: null, // Missing issuer
286
+ audience: 'did:web:mock',
287
+ });
288
+ await assert.rejects(() => adapter.decryptSymmetricKey('key', invalidConfigs), /Issuer is required/);
289
+ });
290
+ await test('should reject non-KMS metadata', async () => {
291
+ const symmetricCrypto = new GenericAesCtrStreamingCrypto();
292
+ const adapter = new KMSCryptoAdapter(symmetricCrypto, 'https://mock-gateway.example.com', 'did:web:mock');
293
+ const invalidConfigs = /** @type {any} */ ({
294
+ decryptionOptions: { spaceDID: 'did:key:test', delegationProof: {} },
295
+ metadata: { strategy: 'lit' }, // Wrong strategy
296
+ delegationCAR: new Uint8Array(),
297
+ resourceCID: 'bafybeid',
298
+ issuer: {},
299
+ audience: 'did:web:mock',
300
+ });
301
+ await assert.rejects(() => adapter.decryptSymmetricKey('key', invalidConfigs), /KMSCryptoAdapter can only handle KMS metadata/);
302
+ });
303
+ });
304
+ });
305
+ //# sourceMappingURL=kms-crypto-adapter.spec.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=lit-crypto-adapter.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lit-crypto-adapter.spec.d.ts","sourceRoot":"","sources":["../../test/lit-crypto-adapter.spec.js"],"names":[],"mappings":""}