From 56b9e63b7265811a8d76ee23f8cca6c2dd072f59 Mon Sep 17 00:00:00 2001 From: Julian Adams Date: Thu, 9 Aug 2018 18:30:15 -0500 Subject: [PATCH] Composer: Fix external session. Closes #335 --- email_composer/src/containers/Composer.js | 2 +- email_composer/src/libs/signal.js | 43 ++++++++++----- email_composer/src/utils/AESUtils.js | 65 +++++++++++++++++++---- 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/email_composer/src/containers/Composer.js b/email_composer/src/containers/Composer.js index dba4030e0..2ba5afd65 100644 --- a/email_composer/src/containers/Composer.js +++ b/email_composer/src/containers/Composer.js @@ -134,7 +134,7 @@ class ComposerWrapper extends Component { : Status.DISABLED; state = { ...composerData, status }; } - const { key, iv } = generateKeyAndIv(null, 8, null); + const { key, iv } = generateKeyAndIv(null, 8); state = { ...state, key, iv }; fileManager.on(FILE_PROGRESS, this.handleUploadProgress); fileManager.on(FILE_FINISH, this.handleUploadSuccess); diff --git a/email_composer/src/libs/signal.js b/email_composer/src/libs/signal.js index 5ac428d4a..7412c6a44 100644 --- a/email_composer/src/libs/signal.js +++ b/email_composer/src/libs/signal.js @@ -9,9 +9,12 @@ import { import SignalProtocolStore from './store'; import { CustomError } from './../utils/CustomError'; import { - AesEncryptToBase64, + AesEncrypt, generateKeyAndIv, - base64ToWordArray + base64ToWordArray, + wordArrayToByteArray, + wordArrayToBase64, + byteArrayToWordArray } from '../utils/AESUtils'; const KeyHelper = libsignal.KeyHelper; @@ -199,6 +202,7 @@ const encryptPostEmail = async ({ peer, fileKeyParams ); + const fileKey = criptextEmails[0] ? criptextEmails[0].fileKey : null; const allExternalRecipients = [ ...externalRecipients.to, @@ -207,7 +211,7 @@ const encryptPostEmail = async ({ ]; const hasExternalRecipients = allExternalRecipients.length > 0; const { session, encryptedBody } = externalEmailPassword.length - ? await encryptExternalEmail(body, externalEmailPassword) + ? await encryptExternalEmail(body, externalEmailPassword, fileKey) : { session: null, encryptedBody: null }; const guestEmail = hasExternalRecipients @@ -235,7 +239,7 @@ const encryptPostEmail = async ({ return res; }; -const createDummyKeyBundle = async () => { +const createDummyKeyBundle = async fileKey => { const preKeyId = 1; const signedPreKeyId = 1; const { identityKey, registrationId } = await generateIdentity(); @@ -252,6 +256,7 @@ const createDummyKeyBundle = async () => { signedPreKey }; const dummySession = { + fileKey, identityKey: { publicKey: util.toBase64(identityKey.pubKey), privateKey: util.toBase64(identityKey.privKey) @@ -295,10 +300,10 @@ const generatePreKeyBundle = async ({ return { preKey, signedPreKey }; }; -const encryptExternalEmail = async (body, password) => { +const encryptExternalEmail = async (body, password, fileKey) => { const recipient = password; const deviceId = 1; - const { dummySession, sessionParams } = await createDummyKeyBundle(); + const { dummySession, sessionParams } = await createDummyKeyBundle(fileKey); const keys = { preKey: { id: sessionParams.preKey.keyId, @@ -319,12 +324,26 @@ const encryptExternalEmail = async (body, password) => { ); const saltLength = 8; - const keyLength = 128 / 32; - const { key, iv } = generateKeyAndIv(password, saltLength, keyLength); - const keyArray = base64ToWordArray(key); - const ivArray = base64ToWordArray(iv); - const sessionString = JSON.stringify(dummySession); - const session = AesEncryptToBase64(sessionString, keyArray, ivArray); + const { key, iv, salt } = generateKeyAndIv(password, saltLength); + const saltWArray = base64ToWordArray(salt); + const ivWArray = base64ToWordArray(iv); + const keyWArray = base64ToWordArray(key); + + const dummySessionString = JSON.stringify(dummySession); + const encryptedSessionWArray = AesEncrypt( + dummySessionString, + keyWArray, + ivWArray + ); + const saltBArray = wordArrayToByteArray(saltWArray); + const ivBArray = wordArrayToByteArray(ivWArray); + const encryptedSessionBArray = wordArrayToByteArray(encryptedSessionWArray); + const sessionByteArray = saltBArray.concat( + ivBArray.concat(encryptedSessionBArray) + ); + + const sessionWordArray = byteArrayToWordArray(sessionByteArray); + const session = wordArrayToBase64(sessionWordArray); return { session, encryptedBody: encryptedBody.body diff --git a/email_composer/src/utils/AESUtils.js b/email_composer/src/utils/AESUtils.js index 6e59bd061..1c1438302 100644 --- a/email_composer/src/utils/AESUtils.js +++ b/email_composer/src/utils/AESUtils.js @@ -1,26 +1,71 @@ import CryptoJS from 'crypto-js'; -export const generateKeyAndIv = (phrase, saltsize, keysize) => { +export const generateKeyAndIv = (phrase, saltsize) => { const passphrase = phrase || CryptoJS.lib.WordArray.random(saltsize); - const keySalt = CryptoJS.lib.WordArray.random(saltsize); - const ivSalt = CryptoJS.lib.WordArray.random(saltsize); + const salt = CryptoJS.lib.WordArray.random(saltsize); const keyParams = { - keySize: keysize || 128 / 32, - iterations: 1000 + keySize: 128 / 32, + iterations: 10000, + hasher: CryptoJS.algo.SHA256 }; - const keyArray = CryptoJS.PBKDF2(passphrase, keySalt, keyParams); - const ivArray = CryptoJS.PBKDF2(passphrase, ivSalt, keyParams); + const keyArray = CryptoJS.PBKDF2(passphrase, salt, keyParams); + const ivArray = CryptoJS.lib.WordArray.random(16); return { key: keyArray.toString(CryptoJS.enc.Base64), - iv: ivArray.toString(CryptoJS.enc.Base64) + iv: ivArray.toString(CryptoJS.enc.Base64), + salt: salt.toString(CryptoJS.enc.Base64) }; }; -export const AesEncryptToBase64 = (data, keyArray, ivArray) => { +export const AesEncrypt = (data, keyArray, ivArray) => { const encrypted = CryptoJS.AES.encrypt(data, keyArray, { iv: ivArray }); - return encrypted.toString(); + const based64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64); + return base64ToWordArray(based64); }; export const base64ToWordArray = base64String => { return CryptoJS.enc.Base64.parse(base64String); }; + +export const wordArrayToBase64 = wordArray => { + return CryptoJS.enc.Base64.stringify(wordArray); +}; + +export const byteArrayToWordArray = bytearray => { + const wa = []; + let i; + for (i = 0; i < bytearray.length; i++) { + wa[(i / 4) | 0] |= bytearray[i] << (24 - 8 * i); + } + return CryptoJS.lib.WordArray.create(wa, bytearray.length); +}; + +const wordToByteArray = (word, length) => { + const ba = []; + const xFF = 0xff; + if (length > 0) ba.push(word >>> 24); + if (length > 1) ba.push((word >>> 16) & xFF); + if (length > 2) ba.push((word >>> 8) & xFF); + if (length > 3) ba.push(word & xFF); + return ba; +}; + +export const wordArrayToByteArray = (wordarray, length) => { + if ( + wordarray.hasOwnProperty('sigBytes') && + wordarray.hasOwnProperty('words') + ) { + length = wordarray.sigBytes; + wordarray = wordarray.words; + } + const result = []; + let i = 0; + let bytes; + while (length > 0) { + bytes = wordToByteArray(wordarray[i], Math.min(4, length)); + length -= bytes.length; + result.push(bytes); + i++; + } + return [].concat.apply([], result); +};