diff --git a/cli/package-lock.json b/cli/package-lock.json index 4dfa91eb..371a465d 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -360,7 +360,7 @@ "node_modules/@opentdf/sdk": { "version": "0.2.0", "resolved": "file:../lib/opentdf-sdk-0.2.0.tgz", - "integrity": "sha512-RbOa0RteYE0loL/ZKrUytp/tMSTGltrbM4Cy68z6WJPjIcHQXq6Su/ejhTJVBgtrgZJKkaBnbaQLWbCz+j5rpQ==", + "integrity": "sha512-M6CnApIEtUv6POjH/s0fvIBLSeiR6DLnY1z9HA64MHaXI7O+GgJ2ZNg86eaIoKAZeso4pe9Ng8ptw+V3Zmf5kQ==", "license": "BSD-3-Clause-Clear", "dependencies": { "browser-fs-access": "^0.34.1", diff --git a/cli/src/cli.ts b/cli/src/cli.ts index fe9ad484..7b6ef877 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -1,6 +1,6 @@ import './polyfills.js'; import { createWriteStream, openAsBlob } from 'node:fs'; -import { stat, writeFile } from 'node:fs/promises'; +import { stat } from 'node:fs/promises'; import { Writable } from 'node:stream'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; @@ -15,6 +15,7 @@ import { AuthProviders, version, OpenTDF, + DecoratedStream, } from '@opentdf/sdk'; import { CLIError, Level, log } from './logger.js'; import { webcrypto } from 'crypto'; @@ -478,26 +479,21 @@ export const handleArgs = (args: string[]) => { }); log('SILLY', `Initialized client ${JSON.stringify(client)}`); + let ct: DecoratedStream; if ('tdf3' === argv.containerType || 'ztdf' === argv.containerType) { log('DEBUG', `TDF3 Create`); - const ct = await client.createZTDF(await parseCreateZTDFOptions(argv)); - if (!ct) { - throw new CLIError('CRITICAL', 'Encrypt configuration error: No output?'); - } - const destination = createWriteStream(argv.output as string); - await ct.pipeTo(Writable.toWeb(destination)); + ct = await client.createZTDF(await parseCreateZTDFOptions(argv)); } else { - log('SILLY', `Initialized client ${JSON.stringify(client)}`); - - const cyphertext = await client.createNanoTDF(await parseCreateNanoTDFOptions(argv)); - - log('DEBUG', `Handle cyphertext output ${JSON.stringify(cyphertext)}`); - if (argv.output) { - await writeFile(argv.output, new Uint8Array(cyphertext)); - } else { - console.log(base64.encodeArrayBuffer(cyphertext)); - } + log('DEBUG', `Nano Create`); + ct = await client.createNanoTDF(await parseCreateNanoTDFOptions(argv)); + } + if (!ct) { + throw new CLIError('CRITICAL', 'Encrypt configuration error: No output?'); } + const destination = argv.output + ? process.stdout + : createWriteStream(argv.output as string); + await ct.pipeTo(Writable.toWeb(destination)); } ) .usage('openTDF CLI\n\nUsage: $0 [options]') diff --git a/lib/src/opentdf.ts b/lib/src/opentdf.ts index ac67a59a..663e8f81 100644 --- a/lib/src/opentdf.ts +++ b/lib/src/opentdf.ts @@ -223,7 +223,7 @@ export class OpenTDF { ); } - async createNanoTDF(opts: CreateNanoTDFOptions): Promise { + async createNanoTDF(opts: CreateNanoTDFOptions): Promise { opts = { ...this.defaultCreateOptions, ...opts }; const collection = await this.createNanoTDFCollection(opts); const ciphertext = await collection.encrypt(opts.source); @@ -362,7 +362,7 @@ async function streamify(ab: Promise): Promise Promise; + encrypt: (source: Source) => Promise>; close: () => Promise; }; @@ -387,12 +387,21 @@ class Collection { }); } - async encrypt(source: Source): Promise { + async encrypt(source: Source): Promise { if (!this.client) { throw new ConfigurationError('Collection is closed'); } const chunker = await fromSource(source); - return this.client.encrypt(await chunker()); + const cipherChunk = await this.client.encrypt(await chunker()); + const stream: DecoratedStream = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array(cipherChunk)); + controller.close(); + }, + }); + // TODO: client's header object is private + // stream.header = this.client.header; + return stream; } async close() { diff --git a/lib/tests/web/roundtrip.test.ts b/lib/tests/web/roundtrip.test.ts index e3ff218e..6ca65c00 100644 --- a/lib/tests/web/roundtrip.test.ts +++ b/lib/tests/web/roundtrip.test.ts @@ -59,7 +59,7 @@ describe('Local roundtrip Tests', () => { source: { type: 'chunker', location: fromString('hello world') }, }); const nanotdfParsed = await client.read({ - source: { type: 'buffer', location: new Uint8Array(cipherText) }, + source: { type: 'stream', location: cipherText }, }); expect(nanotdfParsed.header?.kas?.url).to.equal(kasEndpoint); expect(nanotdfParsed.header?.kas?.identifier).to.equal('e1'); diff --git a/web-app/package-lock.json b/web-app/package-lock.json index 5d3df1a5..f296869f 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -944,7 +944,7 @@ "node_modules/@opentdf/sdk": { "version": "0.2.0", "resolved": "file:../lib/opentdf-sdk-0.2.0.tgz", - "integrity": "sha512-RbOa0RteYE0loL/ZKrUytp/tMSTGltrbM4Cy68z6WJPjIcHQXq6Su/ejhTJVBgtrgZJKkaBnbaQLWbCz+j5rpQ==", + "integrity": "sha512-M6CnApIEtUv6POjH/s0fvIBLSeiR6DLnY1z9HA64MHaXI7O+GgJ2ZNg86eaIoKAZeso4pe9Ng8ptw+V3Zmf5kQ==", "license": "BSD-3-Clause-Clear", "dependencies": { "browser-fs-access": "^0.34.1", @@ -4852,7 +4852,7 @@ }, "@opentdf/sdk": { "version": "file:../lib/opentdf-sdk-0.2.0.tgz", - "integrity": "sha512-RbOa0RteYE0loL/ZKrUytp/tMSTGltrbM4Cy68z6WJPjIcHQXq6Su/ejhTJVBgtrgZJKkaBnbaQLWbCz+j5rpQ==", + "integrity": "sha512-M6CnApIEtUv6POjH/s0fvIBLSeiR6DLnY1z9HA64MHaXI7O+GgJ2ZNg86eaIoKAZeso4pe9Ng8ptw+V3Zmf5kQ==", "requires": { "browser-fs-access": "^0.34.1", "buffer-crc32": "^0.2.13", diff --git a/web-app/src/App.tsx b/web-app/src/App.tsx index c5b11932..a647fc9d 100644 --- a/web-app/src/App.tsx +++ b/web-app/src/App.tsx @@ -383,77 +383,58 @@ function App() { }, dpopKeys: oidcClient.getSigningKey(), }); + setDownloadState('Encrypting...'); + let f: FileSystemFileHandle | undefined; + const downloadName = `${inputFileName}.tdf`; + if (sinkType === 'fsapi') { + f = await getNewFileHandle('tdf', downloadName); + } + const progressTransformers = makeProgressPair(size, 'Encrypt'); + + let cipherText: ReadableStream; switch (encryptContainerType) { - case 'nano': { - if ('url' in inputSource) { - throw new Error('Unsupported : fetch the url I guess?'); - } - setDownloadState('Encrypting...'); - const cipherText = await client.createNanoTDF({ + case 'nano': + cipherText = await client.createNanoTDF({ source: { type: 'stream', location: source }, }); - switch (sinkType) { - case 'file': - { - saver(new Blob([cipherText]), `${inputFileName}.ntdf`); - } - break; - case 'fsapi': - { - const file = await getNewFileHandle('ntdf', `${inputFileName}.ntdf`); - const writable = await file.createWritable(); - try { - await writable.write(cipherText); - setDownloadState('Encrypt Complete'); - } catch (e) { - setDownloadState(`Encrypt Failed: ${e}`); - } finally { - await writable.close(); - } - } - break; - case 'none': - break; - } break; - } - case 'tdf': { + case 'tdf': try { - let f: FileSystemFileHandle | undefined; - const downloadName = `${inputFileName}.tdf`; - if (sinkType === 'fsapi') { - f = await getNewFileHandle('tdf', downloadName); - } - const progressTransformers = makeProgressPair(size, 'Encrypt'); - const cipherTextFromClient = await client.createZTDF({ + cipherText = await client.createZTDF({ source: { type: 'stream', location: source.pipeThrough(progressTransformers.reader) }, }); - const cipherTextWithProgress = cipherTextFromClient.pipeThrough( - progressTransformers.writer - ); - switch (sinkType) { - case 'file': - await toFile(cipherTextWithProgress, downloadName, { signal: sc.signal }); - break; - case 'fsapi': - if (!f) { - throw new Error(); - } - const writable = await f.createWritable(); - await cipherTextWithProgress.pipeTo(writable, { signal: sc.signal }); - break; - case 'none': - await cipherTextWithProgress.pipeTo(drain(), { signal: sc.signal }); - break; - } } catch (e) { setDownloadState(`Encrypt Failed: ${e}`); console.error('Encrypt Failed', e); } - setStreamController(undefined); break; + default: + setDownloadState(`Unsupported type`); + console.error('Encrypt Failed'); + return; + } + const cipherTextWithProgress = cipherText.pipeThrough(progressTransformers.writer); + try { + switch (sinkType) { + case 'file': + await toFile(cipherTextWithProgress, downloadName, { signal: sc.signal }); + break; + case 'fsapi': + if (!f) { + throw new Error(); + } + const writable = await f.createWritable(); + await cipherTextWithProgress.pipeTo(writable, { signal: sc.signal }); + break; + case 'none': + await cipherTextWithProgress.pipeTo(drain(), { signal: sc.signal }); + break; } + } catch (e) { + setDownloadState(`Encrypt Failed: ${e}`); + console.error('Encrypt Failed', e); } + setStreamController(undefined); return true; };