diff --git a/lib/tdf3/src/assertions.ts b/lib/tdf3/src/assertions.ts index be3aa833..0f14ef09 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,25 @@ 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 +156,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 +174,11 @@ 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 +204,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; +} diff --git a/lib/tdf3/src/models/manifest.ts b/lib/tdf3/src/models/manifest.ts index 4ed4ebdd..4dffa8b8 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..ed48541f 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,23 +1235,37 @@ export async function readStream(cfg: DecryptConfiguration) { const keyForDecryption = await cfg.keyMiddleware(reconstructedKeyBinary); const encryptedSegmentSizeDefault = defaultSegmentSize || DEFAULT_SEGMENT_SIZE; - // check the combined string of hashes - const aggregateHash = segments.map(({ hash }) => base64.decode(hash)).join(''); + // check if the TDF is a legacy TDF + 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)) + ); + + // 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 payloadSigStr = await getSignature( + + const payloadForSigCalculation = isLegacyTDF + ? Binary.fromString(hex.encodeArrayBuffer(aggregateHash)) + : Binary.fromArrayBuffer(aggregateHash.buffer); + const payloadSigInHex = await getSignature( keyForDecryption, - Binary.fromString(aggregateHash), + payloadForSigCalculation, integrityAlgorithm, cfg.cryptoService ); - if ( - manifest.encryptionInformation.integrityInformation.rootSignature.sig !== - base64.encode(payloadSigStr) - ) { + const rootSig = isLegacyTDF + ? base64.encode(payloadSigInHex) + : base64.encodeArrayBuffer(hex.decodeArrayBuffer(payloadSigInHex)); + + if (manifest.encryptionInformation.integrityInformation.rootSignature.sig !== rootSig) { throw new IntegrityError('Failed integrity check on root signature'); } @@ -1252,7 +1283,7 @@ export async function readStream(cfg: DecryptConfiguration) { assertionKey = foundKey; } } - await assertions.verify(assertion, aggregateHash, assertionKey); + await assertions.verify(assertion, aggregateHash, assertionKey, isLegacyTDF); } } @@ -1293,7 +1324,8 @@ export async function readStream(cfg: DecryptConfiguration) { keyForDecryption, cipher, segmentIntegrityAlg, - cfg.cryptoService + cfg.cryptoService, + isLegacyTDF ); let progress = 0; @@ -1328,3 +1360,9 @@ export async function readStream(cfg: DecryptConfiguration) { outputStream.emit('rewrap', metadata); return outputStream; } + +async function concatenateUint8Array(uint8arrays: Uint8Array[]): Promise { + const blob = new Blob(uint8arrays); + const buffer = await blob.arrayBuffer(); + return new Uint8Array(buffer); +} 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';