Browse Source

REF: decrypt storage & advanced mode switch

settingsui
Overtorment 5 years ago
parent
commit
59838a9433
  1. 37
      class/app-storage.js
  2. 4
      screen/plausibledeniability.js
  3. 3
      screen/settings/encryptStorage.js
  4. 18
      screen/settings/settings.js
  5. 5
      screen/wallets/add.js
  6. 3
      screen/wallets/walletMigrate.js
  7. 16
      tests/e2e/bluewallet.spec.js
  8. 6
      tests/setup.js
  9. 3
      tests/unit/Storage.test.js

37
class/app-storage.js

@ -25,7 +25,8 @@ export class AppStorage {
static ELECTRUM_TCP_PORT = 'electrum_tcp_port';
static PREFERRED_CURRENCY = 'preferredCurrency';
static ADVANCED_MODE_ENABLED = 'advancedmodeenabled';
static DELETEWALLETAFTERUNINSTALLKEY = 'deleteWalletAfterUninstall';
static DELETE_WALLET_AFTER_UNINSTALL = 'deleteWalletAfterUninstall';
constructor() {
/** {Array.<AbstractWallet>} */
this.wallets = [];
@ -91,8 +92,12 @@ export class AppStorage {
}
async setResetOnAppUninstallTo(value) {
await this.setItem('deleteWalletAfterUninstall', value === true ? '1' : '');
await RNSecureKeyStore.setResetOnAppUninstallTo(value);
await this.setItem(AppStorage.DELETE_WALLET_AFTER_UNINSTALL, value ? '1' : '');
try {
await RNSecureKeyStore.setResetOnAppUninstallTo(value);
} catch (Error) {
console.warn(Error);
}
}
async storageIsEncrypted() {
@ -110,21 +115,19 @@ export class AppStorage {
try {
let data = await this.getItem('data');
data = this.decryptData(data, password);
if (data !== null && data !== undefined && data !== false) {
return true;
}
return !!data;
} catch (_e) {
return false;
}
return false;
}
/**
* Iterates through all values of `data` trying to
* decrypt each one, and returns first one successfully decrypted
*
* @param data String (serialized array)
* @param data {string} Serialized array
* @param password
* @returns {boolean|string} Either STRING of storage data (which is stringified JSON) or FALSE, which means failure
*/
decryptData(data, password) {
data = JSON.parse(data);
@ -147,8 +150,7 @@ export class AppStorage {
async decryptStorage(password) {
if (password === this.cachedPassword) {
this.cachedPassword = undefined;
await this.setItem(AppStorage.FLAG_ENCRYPTED, '');
await this.setItem('deleteWalletAfterUninstall', '1');
await this.setResetOnAppUninstallTo(true);
await this.saveToDisk();
this.wallets = [];
this.tx_metadata = [];
@ -161,7 +163,7 @@ export class AppStorage {
async isDeleteWalletAfterUninstallEnabled() {
let deleteWalletsAfterUninstall;
try {
deleteWalletsAfterUninstall = await this.getItem('deleteWalletAfterUninstall');
deleteWalletsAfterUninstall = await this.getItem(AppStorage.DELETE_WALLET_AFTER_UNINSTALL);
} catch (_e) {
deleteWalletsAfterUninstall = true;
}
@ -343,8 +345,6 @@ export class AppStorage {
async saveToDisk() {
let walletsToSave = [];
for (let key of this.wallets) {
if (typeof key === 'boolean') continue;
if (typeof key === 'string') key = JSON.parse(key);
if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue;
if (key.prepareForSerialization) key.prepareForSerialization();
walletsToSave.push(JSON.stringify({ ...key, type: key.type }));
@ -506,6 +506,17 @@ export class AppStorage {
return finalBalance;
}
async isAdancedModeEnabled() {
try {
return !!(await this.getItem(AppStorage.ADVANCED_MODE_ENABLED));
} catch (_) {}
return false;
}
async setIsAdancedModeEnabled(value) {
await this.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : '');
}
/**
* Simple async sleeper function
*

4
screen/plausibledeniability.js

@ -4,8 +4,8 @@ import { ScrollView } from 'react-native';
import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueSpacing20 } from '../BlueComponents';
import PropTypes from 'prop-types';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
/** @type {AppStorage} */
let BlueApp = require('../BlueApp');
import { AppStorage } from '../class';
let BlueApp: AppStorage = require('../BlueApp');
let prompt = require('../prompt');
let EV = require('../events');
let loc = require('../loc');

3
screen/settings/encryptStorage.js

@ -12,7 +12,6 @@ import {
BlueText,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
import AsyncStorage from '@react-native-community/async-storage';
import { AppStorage } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import Biometric from '../../class/biometrics';
@ -42,7 +41,7 @@ export default class EncryptStorage extends Component {
const biometricsType = (await Biometric.biometricType()) || 'biometrics';
this.setState({
isLoading: false,
advancedModeEnabled: (await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)) || false,
advancedModeEnabled: await BlueApp.isAdancedModeEnabled(),
storageIsEncrypted: await BlueApp.storageIsEncrypted(),
deleteWalletsAfterUninstall: await BlueApp.isDeleteWalletAfterUninstallEnabled(),
biometrics: { isBiometricsEnabled, isDeviceBiometricCapable, biometricsType },

18
screen/settings/settings.js

@ -9,10 +9,9 @@ import {
BlueHeaderDefaultSub,
BlueListItem,
} from '../../BlueComponents';
import AsyncStorage from '@react-native-community/async-storage';
import { AppStorage } from '../../class';
import { useNavigation } from 'react-navigation-hooks';
const BlueApp = require('../../BlueApp');
const BlueApp: AppStorage = require('../../BlueApp');
const loc = require('../../loc');
const Settings = () => {
@ -23,18 +22,14 @@ const Settings = () => {
useEffect(() => {
(async () => {
setAdvancedModeEnabled(!!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)));
setAdvancedModeEnabled(await BlueApp.isAdancedModeEnabled());
setIsLoading(false);
})();
});
const onAdvancedModeSwitch = async value => {
if (value) {
await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, '1');
} else {
await AsyncStorage.removeItem(AppStorage.ADVANCED_MODE_ENABLED);
}
setAdvancedModeEnabled(value);
await BlueApp.setIsAdancedModeEnabled(value);
};
const onShowAdvancedOptions = () => {
@ -50,12 +45,7 @@ const Settings = () => {
{BlueApp.getWallets().length > 1 && (
<BlueListItem component={TouchableOpacity} onPress={() => navigate('DefaultView')} title="On Launch" />
)}
<BlueListItem
title="Security"
onPress={() => navigate('EncryptStorage')}
component={TouchableOpacity}
testID="EncryptStorageButton"
/>
<BlueListItem title="Security" onPress={() => navigate('EncryptStorage')} component={TouchableOpacity} testID="SecurityButton" />
<BlueListItem title={loc.settings.lightning_settings} component={TouchableOpacity} onPress={() => navigate('LightningSettings')} />
<BlueListItem title={loc.settings.language} component={TouchableOpacity} onPress={() => navigate('Language')} />
<BlueListItem title={loc.settings.currency} component={TouchableOpacity} onPress={() => navigate('Currency')} />

5
screen/wallets/add.js

@ -34,8 +34,7 @@ import { AppStorage, HDSegwitBech32Wallet, SegwitP2SHWallet } from '../../class'
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
let EV = require('../../events');
let A = require('../../analytics');
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let BlueApp: AppStorage = require('../../BlueApp');
let loc = require('../../loc');
export default class WalletsAdd extends Component {
static navigationOptions = ({ navigation }) => ({
@ -54,7 +53,7 @@ export default class WalletsAdd extends Component {
async componentDidMount() {
let walletBaseURI = await AsyncStorage.getItem(AppStorage.LNDHUB);
let isAdvancedOptionsEnabled = !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED));
let isAdvancedOptionsEnabled = await BlueApp.isAdancedModeEnabled();
walletBaseURI = walletBaseURI || '';
this.setState({

3
screen/wallets/walletMigrate.js

@ -1,3 +1,4 @@
import { AppStorage } from '../../class';
import AsyncStorage from '@react-native-community/async-storage';
import RNFS from 'react-native-fs';
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
@ -15,7 +16,7 @@ export default class WalletMigrate {
if (firstLaunch === undefined || firstLaunch === null || firstLaunch === false || firstLaunch === '') {
try {
await RNSecureKeyStore.setResetOnAppUninstallTo(false);
const deleteWalletsFromKeychain = await RNSecureKeyStore.get('deleteWalletAfterUninstall');
const deleteWalletsFromKeychain = await RNSecureKeyStore.get(AppStorage.DELETE_WALLET_AFTER_UNINSTALL);
await RNSecureKeyStore.setResetOnAppUninstallTo(deleteWalletsFromKeychain === '1');
} catch (_e) {}
await AsyncStorage.setItem('RnSksIsAppInstalled', '1');

16
tests/e2e/addWallet.spec.js → tests/e2e/bluewallet.spec.js

@ -25,10 +25,10 @@ describe('BlueWallet UI Tests', () => {
// go to settings
await expect(element(by.id('SettingsButton'))).toBeVisible();
await element(by.id('SettingsButton')).tap();
await expect(element(by.id('EncryptStorageButton'))).toBeVisible();
await expect(element(by.id('SecurityButton'))).toBeVisible();
// go to Security page where we will enable encryption
await element(by.id('EncryptStorageButton')).tap();
await element(by.id('SecurityButton')).tap();
await expect(element(by.id('EncyptedAndPasswordProtected'))).toBeVisible();
await expect(element(by.id('PlausibleDeniabilityButton'))).toBeNotVisible();
@ -79,8 +79,8 @@ describe('BlueWallet UI Tests', () => {
// go to settings -> security screen -> plausible deniability screen
await element(by.id('SettingsButton')).tap();
await expect(element(by.id('EncryptStorageButton'))).toBeVisible();
await element(by.id('EncryptStorageButton')).tap();
await expect(element(by.id('SecurityButton'))).toBeVisible();
await element(by.id('SecurityButton')).tap();
await expect(element(by.id('EncyptedAndPasswordProtected'))).toBeVisible();
await expect(element(by.id('PlausibleDeniabilityButton'))).toBeVisible();
await element(by.id('PlausibleDeniabilityButton')).tap();
@ -155,7 +155,7 @@ describe('BlueWallet UI Tests', () => {
await yo('WalletsList');
await helperCreateWallet();
await element(by.id('SettingsButton')).tap();
await element(by.id('EncryptStorageButton')).tap();
await element(by.id('SecurityButton')).tap();
if (device.getPlatform() === 'ios') {
console.warn('Android only test skipped');
return;
@ -201,7 +201,7 @@ describe('BlueWallet UI Tests', () => {
// now go to settings, and decrypt
await element(by.id('SettingsButton')).tap();
await element(by.id('EncryptStorageButton')).tap();
await element(by.id('SecurityButton')).tap();
// putting FAKE storage password. should not succeed
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol
@ -228,7 +228,7 @@ describe('BlueWallet UI Tests', () => {
await yo('WalletsList');
await helperCreateWallet();
await element(by.id('SettingsButton')).tap();
await element(by.id('EncryptStorageButton')).tap();
await element(by.id('SecurityButton')).tap();
if (device.getPlatform() === 'ios') {
console.warn('Android only test skipped');
return;
@ -273,7 +273,7 @@ describe('BlueWallet UI Tests', () => {
// now go to settings, and decrypt
await element(by.id('SettingsButton')).tap();
await element(by.id('EncryptStorageButton')).tap();
await element(by.id('SecurityButton')).tap();
// putting MAIN storage password. should not succeed
await element(by.type('android.widget.CompoundButton')).tap(); // thats a switch lol

6
tests/setup.js

@ -6,6 +6,12 @@ jest.mock('react-native-watch-connectivity', () => {
}
})
jest.mock('react-native-secure-key-store', () => {
return {
setResetOnAppUninstallTo: jest.fn(),
}
})
jest.mock('react-native-quick-actions', () => {
return {
clearShortcutItems: jest.fn(),

3
tests/unit/Storage.test.js

@ -1,9 +1,8 @@
/* global it, jest */
/* global it */
import { SegwitP2SHWallet, AppStorage } from '../../class';
import AsyncStorage from '@react-native-community/async-storage';
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
let assert = require('assert');
jest.useFakeTimers();
it('Appstorage - loadFromDisk works', async () => {
/** @type {AppStorage} */

Loading…
Cancel
Save