diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index 43e956339..7bc2bdddf 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -10,6 +10,8 @@ using Android.OS; using Android.Provider; using Android.Support.V4.App; using Android.Support.V4.Content; +using Android.Text; +using Android.Text.Method; using Android.Webkit; using Android.Widget; using Bit.App.Abstractions; @@ -218,7 +220,8 @@ namespace Bit.Droid.Services } public Task DisplayPromptAync(string title = null, string description = null, - string text = null, string okButtonText = null, string cancelButtonText = null) + string text = null, string okButtonText = null, string cancelButtonText = null, + bool numericKeyboard = false) { var activity = (MainActivity)CrossCurrentActivity.Current.Activity; if(activity == null) @@ -231,12 +234,19 @@ namespace Bit.Droid.Services alertBuilder.SetMessage(description); var input = new EditText(activity) { - InputType = Android.Text.InputTypes.ClassText + InputType = InputTypes.ClassText }; if(text == null) { text = string.Empty; } + if(numericKeyboard) + { + input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned; +#pragma warning disable CS0618 // Type or member is obsolete + input.KeyListener = DigitsKeyListener.GetInstance(false, false); +#pragma warning restore CS0618 // Type or member is obsolete + } input.Text = text; input.SetSelection(text.Length); diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs index 98def1c1d..3359882e5 100644 --- a/src/App/Abstractions/IDeviceActionService.cs +++ b/src/App/Abstractions/IDeviceActionService.cs @@ -15,7 +15,7 @@ namespace Bit.App.Abstractions Task ClearCacheAsync(); Task SelectFileAsync(); Task DisplayPromptAync(string title = null, string description = null, string text = null, - string okButtonText = null, string cancelButtonText = null); + string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false); void RateApp(); } } \ No newline at end of file diff --git a/src/App/Pages/Accounts/LockPage.xaml b/src/App/Pages/Accounts/LockPage.xaml index f800b5178..38b456ee7 100644 --- a/src/App/Pages/Accounts/LockPage.xaml +++ b/src/App/Pages/Accounts/LockPage.xaml @@ -44,6 +44,7 @@ x:Name="_pin" Text="{Binding Pin}" StyleClass="box-value" + Keyboard="Numeric" IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}" Grid.Row="1" Grid.Column="0" /> diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index c760b66bf..5bae3af41 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -102,7 +102,7 @@ namespace Bit.App.Pages var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin)); failed = decPin != Pin; _lockService.PinLocked = failed; - if(failed) + if(!failed) { DoContinue(); } diff --git a/src/App/Pages/Settings/SettingsPage.xaml.cs b/src/App/Pages/Settings/SettingsPage.xaml.cs index f87fa13ed..b5d3c3d87 100644 --- a/src/App/Pages/Settings/SettingsPage.xaml.cs +++ b/src/App/Pages/Settings/SettingsPage.xaml.cs @@ -98,6 +98,10 @@ namespace Bit.App.Pages { await _vm.LockOptionsAsync(); } + else if(item.Name == AppResources.UnlockWithPIN) + { + await _vm.UpdatePinAsync(); + } } } } diff --git a/src/App/Pages/Settings/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPageViewModel.cs index 34453103d..720ca484f 100644 --- a/src/App/Pages/Settings/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPageViewModel.cs @@ -190,6 +190,48 @@ namespace Bit.App.Pages BuildList(); } + public async Task UpdatePinAsync() + { + _pin = !_pin; + if(_pin) + { + var pin = await _deviceActionService.DisplayPromptAync(AppResources.EnterPIN, + AppResources.SetPINDescription, null, AppResources.Submit, AppResources.Cancel, true); + var masterPassOnRestart = true; + if(!string.IsNullOrWhiteSpace(pin)) + { + if(masterPassOnRestart) + { + var encPin = await _cryptoService.EncryptAsync(pin); + await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString); + } + else + { + var kdf = await _userService.GetKdfAsync(); + var kdfIterations = await _userService.GetKdfIterationsAsync(); + var email = await _userService.GetEmailAsync(); + var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, + kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256), + kdfIterations.GetValueOrDefault(5000)); + var key = await _cryptoService.GetKeyAsync(); + var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); + await _storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString); + } + } + else + { + _pin = false; + } + } + if(!_pin) + { + await _storageService.RemoveAsync(Constants.PinProtectedKey); + await _storageService.RemoveAsync(Constants.ProtectedPin); + } + + BuildList(); + } + private void BuildList() { var doUpper = Device.RuntimePlatform != Device.Android; @@ -202,7 +244,7 @@ namespace Bit.App.Pages { new SettingsPageListItem { Name = AppResources.LockOptions, SubLabel = _lockOptionValue }, new SettingsPageListItem { Name = string.Format(AppResources.UnlockWith, AppResources.Fingerprint) }, - new SettingsPageListItem { Name = AppResources.UnlockWithPIN }, + new SettingsPageListItem { Name = AppResources.UnlockWithPIN, SubLabel = _pin ? "✓" : null }, new SettingsPageListItem { Name = AppResources.LockNow }, new SettingsPageListItem { Name = AppResources.TwoStepLogin } }; diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index f8a1d8119..85087e93b 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -3183,6 +3183,15 @@ namespace Bit.App.Resources { } } + /// + /// Looks up a localized string similar to Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application.. + /// + public static string SetPINDescription { + get { + return ResourceManager.GetString("SetPINDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Enter a 4 digit PIN code to unlock the app with.. /// diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 1ff96a1f0..196bbac01 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -1496,4 +1496,7 @@ 5 minutes + + Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application. + \ No newline at end of file diff --git a/src/iOS/Services/DeviceActionService.cs b/src/iOS/Services/DeviceActionService.cs index 89dcda04b..efa7c052b 100644 --- a/src/iOS/Services/DeviceActionService.cs +++ b/src/iOS/Services/DeviceActionService.cs @@ -173,7 +173,8 @@ namespace Bit.iOS.Services } public Task DisplayPromptAync(string title = null, string description = null, - string text = null, string okButtonText = null, string cancelButtonText = null) + string text = null, string okButtonText = null, string cancelButtonText = null, + bool numericKeyboard = false) { var result = new TaskCompletionSource(); var alert = UIAlertController.Create(title ?? string.Empty, description, UIAlertControllerStyle.Alert); @@ -192,6 +193,10 @@ namespace Bit.iOS.Services { input = x; input.Text = text ?? string.Empty; + if(numericKeyboard) + { + input.KeyboardType = UIKeyboardType.NumberPad; + } }); var vc = GetPresentedViewController(); vc?.PresentViewController(alert, true, null);