import { APIClient } from '@wireapp/api-client' ; import { Cryptobox } from '@wireapp/cryptobox' ; import * as Proteus from '@wireapp/proteus' ; import { CRUDEngine , MemoryEngine } from '@wireapp/store-engine' ; import * as bazinga64 from 'bazinga64' ; import * as crypto from 'crypto' ; import { promisify } from 'util' ; import { SessionPayloadBundle } from '.' ; import * as CryptographyHelper from '../test/CryptographyHelper' ; import { decryptAsset , encryptAsset } from './AssetCryptography' ; import { CryptographyService } from './CryptographyService' ; async function createEngine ( storeName ) { const engine = new MemoryEngine ( ) ; await engine . init ( storeName ) ; return engine ; } describe ( 'CryptographyService' , ( ) => { let cryptographyService ; let aliceLastResortPreKey ; let bob ; beforeAll ( async ( ) => { await Proteus . init ( ) ; } ) ; beforeEach ( async ( ) => { cryptographyService = new CryptographyService ( new APIClient ( ) , await createEngine ( 'wire' ) ) ; const preKeys = await cryptographyService . cryptobox . create ( ) ; aliceLastResortPreKey = preKeys . filter ( preKey => preKey . key_id === Proteus . keys . PreKey . MAX_PREKEY_ID ) [ 0 ] ; bob = new Cryptobox ( await createEngine ( 'wire' ) ) ; await bob . create ( ) ; } ) ; describe ( '"constructor"' , ( ) => { it ( 'creates an instance.' , ( ) => { expect ( cryptographyService . cryptobox [ 'identity' ] ! . public_key . fingerprint ( ) ) . toBeDefined ( ) ; expect ( cryptographyService ) . toBeDefined ( ) ; } ) ; } ) ; describe ( '"constructSessionId"' , ( ) => { it ( 'constructs a Session ID by a given User ID and Client ID.' , ( ) => { const clientId = '1ceb9063fced26d3' ; const userId = 'afbb5d60-1187-4385-9c29-7361dea79647' ; const actual = CryptographyService . constructSessionId ( userId , clientId , null ) ; expect ( actual ) . toContain ( clientId ) ; expect ( actual ) . toContain ( userId ) ; } ) ; it ( 'constructs a Session ID by a given User ID and Client ID and domain.' , ( ) => { const clientId = '1ceb9063fced26d3' ; const userId = 'afbb5d60-1187-4385-9c29-7361dea79647' ; const actual = CryptographyService . constructSessionId ( userId , clientId , 'test.wire.link' ) ; expect ( actual ) . toContain ( clientId ) ; expect ( actual ) . toContain ( userId ) ; expect ( actual ) . toContain ( 'test.wire.link' ) ; } ) ; } ) ; describe ( '"decrypt"' , ( ) => { it ( 'decrypts a Base64-encoded cipher message.' , async ( ) => { const alicePublicKey = cryptographyService . cryptobox [ 'identity' ] ! . public_key ; const publicPreKeyBundle = new Proteus . keys . PreKeyBundle ( alicePublicKey , aliceLastResortPreKey ) ; const text = 'Hello Alice!' ; const encryptedPreKeyMessage = await bob . encrypt ( 'alice-user-id@alice-client-id' , text , publicPreKeyBundle . serialise ( ) ) ; const encodedPreKeyMessage = bazinga64 . Encoder . toBase64 ( encryptedPreKeyMessage ) . asString ; const decryptedMessage = await cryptographyService . decrypt ( 'bob-user-id@bob-client-id' , encodedPreKeyMessage ) ; const plaintext = Buffer . from ( decryptedMessage ) . toString ( 'utf8' ) ; expect ( plaintext ) . toBe ( text ) ; } ) ; it ( 'is resistant to duplicated message errors' , async ( ) => { const receiver = cryptographyService . cryptobox [ 'identity' ] ! ; const preKey = await cryptographyService . cryptobox [ 'get_prekey' ] ( ) ; const text = 'Hi!' ; const encodedPreKeyMessage = await CryptographyHelper . createEncodedCipherText ( receiver , preKey ! , text ) ; const sessionId = 'alice-to-bob' ; const plaintext = await CryptographyHelper . getPlainText ( cryptographyService , encodedPreKeyMessage , sessionId ) ; await CryptographyHelper . getPlainText ( cryptographyService , encodedPreKeyMessage , sessionId ) ; await CryptographyHelper . getPlainText ( cryptographyService , encodedPreKeyMessage , sessionId ) ; await CryptographyHelper . getPlainText ( cryptographyService , encodedPreKeyMessage , sessionId ) ; expect ( plaintext ) . toBe ( text ) ; } ) ; } ) ; describe ( '"encrypt"' , ( ) => { it ( 'generates a set of encrypted data based on PreKeys from multiple clients.' , async ( ) => { const firstUserID = 'bc0c99f1-49a5-4ad2-889a-62885af37088' ; const secondUserID = '2bde49aa-bdb5-458f-98cf-7d3552b10916' ; const firstClientId = '2b83ee08d7ac550d' ; const preKeyBundleMap = { [ firstUserID ] : { '5e80ea7886680975' : { id : 1337 , key : 'pQABARn//wKhAFggJ1Fbpg5l6wnzKOJE+vXpRnkqUYhIvVnR5lNXEbO2o/0DoQChAFggHxZvgvtDktY/vqBcpjjo6rQnXvcNQhfwmy8AJQJKlD0E9g==' , } , be67218b77d02d30 : { id : 72 , key : 'pQABARn//wKhAFggTWwHUoppQ8aXWhbH95YWnNp6uOYMxo2y4wbarWbF+EEDoQChAFggUiFoPtsiR0WFowIvl0myD+bVnFQJBYarqieI0Gly46QE9g==' , } , [ firstClientId ] : { id : 42 , key : 'pQABARn//wKhAFggWcbwny0jdqlcnnn0j4QSENIVVq/KgyQ3mmdpunfvGZQDoQChAFggrsQBkQkrVZ8sWhr8wTeaC+dmctuJ3oRqfdHsymTtKmgE9g==' , } , } , [ secondUserID ] : { '5bad8cdeddc5a90f' : { id : 1 , key : 'pQABARn//wKhAFggEYATUNJBQ7E2tfHT7HMLxa4O3Ckd7PciUdyKiGNNWbYDoQChAFggP/s0BHmHQDNwrO4pC1dqdNHsW7bnpmF9mBadrbep4PoE9g==' , } , bc78eded90386d20 : { id : 65535 , key : 'pQABARn//wKhAFgg1xOfzMpWmpN2aBGW+0RG23L0I301pncd/HXqUm+pVyoDoQChAFggnl+dmwGW45AArcPutjUkAjYmhIbXBPrqkVrNyg0ZI08E9g==' , } , } , } ; const text = new Uint8Array ( Buffer . from ( 'Hello' , 'utf8' ) ) ; const { encrypted } = await cryptographyService . encrypt ( text , preKeyBundleMap ) ; expect ( Object . keys ( encrypted ) . length ) . toBe ( 2 ) ; expect ( Object . keys ( encrypted [ firstUserID ] ) . length ) . toBe ( 3 ) ; expect ( Object . keys ( encrypted [ secondUserID ] ) . length ) . toBe ( 2 ) ; expect ( encrypted [ firstUserID ] [ firstClientId ] ) . toEqual ( jasmine . any ( Uint8Array ) ) ; } ) ; it ( 'does not generate a message counter twice when ran asynchronously multiple times for the same cryptographic session' , async ( ) => { const userId = 'bc0c99f1-49a5-4ad2-889a-62885af37088' ; const clientId = '5e80ea7886680975' ; const preKeyBundleMap = { [ userId ] : { [ clientId ] : { id : 1337 , key : 'pQABARn//wKhAFggJ1Fbpg5l6wnzKOJE+vXpRnkqUYhIvVnR5lNXEbO2o/0DoQChAFggHxZvgvtDktY/vqBcpjjo6rQnXvcNQhfwmy8AJQJKlD0E9g==' , } , } , } ; const text = new Uint8Array ( [ 72 , 101 , 108 , 108 , 111 , 33 ] ) ; const encryptionRuns = 100 ; const otrBundles = await Promise . all ( Array . from ( Array ( encryptionRuns ) . keys ( ) ) . map ( ( ) => cryptographyService . encrypt ( text , preKeyBundleMap ) ) ) ; const encryptedPayloads = otrBundles . map ( ( { encrypted } ) => encrypted [ userId ] [ clientId ] ) ; const messageCounters = encryptedPayloads . map ( encodedCiphertext => { const messageBytes = encodedCiphertext ; const messageEnvelope = Proteus . message . Envelope . deserialise ( messageBytes . buffer ) ; const preKeyMessage = messageEnvelope . message as Proteus . message . PreKeyMessage ; const cipherMessage = preKeyMessage . message ; return cipherMessage . counter ; } ) ; const uniqueValues = messageCounters . filter ( ( value , index , self ) => self . indexOf ( value ) === index ) ; expect ( uniqueValues . length ) . toBe ( encryptionRuns ) ; } ) ; } ) ; describe ( '"encryptAsset"' , ( ) => { it ( 'encrypts and decrypts ArrayBuffer' , async ( ) => { const bytes = new Uint8Array ( 16 ) ; await promisify ( crypto . randomFill ) ( bytes ) ; const byteBuffer = Buffer . from ( bytes . buffer ) ; const encryptedAsset = await encryptAsset ( { plainText : byteBuffer } ) ; const decryptedBuffer = await decryptAsset ( encryptedAsset ) ; expect ( decryptedBuffer ) . toEqual ( byteBuffer ) ; } ) ; } ) ; describe ( '"encryptPayloadForSession"' , ( ) => { it ( 'encodes plaintext.' , async ( ) => { const sessionWithBobId = 'bob-user-id@bob-client-id' ; const text = new Uint8Array ( [ 72 , 101 , 108 , 108 , 111 , 32 , 66 , 111 , 98 , 33 ] ) ; const encodedPreKey = 'pQABAQACoQBYIHOFFWPnWlr4sulxUWYoP0A6rsJiBO/Ec3Y914t67CIAA6EAoQBYIPFH5CK/a0YwKEx4n/+U/IPRN+mJXVv++MCs5Z4dLmz4BPY=' ; const { sessionId , encryptedPayload } = ( await cryptographyService [ 'encryptPayloadForSession' ] ( sessionWithBobId , text , encodedPreKey ) ) as SessionPayloadBundle ; expect ( Buffer . from ( encryptedPayload ) . toString ( 'utf8' ) ) . not . toBe ( '💣' ) ; expect ( sessionId ) . toBe ( sessionWithBobId ) ; } ) ; it ( 'encodes invalid text as Bomb Emoji.' , async ( ) => { const sessionWithBobId = 'bob-user-id@bob-client-id' ; const encodedPreKey = 'pQABAQACoQBYIHOFFWPnWlr4sulxUWYoP0A6rsJiBO/Ec3Y914t67CIAA6EAoQBYIPFH5CK/a0YwKEx4n/+U/IPRN+mJXVv++MCs5Z4dLmz4BPY=' ; const { sessionId , encryptedPayload } = ( await cryptographyService [ 'encryptPayloadForSession' ] ( sessionWithBobId , undefined as any , encodedPreKey ) ) as SessionPayloadBundle ; expect ( Buffer . from ( encryptedPayload ) . toString ( ) ) . toBe ( '💣' ) ; expect ( sessionId ) . toBe ( sessionWithBobId ) ; } ) ; } ) ; } ) ;