support e2e key handling in mobile

This commit is contained in:
Roland Osborne 2022-12-08 14:54:07 -08:00
parent 4a8c2f1776
commit 5f4b04adc6
5 changed files with 173 additions and 55 deletions

View File

@ -669,7 +669,7 @@ SPEC CHECKSUMS:
FirebaseInstallations: 99d24bac0243cf8b0e96cf5426340d211f0bcc80 FirebaseInstallations: 99d24bac0243cf8b0e96cf5426340d211f0bcc80
FirebaseMessaging: 4487bbff9b9b927ba1dd3ea40d1ceb58e4ee3cb5 FirebaseMessaging: 4487bbff9b9b927ba1dd3ea40d1ceb58e4ee3cb5
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7 GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431

View File

@ -1,5 +1,6 @@
import { useState, useRef, useContext } from 'react'; import { useState, useRef, useContext } from 'react';
import { StoreContext } from 'context/StoreContext'; import { StoreContext } from 'context/StoreContext';
import { setAccountSeal } from 'api/setAccountSeal';
import { setAccountSearchable } from 'api/setAccountSearchable'; import { setAccountSearchable } from 'api/setAccountSearchable';
import { setAccountNotifications } from 'api/setAccountNotifications'; import { setAccountNotifications } from 'api/setAccountNotifications';
import { getAccountStatus } from 'api/getAccountStatus'; import { getAccountStatus } from 'api/getAccountStatus';
@ -78,6 +79,7 @@ export function useAccountContext() {
updateState({ sealKey: key }); updateState({ sealKey: key });
}, },
unlockAccountSeal: async (key) => { unlockAccountSeal: async (key) => {
const { guid } = session.current;
await store.actions.setAccountSealKey(guid, key); await store.actions.setAccountSealKey(guid, key);
updateState({ sealKey: key }); updateState({ sealKey: key });
}, },

View File

