@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.
- package/dist/crypto/adapters/kms-crypto-adapter.d.ts +4 -10
- package/dist/crypto/adapters/kms-crypto-adapter.d.ts.map +1 -1
- package/dist/crypto/adapters/kms-crypto-adapter.js +4 -4
- package/dist/examples/decrypt-test.d.ts +2 -0
- package/dist/examples/decrypt-test.d.ts.map +1 -0
- package/dist/examples/decrypt-test.js +102 -0
- package/dist/examples/encrypt-test.d.ts +2 -0
- package/dist/examples/encrypt-test.d.ts.map +1 -0
- package/dist/examples/encrypt-test.js +93 -0
- package/dist/test/cid-verification.spec.d.ts +2 -0
- package/dist/test/cid-verification.spec.d.ts.map +1 -0
- package/dist/test/cid-verification.spec.js +314 -0
- package/dist/test/crypto-compatibility.spec.d.ts +2 -0
- package/dist/test/crypto-compatibility.spec.d.ts.map +1 -0
- package/dist/test/crypto-compatibility.spec.js +124 -0
- package/dist/test/crypto-counter-security.spec.d.ts +2 -0
- package/dist/test/crypto-counter-security.spec.d.ts.map +1 -0
- package/dist/test/crypto-counter-security.spec.js +147 -0
- package/dist/test/crypto-streaming.spec.d.ts +2 -0
- package/dist/test/crypto-streaming.spec.d.ts.map +1 -0
- package/dist/test/crypto-streaming.spec.js +129 -0
- package/dist/test/encrypted-metadata.spec.d.ts +2 -0
- package/dist/test/encrypted-metadata.spec.d.ts.map +1 -0
- package/dist/test/encrypted-metadata.spec.js +68 -0
- package/dist/test/factories.spec.d.ts +2 -0
- package/dist/test/factories.spec.d.ts.map +1 -0
- package/dist/test/factories.spec.js +129 -0
- package/dist/test/file-metadata.spec.d.ts +2 -0
- package/dist/test/file-metadata.spec.d.ts.map +1 -0
- package/dist/test/file-metadata.spec.js +433 -0
- package/dist/test/fixtures/test-fixtures.d.ts +28 -0
- package/dist/test/fixtures/test-fixtures.d.ts.map +1 -0
- package/dist/test/fixtures/test-fixtures.js +63 -0
- package/dist/test/helpers/test-file-utils.d.ts +60 -0
- package/dist/test/helpers/test-file-utils.d.ts.map +1 -0
- package/dist/test/helpers/test-file-utils.js +139 -0
- package/dist/test/https-enforcement.spec.d.ts +2 -0
- package/dist/test/https-enforcement.spec.d.ts.map +1 -0
- package/dist/test/https-enforcement.spec.js +125 -0
- package/dist/test/kms-crypto-adapter.spec.d.ts +2 -0
- package/dist/test/kms-crypto-adapter.spec.d.ts.map +1 -0
- package/dist/test/kms-crypto-adapter.spec.js +305 -0
- package/dist/test/lit-crypto-adapter.spec.d.ts +2 -0
- package/dist/test/lit-crypto-adapter.spec.d.ts.map +1 -0
- package/dist/test/lit-crypto-adapter.spec.js +76 -0
- package/dist/test/memory-efficiency.spec.d.ts +2 -0
- package/dist/test/memory-efficiency.spec.d.ts.map +1 -0
- package/dist/test/memory-efficiency.spec.js +93 -0
- package/dist/test/mocks/key-manager.d.ts +58 -0
- package/dist/test/mocks/key-manager.d.ts.map +1 -0
- package/dist/test/mocks/key-manager.js +137 -0
- package/dist/test/node-crypto-adapter.spec.d.ts +2 -0
- package/dist/test/node-crypto-adapter.spec.d.ts.map +1 -0
- package/dist/test/node-crypto-adapter.spec.js +103 -0
- package/dist/test/node-generic-crypto-adapter.spec.d.ts +2 -0
- package/dist/test/node-generic-crypto-adapter.spec.d.ts.map +1 -0
- package/dist/test/node-generic-crypto-adapter.spec.js +95 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +12 -0
- package/dist/tsconfig.spec.tsbuildinfo +1 -0
- package/dist/types.d.ts +1 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import './setup.js';
|
|
2
|
+
import { test, describe } from 'node:test';
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
import { GenericAesCtrStreamingCrypto } from '../src/crypto/symmetric/generic-aes-ctr-streaming-crypto.js';
|
|
5
|
+
import { LitCryptoAdapter } from '../src/crypto/adapters/lit-crypto-adapter.js';
|
|
6
|
+
import { stringToUint8Array, streamToUint8Array, uint8ArrayToString, } from './helpers/test-file-utils.js';
|
|
7
|
+
// Mock Lit client - cast to any for testing
|
|
8
|
+
const mockLitClient = /** @type {any} */ ({
|
|
9
|
+
// Add mock methods as needed
|
|
10
|
+
});
|
|
11
|
+
const mockAuthManager = /** @type {any} */ ({
|
|
12
|
+
// Add mock methods as needed
|
|
13
|
+
});
|
|
14
|
+
await describe('LitCryptoAdapter', async () => {
|
|
15
|
+
await describe('Generic AES-CTR Crypto Implementation', async () => {
|
|
16
|
+
await test('should delegate symmetric crypto operations to the generic implementation', async () => {
|
|
17
|
+
const symmetricCrypto = new GenericAesCtrStreamingCrypto();
|
|
18
|
+
const adapter = new LitCryptoAdapter(symmetricCrypto, mockLitClient, mockAuthManager);
|
|
19
|
+
const originalText = 'Op, this is a test for strategy-based encryption!';
|
|
20
|
+
const blob = new Blob([stringToUint8Array(originalText)]);
|
|
21
|
+
// Test that it delegates to the symmetric crypto implementation
|
|
22
|
+
const { key, iv, encryptedStream } = await adapter.encryptStream(blob);
|
|
23
|
+
assert(key instanceof Uint8Array, 'Key should be a Uint8Array');
|
|
24
|
+
assert(iv instanceof Uint8Array, 'IV should be a Uint8Array');
|
|
25
|
+
assert(encryptedStream instanceof ReadableStream, 'Encrypted stream should be a ReadableStream');
|
|
26
|
+
// Test decryption delegation
|
|
27
|
+
const decryptedStream = await adapter.decryptStream(encryptedStream, key, iv);
|
|
28
|
+
const decryptedBytes = await streamToUint8Array(decryptedStream);
|
|
29
|
+
const decryptedText = uint8ArrayToString(decryptedBytes);
|
|
30
|
+
assert.strictEqual(decryptedText, originalText, 'Decrypted text should match original');
|
|
31
|
+
});
|
|
32
|
+
await test('should initialize Generic Lit adapter with correct configuration', async () => {
|
|
33
|
+
const symmetricCrypto = new GenericAesCtrStreamingCrypto();
|
|
34
|
+
const adapter = new LitCryptoAdapter(symmetricCrypto, mockLitClient, mockAuthManager);
|
|
35
|
+
// Test that the adapter has the required methods
|
|
36
|
+
assert(typeof adapter.encryptSymmetricKey === 'function', 'encryptSymmetricKey should be a function');
|
|
37
|
+
assert(typeof adapter.decryptSymmetricKey === 'function', 'decryptSymmetricKey should be a function');
|
|
38
|
+
// Verify adapter has the lit client
|
|
39
|
+
assert.strictEqual(adapter.litClient, mockLitClient, 'Adapter should store the Lit client');
|
|
40
|
+
// Verify it uses the correct crypto implementation
|
|
41
|
+
assert(adapter.symmetricCrypto instanceof GenericAesCtrStreamingCrypto, 'Should use GenericAesCtrStreamingCrypto');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
await describe('Cross-Implementation Compatibility', async () => {
|
|
45
|
+
await test('should demonstrate algorithm differences between implementations', async () => {
|
|
46
|
+
const genericCrypto = new GenericAesCtrStreamingCrypto();
|
|
47
|
+
const genericAdapter = new LitCryptoAdapter(genericCrypto, mockLitClient, mockAuthManager);
|
|
48
|
+
const originalText = 'Test data for algorithm comparison';
|
|
49
|
+
const blob = new Blob([stringToUint8Array(originalText)]);
|
|
50
|
+
// Encrypt with all adapters
|
|
51
|
+
const genericResult = await genericAdapter.encryptStream(blob);
|
|
52
|
+
// Convert streams to bytes
|
|
53
|
+
const genericEncrypted = await streamToUint8Array(genericResult.encryptedStream);
|
|
54
|
+
// Verify both can decrypt their own data
|
|
55
|
+
const genericDecryptStream = new ReadableStream({
|
|
56
|
+
start(controller) {
|
|
57
|
+
controller.enqueue(genericEncrypted);
|
|
58
|
+
controller.close();
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const genericDecrypted = await genericAdapter.decryptStream(genericDecryptStream, genericResult.key, genericResult.iv);
|
|
62
|
+
const genericDecryptedBytes = await streamToUint8Array(genericDecrypted);
|
|
63
|
+
// Should decrypt to the same original text
|
|
64
|
+
assert.strictEqual(uint8ArrayToString(genericDecryptedBytes), originalText, 'Generic adapter should decrypt correctly');
|
|
65
|
+
console.log('All crypto implementations work with Lit adapter');
|
|
66
|
+
});
|
|
67
|
+
await test('should verify factory function behavior', async () => {
|
|
68
|
+
const { createGenericLitAdapter } = await import('../src/crypto/factories.node.js');
|
|
69
|
+
const genericAdapter = createGenericLitAdapter(mockLitClient, mockAuthManager);
|
|
70
|
+
// Verify factory functions create adapters with correct crypto implementations
|
|
71
|
+
assert(genericAdapter.symmetricCrypto instanceof GenericAesCtrStreamingCrypto, 'Generic factory should create adapter with GenericAesCtrStreamingCrypto');
|
|
72
|
+
console.log('Factory functions create adapters with correct crypto implementations');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=lit-crypto-adapter.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-efficiency.spec.d.ts","sourceRoot":"","sources":["../../test/memory-efficiency.spec.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import './setup.js';
|
|
2
|
+
import { test, describe } from 'node:test';
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
import { GenericAesCtrStreamingCrypto } from '../src/crypto/symmetric/generic-aes-ctr-streaming-crypto.js';
|
|
5
|
+
import { createTestFile } from './helpers/test-file-utils.js';
|
|
6
|
+
/**
|
|
7
|
+
* These tests demonstrate why streaming is necessary for large files.
|
|
8
|
+
* They show that buffered approaches fail with memory errors while streaming succeeds.
|
|
9
|
+
*/
|
|
10
|
+
await describe('Memory Efficiency - Why Streaming Matters', async () => {
|
|
11
|
+
await test('should show streaming handles progressively larger files', async () => {
|
|
12
|
+
const streamingCrypto = new GenericAesCtrStreamingCrypto();
|
|
13
|
+
// Test with multiple sizes to show streaming scales linearly
|
|
14
|
+
const testSizes = [5, 10, 15, 20, 50, 100, 500, 1000]; // MB - sizes that would challenge buffered approaches
|
|
15
|
+
for (const sizeMB of testSizes) {
|
|
16
|
+
console.log(`Processing ${sizeMB}MB file...`);
|
|
17
|
+
const testFile = createTestFile(sizeMB);
|
|
18
|
+
const startTime = Date.now();
|
|
19
|
+
const { encryptedStream } = await streamingCrypto.encryptStream(testFile);
|
|
20
|
+
let processedBytes = 0;
|
|
21
|
+
let chunkCount = 0;
|
|
22
|
+
const reader = encryptedStream.getReader();
|
|
23
|
+
try {
|
|
24
|
+
// eslint-disable-next-line no-constant-condition
|
|
25
|
+
while (true) {
|
|
26
|
+
const { done, value } = await reader.read();
|
|
27
|
+
if (done)
|
|
28
|
+
break;
|
|
29
|
+
processedBytes += value.length;
|
|
30
|
+
chunkCount++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
reader.releaseLock();
|
|
35
|
+
}
|
|
36
|
+
const processingTime = Date.now() - startTime;
|
|
37
|
+
const throughput = processedBytes / 1024 / 1024 / (processingTime / 1000); // MB/s
|
|
38
|
+
assert.strictEqual(processedBytes, testFile.size, `Should process entire ${sizeMB}MB file`);
|
|
39
|
+
console.log(`✓ ${sizeMB}MB: ${chunkCount} chunks, ${throughput.toFixed(1)} MB/s`);
|
|
40
|
+
}
|
|
41
|
+
console.log('DEMONSTRATED: Streaming handles large files with consistent performance');
|
|
42
|
+
});
|
|
43
|
+
await test('should project memory behavior for realistic file sizes', async () => {
|
|
44
|
+
const streamingCrypto = new GenericAesCtrStreamingCrypto();
|
|
45
|
+
// Test with a size we can actually handle to project larger files
|
|
46
|
+
const testFile = createTestFile(5); // 5MB
|
|
47
|
+
const getMemoryUsage = () => {
|
|
48
|
+
if (globalThis.gc)
|
|
49
|
+
globalThis.gc();
|
|
50
|
+
return process.memoryUsage ? process.memoryUsage().heapUsed : 0;
|
|
51
|
+
};
|
|
52
|
+
const baseMemory = getMemoryUsage();
|
|
53
|
+
const { encryptedStream } = await streamingCrypto.encryptStream(testFile);
|
|
54
|
+
let peakMemoryDelta = 0;
|
|
55
|
+
let processedBytes = 0;
|
|
56
|
+
const reader = encryptedStream.getReader();
|
|
57
|
+
try {
|
|
58
|
+
// eslint-disable-next-line no-constant-condition
|
|
59
|
+
while (true) {
|
|
60
|
+
const { done, value } = await reader.read();
|
|
61
|
+
if (done)
|
|
62
|
+
break;
|
|
63
|
+
processedBytes += value.length;
|
|
64
|
+
// Sample memory usage
|
|
65
|
+
const currentMemory = getMemoryUsage();
|
|
66
|
+
const memoryDelta = currentMemory - baseMemory;
|
|
67
|
+
peakMemoryDelta = Math.max(peakMemoryDelta, memoryDelta);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
reader.releaseLock();
|
|
72
|
+
}
|
|
73
|
+
const peakMemoryMB = peakMemoryDelta / 1024 / 1024;
|
|
74
|
+
const fileSize = testFile.size / 1024 / 1024;
|
|
75
|
+
const memoryEfficiency = (peakMemoryDelta / testFile.size) * 100;
|
|
76
|
+
console.log(`File size: ${fileSize.toFixed(1)}MB`);
|
|
77
|
+
console.log(`Peak memory delta: ${peakMemoryMB.toFixed(2)}MB`);
|
|
78
|
+
console.log(`Memory efficiency: ${memoryEfficiency.toFixed(1)}% of file size`);
|
|
79
|
+
// Project to larger file sizes
|
|
80
|
+
console.log('\nProjected memory usage:');
|
|
81
|
+
const projectedSizes = [100, 1000, 5000]; // MB = 100MB, 1GB, 5GB
|
|
82
|
+
for (const sizeMB of projectedSizes) {
|
|
83
|
+
const projectedMemory = (memoryEfficiency / 100) * sizeMB;
|
|
84
|
+
const sizeLabel = sizeMB >= 1000 ? `${sizeMB / 1000}GB` : `${sizeMB}MB`;
|
|
85
|
+
console.log(` ${sizeLabel}: ~${projectedMemory.toFixed(1)}MB memory`);
|
|
86
|
+
}
|
|
87
|
+
// Memory should be bounded (much less than file size)
|
|
88
|
+
assert(peakMemoryMB < fileSize, 'Streaming should use less memory than file size');
|
|
89
|
+
assert(memoryEfficiency < 50, 'Memory usage should be less than 50% of file size');
|
|
90
|
+
console.log('DEMONSTRATED: Streaming memory usage scales sub-linearly with file size');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=memory-efficiency.spec.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create mock KMS service with proper capability handlers
|
|
3
|
+
*
|
|
4
|
+
* @param {object} options
|
|
5
|
+
* @param {string} options.mockPublicKey - Mock RSA public key in PEM format
|
|
6
|
+
* @param {string} [options.mockProvider] - Mock KMS provider
|
|
7
|
+
* @param {string} [options.mockAlgorithm] - Mock algorithm
|
|
8
|
+
* @param {Function} [options.onEncryptionSetup] - Optional callback for setup calls
|
|
9
|
+
* @param {Function} [options.onKeyDecrypt] - Optional callback for decrypt calls
|
|
10
|
+
*/
|
|
11
|
+
export function createMockKeyManagerService(options: {
|
|
12
|
+
mockPublicKey: string;
|
|
13
|
+
mockProvider?: string | undefined;
|
|
14
|
+
mockAlgorithm?: string | undefined;
|
|
15
|
+
onEncryptionSetup?: Function | undefined;
|
|
16
|
+
onKeyDecrypt?: Function | undefined;
|
|
17
|
+
}): {
|
|
18
|
+
space: {
|
|
19
|
+
encryption: {
|
|
20
|
+
setup: Server.ServiceMethod<Server.API.Capability<"space/encryption/setup", `did:key:${string}` & `did:${string}` & Server.API.Phantom<{
|
|
21
|
+
protocol: "did:";
|
|
22
|
+
}>, Partial<Pick<{
|
|
23
|
+
location: string | undefined;
|
|
24
|
+
keyring: string | undefined;
|
|
25
|
+
}, "location" | "keyring">>>, {
|
|
26
|
+
publicKey: string;
|
|
27
|
+
provider: string;
|
|
28
|
+
algorithm: string;
|
|
29
|
+
}, Server.API.Failure & {
|
|
30
|
+
name: string;
|
|
31
|
+
message: string;
|
|
32
|
+
}>;
|
|
33
|
+
key: {
|
|
34
|
+
decrypt: Server.ServiceMethod<Server.API.Capability<"space/encryption/key/decrypt", `did:key:${string}` & `did:${string}` & Server.API.Phantom<{
|
|
35
|
+
protocol: "did:";
|
|
36
|
+
}>, Pick<{
|
|
37
|
+
key: Uint8Array<ArrayBufferLike>;
|
|
38
|
+
}, "key">>, {
|
|
39
|
+
decryptedSymmetricKey: string;
|
|
40
|
+
}, Server.API.Failure & {
|
|
41
|
+
name: string;
|
|
42
|
+
message: string;
|
|
43
|
+
}>;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Create a mock key manager service server
|
|
50
|
+
*
|
|
51
|
+
* @param {object} service - The service object with capability handlers
|
|
52
|
+
* @param {*} keyManagerServiceDID - The key manager service DID keypair
|
|
53
|
+
* @param {number} port - The port to listen on
|
|
54
|
+
* @param {boolean} [useHttps] - Whether to use HTTPS URLs (testing HTTPS scenarios)
|
|
55
|
+
*/
|
|
56
|
+
export function createMockKeyManagerServer(service: object, keyManagerServiceDID: any, port: number, useHttps?: boolean): Promise<any>;
|
|
57
|
+
import * as Server from '@ucanto/server';
|
|
58
|
+
//# sourceMappingURL=key-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-manager.d.ts","sourceRoot":"","sources":["../../../test/mocks/key-manager.js"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AACH,qDANG;IAAwB,aAAa,EAA7B,MAAM;IACW,YAAY;IACZ,aAAa;IACX,iBAAiB;IACjB,YAAY;CACzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwEA;AAED;;;;;;;GAOG;AACH,oDALW,MAAM,wBACN,GAAC,QACD,MAAM,aACN,OAAO,gBA6DjB;wBA3JuB,gBAAgB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import * as Server from '@ucanto/server';
|
|
3
|
+
import { CAR } from '@ucanto/transport';
|
|
4
|
+
import * as Space from '@storacha/capabilities/space';
|
|
5
|
+
import { base64 } from 'multiformats/bases/base64';
|
|
6
|
+
/**
|
|
7
|
+
* Create mock KMS service with proper capability handlers
|
|
8
|
+
*
|
|
9
|
+
* @param {object} options
|
|
10
|
+
* @param {string} options.mockPublicKey - Mock RSA public key in PEM format
|
|
11
|
+
* @param {string} [options.mockProvider] - Mock KMS provider
|
|
12
|
+
* @param {string} [options.mockAlgorithm] - Mock algorithm
|
|
13
|
+
* @param {Function} [options.onEncryptionSetup] - Optional callback for setup calls
|
|
14
|
+
* @param {Function} [options.onKeyDecrypt] - Optional callback for decrypt calls
|
|
15
|
+
*/
|
|
16
|
+
export function createMockKeyManagerService(options) {
|
|
17
|
+
const { mockPublicKey, mockProvider = 'google-kms', mockAlgorithm = 'RSA-OAEP-2048-SHA256', onEncryptionSetup, onKeyDecrypt, } = options;
|
|
18
|
+
return {
|
|
19
|
+
space: {
|
|
20
|
+
encryption: {
|
|
21
|
+
setup: Server.provide(Space.EncryptionSetup, async (input) => {
|
|
22
|
+
// Call optional callback for testing
|
|
23
|
+
if (onEncryptionSetup) {
|
|
24
|
+
onEncryptionSetup(input);
|
|
25
|
+
}
|
|
26
|
+
// Validate the space DID format
|
|
27
|
+
if (!input.capability.with.startsWith('did:key:')) {
|
|
28
|
+
return Server.error({
|
|
29
|
+
name: 'InvalidSpace',
|
|
30
|
+
message: 'Space DID must be a did:key',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Return mock RSA public key and metadata
|
|
34
|
+
return Server.ok({
|
|
35
|
+
publicKey: mockPublicKey,
|
|
36
|
+
provider: mockProvider,
|
|
37
|
+
algorithm: mockAlgorithm,
|
|
38
|
+
});
|
|
39
|
+
}),
|
|
40
|
+
key: {
|
|
41
|
+
decrypt: Server.provide(Space.EncryptionKeyDecrypt, async (input) => {
|
|
42
|
+
// Call optional callback for testing
|
|
43
|
+
if (onKeyDecrypt) {
|
|
44
|
+
onKeyDecrypt(input);
|
|
45
|
+
}
|
|
46
|
+
// Validate the space DID
|
|
47
|
+
if (!input.capability.with.startsWith('did:key:')) {
|
|
48
|
+
return Server.error({
|
|
49
|
+
name: 'InvalidSpace',
|
|
50
|
+
message: 'Space DID must be a did:key',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Validate encrypted key is provided
|
|
54
|
+
if (!input.capability.nb.key) {
|
|
55
|
+
return Server.error({
|
|
56
|
+
name: 'KeyNotFound',
|
|
57
|
+
message: 'key is required',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// For testing purposes, "decrypt" by converting bytes back to base64 string
|
|
61
|
+
// In real implementation, this would call Google KMS
|
|
62
|
+
const keyBytes = input.capability.nb.key;
|
|
63
|
+
// No base64 decode here, just return the bytes as base64 string for mock
|
|
64
|
+
const mockDecryptedKey = base64.encode(keyBytes);
|
|
65
|
+
return Server.ok({
|
|
66
|
+
decryptedSymmetricKey: mockDecryptedKey,
|
|
67
|
+
});
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a mock key manager service server
|
|
76
|
+
*
|
|
77
|
+
* @param {object} service - The service object with capability handlers
|
|
78
|
+
* @param {*} keyManagerServiceDID - The key manager service DID keypair
|
|
79
|
+
* @param {number} port - The port to listen on
|
|
80
|
+
* @param {boolean} [useHttps] - Whether to use HTTPS URLs (testing HTTPS scenarios)
|
|
81
|
+
*/
|
|
82
|
+
export function createMockKeyManagerServer(service, keyManagerServiceDID, port, useHttps = false) {
|
|
83
|
+
const ucantoServer = Server.create({
|
|
84
|
+
id: keyManagerServiceDID,
|
|
85
|
+
service,
|
|
86
|
+
codec: CAR.inbound,
|
|
87
|
+
validateAuthorization: () => ({ ok: {} }), // Skip auth validation for tests
|
|
88
|
+
});
|
|
89
|
+
const httpServer = createServer(async (req, res) => {
|
|
90
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
91
|
+
res.setHeader('Access-Control-Allow-Methods', '*');
|
|
92
|
+
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
93
|
+
if (req.method === 'OPTIONS')
|
|
94
|
+
return res.end();
|
|
95
|
+
if (req.method === 'POST') {
|
|
96
|
+
const bodyBuffer = Buffer.concat(await collect(req));
|
|
97
|
+
const reqHeaders = /** @type {Record<string, string>} */ (Object.fromEntries(Object.entries(req.headers)));
|
|
98
|
+
const { headers, body, status } = await ucantoServer.request({
|
|
99
|
+
body: new Uint8Array(bodyBuffer.buffer, bodyBuffer.byteOffset, bodyBuffer.byteLength),
|
|
100
|
+
headers: reqHeaders,
|
|
101
|
+
});
|
|
102
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
103
|
+
res.setHeader(key, value);
|
|
104
|
+
}
|
|
105
|
+
res.writeHead(status ?? 200);
|
|
106
|
+
res.end(body);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
res.end();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
httpServer.listen(port, (/** @type {Error | undefined} */ err) => {
|
|
114
|
+
if (err) {
|
|
115
|
+
reject(err);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const protocol = useHttps ? 'https' : 'http';
|
|
119
|
+
resolve({
|
|
120
|
+
server: httpServer,
|
|
121
|
+
url: `${protocol}://localhost:${port}`,
|
|
122
|
+
close: () => new Promise((resolve) => httpServer.close(resolve)),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** @param {import('node:stream').Readable} stream */
|
|
129
|
+
const collect = (stream) => {
|
|
130
|
+
return /** @type {Promise<Buffer[]>} */ (new Promise((resolve, reject) => {
|
|
131
|
+
const chunks = /** @type {Buffer[]} */ ([]);
|
|
132
|
+
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
133
|
+
stream.on('error', (err) => reject(err));
|
|
134
|
+
stream.on('end', () => resolve(chunks));
|
|
135
|
+
}));
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=key-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-crypto-adapter.spec.d.ts","sourceRoot":"","sources":["../../test/node-crypto-adapter.spec.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { NodeAesCbcCrypto } from '../src/crypto/symmetric/node-aes-cbc-crypto.js';
|
|
4
|
+
import { stringToUint8Array, uint8ArrayToString, streamToUint8Array, createMockBlob, } from './helpers/test-file-utils.js';
|
|
5
|
+
await describe('NodeAesCbcCrypto', async () => {
|
|
6
|
+
await test('should encrypt and decrypt data and return the original data', async () => {
|
|
7
|
+
const adapter = new NodeAesCbcCrypto();
|
|
8
|
+
const originalText = 'Hello, this is a test for Node.js AES-CBC encryption!';
|
|
9
|
+
const mockBlob = createMockBlob(stringToUint8Array(originalText));
|
|
10
|
+
// Encrypt
|
|
11
|
+
const { key, iv, encryptedStream } = await adapter.encryptStream(mockBlob);
|
|
12
|
+
// Decrypt
|
|
13
|
+
const decryptedStream = await adapter.decryptStream(encryptedStream, key, iv);
|
|
14
|
+
const decryptedBytes = await streamToUint8Array(decryptedStream);
|
|
15
|
+
const decryptedText = uint8ArrayToString(decryptedBytes);
|
|
16
|
+
assert.strictEqual(decryptedText, originalText);
|
|
17
|
+
});
|
|
18
|
+
await test('should handle empty data', async () => {
|
|
19
|
+
const adapter = new NodeAesCbcCrypto();
|
|
20
|
+
const mockBlob = createMockBlob(new Uint8Array(0));
|
|
21
|
+
const { key, iv, encryptedStream } = await adapter.encryptStream(mockBlob);
|
|
22
|
+
const decryptedStream = await adapter.decryptStream(encryptedStream, key, iv);
|
|
23
|
+
const decryptedBytes = await streamToUint8Array(decryptedStream);
|
|
24
|
+
assert.strictEqual(decryptedBytes.length, 0);
|
|
25
|
+
});
|
|
26
|
+
await test('should combine key and IV correctly', async () => {
|
|
27
|
+
const adapter = new NodeAesCbcCrypto();
|
|
28
|
+
const key = new Uint8Array(32).fill(3); // 32-byte AES-256 key
|
|
29
|
+
const iv = new Uint8Array(16).fill(6); // 16-byte AES-CBC IV
|
|
30
|
+
const combined = adapter.combineKeyAndIV(key, iv);
|
|
31
|
+
assert.strictEqual(combined.length, 48, 'Combined length should be 48 bytes (32 + 16)');
|
|
32
|
+
// Verify first 32 bytes are the key
|
|
33
|
+
for (let i = 0; i < 32; i++) {
|
|
34
|
+
assert.strictEqual(combined[i], 3, `Key byte ${i} should match`);
|
|
35
|
+
}
|
|
36
|
+
// Verify last 16 bytes are the IV
|
|
37
|
+
for (let i = 32; i < 48; i++) {
|
|
38
|
+
assert.strictEqual(combined[i], 6, `IV byte ${i - 32} should match`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
await test('should split combined key and IV correctly', async () => {
|
|
42
|
+
const adapter = new NodeAesCbcCrypto();
|
|
43
|
+
const originalKey = new Uint8Array(32).fill(123);
|
|
44
|
+
const originalIV = new Uint8Array(16).fill(234);
|
|
45
|
+
const combined = adapter.combineKeyAndIV(originalKey, originalIV);
|
|
46
|
+
const { key, iv } = adapter.splitKeyAndIV(combined);
|
|
47
|
+
assert.strictEqual(key.length, 32, 'Split key should be 32 bytes');
|
|
48
|
+
assert.strictEqual(iv.length, 16, 'Split IV should be 16 bytes');
|
|
49
|
+
// Verify key matches
|
|
50
|
+
for (let i = 0; i < 32; i++) {
|
|
51
|
+
assert.strictEqual(key[i], originalKey[i], `Key byte ${i} should match original`);
|
|
52
|
+
}
|
|
53
|
+
// Verify IV matches
|
|
54
|
+
for (let i = 0; i < 16; i++) {
|
|
55
|
+
assert.strictEqual(iv[i], originalIV[i], `IV byte ${i} should match original`);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
await test('should roundtrip combine/split correctly', async () => {
|
|
59
|
+
const adapter = new NodeAesCbcCrypto();
|
|
60
|
+
const { randomBytes } = await import('crypto');
|
|
61
|
+
// Convert Buffer to Uint8Array to match the expected types
|
|
62
|
+
const originalKey = new Uint8Array(randomBytes(32));
|
|
63
|
+
const originalIV = new Uint8Array(randomBytes(16));
|
|
64
|
+
const combined = adapter.combineKeyAndIV(originalKey, originalIV);
|
|
65
|
+
const { key, iv } = adapter.splitKeyAndIV(combined);
|
|
66
|
+
assert.deepStrictEqual(key, originalKey, 'Roundtrip key should match original');
|
|
67
|
+
assert.deepStrictEqual(iv, originalIV, 'Roundtrip IV should match original');
|
|
68
|
+
});
|
|
69
|
+
await test('should validate key length in combineKeyAndIV', async () => {
|
|
70
|
+
const adapter = new NodeAesCbcCrypto();
|
|
71
|
+
const wrongKey = new Uint8Array(31); // Wrong size
|
|
72
|
+
const correctIV = new Uint8Array(16);
|
|
73
|
+
assert.throws(() => adapter.combineKeyAndIV(wrongKey, correctIV), {
|
|
74
|
+
name: 'Error',
|
|
75
|
+
message: 'AES-256 key must be 32 bytes, got 31',
|
|
76
|
+
}, 'Should throw error for wrong key size');
|
|
77
|
+
});
|
|
78
|
+
await test('should validate IV length in combineKeyAndIV', async () => {
|
|
79
|
+
const adapter = new NodeAesCbcCrypto();
|
|
80
|
+
const correctKey = new Uint8Array(32);
|
|
81
|
+
const wrongIV = new Uint8Array(15); // Wrong size
|
|
82
|
+
assert.throws(() => adapter.combineKeyAndIV(correctKey, wrongIV), {
|
|
83
|
+
name: 'Error',
|
|
84
|
+
message: 'AES-CBC IV must be 16 bytes, got 15',
|
|
85
|
+
}, 'Should throw error for wrong IV size');
|
|
86
|
+
});
|
|
87
|
+
await test('should validate combined length in splitKeyAndIV', async () => {
|
|
88
|
+
const adapter = new NodeAesCbcCrypto();
|
|
89
|
+
const wrongCombined = new Uint8Array(47); // Wrong size
|
|
90
|
+
assert.throws(() => adapter.splitKeyAndIV(wrongCombined), {
|
|
91
|
+
name: 'Error',
|
|
92
|
+
message: 'AES-256-CBC combined key+IV must be 48 bytes, got 47',
|
|
93
|
+
}, 'Should throw error for wrong combined size');
|
|
94
|
+
});
|
|
95
|
+
await test('should use constants for validation', async () => {
|
|
96
|
+
const adapter = new NodeAesCbcCrypto();
|
|
97
|
+
// Test with different wrong sizes to ensure constants are used
|
|
98
|
+
assert.throws(() => adapter.combineKeyAndIV(new Uint8Array(16), new Uint8Array(16)), /AES-256 key must be 32 bytes, got 16/, 'Should use KEY_LENGTH constant in error message');
|
|
99
|
+
assert.throws(() => adapter.combineKeyAndIV(new Uint8Array(32), new Uint8Array(8)), /AES-CBC IV must be 16 bytes, got 8/, 'Should use IV_LENGTH constant in error message');
|
|
100
|
+
assert.throws(() => adapter.splitKeyAndIV(new Uint8Array(32)), /AES-256-CBC combined key\+IV must be 48 bytes, got 32/, 'Should use calculated expected length in error message');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=node-crypto-adapter.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-generic-crypto-adapter.spec.d.ts","sourceRoot":"","sources":["../../test/node-generic-crypto-adapter.spec.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import './setup.js';
|
|
2
|
+
import { test, describe } from 'node:test';
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
import { GenericAesCtrStreamingCrypto } from '../src/crypto/symmetric/generic-aes-ctr-streaming-crypto.js';
|
|
5
|
+
import { stringToUint8Array, streamToUint8Array, uint8ArrayToString, } from './helpers/test-file-utils.js';
|
|
6
|
+
await describe('GenericAesCtrStreamingCrypto (Node Environment)', async () => {
|
|
7
|
+
await test('should encrypt and decrypt a Blob and return the original data', async () => {
|
|
8
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
9
|
+
const originalText = 'Op, this is a test for streaming encryption!';
|
|
10
|
+
const blob = new Blob([stringToUint8Array(originalText)]);
|
|
11
|
+
// Encrypt
|
|
12
|
+
const { key, iv, encryptedStream } = await adapter.encryptStream(blob);
|
|
13
|
+
// Decrypt
|
|
14
|
+
const decryptedStream = await adapter.decryptStream(encryptedStream, key, iv);
|
|
15
|
+
const decryptedBytes = await streamToUint8Array(decryptedStream);
|
|
16
|
+
const decryptedText = uint8ArrayToString(decryptedBytes);
|
|
17
|
+
assert.strictEqual(decryptedText, originalText);
|
|
18
|
+
});
|
|
19
|
+
await test('should handle empty data', async () => {
|
|
20
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
21
|
+
const blob = new Blob([]);
|
|
22
|
+
const { key, iv, encryptedStream } = await adapter.encryptStream(blob);
|
|
23
|
+
const decryptedStream = await adapter.decryptStream(encryptedStream, key, iv);
|
|
24
|
+
const decryptedBytes = await streamToUint8Array(decryptedStream);
|
|
25
|
+
assert.strictEqual(decryptedBytes.length, 0);
|
|
26
|
+
});
|
|
27
|
+
await test('should combine key and IV correctly', async () => {
|
|
28
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
29
|
+
const key = new Uint8Array(32).fill(1); // 32-byte AES-256 key
|
|
30
|
+
const iv = new Uint8Array(16).fill(2); // 16-byte AES-CTR IV
|
|
31
|
+
const combined = adapter.combineKeyAndIV(key, iv);
|
|
32
|
+
assert.strictEqual(combined.length, 48, 'Combined length should be 48 bytes (32 + 16)');
|
|
33
|
+
// Verify first 32 bytes are the key
|
|
34
|
+
for (let i = 0; i < 32; i++) {
|
|
35
|
+
assert.strictEqual(combined[i], 1, `Key byte ${i} should match`);
|
|
36
|
+
}
|
|
37
|
+
// Verify last 16 bytes are the IV
|
|
38
|
+
for (let i = 32; i < 48; i++) {
|
|
39
|
+
assert.strictEqual(combined[i], 2, `IV byte ${i - 32} should match`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
await test('should split combined key and IV correctly', async () => {
|
|
43
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
44
|
+
const originalKey = new Uint8Array(32).fill(42);
|
|
45
|
+
const originalIV = new Uint8Array(16).fill(84);
|
|
46
|
+
const combined = adapter.combineKeyAndIV(originalKey, originalIV);
|
|
47
|
+
const { key, iv } = adapter.splitKeyAndIV(combined);
|
|
48
|
+
assert.strictEqual(key.length, 32, 'Split key should be 32 bytes');
|
|
49
|
+
assert.strictEqual(iv.length, 16, 'Split IV should be 16 bytes');
|
|
50
|
+
// Verify key matches
|
|
51
|
+
for (let i = 0; i < 32; i++) {
|
|
52
|
+
assert.strictEqual(key[i], originalKey[i], `Key byte ${i} should match original`);
|
|
53
|
+
}
|
|
54
|
+
// Verify IV matches
|
|
55
|
+
for (let i = 0; i < 16; i++) {
|
|
56
|
+
assert.strictEqual(iv[i], originalIV[i], `IV byte ${i} should match original`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
await test('should roundtrip combine/split correctly', async () => {
|
|
60
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
61
|
+
const originalKey = globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
62
|
+
const originalIV = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
63
|
+
const combined = adapter.combineKeyAndIV(originalKey, originalIV);
|
|
64
|
+
const { key, iv } = adapter.splitKeyAndIV(combined);
|
|
65
|
+
assert.deepStrictEqual(key, originalKey, 'Roundtrip key should match original');
|
|
66
|
+
assert.deepStrictEqual(iv, originalIV, 'Roundtrip IV should match original');
|
|
67
|
+
});
|
|
68
|
+
await test('should validate key length in combineKeyAndIV', async () => {
|
|
69
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
70
|
+
const wrongKey = new Uint8Array(31); // Wrong size
|
|
71
|
+
const correctIV = new Uint8Array(16);
|
|
72
|
+
assert.throws(() => adapter.combineKeyAndIV(wrongKey, correctIV), {
|
|
73
|
+
name: 'Error',
|
|
74
|
+
message: 'AES-256 key must be 32 bytes, got 31',
|
|
75
|
+
}, 'Should throw error for wrong key size');
|
|
76
|
+
});
|
|
77
|
+
await test('should validate IV length in combineKeyAndIV', async () => {
|
|
78
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
79
|
+
const correctKey = new Uint8Array(32);
|
|
80
|
+
const wrongIV = new Uint8Array(15); // Wrong size
|
|
81
|
+
assert.throws(() => adapter.combineKeyAndIV(correctKey, wrongIV), {
|
|
82
|
+
name: 'Error',
|
|
83
|
+
message: 'AES-CTR IV must be 16 bytes, got 15',
|
|
84
|
+
}, 'Should throw error for wrong IV size');
|
|
85
|
+
});
|
|
86
|
+
await test('should validate combined length in splitKeyAndIV', async () => {
|
|
87
|
+
const adapter = new GenericAesCtrStreamingCrypto();
|
|
88
|
+
const wrongCombined = new Uint8Array(47); // Wrong size
|
|
89
|
+
assert.throws(() => adapter.splitKeyAndIV(wrongCombined), {
|
|
90
|
+
name: 'Error',
|
|
91
|
+
message: 'AES-256-CTR combined key+IV must be 48 bytes, got 47',
|
|
92
|
+
}, 'Should throw error for wrong combined size');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=node-generic-crypto-adapter.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../test/setup.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Centralized polyfill for globalThis.crypto for Node.js <19
|
|
2
|
+
if (typeof globalThis.crypto === 'undefined') {
|
|
3
|
+
try {
|
|
4
|
+
// @ts-expect-error
|
|
5
|
+
globalThis.crypto = (await import('crypto')).webcrypto;
|
|
6
|
+
}
|
|
7
|
+
catch (e) {
|
|
8
|
+
throw new Error('globalThis.crypto is not available.');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=setup.js.map
|