From 5f4b04adc6d23180dd70c67bc88126385db4c34d Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Thu, 8 Dec 2022 14:54:07 -0800 Subject: [PATCH] support e2e key handling in mobile --- app/mobile/ios/Podfile.lock | 2 +- .../src/context/useAccountContext.hook.js | 2 + .../profile/profileBody/ProfileBody.jsx | 69 +++++--- .../profile/profileBody/ProfileBody.styled.js | 4 +- .../profileBody/useProfileBody.hook.js | 151 ++++++++++++++---- 5 files changed, 173 insertions(+), 55 deletions(-) diff --git a/app/mobile/ios/Podfile.lock b/app/mobile/ios/Podfile.lock index 2e908c83..e1bdc265 100644 --- a/app/mobile/ios/Podfile.lock +++ b/app/mobile/ios/Podfile.lock @@ -669,7 +669,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 99d24bac0243cf8b0e96cf5426340d211f0bcc80 FirebaseMessaging: 4487bbff9b9b927ba1dd3ea40d1ceb58e4ee3cb5 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7 nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 diff --git a/app/mobile/src/context/useAccountContext.hook.js b/app/mobile/src/context/useAccountContext.hook.js index 3f81c84d..eef7251e 100644 --- a/app/mobile/src/context/useAccountContext.hook.js +++ b/app/mobile/src/context/useAccountContext.hook.js @@ -1,5 +1,6 @@ import { useState, useRef, useContext } from 'react'; import { StoreContext } from 'context/StoreContext'; +import { setAccountSeal } from 'api/setAccountSeal'; import { setAccountSearchable } from 'api/setAccountSearchable'; import { setAccountNotifications } from 'api/setAccountNotifications'; import { getAccountStatus } from 'api/getAccountStatus'; @@ -78,6 +79,7 @@ export function useAccountContext() { updateState({ sealKey: key }); }, unlockAccountSeal: async (key) => { + const { guid } = session.current; await store.actions.setAccountSealKey(guid, key); updateState({ sealKey: key }); }, diff --git a/app/mobile/src/session/profile/profileBody/ProfileBody.jsx b/app/mobile/src/session/profile/profileBody/ProfileBody.jsx index fd807a6c..1415a02e 100644 --- a/app/mobile/src/session/profile/profileBody/ProfileBody.jsx +++ b/app/mobile/src/session/profile/profileBody/ProfileBody.jsx @@ -41,6 +41,20 @@ export function ProfileBody({ navigation }) { } } + const saveSeal = async () => { + try { + await actions.saveSeal(); + actions.hideSealEdit(); + } + catch (err) { + console.log(err); + Alert.alert( + 'Failed to Update Topic Sealing', + 'Please try again.', + ) + } + } + const saveDetails = async () => { try { await actions.saveDetails(); @@ -258,9 +272,6 @@ export function ProfileBody({ navigation }) { - - - @@ -292,7 +303,7 @@ export function ProfileBody({ navigation }) { { state.showSealUnlock && ( @@ -306,7 +317,7 @@ export function ProfileBody({ navigation }) { { !state.showSealPassword && ( @@ -316,7 +327,7 @@ export function ProfileBody({ navigation }) { { state.showSealPassword && ( @@ -326,7 +337,7 @@ export function ProfileBody({ navigation }) { { !state.showSealConfirm && ( @@ -336,7 +347,7 @@ export function ProfileBody({ navigation }) { { state.showSealConfirm && ( @@ -356,6 +367,7 @@ export function ProfileBody({ navigation }) { { state.sealMode === 'unlocked' && ( + )} @@ -363,23 +375,39 @@ export function ProfileBody({ navigation }) { Cancel - { state.sealMode !== 'unlocking' && ( - - Save - + { state.canSaveSeal && ( + <> + { state.sealMode !== 'unlocking' && ( + + Save + + )} + { state.sealMode === 'unlocking' && ( + + Unlock + + )} + )} - { state.sealMode === 'unlocking' && ( - - Unlock - + { !state.canSaveSeal && ( + <> + { state.sealMode !== 'unlocking' && ( + + Save + + )} + { state.sealMode === 'unlocking' && ( + + Unlock + + )} + )} + - - - - ); }; diff --git a/app/mobile/src/session/profile/profileBody/ProfileBody.styled.js b/app/mobile/src/session/profile/profileBody/ProfileBody.styled.js index b48174ad..d7280da5 100644 --- a/app/mobile/src/session/profile/profileBody/ProfileBody.styled.js +++ b/app/mobile/src/session/profile/profileBody/ProfileBody.styled.js @@ -140,8 +140,8 @@ export const styles = StyleSheet.create({ sealUpdate: { position: 'absolute', top: 0, - height: '100%', - left: 0, + height: 36, + left: 8, width: '100%', }, sealable: { diff --git a/app/mobile/src/session/profile/profileBody/useProfileBody.hook.js b/app/mobile/src/session/profile/profileBody/useProfileBody.hook.js index 2d4311c3..07cd5884 100644 --- a/app/mobile/src/session/profile/profileBody/useProfileBody.hook.js +++ b/app/mobile/src/session/profile/profileBody/useProfileBody.hook.js @@ -41,7 +41,8 @@ export function useProfileBody() { sealKey: null, sealEnabled: false, sealUnlocked: false, - + canSaveSeal: false, + sealEdit: false, sealMode: null, sealable: false, @@ -111,36 +112,107 @@ export function useProfileBody() { return encoded }; + const sealEnable = async () => { + // generate key to encrypt private key + const salt = CryptoJS.lib.WordArray.random(128 / 8); + const aes = CryptoJS.PBKDF2(state.sealPassword, salt, { + keySize: 256 / 32, + iterations: 1024, + }); + + // generate rsa key for sealing channel, delay for activity indicator + await new Promise(r => setTimeout(r, 1000)); + const crypto = new JSEncrypt({ default_key_size: 2048 }); + const key = crypto.getKey(); + + // encrypt private key + const iv = CryptoJS.lib.WordArray.random(128 / 8); + const privateKey = convertPem(crypto.getPrivateKey()); + const publicKey = convertPem(crypto.getPublicKey()); + const enc = CryptoJS.AES.encrypt(privateKey, aes, { iv: iv }); + + const seal = { + passwordSalt: salt.toString(), + privateKeyIv: iv.toString(), + privateKeyEncrypted: enc.ciphertext.toString(CryptoJS.enc.Base64), + publicKey: publicKey, + } + const sealKey = { + public: publicKey, + private: privateKey, + } + await account.actions.setAccountSeal(seal, sealKey); + }; + + const sealDisable = async () => { + await account.actions.setAccountSeal({}, {}); + }; + + const sealUnlock = async () => { + // generate key to encrypt private key + const salt = CryptoJS.enc.Hex.parse(state.seal.passwordSalt); + const aes = CryptoJS.PBKDF2(state.sealUnlock, salt, { + keySize: 256 / 32, + iterations: 1024, + }); + + // decrypt private key + const iv = CryptoJS.enc.Hex.parse(state.seal.privateKeyIv); + const enc = CryptoJS.enc.Base64.parse(state.seal.privateKeyEncrypted) + let cipherParams = CryptoJS.lib.CipherParams.create({ + ciphertext: enc, + iv: iv + }); + const dec = CryptoJS.AES.decrypt(cipherParams, aes, { iv: iv }); + + // store unlocked seal + const sealKey = { + public: state.seal.publicKey, + private: dec.toString(CryptoJS.enc.Utf8), + }; + await account.actions.unlockAccountSeal(sealKey); + }; + + const sealUpdate = async () => { + // generate key to encrypt private key + const salt = CryptoJS.lib.WordArray.random(128 / 8); + const aes = CryptoJS.PBKDF2(state.sealPassword, salt, { + keySize: 256 / 32, + iterations: 1024, + }); + + // encrypt private key + const iv = CryptoJS.lib.WordArray.random(128 / 8); + const enc = CryptoJS.AES.encrypt(state.sealKey.private, aes, { iv: iv }); + + // update account + const seal = { + passwordSalt: salt.toString(), + privateKeyIv: iv.toString(), + privateKeyEncrypted: enc.ciphertext.toString(CryptoJS.enc.Base64), + publicKey: state.sealKey.public, + } + const sealKey = { ...state.sealKey } + await account.actions.setAccountSeal(seal, sealKey); + }; + + useEffect(() => { + if (state.sealMode === 'unlocking' && state.sealUnlock != null && state.sealUnlock !== '') { + return updateState({ canSaveSeal: true }); + } + if (state.sealMode === 'enabling' && state.sealPassword != null && state.sealPassword === state.sealConfirm) { + return updateState({ canSaveSeal: true }); + } + if (state.sealMode === 'disabling' && state.sealDelete === 'delete') { + return updateState({ canSaveSeal: true }); + } + if (state.sealMode === 'updating' && state.sealPassword != null && state.sealPassword === state.sealConfirm) { + return updateState({ canSaveSeal: true }); + } + updateState({ canSaveSeal: false }); + }, [state.sealMode, state.sealable, state.sealUnlock, state.sealPassword, state.sealConfirm, state.sealDelete]); + const actions = { - sealTest: async () => { - console.log("SEAL TEST"); - - // generate key to encrypt private key - const salt = CryptoJS.lib.WordArray.random(128 / 8); - const aes = CryptoJS.PBKDF2('testpassword', salt, { - keySize: 256 / 32, - iterations: 1024, - }); - - // generate rsa key for sealing channel, delay for activity indicator - await new Promise(r => setTimeout(r, 1000)); - const crypto = new JSEncrypt({ default_key_size: 2048 }); - const key = crypto.getKey(); - - // encrypt private key - const iv = CryptoJS.lib.WordArray.random(128 / 8); - const privateKey = convertPem(crypto.getPrivateKey()); - const enc = CryptoJS.AES.encrypt(privateKey, aes, { iv: iv }); - - const seal = { - passwordSalt: salt.toString(), - privateKeyIv: iv.toString(), - privateKeyEncrypted: enc.ciphertext.toString(CryptoJS.enc.Base64), - publicKey: convertPem(crypto.getPublicKey()), - } - console.log("SEAL:", seal); - - }, showSealEdit: () => { let sealMode = null; const sealable = state.sealEnabled; @@ -153,7 +225,7 @@ export function useProfileBody() { else { sealMode = 'disabled'; } - updateState({ sealEdit: true, sealable, sealMode }); + updateState({ sealEdit: true, sealable, sealMode, sealUnlock: null, sealPassword: null, sealConfirm: null, sealDelete: null }); }, hideSealEdit: () => { updateState({ sealEdit: false }); @@ -181,6 +253,23 @@ export function useProfileBody() { } updateState({ sealable, sealMode }); }, + saveSeal: async () => { + if (state.sealMode === 'enabling') { + await sealEnable(); + } + else if (state.sealMode === 'disabling') { + await sealDisable(); + } + else if (state.sealMode === 'unlocking') { + await sealUnlock(); + } + else if (state.sealMode === 'updating') { + await sealUpdate(); + } + else { + console.log(state.sealMode); + } + }, showSealUnlock: () => { updateState({ showSealUnlock: true }); },