@ -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 () => { const saveDetails = async () => {
try { try {
await actions.saveDetails(); await actions.saveDetails();
@ -258,9 +272,6 @@ export function ProfileBody({ navigation }) {
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</Modal> </Modal>
<Modal <Modal
animationType="fade" animationType="fade"
transparent={true} transparent={true}
@ -282,7 +293,7 @@ export function ProfileBody({ navigation }) {
{ !state.showSealUnlock && ( { !state.showSealUnlock && (
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealUnlock} onChangeText={actions.setSealUnlock} <TextInput style={styles.input} value={state.sealUnlock} onChangeText={actions.setSealUnlock}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Seal Password" autoCapitalize={'none'} secureTextEntry={true} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} /> placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.showSealUnlock}> <TouchableOpacity onPress={actions.showSealUnlock}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
@ -292,7 +303,7 @@ export function ProfileBody({ navigation }) {
{ state.showSealUnlock && ( { state.showSealUnlock && (
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealUnlock} onChangeText={actions.setSealUnlock} <TextInput style={styles.input} value={state.sealUnlock} onChangeText={actions.setSealUnlock}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Seal Password" autoCapitalize={'none'} secureTextEntry={false} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} /> placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.hideSealUnlock}> <TouchableOpacity onPress={actions.hideSealUnlock}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
@ -306,7 +317,7 @@ export function ProfileBody({ navigation }) {
{ !state.showSealPassword && ( { !state.showSealPassword && (
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealPassword} onChangeText={actions.setSealPassword} <TextInput style={styles.input} value={state.sealPassword} onChangeText={actions.setSealPassword}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Password Seal" autoCapitalize={'none'} secureTextEntry={true} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} /> placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.showSealPassword}> <TouchableOpacity onPress={actions.showSealPassword}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
@ -316,7 +327,7 @@ export function ProfileBody({ navigation }) {
{ state.showSealPassword && ( { state.showSealPassword && (
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealPassword} onChangeText={actions.setSealPassword} <TextInput style={styles.input} value={state.sealPassword} onChangeText={actions.setSealPassword}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Password Seal" autoCapitalize={'none'} secureTextEntry={false} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} /> placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.hideSealPassword}> <TouchableOpacity onPress={actions.hideSealPassword}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
@ -326,7 +337,7 @@ export function ProfileBody({ navigation }) {
{ !state.showSealConfirm && ( { !state.showSealConfirm && (
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealConfirm} onChangeText={actions.setSealConfirm} <TextInput style={styles.input} value={state.sealConfirm} onChangeText={actions.setSealConfirm}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Confirm Seal" autoCapitalize={'none'} secureTextEntry={true} placeholder="Confirm Password"
placeholderTextColor={Colors.grey} /> placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.showSealConfirm}> <TouchableOpacity onPress={actions.showSealConfirm}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
@ -336,7 +347,7 @@ export function ProfileBody({ navigation }) {
{ state.showSealConfirm && ( { state.showSealConfirm && (
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealConfirm} onChangeText={actions.setSealConfirm} <TextInput style={styles.input} value={state.sealConfirm} onChangeText={actions.setSealConfirm}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Confirm Seal" autoCapitalize={'none'} secureTextEntry={false} placeholder="Confirm Password"
placeholderTextColor={Colors.grey} /> placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.hideSealConfirm}> <TouchableOpacity onPress={actions.hideSealConfirm}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" /> <Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
@ -356,6 +367,7 @@ export function ProfileBody({ navigation }) {
{ state.sealMode === 'unlocked' && ( { state.sealMode === 'unlocked' && (
<View style={styles.inputField}> <View style={styles.inputField}>
<TextInput style={styles.input} value={'xxxxxxxx'} editable="false" secureTextEntry={true} /> <TextInput style={styles.input} value={'xxxxxxxx'} editable="false" secureTextEntry={true} />
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
<TouchableOpacity style={styles.sealUpdate} onPress={actions.updateSeal} /> <TouchableOpacity style={styles.sealUpdate} onPress={actions.updateSeal} />
</View> </View>
)} )}
@ -363,23 +375,39 @@ export function ProfileBody({ navigation }) {
<TouchableOpacity style={styles.cancel} onPress={actions.hideSealEdit}> <TouchableOpacity style={styles.cancel} onPress={actions.hideSealEdit}>
<Text>Cancel</Text> <Text>Cancel</Text>
</TouchableOpacity> </TouchableOpacity>
{ state.canSaveSeal && (
<>
{ state.sealMode !== 'unlocking' && ( { state.sealMode !== 'unlocking' && (
<TouchableOpacity style={styles.cancel} onPress={actions.hideSealEdit}> <TouchableOpacity style={styles.save} onPress={saveSeal}>
<Text>Save</Text> <Text style={styles.saveText}>Save</Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
{ state.sealMode === 'unlocking' && ( { state.sealMode === 'unlocking' && (
<TouchableOpacity style={styles.cancel} onPress={actions.hideSealEdit}> <TouchableOpacity style={styles.save} onPress={saveSeal}>
<Text>Unlock</Text> <Text style={styles.saveText}>Unlock</Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
</>
)}
{ !state.canSaveSeal && (
<>
{ state.sealMode !== 'unlocking' && (
<View style={styles.disabled}>
<Text style={styles.disabledText}>Save</Text>
</View>
)}
{ state.sealMode === 'unlocking' && (
<View style={styles.disabled}>
<Text style={styles.disabledText}>Unlock</Text>
</View>
)}
</>
)}
</View> </View>
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</Modal> </Modal>
<Modal <Modal
animationType="fade" animationType="fade"
transparent={true} transparent={true}
@ -458,7 +486,6 @@ export function ProfileBody({ navigation }) {
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
</Modal> </Modal>
</View> </View>
); );
}; };

View File

@ -140,8 +140,8 @@ export const styles = StyleSheet.create({
sealUpdate: { sealUpdate: {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
height: '100%', height: 36,
left: 0, left: 8,
width: '100%', width: '100%',
}, },
sealable: { sealable: {

View File

@ -41,6 +41,7 @@ export function useProfileBody() {
sealKey: null, sealKey: null,
sealEnabled: false, sealEnabled: false,
sealUnlocked: false, sealUnlocked: false,
canSaveSeal: false,
sealEdit: false, sealEdit: false,
sealMode: null, sealMode: null,
@ -111,13 +112,10 @@ export function useProfileBody() {
return encoded return encoded
}; };
const actions = { const sealEnable = async () => {
sealTest: async () => {
console.log("SEAL TEST");
// generate key to encrypt private key // generate key to encrypt private key
const salt = CryptoJS.lib.WordArray.random(128 / 8); const salt = CryptoJS.lib.WordArray.random(128 / 8);
const aes = CryptoJS.PBKDF2('testpassword', salt, { const aes = CryptoJS.PBKDF2(state.sealPassword, salt, {
keySize: 256 / 32, keySize: 256 / 32,
iterations: 1024, iterations: 1024,
}); });
@ -130,17 +128,91 @@ export function useProfileBody() {
// encrypt private key // encrypt private key
const iv = CryptoJS.lib.WordArray.random(128 / 8); const iv = CryptoJS.lib.WordArray.random(128 / 8);
const privateKey = convertPem(crypto.getPrivateKey()); const privateKey = convertPem(crypto.getPrivateKey());
const publicKey = convertPem(crypto.getPublicKey());
const enc = CryptoJS.AES.encrypt(privateKey, aes, { iv: iv }); const enc = CryptoJS.AES.encrypt(privateKey, aes, { iv: iv });
const seal = { const seal = {
passwordSalt: salt.toString(), passwordSalt: salt.toString(),
privateKeyIv: iv.toString(), privateKeyIv: iv.toString(),
privateKeyEncrypted: enc.ciphertext.toString(CryptoJS.enc.Base64), privateKeyEncrypted: enc.ciphertext.toString(CryptoJS.enc.Base64),
publicKey: convertPem(crypto.getPublicKey()), publicKey: publicKey,
} }
console.log("SEAL:", seal); 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 = {
showSealEdit: () => { showSealEdit: () => {
let sealMode = null; let sealMode = null;
const sealable = state.sealEnabled; const sealable = state.sealEnabled;
@ -153,7 +225,7 @@ export function useProfileBody() {
else { else {
sealMode = 'disabled'; sealMode = 'disabled';
} }
updateState({ sealEdit: true, sealable, sealMode }); updateState({ sealEdit: true, sealable, sealMode, sealUnlock: null, sealPassword: null, sealConfirm: null, sealDelete: null });
}, },
hideSealEdit: () => { hideSealEdit: () => {
updateState({ sealEdit: false }); updateState({ sealEdit: false });
@ -181,6 +253,23 @@ export function useProfileBody() {
} }
updateState({ sealable, sealMode }); 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: () => { showSealUnlock: () => {
updateState({ showSealUnlock: true }); updateState({ showSealUnlock: true });
}, },