From 65907879f30d219a55455b710db61c4d26bd2157 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Mon, 21 Oct 2024 20:19:15 -0400 Subject: [PATCH 1/4] fix(sdk): Remove stray call to node:Buffer Update assertion.ts fix(lib): Removes all refs to node:buffer I could find - removes the polyfill from the web-test-runner, to make sure we aren't accidentally including it (at least in things that covers) - updates all .spec and .test files, in turn - renames a bunch of things called `buffer` to something else so I can search for this easier Update cli.ts --- cli/package-lock.json | 53 +---- cli/src/cli.ts | 12 +- lib/package-lock.json | 24 +-- lib/package.json | 1 - lib/src/nanotdf/models/Header.ts | 20 +- lib/src/nanotdf/models/Payload.ts | 12 +- .../nanotdf/models/Policy/EmbeddedPolicy.ts | 12 +- lib/src/nanotdf/models/Policy/RemotePolicy.ts | 10 +- lib/src/nanotdf/models/ResourceLocator.ts | 12 +- lib/src/nanotdf/models/Signature.ts | 8 +- lib/tdf3/src/ciphers/aes-gcm-cipher.ts | 8 +- lib/tdf3/src/client/builders.ts | 29 ++- lib/tdf3/src/tdf.ts | 2 +- lib/tdf3/src/utils/chunkers.ts | 4 +- lib/tdf3/src/utils/index.ts | 18 +- lib/tdf3/src/utils/zip-reader.ts | 11 +- lib/tests/mocha/unit/keysplits.spec.ts | 4 +- lib/tests/mocha/unit/tdf.spec.ts | 8 +- lib/tests/mocha/unit/zip.spec.ts | 11 +- lib/tests/server.ts | 18 +- lib/webpack.test.config.cjs | 5 - remote-store/package-lock.json | 182 ------------------ remote-store/package.json | 1 - web-app/package-lock.json | 24 +-- 24 files changed, 121 insertions(+), 368 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 407e104c..ea4320a5 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -36,9 +36,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz", - "integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -372,14 +372,13 @@ "node_modules/@opentdf/client": { "version": "2.1.0", "resolved": "file:../lib/opentdf-client-2.1.0.tgz", - "integrity": "sha512-Us1uiGHNgs8dZDyfVutDVyo2vXISJOBfxtUTknOOgrnZuz8ZvJtoNvTLb+QW9ha4wdW1eQaiu7bogxlYbOcGAA==", + "integrity": "sha512-1KZVuJLh+MBXPwAx/RiDw0clFvbFqLDaqfi4T5rMgKlFJ4rNX5immyQcZnu7YS8degBed/4CHONgvFya33NGFA==", "license": "BSD-3-Clause-Clear", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", "base64-js": "^1.5.1", "browser-fs-access": "^0.34.1", - "buffer": "^6.0.3", "buffer-crc32": "^0.2.13", "dpop": "^1.2.0", "eventemitter3": "^5.0.1", @@ -973,30 +972,6 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -1844,26 +1819,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", diff --git a/cli/src/cli.ts b/cli/src/cli.ts index fd066c47..10755f69 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -19,6 +19,7 @@ import { import { CLIError, Level, log } from './logger.js'; import { webcrypto } from 'crypto'; import { attributeFQNsAsValues } from '@opentdf/client/nano'; +import { base64 } from '@opentdf/client/encodings'; type AuthToProcess = { auth?: string; @@ -37,8 +38,9 @@ const bindingTypes = ['ecdsa', 'gmac']; const containerTypes = ['tdf3', 'nano', 'dataset', 'ztdf']; const parseJwt = (jwt: string, field = 1) => { - return JSON.parse(Buffer.from(jwt.split('.')[field], 'base64').toString()); + return JSON.parse(base64.decode(jwt.split('.')[field])); }; + const parseJwtComplete = (jwt: string) => { return { header: parseJwt(jwt, 0), payload: parseJwt(jwt) }; }; @@ -413,9 +415,9 @@ export const handleArgs = (args: string[]) => { log('DEBUG', 'Handle output.'); if (argv.output) { - await writeFile(argv.output, Buffer.from(plaintext)); + await writeFile(argv.output, new Uint8Array(plaintext)); } else { - console.log(Buffer.from(plaintext).toString('utf8')); + console.log(new TextDecoder().decode(plaintext)); } } const lastRequest = authProvider.requestLog[authProvider.requestLog.length - 1]; @@ -503,9 +505,9 @@ export const handleArgs = (args: string[]) => { log('DEBUG', `Handle cyphertext output ${JSON.stringify(cyphertext)}`); if (argv.output) { - await writeFile(argv.output, Buffer.from(cyphertext)); + await writeFile(argv.output, new Uint8Array(cyphertext)); } else { - console.log(Buffer.from(cyphertext).toString('base64')); + console.log(base64.encodeArrayBuffer(cyphertext)); } } } diff --git a/lib/package-lock.json b/lib/package-lock.json index 9fb728a9..0267c2b5 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -13,7 +13,6 @@ "axios-retry": "^3.9.0", "base64-js": "^1.5.1", "browser-fs-access": "^0.34.1", - "buffer": "^6.0.3", "buffer-crc32": "^0.2.13", "dpop": "^1.2.0", "eventemitter3": "^5.0.1", @@ -2676,28 +2675,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "license": "MIT", @@ -5148,6 +5125,7 @@ }, "node_modules/ieee754": { "version": "1.2.1", + "dev": true, "funding": [ { "type": "github", diff --git a/lib/package.json b/lib/package.json index f9e8da2e..6e7d0539 100644 --- a/lib/package.json +++ b/lib/package.json @@ -64,7 +64,6 @@ "axios-retry": "^3.9.0", "base64-js": "^1.5.1", "browser-fs-access": "^0.34.1", - "buffer": "^6.0.3", "buffer-crc32": "^0.2.13", "dpop": "^1.2.0", "eventemitter3": "^5.0.1", diff --git a/lib/src/nanotdf/models/Header.ts b/lib/src/nanotdf/models/Header.ts index 7a11d7c3..71577e3e 100644 --- a/lib/src/nanotdf/models/Header.ts +++ b/lib/src/nanotdf/models/Header.ts @@ -232,20 +232,20 @@ export default class Header { /** * Copy the contents of the header to buffer */ - copyToBuffer(buffer: Uint8Array): void { - if (this.length > buffer.length) { + copyToBuffer(target: Uint8Array): void { + if (this.length > target.length) { throw new InvalidFileError('invalid buffer size to copy tdf header'); } let offset = 0; // Write Magic number and version - buffer.set(this.magicNumberVersion, 0); + target.set(this.magicNumberVersion, 0); offset += this.magicNumberVersion.length; // Write kas resource locator const kasResourceLocatorBuf = this.kas.toBuffer(); - buffer.set(kasResourceLocatorBuf, offset); + target.set(kasResourceLocatorBuf, offset); offset += kasResourceLocatorBuf.length; // Write ECC & Binding Mode @@ -253,7 +253,7 @@ export default class Header { const eccBingingMode = (ecdsaBinding << 7) | this.ephemeralCurveName; const eccBingingModeAsByte = new Uint8Array(1); eccBingingModeAsByte[0] = eccBingingMode; - buffer.set(eccBingingModeAsByte, offset); + target.set(eccBingingModeAsByte, offset); offset += eccBingingModeAsByte.length; // Write symmetric & payload config @@ -262,16 +262,16 @@ export default class Header { (isSignatureEnable << 7) | this.signatureCurveName | this.symmetricCipher; const symmetricPayloadConfigAsByte = new Uint8Array(1); symmetricPayloadConfigAsByte[0] = symmetricPayloadConfig; - buffer.set(symmetricPayloadConfigAsByte, offset); + target.set(symmetricPayloadConfigAsByte, offset); offset += symmetricPayloadConfigAsByte.length; // Write the policy const policyBuffer = this.policy.toBuffer(); - buffer.set(policyBuffer, offset); + target.set(policyBuffer, offset); offset += policyBuffer.length; // Write the ephemeral public key - buffer.set(this.ephemeralPublicKey, offset); + target.set(this.ephemeralPublicKey, offset); } /** @@ -304,8 +304,8 @@ export default class Header { */ toBuffer(): ArrayBuffer { const arrayBuffer = new ArrayBuffer(this.length); - const buffer = new Uint8Array(arrayBuffer); - this.copyToBuffer(buffer); + const target = new Uint8Array(arrayBuffer); + this.copyToBuffer(target); return arrayBuffer; } diff --git a/lib/src/nanotdf/models/Payload.ts b/lib/src/nanotdf/models/Payload.ts index 37645a82..55ae7336 100644 --- a/lib/src/nanotdf/models/Payload.ts +++ b/lib/src/nanotdf/models/Payload.ts @@ -167,8 +167,8 @@ export default class Payload { /** * Copy the contents of the signature to buffer */ - copyToBuffer(buffer: Uint8Array): void { - if (this.length > buffer.length) { + copyToBuffer(target: Uint8Array): void { + if (this.length > target.length) { throw new Error('internal: invalid buffer size to copy payload'); } @@ -188,9 +188,9 @@ export default class Payload { payloadSizeAsBg[1] = lengthAsUint24[1]; payloadSizeAsBg[2] = lengthAsUint24[0]; - buffer.set(payloadSizeAsBg, 0); - buffer.set(this.iv, payloadSizeAsBg.length); - buffer.set(this.ciphertext, payloadSizeAsBg.length + this.iv.length); - buffer.set(this.authTag, payloadSizeAsBg.length + this.iv.length + this.ciphertext.length); + target.set(payloadSizeAsBg, 0); + target.set(this.iv, payloadSizeAsBg.length); + target.set(this.ciphertext, payloadSizeAsBg.length + this.iv.length); + target.set(this.authTag, payloadSizeAsBg.length + this.iv.length + this.ciphertext.length); } } diff --git a/lib/src/nanotdf/models/Policy/EmbeddedPolicy.ts b/lib/src/nanotdf/models/Policy/EmbeddedPolicy.ts index 3b8c3de4..321b760b 100644 --- a/lib/src/nanotdf/models/Policy/EmbeddedPolicy.ts +++ b/lib/src/nanotdf/models/Policy/EmbeddedPolicy.ts @@ -69,13 +69,13 @@ class EmbeddedPolicy extends AbstractPolicy implements EmbeddedPolicyInterface { * Return the content of the policy */ override toBuffer(): Uint8Array { - const buffer = new Uint8Array(this.getLength()); + const target = new Uint8Array(this.getLength()); if (this.content.length > EmbeddedPolicy.MAX_POLICY_SIZE) { throw new ConfigurationError("TDF Policy can't be more that 2^16"); } - buffer.set([this.type], 0); + target.set([this.type], 0); // Write the policy length, assuming the host system is little endian // TODO: There should be better way to convert to big endian @@ -86,15 +86,15 @@ class EmbeddedPolicy extends AbstractPolicy implements EmbeddedPolicyInterface { const policyContentSizeAsBg = new Uint8Array(2); policyContentSizeAsBg[0] = temp[1]; policyContentSizeAsBg[1] = temp[0]; - buffer.set(policyContentSizeAsBg, 1); + target.set(policyContentSizeAsBg, 1); // Write the policy content - buffer.set(this.content, policyContentSizeAsBg.length + 1); + target.set(this.content, policyContentSizeAsBg.length + 1); // Write the binding. - buffer.set(this.binding, this.content.length + policyContentSizeAsBg.length + 1); + target.set(this.binding, this.content.length + policyContentSizeAsBg.length + 1); - return buffer; + return target; } } diff --git a/lib/src/nanotdf/models/Policy/RemotePolicy.ts b/lib/src/nanotdf/models/Policy/RemotePolicy.ts index 59131533..0a94c472 100644 --- a/lib/src/nanotdf/models/Policy/RemotePolicy.ts +++ b/lib/src/nanotdf/models/Policy/RemotePolicy.ts @@ -56,18 +56,18 @@ class RemotePolicy extends AbstractPolicy implements RemotePolicyInterface { * Return the content of the policy */ override toBuffer(): Uint8Array { - const buffer = new Uint8Array(this.getLength()); + const target = new Uint8Array(this.getLength()); - buffer.set([PolicyTypeEnum.Remote], 0); + target.set([PolicyTypeEnum.Remote], 0); // Write the remote policy location const resourceLocatorAsBuf = this.remotePolicy.toBuffer(); - buffer.set(resourceLocatorAsBuf, 1); + target.set(resourceLocatorAsBuf, 1); // Write the binding. - buffer.set(this.binding, resourceLocatorAsBuf.length + 1); + target.set(this.binding, resourceLocatorAsBuf.length + 1); - return buffer; + return target; } } diff --git a/lib/src/nanotdf/models/ResourceLocator.ts b/lib/src/nanotdf/models/ResourceLocator.ts index 0944407c..efd1aa9f 100644 --- a/lib/src/nanotdf/models/ResourceLocator.ts +++ b/lib/src/nanotdf/models/ResourceLocator.ts @@ -178,7 +178,7 @@ export default class ResourceLocator { * Return the contents of the Resource Locator in buffer */ toBuffer(): Uint8Array { - const buffer = new Uint8Array(ResourceLocator.BODY_OFFSET + this.body.length + this.idType); + const target = new Uint8Array(ResourceLocator.BODY_OFFSET + this.body.length + this.idType); let idTypeNibble = 0; switch (this.idType) { case ResourceLocatorIdentifierEnum.TwoBytes: @@ -191,13 +191,13 @@ export default class ResourceLocator { idTypeNibble = ResourceLocator.IDENTIFIER_32_BYTE; break; } - buffer.set([this.protocol | idTypeNibble], ResourceLocator.PROTOCOL_OFFSET); - buffer.set([this.lengthOfBody], ResourceLocator.LENGTH_OFFSET); - buffer.set(new TextEncoder().encode(this.body), ResourceLocator.BODY_OFFSET); + target.set([this.protocol | idTypeNibble], ResourceLocator.PROTOCOL_OFFSET); + target.set([this.lengthOfBody], ResourceLocator.LENGTH_OFFSET); + target.set(new TextEncoder().encode(this.body), ResourceLocator.BODY_OFFSET); if (this.id) { - buffer.set(new TextEncoder().encode(this.id), ResourceLocator.BODY_OFFSET + this.body.length); + target.set(new TextEncoder().encode(this.id), ResourceLocator.BODY_OFFSET + this.body.length); } - return buffer; + return target; } /** diff --git a/lib/src/nanotdf/models/Signature.ts b/lib/src/nanotdf/models/Signature.ts index 05610bbf..091b6283 100644 --- a/lib/src/nanotdf/models/Signature.ts +++ b/lib/src/nanotdf/models/Signature.ts @@ -74,12 +74,12 @@ export default class Signature { /** * Copy the contents of the signature to buffer */ - copyToBuffer(buffer: Uint8Array): void { - if (this.length > buffer.length) { + copyToBuffer(target: Uint8Array): void { + if (this.length > target.length) { throw new ConfigurationError('Invalid buffer size to copy signature'); } - buffer.set(this.publicKey, 0); - buffer.set(this.signature, this.publicKey.length); + target.set(this.publicKey, 0); + target.set(this.signature, this.publicKey.length); } } diff --git a/lib/tdf3/src/ciphers/aes-gcm-cipher.ts b/lib/tdf3/src/ciphers/aes-gcm-cipher.ts index 3797ad36..dade0160 100644 --- a/lib/tdf3/src/ciphers/aes-gcm-cipher.ts +++ b/lib/tdf3/src/ciphers/aes-gcm-cipher.ts @@ -18,15 +18,15 @@ type ProcessGcmPayload = { payloadAuthTag: Binary; }; // Should this be a Binary, Buffer, or... both? -function processGcmPayload(buffer: ArrayBuffer): ProcessGcmPayload { +function processGcmPayload(source: ArrayBuffer): ProcessGcmPayload { // Read the 12 byte IV from the beginning of the stream - const payloadIv = Binary.fromArrayBuffer(buffer.slice(0, 12)); + const payloadIv = Binary.fromArrayBuffer(source.slice(0, 12)); // Slice the final 16 bytes of the buffer for the authentication tag - const payloadAuthTag = Binary.fromArrayBuffer(buffer.slice(-16)); + const payloadAuthTag = Binary.fromArrayBuffer(source.slice(-16)); return { - payload: Binary.fromArrayBuffer(buffer.slice(12, -16)), + payload: Binary.fromArrayBuffer(source.slice(12, -16)), payloadIv, payloadAuthTag, }; diff --git a/lib/tdf3/src/client/builders.ts b/lib/tdf3/src/client/builders.ts index 10752d63..5eb382ce 100644 --- a/lib/tdf3/src/client/builders.ts +++ b/lib/tdf3/src/client/builders.ts @@ -149,7 +149,7 @@ class EncryptParamsBuilder { /** * Specify the content to encrypt, in buffer form. - * @param {Buffer} buf - a buffer to encrypt. + * @param buf to encrypt. */ setBufferSource(buf: ArrayBuffer) { const stream = new ReadableStream({ @@ -163,10 +163,9 @@ class EncryptParamsBuilder { /** * Specify the content to encrypt, in buffer form. Returns this object for method chaining. - * @param {Buffer} buf - a buffer to encrypt - * @return {EncryptParamsBuilder} - this object. + * @param buf - a buffer to encrypt */ - withBufferSource(buf: ArrayBuffer) { + withBufferSource(buf: ArrayBuffer): this { this.setBufferSource(buf); return this; } @@ -554,7 +553,7 @@ class DecryptParamsBuilder { /** * Set the TDF ciphertext to decrypt, in buffer form. - * @param {Buffer} buffer - a buffer to decrypt. + * @param buffer to decrypt. */ setBufferSource(buffer: Uint8Array) { this._params.source = { type: 'buffer', location: buffer }; @@ -562,10 +561,9 @@ class DecryptParamsBuilder { /** * Set the TDF ciphertext to decrypt, in buffer form. Returns this object for method chaining. - * @param {Buffer} buffer - a buffer to decrypt. - * @return {DecryptParamsBuilder} - this object. + * @param buffer to decrypt. */ - withBufferSource(buffer: Uint8Array): DecryptParamsBuilder { + withBufferSource(buffer: Uint8Array): this { this.setBufferSource(buffer); return this; } @@ -587,7 +585,7 @@ class DecryptParamsBuilder { * @param {string} url - a tdf3 remote URL. * @return {DecryptParamsBuilder} - this object. */ - withUrlSource(url: string): DecryptParamsBuilder { + withUrlSource(url: string): this { this.setUrlSource(url); return this; } @@ -602,10 +600,9 @@ class DecryptParamsBuilder { /** * Specify the TDF ciphertext to decrypt, in stream form. Returns this object for method chaining. - * @param {Readable} stream - a Readable stream to decrypt. - * @return {DecryptParamsBuilder} - this object. + * @param stream to decrypt. */ - withStreamSource(stream: ReadableStream) { + withStreamSource(stream: ReadableStream): this { if (!stream?.getReader) { throw new ConfigurationError( `Source must be a WebReadableStream. Run node streams through stream.Readable.toWeb()` @@ -621,7 +618,7 @@ class DecryptParamsBuilder { * @param {string} string - a string to decrypt. */ setStringSource(string: string) { - this.setBufferSource(Buffer.from(string, 'binary')); + this.setBufferSource(new TextEncoder().encode(string)); } /** @@ -629,7 +626,7 @@ class DecryptParamsBuilder { * @param {string} string - a string to decrypt. * @return {DecryptParamsBuilder} - this object. */ - withStringSource(string: string): DecryptParamsBuilder { + withStringSource(string: string): this { this.setStringSource(string); return this; } @@ -648,7 +645,7 @@ class DecryptParamsBuilder { * Returns this object for method chaining. * @param source (node) the path of the local file to decrypt, or the Blob (browser/node) */ - withFileSource(source: Blob): DecryptParamsBuilder { + withFileSource(source: Blob): this { this.setFileSource(source); return this; } @@ -672,7 +669,7 @@ class DecryptParamsBuilder { * @param {ArrayBuffer} arraybuffer - the ArrayBuffer used to load file content from a browser * @return {DecryptParamsBuilder} - this object. */ - withArrayBufferSource(arraybuffer: ArrayBuffer): DecryptParamsBuilder { + withArrayBufferSource(arraybuffer: ArrayBuffer): this { this.setArrayBufferSource(arraybuffer); return this; } diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 7b686f32..9d6ce933 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -838,7 +838,7 @@ export async function writeStream(cfg: EncryptConfiguration): Promise { }; }; -export const fromBuffer = (buffer: Uint8Array | Buffer): Chunker => { +export const fromBuffer = (source: Uint8Array | Buffer): Chunker => { return (byteStart?: number, byteEnd?: number) => { - return Promise.resolve(buffer.slice(byteStart, byteEnd)); + return Promise.resolve(source.slice(byteStart, byteEnd)); }; }; diff --git a/lib/tdf3/src/utils/index.ts b/lib/tdf3/src/utils/index.ts index 0fa41f7a..b8bf7494 100644 --- a/lib/tdf3/src/utils/index.ts +++ b/lib/tdf3/src/utils/index.ts @@ -130,39 +130,39 @@ function base64Slice(buf: Uint8Array, start: number, end: number): string { // https://github.com/feross/buffer/blob/master/index.js#L483 export function buffToString( - buffer: Uint8Array, + source: Uint8Array, encoding: SupportedEncoding = 'utf8', start = 0, - end = buffer.length + end = source.length ) { if (start < 0) { start = 0; } - if (end > buffer.length) { - end = buffer.length; + if (end > source.length) { + end = source.length; } // Return early if start > buffer.length. Done here to prevent potential uint32 // coercion fail below. - if (start > buffer.length || end <= 0 || end <= start) { + if (start > source.length || end <= 0 || end <= start) { return ''; } switch (encoding) { case 'hex': - return hexSlice(buffer, start, end); + return hexSlice(source, start, end); case 'utf8': case 'utf-8': - return utf8Slice(buffer, start, end); + return utf8Slice(source, start, end); case 'latin1': case 'binary': - return latin1Slice(buffer, start, end); + return latin1Slice(source, start, end); case 'base64': - return base64Slice(buffer, start, end); + return base64Slice(source, start, end); } } diff --git a/lib/tdf3/src/utils/zip-reader.ts b/lib/tdf3/src/utils/zip-reader.ts index 1fd6cdbe..1b59fb57 100644 --- a/lib/tdf3/src/utils/zip-reader.ts +++ b/lib/tdf3/src/utils/zip-reader.ts @@ -69,8 +69,8 @@ export class ZipReader { /** * Utility function to get the centralDirectory for the zip file. - * @param {Buffer} chunkBuffer Takes a buffer of a portion of the file - * @return {Object} The central directory represented as an object + * It reads the end of the file to find it. + * @return The central directory represented as an object */ async getCentralDirectory(): Promise { const chunk = await this.getChunk(-1000); @@ -138,10 +138,9 @@ export class ZipReader { } /** - * Takes a portion of a ZIP (must be the last portion of a ZIP to work) and returns an array of Buffers - * that correspond to each central directory. - * @param {Buffer} chunkBuffer The last portion of a zip file - * @returns {Array} An array of buffers + * extracts the CD buffer entries from the end of a zip file. + * @param chunkBuffer The last portion of a zip file + * @returns an array of typed arrays, each element corresponding to a central directory record */ getCDBuffers(chunkBuffer: Uint8Array): Uint8Array[] { const cdBuffers = []; diff --git a/lib/tests/mocha/unit/keysplits.spec.ts b/lib/tests/mocha/unit/keysplits.spec.ts index f46638bb..4d6bad44 100644 --- a/lib/tests/mocha/unit/keysplits.spec.ts +++ b/lib/tests/mocha/unit/keysplits.spec.ts @@ -8,8 +8,8 @@ import * as defaultCryptoService from '../../../tdf3/src/crypto/index.js'; describe('keysplits', () => { it('binary xor', () => { - expect(bxor(Buffer.from([0x0f]), Buffer.from([0xf0]))).to.eql(Buffer.from([0xff])); - expect(bxor(Buffer.from([0x0f]), Buffer.from([0x0f]))).to.eql(Buffer.from([0x00])); + expect(bxor(new Uint8Array([0x0f]), new Uint8Array([0xf0]))).to.eql(new Uint8Array([0xff])); + expect(bxor(new Uint8Array([0x0f]), new Uint8Array([0x0f]))).to.eql(new Uint8Array([0x00])); }); it('should return the original byte array with split set to one part', async () => { diff --git a/lib/tests/mocha/unit/tdf.spec.ts b/lib/tests/mocha/unit/tdf.spec.ts index 62404aea..f30c5432 100644 --- a/lib/tests/mocha/unit/tdf.spec.ts +++ b/lib/tests/mocha/unit/tdf.spec.ts @@ -43,13 +43,9 @@ HJg= describe('TDF', () => { it('Encodes the postMessage origin properly in wrapHtml', () => { - const cipherText = 'abcezas123'; + const cipherText = new TextEncoder().encode('abcezas123'); const transferUrl = 'https://local.virtru.com/start?htmlProtocol=1'; - const wrapped = TDF.wrapHtml( - Buffer.from(cipherText), - JSON.stringify({ thisIs: 'metadata' }), - transferUrl - ); + const wrapped = TDF.wrapHtml(cipherText, JSON.stringify({ thisIs: 'metadata' }), transferUrl); const rawHtml = new TextDecoder().decode(wrapped); expect(rawHtml).to.include("'https://local.virtru.com', [channel.port2]);"); }); diff --git a/lib/tests/mocha/unit/zip.spec.ts b/lib/tests/mocha/unit/zip.spec.ts index a48a33e5..a38b34e2 100644 --- a/lib/tests/mocha/unit/zip.spec.ts +++ b/lib/tests/mocha/unit/zip.spec.ts @@ -32,24 +32,25 @@ describe('zip utilities', () => { describe('writeUInt64LE', () => { it('not too different', () => { - const b0 = Buffer.alloc(8); + // allocate a new uint8array with 8 bytes + const b0 = new Uint8Array(8); new DataView(b0.buffer).setBigUint64(0, BigInt(1), true); - const b1 = Buffer.alloc(8); + const b1 = new Uint8Array(8); writeUInt64LE(b1, 1, 0); expect(b1).to.eql(b0); }); it('unsafe ints throw', () => { - expect(() => writeUInt64LE(Buffer.alloc(0), 2 ** 54, 0)).to.throw(/unsafe number/); + expect(() => writeUInt64LE(new Uint8Array(0), 2 ** 54, 0)).to.throw(/unsafe number/); }); }); describe('readUInt64LE', () => { it('one', () => { - const b0 = Buffer.alloc(8); + const b0 = new Uint8Array(8); new DataView(b0.buffer).setBigUint64(0, 1n, true); expect(readUInt64LE(b0, 0)).to.equal(1); }); it('unsafe ints throw', () => { - const b0 = Buffer.alloc(8); + const b0 = new Uint8Array(8); new DataView(b0.buffer).setBigUint64(0, 9007199254740992n, true); expect(() => readUInt64LE(b0, 0)).to.throw(/exceeds/); }); diff --git a/lib/tests/server.ts b/lib/tests/server.ts index 972cada3..9041db3f 100644 --- a/lib/tests/server.ts +++ b/lib/tests/server.ts @@ -35,6 +35,18 @@ type RewrapBody = { clientPublicKey: string; }; +function concat(b: ArrayBufferView[]) { + const length = b.reduce((lk, ak) => lk + ak.byteLength, 0); + const buf = new Uint8Array(length); + let offset = 0; + for (const v of b) { + const uint8view = new Uint8Array(v.buffer, v.byteOffset, v.byteLength); + buf.set(uint8view, offset); + offset += uint8view.byteLength; + } + return buf; +} + function getBody(request: IncomingMessage): Promise { return new Promise((resolve, reject) => { const bodyParts: Uint8Array[] = []; @@ -43,7 +55,7 @@ function getBody(request: IncomingMessage): Promise { bodyParts.push(chunk); }) .on('end', () => { - resolve(Buffer.concat(bodyParts)); + resolve(concat(bodyParts)); }) .on('error', reject); }); @@ -237,11 +249,11 @@ const kas: RequestListener = async (req, res) => { res.statusCode = 206; // Partial Content res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Length', rangeData.length); - res.end(Buffer.from(rangeData.buffer)); + res.end(rangeData); } else { res.statusCode = 200; // OK res.setHeader('Content-Type', 'application/octet-stream'); - res.end(Buffer.from(fullRange.buffer)); + res.end(fullRange); } } else if (url.pathname === '/attributes/*/fqn') { const fqnAttributeValues: Record = {}; diff --git a/lib/webpack.test.config.cjs b/lib/webpack.test.config.cjs index 192703f9..814e9606 100644 --- a/lib/webpack.test.config.cjs +++ b/lib/webpack.test.config.cjs @@ -9,11 +9,6 @@ module.exports = { path: path.resolve(__dirname, 'tests/mocha/dist'), filename: '[name].js', }, - plugins: [ - new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - }), - ], resolve: { extensions: ['.js'], }, diff --git a/remote-store/package-lock.json b/remote-store/package-lock.json index 99ef279a..c5540aed 100644 --- a/remote-store/package-lock.json +++ b/remote-store/package-lock.json @@ -14,7 +14,6 @@ "@aws-sdk/middleware-endpoint": "^3.370.0", "@aws-sdk/protocol-http": "^3.370.0", "@aws-sdk/smithy-client": "^3.370.0", - "@opentdf/client": "file:../lib/opentdf-client-2.1.0.tgz", "axios": "^1.6.1" }, "devDependencies": { @@ -1535,18 +1534,6 @@ "node": ">=16.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz", - "integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1818,26 +1805,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@opentdf/client": { - "version": "2.1.0", - "resolved": "file:../lib/opentdf-client-2.1.0.tgz", - "integrity": "sha512-Us1uiGHNgs8dZDyfVutDVyo2vXISJOBfxtUTknOOgrnZuz8ZvJtoNvTLb+QW9ha4wdW1eQaiu7bogxlYbOcGAA==", - "license": "BSD-3-Clause-Clear", - "dependencies": { - "axios": "^1.6.1", - "axios-retry": "^3.9.0", - "base64-js": "^1.5.1", - "browser-fs-access": "^0.34.1", - "buffer": "^6.0.3", - "buffer-crc32": "^0.2.13", - "dpop": "^1.2.0", - "eventemitter3": "^5.0.1", - "jose": "^4.14.4", - "json-canonicalize": "^1.0.6", - "streamsaver": "^2.0.6", - "uuid": "~9.0.0" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3299,42 +3266,12 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/axios-retry": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", - "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.15.4", - "is-retry-allowed": "^2.2.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3371,51 +3308,12 @@ "node": ">=8" } }, - "node_modules/browser-fs-access": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz", - "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==", - "license": "Apache-2.0" - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3641,15 +3539,6 @@ "node": ">=6.0.0" } }, - "node_modules/dpop": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", - "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3870,12 +3759,6 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4234,26 +4117,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -4385,18 +4248,6 @@ "node": ">=8" } }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -4433,15 +4284,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -4454,12 +4296,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/json-canonicalize": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/json-canonicalize/-/json-canonicalize-1.0.6.tgz", - "integrity": "sha512-kP2iYpOS5SZHYhIaR1t9oG80d4uTY3jPoaBj+nimy3njtJk8+sRsVatN8pyJRDRtk9Su3+6XqA2U8k0dByJBUQ==", - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", @@ -5200,12 +5036,6 @@ "node": ">=8.10.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5439,18 +5269,6 @@ "spdx-ranges": "^2.0.0" } }, - "node_modules/streamsaver": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-2.0.6.tgz", - "integrity": "sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - } - ], - "license": "MIT" - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/remote-store/package.json b/remote-store/package.json index 443edeec..69ce4785 100644 --- a/remote-store/package.json +++ b/remote-store/package.json @@ -41,7 +41,6 @@ "@aws-sdk/middleware-endpoint": "^3.370.0", "@aws-sdk/protocol-http": "^3.370.0", "@aws-sdk/smithy-client": "^3.370.0", - "@opentdf/client": "file:../lib/opentdf-client-2.1.0.tgz", "axios": "^1.6.1" }, "devDependencies": { diff --git a/web-app/package-lock.json b/web-app/package-lock.json index c8c1493c..ad42f581 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -350,9 +350,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz", - "integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -607,14 +607,13 @@ "node_modules/@opentdf/client": { "version": "2.1.0", "resolved": "file:../lib/opentdf-client-2.1.0.tgz", - "integrity": "sha512-Us1uiGHNgs8dZDyfVutDVyo2vXISJOBfxtUTknOOgrnZuz8ZvJtoNvTLb+QW9ha4wdW1eQaiu7bogxlYbOcGAA==", + "integrity": "sha512-1KZVuJLh+MBXPwAx/RiDw0clFvbFqLDaqfi4T5rMgKlFJ4rNX5immyQcZnu7YS8degBed/4CHONgvFya33NGFA==", "license": "BSD-3-Clause-Clear", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", "base64-js": "^1.5.1", "browser-fs-access": "^0.34.1", - "buffer": "^6.0.3", "buffer-crc32": "^0.2.13", "dpop": "^1.2.0", "eventemitter3": "^5.0.1", @@ -1295,6 +1294,7 @@ }, "node_modules/buffer": { "version": "6.0.3", + "dev": true, "funding": [ { "type": "github", @@ -2152,6 +2152,7 @@ }, "node_modules/ieee754": { "version": "1.2.1", + "dev": true, "funding": [ { "type": "github", @@ -3958,9 +3959,9 @@ } }, "@babel/runtime": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz", - "integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -4118,13 +4119,12 @@ }, "@opentdf/client": { "version": "file:../lib/opentdf-client-2.1.0.tgz", - "integrity": "sha512-Us1uiGHNgs8dZDyfVutDVyo2vXISJOBfxtUTknOOgrnZuz8ZvJtoNvTLb+QW9ha4wdW1eQaiu7bogxlYbOcGAA==", + "integrity": "sha512-1KZVuJLh+MBXPwAx/RiDw0clFvbFqLDaqfi4T5rMgKlFJ4rNX5immyQcZnu7YS8degBed/4CHONgvFya33NGFA==", "requires": { "axios": "^1.6.1", "axios-retry": "^3.9.0", "base64-js": "^1.5.1", "browser-fs-access": "^0.34.1", - "buffer": "^6.0.3", "buffer-crc32": "^0.2.13", "dpop": "^1.2.0", "eventemitter3": "^5.0.1", @@ -4526,6 +4526,7 @@ }, "buffer": { "version": "6.0.3", + "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -5047,7 +5048,8 @@ "dev": true }, "ieee754": { - "version": "1.2.1" + "version": "1.2.1", + "dev": true }, "ignore": { "version": "5.2.4", From c045bb89ecbbb4f12d03c4f3d829b9da1dabc783 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 23 Oct 2024 17:13:16 -0400 Subject: [PATCH 2/4] fix(cli): Adds --assertions to cli for testing --- cli/package-lock.json | 2 +- cli/src/cli.ts | 28 +++- lib/package.json | 7 + lib/tdf3/src/assertions.ts | 191 ++++++++++++++++++++++++ lib/tdf3/src/client/AssertionConfig.ts | 29 ---- lib/tdf3/src/client/builders.ts | 2 +- lib/tdf3/src/models/assertion.ts | 131 ---------------- lib/tdf3/src/models/index.ts | 2 +- lib/tdf3/src/models/manifest.ts | 2 +- lib/tdf3/src/tdf.ts | 65 ++------ lib/tests/mocha/encrypt-decrypt.spec.ts | 5 +- lib/tests/mocha/unit/assertions.spec.ts | 25 ++++ remote-store/package-lock.json | 137 +++++++++++++++++ remote-store/package.json | 1 + web-app/package-lock.json | 4 +- 15 files changed, 406 insertions(+), 225 deletions(-) create mode 100644 lib/tdf3/src/assertions.ts delete mode 100644 lib/tdf3/src/client/AssertionConfig.ts delete mode 100644 lib/tdf3/src/models/assertion.ts create mode 100644 lib/tests/mocha/unit/assertions.spec.ts diff --git a/cli/package-lock.json b/cli/package-lock.json index ea4320a5..b7d0ede2 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -372,7 +372,7 @@ "node_modules/@opentdf/client": { "version": "2.1.0", "resolved": "file:../lib/opentdf-client-2.1.0.tgz", - "integrity": "sha512-1KZVuJLh+MBXPwAx/RiDw0clFvbFqLDaqfi4T5rMgKlFJ4rNX5immyQcZnu7YS8degBed/4CHONgvFya33NGFA==", + "integrity": "sha512-uE8lpCTtSsoLZtrVh5NARUO9vMhAbzKgw7r4O4eyKoJvbi8La7p0IfuiBiLWrvdDZgARruHAbTYNC8Vi1a/80Q==", "license": "BSD-3-Clause-Clear", "dependencies": { "axios": "^1.6.1", diff --git a/cli/src/cli.ts b/cli/src/cli.ts index 10755f69..56d8581c 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -18,6 +18,7 @@ import { } from '@opentdf/client'; import { CLIError, Level, log } from './logger.js'; import { webcrypto } from 'crypto'; +import * as assertions from '@opentdf/client/assertions'; import { attributeFQNsAsValues } from '@opentdf/client/nano'; import { base64 } from '@opentdf/client/encodings'; @@ -119,8 +120,26 @@ async function tdf3DecryptParamsFor(argv: Partial): Promise): Promise { const c = new EncryptParamsBuilder(); + if (argv.assertions?.length) { + c.withAssertions(parseAssertionConfig(argv.assertions)); + } if (argv.attributes?.length) { c.setAttributes(argv.attributes.split(',')); } @@ -203,7 +222,7 @@ export const handleArgs = (args: string[]) => { group: 'Security:', desc: 'allowed KAS origins, comma separated; defaults to [kasEndpoint]', type: 'string', - validate: (attributes: string) => attributes.split(','), + validate: (uris: string) => uris.split(','), }) .option('ignoreAllowList', { group: 'Security:', @@ -254,6 +273,13 @@ export const handleArgs = (args: string[]) => { // Policy, encryption, and container options .options({ + assertions: { + group: 'Encrypt Options:', + desc: 'ZTDF assertion config objects', + type: 'string', + default: '', + validate: parseAssertionConfig, + }, attributes: { group: 'Encrypt Options:', desc: 'Data attributes for the policy', diff --git a/lib/package.json b/lib/package.json index 6e7d0539..42734f80 100644 --- a/lib/package.json +++ b/lib/package.json @@ -29,6 +29,13 @@ "require": "./dist/cjs/tdf3/index.js", "import": "./dist/web/tdf3/index.js" }, + "./assertions": { + "default": { + "types": "./dist/types/tdf3/src/assertions.d.ts", + "require": "./dist/cjs/tdf3/src/assertions.js", + "import": "./dist/web/tdf3/src/assertions.js" + } + }, "./encodings": { "default": { "types": "./dist/types/src/encodings/index.d.ts", diff --git a/lib/tdf3/src/assertions.ts b/lib/tdf3/src/assertions.ts new file mode 100644 index 00000000..53bd82cf --- /dev/null +++ b/lib/tdf3/src/assertions.ts @@ -0,0 +1,191 @@ +import { canonicalizeEx } from 'json-canonicalize'; +import { SignJWT, jwtVerify } from 'jose'; +import { base64, hex } from '../../src/encodings/index.js'; +import { ConfigurationError, IntegrityError, InvalidFileError } from '../../src/errors.js'; + +export type AssertionKeyAlg = 'RS256' | 'HS256'; +export type AssertionType = 'handling' | 'other'; +export type Scope = 'tdo' | 'payload'; +export type AppliesToState = 'encrypted' | 'unencrypted'; +export type BindingMethod = 'jws'; + +// Statement type +export type Statement = { + format: string; + schema: string; + value: string; +}; + +// Binding type +export type Binding = { + method: string; + signature: string; +}; + +// Assertion type +export type Assertion = { + id: string; + type: AssertionType; + scope: Scope; + appliesToState?: AppliesToState; + statement: Statement; + binding: Binding; +}; + +export type AssertionPayload = { + assertionHash: string; + assertionSig: string; +}; + +/** + * Computes the SHA-256 hash of the assertion object, excluding the 'binding' and 'hash' properties. + * + * @returns the hexadecimal string representation of the hash + */ +export async function hash(a: Assertion): Promise { + const result = canonicalizeEx(a, { exclude: ['binding', 'hash', 'sign', 'verify'] }); + + const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(result)); + return hex.encodeArrayBuffer(hash); +} + +/** + * Signs the given hash and signature using the provided key and sets the binding method and signature. + * + * @param hash - The hash to be signed. + * @param sig - The signature to be signed. + * @param {AssertionKey} key - The key used for signing. + * @returns {Promise} A promise that resolves when the signing is complete. + */ +async function sign( + thiz: Assertion, + assertionHash: string, + sig: string, + key: AssertionKey +): Promise { + const payload: AssertionPayload = { + assertionHash, + assertionSig: sig, + }; + + let token: string; + try { + token = await new SignJWT(payload).setProtectedHeader({ alg: key.alg }).sign(key.key as any); + } catch (error) { + throw new ConfigurationError(`Signing assertion failed: ${error.message}`, error); + } + thiz.binding.method = 'jws'; + thiz.binding.signature = token; + return thiz; +} + +// a function that takes an unknown or any object and asserts that it is or is not an AssertionConfig object +export function isAssertionConfig(obj: unknown): obj is AssertionConfig { + return ( + !!obj && + typeof obj === 'object' && + 'id' in obj && + typeof obj.id === 'string' && + 'type' in obj && + (obj.type === 'handling' || obj.type === 'other') && + 'scope' in obj && + (obj.scope === 'tdo' || obj.scope === 'payload') && + 'appliesToState' in obj && + (obj.appliesToState === 'encrypted' || obj.appliesToState === 'unencrypted') && + 'statement' in obj && + !!obj.statement && + typeof obj.statement === 'object' && + 'format' in obj.statement && + 'schema' in obj.statement && + 'value' in obj.statement + ); +} + +/** + * Verifies the signature of the assertion using the provided key. + * + * @param {AssertionKey} key - The key used for verification. + * @returns {Promise<[string, string]>} A promise that resolves to a tuple containing the assertion hash and signature. + * @throws {Error} If the verification fails. + */ +export async function verify( + thiz: Assertion, + aggregateHash: string, + key: AssertionKey +): Promise { + let payload: AssertionPayload; + try { + const uj = await jwtVerify(thiz.binding.signature, key.key as any, { + algorithms: [key.alg], + }); + payload = uj.payload as AssertionPayload; + } catch (error) { + throw new InvalidFileError(`Verifying assertion failed: ${error.message}`, error); + } + const { assertionHash, assertionSig } = payload; + + // Get the hash of the assertion + const hashOfAssertion = await hash(thiz); + const combinedHash = aggregateHash + hashOfAssertion; + const encodedHash = base64.encode(combinedHash); + + // check if assertionHash is same as hashOfAssertion + if (hashOfAssertion !== assertionHash) { + throw new IntegrityError('Assertion hash mismatch'); + } + + // check if assertionSig is same as encodedHash + if (assertionSig !== encodedHash) { + throw new IntegrityError('Failed integrity check on assertion signature'); + } +} + +/** + * Creates an Assertion object with the specified properties. + */ +export async function CreateAssertion( + aggregateHash: string, + assertionConfig: AssertionConfig +): Promise { + if (!assertionConfig.signingKey) { + throw new ConfigurationError('Assertion signing key is required'); + } + + const a: Assertion = { + id: assertionConfig.id, + type: assertionConfig.type, + scope: assertionConfig.scope, + appliesToState: assertionConfig.appliesToState, + statement: assertionConfig.statement, + // empty binding + binding: { method: '', signature: '' }, + }; + + const assertionHash = await hash(a); + const combinedHash = aggregateHash + assertionHash; + const encodedHash = base64.encode(combinedHash); + + return await sign(a, assertionHash, encodedHash, assertionConfig.signingKey); +} + +export type AssertionKey = { + alg: AssertionKeyAlg; + key: unknown; // Replace AnyKey with the actual type of your key +}; + +// AssertionConfig is a shadow of Assertion with the addition of the signing key. +// It is used on creation of the assertion. +export type AssertionConfig = { + id: string; + type: AssertionType; + scope: Scope; + appliesToState: AppliesToState; + statement: Statement; + signingKey?: AssertionKey; +}; + +// AssertionVerificationKeys represents the verification keys for assertions. +export type AssertionVerificationKeys = { + DefaultKey?: AssertionKey; + Keys: Record; +}; diff --git a/lib/tdf3/src/client/AssertionConfig.ts b/lib/tdf3/src/client/AssertionConfig.ts deleted file mode 100644 index cd065aa8..00000000 --- a/lib/tdf3/src/client/AssertionConfig.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - AssertionKeyAlg, - AssertionType, - Scope, - AppliesToState, - Statement, -} from '../models/assertion.js'; - -export type AssertionKey = { - alg: AssertionKeyAlg; - key: any; // Replace AnyKey with the actual type of your key -}; - -// AssertionConfig is a shadow of Assertion with the addition of the signing key. -// It is used on creation of the assertion. -export type AssertionConfig = { - id: string; - type: AssertionType; - scope: Scope; - appliesToState: AppliesToState; - statement: Statement; - signingKey?: AssertionKey; -}; - -// AssertionVerificationKeys represents the verification keys for assertions. -export type AssertionVerificationKeys = { - DefaultKey?: AssertionKey; - Keys: Record; -}; diff --git a/lib/tdf3/src/client/builders.ts b/lib/tdf3/src/client/builders.ts index 5eb382ce..3cf9f9aa 100644 --- a/lib/tdf3/src/client/builders.ts +++ b/lib/tdf3/src/client/builders.ts @@ -8,7 +8,7 @@ import { PemKeyPair } from '../crypto/declarations.js'; import { EntityObject } from '../../../src/tdf/EntityObject.js'; import { DecoratedReadableStream } from './DecoratedReadableStream.js'; import { type Chunker } from '../utils/chunkers.js'; -import { AssertionConfig, AssertionVerificationKeys } from './AssertionConfig.js'; +import { AssertionConfig, AssertionVerificationKeys } from '../assertions.js'; import { Value } from '../../../src/policy/attributes.js'; export const DEFAULT_SEGMENT_SIZE: number = 1024 * 1024; diff --git a/lib/tdf3/src/models/assertion.ts b/lib/tdf3/src/models/assertion.ts deleted file mode 100644 index ea19f3b4..00000000 --- a/lib/tdf3/src/models/assertion.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { canonicalizeEx } from 'json-canonicalize'; -import { SignJWT, jwtVerify } from 'jose'; -import { AssertionKey } from './../client/AssertionConfig.js'; -import { hex } from '../../../src/encodings/index.js'; -import { ConfigurationError, InvalidFileError } from '../../../src/errors.js'; - -export type AssertionKeyAlg = 'RS256' | 'HS256'; -export type AssertionType = 'handling' | 'other'; -export type Scope = 'tdo' | 'payload'; -export type AppliesToState = 'encrypted' | 'unencrypted'; -export type BindingMethod = 'jws'; - -const kAssertionHash = 'assertionHash'; -const kAssertionSignature = 'assertionSig'; - -// Statement type -export type Statement = { - format: string; - schema: string; - value: string; -}; - -// Binding type -export type Binding = { - method: string; - signature: string; -}; - -// Assertion type -export type Assertion = { - id: string; - type: AssertionType; - scope: Scope; - appliesToState?: AppliesToState; - statement: Statement; - binding: Binding; - hash: () => Promise; - sign: (hash: string, sig: string, key: AssertionKey) => Promise; - verify: (key: AssertionKey) => Promise<[string, string]>; -}; - -/** - * Computes the SHA-256 hash of the assertion object, excluding the 'binding' and 'hash' properties. - * - * @returns {Promise} A promise that resolves to the hexadecimal string representation of the hash. - */ -export async function hash(this: Assertion): Promise { - const result = canonicalizeEx(this, { exclude: ['binding', 'hash', 'sign', 'verify'] }); - - const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(result)); - return hex.encodeArrayBuffer(hash); -} - -/** - * Signs the given hash and signature using the provided key and sets the binding method and signature. - * - * @param {string} hash - The hash to be signed. - * @param {string} sig - The signature to be signed. - * @param {AssertionKey} key - The key used for signing. - * @returns {Promise} A promise that resolves when the signing is complete. - */ -export async function sign( - this: Assertion, - assertionHash: string, - sig: string, - key: AssertionKey -): Promise { - const payload: Record = {}; - payload[kAssertionHash] = assertionHash; - payload[kAssertionSignature] = sig; - - let token: string; - try { - token = await new SignJWT(payload).setProtectedHeader({ alg: key.alg }).sign(key.key); - } catch (error) { - throw new ConfigurationError(`Signing assertion failed: ${error.message}`, error); - } - this.binding.method = 'jws'; - this.binding.signature = token; -} - -/** - * Verifies the signature of the assertion using the provided key. - * - * @param {AssertionKey} key - The key used for verification. - * @returns {Promise<[string, string]>} A promise that resolves to a tuple containing the assertion hash and signature. - * @throws {Error} If the verification fails. - */ -export async function verify(this: Assertion, key: AssertionKey): Promise<[string, string]> { - try { - const { payload } = await jwtVerify(this.binding.signature, key.key, { - algorithms: [key.alg], - }); - - return [payload[kAssertionHash] as string, payload[kAssertionSignature] as string]; - } catch (error) { - throw new InvalidFileError(`Verifying assertion failed: ${error.message}`, error); - } -} - -/** - * Creates an Assertion object with the specified properties. - * - * @param {string} id - The unique identifier for the assertion. - * @param {AssertionType} type - The type of the assertion (e.g., 'handling', 'other'). - * @param {Scope} scope - The scope of the assertion (e.g., 'tdo', 'payload'). - * @param {Statement} statement - The statement associated with the assertion. - * @param {Binding} binding - The binding method and signature for the assertion. - * @param {AppliesToState} [appliesToState] - The state to which the assertion applies (optional). - * @returns {Assertion} The created Assertion object. - */ -export function CreateAssertion( - id: string, - type: AssertionType, - scope: Scope, - statement: Statement, - appliesToState?: AppliesToState, - binding?: Binding -): Assertion { - return { - id, - type, - scope, - appliesToState, - statement, - binding: { method: binding?.method ?? '', signature: binding?.signature ?? '' }, - hash, - sign, - verify, - }; -} diff --git a/lib/tdf3/src/models/index.ts b/lib/tdf3/src/models/index.ts index 12ba37ef..b7a9d0c0 100644 --- a/lib/tdf3/src/models/index.ts +++ b/lib/tdf3/src/models/index.ts @@ -5,4 +5,4 @@ export * from './manifest.js'; export * from './payload.js'; export * from './policy.js'; export * from './upsert-response.js'; -export * from './assertion.js'; +export * from '../assertions.js'; diff --git a/lib/tdf3/src/models/manifest.ts b/lib/tdf3/src/models/manifest.ts index 51e86f81..4ed4ebdd 100644 --- a/lib/tdf3/src/models/manifest.ts +++ b/lib/tdf3/src/models/manifest.ts @@ -1,4 +1,4 @@ -import { type Assertion } from './assertion.js'; +import { type Assertion } from '../assertions.js'; import { type Payload } from './payload.js'; import { type EncryptionInformation } from './encryption-information.js'; diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 9d6ce933..8e118419 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -5,12 +5,8 @@ import { DecoratedReadableStream } from './client/DecoratedReadableStream.js'; import { EntityObject } from '../../src/tdf/EntityObject.js'; import { pemToCryptoPublicKey, validateSecureUrl } from '../../src/utils.js'; import { DecryptParams } from './client/builders.js'; -import { - AssertionConfig, - AssertionKey, - AssertionVerificationKeys, -} from './client/AssertionConfig.js'; -import { Assertion, CreateAssertion } from './models/assertion.js'; +import { AssertionConfig, AssertionKey, AssertionVerificationKeys } from './assertions.js'; +import * as assertions from './assertions.js'; import { AttributeSet, @@ -452,7 +448,7 @@ async function _generateManifest( }; const encryptionInformationStr = await encryptionInformation.write(policy, keyInfo); - const assertions: Assertion[] = []; + const assertions: assertions.Assertion[] = []; return { payload, // generate the manifest first, then insert integrity information into it @@ -741,31 +737,19 @@ export async function writeStream(cfg: EncryptConfiguration): Promise 0) { await Promise.all( cfg.assertionConfigs.map(async (assertionConfig) => { // Create assertion using the assertionConfig values - const assertion = CreateAssertion( - assertionConfig.id, - assertionConfig.type, - assertionConfig.scope, - assertionConfig.statement, - assertionConfig.appliesToState - ); - - const assertionHash = await assertion.hash(); - const combinedHash = aggregateHash + assertionHash; - const encodedHash = base64.encode(combinedHash); - - // Create assertion key using the signingKey from the config, or a default key - const assertionKey: AssertionKey = assertionConfig.signingKey ?? { + const signingKey: AssertionKey = assertionConfig.signingKey ?? { alg: 'HS256', key: new Uint8Array(cfg.keyForEncryption.unwrappedKeyBinary.asArrayBuffer()), }; - - // Sign the assertion - await assertion.sign(assertionHash, encodedHash, assertionKey); + const assertion = await assertions.CreateAssertion(aggregateHash, { + ...assertionConfig, + signingKey, + }); // Add signed assertion to the signedAssertions array signedAssertions.push(assertion); @@ -1226,8 +1210,7 @@ export async function readStream(cfg: DecryptConfiguration) { } // // Validate assertions - const assertions = manifest.assertions || []; - for (const assertion of assertions) { + for (const assertion of manifest.assertions || []) { // Create a default assertion key let assertionKey: AssertionKey = { alg: 'HS256', @@ -1240,33 +1223,7 @@ export async function readStream(cfg: DecryptConfiguration) { assertionKey = foundKey; } } - - // create assertion object from the assertion - const assertionObj = CreateAssertion( - assertion.id, - assertion.type, - assertion.scope, - assertion.statement, - assertion.appliesToState, - assertion.binding - ); - - const [assertionHash, assertionSig] = await assertionObj.verify(assertionKey); - - // Get the hash of the assertion - const hashOfAssertion = await assertionObj.hash(); - const combinedHash = aggregateHash + hashOfAssertion; - const encodedHash = base64.encode(combinedHash); - - // check if assertionHash is same as hashOfAssertion - if (hashOfAssertion !== assertionHash) { - throw new IntegrityError('Assertion hash mismatch'); - } - - // check if assertionSig is same as encodedHash - if (assertionSig !== encodedHash) { - throw new IntegrityError('Failed integrity check on assertion signature'); - } + await assertions.verify(assertion, aggregateHash, assertionKey); } let mapOfRequestsOffset = 0; diff --git a/lib/tests/mocha/encrypt-decrypt.spec.ts b/lib/tests/mocha/encrypt-decrypt.spec.ts index 532856e8..a2094aea 100644 --- a/lib/tests/mocha/encrypt-decrypt.spec.ts +++ b/lib/tests/mocha/encrypt-decrypt.spec.ts @@ -7,10 +7,7 @@ import { WebCryptoService } from '../../tdf3/index.js'; import { Client } from '../../tdf3/src/index.js'; import { SplitKey } from '../../tdf3/src/models/encryption-information.js'; import { AesGcmCipher } from '../../tdf3/src/ciphers/aes-gcm-cipher.js'; -import { - AssertionConfig, - AssertionVerificationKeys, -} from '../../tdf3/src/client/AssertionConfig.js'; +import { AssertionConfig, AssertionVerificationKeys } from '../../tdf3/src/assertions.js'; import { Scope } from '../../tdf3/src/client/builders.js'; const Mocks = getMocks(); diff --git a/lib/tests/mocha/unit/assertions.spec.ts b/lib/tests/mocha/unit/assertions.spec.ts new file mode 100644 index 00000000..c3e3491d --- /dev/null +++ b/lib/tests/mocha/unit/assertions.spec.ts @@ -0,0 +1,25 @@ +// tests for assertions.ts + +import { expect } from 'chai'; + +import * as assertions from '../../../tdf3/src/assertions.js'; + +describe('assertions', () => { + describe('isAssertionConfig', () => { + it('validates config', () => { + expect( + assertions.isAssertionConfig({ + id: 'assertion1', + type: 'handling', + scope: 'tdo', + appliesToState: 'unencrypted', + statement: { + format: 'base64binary', + schema: 'text', + value: 'ICAgIDxlZGoOkVkaD4=', + }, + }) + ).to.be.true; + }); + }); +}); diff --git a/remote-store/package-lock.json b/remote-store/package-lock.json index c5540aed..80b7c94e 100644 --- a/remote-store/package-lock.json +++ b/remote-store/package-lock.json @@ -14,6 +14,7 @@ "@aws-sdk/middleware-endpoint": "^3.370.0", "@aws-sdk/protocol-http": "^3.370.0", "@aws-sdk/smithy-client": "^3.370.0", + "@opentdf/client": "file:../lib/opentdf-client-2.1.0.tgz", "axios": "^1.6.1" }, "devDependencies": { @@ -1534,6 +1535,18 @@ "node": ">=16.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1805,6 +1818,25 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@opentdf/client": { + "version": "2.1.0", + "resolved": "file:../lib/opentdf-client-2.1.0.tgz", + "integrity": "sha512-uE8lpCTtSsoLZtrVh5NARUO9vMhAbzKgw7r4O4eyKoJvbi8La7p0IfuiBiLWrvdDZgARruHAbTYNC8Vi1a/80Q==", + "license": "BSD-3-Clause-Clear", + "dependencies": { + "axios": "^1.6.1", + "axios-retry": "^3.9.0", + "base64-js": "^1.5.1", + "browser-fs-access": "^0.34.1", + "buffer-crc32": "^0.2.13", + "dpop": "^1.2.0", + "eventemitter3": "^5.0.1", + "jose": "^4.14.4", + "json-canonicalize": "^1.0.6", + "streamsaver": "^2.0.6", + "uuid": "~9.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3266,12 +3298,42 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axios-retry": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", + "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.15.4", + "is-retry-allowed": "^2.2.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3308,12 +3370,27 @@ "node": ">=8" } }, + "node_modules/browser-fs-access": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz", + "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==", + "license": "Apache-2.0" + }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3539,6 +3616,15 @@ "node": ">=6.0.0" } }, + "node_modules/dpop": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", + "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3759,6 +3845,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4248,6 +4340,18 @@ "node": ">=8" } }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -4284,6 +4388,15 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -4296,6 +4409,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-canonicalize": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/json-canonicalize/-/json-canonicalize-1.0.6.tgz", + "integrity": "sha512-kP2iYpOS5SZHYhIaR1t9oG80d4uTY3jPoaBj+nimy3njtJk8+sRsVatN8pyJRDRtk9Su3+6XqA2U8k0dByJBUQ==", + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", @@ -5036,6 +5155,12 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5269,6 +5394,18 @@ "spdx-ranges": "^2.0.0" } }, + "node_modules/streamsaver": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-2.0.6.tgz", + "integrity": "sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + } + ], + "license": "MIT" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/remote-store/package.json b/remote-store/package.json index 69ce4785..443edeec 100644 --- a/remote-store/package.json +++ b/remote-store/package.json @@ -41,6 +41,7 @@ "@aws-sdk/middleware-endpoint": "^3.370.0", "@aws-sdk/protocol-http": "^3.370.0", "@aws-sdk/smithy-client": "^3.370.0", + "@opentdf/client": "file:../lib/opentdf-client-2.1.0.tgz", "axios": "^1.6.1" }, "devDependencies": { diff --git a/web-app/package-lock.json b/web-app/package-lock.json index ad42f581..bd4a71b9 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -607,7 +607,7 @@ "node_modules/@opentdf/client": { "version": "2.1.0", "resolved": "file:../lib/opentdf-client-2.1.0.tgz", - "integrity": "sha512-1KZVuJLh+MBXPwAx/RiDw0clFvbFqLDaqfi4T5rMgKlFJ4rNX5immyQcZnu7YS8degBed/4CHONgvFya33NGFA==", + "integrity": "sha512-uE8lpCTtSsoLZtrVh5NARUO9vMhAbzKgw7r4O4eyKoJvbi8La7p0IfuiBiLWrvdDZgARruHAbTYNC8Vi1a/80Q==", "license": "BSD-3-Clause-Clear", "dependencies": { "axios": "^1.6.1", @@ -4119,7 +4119,7 @@ }, "@opentdf/client": { "version": "file:../lib/opentdf-client-2.1.0.tgz", - "integrity": "sha512-1KZVuJLh+MBXPwAx/RiDw0clFvbFqLDaqfi4T5rMgKlFJ4rNX5immyQcZnu7YS8degBed/4CHONgvFya33NGFA==", + "integrity": "sha512-uE8lpCTtSsoLZtrVh5NARUO9vMhAbzKgw7r4O4eyKoJvbi8La7p0IfuiBiLWrvdDZgARruHAbTYNC8Vi1a/80Q==", "requires": { "axios": "^1.6.1", "axios-retry": "^3.9.0", From 67d6973d920c5090ce8bd984260ed5be173319cd Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Mon, 28 Oct 2024 12:19:01 -0400 Subject: [PATCH 3/4] Update assertions.ts --- lib/tdf3/src/assertions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/tdf3/src/assertions.ts b/lib/tdf3/src/assertions.ts index 53bd82cf..be3aa833 100644 --- a/lib/tdf3/src/assertions.ts +++ b/lib/tdf3/src/assertions.ts @@ -1,5 +1,5 @@ import { canonicalizeEx } from 'json-canonicalize'; -import { SignJWT, jwtVerify } from 'jose'; +import { type KeyLike, SignJWT, jwtVerify } from 'jose'; import { base64, hex } from '../../src/encodings/index.js'; import { ConfigurationError, IntegrityError, InvalidFileError } from '../../src/errors.js'; @@ -70,7 +70,7 @@ async function sign( let token: string; try { - token = await new SignJWT(payload).setProtectedHeader({ alg: key.alg }).sign(key.key as any); + token = await new SignJWT(payload).setProtectedHeader({ alg: key.alg }).sign(key.key); } catch (error) { throw new ConfigurationError(`Signing assertion failed: ${error.message}`, error); } @@ -115,7 +115,7 @@ export async function verify( ): Promise { let payload: AssertionPayload; try { - const uj = await jwtVerify(thiz.binding.signature, key.key as any, { + const uj = await jwtVerify(thiz.binding.signature, key.key, { algorithms: [key.alg], }); payload = uj.payload as AssertionPayload; @@ -170,7 +170,7 @@ export async function CreateAssertion( export type AssertionKey = { alg: AssertionKeyAlg; - key: unknown; // Replace AnyKey with the actual type of your key + key: KeyLike | Uint8Array; }; // AssertionConfig is a shadow of Assertion with the addition of the signing key. From 2b94bfd4000a04302909801c8d8bd7dcddc89657 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Mon, 28 Oct 2024 14:54:10 -0400 Subject: [PATCH 4/4] feat(sdk): Allows skipping assertion verification --- cli/src/cli.ts | 9 +++++++++ lib/tdf3/src/client/builders.ts | 7 +++++++ lib/tdf3/src/client/index.ts | 2 ++ lib/tdf3/src/tdf.ts | 26 ++++++++++++++------------ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/cli/src/cli.ts b/cli/src/cli.ts index 56d8581c..f5896835 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -116,6 +116,9 @@ function addParams(client: AnyNanoClient, argv: Partial) { async function tdf3DecryptParamsFor(argv: Partial): Promise { const c = new DecryptParamsBuilder(); + if (argv.noVerifyAssertions) { + c.withNoVerifyAssertions(true); + } c.setFileSource(await openAsBlob(argv.file as string)); return c.build(); } @@ -229,6 +232,12 @@ export const handleArgs = (args: string[]) => { desc: 'disable KAS allowlist feature for decrypt', type: 'boolean', }) + .option('noVerifyAssertions', { + alias: 'no-verify-assertions', + group: 'Security', + desc: 'Do not verify assertions', + type: 'boolean', + }) .option('auth', { group: 'OAuth and OIDC:', type: 'string', diff --git a/lib/tdf3/src/client/builders.ts b/lib/tdf3/src/client/builders.ts index 3cf9f9aa..a8e96d7c 100644 --- a/lib/tdf3/src/client/builders.ts +++ b/lib/tdf3/src/client/builders.ts @@ -519,6 +519,7 @@ export type DecryptParams = { keyMiddleware?: DecryptKeyMiddleware; streamMiddleware?: DecryptStreamMiddleware; assertionVerificationKeys?: AssertionVerificationKeys; + noVerifyAssertions?: boolean; }; /** @@ -674,6 +675,12 @@ class DecryptParamsBuilder { return this; } + /** Skip assertion verification */ + withNoVerifyAssertions(v: boolean): DecryptParamsBuilder { + this._params.noVerifyAssertions = v; + return this; + } + _deepCopy(_params: DecryptParams) { return freeze({ ..._params }); } diff --git a/lib/tdf3/src/client/index.ts b/lib/tdf3/src/client/index.ts index 1b292c5d..658fe04c 100644 --- a/lib/tdf3/src/client/index.ts +++ b/lib/tdf3/src/client/index.ts @@ -561,6 +561,7 @@ export class Client { keyMiddleware = async (key: Binary) => key, streamMiddleware = async (stream: DecoratedReadableStream) => stream, assertionVerificationKeys, + noVerifyAssertions, }: DecryptParams): Promise { const dpopKeys = await this.dpopKeys; let entityObject; @@ -593,6 +594,7 @@ export class Client { keyMiddleware, progressHandler: this.clientConfig.progressHandler, assertionVerificationKeys, + noVerifyAssertions, }) ); } diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 8e118419..d615e8f9 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -162,6 +162,7 @@ export type DecryptConfiguration = { progressHandler?: (bytesProcessed: number) => void; fileStreamServiceWorker?: string; assertionVerificationKeys?: AssertionVerificationKeys; + noVerifyAssertions?: boolean; }; export type UpsertConfiguration = { @@ -1209,21 +1210,22 @@ export async function readStream(cfg: DecryptConfiguration) { throw new IntegrityError('Failed integrity check on root signature'); } - // // Validate assertions - for (const assertion of manifest.assertions || []) { - // Create a default assertion key - let assertionKey: AssertionKey = { - alg: 'HS256', - key: new Uint8Array(reconstructedKeyBinary.asArrayBuffer()), - }; + if (!cfg.noVerifyAssertions) { + for (const assertion of manifest.assertions || []) { + // Create a default assertion key + let assertionKey: AssertionKey = { + alg: 'HS256', + key: new Uint8Array(reconstructedKeyBinary.asArrayBuffer()), + }; - if (cfg.assertionVerificationKeys) { - const foundKey = cfg.assertionVerificationKeys.Keys[assertion.id]; - if (foundKey) { - assertionKey = foundKey; + if (cfg.assertionVerificationKeys) { + const foundKey = cfg.assertionVerificationKeys.Keys[assertion.id]; + if (foundKey) { + assertionKey = foundKey; + } } + await assertions.verify(assertion, aggregateHash, assertionKey); } - await assertions.verify(assertion, aggregateHash, assertionKey); } let mapOfRequestsOffset = 0;