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
FirebaseMessaging: 4487bbff9b9b927ba1dd3ea40d1ceb58e4ee3cb5
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431

View File

@ -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 });
},

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 () => {
try {
await actions.saveDetails();
@ -258,9 +272,6 @@ export function ProfileBody({ navigation }) {
</View>
</KeyboardAvoidingView>
</Modal>
<Modal
animationType="fade"
transparent={true}
@ -282,7 +293,7 @@ export function ProfileBody({ navigation }) {
{ !state.showSealUnlock && (
<View style={styles.inputField}>
<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} />
<TouchableOpacity onPress={actions.showSealUnlock}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
@ -292,7 +303,7 @@ export function ProfileBody({ navigation }) {
{ state.showSealUnlock && (
<View style={styles.inputField}>
<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} />
<TouchableOpacity onPress={actions.hideSealUnlock}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
@ -306,7 +317,7 @@ export function ProfileBody({ navigation }) {
{ !state.showSealPassword && (
<View style={styles.inputField}>
<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} />
<TouchableOpacity onPress={actions.showSealPassword}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
@ -316,7 +327,7 @@ export function ProfileBody({ navigation }) {
{ state.showSealPassword && (
<View style={styles.inputField}>
<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} />
<TouchableOpacity onPress={actions.hideSealPassword}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
@ -326,7 +337,7 @@ export function ProfileBody({ navigation }) {
{ !state.showSealConfirm && (
<View style={styles.inputField}>
<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} />
<TouchableOpacity onPress={actions.showSealConfirm}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
@ -336,7 +347,7 @@ export function ProfileBody({ navigation }) {
{ state.showSealConfirm && (
<View style={styles.inputField}>
<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} />
<TouchableOpacity onPress={actions.hideSealConfirm}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
@ -356,6 +367,7 @@ export function ProfileBody({ navigation }) {
{ state.sealMode === 'unlocked' && (
<View style={styles.inputField}>
<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} />
</View>
)}
@ -363,23 +375,39 @@ export function ProfileBody({ navigation }) {
<TouchableOpacity style={styles.cancel} onPress={actions.hideSealEdit}>
<Text>Cancel</Text>
</TouchableOpacity>
{ state.sealMode !== 'unlocking' && (
<TouchableOpacity style={styles.cancel} onPress={actions.hideSealEdit}>
<Text>Save</Text>
</TouchableOpacity>
{ state.canSaveSeal && (
<>
{ state.sealMode !== 'unlocking' && (
<TouchableOpacity style={styles.save} onPress={saveSeal}>
<Text style={styles.saveText}>Save</Text>
</TouchableOpacity>
)}
{ state.sealMode === 'unlocking' && (
<TouchableOpacity style={styles.save} onPress={saveSeal}>
<Text style={styles.saveText}>Unlock</Text>
</TouchableOpacity>
)}
</>
)}
{ state.sealMode === 'unlocking' && (
<TouchableOpacity style={styles.cancel} onPress={actions.hideSealEdit}>
<Text>Unlock</Text>
</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>
</KeyboardAvoidingView>
</Modal>
<Modal
animationType="fade"
transparent={true}
@ -458,7 +486,6 @@ export function ProfileBody({ navigation }) {
</View>
</KeyboardAvoidingView>
</Modal>
</View>
);
};

View File

@ -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: {

View File

@ -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 });
},