From b2fce36bf0635698591c8f7fd444bbd0829b220a Mon Sep 17 00:00:00 2001 From: sujan kota Date: Thu, 5 Dec 2024 19:27:51 -0500 Subject: [PATCH 1/6] feat(sdk): remove hex encoding for segment hash --- lib/tdf3/src/models/manifest.ts | 1 + lib/tdf3/src/tdf.ts | 78 ++++++++++++++++++++++++--------- lib/tdf3/src/version.ts | 2 +- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/lib/tdf3/src/models/manifest.ts b/lib/tdf3/src/models/manifest.ts index 4ed4ebdd..3b1bb1ff 100644 --- a/lib/tdf3/src/models/manifest.ts +++ b/lib/tdf3/src/models/manifest.ts @@ -6,4 +6,5 @@ export type Manifest = { payload: Payload; encryptionInformation: EncryptionInformation; assertions: Assertion[]; + tdf_spec_version : string; }; diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 56f0c250..28072822 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -6,6 +6,8 @@ import { EntityObject } from '../../src/tdf/index.js'; import { pemToCryptoPublicKey, validateSecureUrl } from '../../src/utils.js'; import { DecryptParams } from './client/builders.js'; import { AssertionConfig, AssertionKey, AssertionVerificationKeys } from './assertions.js'; +import { version } from './version.js'; +import { hex } from '../../src/encodings/index.js'; import * as assertions from './assertions.js'; import { @@ -457,6 +459,7 @@ async function _generateManifest( // generate the manifest first, then insert integrity information into it encryptionInformation: encryptionInformationStr, assertions: assertions, + tdf_spec_version: version, }; } @@ -616,7 +619,7 @@ export async function writeStream(cfg: EncryptConfiguration): Promise { if (segmentIntegrityAlgorithm !== 'GMAC' && segmentIntegrityAlgorithm !== 'HS256') { } - const segmentHashStr = await getSignature( + const segmentHashAsHex = await getSignature( reconstructedKeyBinary, Binary.fromArrayBuffer(encryptedChunk.buffer), segmentIntegrityAlgorithm, cryptoService ); - if (hash !== btoa(segmentHashStr)) { + + const segmentHash = isLegacyTDF ? btoa(segmentHashAsHex) : + btoa(String.fromCharCode(...new Uint8Array(hex.decodeArrayBuffer(segmentHashAsHex)))); + + if (hash !== segmentHash) { throw new IntegrityError('Failed integrity check on segment hash'); } return await cipher.decrypt(encryptedChunk, reconstructedKeyBinary); @@ -1091,7 +1103,8 @@ async function updateChunkQueue( reconstructedKeyBinary: Binary, cipher: SymmetricCipher, segmentIntegrityAlgorithm: IntegrityAlgorithm, - cryptoService: CryptoService + cryptoService: CryptoService, + isLegacyTDF: boolean, ) { const chunksInOneDownload = 500; let requests = []; @@ -1132,6 +1145,7 @@ async function updateChunkQueue( slice, cipher, segmentIntegrityAlgorithm, + isLegacyTDF }); } })() @@ -1146,6 +1160,7 @@ export async function sliceAndDecrypt({ cipher, cryptoService, segmentIntegrityAlgorithm, + isLegacyTDF }: { buffer: Uint8Array; reconstructedKeyBinary: Binary; @@ -1153,6 +1168,7 @@ export async function sliceAndDecrypt({ cipher: SymmetricCipher; cryptoService: CryptoService; segmentIntegrityAlgorithm: IntegrityAlgorithm; + isLegacyTDF: boolean; }) { for (const index in slice) { const { encryptedOffset, encryptedSegmentSize, _resolve, _reject } = slice[index]; @@ -1170,7 +1186,8 @@ export async function sliceAndDecrypt({ slice[index]['hash'], cipher, segmentIntegrityAlgorithm, - cryptoService + cryptoService, + isLegacyTDF, ); slice[index].decryptedChunk = result; if (_resolve) { @@ -1218,22 +1235,31 @@ export async function readStream(cfg: DecryptConfiguration) { const keyForDecryption = await cfg.keyMiddleware(reconstructedKeyBinary); const encryptedSegmentSizeDefault = defaultSegmentSize || DEFAULT_SEGMENT_SIZE; + // check if the TDF is a legacy TDF + const isLegacyTDF = manifest.tdf_spec_version ? false : true; + // check the combined string of hashes const aggregateHash = segments.map(({ hash }) => base64.decode(hash)).join(''); const integrityAlgorithm = rootSignature.alg; if (integrityAlgorithm !== 'GMAC' && integrityAlgorithm !== 'HS256') { throw new UnsupportedError(`Unsupported integrity alg [${integrityAlgorithm}]`); } - const payloadSigStr = await getSignature( + + const payloadForSigCalculation = isLegacyTDF ? Binary.fromString(hex.encode(aggregateHash)) + : Binary.fromString(aggregateHash); + const payloadSigInHex = await getSignature( keyForDecryption, - Binary.fromString(aggregateHash), + payloadForSigCalculation, integrityAlgorithm, - cfg.cryptoService + cfg.cryptoService, ); + const rootSig = isLegacyTDF ? base64.encode(payloadSigInHex) + : base64.encodeArrayBuffer(hex.decodeArrayBuffer(payloadSigInHex)); + if ( manifest.encryptionInformation.integrityInformation.rootSignature.sig !== - base64.encode(payloadSigStr) + rootSig ) { throw new IntegrityError('Failed integrity check on root signature'); } @@ -1293,7 +1319,8 @@ export async function readStream(cfg: DecryptConfiguration) { keyForDecryption, cipher, segmentIntegrityAlg, - cfg.cryptoService + cfg.cryptoService, + isLegacyTDF, ); let progress = 0; @@ -1328,3 +1355,14 @@ export async function readStream(cfg: DecryptConfiguration) { outputStream.emit('rewrap', metadata); return outputStream; } + +async function concatenateUint8Array(uint8arrays: Uint8Array[]): Promise { + // Put the inputs into a Blob. + const blob = new Blob(uint8arrays); + + // Pull an ArrayBuffer out. (Has to be async.) + const buffer = await blob.arrayBuffer(); + + // Convert that ArrayBuffer to a Uint8Array. + return new Uint8Array(buffer); +} \ No newline at end of file diff --git a/lib/tdf3/src/version.ts b/lib/tdf3/src/version.ts index fb0106b0..ac266508 100644 --- a/lib/tdf3/src/version.ts +++ b/lib/tdf3/src/version.ts @@ -1,2 +1,2 @@ -export const version = '0.1.0'; +export const version = '1.0.0'; export const clientType = 'tdf3-js-client'; From 052af554caf12338ea1a37508713cd38a7744b38 Mon Sep 17 00:00:00 2001 From: sujankota Date: Fri, 6 Dec 2024 00:29:28 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=A4=96=20=F0=9F=8E=A8=20Autoformat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tdf3/src/models/manifest.ts | 2 +- lib/tdf3/src/tdf.ts | 36 ++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/tdf3/src/models/manifest.ts b/lib/tdf3/src/models/manifest.ts index 3b1bb1ff..4dffa8b8 100644 --- a/lib/tdf3/src/models/manifest.ts +++ b/lib/tdf3/src/models/manifest.ts @@ -6,5 +6,5 @@ export type Manifest = { payload: Payload; encryptionInformation: EncryptionInformation; assertions: Assertion[]; - tdf_spec_version : string; + tdf_spec_version: string; }; diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 28072822..6ffccea3 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -619,7 +619,7 @@ export async function writeStream(cfg: EncryptConfiguration): Promise Date: Thu, 5 Dec 2024 19:30:15 -0500 Subject: [PATCH 3/6] cleanup --- lib/tdf3/src/tdf.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 6ffccea3..c56512d2 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -1357,12 +1357,7 @@ export async function readStream(cfg: DecryptConfiguration) { } async function concatenateUint8Array(uint8arrays: Uint8Array[]): Promise { - // Put the inputs into a Blob. const blob = new Blob(uint8arrays); - - // Pull an ArrayBuffer out. (Has to be async.) const buffer = await blob.arrayBuffer(); - - // Convert that ArrayBuffer to a Uint8Array. return new Uint8Array(buffer); } From b7dcd8a9732c63d0214482162a08a74453294641 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Fri, 6 Dec 2024 11:27:25 -0500 Subject: [PATCH 4/6] Update tdf.ts --- lib/tdf3/src/tdf.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index c56512d2..f69ab4f9 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -619,7 +619,7 @@ export async function writeStream(cfg: EncryptConfiguration): Promise Date: Fri, 6 Dec 2024 13:32:19 -0500 Subject: [PATCH 5/6] fix assertion hash --- lib/tdf3/src/assertions.ts | 34 +++++++++++++++++++++++++++------- lib/tdf3/src/tdf.ts | 17 ++++++++++------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/lib/tdf3/src/assertions.ts b/lib/tdf3/src/assertions.ts index be3aa833..728d03bc 100644 --- a/lib/tdf3/src/assertions.ts +++ b/lib/tdf3/src/assertions.ts @@ -110,8 +110,9 @@ export function isAssertionConfig(obj: unknown): obj is AssertionConfig { */ export async function verify( thiz: Assertion, - aggregateHash: string, - key: AssertionKey + aggregateHash: Uint8Array, + key: AssertionKey, + isLegacyTDF: boolean ): Promise { let payload: AssertionPayload; try { @@ -126,14 +127,23 @@ export async function verify( // 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'); } + let encodedHash: string; + if (isLegacyTDF) { + const aggregateHashAsStr = new TextDecoder('utf-8').decode(aggregateHash); + const combinedHash = aggregateHashAsStr + hashOfAssertion; + encodedHash = base64.encode(combinedHash); + } else { + const combinedHash = concatenateUint8Arrays(aggregateHash, + new Uint8Array(hex.decodeArrayBuffer(assertionHash))); + encodedHash = base64.encodeArrayBuffer(combinedHash); + } + // check if assertionSig is same as encodedHash if (assertionSig !== encodedHash) { throw new IntegrityError('Failed integrity check on assertion signature'); @@ -144,7 +154,7 @@ export async function verify( * Creates an Assertion object with the specified properties. */ export async function CreateAssertion( - aggregateHash: string, + aggregateHash: Uint8Array, assertionConfig: AssertionConfig ): Promise { if (!assertionConfig.signingKey) { @@ -162,8 +172,8 @@ export async function CreateAssertion( }; const assertionHash = await hash(a); - const combinedHash = aggregateHash + assertionHash; - const encodedHash = base64.encode(combinedHash); + const combinedHash = concatenateUint8Arrays(aggregateHash, new Uint8Array(hex.decodeArrayBuffer(assertionHash))); + const encodedHash = base64.encodeArrayBuffer(combinedHash); return await sign(a, assertionHash, encodedHash, assertionConfig.signingKey); } @@ -189,3 +199,13 @@ export type AssertionVerificationKeys = { DefaultKey?: AssertionKey; Keys: Record; }; + +function concatenateUint8Arrays(array1: Uint8Array, array2: Uint8Array): Uint8Array { + const combinedLength = array1.length + array2.length; + const combinedArray = new Uint8Array(combinedLength); + + combinedArray.set(array1, 0); + combinedArray.set(array2, array1.length); + + return combinedArray; +} \ No newline at end of file diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index f69ab4f9..040eab3e 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -755,8 +755,7 @@ export async function writeStream(cfg: EncryptConfiguration): Promise base64.decode(hash)).join(''); + // Decode each hash and store it in an array of Uint8Array + const segmentHashList = segments.map(({ hash }) => new Uint8Array(base64.decodeArrayBuffer(hash))); + + // Concatenate all segment hashes into a single Uint8Array + const aggregateHash = await concatenateUint8Array(segmentHashList); + const integrityAlgorithm = rootSignature.alg; if (integrityAlgorithm !== 'GMAC' && integrityAlgorithm !== 'HS256') { throw new UnsupportedError(`Unsupported integrity alg [${integrityAlgorithm}]`); } const payloadForSigCalculation = isLegacyTDF - ? Binary.fromString(hex.encode(aggregateHash)) - : Binary.fromString(aggregateHash); + ? Binary.fromString(hex.encodeArrayBuffer(aggregateHash)) + : Binary.fromArrayBuffer(aggregateHash.buffer); const payloadSigInHex = await getSignature( keyForDecryption, payloadForSigCalculation, @@ -1278,7 +1281,7 @@ export async function readStream(cfg: DecryptConfiguration) { assertionKey = foundKey; } } - await assertions.verify(assertion, aggregateHash, assertionKey); + await assertions.verify(assertion, aggregateHash, assertionKey, isLegacyTDF); } } From 6c5f37d196294034861cb0890172419252128bf1 Mon Sep 17 00:00:00 2001 From: sujankota Date: Fri, 6 Dec 2024 18:33:28 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A4=96=20=F0=9F=8E=A8=20Autoformat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tdf3/src/assertions.ts | 13 +++++++++---- lib/tdf3/src/tdf.ts | 8 +++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/tdf3/src/assertions.ts b/lib/tdf3/src/assertions.ts index 728d03bc..0f14ef09 100644 --- a/lib/tdf3/src/assertions.ts +++ b/lib/tdf3/src/assertions.ts @@ -139,8 +139,10 @@ export async function verify( const combinedHash = aggregateHashAsStr + hashOfAssertion; encodedHash = base64.encode(combinedHash); } else { - const combinedHash = concatenateUint8Arrays(aggregateHash, - new Uint8Array(hex.decodeArrayBuffer(assertionHash))); + const combinedHash = concatenateUint8Arrays( + aggregateHash, + new Uint8Array(hex.decodeArrayBuffer(assertionHash)) + ); encodedHash = base64.encodeArrayBuffer(combinedHash); } @@ -172,7 +174,10 @@ export async function CreateAssertion( }; const assertionHash = await hash(a); - const combinedHash = concatenateUint8Arrays(aggregateHash, new Uint8Array(hex.decodeArrayBuffer(assertionHash))); + const combinedHash = concatenateUint8Arrays( + aggregateHash, + new Uint8Array(hex.decodeArrayBuffer(assertionHash)) + ); const encodedHash = base64.encodeArrayBuffer(combinedHash); return await sign(a, assertionHash, encodedHash, assertionConfig.signingKey); @@ -208,4 +213,4 @@ function concatenateUint8Arrays(array1: Uint8Array, array2: Uint8Array): Uint8Ar combinedArray.set(array2, array1.length); return combinedArray; -} \ No newline at end of file +} diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 040eab3e..ed48541f 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -1239,11 +1239,13 @@ export async function readStream(cfg: DecryptConfiguration) { const isLegacyTDF = manifest.tdf_spec_version ? false : true; // Decode each hash and store it in an array of Uint8Array - const segmentHashList = segments.map(({ hash }) => new Uint8Array(base64.decodeArrayBuffer(hash))); + const segmentHashList = segments.map( + ({ hash }) => new Uint8Array(base64.decodeArrayBuffer(hash)) + ); // Concatenate all segment hashes into a single Uint8Array const aggregateHash = await concatenateUint8Array(segmentHashList); - + const integrityAlgorithm = rootSignature.alg; if (integrityAlgorithm !== 'GMAC' && integrityAlgorithm !== 'HS256') { throw new UnsupportedError(`Unsupported integrity alg [${integrityAlgorithm}]`); @@ -1251,7 +1253,7 @@ export async function readStream(cfg: DecryptConfiguration) { const payloadForSigCalculation = isLegacyTDF ? Binary.fromString(hex.encodeArrayBuffer(aggregateHash)) - : Binary.fromArrayBuffer(aggregateHash.buffer); + : Binary.fromArrayBuffer(aggregateHash.buffer); const payloadSigInHex = await getSignature( keyForDecryption, payloadForSigCalculation,