using System; using UIKit; using Foundation; using Bit.iOS.Core.Views; using Bit.App.Resources; using Bit.iOS.Core.Utilities; using Bit.App.Abstractions; using Bit.Core.Abstractions; using Bit.Core.Utilities; using System.Threading.Tasks; using Bit.Core.Models.Domain; using Bit.Core.Enums; namespace Bit.iOS.Core.Controllers { public abstract class LockPasswordViewController : ExtendedUITableViewController { private ILockService _lockService; private ICryptoService _cryptoService; private IDeviceActionService _deviceActionService; private IUserService _userService; private IStorageService _storageService; private IStorageService _secureStorageService; private IPlatformUtilsService _platformUtilsService; private Tuple _pinSet; private bool _hasKey; private bool _pinLock; private bool _fingerprintLock; private int _invalidPinAttempts; public LockPasswordViewController(IntPtr handle) : base(handle) { } public abstract UINavigationItem BaseNavItem { get; } public abstract UIBarButtonItem BaseCancelButton { get; } public abstract UIBarButtonItem BaseSubmitButton { get; } public abstract Action Success { get; } public abstract Action Cancel { get; } public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell( AppResources.MasterPassword); public override void ViewDidLoad() { _lockService = ServiceContainer.Resolve("lockService"); _cryptoService = ServiceContainer.Resolve("cryptoService"); _deviceActionService = ServiceContainer.Resolve("deviceActionService"); _userService = ServiceContainer.Resolve("userService"); _storageService = ServiceContainer.Resolve("storageService"); _secureStorageService = ServiceContainer.Resolve("secureStorageService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _pinSet = _lockService.IsPinLockSetAsync().GetAwaiter().GetResult(); _hasKey = _cryptoService.HasKeyAsync().GetAwaiter().GetResult(); _pinLock = (_pinSet.Item1 && _hasKey) || _pinSet.Item2; _fingerprintLock = _lockService.IsFingerprintLockSetAsync().GetAwaiter().GetResult(); BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword; BaseCancelButton.Title = AppResources.Cancel; BaseSubmitButton.Title = AppResources.Submit; var descriptor = UIFontDescriptor.PreferredBody; MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => { CheckPasswordAsync().GetAwaiter().GetResult(); return true; }; if(_pinLock) { MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; } TableView.RowHeight = UITableView.AutomaticDimension; TableView.EstimatedRowHeight = 70; TableView.Source = new TableSource(this); TableView.AllowsSelection = true; base.ViewDidLoad(); if(_fingerprintLock) { var tasks = Task.Run(async () => { await Task.Delay(500); NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptFingerprintAsync()); }); } } public override void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); if(!_fingerprintLock) { MasterPasswordCell.TextField.BecomeFirstResponder(); } } protected async Task CheckPasswordAsync() { if(string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text)) { var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, _pinLock ? AppResources.PIN : AppResources.MasterPassword), AppResources.Ok); PresentViewController(alert, true, null); return; } var email = await _userService.GetEmailAsync(); var kdf = await _userService.GetKdfAsync(); var kdfIterations = await _userService.GetKdfIterationsAsync(); var inputtedValue = MasterPasswordCell.TextField.Text; if(_pinLock) { var failed = true; try { if(_pinSet.Item1) { var protectedPin = await _storageService.GetAsync(Bit.Core.Constants.ProtectedPin); var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin)); failed = decPin != inputtedValue; _lockService.PinLocked = failed; if(!failed) { DoContinue(); } } else { var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); failed = false; await SetKeyAndContinueAsync(key2); } } catch { failed = true; } if(failed) { _invalidPinAttempts++; if(_invalidPinAttempts >= 5) { Cancel?.Invoke(); return; } InvalidValue(); } } else { var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations); var keyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2); var storedKeyHash = await _cryptoService.GetKeyHashAsync(); if(storedKeyHash == null) { var oldKey = await _secureStorageService.GetAsync("oldKey"); if(key2.KeyB64 == oldKey) { await _secureStorageService.RemoveAsync("oldKey"); await _cryptoService.SetKeyHashAsync(keyHash); storedKeyHash = keyHash; } } if(storedKeyHash != null && keyHash != null && storedKeyHash == keyHash) { await SetKeyAndContinueAsync(key2); } else { InvalidValue(); } } } private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key) { if(!_hasKey) { await _cryptoService.SetKeyAsync(key); } DoContinue(); } private void DoContinue() { MasterPasswordCell.TextField.ResignFirstResponder(); Success(); } public async Task PromptFingerprintAsync() { if(!_fingerprintLock) { return; } var success = await _platformUtilsService.AuthenticateFingerprintAsync(null, _pinLock ? AppResources.PIN : AppResources.MasterPassword, () => MasterPasswordCell.TextField.BecomeFirstResponder()); _lockService.FingerprintLocked = !success; if(success) { DoContinue(); } } private void InvalidValue() { var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword), AppResources.Ok, (a) => { MasterPasswordCell.TextField.Text = string.Empty; MasterPasswordCell.TextField.BecomeFirstResponder(); }); PresentViewController(alert, true, null); } public class TableSource : ExtendedUITableViewSource { private LockPasswordViewController _controller; public TableSource(LockPasswordViewController controller) { _controller = controller; } public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { if(indexPath.Section == 0) { if(indexPath.Row == 0) { return _controller.MasterPasswordCell; } } else if(indexPath.Section == 1) { if(indexPath.Row == 0) { var fingerprintButtonText = _controller._deviceActionService.SupportsFaceId() ? AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock; var cell = new ExtendedUITableViewCell(); cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor; cell.TextLabel.Text = fingerprintButtonText; return cell; } } return new ExtendedUITableViewCell(); } public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath) { return UITableView.AutomaticDimension; } public override nint NumberOfSections(UITableView tableView) { return _controller._fingerprintLock ? 2 : 1; } public override nint RowsInSection(UITableView tableview, nint section) { if(section <= 1) { return 1; } return 0; } public override nfloat GetHeightForHeader(UITableView tableView, nint section) { return section == 1 ? 0.00001f : UITableView.AutomaticDimension; } public override string TitleForHeader(UITableView tableView, nint section) { return null; } public override void RowSelected(UITableView tableView, NSIndexPath indexPath) { tableView.DeselectRow(indexPath, true); tableView.EndEditing(true); if(indexPath.Section == 1 && indexPath.Row == 0) { var task = _controller.PromptFingerprintAsync(); return; } var cell = tableView.CellAt(indexPath); if(cell == null) { return; } if(cell is ISelectable selectableCell) { selectableCell.Select(); } } } } }