Skip to content

Commit

Permalink
Improvement/update keychain lib (#1780)
Browse files Browse the repository at this point in the history
* Update keychain

* Update accessible

* setUserAuthenticationValidityDurationSeconds

* pack

* proguard

* enable biometrics

* settings

* enterpasswordui

* enterpasswordui

* cleanup

* fixbiometrics

* wording

* Fix showing biometrics option when not enabled

* 535

* 1.0.5

* Refactor keychain access

* Fix passcode on android

* bump

* Fix disable biometrics by choice on import from seed

* Fix reset password with biometrics android

* Fallback to password for biometric error

* Dont reset state if reset password failed

* Catch biometric error

* Update comment

* ci

* unit

Co-authored-by: Ibrahim Taveras <[email protected]>
Co-authored-by: Esteban Miño <[email protected]>
  • Loading branch information
3 people authored Nov 6, 2020
1 parent 5baf8de commit 9306c18
Show file tree
Hide file tree
Showing 21 changed files with 823 additions and 380 deletions.
6 changes: 6 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@
# public *;
#}
-dontwarn io.branch.**


# react native keychain https://github.com/oblador/react-native-keychain#proguard-rules
-keep class com.facebook.crypto.** {
*;
}
13 changes: 13 additions & 0 deletions app/components/Nav/Main/MainNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import LockScreen from '../../Views/LockScreen';
import ChoosePasswordSimple from '../../Views/ChoosePasswordSimple';
import EnterPasswordSimple from '../../Views/EnterPasswordSimple';
import ChoosePassword from '../../Views/ChoosePassword';
import ResetPassword from '../../Views/ResetPassword';
import AccountBackupStep1 from '../../Views/AccountBackupStep1';
import AccountBackupStep1B from '../../Views/AccountBackupStep1B';
import ManualBackupStep1 from '../../Views/ManualBackupStep1';
Expand Down Expand Up @@ -162,6 +163,18 @@ export default createStackNavigator(
ChoosePasswordSimple: {
screen: ChoosePasswordSimple
},
ResetPassword: {
screen: ResetPassword
},
ManualBackupStep1: {
screen: ManualBackupStep1
},
ManualBackupStep2: {
screen: ManualBackupStep2
},
ManualBackupStep3: {
screen: ManualBackupStep3
},
EnterPasswordSimple: {
screen: EnterPasswordSimple
}
Expand Down
8 changes: 8 additions & 0 deletions app/components/Nav/Main/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,12 @@ exports[`Main should render correctly 1`] = `
"EnterPasswordSimple": null,
"ExperimentalSettings": null,
"GeneralSettings": null,
"ManualBackupStep1": null,
"ManualBackupStep2": null,
"ManualBackupStep3": null,
"NetworkSettings": null,
"NetworksSettings": null,
"ResetPassword": null,
"RevealPrivateCredentialView": null,
"SecuritySettings": null,
"Settings": null,
Expand Down Expand Up @@ -546,8 +550,12 @@ exports[`Main should render correctly 1`] = `
"EnterPasswordSimple": null,
"ExperimentalSettings": null,
"GeneralSettings": null,
"ManualBackupStep1": null,
"ManualBackupStep2": null,
"ManualBackupStep3": null,
"NetworkSettings": null,
"NetworksSettings": null,
"ResetPassword": null,
"RevealPrivateCredentialView": null,
"SecuritySettings": null,
"Settings": null,
Expand Down
50 changes: 19 additions & 31 deletions app/components/Views/ChoosePassword/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ import zxcvbn from 'zxcvbn';
import Logger from '../../../util/Logger';
import { ONBOARDING, PREVIOUS_SCREEN } from '../../../constants/navigation';
import {
SEED_PHRASE_HINTS,
EXISTING_USER,
NEXT_MAKER_REMINDER,
BIOMETRY_CHOICE,
BIOMETRY_CHOICE_DISABLED,
PASSCODE_DISABLED,
TRUE
TRUE,
SEED_PHRASE_HINTS,
BIOMETRY_CHOICE_DISABLED
} from '../../../constants/storage';
import { getPasswordStrengthWord, passwordRequirementsMet } from '../../../util/password';

Expand Down Expand Up @@ -249,7 +247,7 @@ class ChoosePassword extends PureComponent {
async componentDidMount() {
const biometryType = await SecureKeychain.getSupportedBiometryType();
if (biometryType) {
this.setState({ biometryType, biometryChoice: true });
this.setState({ biometryType: Device.isAndroid() ? 'biometrics' : biometryType, biometryChoice: true });
}
setTimeout(() => {
this.setState({
Expand Down Expand Up @@ -317,29 +315,11 @@ class ChoosePassword extends PureComponent {

// Set state in app as it was with password
if (this.state.biometryType && this.state.biometryChoice) {
const authOptions = {
accessControl: SecureKeychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE
};
await SecureKeychain.setGenericPassword('metamask-user', password, authOptions);
// If the user enables biometrics, we're trying to read the password
// immediately so we get the permission prompt
if (Device.isIos()) {
await SecureKeychain.getGenericPassword();
}
await AsyncStorage.setItem(BIOMETRY_CHOICE, this.state.biometryType);
await AsyncStorage.removeItem(BIOMETRY_CHOICE_DISABLED);
await AsyncStorage.removeItem(PASSCODE_DISABLED);
await SecureKeychain.setGenericPassword(password, SecureKeychain.TYPES.BIOMETRICS);
} else if (this.state.rememberMe) {
await SecureKeychain.setGenericPassword(password, SecureKeychain.TYPES.REMEMBER_ME);
} else {
if (this.state.rememberMe) {
await SecureKeychain.setGenericPassword('metamask-user', password, {
accessControl: SecureKeychain.ACCESS_CONTROL.WHEN_UNLOCKED_THIS_DEVICE_ONLY
});
} else {
await SecureKeychain.resetGenericPassword();
}
await AsyncStorage.removeItem(BIOMETRY_CHOICE);
await AsyncStorage.setItem(BIOMETRY_CHOICE_DISABLED, TRUE);
await AsyncStorage.setItem(PASSCODE_DISABLED, TRUE);
await SecureKeychain.resetGenericPassword();
}
await AsyncStorage.setItem(EXISTING_USER, TRUE);
await AsyncStorage.removeItem(SEED_PHRASE_HINTS);
Expand All @@ -351,8 +331,7 @@ class ChoosePassword extends PureComponent {
} catch (error) {
await this.recreateVault('');
// Set state in app as it was with no password
await SecureKeychain.setGenericPassword('metamask-user', '');
await AsyncStorage.removeItem(BIOMETRY_CHOICE);
await SecureKeychain.resetGenericPassword();
await AsyncStorage.removeItem(NEXT_MAKER_REMINDER);
await AsyncStorage.setItem(EXISTING_USER, TRUE);
await AsyncStorage.removeItem(SEED_PHRASE_HINTS);
Expand Down Expand Up @@ -454,6 +433,15 @@ class ChoosePassword extends PureComponent {
current && current.focus();
};

updateBiometryChoice = async biometryChoice => {
if (!biometryChoice) {
await AsyncStorage.setItem(BIOMETRY_CHOICE_DISABLED, TRUE);
} else {
await AsyncStorage.removeItem(BIOMETRY_CHOICE_DISABLED);
}
this.setState({ biometryChoice });
};

renderSwitch = () => {
const { biometryType, rememberMe, biometryChoice } = this.state;
return (
Expand All @@ -465,7 +453,7 @@ class ChoosePassword extends PureComponent {
</Text>
<View>
<Switch
onValueChange={biometryChoice => this.setState({ biometryChoice })} // eslint-disable-line react/jsx-no-bind
onValueChange={this.updateBiometryChoice} // eslint-disable-line react/jsx-no-bind
value={biometryChoice}
style={styles.biometrySwitch}
trackColor={Device.isIos() ? { true: colors.green300, false: colors.grey300 } : null}
Expand Down
2 changes: 1 addition & 1 deletion app/components/Views/CreateWallet/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class CreateWallet extends PureComponent {
const { KeyringController } = Engine.context;
await Engine.resetState();
await KeyringController.createNewVaultAndKeychain('');
await SecureKeychain.setGenericPassword('metamask-user', '');
await SecureKeychain.setGenericPassword('');
await AsyncStorage.removeItem(BIOMETRY_CHOICE);
await AsyncStorage.removeItem(NEXT_MAKER_REMINDER);
await AsyncStorage.setItem(EXISTING_USER, TRUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exports[`EnterPasswordSimple should render correctly 1`] = `
style={
Object {
"flex": 1,
"padding": 20,
"padding": 16,
}
}
testID="enter-password-screen"
Expand All @@ -35,7 +35,7 @@ exports[`EnterPasswordSimple should render correctly 1`] = `
style={
Object {
"flex": 1,
"padding": 20,
"padding": 16,
}
}
viewIsInsideTabBar={false}
Expand All @@ -51,6 +51,8 @@ exports[`EnterPasswordSimple should render correctly 1`] = `
<Text
style={
Object {
"color": "#000000",
"fontSize": 16,
"marginBottom": 15,
}
}
Expand All @@ -76,8 +78,7 @@ exports[`EnterPasswordSimple should render correctly 1`] = `
<View
style={
Object {
"marginTop": 20,
"paddingHorizontal": 10,
"marginTop": 10,
}
}
>
Expand Down
7 changes: 4 additions & 3 deletions app/components/Views/EnterPasswordSimple/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const styles = StyleSheet.create({
},
wrapper: {
flex: 1,
padding: 20
padding: 16
},
input: {
borderWidth: 2,
Expand All @@ -25,10 +25,11 @@ const styles = StyleSheet.create({
padding: 10
},
ctaWrapper: {
marginTop: 20,
paddingHorizontal: 10
marginTop: 10
},
enterPassword: {
color: colors.black,
fontSize: 16,
marginBottom: 15
}
});
Expand Down
34 changes: 6 additions & 28 deletions app/components/Views/ImportFromSeed/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ import { failedSeedPhraseRequirements } from '../../../util/validators';
import { OutlinedTextField } from 'react-native-material-textfield';
import {
SEED_PHRASE_HINTS,
BIOMETRY_CHOICE,
BIOMETRY_CHOICE_DISABLED,
NEXT_MAKER_REMINDER,
PASSCODE_DISABLED,
ONBOARDING_WIZARD,
EXISTING_USER,
METRICS_OPT_IN,
Expand Down Expand Up @@ -231,7 +229,7 @@ class ImportFromSeed extends PureComponent {
if (previouslyDisabled && previouslyDisabled === TRUE) {
enabled = false;
}
this.setState({ biometryType, biometryChoice: enabled });
this.setState({ biometryType: Device.isAndroid() ? 'biometrics' : biometryType, biometryChoice: enabled });
}
// Workaround https://github.com/facebook/react-native/issues/9958
setTimeout(() => {
Expand Down Expand Up @@ -268,31 +266,11 @@ class ImportFromSeed extends PureComponent {
await KeyringController.createNewVaultAndRestore(this.state.password, this.state.seed);

if (this.state.biometryType && this.state.biometryChoice) {
const authOptions = {
accessControl: SecureKeychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE
};

await SecureKeychain.setGenericPassword('metamask-user', this.state.password, authOptions);

// If the user enables biometrics, we're trying to read the password
// immediately so we get the permission prompt
if (Device.isIos()) {
await SecureKeychain.getGenericPassword();
}
await AsyncStorage.setItem(BIOMETRY_CHOICE, this.state.biometryType);
await AsyncStorage.removeItem(BIOMETRY_CHOICE_DISABLED);
await AsyncStorage.removeItem(PASSCODE_DISABLED);
await SecureKeychain.setGenericPassword(this.state.password, SecureKeychain.TYPES.BIOMETRICS);
} else if (this.state.rememberMe) {
await SecureKeychain.setGenericPassword(this.state.password, SecureKeychain.TYPES.REMEMBER_ME);
} else {
if (this.state.rememberMe) {
await SecureKeychain.setGenericPassword('metamask-user', this.state.password, {
accessControl: SecureKeychain.ACCESS_CONTROL.WHEN_UNLOCKED_THIS_DEVICE_ONLY
});
} else {
await SecureKeychain.resetGenericPassword();
}
await AsyncStorage.removeItem(BIOMETRY_CHOICE);
await AsyncStorage.setItem(BIOMETRY_CHOICE_DISABLED, TRUE);
await AsyncStorage.setItem(PASSCODE_DISABLED, TRUE);
await SecureKeychain.resetGenericPassword();
}
// Get onboarding wizard state
const onboardingWizard = await AsyncStorage.getItem(ONBOARDING_WIZARD);
Expand Down Expand Up @@ -383,7 +361,7 @@ class ImportFromSeed extends PureComponent {
{strings(`biometrics.enable_${this.state.biometryType.toLowerCase()}`)}
</Text>
<Switch
onValueChange={biometryChoice => this.setState({ biometryChoice })} // eslint-disable-line react/jsx-no-bind
onValueChange={this.updateBiometryChoice}
value={this.state.biometryChoice}
style={styles.biometrySwitch}
trackColor={Device.isIos() ? { true: colors.green300, false: colors.grey300 } : null}
Expand Down
26 changes: 5 additions & 21 deletions app/components/Views/ImportWallet/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
EXISTING_USER,
BIOMETRY_CHOICE,
BIOMETRY_CHOICE_DISABLED,
PASSCODE_DISABLED,
NEXT_MAKER_REMINDER,
METRICS_OPT_IN,
TRUE
Expand Down Expand Up @@ -272,8 +271,9 @@ class ImportWallet extends PureComponent {
}

if (password === this.password) {
const biometryType = await SecureKeychain.getSupportedBiometryType();
let biometryType = await SecureKeychain.getSupportedBiometryType();
if (biometryType) {
if (Device.isAndroid()) biometryType = 'biometrics';
this.setState({ biometryType, biometryChoice: true });

Alert.alert(
Expand Down Expand Up @@ -308,32 +308,16 @@ class ImportWallet extends PureComponent {
}
}

cancelledBiometricsPermission = async () => {
await AsyncStorage.removeItem(BIOMETRY_CHOICE);
await AsyncStorage.setItem(BIOMETRY_CHOICE_DISABLED, TRUE);
await AsyncStorage.setItem(PASSCODE_DISABLED, TRUE);
};

finishSync = async opts => {
if (opts.biometrics) {
const authOptions = {
accessControl: SecureKeychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE
};
await SecureKeychain.setGenericPassword('metamask-user', opts.password, authOptions);

// If the user enables biometrics, we're trying to read the password
// immediately so we get the permission prompt
try {
if (Device.isIos()) {
await SecureKeychain.getGenericPassword();
await AsyncStorage.setItem(BIOMETRY_CHOICE, opts.biometryType);
}
await SecureKeychain.setGenericPassword(opts.password, SecureKeychain.TYPES.BIOMETRICS);
} catch (e) {
Logger.error(e, 'User cancelled biometrics permission');
await this.cancelledBiometricsPermission();
SecureKeychain.resetGenericPassword();
}
} else {
await this.cancelledBiometricsPermission();
SecureKeychain.resetGenericPassword();
}

try {
Expand Down
Loading

0 comments on commit 9306c18

Please sign in to comment.