import { Transform as PortableTransform } from 'readable-stream' ; import { Transform } from 'stream' ; import { NodeAlgorithmSuite , NodeMaterialsManager , getDecryptionHelper , CommitmentPolicy , CommitmentPolicySuites , SignaturePolicy , SignaturePolicySuites , ClientOptions , needs , } from '@aws-crypto/material-management-node' ; import { deserializeFactory , MessageHeaderV2 } from '@aws-crypto/serialize' ; import { VerifyInfo } from './verify_stream' ; const toUtf8 = ( input ) => Buffer . from ( input . buffer , input . byteOffset , input . byteLength ) . toString ( 'utf8' ) ; const deserialize = deserializeFactory ( toUtf8 , NodeAlgorithmSuite ) ; const PortableTransformWithType = PortableTransform as new ( ... args ) => Transform ; interface HeaderState { buffer : Buffer ; headerParsed : boolean ; } export class ParseHeaderStream extends PortableTransformWithType { private materialsManager ! : NodeMaterialsManager ; private commitmentPolicy ! : CommitmentPolicy ; private signaturePolicy ! : SignaturePolicy ; private maxEncryptedDataKeys ! : number | false ; private _headerState : HeaderState ; constructor ( signaturePolicy , { commitmentPolicy , maxEncryptedDataKeys } , cmm ) { super ( ) ; needs ( CommitmentPolicy [ commitmentPolicy ] , 'Invalid commitment policy.' ) ; needs ( SignaturePolicy [ signaturePolicy ] , 'Invalid signature policy.' ) ; needs ( maxEncryptedDataKeys === false || maxEncryptedDataKeys >= 1 , 'Invalid maxEncryptedDataKeys value.' ) ; Object . defineProperty ( this , 'materialsManager' , { value : cmm , enumerable : true , } ) ; Object . defineProperty ( this , 'commitmentPolicy' , { value : commitmentPolicy , enumerable : true , } ) ; Object . defineProperty ( this , 'maxEncryptedDataKeys' , { value : maxEncryptedDataKeys , enumerable : true , } ) ; this . _headerState = { buffer : Buffer . alloc ( 0 ) , headerParsed : false , } ; Object . defineProperty ( this , 'signaturePolicy' , { value : signaturePolicy , enumerable : true , } ) ; } _transform ( chunk , encoding , callback ) { try { const { _headerState , commitmentPolicy , materialsManager , signaturePolicy , maxEncryptedDataKeys , } = this ; const { buffer } = _headerState ; const headerBuffer = Buffer . concat ( [ buffer , chunk ] ) ; const headerInfo = deserialize . deserializeMessageHeader ( headerBuffer , { maxEncryptedDataKeys , } ) ; if ( ! headerInfo ) { _headerState . buffer = headerBuffer ; return callback ( ) ; } const { messageHeader , algorithmSuite } = headerInfo ; const messageIDStr = Buffer . from ( messageHeader . messageId ) . toString ( 'hex' ) ; CommitmentPolicySuites . isDecryptEnabled ( commitmentPolicy , algorithmSuite , messageIDStr ) ; SignaturePolicySuites . isDecryptEnabled ( signaturePolicy , algorithmSuite , messageIDStr ) ; const { rawHeader , headerAuth } = headerInfo ; const { headerIv , headerAuthTag , headerAuthLength } = headerAuth ; const suite = new NodeAlgorithmSuite ( algorithmSuite . id ) ; const { messageId , encryptionContext , encryptedDataKeys } = messageHeader ; materialsManager . decryptMaterials ( { suite , encryptionContext , encryptedDataKeys } ) . then ( ( material ) => { CommitmentPolicySuites . isDecryptEnabled ( commitmentPolicy , material . suite , messageIDStr ) ; SignaturePolicySuites . isDecryptEnabled ( signaturePolicy , material . suite , messageIDStr ) ; _headerState . buffer = Buffer . alloc ( 0 ) ; const { getDecipherInfo , getVerify , dispose } = getDecryptionHelper ( material ) ; const getDecipher = getDecipherInfo ( messageId , ( messageHeader as MessageHeaderV2 ) . suiteData ) ; const headerAuth = getDecipher ( headerIv ) ; headerAuth . setAAD ( Buffer . from ( rawHeader . buffer , rawHeader . byteOffset , rawHeader . byteLength ) ) ; headerAuth . setAuthTag ( Buffer . from ( headerAuthTag . buffer , headerAuthTag . byteOffset , headerAuthTag . byteLength ) ) ; headerAuth . update ( Buffer . alloc ( 0 ) ) ; headerAuth . final ( ) ; const verify = getVerify ? getVerify ( ) : void 0 ; const verifyInfo = { headerInfo , getDecipher , verify , dispose , } ; this . emit ( 'VerifyInfo' , verifyInfo ) ; this . emit ( 'MessageHeader' , headerInfo . messageHeader ) ; _headerState . headerParsed = true ; const readPos = rawHeader . byteLength + headerAuthLength ; const tail = headerBuffer . slice ( readPos ) ; this . _transform = ( chunk , _enc , cb ) => { try { cb ( null , chunk ) ; } catch ( ex ) { this . emit ( 'error' , ex ) ; } } ; return setImmediate ( ( ) => this . _transform ( tail , encoding , callback ) ) ; } ) . catch ( ( err ) => callback ( err ) ) ; } catch ( ex ) { this . emit ( 'error' , ex ) ; } } _flush ( callback ) { callback ( this . _headerState . headerParsed ? undefined : new Error ( 'Incomplete Header' ) ) ; } }