import { Transform as PortableTransform } from 'readable-stream' ; import { Transform } from 'stream' ; import { needs , GetDecipher , AwsEsdkJsDecipherGCM , } from '@aws-crypto/material-management-node' ; import { aadFactory , ContentType } from '@aws-crypto/serialize' ; import { VerifyStream } from './verify_stream' ; const fromUtf8 = ( input ) => Buffer . from ( input , 'utf8' ) ; const aadUtility = aadFactory ( fromUtf8 ) ; const PortableTransformWithType = PortableTransform as new ( ... args ) => Transform ; export interface DecipherInfo { messageId : Buffer ; contentType : ContentType ; getDecipher : GetDecipher ; dispose : ( ) => void ; } interface DecipherState { decipher : AwsEsdkJsDecipherGCM ; content : Buffer [ ] ; contentLength : number ; } export interface BodyInfo { iv : Buffer ; contentLength : number ; sequenceNumber : number ; isFinalFrame : boolean ; } const ioTick = async ( ) => new Promise ( ( resolve ) => setImmediate ( resolve ) ) ; const noop = ( ) => { } ; export function getDecipherStream ( ) { let decipherInfo ; let decipherState = { } as any ; let pathologicalDrain = noop ; let frameComplete = false ; return new ( class DecipherStream extends PortableTransformWithType { constructor ( ) { super ( ) ; this . on ( 'pipe' , ( source ) => { needs ( source instanceof VerifyStream , 'Unsupported source' ) ; source . once ( 'DecipherInfo' , ( info ) => { decipherInfo = info ; } ) . on ( 'BodyInfo' , this . _onBodyHeader ) . on ( 'AuthTag' , ( authTag , next ) => { this . _onAuthTag ( authTag , next ) . catch ( ( e ) => this . emit ( 'error' , e ) ) ; } ) ; } ) ; } _onBodyHeader = ( { iv , contentLength , sequenceNumber , isFinalFrame , } ) => { needs ( decipherInfo , 'Malformed State.' ) ; needs ( ! decipherState . decipher , 'Malformed State.' ) ; const { messageId , contentType , getDecipher } = decipherInfo ; const aadString = aadUtility . messageAADContentString ( { contentType , isFinalFrame , } ) ; const messageAAD = aadUtility . messageAAD ( messageId , aadString , sequenceNumber , contentLength ) ; const decipher = getDecipher ( iv ) . setAAD ( Buffer . from ( messageAAD . buffer , messageAAD . byteOffset , messageAAD . byteLength ) ) ; const content = [ ] ; decipherState = { decipher , content , contentLength } ; } ; _transform ( chunk , _encoding , callback ) { needs ( decipherState . decipher , 'Malformed State.' ) ; decipherState . contentLength -= chunk . length ; needs ( decipherState . contentLength >= 0 , 'Lengths do not match' ) ; const { content } = decipherState ; content . push ( chunk ) ; if ( decipherState . contentLength > 0 ) { callback ( ) ; } else { frameComplete = callback ; } } _read ( size ) { pathologicalDrain ( ) ; pathologicalDrain = noop ; super . _read ( size ) ; } _onAuthTag = async ( authTag , next ) => { const { decipher , content , contentLength } = decipherState ; needs ( frameComplete || contentLength === 0 , 'AuthTag before frame.' ) ; needs ( contentLength === 0 , 'Lengths do not match' ) ; decipherState = { } as any ; decipher . setAuthTag ( authTag ) ; if ( ! content . length ) decipher . update ( Buffer . alloc ( 0 ) ) ; const clear = [ ] ; for ( const cipherChunk of content ) { const clearChunk = decipher . update ( cipherChunk ) ; clear . push ( clearChunk ) ; await ioTick ( ) ; } const tail = decipher . final ( ) ; clear . push ( tail ) ; for ( const clearChunk of clear ) { if ( ! this . push ( clearChunk ) ) { await new Promise ( ( resolve ) => { pathologicalDrain = resolve ; } ) ; } } next ( ) ; if ( frameComplete ) frameComplete ( ) ; frameComplete = false ; } ; _destroy ( ) { decipherInfo && decipherInfo . dispose ( ) ; } } ) ( ) ; }