import { TestVectorInfo , TestVectorResult , parseIntegrationTestVectorsToTestVectorIterator , PositiveTestVectorInfo , } from '@aws-crypto/integration-vectors' ; import { EncryptTestVectorInfo , getEncryptTestVectorIterator , } from './get_encrypt_test_iterator' ; import { decryptMaterialsManagerNode , encryptMaterialsManagerNode , } from './decrypt_materials_manager_node' ; import { buildClient , CommitmentPolicy , MessageHeader , needs , DecryptOutput , } from '@aws-crypto/client-node' ; import { URL } from 'url' ; import got from 'got' ; import streamToPromise from 'stream-to-promise' ; const { encrypt , decrypt , decryptUnsignedMessageStream } = buildClient ( CommitmentPolicy . FORBID_ENCRYPT_ALLOW_DECRYPT ) ; import * as stream from 'stream' ; import * as util from 'util' ; const pipeline = util . promisify ( stream . pipeline ) ; const notSupportedEncryptMessages = [ 'frameLength out of bounds: 0 > frameLength >= 4294967295' , 'Not supported at this time.' , 'Negative Integration Tests are not supported for EncryptTests' , ] ; const notSupportedDecryptMessages = [ 'Not supported at this time.' ] ; async function runDecryption ( testVectorInfo ) { const cmm = decryptMaterialsManagerNode ( testVectorInfo . keysInfo ) ; if ( testVectorInfo . decryptionMethod == 'streaming-unsigned-only' ) { const plaintext = [ ] ; let messageHeader = false ; await pipeline ( await testVectorInfo . cipherStream ( ) , decryptUnsignedMessageStream ( cmm ) . once ( 'MessageHeader' , ( header ) => { messageHeader = header ; } ) . on ( 'data' , ( chunk ) => plaintext . push ( chunk ) ) , new stream . PassThrough ( ) ) ; needs ( messageHeader , 'Unknown format' ) ; return { plaintext : Buffer . concat ( plaintext ) , messageHeader , } ; } else { return await decrypt ( cmm , await testVectorInfo . cipherStream ( ) , { } ) ; } } export async function testDecryptVector ( testVectorInfo ) { if ( 'plainTextStream' in testVectorInfo ) { return testPositiveDecryptVector ( testVectorInfo ) ; } try { await runDecryption ( testVectorInfo ) ; } catch ( err ) { return { result : true , name : testVectorInfo . name } ; } return { result : false , name : testVectorInfo . name , err : new Error ( `Should have failed with ${ testVectorInfo . errorDescription } but decryption succeeded` ) , } ; } async function testPositiveDecryptVector ( testVectorInfo ) { const knownGood = await streamToPromise ( await testVectorInfo . plainTextStream ( ) ) ; try { const { plaintext } = await runDecryption ( testVectorInfo ) ; if ( knownGood . equals ( plaintext ) ) { return { result : true , name : testVectorInfo . name } ; } throw new Error ( 'Decrypted Plaintext did not match expected plaintext' ) ; } catch ( err ) { return { result : false , name : testVectorInfo . name , err } ; } } export async function testEncryptVector ( { name , keysInfo , encryptOp , plainTextData } , decryptOracle ) { try { const cmm = encryptMaterialsManagerNode ( keysInfo ) ; const { result : encryptResult } = await encrypt ( cmm , plainTextData , encryptOp ) ; const decryptResponse = await got . post ( decryptOracle , { headers : { 'Content-Type' : 'application/octet-stream' , Accept : 'application/octet-stream' , } , body : encryptResult , responseType : 'buffer' , } ) ; needs ( decryptResponse . statusCode === 200 , 'decrypt failure' ) ; const { body } = decryptResponse ; const result = plainTextData . equals ( body ) ; return { result , name } ; } catch ( err ) { return { result : false , name , err } ; } } function handleTestResults ( { name , result , err } , notSupportedMessages ) { if ( result ) { console . log ( { name , result } ) ; return true ; } else { if ( err && notSupportedMessages . includes ( err . message ) ) { console . log ( { name , result : `Not supported: ${ err . message }` } ) ; return true ; } console . log ( { name , result , err } ) ; return false ; } } export async function integrationDecryptTestVectors ( vectorFile , tolerateFailures = 0 , testName ? , concurrency = 1 ) { const tests = await parseIntegrationTestVectorsToTestVectorIterator ( vectorFile ) ; return parallelTests ( concurrency , tolerateFailures , runTest , tests ) ; async function runTest ( test ) { if ( testName ) { if ( test . name !== testName ) return true ; } return handleTestResults ( await testDecryptVector ( test ) , notSupportedDecryptMessages ) ; } } export async function integrationEncryptTestVectors ( manifestFile , keyFile , decryptOracle , tolerateFailures = 0 , testName ? , concurrency = 1 ) { const decryptOracleUrl = new URL ( decryptOracle ) . toString ( ) ; const tests = await getEncryptTestVectorIterator ( manifestFile , keyFile ) ; return parallelTests ( concurrency , tolerateFailures , runTest , tests ) ; async function runTest ( test ) { if ( testName ) { if ( test . name !== testName ) return true ; } return handleTestResults ( await testEncryptVector ( test , decryptOracleUrl ) , notSupportedEncryptMessages ) ; } } async function parallelTests < Test extends EncryptTestVectorInfo | TestVectorInfo , work extends ( test : Test ) => Promise < boolean > > ( max , tolerateFailures , runTest , tests ) { let _resolve ; const queue = new Set < Promise < void > > ( ) ; let failureCount = 0 ; return new Promise < number > ( ( resolve ) => { _resolve = resolve ; enqueue ( ) ; } ) ; function enqueue ( ) { if ( failureCount > tolerateFailures ) return _resolve ( failureCount ) ; if ( queue . size > max ) return ; const { value , done } = tests . next ( ) ; if ( ! value && done ) return _resolve ( failureCount ) ; const work = runTest ( value ) . then ( ( pass ) => { if ( ! pass ) failureCount += 1 ; } ) . catch ( ( err ) => { console . log ( err ) ; failureCount += 1 ; } ) . then ( ( ) => { queue . delete ( work ) ; enqueue ( ) ; } ) ; queue . add ( work ) ; enqueue ( ) ; } }