mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
Trusted Device Encryption feature (#2656)
* [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added. * [PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account. * [PM-1208] Add continue button and not you option * [PM-1379] add DeviceTrustCryptoService with establish trust logic (#2535) * [PM-1379] add DeviceCryptoService with establish trust logic * PM-1379 update api location and other minor refactors * pm-1379 fix encoding * update trusted device keys api call to Put * [PM-1379] rename DeviceCryptoService to DeviceTrustCryptoService - refactors to prevent side effects * [PM-1379] rearrange methods in DeviceTrustCryptoService * [PM-1379] rearrange methods in abstraction * [PM-1379] deconstruct tuples * [PM-1379] remove extra tasks * [PM-2583] Answer auth request with mp field as null if doesn't have it. (#2609) * [PM-2287][PM-2289][PM-2293] Approval Options (#2608) * [PM-2293] Add AuthRequestType to PasswordlessLoginPage. * [PM-2293] Add Actions to ApproveWithDevicePage * [PM-2293] Change screen text based on AuthRequestType * [PM-2293] Refactor AuthRequestType enum. Add label. Remove unnecessary actions. * [PM-2293] Change boolean variable expression. * [PM-2293] Trust device after admin request login. * code format * [PM-2287] Add trust device to master password unlock. Change trust device method. Remove email from SSO login page. * [PM-2293] Fix state variable get set. * [PM-2287][PM-2289][PM-2293] Rename method * [PM-1201] Change timeout actions available based on hasMasterPassword (#2610) * [PM-1201] Change timeout actions available based on hasMasterPassword * [PM-2731] add user key and master key types * [PM-2713] add new state for new keys and obsolete old ones - UserKey - MasterKey - UserKeyMasterKey (enc UserKey from User Table) * [PM-271] add UserKey and MasterKey support to crypto service * [PM-2713] rename key hash to password hash & begin add methods to crypto service * [PM-2713] continue organizing crypto service * [PM-2713] more updates to crypto service * [PM-2713] add new pin methods to state service * [PM-2713] fix signature of GetUserKeyPin * [PM-2713] add make user key method to crypto service * [PM-2713] refresh pin key when setting user key * [PM-2713] use new MakeMasterKey method * [PM-2713] add toggle method to crypto service for keys * [PM-2713] converting calls to new crypto service api * [PM-2713] add migration for pin on lock screens * [PM-2713] more conversions to new crypto service api * [PM-2713] convert cipher service and others to crypto service api * [PM-2713] More conversions to crypto api * [PM-2713] use new crypto service api in auth service * [PM-2713] remove unused cached values in crypto service * [PM-2713] set decrypt and set user key in login helper * fix bad merge * Update crypto service api call to fix build * [PM-1208] Fix app resource file * [PM-1208] Fix merge * [PM-1208] Fix merge * [PM-2713] optimize async code in crypto service * [PM-2713] rename password hash to master key hash * [PM-2713] fix casting issues and pin * [PM-2713] remove extra comment * [PM-2713] remove broken casting * [PM-2297] Login with trusted device (Flow 2) (#2623) * [PM-2297] Add DecryptUserKeyWithDeviceKey method * [PM-2297] Add methods to DeviceTrustCryptoService update decryption options model * [PM-2297] Update account decryption options model * [PM-2297] Fix TrustedDeviceOption and DeviceResponse model. Change StateService device key get set to have default user id * [PM-2297] Update navigation to decryption options * [PM-2297] Add missing action navigations to iOS extensions * [PM-2297] Fix trust device bug/typo * [PM-2297] Fix model bug * [PM-2297] Fix state var crash * [PM-2297] Add trust device login logic to auth service * [PM-2297] Refactor auth service key connector code * [PM-2297] Remove reconciledOptions for deviceKey in state service * [PM-2297] Remove unnecessary user id params * [PM-2289] [PM-2293] TDE Login with device Admin Request (#2642) * [PM-2713] deconstruct new key pair * [PM-2713] rename PrivateKey methods to UserPrivateKey on crypto service * [PM-2713] rename PinLockEnum to PinLockType * [PM-2713] don't pass user key as param when encrypting * [PM-2713] rename toggle method, don't reset enc user key * [PM-2713] pr feedback * [PM-2713] PR feedback * [PM-2713] rename get pin lock type method * [PM-2713] revert feedback for build * [PM-2713] rename state methods * [PM-2713] combine makeDataEncKey methods * [PM-2713] consolidate attachment key creation - also fix ios files missed during symbol rename * [PM-2713] replace generic with inherited class * rename account keys to be more descriptive * [PM-2713] add auto unlock key to mobile * [PM-1208] Add TDE flows for new users (#2655) * [PM-1208] Create new user on SSO. Logout if not password is setup or has pending admin auth request. * [PM-1208] Fix new user UserKey decryption. * [PM-1208] Add new user continue to vault logic. Auto enrol user on continue. * [PM-1208] Trust device only if needed * [PM-1208] Add logic for New User SSO. * [PM-1208] Add logic for New User SSO (missing file). * [PM-2713] set user key on set password page * [PM-2713] set enc user key during kc onboarding * fix formatting * [PM-2713] make method async again - returning null from a task thats not async throws * [PM-2713] clear service cache when adding new account * Fix build after merge * [PM-3313] Fix Android SSO Login (#2663) * [PM-3313] Catch exception on AuthPendingRequest * [PM-3313] Fix lock timeout action if user doesn't have a master password. * code format * [PM-3313] Null email in Approval Options screen (#2664) * [PM-3313] Fix null email in approval options screen * [PM-3320][PM-3321] Fix labels and UI tweaks (#2666) * [PM-3320] Fix UI copy and remember me default ON. * [PM-3321] Fix UI on Log in with device screen. * [PM-3337] Fix admin request deny error (#2669) * [PM-3342] Not you button logs user out. (#2672) * [PM-3319] Check for admin request in Lock page (#2668) * [PM-3319] Ignore admin auth request when choosing mp as decryption option. * [PM-2289] Change header title based on auth request type (#2670) * [PM-2289] Change header title based on auth request type * [PM-3333] Check for purged admin auth requests (#2671) * [PM-3333] Check for purged admin auth requests Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> --------- Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> * [PM-3341] Vault Timeout Action not persisted correctly (#2673) * [PM-3341] Fix timeout action change when navigating * [PM-3357] Fix copy for Login Initiated (#2674) * [PM-3362] Fix auth request approval (#2675) * [PM-3362] Fix auth request approval * [PM-3362] Add new exception type * [PM-3102] Update Master password reprompt to be based on MP instead of Key Connector (#2653) * PM-3102 Added check to see if a user has master password set replacing previous usage of key connector. * PM-3102 Fix formatting * [PM-2713] Final merge from Key Migration branch to TDE Feature branch (#2667) * [PM-2713] add async to key connector service methods * [PM-2713] rename ephemeral pin key * add state for biometric key and accept UserKey instead of string for auto key * Get UserKey from bio state on unlock * PM-2713 Fix auto-migrating EncKeyEncrypted into MasterKey encrypted UserKey when requesting DecryptUserKeyWithMasterKeyAsync is called * renaming bio key and fix build * PM-3194 Fix biometrics button to be shown on upgrade when no UserKey is present yet * revert removal of key connector service from auth service * PM-2713 set user key when using KC * clear enc user key after migration * use is true for nullable bool * PR feedback, refactor kc service --------- Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> * Fix app fresh install user login with master password. (#2676) * [PM-3303] Fix biometric login after key migration (#2679) * [PM-3303] Add condition to biometric unlock * [PM-3381] Fix TDE login 2FA flow (#2678) * [PM-3381] Check for vault lock on 2FA screen * [PM-3381] Move logic to ViewModel * [PM-3381] Fix null vm error * [PM-3379] Fix key rotation on trusted device. (#2680) * [PM-3381] Update login flows (#2683) * [PM-3381] Update login flows * [PM-3381] Remove _authingWithSso parameter * PM-3385 Fix MP reprompt item level when no MP hash is stored like logging in with TDE. Also refactor code to be more maintainable (#2687) * PM-3386 Fix MP reprompt / OTP decision to be also based on the master key hash. (#2688) * PM-3450 Fix has master password with mp key hash check (#2689) * [PM-3394] Fix login with device for passwordless approvals (#2686) * set activeUserId to null when logging in a new account - Also stop the user key from being set in inactive accounts * get token for login with device if approving device doesn't have master key * add comment * simplify logic * check for route instead of using isAuthenticated - we don't clear the user id when logging in new account - this means we can't trust the state service, so we have to base our logic off the route in login with device * use authenticated auth request for tde login with device * [PM-3394] Add authingWithSso parameter to LoginPasswordlessRequestPage. * pr feedback * [PM-3394] Refactor condition Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> --------- Co-authored-by: André Bispo <abispo@bitwarden.com> Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> * [PM-3462] Handle force password reset on mobile with TDE (#2694) * [PM-3462] Handle force password reset on mobile with TDE * [PM-3462] update references to refactored crypto method - fix kc bug, we were sending private key instead of user key to server - rename kc service method to be correct * [PM-3462] Update TwoFactorPage login logic * [PM-3462] Added pending admin request check to TwoFactorPage * [PM-3462] Added new exception types for null keys --------- Co-authored-by: André Bispo <abispo@bitwarden.com> * [PM-1029] Fix Async suffix in ApiService. Add UserKeyNullExceptions. * [PM 3513] Fix passwordless 2fa login with device on mobile (#2700) * [PM-3513] Fix 2FA for normal login with device with users without mp * move _userKey --------- Co-authored-by: André Bispo <abispo@bitwarden.com> * clear encrypted pin on logout (#2699) --------- Co-authored-by: André Bispo <abispo@bitwarden.com> Co-authored-by: Jake Fink <jfink@bitwarden.com> Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
This commit is contained in:
parent
a23454bc53
commit
bfcfd367dd
90 changed files with 3348 additions and 1365 deletions
|
@ -68,9 +68,9 @@ namespace Bit.Droid
|
|||
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
|
||||
|
||||
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
|
||||
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
|
||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
|
||||
ServiceContainer.Resolve<IUserVerificationService>());
|
||||
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||
|
||||
var accountsManager = new AccountsManager(
|
||||
|
@ -156,9 +156,9 @@ namespace Bit.Droid
|
|||
messagingService, broadcasterService);
|
||||
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
||||
platformUtilsService, new LazyResolve<IEventService>());
|
||||
var biometricService = new BiometricService(stateService);
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
var biometricService = new BiometricService(stateService, cryptoService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||
|
||||
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using Android.OS;
|
||||
using Android.Security.Keystore;
|
||||
using Bit.App.Services;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Java.Security;
|
||||
|
@ -9,10 +10,8 @@ using Javax.Crypto;
|
|||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class BiometricService : IBiometricService
|
||||
public class BiometricService : BaseBiometricService
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
|
||||
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
|
||||
|
||||
private const string KeyStoreName = "AndroidKeyStore";
|
||||
|
@ -24,14 +23,14 @@ namespace Bit.Droid.Services
|
|||
|
||||
private readonly KeyStore _keystore;
|
||||
|
||||
public BiometricService(IStateService stateService)
|
||||
public BiometricService(IStateService stateService, ICryptoService cryptoService)
|
||||
: base(stateService, cryptoService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_keystore = KeyStore.GetInstance(KeyStoreName);
|
||||
_keystore.Load(null);
|
||||
}
|
||||
|
||||
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||
public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||
{
|
||||
|
@ -41,7 +40,7 @@ namespace Bit.Droid.Services
|
|||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
|
@ -6,10 +7,8 @@ namespace Bit.App.Abstractions
|
|||
{
|
||||
string[] ProtectedFields { get; }
|
||||
|
||||
Task<bool> ShowPasswordPromptAsync();
|
||||
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
|
||||
|
||||
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
||||
|
||||
Task<bool> Enabled();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<StackLayout StyleClass="box">
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
IsVisible="{Binding PinLock}"
|
||||
IsVisible="{Binding PinEnabled}"
|
||||
Padding="0, 10, 0, 0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
@ -89,7 +89,7 @@
|
|||
<Grid
|
||||
x:Name="_passwordGrid"
|
||||
StyleClass="box-row"
|
||||
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
|
||||
IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
|
||||
Padding="0, 10, 0, 0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
|
|
@ -20,13 +20,14 @@ namespace Bit.App.Pages
|
|||
private bool _promptedAfterResume;
|
||||
private bool _appeared;
|
||||
|
||||
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true)
|
||||
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true, bool checkPendingAuthRequests = true)
|
||||
{
|
||||
_appOptions = appOptions;
|
||||
_autoPromptBiometric = autoPromptBiometric;
|
||||
InitializeComponent();
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
|
||||
_vm = BindingContext as LockPageViewModel;
|
||||
_vm.CheckPendingAuthRequests = checkPendingAuthRequests;
|
||||
_vm.Page = this;
|
||||
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
||||
|
||||
|
@ -44,7 +45,7 @@ namespace Bit.App.Pages
|
|||
{
|
||||
get
|
||||
{
|
||||
if (_vm?.PinLock ?? false)
|
||||
if (_vm?.PinEnabled ?? false)
|
||||
{
|
||||
return _pin;
|
||||
}
|
||||
|
@ -54,7 +55,7 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task PromptBiometricAfterResumeAsync()
|
||||
{
|
||||
if (_vm.BiometricLock)
|
||||
if (_vm.BiometricEnabled)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
if (!_promptedAfterResume)
|
||||
|
@ -91,13 +92,13 @@ namespace Bit.App.Pages
|
|||
|
||||
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
||||
|
||||
if (!_vm.BiometricLock)
|
||||
if (!_vm.BiometricEnabled)
|
||||
{
|
||||
RequestFocus(SecretEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_vm.UsingKeyConnector && !_vm.PinLock)
|
||||
if (!_vm.HasMasterPassword && !_vm.PinEnabled)
|
||||
{
|
||||
_passwordGrid.IsVisible = false;
|
||||
_unlockButton.IsVisible = false;
|
||||
|
|
|
@ -27,27 +27,27 @@ namespace Bit.App.Pages
|
|||
private readonly IEnvironmentService _environmentService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IBiometricService _biometricService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IWatchDeviceService _watchDeviceService;
|
||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
|
||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private readonly ISyncService _syncService;
|
||||
private string _email;
|
||||
private string _masterPassword;
|
||||
private string _pin;
|
||||
private bool _showPassword;
|
||||
private bool _pinLock;
|
||||
private bool _biometricLock;
|
||||
private PinLockType _pinStatus;
|
||||
private bool _pinEnabled;
|
||||
private bool _biometricEnabled;
|
||||
private bool _biometricIntegrityValid = true;
|
||||
private bool _biometricButtonVisible;
|
||||
private bool _usingKeyConnector;
|
||||
private bool _hasMasterPassword;
|
||||
private string _biometricButtonText;
|
||||
private string _loggedInAsText;
|
||||
private string _lockedVerifyText;
|
||||
private bool _isPinProtected;
|
||||
private bool _isPinProtectedWithKey;
|
||||
|
||||
public LockPageViewModel()
|
||||
{
|
||||
|
@ -60,11 +60,13 @@ namespace Bit.App.Pages
|
|||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>();
|
||||
|
||||
PageTitle = AppResources.VerifyMasterPassword;
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
|
@ -100,21 +102,21 @@ namespace Bit.App.Pages
|
|||
});
|
||||
}
|
||||
|
||||
public bool PinLock
|
||||
public bool PinEnabled
|
||||
{
|
||||
get => _pinLock;
|
||||
set => SetProperty(ref _pinLock, value);
|
||||
get => _pinEnabled;
|
||||
set => SetProperty(ref _pinEnabled, value);
|
||||
}
|
||||
|
||||
public bool UsingKeyConnector
|
||||
public bool HasMasterPassword
|
||||
{
|
||||
get => _usingKeyConnector;
|
||||
get => _hasMasterPassword;
|
||||
}
|
||||
|
||||
public bool BiometricLock
|
||||
public bool BiometricEnabled
|
||||
{
|
||||
get => _biometricLock;
|
||||
set => SetProperty(ref _biometricLock, value);
|
||||
get => _biometricEnabled;
|
||||
set => SetProperty(ref _biometricEnabled, value);
|
||||
}
|
||||
|
||||
public bool BiometricIntegrityValid
|
||||
|
@ -147,6 +149,8 @@ namespace Bit.App.Pages
|
|||
set => SetProperty(ref _lockedVerifyText, value);
|
||||
}
|
||||
|
||||
public bool CheckPendingAuthRequests { get; set; }
|
||||
|
||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||
|
||||
public Command SubmitCommand { get; }
|
||||
|
@ -162,18 +166,32 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||
_isPinProtectedWithKey;
|
||||
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
|
||||
|
||||
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||
if (_usingKeyConnector && !(BiometricLock || PinLock))
|
||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
||||
if (pendingRequest != null && CheckPendingAuthRequests)
|
||||
{
|
||||
await _vaultTimeoutService.LogOutAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
|
||||
|
||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
||||
?? await _stateService.GetPinProtectedKeyAsync();
|
||||
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||
_pinStatus == PinLockType.Persistent;
|
||||
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync();
|
||||
|
||||
// Users without MP and without biometric or pin has no MP to unlock with
|
||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
||||
if (await _stateService.IsAuthenticatedAsync()
|
||||
&& !_hasMasterPassword
|
||||
&& !BiometricEnabled
|
||||
&& !PinEnabled)
|
||||
{
|
||||
await _vaultTimeoutService.LogOutAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_email = await _stateService.GetEmailAsync();
|
||||
if (string.IsNullOrWhiteSpace(_email))
|
||||
{
|
||||
|
@ -188,26 +206,18 @@ namespace Bit.App.Pages
|
|||
}
|
||||
var webVaultHostname = CoreHelpers.GetHostname(webVault);
|
||||
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
|
||||
if (PinLock)
|
||||
if (PinEnabled)
|
||||
{
|
||||
PageTitle = AppResources.VerifyPIN;
|
||||
LockedVerifyText = AppResources.VaultLockedPIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_usingKeyConnector)
|
||||
{
|
||||
PageTitle = AppResources.UnlockVault;
|
||||
LockedVerifyText = AppResources.VaultLockedIdentity;
|
||||
}
|
||||
else
|
||||
{
|
||||
PageTitle = AppResources.VerifyMasterPassword;
|
||||
LockedVerifyText = AppResources.VaultLockedMasterPassword;
|
||||
}
|
||||
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
|
||||
LockedVerifyText = _hasMasterPassword ? AppResources.VaultLockedMasterPassword : AppResources.VaultLockedIdentity;
|
||||
}
|
||||
|
||||
if (BiometricLock)
|
||||
if (BiometricEnabled)
|
||||
{
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
if (!_biometricIntegrityValid)
|
||||
|
@ -229,14 +239,14 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task SubmitAsync()
|
||||
{
|
||||
if (PinLock && string.IsNullOrWhiteSpace(Pin))
|
||||
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
|
||||
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||
|
@ -247,34 +257,54 @@ namespace Bit.App.Pages
|
|||
ShowPassword = false;
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
|
||||
if (PinLock)
|
||||
if (PinEnabled)
|
||||
{
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
if (_isPinProtected)
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (_pinStatus == PinLockType.Persistent)
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (_pinStatus == PinLockType.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockType.Transient,
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
await _stateService.GetPinProtectedKeyAsync());
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
failed = decPin != Pin;
|
||||
if (!failed)
|
||||
{
|
||||
Pin = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
}
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
|
||||
failed = false;
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
failed = decryptedPin != Pin;
|
||||
if (!failed)
|
||||
{
|
||||
Pin = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
@ -295,19 +325,21 @@ namespace Bit.App.Pages
|
|||
}
|
||||
else
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
|
||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
||||
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||
var passwordValid = false;
|
||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||
|
||||
if (storedKeyHash != null)
|
||||
{
|
||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
|
||||
// Offline unlock possible
|
||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Online unlock required
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
|
||||
var request = new PasswordVerificationRequest();
|
||||
request.MasterPasswordHash = keyHash;
|
||||
|
||||
|
@ -316,8 +348,8 @@ namespace Bit.App.Pages
|
|||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||
passwordValid = true;
|
||||
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
|
||||
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -327,15 +359,6 @@ namespace Bit.App.Pages
|
|||
}
|
||||
if (passwordValid)
|
||||
{
|
||||
if (_isPinProtected)
|
||||
{
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
|
||||
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
|
||||
}
|
||||
|
||||
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
||||
{
|
||||
// Save the ForcePasswordResetReason to force a password reset after unlock
|
||||
|
@ -345,10 +368,13 @@ namespace Bit.App.Pages
|
|||
|
||||
MasterPassword = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
|
||||
// Re-enable biometrics
|
||||
if (BiometricLock & !BiometricIntegrityValid)
|
||||
if (BiometricEnabled & !BiometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync();
|
||||
}
|
||||
|
@ -425,7 +451,7 @@ namespace Bit.App.Pages
|
|||
public void TogglePassword()
|
||||
{
|
||||
ShowPassword = !ShowPassword;
|
||||
var secret = PinLock ? Pin : MasterPassword;
|
||||
var secret = PinEnabled ? Pin : MasterPassword;
|
||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||
}
|
||||
|
||||
|
@ -433,32 +459,35 @@ namespace Bit.App.Pages
|
|||
{
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
BiometricButtonVisible = BiometricIntegrityValid;
|
||||
if (!BiometricLock || !BiometricIntegrityValid)
|
||||
if (!BiometricEnabled || !BiometricIntegrityValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
PinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
{
|
||||
await DoContinueAsync();
|
||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
||||
await SetUserKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
|
||||
private async Task SetUserKeyAndContinueAsync(UserKey key)
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
await _cryptoService.SetUserKeyAsync(key);
|
||||
}
|
||||
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
|
||||
await DoContinueAsync();
|
||||
}
|
||||
|
||||
private async Task DoContinueAsync()
|
||||
{
|
||||
_syncService.FullSyncAsync(false).FireAndForget();
|
||||
await _stateService.SetBiometricLockedAsync(false);
|
||||
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
|
||||
_messagingService.Send("unlocked");
|
||||
|
|
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.LoginApproveDevicePage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="pages:LoginApproveDeviceViewModel"
|
||||
x:Name="_page"
|
||||
Title="{Binding PageTitle}">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
<pages:LoginApproveDeviceViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<StackLayout Padding="10, 10">
|
||||
<StackLayout Padding="5, 10" Orientation="Horizontal">
|
||||
<StackLayout HorizontalOptions="FillAndExpand">
|
||||
<Label
|
||||
StyleClass="text-md"
|
||||
Text="{u:I18n RememberThisDevice}"/>
|
||||
<Label
|
||||
StyleClass="box-sub-label"
|
||||
Text="{u:I18n TurnOffUsingPublicDevice}"/>
|
||||
</StackLayout>
|
||||
<Switch
|
||||
Scale="0.8"
|
||||
IsToggled="{Binding RememberThisDevice}"
|
||||
VerticalOptions="Center"/>
|
||||
</StackLayout>
|
||||
<StackLayout Margin="0, 20, 0, 0">
|
||||
<Button
|
||||
x:Name="_continue"
|
||||
Text="{u:I18n Continue}"
|
||||
StyleClass="btn-primary"
|
||||
Command="{Binding ContinueCommand}"
|
||||
IsVisible="{Binding IsNewUser}"/>
|
||||
<Button
|
||||
x:Name="_approveWithMyOtherDevice"
|
||||
Text="{u:I18n ApproveWithMyOtherDevice}"
|
||||
StyleClass="btn-primary"
|
||||
Command="{Binding ApproveWithMyOtherDeviceCommand}"
|
||||
IsVisible="{Binding ApproveWithMyOtherDeviceEnabled}"/>
|
||||
<Button
|
||||
x:Name="_requestAdminApproval"
|
||||
Text="{u:I18n RequestAdminApproval}"
|
||||
StyleClass="box-button-row"
|
||||
Command="{Binding RequestAdminApprovalCommand}"
|
||||
IsVisible="{Binding RequestAdminApprovalEnabled}"/>
|
||||
<Button
|
||||
x:Name="_approveWithMasterPassword"
|
||||
Text="{u:I18n ApproveWithMasterPassword}"
|
||||
StyleClass="box-button-row"
|
||||
Command="{Binding ApproveWithMasterPasswordCommand}"
|
||||
IsVisible="{Binding ApproveWithMasterPasswordEnabled}"/>
|
||||
<Label
|
||||
Text="{Binding LoggingInAsText}"
|
||||
StyleClass="text-sm"
|
||||
Margin="0,40,0,0"
|
||||
AutomationId="LoggingInAsLabel"
|
||||
/>
|
||||
<Label
|
||||
Text="{u:I18n NotYou}"
|
||||
StyleClass="text-md"
|
||||
HorizontalOptions="Start"
|
||||
TextColor="{DynamicResource HyperlinkColor}"
|
||||
AutomationId="NotYouLabel">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding LogoutCommand}" />
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</pages:BaseContentPage>
|
||||
|
||||
|
64
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
64
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class LoginApproveDevicePage : BaseContentPage
|
||||
{
|
||||
|
||||
private readonly LoginApproveDeviceViewModel _vm;
|
||||
private readonly AppOptions _appOptions;
|
||||
|
||||
public LoginApproveDevicePage(AppOptions appOptions = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as LoginApproveDeviceViewModel;
|
||||
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPasswordAsync().FireAndForget();
|
||||
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
|
||||
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
|
||||
_vm.ContinueToVaultAction = () => ContinueToVaultAsync().FireAndForget();
|
||||
_vm.Page = this;
|
||||
_appOptions = appOptions;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
_vm.InitAsync();
|
||||
}
|
||||
|
||||
private async Task ContinueToVaultAsync()
|
||||
{
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
|
||||
private async Task StartLogInWithMasterPasswordAsync()
|
||||
{
|
||||
var page = new LockPage(_appOptions, checkPendingAuthRequests: false);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async Task StartLoginWithDeviceAsync()
|
||||
{
|
||||
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions, true);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async Task RequestAdminApprovalAsync()
|
||||
{
|
||||
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions, true);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
150
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
150
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
|
@ -0,0 +1,150 @@
|
|||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class LoginApproveDeviceViewModel : BaseViewModel
|
||||
{
|
||||
private bool _rememberThisDevice;
|
||||
private bool _approveWithMyOtherDeviceEnabled;
|
||||
private bool _requestAdminApprovalEnabled;
|
||||
private bool _approveWithMasterPasswordEnabled;
|
||||
private string _email;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IApiService _apiService;
|
||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
|
||||
public ICommand ApproveWithMyOtherDeviceCommand { get; }
|
||||
public ICommand RequestAdminApprovalCommand { get; }
|
||||
public ICommand ApproveWithMasterPasswordCommand { get; }
|
||||
public ICommand ContinueCommand { get; }
|
||||
public ICommand LogoutCommand { get; }
|
||||
|
||||
public Action LogInWithMasterPasswordAction { get; set; }
|
||||
public Action LogInWithDeviceAction { get; set; }
|
||||
public Action RequestAdminApprovalAction { get; set; }
|
||||
public Action ContinueToVaultAction { get; set; }
|
||||
|
||||
public LoginApproveDeviceViewModel()
|
||||
{
|
||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||
_apiService = ServiceContainer.Resolve<IApiService>();
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
_authService = ServiceContainer.Resolve<IAuthService>();
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>();
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>();
|
||||
|
||||
PageTitle = AppResources.LogInInitiated;
|
||||
RememberThisDevice = true;
|
||||
|
||||
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync,
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
LogoutCommand = new Command(() => _messagingService.Send(AccountsManagerMessageCommands.LOGOUT));
|
||||
}
|
||||
|
||||
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
|
||||
|
||||
public bool RememberThisDevice
|
||||
{
|
||||
get => _rememberThisDevice;
|
||||
set => SetProperty(ref _rememberThisDevice, value);
|
||||
}
|
||||
|
||||
public bool ApproveWithMyOtherDeviceEnabled
|
||||
{
|
||||
get => _approveWithMyOtherDeviceEnabled;
|
||||
set => SetProperty(ref _approveWithMyOtherDeviceEnabled, value);
|
||||
}
|
||||
|
||||
public bool RequestAdminApprovalEnabled
|
||||
{
|
||||
get => _requestAdminApprovalEnabled;
|
||||
set => SetProperty(ref _requestAdminApprovalEnabled, value,
|
||||
additionalPropertyNames: new[] { nameof(IsNewUser) });
|
||||
}
|
||||
|
||||
public bool ApproveWithMasterPasswordEnabled
|
||||
{
|
||||
get => _approveWithMasterPasswordEnabled;
|
||||
set => SetProperty(ref _approveWithMasterPasswordEnabled, value,
|
||||
additionalPropertyNames: new[] { nameof(IsNewUser) });
|
||||
}
|
||||
|
||||
public bool IsNewUser => !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled;
|
||||
|
||||
public string Email
|
||||
{
|
||||
get => _email;
|
||||
set => SetProperty(ref _email, value, additionalPropertyNames:
|
||||
new string[] {
|
||||
nameof(LoggingInAsText)
|
||||
});
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Email = await _stateService.GetActiveUserEmailAsync();
|
||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
|
||||
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
|
||||
ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateNewSsoUserAsync()
|
||||
{
|
||||
await _authService.CreateNewSsoUserAsync(await _stateService.GetRememberedOrgIdentifierAsync());
|
||||
if (RememberThisDevice)
|
||||
{
|
||||
await _deviceTrustCryptoService.TrustDeviceAsync();
|
||||
}
|
||||
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
await Device.InvokeOnMainThreadAsync(ContinueToVaultAction);
|
||||
}
|
||||
|
||||
private async Task SetDeviceTrustAndInvokeAsync(Action action)
|
||||
{
|
||||
await _deviceTrustCryptoService.SetShouldTrustDeviceAsync(RememberThisDevice);
|
||||
await Device.InvokeOnMainThreadAsync(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ using Bit.App.Models;
|
|||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
|
@ -135,7 +136,7 @@ namespace Bit.App.Pages
|
|||
|
||||
private async Task StartLoginWithDeviceAsync()
|
||||
{
|
||||
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
|
||||
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
|
|
|
@ -21,17 +21,17 @@
|
|||
<StackLayout
|
||||
Padding="7, 0, 7, 20">
|
||||
<Label
|
||||
Text="{u:I18n LogInInitiated}"
|
||||
Text="{Binding Title}"
|
||||
FontSize="Title"
|
||||
FontAttributes="Bold"
|
||||
Margin="0,14,0,21"
|
||||
AutomationId="LogInInitiatedLabel" />
|
||||
<Label
|
||||
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
|
||||
Text="{Binding SubTitle}"
|
||||
FontSize="Small"
|
||||
Margin="0,0,0,10"/>
|
||||
<Label
|
||||
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
|
||||
Text="{Binding Description}"
|
||||
FontSize="Small"
|
||||
Margin="0,0,0,24"/>
|
||||
<Label
|
||||
|
@ -40,41 +40,39 @@
|
|||
FontAttributes="Bold"/>
|
||||
<controls:MonoLabel
|
||||
FormattedText="{Binding FingerprintPhrase}"
|
||||
FontSize="Medium"
|
||||
FontSize="Small"
|
||||
TextColor="{DynamicResource FingerprintPhrase}"
|
||||
AutomationId="FingerprintPhraseValue" />
|
||||
<Label
|
||||
Text="{u:I18n ResendNotification}"
|
||||
StyleClass="text-md"
|
||||
IsVisible="{Binding ResendNotificationVisible}"
|
||||
StyleClass="text-sm"
|
||||
FontAttributes="Bold"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,40,0,0"
|
||||
Margin="0,24,0,0"
|
||||
TextColor="{DynamicResource HyperlinkColor}"
|
||||
AutomationId="ResendNotificationButton">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
<StackLayout
|
||||
Orientation="Horizontal"
|
||||
Margin="0,30,0,0">
|
||||
<Label
|
||||
Text="{u:I18n NeedAnotherOption}"
|
||||
FontSize="Small"
|
||||
VerticalTextAlignment="End"/>
|
||||
<Label
|
||||
Text="{u:I18n ViewAllLoginOptions}"
|
||||
StyleClass="text-md"
|
||||
VerticalTextAlignment="End"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="5, 0"
|
||||
TextColor="{DynamicResource HyperlinkColor}"
|
||||
AutomationId="ViewAllLoginOptionsButton">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding CloseCommand}" />
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
</StackLayout>
|
||||
|
||||
<BoxView
|
||||
HeightRequest="1"
|
||||
Margin="0,24,0,24"
|
||||
Color="{DynamicResource DisabledIconColor}" />
|
||||
<Label
|
||||
Text="{Binding OtherOptions}"
|
||||
FontSize="Small"/>
|
||||
<Label
|
||||
Text="{u:I18n ViewAllLoginOptions}"
|
||||
StyleClass="text-sm"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{DynamicResource HyperlinkColor}"
|
||||
AutomationId="ViewAllLoginOptionsButton">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding CloseCommand}" />
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</pages:BaseContentPage>
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
|
@ -12,13 +13,15 @@ namespace Bit.App.Pages
|
|||
private LoginPasswordlessRequestViewModel _vm;
|
||||
private readonly AppOptions _appOptions;
|
||||
|
||||
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
|
||||
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null, bool authingWithSso = false)
|
||||
{
|
||||
InitializeComponent();
|
||||
_appOptions = appOptions;
|
||||
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.Email = email;
|
||||
_vm.AuthRequestType = authRequestType;
|
||||
_vm.AuthingWithSso = authingWithSso;
|
||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
||||
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -12,6 +13,7 @@ using Bit.Core;
|
|||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
|
@ -32,6 +34,9 @@ namespace Bit.App.Pages
|
|||
private IPlatformUtilsService _platformUtilsService;
|
||||
private IEnvironmentService _environmentService;
|
||||
private ILogger _logger;
|
||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
|
||||
protected override II18nService i18nService => _i18nService;
|
||||
protected override IEnvironmentService environmentService => _environmentService;
|
||||
|
@ -44,6 +49,7 @@ namespace Bit.App.Pages
|
|||
private string _email;
|
||||
private string _requestId;
|
||||
private string _requestAccessCode;
|
||||
private AuthRequestType _authRequestType;
|
||||
// Item1 publicKey, Item2 privateKey
|
||||
private Tuple<byte[], byte[]> _requestKeyPair;
|
||||
|
||||
|
@ -57,8 +63,9 @@ namespace Bit.App.Pages
|
|||
_i18nService = ServiceContainer.Resolve<II18nService>();
|
||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||
_logger = ServiceContainer.Resolve<ILogger>();
|
||||
|
||||
PageTitle = AppResources.LogInWithAnotherDevice;
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
||||
|
||||
CreatePasswordlessLoginCommand = new AsyncCommand(CreatePasswordlessLoginAsync,
|
||||
onException: ex => HandleException(ex),
|
||||
|
@ -73,10 +80,91 @@ namespace Bit.App.Pages
|
|||
public Action LogInSuccessAction { get; set; }
|
||||
public Action UpdateTempPasswordAction { get; set; }
|
||||
public Action CloseAction { get; set; }
|
||||
public bool AuthingWithSso { get; set; }
|
||||
|
||||
public ICommand CreatePasswordlessLoginCommand { get; }
|
||||
public ICommand CloseCommand { get; }
|
||||
|
||||
public string HeaderTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_authRequestType)
|
||||
{
|
||||
case AuthRequestType.AuthenticateAndUnlock:
|
||||
return AppResources.LogInWithDevice;
|
||||
case AuthRequestType.AdminApproval:
|
||||
return AppResources.LogInInitiated;
|
||||
default:
|
||||
return string.Empty;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_authRequestType)
|
||||
{
|
||||
case AuthRequestType.AuthenticateAndUnlock:
|
||||
return AppResources.LogInInitiated;
|
||||
case AuthRequestType.AdminApproval:
|
||||
return AppResources.AdminApprovalRequested;
|
||||
default:
|
||||
return string.Empty;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string SubTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_authRequestType)
|
||||
{
|
||||
case AuthRequestType.AuthenticateAndUnlock:
|
||||
return AppResources.ANotificationHasBeenSentToYourDevice;
|
||||
case AuthRequestType.AdminApproval:
|
||||
return AppResources.YourRequestHasBeenSentToYourAdmin;
|
||||
default:
|
||||
return string.Empty;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_authRequestType)
|
||||
{
|
||||
case AuthRequestType.AuthenticateAndUnlock:
|
||||
return AppResources.PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice;
|
||||
case AuthRequestType.AdminApproval:
|
||||
return AppResources.YouWillBeNotifiedOnceApproved;
|
||||
default:
|
||||
return string.Empty;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string OtherOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_authRequestType)
|
||||
{
|
||||
case AuthRequestType.AuthenticateAndUnlock:
|
||||
return AppResources.LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption;
|
||||
case AuthRequestType.AdminApproval:
|
||||
return AppResources.TroubleLoggingIn;
|
||||
default:
|
||||
return string.Empty;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string FingerprintPhrase
|
||||
{
|
||||
get => _fingerprintPhrase;
|
||||
|
@ -89,6 +177,25 @@ namespace Bit.App.Pages
|
|||
set => SetProperty(ref _email, value);
|
||||
}
|
||||
|
||||
public AuthRequestType AuthRequestType
|
||||
{
|
||||
get => _authRequestType;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _authRequestType, value, additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(Title),
|
||||
nameof(SubTitle),
|
||||
nameof(Description),
|
||||
nameof(OtherOptions),
|
||||
nameof(ResendNotificationVisible)
|
||||
});
|
||||
PageTitle = HeaderTitle;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ResendNotificationVisible => AuthRequestType == AuthRequestType.AuthenticateAndUnlock;
|
||||
|
||||
public void StartCheckLoginRequestStatus()
|
||||
{
|
||||
try
|
||||
|
@ -119,25 +226,39 @@ namespace Bit.App.Pages
|
|||
|
||||
private async Task CheckLoginRequestStatus()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode))
|
||||
if (string.IsNullOrEmpty(_requestId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode);
|
||||
PasswordlessLoginResponse response = null;
|
||||
if (AuthingWithSso)
|
||||
{
|
||||
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
|
||||
}
|
||||
|
||||
if (response.RequestApproved == null || !response.RequestApproved.Value)
|
||||
if (response?.RequestApproved != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopCheckLoginRequestStatus();
|
||||
|
||||
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
||||
var authResult = await _authService.LogInPasswordlessAsync(AuthingWithSso, Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
|
||||
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
||||
{
|
||||
await HandleLoginCompleteAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
|
||||
{
|
||||
return;
|
||||
|
@ -153,8 +274,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
else
|
||||
{
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
LogInSuccessAction?.Invoke();
|
||||
await HandleLoginCompleteAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -164,22 +284,67 @@ namespace Bit.App.Pages
|
|||
}
|
||||
}
|
||||
|
||||
private async Task HandleLoginCompleteAsync()
|
||||
{
|
||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
LogInSuccessAction?.Invoke();
|
||||
}
|
||||
|
||||
private async Task CreatePasswordlessLoginAsync()
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
||||
|
||||
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
||||
if (response != null)
|
||||
PasswordlessLoginResponse response = null;
|
||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
||||
if (pendingRequest != null && _authRequestType == AuthRequestType.AdminApproval)
|
||||
{
|
||||
FingerprintPhrase = response.FingerprintPhrase;
|
||||
_requestId = response.Id;
|
||||
_requestAccessCode = response.RequestAccessCode;
|
||||
_requestKeyPair = response.RequestKeyPair;
|
||||
response = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
||||
if (response == null || (response.IsAnswered && !response.RequestApproved.Value))
|
||||
{
|
||||
// handle pending auth request not valid remove it from state
|
||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||
pendingRequest = null;
|
||||
response = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Derive pubKey from privKey in state to avoid MITM attacks
|
||||
// Also generate FingerprintPhrase locally for the same reason
|
||||
var derivedPublicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(pendingRequest.PrivateKey);
|
||||
response.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(Email, derivedPublicKey));
|
||||
response.RequestKeyPair = new Tuple<byte[], byte[]>(derivedPublicKey, pendingRequest.PrivateKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
|
||||
}
|
||||
|
||||
await HandlePasswordlessLoginAsync(response, pendingRequest == null && _authRequestType == AuthRequestType.AdminApproval);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
}
|
||||
|
||||
private async Task HandlePasswordlessLoginAsync(PasswordlessLoginResponse response, bool createPendingAdminRequest)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(response));
|
||||
}
|
||||
|
||||
if (createPendingAdminRequest)
|
||||
{
|
||||
var pendingAuthRequest = new PendingAdminAuthRequest { Id = response.Id, PrivateKey = response.RequestKeyPair.Item2 };
|
||||
await _stateService.SetPendingAdminAuthRequestAsync(pendingAuthRequest);
|
||||
}
|
||||
|
||||
FingerprintPhrase = response.FingerprintPhrase;
|
||||
_requestId = response.Id;
|
||||
_requestAccessCode = response.RequestAccessCode;
|
||||
_requestKeyPair = response.RequestKeyPair;
|
||||
}
|
||||
|
||||
private void HandleException(Exception ex)
|
||||
{
|
||||
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
||||
|
|
|
@ -29,6 +29,8 @@ namespace Bit.App.Pages
|
|||
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
||||
_vm.UpdateTempPasswordAction =
|
||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||
_vm.StartDeviceApprovalOptionsAction =
|
||||
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||
_vm.CloseAction = async () =>
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
|
@ -106,10 +108,17 @@ namespace Bit.App.Pages
|
|||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async Task StartDeviceApprovalOptionsAsync()
|
||||
{
|
||||
var page = new LoginApproveDevicePage();
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async Task SsoAuthSuccessAsync()
|
||||
{
|
||||
RestoreAppOptionsFromCopy();
|
||||
await AppHelpers.ClearPreviousPage();
|
||||
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
|
|
|
@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
|
|||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Essentials;
|
||||
|
@ -29,6 +30,8 @@ namespace Bit.App.Pages
|
|||
private readonly IStateService _stateService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
|
||||
private string _orgIdentifier;
|
||||
|
||||
|
@ -45,7 +48,8 @@ namespace Bit.App.Pages
|
|||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
||||
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
||||
|
||||
PageTitle = AppResources.Bitwarden;
|
||||
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
|
||||
|
@ -61,6 +65,7 @@ namespace Bit.App.Pages
|
|||
public Action StartTwoFactorAction { get; set; }
|
||||
public Action StartSetPasswordAction { get; set; }
|
||||
public Action SsoAuthSuccessAction { get; set; }
|
||||
public Action StartDeviceApprovalOptionsAction { get; set; }
|
||||
public Action CloseAction { get; set; }
|
||||
public Action UpdateTempPasswordAction { get; set; }
|
||||
|
||||
|
@ -109,7 +114,7 @@ namespace Bit.App.Pages
|
|||
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||
|
||||
var response = await _apiService.PreValidateSso(OrgIdentifier);
|
||||
var response = await _apiService.PreValidateSsoAsync(OrgIdentifier);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(response?.Token))
|
||||
{
|
||||
|
@ -144,7 +149,6 @@ namespace Bit.App.Pages
|
|||
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
|
||||
new Uri(REDIRECT_URI));
|
||||
|
||||
|
||||
var code = GetResultCode(authResult, state);
|
||||
if (!string.IsNullOrEmpty(code))
|
||||
{
|
||||
|
@ -197,28 +201,93 @@ namespace Bit.App.Pages
|
|||
try
|
||||
{
|
||||
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
|
||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
if (response.TwoFactor)
|
||||
{
|
||||
StartTwoFactorAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
else if (response.ResetMasterPassword)
|
||||
|
||||
// Trusted device option is sent regardless if this is a trusted device or not
|
||||
// If it is trusted, it will have the necessary keys
|
||||
if (decryptOptions?.TrustedDeviceOption != null)
|
||||
{
|
||||
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
||||
{
|
||||
// If we have a device key but no keys on server, we need to remove the device key
|
||||
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
|
||||
{
|
||||
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
|
||||
StartDeviceApprovalOptionsAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||
if (!decryptOptions.HasMasterPassword &&
|
||||
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
||||
{
|
||||
StartSetPasswordAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
|
||||
if (response.ForcePasswordReset)
|
||||
{
|
||||
UpdateTempPasswordAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
// Device is trusted and has keys, so we can decrypt
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
SsoAuthSuccessAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for pending Admin Auth requests before navigating to device approval options
|
||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
||||
if (pendingRequest != null)
|
||||
{
|
||||
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
||||
if (authRequest?.RequestApproved == true)
|
||||
{
|
||||
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
|
||||
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
||||
{
|
||||
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
|
||||
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
|
||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
SsoAuthSuccessAction?.Invoke();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||
StartDeviceApprovalOptionsAction?.Invoke();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StartDeviceApprovalOptionsAction?.Invoke();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// In the standard, non TDE case, a user must set password if they don't
|
||||
// have one and they aren't using key connector.
|
||||
// Note: TDE & Key connector are mutually exclusive org config options.
|
||||
if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword == true))
|
||||
{
|
||||
// TODO: We need to look into how to handle this when Org removes TDE
|
||||
// Will we have the User Key by now to set a new password?
|
||||
StartSetPasswordAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
else if (response.ForcePasswordReset)
|
||||
{
|
||||
UpdateTempPasswordAction?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
||||
SsoAuthSuccessAction?.Invoke();
|
||||
}
|
||||
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
SsoAuthSuccessAction?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
||||
|
|
|
@ -177,25 +177,28 @@ namespace Bit.App.Pages
|
|||
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
||||
Email = Email.Trim().ToLower();
|
||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig);
|
||||
var encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
|
||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
|
||||
newMasterKey,
|
||||
await _cryptoService.MakeUserKeyAsync()
|
||||
);
|
||||
var hashedPassword = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey);
|
||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
||||
var request = new RegisterRequest
|
||||
{
|
||||
Email = Email,
|
||||
Name = Name,
|
||||
MasterPasswordHash = hashedPassword,
|
||||
MasterPasswordHint = Hint,
|
||||
Key = encKey.Item2.EncryptedString,
|
||||
Key = newProtectedUserKey.EncryptedString,
|
||||
Kdf = kdfConfig.Type,
|
||||
KdfIterations = kdfConfig.Iterations,
|
||||
KdfMemory = kdfConfig.Memory,
|
||||
KdfParallelism = kdfConfig.Parallelism,
|
||||
Keys = new KeysRequest
|
||||
{
|
||||
PublicKey = keys.Item1,
|
||||
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||
PublicKey = newPublicKey,
|
||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
||||
},
|
||||
CaptchaResponse = _captchaToken,
|
||||
};
|
||||
|
|
|
@ -30,14 +30,14 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task Init()
|
||||
{
|
||||
Organization = await _keyConnectorService.GetManagingOrganization();
|
||||
Organization = await _keyConnectorService.GetManagingOrganizationAsync();
|
||||
}
|
||||
|
||||
public async Task MigrateAccount()
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
|
||||
await _keyConnectorService.MigrateUser();
|
||||
await _keyConnectorService.MigrateUserAsync();
|
||||
await _syncService.FullSyncAsync(true);
|
||||
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
@ -47,7 +47,7 @@ namespace Bit.App.Pages
|
|||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
|
||||
await _apiService.PostLeaveOrganization(Organization.Id);
|
||||
await _apiService.PostLeaveOrganizationAsync(Organization.Id);
|
||||
await _syncService.FullSyncAsync(true);
|
||||
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
|
|
@ -165,26 +165,18 @@ namespace Bit.App.Pages
|
|||
|
||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||
var email = await _stateService.GetEmailAsync();
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
|
||||
var localMasterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
|
||||
|
||||
Tuple<SymmetricCryptoKey, EncString> encKey;
|
||||
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
||||
if (existingEncKey == null)
|
||||
{
|
||||
encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
encKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||
}
|
||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
|
||||
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
|
||||
|
||||
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
||||
var request = new SetPasswordRequest
|
||||
{
|
||||
MasterPasswordHash = masterPasswordHash,
|
||||
Key = encKey.Item2.EncryptedString,
|
||||
Key = newProtectedUserKey.EncryptedString,
|
||||
MasterPasswordHint = Hint,
|
||||
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
|
||||
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
|
||||
|
@ -193,8 +185,8 @@ namespace Bit.App.Pages
|
|||
OrgIdentifier = OrgIdentifier,
|
||||
Keys = new KeysRequest
|
||||
{
|
||||
PublicKey = keys.Item1,
|
||||
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||
PublicKey = newPublicKey,
|
||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -204,19 +196,20 @@ namespace Bit.App.Pages
|
|||
// Set Password and relevant information
|
||||
await _apiService.SetPasswordAsync(request);
|
||||
await _stateService.SetKdfConfigurationAsync(kdfConfig);
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
|
||||
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
|
||||
await _cryptoService.SetUserKeyAsync(newUserKey);
|
||||
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
||||
await _cryptoService.SetMasterKeyHashAsync(localMasterPasswordHash);
|
||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
|
||||
await _cryptoService.SetUserPrivateKeyAsync(newProtectedPrivateKey.EncryptedString);
|
||||
|
||||
if (ResetPasswordAutoEnroll)
|
||||
{
|
||||
// Grab Organization Keys
|
||||
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
|
||||
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
|
||||
// Grab user's Encryption Key and encrypt with Org Public Key
|
||||
var userEncKey = await _cryptoService.GetEncKeyAsync();
|
||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
|
||||
// Grab User Key and encrypt with Org Public Key
|
||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
|
||||
// Request
|
||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using Bit.App.Controls;
|
|||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
|
@ -17,26 +18,29 @@ namespace Bit.App.Pages
|
|||
|
||||
private TwoFactorPageViewModel _vm;
|
||||
private bool _inited;
|
||||
private bool _authingWithSso;
|
||||
private string _orgIdentifier;
|
||||
|
||||
public TwoFactorPage(bool? authingWithSso = false, AppOptions appOptions = null, string orgIdentifier = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
SetActivityIndicator();
|
||||
_authingWithSso = authingWithSso ?? false;
|
||||
_appOptions = appOptions;
|
||||
_orgIdentifier = orgIdentifier;
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_vm = BindingContext as TwoFactorPageViewModel;
|
||||
_vm.Page = this;
|
||||
_vm.AuthingWithSso = authingWithSso ?? false;
|
||||
_vm.StartSetPasswordAction = () =>
|
||||
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
|
||||
_vm.TwoFactorAuthSuccessAction = () =>
|
||||
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
|
||||
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessToMainAsync());
|
||||
_vm.LockAction = () =>
|
||||
Device.BeginInvokeOnMainThread(TwoFactorAuthSuccessWithSSOLocked);
|
||||
_vm.UpdateTempPasswordAction =
|
||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||
_vm.StartDeviceApprovalOptionsAction =
|
||||
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
||||
DuoWebView = _duoWebView;
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
|
@ -180,21 +184,25 @@ namespace Bit.App.Pages
|
|||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private async Task TwoFactorAuthSuccessAsync()
|
||||
private async Task StartDeviceApprovalOptionsAsync()
|
||||
{
|
||||
if (_authingWithSso)
|
||||
var page = new LoginApproveDevicePage();
|
||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
|
||||
private void TwoFactorAuthSuccessWithSSOLocked()
|
||||
{
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
}
|
||||
|
||||
private async Task TwoFactorAuthSuccessToMainAsync()
|
||||
{
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
return;
|
||||
}
|
||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||
}
|
||||
|
||||
private void Token_TextChanged(object sender, TextChangedEventArgs e)
|
||||
|
|
|
@ -11,6 +11,7 @@ using Bit.Core.Abstractions;
|
|||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
|
@ -32,12 +33,12 @@ namespace Bit.App.Pages
|
|||
private readonly IStateService _stateService;
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private TwoFactorProviderType? _selectedProviderType;
|
||||
private string _totpInstruction;
|
||||
private string _webVaultUrl = "https://vault.bitwarden.com";
|
||||
private bool _authingWithSso = false;
|
||||
private bool _enableContinue = false;
|
||||
private bool _showContinue = true;
|
||||
|
||||
|
@ -54,7 +55,9 @@ namespace Bit.App.Pages
|
|||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>();
|
||||
_logger = ServiceContainer.Resolve<ILogger>();
|
||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||
|
||||
PageTitle = AppResources.TwoStepLogin;
|
||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||
|
@ -69,6 +72,8 @@ namespace Bit.App.Pages
|
|||
|
||||
public bool Remember { get; set; }
|
||||
|
||||
public bool AuthingWithSso { get; set; }
|
||||
|
||||
public string Token { get; set; }
|
||||
|
||||
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
|
||||
|
@ -118,6 +123,8 @@ namespace Bit.App.Pages
|
|||
public Command SubmitCommand { get; }
|
||||
public ICommand MoreCommand { get; }
|
||||
public Action TwoFactorAuthSuccessAction { get; set; }
|
||||
public Action LockAction { get; set; }
|
||||
public Action StartDeviceApprovalOptionsAction { get; set; }
|
||||
public Action StartSetPasswordAction { get; set; }
|
||||
public Action CloseAction { get; set; }
|
||||
public Action UpdateTempPasswordAction { get; set; }
|
||||
|
@ -136,8 +143,6 @@ namespace Bit.App.Pages
|
|||
return;
|
||||
}
|
||||
|
||||
_authingWithSso = _authService.AuthingWithSso();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
|
||||
{
|
||||
_webVaultUrl = _environmentService.BaseUrl;
|
||||
|
@ -315,21 +320,84 @@ namespace Bit.App.Pages
|
|||
|
||||
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
_messagingService.Send("listenYubiKeyOTP", false);
|
||||
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
||||
|
||||
if (_authingWithSso && result.ResetMasterPassword)
|
||||
if (decryptOptions?.TrustedDeviceOption != null)
|
||||
{
|
||||
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
||||
{
|
||||
// If we have a device key but no keys on server, we need to remove the device key
|
||||
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
|
||||
{
|
||||
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
|
||||
StartDeviceApprovalOptionsAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||
if (!decryptOptions.HasMasterPassword &&
|
||||
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
||||
{
|
||||
StartSetPasswordAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
|
||||
if (result.ForcePasswordReset)
|
||||
{
|
||||
UpdateTempPasswordAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
// Device is trusted and has keys, so we can decrypt
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
await TwoFactorAuthSuccessAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for pending Admin Auth requests before navigating to device approval options
|
||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
||||
if (pendingRequest != null)
|
||||
{
|
||||
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
||||
if (authRequest?.RequestApproved == true)
|
||||
{
|
||||
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
|
||||
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
||||
{
|
||||
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
|
||||
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
|
||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
await TwoFactorAuthSuccessAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||
StartDeviceApprovalOptionsAction?.Invoke();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StartDeviceApprovalOptionsAction?.Invoke();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// In the standard, non TDE case, a user must set password if they don't
|
||||
// have one and they aren't using key connector.
|
||||
// Note: TDE & Key connector are mutually exclusive org config options.
|
||||
if (result.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false))
|
||||
{
|
||||
// TODO: We need to look into how to handle this when Org removes TDE
|
||||
// Will we have the User Key by now to set a new password?
|
||||
StartSetPasswordAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
else if (result.ForcePasswordReset)
|
||||
{
|
||||
UpdateTempPasswordAction?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
TwoFactorAuthSuccessAction?.Invoke();
|
||||
}
|
||||
|
||||
_syncService.FullSyncAsync(true).FireAndForget();
|
||||
await TwoFactorAuthSuccessAsync();
|
||||
}
|
||||
catch (ApiException e)
|
||||
{
|
||||
|
@ -422,5 +490,17 @@ namespace Bit.App.Pages
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TwoFactorAuthSuccessAsync()
|
||||
{
|
||||
if (AuthingWithSso && await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
LockAction?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
TwoFactorAuthSuccessAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,12 +93,12 @@ namespace Bit.App.Pages
|
|||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
var email = await _stateService.GetEmailAsync();
|
||||
|
||||
// Create new key and hash new password
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||
// Create new master key and hash new password
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey);
|
||||
|
||||
// Create new encKey for the User
|
||||
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||
// Encrypt user key with new master key
|
||||
var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
|
||||
// Initiate API action
|
||||
try
|
||||
|
@ -108,10 +108,10 @@ namespace Bit.App.Pages
|
|||
switch (_reason)
|
||||
{
|
||||
case ForcePasswordResetReason.AdminForcePasswordReset:
|
||||
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||
await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
||||
break;
|
||||
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
||||
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||
await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
@ -155,7 +155,7 @@ namespace Bit.App.Pages
|
|||
|
||||
private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey)
|
||||
{
|
||||
var currentPasswordHash = await _cryptoService.HashPasswordAsync(CurrentMasterPassword, null);
|
||||
var currentPasswordHash = await _cryptoService.HashMasterKeyAsync(CurrentMasterPassword, null);
|
||||
|
||||
var request = new PasswordRequest
|
||||
{
|
||||
|
|
|
@ -21,7 +21,6 @@ namespace Bit.App.Pages
|
|||
private readonly II18nService _i18nService;
|
||||
private readonly IExportService _exportService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly ILogger _logger;
|
||||
|
@ -45,8 +44,7 @@ namespace Bit.App.Pages
|
|||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
|
@ -67,7 +65,7 @@ namespace Bit.App.Pages
|
|||
_initialized = true;
|
||||
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
|
||||
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
|
||||
UseOTPVerification = await _keyConnectorService.GetUsesKeyConnector();
|
||||
UseOTPVerification = !await _userVerificationService.HasMasterPasswordAsync(true);
|
||||
|
||||
if (UseOTPVerification)
|
||||
{
|
||||
|
@ -165,9 +163,9 @@ namespace Bit.App.Pages
|
|||
return;
|
||||
}
|
||||
|
||||
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
|
||||
? VerificationType.OTP
|
||||
: VerificationType.MasterPassword;
|
||||
var verificationType = await _userVerificationService.HasMasterPasswordAsync(true)
|
||||
? VerificationType.MasterPassword
|
||||
: VerificationType.OTP;
|
||||
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
|
||||
{
|
||||
return;
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Bit.App.Pages
|
|||
private readonly IBiometricService _biometricService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ILocalizeService _localizeService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly ILogger _loggerService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
|
@ -48,6 +48,7 @@ namespace Bit.App.Pages
|
|||
private bool _reportLoggingEnabled;
|
||||
private bool _approvePasswordlessLoginRequests;
|
||||
private bool _shouldConnectToWatch;
|
||||
private bool _hasMasterPassword;
|
||||
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
||||
new List<KeyValuePair<string, int?>>
|
||||
{
|
||||
|
@ -88,7 +89,7 @@ namespace Bit.App.Pages
|
|||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
||||
|
@ -100,12 +101,17 @@ namespace Bit.App.Pages
|
|||
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
||||
}
|
||||
|
||||
private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _biometric || _pin;
|
||||
|
||||
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||
|
||||
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
var decryptionOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
// set has true for backwards compatibility
|
||||
_hasMasterPassword = decryptionOptions?.HasMasterPassword ?? true;
|
||||
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
||||
var lastSync = await _syncService.GetLastSyncAsync();
|
||||
if (lastSync != null)
|
||||
|
@ -124,8 +130,17 @@ namespace Bit.App.Pages
|
|||
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
||||
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
||||
|
||||
var action = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
|
||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
|
||||
|
||||
var pinSet = await _vaultTimeoutService.GetPinLockTypeAsync();
|
||||
_pin = pinSet != PinLockType.Disabled;
|
||||
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
||||
var timeoutAction = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
|
||||
if (!IsVaultTimeoutActionLockAllowed && timeoutAction == VaultTimeoutAction.Lock)
|
||||
{
|
||||
timeoutAction = VaultTimeoutAction.Logout;
|
||||
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
||||
}
|
||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == timeoutAction).Key;
|
||||
|
||||
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
||||
{
|
||||
|
@ -137,10 +152,6 @@ namespace Bit.App.Pages
|
|||
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
|
||||
t.Value != null).ToList();
|
||||
}
|
||||
|
||||
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
_pin = pinSet.Item1 || pinSet.Item2;
|
||||
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
||||
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
|
||||
|
||||
if (_vaultTimeoutDisplayValue == null)
|
||||
|
@ -148,8 +159,7 @@ namespace Bit.App.Pages
|
|||
_vaultTimeoutDisplayValue = AppResources.Custom;
|
||||
}
|
||||
|
||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
||||
!await _keyConnectorService.GetUsesKeyConnector();
|
||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() && await _userVerificationService.HasMasterPasswordAsync();
|
||||
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
|
||||
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
|
||||
|
@ -323,6 +333,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
if (oldTimeout != newTimeout)
|
||||
{
|
||||
await _cryptoService.RefreshKeysAsync();
|
||||
await Device.InvokeOnMainThreadAsync(BuildList);
|
||||
}
|
||||
}
|
||||
|
@ -387,8 +398,11 @@ namespace Bit.App.Pages
|
|||
// do nothing if we have a policy set
|
||||
return;
|
||||
}
|
||||
var options = _vaultTimeoutActionOptions.Select(o =>
|
||||
o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
|
||||
|
||||
var options = IsVaultTimeoutActionLockAllowed
|
||||
? _vaultTimeoutActionOptions.Select(o => CreateSelectableOption(o.Key, _vaultTimeoutActionDisplayValue == o.Key)).ToArray()
|
||||
: _vaultTimeoutActionOptions.Where(o => o.Value == VaultTimeoutAction.Logout).Select(v => ToSelectedOption(v.Key)).ToArray();
|
||||
|
||||
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
||||
AppResources.Cancel, null, options);
|
||||
if (selection == null || selection == AppResources.Cancel)
|
||||
|
@ -428,7 +442,7 @@ namespace Bit.App.Pages
|
|||
if (!string.IsNullOrWhiteSpace(pin))
|
||||
{
|
||||
var masterPassOnRestart = false;
|
||||
if (!await _keyConnectorService.GetUsesKeyConnector())
|
||||
if (await _userVerificationService.HasMasterPasswordAsync())
|
||||
{
|
||||
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
|
||||
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
|
||||
|
@ -437,19 +451,20 @@ namespace Bit.App.Pages
|
|||
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
var email = await _stateService.GetEmailAsync();
|
||||
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig);
|
||||
var key = await _cryptoService.GetKeyAsync();
|
||||
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
|
||||
var pinKey = await _cryptoService.MakePinKeyAsync(pin, email, kdfConfig);
|
||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||
var protectedPinKey = await _cryptoService.EncryptAsync(userKey.Key, pinKey);
|
||||
|
||||
var encPin = await _cryptoService.EncryptAsync(pin);
|
||||
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
||||
|
||||
if (masterPassOnRestart)
|
||||
{
|
||||
var encPin = await _cryptoService.EncryptAsync(pin);
|
||||
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
||||
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
|
||||
await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(protectedPinKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
|
||||
await _stateService.SetPinKeyEncryptedUserKeyAsync(protectedPinKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -459,8 +474,8 @@ namespace Bit.App.Pages
|
|||
}
|
||||
if (!_pin)
|
||||
{
|
||||
await _cryptoService.ClearPinProtectedKeyAsync();
|
||||
await _vaultTimeoutService.ClearAsync();
|
||||
await UpdateVaultTimeoutActionIfNeededAsync();
|
||||
}
|
||||
BuildList();
|
||||
}
|
||||
|
@ -489,9 +504,10 @@ namespace Bit.App.Pages
|
|||
else
|
||||
{
|
||||
await _stateService.SetBiometricUnlockAsync(null);
|
||||
await UpdateVaultTimeoutActionIfNeededAsync();
|
||||
}
|
||||
await _stateService.SetBiometricLockedAsync(false);
|
||||
await _cryptoService.ToggleKeyAsync();
|
||||
await _cryptoService.RefreshKeysAsync();
|
||||
BuildList();
|
||||
}
|
||||
|
||||
|
@ -835,9 +851,11 @@ namespace Bit.App.Pages
|
|||
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
||||
}
|
||||
|
||||
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||
private string CreateSelectableOption(string option, bool selected) => selected ? ToSelectedOption(option) : option;
|
||||
|
||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == ToSelectedOption(compareTo);
|
||||
|
||||
private string ToSelectedOption(string option) => $"✓ {option}";
|
||||
|
||||
public async Task SetScreenCaptureAllowedAsync()
|
||||
{
|
||||
|
@ -869,5 +887,17 @@ namespace Bit.App.Pages
|
|||
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
|
||||
BuildList();
|
||||
}
|
||||
|
||||
private async Task UpdateVaultTimeoutActionIfNeededAsync()
|
||||
{
|
||||
if (IsVaultTimeoutActionLockAllowed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.First(o => o.Value == VaultTimeoutAction.Logout).Key;
|
||||
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
||||
_deviceActionService.Toast(AppResources.VaultTimeoutActionChangedToLogOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
});
|
||||
await UpdateVaultButtonTitleAsync();
|
||||
if (await _keyConnectorService.UserNeedsMigration())
|
||||
if (await _keyConnectorService.UserNeedsMigrationAsync())
|
||||
{
|
||||
_messagingService.Send("convertAccountToKeyConnector");
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace Bit.App.Pages
|
|||
_cipherDomain = await _cipherService.GetAsync(CipherId);
|
||||
Cipher = await _cipherDomain.DecryptAsync();
|
||||
LoadAttachments();
|
||||
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
|
||||
_hasUpdatedKey = await _cryptoService.HasUserKeyAsync();
|
||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
||||
if (!_canAccessAttachments)
|
||||
|
|
|
@ -82,7 +82,7 @@ namespace Bit.App.Pages
|
|||
return;
|
||||
}
|
||||
|
||||
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Bit.App.Pages
|
|||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IAutofillHandler _autofillHandler;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
|
||||
private CipherAddEditPageViewModel _vm;
|
||||
private bool _fromAutofill;
|
||||
|
@ -43,7 +43,7 @@ namespace Bit.App.Pages
|
|||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
||||
|
||||
_appOptions = appOptions;
|
||||
_fromAutofill = fromAutofill;
|
||||
|
@ -175,8 +175,8 @@ namespace Bit.App.Pages
|
|||
RequestFocus(_nameEntry);
|
||||
}
|
||||
});
|
||||
// Hide password reprompt option if using key connector
|
||||
_passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector();
|
||||
|
||||
_passwordPrompt.IsVisible = await _userVerificationService.HasMasterPasswordAsync();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
|
|
@ -698,12 +698,12 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task<bool> PromptPasswordAsync()
|
||||
{
|
||||
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
|
||||
if (_passwordReprompted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
|
||||
return _passwordReprompted = await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Cipher.Reprompt);
|
||||
}
|
||||
|
||||
private async Task<bool> CanCloneAsync()
|
||||
|
|
|
@ -191,7 +191,7 @@ namespace Bit.App.Pages
|
|||
|
||||
if (_appOptions?.OtpData != null)
|
||||
{
|
||||
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ namespace Bit.App.Pages
|
|||
}
|
||||
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
|
||||
{
|
||||
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace Bit.App.Pages
|
|||
|
||||
var cipher = listItem.Cipher;
|
||||
|
||||
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
137
src/App/Resources/AppResources.Designer.cs
generated
137
src/App/Resources/AppResources.Designer.cs
generated
|
@ -418,6 +418,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Admin approval requested.
|
||||
/// </summary>
|
||||
public static string AdminApprovalRequested {
|
||||
get {
|
||||
return ResourceManager.GetString("AdminApprovalRequested", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to All.
|
||||
/// </summary>
|
||||
|
@ -571,6 +580,24 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Approve with master password.
|
||||
/// </summary>
|
||||
public static string ApproveWithMasterPassword {
|
||||
get {
|
||||
return ResourceManager.GetString("ApproveWithMasterPassword", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Approve with my other device.
|
||||
/// </summary>
|
||||
public static string ApproveWithMyOtherDevice {
|
||||
get {
|
||||
return ResourceManager.GetString("ApproveWithMyOtherDevice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to April.
|
||||
/// </summary>
|
||||
|
@ -3694,6 +3721,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logged in!.
|
||||
/// </summary>
|
||||
public static string LoggedIn {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggedIn", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
||||
/// </summary>
|
||||
|
@ -3712,6 +3748,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logging in as {0}.
|
||||
/// </summary>
|
||||
public static string LoggingInAsX {
|
||||
get {
|
||||
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
||||
/// </summary>
|
||||
|
@ -3748,6 +3793,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Login approved.
|
||||
/// </summary>
|
||||
public static string LoginApproved {
|
||||
get {
|
||||
return ResourceManager.GetString("LoginApproved", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Login attempt by {0} on {1}.
|
||||
/// </summary>
|
||||
|
@ -3787,7 +3841,7 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Log in initiated.
|
||||
/// Looks up a localized string similar to Login initiated.
|
||||
/// </summary>
|
||||
public static string LogInInitiated {
|
||||
get {
|
||||
|
@ -3885,6 +3939,24 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Log in with device.
|
||||
/// </summary>
|
||||
public static string LogInWithDevice {
|
||||
get {
|
||||
return ResourceManager.GetString("LogInWithDevice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Log in with device must be set up in the settings of the Bitwarden app. Need another option?.
|
||||
/// </summary>
|
||||
public static string LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption {
|
||||
get {
|
||||
return ResourceManager.GetString("LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Log in with master password.
|
||||
/// </summary>
|
||||
|
@ -5345,6 +5417,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remember this device.
|
||||
/// </summary>
|
||||
public static string RememberThisDevice {
|
||||
get {
|
||||
return ResourceManager.GetString("RememberThisDevice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove.
|
||||
/// </summary>
|
||||
|
@ -5417,6 +5498,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Request admin approval.
|
||||
/// </summary>
|
||||
public static string RequestAdminApproval {
|
||||
get {
|
||||
return ResourceManager.GetString("RequestAdminApproval", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Request one-time password.
|
||||
/// </summary>
|
||||
|
@ -6444,6 +6534,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Trouble logging in?.
|
||||
/// </summary>
|
||||
public static string TroubleLoggingIn {
|
||||
get {
|
||||
return ResourceManager.GetString("TroubleLoggingIn", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Try again.
|
||||
/// </summary>
|
||||
|
@ -6453,6 +6552,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Turn off using a public device.
|
||||
/// </summary>
|
||||
public static string TurnOffUsingPublicDevice {
|
||||
get {
|
||||
return ResourceManager.GetString("TurnOffUsingPublicDevice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 20 seconds.
|
||||
/// </summary>
|
||||
|
@ -6984,6 +7092,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Vault timeout action changed to log out.
|
||||
/// </summary>
|
||||
public static string VaultTimeoutActionChangedToLogOut {
|
||||
get {
|
||||
return ResourceManager.GetString("VaultTimeoutActionChangedToLogOut", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
|
||||
/// </summary>
|
||||
|
@ -7362,6 +7479,24 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your request has been sent to your admin..
|
||||
/// </summary>
|
||||
public static string YourRequestHasBeenSentToYourAdmin {
|
||||
get {
|
||||
return ResourceManager.GetString("YourRequestHasBeenSentToYourAdmin", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You will be notified once approved. .
|
||||
/// </summary>
|
||||
public static string YouWillBeNotifiedOnceApproved {
|
||||
get {
|
||||
return ResourceManager.GetString("YouWillBeNotifiedOnceApproved", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button..
|
||||
/// </summary>
|
||||
|
|
|
@ -2506,7 +2506,7 @@ Do you want to switch to this account?</value>
|
|||
<value>Log in with device</value>
|
||||
</data>
|
||||
<data name="LogInInitiated" xml:space="preserve">
|
||||
<value>Log in initiated</value>
|
||||
<value>Login initiated</value>
|
||||
</data>
|
||||
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
|
||||
<value>A notification has been sent to your device.</value>
|
||||
|
@ -2628,6 +2628,24 @@ Do you want to switch to this account?</value>
|
|||
<data name="CurrentMasterPassword" xml:space="preserve">
|
||||
<value>Current master password</value>
|
||||
</data>
|
||||
<data name="LoggedIn" xml:space="preserve">
|
||||
<value>Logged in!</value>
|
||||
</data>
|
||||
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
|
||||
<value>Approve with my other device</value>
|
||||
</data>
|
||||
<data name="RequestAdminApproval" xml:space="preserve">
|
||||
<value>Request admin approval</value>
|
||||
</data>
|
||||
<data name="ApproveWithMasterPassword" xml:space="preserve">
|
||||
<value>Approve with master password</value>
|
||||
</data>
|
||||
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
|
||||
<value>Turn off using a public device</value>
|
||||
</data>
|
||||
<data name="RememberThisDevice" xml:space="preserve">
|
||||
<value>Remember this device</value>
|
||||
</data>
|
||||
<data name="Passkey" xml:space="preserve">
|
||||
<value>Passkey</value>
|
||||
</data>
|
||||
|
@ -2668,6 +2686,24 @@ Do you want to switch to this account?</value>
|
|||
<data name="InvalidAPIToken" xml:space="preserve">
|
||||
<value>Invalid API token</value>
|
||||
</data>
|
||||
<data name="AdminApprovalRequested" xml:space="preserve">
|
||||
<value>Admin approval requested</value>
|
||||
</data>
|
||||
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
|
||||
<value>Your request has been sent to your admin.</value>
|
||||
</data>
|
||||
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
|
||||
<value>You will be notified once approved. </value>
|
||||
</data>
|
||||
<data name="TroubleLoggingIn" xml:space="preserve">
|
||||
<value>Trouble logging in?</value>
|
||||
</data>
|
||||
<data name="LoggingInAsX" xml:space="preserve">
|
||||
<value>Logging in as {0}</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
|
||||
<value>Vault timeout action changed to log out</value>
|
||||
</data>
|
||||
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
|
||||
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
|
||||
</data>
|
||||
|
@ -2714,6 +2750,15 @@ Do you want to switch to this account?</value>
|
|||
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
|
||||
<value>Cannot edit multiple URIs at once</value>
|
||||
</data>
|
||||
<data name="LoginApproved" xml:space="preserve">
|
||||
<value>Login approved</value>
|
||||
</data>
|
||||
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
|
||||
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
|
||||
</data>
|
||||
<data name="LogInWithDevice" xml:space="preserve">
|
||||
<value>Log in with device</value>
|
||||
</data>
|
||||
<data name="LoggingInOn" xml:space="preserve">
|
||||
<value>Logging in on</value>
|
||||
</data>
|
||||
|
|
25
src/App/Services/BaseBiometricService.cs
Normal file
25
src/App/Services/BaseBiometricService.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
public abstract class BaseBiometricService : IBiometricService
|
||||
{
|
||||
protected readonly IStateService _stateService;
|
||||
protected readonly ICryptoService _cryptoService;
|
||||
|
||||
protected BaseBiometricService(IStateService stateService, ICryptoService cryptoService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_cryptoService = cryptoService;
|
||||
}
|
||||
|
||||
public async Task<bool> CanUseBiometricsUnlockAsync()
|
||||
{
|
||||
return await _cryptoService.GetBiometricUnlockKeyAsync() != null || await _stateService.GetKeyEncryptedAsync() != null;
|
||||
}
|
||||
|
||||
public abstract Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
|
||||
public abstract Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
|
@ -20,8 +19,13 @@ namespace Bit.App.Services
|
|||
|
||||
public string[] ProtectedFields { get; } = { "LoginTotp", "LoginPassword", "H_FieldValue", "CardNumber", "CardCode" };
|
||||
|
||||
public async Task<bool> ShowPasswordPromptAsync()
|
||||
public async Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password)
|
||||
{
|
||||
if (repromptType == CipherRepromptType.None || await ShouldByPassMasterPasswordRepromptAsync())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await _platformUtilsService.ShowPasswordDialogAsync(AppResources.PasswordConfirmation, AppResources.PasswordConfirmationDesc, ValidatePasswordAsync);
|
||||
}
|
||||
|
||||
|
@ -41,10 +45,9 @@ namespace Bit.App.Services
|
|||
return await _cryptoService.CompareAndUpdateKeyHashAsync(password, null);
|
||||
}
|
||||
|
||||
public async Task<bool> Enabled()
|
||||
private async Task<bool> ShouldByPassMasterPasswordRepromptAsync()
|
||||
{
|
||||
var keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
return !await keyConnectorService.GetUsesKeyConnector();
|
||||
return await _cryptoService.GetMasterKeyHashAsync() is null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -206,6 +206,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||
|
||||
private async Task AddAccountAsync()
|
||||
{
|
||||
await AppHelpers.ClearServiceCacheAsync();
|
||||
await Device.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
Options.HideAccountSwitcher = false;
|
||||
|
|
|
@ -99,60 +99,55 @@ namespace Bit.App.Utilities
|
|||
{
|
||||
await page.Navigation.PushModalAsync(new NavigationPage(new CipherDetailsPage(cipher.Id)));
|
||||
}
|
||||
else if (selection == AppResources.Edit)
|
||||
else if (selection == AppResources.Edit
|
||||
&&
|
||||
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await page.Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(cipher.Id)));
|
||||
}
|
||||
await page.Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(cipher.Id)));
|
||||
}
|
||||
else if (selection == AppResources.CopyUsername)
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Type == CipherType.Login ? cipher.Login.Username : cipher.Fido2Key.UserName);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
|
||||
}
|
||||
else if (selection == AppResources.CopyPassword)
|
||||
else if (selection == AppResources.CopyPassword
|
||||
&&
|
||||
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||
}
|
||||
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||
}
|
||||
else if (selection == AppResources.CopyTotp)
|
||||
else if (selection == AppResources.CopyTotp
|
||||
&&
|
||||
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (!string.IsNullOrWhiteSpace(totp))
|
||||
{
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (!string.IsNullOrWhiteSpace(totp))
|
||||
{
|
||||
await clipboardService.CopyTextAsync(totp);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||
}
|
||||
await clipboardService.CopyTextAsync(totp);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||
}
|
||||
}
|
||||
else if (selection == AppResources.Launch && cipher.CanLaunch)
|
||||
{
|
||||
platformUtilsService.LaunchUri(cipher.LaunchUri);
|
||||
}
|
||||
else if (selection == AppResources.CopyNumber)
|
||||
else if (selection == AppResources.CopyNumber
|
||||
&&
|
||||
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
|
||||
}
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
|
||||
}
|
||||
else if (selection == AppResources.CopySecurityCode)
|
||||
else if (selection == AppResources.CopySecurityCode
|
||||
&&
|
||||
await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
||||
{
|
||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
|
||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
||||
}
|
||||
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
||||
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
|
||||
eventService.CollectAsync(EventType.Cipher_ClientCopiedCardCode, cipher.Id).FireAndForget();
|
||||
}
|
||||
else if (selection == AppResources.CopyNotes)
|
||||
{
|
||||
|
|
|
@ -60,9 +60,9 @@ namespace Bit.App.Utilities
|
|||
/// </summary>
|
||||
public class VerificationActionsFlowHelper : IVerificationActionsFlowHelper
|
||||
{
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
|
||||
private VerificationFlowAction? _action;
|
||||
private IActionFlowParmeters _parameters;
|
||||
|
@ -71,13 +71,14 @@ namespace Bit.App.Utilities
|
|||
|
||||
private readonly Dictionary<VerificationFlowAction, IActionFlowExecutioner> _actionExecutionerDictionary = new Dictionary<VerificationFlowAction, IActionFlowExecutioner>();
|
||||
|
||||
public VerificationActionsFlowHelper(IKeyConnectorService keyConnectorService,
|
||||
public VerificationActionsFlowHelper(
|
||||
IPasswordRepromptService passwordRepromptService,
|
||||
ICryptoService cryptoService)
|
||||
ICryptoService cryptoService,
|
||||
IUserVerificationService userVerificationService)
|
||||
{
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_passwordRepromptService = passwordRepromptService;
|
||||
_cryptoService = cryptoService;
|
||||
_userVerificationService = userVerificationService;
|
||||
|
||||
_actionExecutionerDictionary.Add(VerificationFlowAction.DeleteAccount, ServiceContainer.Resolve<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner"));
|
||||
}
|
||||
|
@ -107,9 +108,9 @@ namespace Bit.App.Utilities
|
|||
|
||||
public async Task ValidateAndExecuteAsync()
|
||||
{
|
||||
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
|
||||
? VerificationType.OTP
|
||||
: VerificationType.MasterPassword;
|
||||
var verificationType = await _userVerificationService.HasMasterPasswordAsync(true)
|
||||
? VerificationType.MasterPassword
|
||||
: VerificationType.OTP;
|
||||
|
||||
switch (verificationType)
|
||||
{
|
||||
|
@ -121,7 +122,7 @@ namespace Bit.App.Utilities
|
|||
}
|
||||
|
||||
var parameters = GetParameters();
|
||||
parameters.Secret = await _cryptoService.HashPasswordAsync(password, null);
|
||||
parameters.Secret = await _cryptoService.HashMasterKeyAsync(password, null);
|
||||
parameters.VerificationType = VerificationType.MasterPassword;
|
||||
await ExecuteAsync(parameters);
|
||||
break;
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
|
@ -46,7 +47,7 @@ namespace Bit.Core.Abstractions
|
|||
Task PutDeleteCipherAsync(string id);
|
||||
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
||||
Task RefreshIdentityTokenAsync();
|
||||
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
|
||||
Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier);
|
||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true);
|
||||
Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
|
||||
|
@ -70,11 +71,11 @@ namespace Bit.Core.Abstractions
|
|||
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
|
||||
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
|
||||
OrganizationUserResetPasswordEnrollmentRequest request);
|
||||
Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl);
|
||||
Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
|
||||
Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
|
||||
Task PostConvertToKeyConnector();
|
||||
Task PostLeaveOrganization(string id);
|
||||
Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl);
|
||||
Task PostMasterKeyToKeyConnectorAsync(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
|
||||
Task PostSetKeyConnectorKeyAsync(SetKeyConnectorKeyRequest request);
|
||||
Task PostConvertToKeyConnectorAsync();
|
||||
Task PostLeaveOrganizationAsync(string id);
|
||||
|
||||
Task<SendResponse> GetSendAsync(string id);
|
||||
Task<SendResponse> PostSendAsync(SendRequest request);
|
||||
|
@ -90,9 +91,12 @@ namespace Bit.Core.Abstractions
|
|||
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
||||
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
|
||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType);
|
||||
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
||||
Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier);
|
||||
Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest);
|
||||
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
|
||||
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
|
||||
Task<ConfigResponse> GetConfigsAsync();
|
||||
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
||||
}
|
||||
|
|
|
@ -27,14 +27,18 @@ namespace Bit.Core.Abstractions
|
|||
Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId);
|
||||
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
||||
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
|
||||
Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
|
||||
Task<AuthResult> LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
|
||||
|
||||
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
|
||||
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
||||
/// <summary>
|
||||
/// Gets a passwordless login request by <paramref name="id"/> and <paramref name="accessCode"/>. No authentication required.
|
||||
/// </summary>
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
|
||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email);
|
||||
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
|
||||
Task CreateNewSsoUserAsync(string organizationSsoId);
|
||||
|
||||
void LogOut(Action callback);
|
||||
void Init();
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
|
|||
{
|
||||
public interface IBiometricService
|
||||
{
|
||||
Task<bool> CanUseBiometricsUnlockAsync();
|
||||
Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null);
|
||||
Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
|
||||
}
|
||||
|
|
|
@ -9,49 +9,56 @@ namespace Bit.Core.Abstractions
|
|||
{
|
||||
public interface ICryptoService
|
||||
{
|
||||
Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null);
|
||||
Task ClearKeyAsync(string userId = null);
|
||||
Task ClearKeyHashAsync(string userId = null);
|
||||
Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null);
|
||||
Task ClearKeysAsync(string userId = null);
|
||||
Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null);
|
||||
Task ClearPinProtectedKeyAsync(string userId = null);
|
||||
void ClearCache();
|
||||
Task RefreshKeysAsync();
|
||||
Task SetUserKeyAsync(UserKey userKey, string userId = null);
|
||||
Task<UserKey> GetUserKeyAsync(string userId = null);
|
||||
Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null);
|
||||
Task<bool> HasUserKeyAsync(string userId = null);
|
||||
Task<bool> HasEncryptedUserKeyAsync(string userId = null);
|
||||
Task<UserKey> MakeUserKeyAsync();
|
||||
Task ClearUserKeyAsync(string userId = null);
|
||||
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
|
||||
Task<UserKey> GetAutoUnlockKeyAsync(string userId = null);
|
||||
Task<bool> HasAutoUnlockKeyAsync(string userId = null);
|
||||
Task<UserKey> GetBiometricUnlockKeyAsync(string userId = null);
|
||||
Task SetMasterKeyAsync(MasterKey masterKey, string userId = null);
|
||||
Task<MasterKey> GetMasterKeyAsync(string userId = null);
|
||||
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig);
|
||||
Task ClearMasterKeyAsync(string userId = null);
|
||||
Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null);
|
||||
Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null);
|
||||
Task<Tuple<SymmetricCryptoKey, EncString>> MakeDataEncKeyAsync(SymmetricCryptoKey key);
|
||||
Task<string> HashMasterKeyAsync(string password, MasterKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
|
||||
Task SetMasterKeyHashAsync(string keyHash);
|
||||
Task<string> GetMasterKeyHashAsync();
|
||||
Task ClearMasterKeyHashAsync(string userId = null);
|
||||
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, MasterKey key);
|
||||
Task SetOrgKeysAsync(IEnumerable<ProfileOrganizationResponse> orgs);
|
||||
Task<OrgKey> GetOrgKeyAsync(string orgId);
|
||||
Task<Dictionary<string, OrgKey>> GetOrgKeysAsync();
|
||||
Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null);
|
||||
Task<byte[]> GetUserPublicKeyAsync();
|
||||
Task SetUserPrivateKeyAsync(string encPrivateKey);
|
||||
Task<byte[]> GetUserPrivateKeyAsync();
|
||||
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
|
||||
Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
|
||||
Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null);
|
||||
Task<PinKey> MakePinKeyAsync(string pin, string salt, KdfConfig config);
|
||||
Task ClearPinKeysAsync(string userId = null);
|
||||
Task<UserKey> DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null);
|
||||
Task<MasterKey> DecryptMasterKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedMasterKey = null);
|
||||
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
|
||||
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
|
||||
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);
|
||||
Task<int> RandomNumberAsync(int min, int max);
|
||||
Task<string> RandomStringAsync(int length);
|
||||
Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
|
||||
Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null);
|
||||
Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null);
|
||||
Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
|
||||
Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
|
||||
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
|
||||
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
|
||||
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
|
||||
Task<SymmetricCryptoKey> GetKeyAsync(string userId = null);
|
||||
Task<string> GetKeyHashAsync();
|
||||
Task<SymmetricCryptoKey> GetOrgKeyAsync(string orgId);
|
||||
Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync();
|
||||
Task<byte[]> GetPrivateKeyAsync();
|
||||
Task<byte[]> GetPublicKeyAsync();
|
||||
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key);
|
||||
Task<bool> HasEncKeyAsync();
|
||||
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
|
||||
Task<bool> HasKeyAsync(string userId = null);
|
||||
Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
|
||||
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfConfig config);
|
||||
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfConfig config, EncString protectedKeyEs = null);
|
||||
Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
|
||||
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfConfig config);
|
||||
Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync();
|
||||
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
|
||||
Task<int> RandomNumberAsync(int min, int max);
|
||||
Task<string> RandomStringAsync(int length);
|
||||
Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
|
||||
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
|
||||
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);
|
||||
Task SetEncKeyAsync(string encKey);
|
||||
Task SetEncPrivateKeyAsync(string encPrivateKey);
|
||||
Task SetKeyAsync(SymmetricCryptoKey key);
|
||||
Task SetKeyHashAsync(string keyHash);
|
||||
Task SetOrgKeysAsync(IEnumerable<ProfileOrganizationResponse> orgs);
|
||||
Task ToggleKeyAsync();
|
||||
Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
|
||||
}
|
||||
}
|
||||
|
|
17
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
17
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IDeviceTrustCryptoService
|
||||
{
|
||||
Task<SymmetricCryptoKey> GetDeviceKeyAsync();
|
||||
Task<DeviceResponse> TrustDeviceAsync();
|
||||
Task<DeviceResponse> TrustDeviceIfNeededAsync();
|
||||
Task RemoveTrustedDeviceAsync();
|
||||
Task<bool> GetShouldTrustDeviceAsync();
|
||||
Task SetShouldTrustDeviceAsync(bool value);
|
||||
Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey);
|
||||
Task<bool> IsDeviceTrustedAsync();
|
||||
}
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Response;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IKeyConnectorService
|
||||
{
|
||||
Task SetUsesKeyConnector(bool usesKeyConnector);
|
||||
Task<bool> GetUsesKeyConnector();
|
||||
Task<bool> UserNeedsMigration();
|
||||
Task MigrateUser();
|
||||
Task GetAndSetKey(string url);
|
||||
Task<Organization> GetManagingOrganization();
|
||||
Task SetUsesKeyConnectorAsync(bool usesKeyConnector);
|
||||
Task<bool> GetUsesKeyConnectorAsync();
|
||||
Task<bool> UserNeedsMigrationAsync();
|
||||
Task MigrateUserAsync();
|
||||
Task SetMasterKeyFromUrlAsync(string url);
|
||||
Task<Organization> GetManagingOrganizationAsync();
|
||||
Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse);
|
||||
}
|
||||
}
|
||||
|
|
12
src/Core/Abstractions/IPasswordResetEnrollmentService.cs
Normal file
12
src/Core/Abstractions/IPasswordResetEnrollmentService.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IPasswordResetEnrollmentService
|
||||
{
|
||||
Task EnrollIfRequiredAsync(string organizationSsoId);
|
||||
Task EnrollAsync(string organizationId);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,14 @@ namespace Bit.Core.Abstractions
|
|||
public interface IStateService
|
||||
{
|
||||
List<AccountView> AccountViews { get; }
|
||||
Task<UserKey> GetUserKeyAsync(string userId = null);
|
||||
Task SetUserKeyAsync(UserKey value, string userId = null);
|
||||
Task<MasterKey> GetMasterKeyAsync(string userId = null);
|
||||
Task SetMasterKeyAsync(MasterKey value, string userId = null);
|
||||
Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null);
|
||||
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
|
||||
Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null);
|
||||
Task SetUserKeyAutoUnlockAsync(UserKey value, string userId = null);
|
||||
Task<string> GetActiveUserIdAsync();
|
||||
Task<string> GetActiveUserEmailAsync();
|
||||
Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper);
|
||||
|
@ -27,6 +35,8 @@ namespace Bit.Core.Abstractions
|
|||
Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync();
|
||||
Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value);
|
||||
Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null);
|
||||
Task<UserKey> GetUserKeyBiometricUnlockAsync(string userId = null);
|
||||
Task SetUserKeyBiometricUnlockAsync(UserKey value, string userId = null);
|
||||
Task<bool?> GetBiometricUnlockAsync(string userId = null);
|
||||
Task SetBiometricUnlockAsync(bool? value, string userId = null);
|
||||
Task<bool> GetBiometricLockedAsync(string userId = null);
|
||||
|
@ -36,26 +46,22 @@ namespace Bit.Core.Abstractions
|
|||
Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
|
||||
Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
|
||||
Task<bool> CanAccessPremiumAsync(string userId = null);
|
||||
Task SetPersonalPremiumAsync(bool value, string userId = null);
|
||||
Task<string> GetProtectedPinAsync(string userId = null);
|
||||
Task SetPersonalPremiumAsync(bool value, string userId = null);
|
||||
Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null);
|
||||
Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null);
|
||||
Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null);
|
||||
Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null);
|
||||
Task SetProtectedPinAsync(string value, string userId = null);
|
||||
Task<string> GetPinProtectedAsync(string userId = null);
|
||||
Task SetPinProtectedAsync(string value, string userId = null);
|
||||
Task<EncString> GetPinProtectedKeyAsync(string userId = null);
|
||||
Task SetPinProtectedKeyAsync(EncString value, string userId = null);
|
||||
Task SetKdfConfigurationAsync(KdfConfig config, string userId = null);
|
||||
Task<string> GetKeyEncryptedAsync(string userId = null);
|
||||
Task SetKeyEncryptedAsync(string value, string userId = null);
|
||||
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
|
||||
Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null);
|
||||
Task<string> GetKeyHashAsync(string userId = null);
|
||||
Task SetKeyHashAsync(string value, string userId = null);
|
||||
Task<string> GetEncKeyEncryptedAsync(string userId = null);
|
||||
Task SetEncKeyEncryptedAsync(string value, string userId = null);
|
||||
Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null);
|
||||
Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
|
||||
Task<string> GetPrivateKeyEncryptedAsync(string userId = null);
|
||||
Task SetPrivateKeyEncryptedAsync(string value, string userId = null);
|
||||
Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null);
|
||||
Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null);
|
||||
Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null);
|
||||
Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
|
||||
Task<bool?> GetAutofillTileAddedAsync();
|
||||
|
@ -172,9 +178,33 @@ namespace Bit.Core.Abstractions
|
|||
Task<string> GetAvatarColorAsync(string userId = null);
|
||||
Task<string> GetPreLoginEmailAsync();
|
||||
Task SetPreLoginEmailAsync(string value);
|
||||
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
|
||||
Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null);
|
||||
Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null);
|
||||
string GetLocale();
|
||||
void SetLocale(string locale);
|
||||
ConfigResponse GetConfigs();
|
||||
void SetConfigs(ConfigResponse value);
|
||||
Task<bool> GetShouldTrustDeviceAsync();
|
||||
Task SetShouldTrustDeviceAsync(bool value);
|
||||
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
Task<string> GetPinProtectedAsync(string userId = null);
|
||||
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
Task SetPinProtectedAsync(string value, string userId = null);
|
||||
[Obsolete("Use GetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
|
||||
Task<EncString> GetPinProtectedKeyAsync(string userId = null);
|
||||
[Obsolete("Use SetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
|
||||
Task SetPinProtectedKeyAsync(EncString value, string userId = null);
|
||||
[Obsolete("Use GetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
Task<string> GetEncKeyEncryptedAsync(string userId = null);
|
||||
[Obsolete("Use SetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
Task SetEncKeyEncryptedAsync(string value, string userId = null);
|
||||
[Obsolete("Left for migration purposes")]
|
||||
Task SetKeyEncryptedAsync(string value, string userId = null);
|
||||
|
||||
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
|
||||
Task<string> GetKeyEncryptedAsync(string userId = null);
|
||||
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
|
||||
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ namespace Bit.Core.Abstractions
|
|||
public interface IUserVerificationService
|
||||
{
|
||||
Task<bool> VerifyUser(string secret, VerificationType verificationType);
|
||||
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
|
@ -16,7 +17,7 @@ namespace Bit.Core.Abstractions
|
|||
Task<bool> ShouldLockAsync(string userId = null);
|
||||
Task<bool> IsLoggedOutByTimeoutAsync(string userId = null);
|
||||
Task<bool> ShouldLogOutByTimeoutAsync(string userId = null);
|
||||
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null);
|
||||
Task<PinLockType> GetPinLockTypeAsync(string userId = null);
|
||||
Task<bool> IsBiometricLockSetAsync(string userId = null);
|
||||
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
|
||||
Task LogOutAsync(bool userInitiated = true, string userId = null);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace Bit.Core
|
||||
using System;
|
||||
|
||||
namespace Bit.Core
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
|
@ -53,6 +55,7 @@
|
|||
public const string AppLocaleKey = "appLocale";
|
||||
public const string ClearSensitiveFields = "clearSensitiveFields";
|
||||
public const string ForceUpdatePassword = "forceUpdatePassword";
|
||||
public const string ShouldTrustDevice = "shouldTrustDevice";
|
||||
public const int SelectFileRequestCode = 42;
|
||||
public const int SelectFilePermissionRequestCode = 43;
|
||||
public const int SaveFileRequestCode = 44;
|
||||
|
@ -82,6 +85,9 @@
|
|||
|
||||
public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}";
|
||||
public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}";
|
||||
public static string MasterKeyEncryptedUserKeyKey(string userId) => $"masterKeyEncryptedUserKey_{userId}";
|
||||
public static string UserKeyAutoUnlockKey(string userId) => $"autoUnlock_{userId}";
|
||||
public static string UserKeyBiometricUnlockKey(string userId) => $"biometricUnlock_{userId}";
|
||||
public static string CiphersKey(string userId) => $"ciphers_{userId}";
|
||||
public static string FoldersKey(string userId) => $"folders_{userId}";
|
||||
public static string CollectionsKey(string userId) => $"collections_{userId}";
|
||||
|
@ -90,12 +96,11 @@
|
|||
public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}";
|
||||
public static string SendsKey(string userId) => $"sends_{userId}";
|
||||
public static string PoliciesKey(string userId) => $"policies_{userId}";
|
||||
public static string KeyKey(string userId) => $"key_{userId}";
|
||||
public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}";
|
||||
public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}";
|
||||
public static string EncKeyKey(string userId) => $"encKey_{userId}";
|
||||
public static string DeviceKeyKey(string userId) => $"deviceKey_{userId}";
|
||||
public static string KeyHashKey(string userId) => $"keyHash_{userId}";
|
||||
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}";
|
||||
public static string PinKeyEncryptedUserKeyKey(string userId) => $"pinKeyEncryptedUserKey_{userId}";
|
||||
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";
|
||||
public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}";
|
||||
public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}";
|
||||
|
@ -124,5 +129,12 @@
|
|||
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
|
||||
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
|
||||
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
|
||||
public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}";
|
||||
[Obsolete]
|
||||
public static string KeyKey(string userId) => $"key_{userId}";
|
||||
[Obsolete]
|
||||
public static string EncKeyKey(string userId) => $"encKey_{userId}";
|
||||
[Obsolete]
|
||||
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}";
|
||||
}
|
||||
}
|
||||
|
|
11
src/Core/Enums/AuthRequestType.cs
Normal file
11
src/Core/Enums/AuthRequestType.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum AuthRequestType : byte
|
||||
{
|
||||
AuthenticateAndUnlock = 0,
|
||||
Unlock = 1,
|
||||
AdminApproval = 2
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
namespace Bit.Core.Enums
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum DeviceType : byte
|
||||
{
|
||||
|
@ -24,4 +27,24 @@
|
|||
VivaldiExtension = 19,
|
||||
SafariExtension = 20
|
||||
}
|
||||
|
||||
public static class DeviceTypeExtensions
|
||||
{
|
||||
public static List<DeviceType> GetMobileTypes() => new List<DeviceType>
|
||||
{
|
||||
DeviceType.Android,
|
||||
DeviceType.AndroidAmazon,
|
||||
DeviceType.iOS
|
||||
};
|
||||
|
||||
public static List<DeviceType> GetDesktopTypes() => new List<DeviceType>
|
||||
{
|
||||
DeviceType.WindowsDesktop,
|
||||
DeviceType.MacOsDesktop,
|
||||
DeviceType.LinuxDesktop,
|
||||
DeviceType.UWP,
|
||||
};
|
||||
|
||||
public static List<DeviceType> GetDesktopAndMobileTypes() => GetMobileTypes().Concat(GetDesktopTypes()).ToList();
|
||||
}
|
||||
}
|
||||
|
|
12
src/Core/Exceptions/MasterKeyNullException.cs
Normal file
12
src/Core/Exceptions/MasterKeyNullException.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
namespace Bit.Core.Exceptions
|
||||
{
|
||||
public class MasterKeyNullException : Exception
|
||||
{
|
||||
public MasterKeyNullException()
|
||||
: base("MasterKey is null.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
src/Core/Exceptions/UserAndMasterKeysNullException.cs
Normal file
12
src/Core/Exceptions/UserAndMasterKeysNullException.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
namespace Bit.Core.Exceptions
|
||||
{
|
||||
public class UserAndMasterKeysNullException : Exception
|
||||
{
|
||||
public UserAndMasterKeysNullException()
|
||||
: base("UserKey and MasterKey are null.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
src/Core/Exceptions/UserKeyNullException.cs
Normal file
12
src/Core/Exceptions/UserKeyNullException.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
namespace Bit.Core.Exceptions
|
||||
{
|
||||
public class UserKeyNullException : Exception
|
||||
{
|
||||
public UserKeyNullException()
|
||||
: base("UserKey is null.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,6 +53,7 @@ namespace Bit.Core.Models.Domain
|
|||
HasPremiumPersonally = copy.HasPremiumPersonally;
|
||||
AvatarColor = copy.AvatarColor;
|
||||
ForcePasswordResetReason = copy.ForcePasswordResetReason;
|
||||
UserDecryptionOptions = copy.UserDecryptionOptions;
|
||||
}
|
||||
|
||||
public string UserId;
|
||||
|
@ -68,6 +69,7 @@ namespace Bit.Core.Models.Domain
|
|||
public bool? EmailVerified;
|
||||
public bool? HasPremiumPersonally;
|
||||
public ForcePasswordResetReason? ForcePasswordResetReason;
|
||||
public AccountDecryptionOptions UserDecryptionOptions;
|
||||
}
|
||||
|
||||
public class AccountTokens
|
||||
|
@ -117,9 +119,14 @@ namespace Bit.Core.Models.Domain
|
|||
|
||||
public class AccountVolatileData
|
||||
{
|
||||
public SymmetricCryptoKey Key;
|
||||
public EncString PinProtectedKey;
|
||||
public UserKey UserKey;
|
||||
public MasterKey MasterKey;
|
||||
public EncString PinKeyEncryptedUserKeyEphemeral;
|
||||
public bool? BiometricLocked;
|
||||
[Obsolete("Jul 6 2023: Key has been deprecated. We will use the User Key in the future. It remains here for migration during app upgrade.")]
|
||||
public SymmetricCryptoKey Key;
|
||||
[Obsolete("Jul 6 2023: PinProtectedKey has been deprecated in favor of UserKeyPinEphemeral. It remains here for migration during app upgrade.")]
|
||||
public EncString PinProtectedKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
27
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
27
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
public class AccountDecryptionOptions
|
||||
{
|
||||
public bool HasMasterPassword { get; set; }
|
||||
public TrustedDeviceOption TrustedDeviceOption { get; set; }
|
||||
public KeyConnectorOption KeyConnectorOption { get; set; }
|
||||
|
||||
public bool RequireSetPassword => !HasMasterPassword && KeyConnectorOption == null;
|
||||
}
|
||||
|
||||
public class TrustedDeviceOption
|
||||
{
|
||||
public bool HasAdminApproval { get; set; }
|
||||
public bool HasLoginApprovingDevice { get; set; }
|
||||
public bool HasManageResetPasswordPermission { get; set; }
|
||||
public string EncryptedPrivateKey { get; set; }
|
||||
public string EncryptedUserKey { get; set; }
|
||||
}
|
||||
|
||||
public class KeyConnectorOption
|
||||
{
|
||||
public string KeyConnectorUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Domain
|
||||
|
@ -8,6 +9,8 @@ namespace Bit.Core.Models.Domain
|
|||
public bool TwoFactor { get; set; }
|
||||
public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey);
|
||||
public string CaptchaSiteKey { get; set; }
|
||||
// TODO: PM-3287 - Remove after 3 releases of backwards compatibility - Target release 2023.12
|
||||
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
|
||||
public bool ResetMasterPassword { get; set; }
|
||||
public bool ForcePasswordReset { get; set; }
|
||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
||||
|
|
10
src/Core/Models/Domain/PendingAdminAuthRequest.cs
Normal file
10
src/Core/Models/Domain/PendingAdminAuthRequest.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
public class PendingAdminAuthRequest
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public byte[] PrivateKey { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -74,4 +74,32 @@ namespace Bit.Core.Models.Domain
|
|||
public string EncKeyB64 { get; set; }
|
||||
public string MacKeyB64 { get; set; }
|
||||
}
|
||||
|
||||
public class UserKey : SymmetricCryptoKey
|
||||
{
|
||||
public UserKey(byte[] key, EncryptionType? encType = null)
|
||||
: base(key, encType)
|
||||
{ }
|
||||
}
|
||||
|
||||
public class MasterKey : SymmetricCryptoKey
|
||||
{
|
||||
public MasterKey(byte[] key, EncryptionType? encType = null)
|
||||
: base(key, encType)
|
||||
{ }
|
||||
}
|
||||
|
||||
public class PinKey : SymmetricCryptoKey
|
||||
{
|
||||
public PinKey(byte[] key, EncryptionType? encType = null)
|
||||
: base(key, encType)
|
||||
{ }
|
||||
}
|
||||
|
||||
public class OrgKey : SymmetricCryptoKey
|
||||
{
|
||||
public OrgKey(byte[] key, EncryptionType? encType = null)
|
||||
: base(key, encType)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class PasswordlessCreateLoginRequest
|
||||
|
@ -25,10 +27,4 @@ namespace Bit.Core.Models.Request
|
|||
|
||||
public string FingerprintPhrase { get; set; }
|
||||
}
|
||||
|
||||
public enum AuthRequestType : byte
|
||||
{
|
||||
AuthenticateAndUnlock = 0,
|
||||
Unlock = 1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Bit.Core.Models.Request
|
|||
bool requestApproved)
|
||||
{
|
||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
MasterPasswordHash = masterPasswordHash ?? throw new ArgumentNullException(nameof(masterPasswordHash));
|
||||
MasterPasswordHash = masterPasswordHash;
|
||||
DeviceIdentifier = deviceIdentifier ?? throw new ArgumentNullException(nameof(deviceIdentifier));
|
||||
RequestApproved = requestApproved;
|
||||
}
|
||||
|
|
10
src/Core/Models/Request/TrustedDeviceKeysRequest.cs
Normal file
10
src/Core/Models/Request/TrustedDeviceKeysRequest.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class TrustedDeviceKeysRequest
|
||||
{
|
||||
public string EncryptedUserKey { get; set; }
|
||||
public string EncryptedPublicKey { get; set; }
|
||||
public string EncryptedPrivateKey { get; set; }
|
||||
}
|
||||
}
|
13
src/Core/Models/Response/DeviceResponse.cs
Normal file
13
src/Core/Models/Response/DeviceResponse.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Bit.Core.Enums;
|
||||
|
||||
public class DeviceResponse
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public DeviceType Type { get; set; }
|
||||
public string CreationDate { get; set; }
|
||||
public string EncryptedUserKey { get; set; }
|
||||
public string EncryptedPublicKey { get; set; }
|
||||
public string EncryptedPrivateKey { get; set; }
|
||||
}
|
|
@ -27,6 +27,7 @@ namespace Bit.Core.Models.Response
|
|||
public bool ForcePasswordReset { get; set; }
|
||||
public string KeyConnectorUrl { get; set; }
|
||||
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
||||
public AccountDecryptionOptions UserDecryptionOptions { get; set; }
|
||||
[JsonIgnore]
|
||||
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
|
||||
}
|
||||
|
|
|
@ -211,12 +211,12 @@ namespace Bit.Core.Services
|
|||
return SendAsync<DeleteAccountRequest, object>(HttpMethod.Delete, "/accounts", request, true, false);
|
||||
}
|
||||
|
||||
public Task PostConvertToKeyConnector()
|
||||
public Task PostConvertToKeyConnectorAsync()
|
||||
{
|
||||
return SendAsync<object, object>(HttpMethod.Post, "/accounts/convert-to-key-connector", null, true, false);
|
||||
}
|
||||
|
||||
public Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request)
|
||||
public Task PostSetKeyConnectorKeyAsync(SetKeyConnectorKeyRequest request)
|
||||
{
|
||||
return SendAsync<SetKeyConnectorKeyRequest>(HttpMethod.Post, "/accounts/set-key-connector-key", request, true);
|
||||
}
|
||||
|
@ -397,12 +397,38 @@ namespace Bit.Core.Services
|
|||
|
||||
#region Device APIs
|
||||
|
||||
|
||||
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
|
||||
{
|
||||
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
|
||||
{
|
||||
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
|
||||
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
|
||||
});
|
||||
}
|
||||
|
||||
public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
|
||||
{
|
||||
return SendAsync<DeviceTokenRequest, object>(
|
||||
HttpMethod.Put, $"/devices/identifier/{identifier}/token", request, true, false);
|
||||
}
|
||||
|
||||
public Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes)
|
||||
{
|
||||
return SendAsync<DeviceType[], bool>(
|
||||
HttpMethod.Post, "/devices/exist-by-types", deviceTypes, true, true);
|
||||
}
|
||||
|
||||
public Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier)
|
||||
{
|
||||
return SendAsync<object, DeviceResponse>(HttpMethod.Get, $"/devices/identifier/{deviceIdentifier}", null, true, true);
|
||||
}
|
||||
|
||||
public Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest trustedDeviceKeysRequest)
|
||||
{
|
||||
return SendAsync<TrustedDeviceKeysRequest, DeviceResponse>(HttpMethod.Put, $"/devices/{deviceIdentifier}/keys", trustedDeviceKeysRequest, true, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event APIs
|
||||
|
@ -460,7 +486,7 @@ namespace Bit.Core.Services
|
|||
$"/organizations/{identifier}/auto-enroll-status", null, true, true);
|
||||
}
|
||||
|
||||
public Task PostLeaveOrganization(string id)
|
||||
public Task PostLeaveOrganizationAsync(string id)
|
||||
{
|
||||
return SendAsync<object, object>(HttpMethod.Post, $"/organizations/{id}/leave", null, true, false);
|
||||
}
|
||||
|
@ -485,7 +511,7 @@ namespace Bit.Core.Services
|
|||
|
||||
#region Key Connector
|
||||
|
||||
public async Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl)
|
||||
public async Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl)
|
||||
{
|
||||
using (var requestMessage = new HttpRequestMessage())
|
||||
{
|
||||
|
@ -515,7 +541,7 @@ namespace Bit.Core.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
|
||||
public async Task PostMasterKeyToKeyConnectorAsync(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
|
||||
{
|
||||
using (var requestMessage = new HttpRequestMessage())
|
||||
{
|
||||
|
@ -565,9 +591,9 @@ namespace Bit.Core.Services
|
|||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true);
|
||||
}
|
||||
|
||||
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest)
|
||||
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType)
|
||||
{
|
||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, $"/auth-requests", passwordlessCreateLoginRequest, false, true);
|
||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, authRequestType == AuthRequestType.AdminApproval ? "/auth-requests/admin-request" : "/auth-requests", passwordlessCreateLoginRequest, authRequestType == AuthRequestType.AdminApproval, true);
|
||||
}
|
||||
|
||||
public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved)
|
||||
|
@ -576,15 +602,6 @@ namespace Bit.Core.Services
|
|||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Put, $"/auth-requests/{id}", request, true, true);
|
||||
}
|
||||
|
||||
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
|
||||
{
|
||||
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
|
||||
{
|
||||
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
|
||||
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Configs
|
||||
|
@ -610,7 +627,7 @@ namespace Bit.Core.Services
|
|||
return accessToken;
|
||||
}
|
||||
|
||||
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
|
||||
public async Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier)
|
||||
{
|
||||
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
||||
using (var requestMessage = new HttpRequestMessage())
|
||||
|
|
|
@ -27,10 +27,13 @@ namespace Bit.Core.Services
|
|||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private readonly IPasswordResetEnrollmentService _passwordResetEnrollmentService;
|
||||
private readonly bool _setCryptoKeys;
|
||||
|
||||
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
||||
private SymmetricCryptoKey _key;
|
||||
private MasterKey _masterKey;
|
||||
private UserKey _userKey;
|
||||
|
||||
private string _authedUserId;
|
||||
private MasterPasswordPolicyOptions _masterPasswordPolicy;
|
||||
|
@ -46,10 +49,11 @@ namespace Bit.Core.Services
|
|||
II18nService i18nService,
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
IMessagingService messagingService,
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
IPasswordGenerationService passwordGenerationService,
|
||||
IPolicyService policyService,
|
||||
IDeviceTrustCryptoService deviceTrustCryptoService,
|
||||
IPasswordResetEnrollmentService passwordResetEnrollmentService,
|
||||
bool setCryptoKeys = true)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
|
@ -64,6 +68,8 @@ namespace Bit.Core.Services
|
|||
_keyConnectorService = keyConnectorService;
|
||||
_passwordGenerationService = passwordGenerationService;
|
||||
_policyService = policyService;
|
||||
_deviceTrustCryptoService = deviceTrustCryptoService;
|
||||
_passwordResetEnrollmentService = passwordResetEnrollmentService;
|
||||
_setCryptoKeys = setCryptoKeys;
|
||||
|
||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
|
@ -145,8 +151,8 @@ namespace Bit.Core.Services
|
|||
SelectedTwoFactorProviderType = null;
|
||||
_2faForcePasswordResetReason = null;
|
||||
var key = await MakePreloginKeyAsync(masterPassword, email);
|
||||
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
||||
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
|
||||
var hashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key);
|
||||
var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
|
||||
var result = await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null, captchaToken);
|
||||
|
||||
if (await RequirePasswordChangeAsync(email, masterPassword))
|
||||
|
@ -195,18 +201,50 @@ namespace Bit.Core.Services
|
|||
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered)
|
||||
public async Task<AuthResult> LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
|
||||
{
|
||||
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey);
|
||||
var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey);
|
||||
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new SymmetricCryptoKey(decKey), null, null,
|
||||
var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, decryptionKey);
|
||||
|
||||
// If the user is already authenticated, we can just set the key
|
||||
// Note: We can't check for the existance of an access token here because the active user id may not be null
|
||||
if (authingWithSso)
|
||||
{
|
||||
if (string.IsNullOrEmpty(masterKeyHash))
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
var masterKey = new MasterKey(decryptedKey);
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetUserKeyAsync(userKey);
|
||||
}
|
||||
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
|
||||
return null;
|
||||
}
|
||||
|
||||
// The approval device may not have a master key hash if it authenticated with a passwordless method
|
||||
if (string.IsNullOrEmpty(masterKeyHash) && decryptionKey != null)
|
||||
{
|
||||
return await LogInHelperAsync(email, accessCode, null, null, null, null, null, null, null, null, null, authRequestId: authRequestId, userKey2FA: new UserKey(decryptedKey));
|
||||
}
|
||||
|
||||
var decKeyHash = await _cryptoService.RsaDecryptAsync(masterKeyHash, decryptionKey);
|
||||
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decKeyHash), null, null, null, new MasterKey(decryptedKey), null, null,
|
||||
null, null, authRequestId: authRequestId);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId)
|
||||
{
|
||||
SelectedTwoFactorProviderType = null;
|
||||
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
|
||||
var result = await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
|
||||
if (result.ForcePasswordReset)
|
||||
{
|
||||
await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.AdminForcePasswordReset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
|
||||
|
@ -216,8 +254,8 @@ namespace Bit.Core.Services
|
|||
{
|
||||
CaptchaToken = captchaToken;
|
||||
}
|
||||
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
|
||||
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId);
|
||||
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _masterKey,
|
||||
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId, userKey2FA: _userKey);
|
||||
|
||||
// If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync()
|
||||
if (!string.IsNullOrEmpty(_authedUserId) && _2faForcePasswordResetReason.HasValue)
|
||||
|
@ -236,8 +274,8 @@ namespace Bit.Core.Services
|
|||
{
|
||||
SelectedTwoFactorProviderType = null;
|
||||
var key = await MakePreloginKeyAsync(masterPassword, email);
|
||||
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
||||
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
|
||||
var hashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key);
|
||||
var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
|
||||
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, twoFactorProvider,
|
||||
twoFactorToken, remember);
|
||||
}
|
||||
|
@ -337,7 +375,7 @@ namespace Bit.Core.Services
|
|||
|
||||
// Helpers
|
||||
|
||||
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
|
||||
private async Task<MasterKey> MakePreloginKeyAsync(string masterPassword, string email)
|
||||
{
|
||||
email = email.Trim().ToLower();
|
||||
KdfConfig kdfConfig = KdfConfig.Default;
|
||||
|
@ -356,13 +394,13 @@ namespace Bit.Core.Services
|
|||
throw;
|
||||
}
|
||||
}
|
||||
return await _cryptoService.MakeKeyAsync(masterPassword, email, kdfConfig);
|
||||
return await _cryptoService.MakeMasterKeyAsync(masterPassword, email, kdfConfig);
|
||||
}
|
||||
|
||||
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
|
||||
string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
|
||||
string code, string codeVerifier, string redirectUrl, MasterKey masterKey,
|
||||
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null,
|
||||
string captchaToken = null, string orgId = null, string authRequestId = null)
|
||||
string captchaToken = null, string orgId = null, string authRequestId = null, UserKey userKey2FA = null)
|
||||
{
|
||||
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
|
||||
var appId = await _appIdService.GetAppIdAsync();
|
||||
|
@ -426,7 +464,8 @@ namespace Bit.Core.Services
|
|||
Code = code;
|
||||
CodeVerifier = codeVerifier;
|
||||
SsoRedirectUrl = redirectUrl;
|
||||
_key = _setCryptoKeys ? key : null;
|
||||
_masterKey = _setCryptoKeys ? masterKey : null;
|
||||
_userKey = userKey2FA;
|
||||
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
||||
|
@ -459,6 +498,7 @@ namespace Bit.Core.Services
|
|||
ForcePasswordResetReason = result.ForcePasswordReset
|
||||
? ForcePasswordResetReason.AdminForcePasswordReset
|
||||
: (ForcePasswordResetReason?)null,
|
||||
UserDecryptionOptions = tokenResponse.UserDecryptionOptions,
|
||||
},
|
||||
new Account.AccountTokens()
|
||||
{
|
||||
|
@ -470,24 +510,55 @@ namespace Bit.Core.Services
|
|||
_messagingService.Send("accountAdded");
|
||||
if (_setCryptoKeys)
|
||||
{
|
||||
if (key != null)
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
}
|
||||
|
||||
if (localHashedPassword != null)
|
||||
{
|
||||
await _cryptoService.SetKeyHashAsync(localHashedPassword);
|
||||
await _cryptoService.SetMasterKeyHashAsync(localHashedPassword);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
}
|
||||
|
||||
// Trusted Device
|
||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
var hasUserKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (decryptOptions?.TrustedDeviceOption != null && !hasUserKey &&
|
||||
decryptOptions.TrustedDeviceOption.EncryptedPrivateKey != null &&
|
||||
decryptOptions.TrustedDeviceOption.EncryptedUserKey != null)
|
||||
{
|
||||
var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey,
|
||||
decryptOptions.TrustedDeviceOption.EncryptedUserKey);
|
||||
if (key != null)
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (code == null || tokenResponse.Key != null)
|
||||
{
|
||||
if (tokenResponse.KeyConnectorUrl != null)
|
||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
||||
|
||||
// Key Connector
|
||||
if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
|
||||
{
|
||||
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl);
|
||||
var url = tokenResponse.KeyConnectorUrl ?? decryptOptions.KeyConnectorOption.KeyConnectorUrl;
|
||||
await _keyConnectorService.SetMasterKeyFromUrlAsync(url);
|
||||
}
|
||||
|
||||
await _cryptoService.SetEncKeyAsync(tokenResponse.Key);
|
||||
// Login with Device
|
||||
if (masterKey != null && !string.IsNullOrEmpty(authRequestId))
|
||||
{
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
}
|
||||
else if (userKey2FA != null)
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(userKey2FA);
|
||||
}
|
||||
|
||||
// Decrypt UserKey with MasterKey
|
||||
masterKey ??= await _stateService.GetMasterKeyAsync();
|
||||
if (masterKey != null)
|
||||
{
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetUserKeyAsync(userKey);
|
||||
}
|
||||
|
||||
// User doesn't have a key pair yet (old account), let's generate one for them.
|
||||
if (tokenResponse.PrivateKey == null)
|
||||
|
@ -505,40 +576,20 @@ namespace Bit.Core.Services
|
|||
catch { }
|
||||
}
|
||||
|
||||
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
|
||||
await _cryptoService.SetUserPrivateKeyAsync(tokenResponse.PrivateKey);
|
||||
}
|
||||
else if (tokenResponse.KeyConnectorUrl != null)
|
||||
{
|
||||
// SSO Key Connector Onboarding
|
||||
var password = await _cryptoFunctionService.RandomBytesAsync(64);
|
||||
var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
|
||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64);
|
||||
await _cryptoService.SetKeyAsync(k);
|
||||
|
||||
var encKey = await _cryptoService.MakeEncKeyAsync(k);
|
||||
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||
var keyPair = await _cryptoService.MakeKeyPairAsync();
|
||||
|
||||
try
|
||||
// New User has tokenResponse.Key == null
|
||||
if (tokenResponse.Key == null)
|
||||
{
|
||||
await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
|
||||
await _keyConnectorService.ConvertNewUserToKeyConnectorAsync(orgId, tokenResponse);
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
throw new Exception("Unable to reach Key Connector", e);
|
||||
await _keyConnectorService.SetMasterKeyFromUrlAsync(tokenResponse.KeyConnectorUrl);
|
||||
}
|
||||
|
||||
var keys = new KeysRequest
|
||||
{
|
||||
PublicKey = keyPair.Item1,
|
||||
EncryptedPrivateKey = keyPair.Item2.EncryptedString
|
||||
};
|
||||
var setPasswordRequest = new SetKeyConnectorKeyRequest(
|
||||
encKey.Item2.EncryptedString, keys, tokenResponse.KdfConfig, orgId
|
||||
);
|
||||
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_authedUserId = _tokenService.GetUserId();
|
||||
|
@ -549,7 +600,7 @@ namespace Bit.Core.Services
|
|||
|
||||
private void ClearState()
|
||||
{
|
||||
_key = null;
|
||||
_masterKey = null;
|
||||
Email = null;
|
||||
CaptchaToken = null;
|
||||
MasterPasswordHash = null;
|
||||
|
@ -575,14 +626,22 @@ namespace Bit.Core.Services
|
|||
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
|
||||
return await PopulateFingerprintPhrasesAsync(activeRequests);
|
||||
}
|
||||
|
||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
||||
{
|
||||
var response = await _apiService.GetAuthRequestAsync(id);
|
||||
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||
try
|
||||
{
|
||||
var response = await _apiService.GetAuthRequestAsync(id);
|
||||
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||
}
|
||||
catch (ApiException ex) when (ex.Error?.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
// Thrown when request expires and purge job erases it from the db
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode)
|
||||
/// <inheritdoc />
|
||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode)
|
||||
{
|
||||
return await _apiService.GetAuthResponseAsync(id, accessCode);
|
||||
}
|
||||
|
@ -590,15 +649,36 @@ namespace Bit.Core.Services
|
|||
public async Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved)
|
||||
{
|
||||
var publicKey = CoreHelpers.Base64UrlDecode(pubKey);
|
||||
var masterKey = await _cryptoService.GetKeyAsync();
|
||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
|
||||
var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey);
|
||||
var masterKey = await _cryptoService.GetMasterKeyAsync();
|
||||
byte[] keyToEncrypt = null;
|
||||
EncString encryptedMasterPassword = null;
|
||||
|
||||
if (masterKey == null)
|
||||
{
|
||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||
if (userKey == null)
|
||||
{
|
||||
throw new UserAndMasterKeysNullException();
|
||||
}
|
||||
keyToEncrypt = userKey.Key;
|
||||
}
|
||||
else
|
||||
{
|
||||
keyToEncrypt = masterKey.Key;
|
||||
var keyHash = await _stateService.GetKeyHashAsync();
|
||||
if (!string.IsNullOrEmpty(keyHash))
|
||||
{
|
||||
encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(keyHash), publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(keyToEncrypt, publicKey);
|
||||
var deviceId = await _appIdService.GetAppIdAsync();
|
||||
var response = await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword.EncryptedString, deviceId, requestApproved);
|
||||
var response = await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword?.EncryptedString, deviceId, requestApproved);
|
||||
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||
}
|
||||
|
||||
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email)
|
||||
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType)
|
||||
{
|
||||
var deviceId = await _appIdService.GetAppIdAsync();
|
||||
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||
|
@ -606,8 +686,8 @@ namespace Bit.Core.Services
|
|||
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
||||
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
||||
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
|
||||
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, AuthRequestType.AuthenticateAndUnlock, fingerprintPhrase);
|
||||
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest);
|
||||
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, authRequestType, fingerprintPhrase);
|
||||
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest, authRequestType);
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
|
@ -638,5 +718,22 @@ namespace Bit.Core.Services
|
|||
passwordlessLogin.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(userEmail, CoreHelpers.Base64UrlDecode(passwordlessLogin.PublicKey)));
|
||||
return passwordlessLogin;
|
||||
}
|
||||
|
||||
public async Task CreateNewSsoUserAsync(string organizationSsoId)
|
||||
{
|
||||
var orgAutoEnrollStatusResponse = await _apiService.GetOrganizationAutoEnrollStatusAsync(organizationSsoId);
|
||||
var randomBytes = _cryptoFunctionService.RandomBytes(64);
|
||||
var userKey = new UserKey(randomBytes);
|
||||
var (userPubKey, userPrivKey) = await _cryptoService.MakeKeyPairAsync(userKey);
|
||||
await _apiService.PostAccountKeysAsync(new KeysRequest
|
||||
{
|
||||
PublicKey = userPubKey,
|
||||
EncryptedPrivateKey = userPrivKey.EncryptedString
|
||||
});
|
||||
|
||||
await _stateService.SetUserKeyAsync(userKey);
|
||||
await _stateService.SetPrivateKeyEncryptedAsync(userPrivKey.EncryptedString);
|
||||
await _passwordResetEnrollmentService.EnrollAsync(orgAutoEnrollStatusResponse.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,10 +250,9 @@ namespace Bit.Core.Services
|
|||
{
|
||||
try
|
||||
{
|
||||
var hashKey = await _cryptoService.HasKeyAsync();
|
||||
if (!hashKey)
|
||||
if (!await _cryptoService.HasUserKeyAsync())
|
||||
{
|
||||
throw new Exception("No key.");
|
||||
throw new UserKeyNullException();
|
||||
}
|
||||
var decCiphers = new List<CipherView>();
|
||||
async Task decryptAndAddCipherAsync(Cipher cipher)
|
||||
|
@ -591,9 +590,9 @@ namespace Bit.Core.Services
|
|||
|
||||
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
|
||||
{
|
||||
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
||||
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
|
||||
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
|
||||
var (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(cipher.OrganizationId);
|
||||
|
||||
var encFileName = await _cryptoService.EncryptAsync(filename, encKey);
|
||||
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
|
||||
|
||||
CipherResponse response;
|
||||
|
@ -601,7 +600,7 @@ namespace Bit.Core.Services
|
|||
{
|
||||
var request = new AttachmentRequest
|
||||
{
|
||||
Key = orgEncAttachmentKey.EncryptedString,
|
||||
Key = protectedAttachmentKey.EncryptedString,
|
||||
FileName = encFileName.EncryptedString,
|
||||
FileSize = encFileData.Buffer.Length,
|
||||
};
|
||||
|
@ -612,7 +611,7 @@ namespace Bit.Core.Services
|
|||
}
|
||||
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
|
||||
{
|
||||
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey);
|
||||
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, protectedAttachmentKey);
|
||||
}
|
||||
|
||||
var userId = await _stateService.GetActiveUserIdAsync();
|
||||
|
@ -830,6 +829,14 @@ namespace Bit.Core.Services
|
|||
|
||||
// Helpers
|
||||
|
||||
private async Task<Tuple<SymmetricCryptoKey, EncString, SymmetricCryptoKey>> MakeAttachmentKeyAsync(string organizationId)
|
||||
{
|
||||
var encryptionKey = await _cryptoService.GetOrgKeyAsync(organizationId)
|
||||
?? (SymmetricCryptoKey)await _cryptoService.GetUserKeyWithLegacySupportAsync();
|
||||
var (attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(encryptionKey);
|
||||
return new Tuple<SymmetricCryptoKey, EncString, SymmetricCryptoKey>(attachmentKey, protectedAttachmentKey, encryptionKey);
|
||||
}
|
||||
|
||||
private async Task ShareAttachmentWithServerAsync(AttachmentView attachmentView, string cipherId,
|
||||
string organizationId)
|
||||
{
|
||||
|
@ -841,14 +848,16 @@ namespace Bit.Core.Services
|
|||
|
||||
var bytes = await attachmentResponse.Content.ReadAsByteArrayAsync();
|
||||
var decBytes = await _cryptoService.DecryptFromBytesAsync(bytes, null);
|
||||
var key = await _cryptoService.GetOrgKeyAsync(organizationId);
|
||||
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, key);
|
||||
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||
var encData = await _cryptoService.EncryptToBytesAsync(decBytes, dataEncKey.Item1);
|
||||
|
||||
var (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(organizationId);
|
||||
|
||||
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, encKey);
|
||||
var encFileData = await _cryptoService.EncryptToBytesAsync(decBytes, attachmentKey);
|
||||
|
||||
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
||||
var fd = new MultipartFormDataContent(boundary);
|
||||
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
|
||||
fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString);
|
||||
fd.Add(new StringContent(protectedAttachmentKey.EncryptedString), "key");
|
||||
fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString);
|
||||
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace Bit.Core.Services
|
|||
{
|
||||
return _decryptedCollectionCache;
|
||||
}
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
throw new Exception("No key.");
|
||||
|
|
File diff suppressed because it is too large
Load diff
134
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
134
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class DeviceTrustCryptoService : IDeviceTrustCryptoService
|
||||
{
|
||||
private readonly IApiService _apiService;
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IStateService _stateService;
|
||||
|
||||
private const int DEVICE_KEY_SIZE = 64;
|
||||
|
||||
public DeviceTrustCryptoService(
|
||||
IApiService apiService,
|
||||
IAppIdService appIdService,
|
||||
ICryptoFunctionService cryptoFunctionService,
|
||||
ICryptoService cryptoService,
|
||||
IStateService stateService)
|
||||
{
|
||||
_apiService = apiService;
|
||||
_appIdService = appIdService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
_cryptoService = cryptoService;
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync()
|
||||
{
|
||||
return await _stateService.GetDeviceKeyAsync();
|
||||
}
|
||||
|
||||
private async Task SetDeviceKeyAsync(SymmetricCryptoKey deviceKey)
|
||||
{
|
||||
await _stateService.SetDeviceKeyAsync(deviceKey);
|
||||
}
|
||||
|
||||
public async Task RemoveTrustedDeviceAsync()
|
||||
{
|
||||
await SetDeviceKeyAsync(null);
|
||||
}
|
||||
|
||||
public async Task<DeviceResponse> TrustDeviceAsync()
|
||||
{
|
||||
// Attempt to get user key
|
||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||
if (userKey == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Generate deviceKey
|
||||
var deviceKey = await MakeDeviceKeyAsync();
|
||||
|
||||
// Generate asymmetric RSA key pair: devicePrivateKey, devicePublicKey
|
||||
var (devicePublicKey, devicePrivateKey) = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||
|
||||
// Send encrypted keys to server
|
||||
var deviceIdentifier = await _appIdService.GetAppIdAsync();
|
||||
var deviceRequest = new TrustedDeviceKeysRequest
|
||||
{
|
||||
EncryptedUserKey = (await _cryptoService.RsaEncryptAsync(userKey.Key, devicePublicKey)).EncryptedString,
|
||||
EncryptedPublicKey = (await _cryptoService.EncryptAsync(devicePublicKey, userKey)).EncryptedString,
|
||||
EncryptedPrivateKey = (await _cryptoService.EncryptAsync(devicePrivateKey, deviceKey)).EncryptedString,
|
||||
};
|
||||
|
||||
var deviceResponse = await _apiService.UpdateTrustedDeviceKeysAsync(deviceIdentifier, deviceRequest);
|
||||
|
||||
// Store device key if successful
|
||||
await SetDeviceKeyAsync(deviceKey);
|
||||
return deviceResponse;
|
||||
}
|
||||
|
||||
private async Task<SymmetricCryptoKey> MakeDeviceKeyAsync()
|
||||
{
|
||||
// Create 512-bit device key
|
||||
var randomBytes = await _cryptoFunctionService.RandomBytesAsync(DEVICE_KEY_SIZE);
|
||||
return new SymmetricCryptoKey(randomBytes);
|
||||
}
|
||||
|
||||
public async Task<bool> GetShouldTrustDeviceAsync()
|
||||
{
|
||||
return await _stateService.GetShouldTrustDeviceAsync();
|
||||
}
|
||||
|
||||
public async Task SetShouldTrustDeviceAsync(bool value)
|
||||
{
|
||||
await _stateService.SetShouldTrustDeviceAsync(value);
|
||||
}
|
||||
|
||||
public async Task<DeviceResponse> TrustDeviceIfNeededAsync()
|
||||
{
|
||||
if (!await GetShouldTrustDeviceAsync())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var response = await TrustDeviceAsync();
|
||||
await SetShouldTrustDeviceAsync(false);
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<bool> IsDeviceTrustedAsync()
|
||||
{
|
||||
var existingDeviceKey = await GetDeviceKeyAsync();
|
||||
return existingDeviceKey != null;
|
||||
}
|
||||
|
||||
public async Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey)
|
||||
{
|
||||
var existingDeviceKey = await GetDeviceKeyAsync();
|
||||
if (existingDeviceKey == null)
|
||||
{
|
||||
// User doesn't have a device key anymore so device is untrusted
|
||||
return null;
|
||||
}
|
||||
|
||||
// Attempt to decrypt encryptedDevicePrivateKey with device key
|
||||
var devicePrivateKeyBytes = await _cryptoService.DecryptToBytesAsync(
|
||||
new EncString(encryptedDevicePrivateKey),
|
||||
existingDeviceKey
|
||||
);
|
||||
|
||||
// Attempt to decrypt encryptedUserDataKey with devicePrivateKey
|
||||
var userKeyBytes = await _cryptoService.RsaDecryptAsync(encryptedUserKey, devicePrivateKeyBytes);
|
||||
return new UserKey(userKeyBytes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
|
@ -77,10 +78,10 @@ namespace Bit.Core.Services
|
|||
{
|
||||
return _decryptedFolderCache;
|
||||
}
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
throw new Exception("No key.");
|
||||
throw new UserKeyNullException();
|
||||
}
|
||||
var decFolders = new List<FolderView>();
|
||||
async Task decryptAndAddFolderAsync(Folder folder)
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
|
@ -12,26 +13,28 @@ namespace Bit.Core.Services
|
|||
private readonly ICryptoService _cryptoService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
|
||||
public KeyConnectorService(IStateService stateService, ICryptoService cryptoService,
|
||||
ITokenService tokenService, IApiService apiService, OrganizationService organizationService)
|
||||
ITokenService tokenService, IApiService apiService, ICryptoFunctionService cryptoFunctionService, OrganizationService organizationService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_cryptoService = cryptoService;
|
||||
_tokenService = tokenService;
|
||||
_apiService = apiService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
_organizationService = organizationService;
|
||||
}
|
||||
|
||||
public async Task GetAndSetKey(string url)
|
||||
public async Task SetMasterKeyFromUrlAsync(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userKeyResponse = await _apiService.GetUserKeyFromKeyConnector(url);
|
||||
var keyArr = Convert.FromBase64String(userKeyResponse.Key);
|
||||
var k = new SymmetricCryptoKey(keyArr);
|
||||
await _cryptoService.SetKeyAsync(k);
|
||||
var masterKeyResponse = await _apiService.GetMasterKeyFromKeyConnectorAsync(url);
|
||||
var masterKeyBytes = Convert.FromBase64String(masterKeyResponse.Key);
|
||||
var masterKey = new MasterKey(masterKeyBytes);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -39,17 +42,17 @@ namespace Bit.Core.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task SetUsesKeyConnector(bool usesKeyConnector)
|
||||
public async Task SetUsesKeyConnectorAsync(bool usesKeyConnector)
|
||||
{
|
||||
await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector);
|
||||
}
|
||||
|
||||
public async Task<bool> GetUsesKeyConnector()
|
||||
public async Task<bool> GetUsesKeyConnectorAsync()
|
||||
{
|
||||
return await _stateService.GetUsesKeyConnectorAsync();
|
||||
}
|
||||
|
||||
public async Task<Organization> GetManagingOrganization()
|
||||
public async Task<Organization> GetManagingOrganizationAsync()
|
||||
{
|
||||
var orgs = await _organizationService.GetAllAsync();
|
||||
return orgs.Find(o =>
|
||||
|
@ -57,31 +60,66 @@ namespace Bit.Core.Services
|
|||
!o.IsAdmin);
|
||||
}
|
||||
|
||||
public async Task MigrateUser()
|
||||
public async Task MigrateUserAsync()
|
||||
{
|
||||
var organization = await GetManagingOrganization();
|
||||
var key = await _cryptoService.GetKeyAsync();
|
||||
var organization = await GetManagingOrganizationAsync();
|
||||
var masterKey = await _cryptoService.GetMasterKeyAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(key.EncKeyB64);
|
||||
await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest);
|
||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64);
|
||||
await _apiService.PostMasterKeyToKeyConnectorAsync(organization.KeyConnectorUrl, keyConnectorRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Unable to reach Key Connector", e);
|
||||
}
|
||||
|
||||
await _apiService.PostConvertToKeyConnector();
|
||||
await _apiService.PostConvertToKeyConnectorAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> UserNeedsMigration()
|
||||
public async Task<bool> UserNeedsMigrationAsync()
|
||||
{
|
||||
var loggedInUsingSso = await _tokenService.GetIsExternal();
|
||||
var requiredByOrganization = await GetManagingOrganization() != null;
|
||||
var userIsNotUsingKeyConnector = !await GetUsesKeyConnector();
|
||||
var requiredByOrganization = await GetManagingOrganizationAsync() != null;
|
||||
var userIsNotUsingKeyConnector = !await GetUsesKeyConnectorAsync();
|
||||
|
||||
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
|
||||
}
|
||||
|
||||
public async Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse)
|
||||
{
|
||||
// SSO Key Connector Onboarding
|
||||
var password = await _cryptoFunctionService.RandomBytesAsync(64);
|
||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
|
||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
|
||||
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
||||
|
||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
|
||||
newMasterKey,
|
||||
await _cryptoService.MakeUserKeyAsync());
|
||||
|
||||
await _cryptoService.SetUserKeyAsync(newUserKey);
|
||||
|
||||
try
|
||||
{
|
||||
await _apiService.PostMasterKeyToKeyConnectorAsync(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Unable to reach Key Connector", e);
|
||||
}
|
||||
|
||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
|
||||
var keys = new KeysRequest
|
||||
{
|
||||
PublicKey = newPublicKey,
|
||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
||||
};
|
||||
var setPasswordRequest = new SetKeyConnectorKeyRequest(
|
||||
newProtectedUserKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
|
||||
);
|
||||
await _apiService.PostSetKeyConnectorKeyAsync(setPasswordRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ namespace Bit.Core.Services
|
|||
|
||||
public async Task<List<GeneratedPasswordHistory>> GetHistoryAsync()
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
return new List<GeneratedPasswordHistory>();
|
||||
|
@ -262,7 +262,7 @@ namespace Bit.Core.Services
|
|||
|
||||
public async Task AddHistoryAsync(string password, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
return;
|
||||
|
|
62
src/Core/Services/PasswordResetEnrollmentService.cs
Normal file
62
src/Core/Services/PasswordResetEnrollmentService.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class PasswordResetEnrollmentService : IPasswordResetEnrollmentService
|
||||
{
|
||||
private readonly IApiService _apiService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IStateService _stateService;
|
||||
|
||||
public PasswordResetEnrollmentService(IApiService apiService,
|
||||
ICryptoService cryptoService,
|
||||
IOrganizationService organizationService,
|
||||
IStateService stateService)
|
||||
{
|
||||
_apiService = apiService;
|
||||
_cryptoService = cryptoService;
|
||||
_organizationService = organizationService;
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task EnrollIfRequiredAsync(string organizationSsoId)
|
||||
{
|
||||
var orgAutoEnrollStatusResponse = await _apiService.GetOrganizationAutoEnrollStatusAsync(organizationSsoId);
|
||||
|
||||
if (!orgAutoEnrollStatusResponse?.ResetPasswordEnabled ?? false)
|
||||
{
|
||||
await EnrollAsync(orgAutoEnrollStatusResponse.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EnrollAsync(string organizationId)
|
||||
{
|
||||
var orgKeyResponse = await _apiService.GetOrganizationKeysAsync(organizationId);
|
||||
if (orgKeyResponse == null)
|
||||
{
|
||||
throw new Exception("Organization keys missing");
|
||||
}
|
||||
|
||||
var userId = await _stateService.GetActiveUserIdAsync();
|
||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||
var orgPublicKey = CoreHelpers.Base64UrlDecode(orgKeyResponse.PublicKey);
|
||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, orgPublicKey);
|
||||
|
||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
resetRequest.ResetPasswordKey = encryptedKey.EncryptedString;
|
||||
|
||||
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(
|
||||
organizationId,
|
||||
userId,
|
||||
resetRequest
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ namespace Bit.Core.Services
|
|||
return _decryptedSendsCache;
|
||||
}
|
||||
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
throw new Exception("No Key.");
|
||||
|
|
|
@ -241,6 +241,19 @@ namespace Bit.Core.Services
|
|||
))?.Settings?.EnvironmentUrls;
|
||||
}
|
||||
|
||||
public async Task<UserKey> GetUserKeyBiometricUnlockAsync(string userId = null)
|
||||
{
|
||||
var keyB64 = await _storageMediatorService.GetAsync<string>(
|
||||
await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), true);
|
||||
return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64));
|
||||
}
|
||||
|
||||
public async Task SetUserKeyBiometricUnlockAsync(UserKey value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(
|
||||
await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), value?.KeyB64, true);
|
||||
}
|
||||
|
||||
public async Task<bool?> GetBiometricUnlockAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
|
@ -302,12 +315,70 @@ namespace Bit.Core.Services
|
|||
true, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<UserKey> GetUserKeyAsync(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.VolatileData?.UserKey;
|
||||
}
|
||||
|
||||
public async Task SetUserKeyAsync(UserKey value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.VolatileData.UserKey = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<MasterKey> GetMasterKeyAsync(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.VolatileData?.MasterKey;
|
||||
}
|
||||
|
||||
public async Task SetMasterKeyAsync(MasterKey value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.VolatileData.MasterKey = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null)
|
||||
{
|
||||
return await _storageMediatorService.GetAsync<string>(
|
||||
await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), false);
|
||||
}
|
||||
|
||||
public async Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(
|
||||
await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), value, false);
|
||||
}
|
||||
|
||||
public async Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null)
|
||||
{
|
||||
var keyB64 = await _storageMediatorService.GetAsync<string>(
|
||||
await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), true);
|
||||
return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64));
|
||||
}
|
||||
|
||||
public async Task SetUserKeyAutoUnlockAsync(UserKey value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(
|
||||
await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), value?.KeyB64, true);
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessPremiumAsync(string userId = null)
|
||||
{
|
||||
if (userId == null)
|
||||
{
|
||||
userId = await GetActiveUserIdAsync();
|
||||
}
|
||||
|
||||
if (!await IsAuthenticatedAsync(userId))
|
||||
{
|
||||
return false;
|
||||
|
@ -353,36 +424,36 @@ namespace Bit.Core.Services
|
|||
await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<string> GetPinProtectedAsync(string userId = null)
|
||||
public async Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions);
|
||||
var key = await _storageMediatorService.GetAsync<string>(
|
||||
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), false);
|
||||
return key != null ? new EncString(key) : null;
|
||||
}
|
||||
|
||||
public async Task SetPinProtectedAsync(string value, string userId = null)
|
||||
public async Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
await _storageMediatorService.SaveAsync(
|
||||
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), value?.EncryptedString, false);
|
||||
}
|
||||
|
||||
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null)
|
||||
public async Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.VolatileData?.PinProtectedKey;
|
||||
))?.VolatileData?.PinKeyEncryptedUserKeyEphemeral;
|
||||
}
|
||||
|
||||
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
|
||||
public async Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.VolatileData.PinProtectedKey = value;
|
||||
account.VolatileData.PinKeyEncryptedUserKeyEphemeral = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
|
||||
public async Task SetKdfConfigurationAsync(KdfConfig config, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
|
@ -395,35 +466,6 @@ namespace Bit.Core.Services
|
|||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<string> GetKeyEncryptedAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultSecureStorageOptionsAsync());
|
||||
return await GetValueAsync<string>(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task SetKeyEncryptedAsync(string value, string userId)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultSecureStorageOptionsAsync());
|
||||
await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.VolatileData?.Key;
|
||||
}
|
||||
|
||||
public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.VolatileData.Key = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<string> GetKeyHashAsync(string userId = null)
|
||||
{
|
||||
|
@ -439,19 +481,6 @@ namespace Bit.Core.Services
|
|||
await SetValueAsync(Constants.KeyHashKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<string> GetEncKeyEncryptedAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
return await GetValueAsync<string>(Constants.EncKeyKey(reconciledOptions.UserId), reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task SetEncKeyEncryptedAsync(string value, string userId)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
await SetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null)
|
||||
{
|
||||
|
@ -482,6 +511,25 @@ namespace Bit.Core.Services
|
|||
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
var deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(reconciledOptions.UserId), true);
|
||||
if (string.IsNullOrEmpty(deviceKeyB64))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64));
|
||||
}
|
||||
|
||||
public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(reconciledOptions.UserId), value?.KeyB64, true);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
|
@ -1280,6 +1328,42 @@ namespace Bit.Core.Services
|
|||
await SetValueAsync(Constants.PreLoginEmailKey, value, options);
|
||||
}
|
||||
|
||||
public async Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
|
||||
))?.Profile?.UserDecryptionOptions;
|
||||
}
|
||||
|
||||
public async Task<bool> GetShouldTrustDeviceAsync()
|
||||
{
|
||||
return await _storageMediatorService.GetAsync<bool>(Constants.ShouldTrustDevice);
|
||||
}
|
||||
|
||||
public async Task SetShouldTrustDeviceAsync(bool value)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(Constants.ShouldTrustDevice, value);
|
||||
}
|
||||
|
||||
public async Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// GetAsync will throw an ArgumentException exception if there isn't a value to deserialize
|
||||
return await _storageMediatorService.GetAsync<PendingAdminAuthRequest>(await ComposeKeyAsync(Constants.PendingAdminAuthRequest, userId), true);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(await ComposeKeyAsync(Constants.PendingAdminAuthRequest, userId), value, true);
|
||||
}
|
||||
|
||||
public ConfigResponse GetConfigs()
|
||||
{
|
||||
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
|
||||
|
@ -1447,28 +1531,32 @@ namespace Bit.Core.Services
|
|||
}
|
||||
|
||||
// Non-state storage
|
||||
await SetProtectedPinAsync(null, userId);
|
||||
await SetPinProtectedAsync(null, userId);
|
||||
await SetKeyEncryptedAsync(null, userId);
|
||||
await SetKeyHashAsync(null, userId);
|
||||
await SetEncKeyEncryptedAsync(null, userId);
|
||||
await SetOrgKeysEncryptedAsync(null, userId);
|
||||
await SetPrivateKeyEncryptedAsync(null, userId);
|
||||
await SetLastActiveTimeAsync(null, userId);
|
||||
await SetPreviousPageInfoAsync(null, userId);
|
||||
await SetInvalidUnlockAttemptsAsync(null, userId);
|
||||
await SetLocalDataAsync(null, userId);
|
||||
await SetEncryptedCiphersAsync(null, userId);
|
||||
await SetEncryptedCollectionsAsync(null, userId);
|
||||
await SetLastSyncAsync(null, userId);
|
||||
await SetEncryptedFoldersAsync(null, userId);
|
||||
await SetEncryptedPoliciesAsync(null, userId);
|
||||
await SetUsesKeyConnectorAsync(null, userId);
|
||||
await SetOrganizationsAsync(null, userId);
|
||||
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
||||
await SetEncryptedSendsAsync(null, userId);
|
||||
await SetSettingsAsync(null, userId);
|
||||
await SetApprovePasswordlessLoginsAsync(null, userId);
|
||||
await Task.WhenAll(
|
||||
SetUserKeyAutoUnlockAsync(null, userId),
|
||||
SetUserKeyBiometricUnlockAsync(null, userId),
|
||||
SetProtectedPinAsync(null, userId),
|
||||
SetPinKeyEncryptedUserKeyAsync(null, userId),
|
||||
SetKeyHashAsync(null, userId),
|
||||
SetOrgKeysEncryptedAsync(null, userId),
|
||||
SetPrivateKeyEncryptedAsync(null, userId),
|
||||
SetLastActiveTimeAsync(null, userId),
|
||||
SetPreviousPageInfoAsync(null, userId),
|
||||
SetInvalidUnlockAttemptsAsync(null, userId),
|
||||
SetLocalDataAsync(null, userId),
|
||||
SetEncryptedCiphersAsync(null, userId),
|
||||
SetEncryptedCollectionsAsync(null, userId),
|
||||
SetLastSyncAsync(null, userId),
|
||||
SetEncryptedFoldersAsync(null, userId),
|
||||
SetEncryptedPoliciesAsync(null, userId),
|
||||
SetUsesKeyConnectorAsync(null, userId),
|
||||
SetOrganizationsAsync(null, userId),
|
||||
SetEncryptedPasswordGenerationHistoryAsync(null, userId),
|
||||
SetEncryptedSendsAsync(null, userId),
|
||||
SetSettingsAsync(null, userId),
|
||||
SetApprovePasswordlessLoginsAsync(null, userId),
|
||||
SetEncKeyEncryptedAsync(null, userId),
|
||||
SetKeyEncryptedAsync(null, userId),
|
||||
SetPinProtectedAsync(null, userId));
|
||||
}
|
||||
|
||||
private async Task ScaffoldNewAccountAsync(Account account)
|
||||
|
@ -1656,5 +1744,79 @@ namespace Bit.Core.Services
|
|||
await SetValueAsync(Constants.LastUserShouldConnectToWatchKey,
|
||||
shouldConnect ?? await GetShouldConnectToWatchAsync(), await GetDefaultStorageOptionsAsync());
|
||||
}
|
||||
|
||||
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
public async Task<string> GetPinProtectedAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead")]
|
||||
public async Task SetPinProtectedAsync(string value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
|
||||
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.VolatileData?.PinProtectedKey;
|
||||
}
|
||||
|
||||
[Obsolete("Use SetPinKeyEncryptedUserKeyEphemeralAsync instead")]
|
||||
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultInMemoryOptionsAsync());
|
||||
var account = await GetAccountAsync(reconciledOptions);
|
||||
account.VolatileData.PinProtectedKey = value;
|
||||
await SaveAccountAsync(account, reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
public async Task<string> GetEncKeyEncryptedAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
return await GetValueAsync<string>(Constants.EncKeyKey(reconciledOptions.UserId), reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use SetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||
public async Task SetEncKeyEncryptedAsync(string value, string userId)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
await SetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Left for migration purposes")]
|
||||
public async Task SetKeyEncryptedAsync(string value, string userId)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultSecureStorageOptionsAsync());
|
||||
await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
|
||||
public async Task<string> GetKeyEncryptedAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultSecureStorageOptionsAsync());
|
||||
return await GetValueAsync<string>(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
|
||||
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null)
|
||||
{
|
||||
return (await GetAccountAsync(
|
||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||
))?.VolatileData?.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -387,8 +387,8 @@ namespace Bit.Core.Services
|
|||
}
|
||||
return;
|
||||
}
|
||||
await _cryptoService.SetEncKeyAsync(response.Key);
|
||||
await _cryptoService.SetEncPrivateKeyAsync(response.PrivateKey);
|
||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(response.Key);
|
||||
await _cryptoService.SetUserPrivateKeyAsync(response.PrivateKey);
|
||||
await _cryptoService.SetOrgKeysAsync(response.Organizations);
|
||||
await _stateService.SetSecurityStampAsync(response.SecurityStamp);
|
||||
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
|
||||
|
@ -397,7 +397,7 @@ namespace Bit.Core.Services
|
|||
await _stateService.SetNameAsync(response.Name);
|
||||
await _stateService.SetPersonalPremiumAsync(response.Premium);
|
||||
await _stateService.SetAvatarColorAsync(response.AvatarColor);
|
||||
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector);
|
||||
await _keyConnectorService.SetUsesKeyConnectorAsync(response.UsesKeyConnector);
|
||||
}
|
||||
|
||||
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)
|
||||
|
|
|
@ -11,14 +11,18 @@ namespace Bit.Core.Services
|
|||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
|
||||
public UserVerificationService(IApiService apiService, IPlatformUtilsService platformUtilsService,
|
||||
II18nService i18nService, ICryptoService cryptoService)
|
||||
II18nService i18nService, ICryptoService cryptoService, IStateService stateService, IKeyConnectorService keyConnectorService)
|
||||
{
|
||||
_apiService = apiService;
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_i18nService = i18nService;
|
||||
_cryptoService = cryptoService;
|
||||
_stateService = stateService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
}
|
||||
|
||||
async public Task<bool> VerifyUser(string secret, VerificationType verificationType)
|
||||
|
@ -63,5 +67,21 @@ namespace Bit.Core.Services
|
|||
|
||||
await _platformUtilsService.ShowDialogAsync(errorMessage);
|
||||
}
|
||||
|
||||
public async Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false)
|
||||
{
|
||||
async Task<bool> CheckMasterKeyHashAsync()
|
||||
{
|
||||
return !checkMasterKeyHash || await _cryptoService.GetMasterKeyHashAsync() != null;
|
||||
};
|
||||
|
||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
if (decryptOptions != null)
|
||||
{
|
||||
return decryptOptions.HasMasterPassword && await CheckMasterKeyHashAsync();
|
||||
}
|
||||
|
||||
return !await _keyConnectorService.GetUsesKeyConnectorAsync() && await CheckMasterKeyHashAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public enum PinLockType
|
||||
{
|
||||
Disabled,
|
||||
Persistent,
|
||||
Transient
|
||||
}
|
||||
|
||||
public class VaultTimeoutService : IVaultTimeoutService
|
||||
{
|
||||
private readonly ICryptoService _cryptoService;
|
||||
|
@ -18,7 +23,7 @@ namespace Bit.Core.Services
|
|||
private readonly ISearchService _searchService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
private readonly Func<Tuple<string, bool>, Task> _lockedCallback;
|
||||
private readonly Func<Tuple<string, bool, bool>, Task> _loggedOutCallback;
|
||||
|
||||
|
@ -32,7 +37,7 @@ namespace Bit.Core.Services
|
|||
ISearchService searchService,
|
||||
IMessagingService messagingService,
|
||||
ITokenService tokenService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
IUserVerificationService userVerificationService,
|
||||
Func<Tuple<string, bool>, Task> lockedCallback,
|
||||
Func<Tuple<string, bool, bool>, Task> loggedOutCallback)
|
||||
{
|
||||
|
@ -45,7 +50,7 @@ namespace Bit.Core.Services
|
|||
_searchService = searchService;
|
||||
_messagingService = messagingService;
|
||||
_tokenService = tokenService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_userVerificationService = userVerificationService;
|
||||
_lockedCallback = lockedCallback;
|
||||
_loggedOutCallback = loggedOutCallback;
|
||||
}
|
||||
|
@ -54,15 +59,30 @@ namespace Bit.Core.Services
|
|||
|
||||
public async Task<bool> IsLockedAsync(string userId = null)
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync(userId);
|
||||
if (hasKey)
|
||||
// When checking if we need to lock inactive account, we don't want to
|
||||
// set the key, so we only check if the account has an auto unlock key
|
||||
if (userId != null && await _stateService.GetActiveUserIdAsync() != userId)
|
||||
{
|
||||
var biometricSet = await IsBiometricLockSetAsync(userId);
|
||||
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
|
||||
return await _cryptoService.HasAutoUnlockKeyAsync(userId);
|
||||
}
|
||||
|
||||
var biometricSet = await IsBiometricLockSetAsync(userId);
|
||||
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!await _cryptoService.HasUserKeyAsync(userId))
|
||||
{
|
||||
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId);
|
||||
}
|
||||
|
||||
// Check again to verify auto key was set
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync(userId);
|
||||
return !hasKey;
|
||||
}
|
||||
|
||||
|
@ -163,13 +183,15 @@ namespace Bit.Core.Services
|
|||
userId = await _stateService.GetActiveUserIdAsync();
|
||||
}
|
||||
|
||||
if (await _keyConnectorService.GetUsesKeyConnector())
|
||||
if (!await _userVerificationService.HasMasterPasswordAsync())
|
||||
{
|
||||
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId);
|
||||
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) ||
|
||||
isPinProtectedWithKey;
|
||||
var pinStatus = await GetPinLockTypeAsync(userId);
|
||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
||||
?? await _stateService.GetPinProtectedKeyAsync();
|
||||
var pinEnabled = (pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||
pinStatus == PinLockType.Persistent;
|
||||
|
||||
if (!pinLock && !await IsBiometricLockSetAsync())
|
||||
if (!pinEnabled && !await IsBiometricLockSetAsync())
|
||||
{
|
||||
await LogOutAsync(userInitiated, userId);
|
||||
return;
|
||||
|
@ -187,10 +209,11 @@ namespace Bit.Core.Services
|
|||
}
|
||||
}
|
||||
await Task.WhenAll(
|
||||
_cryptoService.ClearKeyAsync(userId),
|
||||
_cryptoService.ClearUserKeyAsync(userId),
|
||||
_cryptoService.ClearMasterKeyAsync(userId),
|
||||
_stateService.SetUserKeyAutoUnlockAsync(null, userId),
|
||||
_cryptoService.ClearOrgKeysAsync(true, userId),
|
||||
_cryptoService.ClearKeyPairAsync(true, userId),
|
||||
_cryptoService.ClearEncKeyAsync(true, userId));
|
||||
_cryptoService.ClearKeyPairAsync(true, userId));
|
||||
|
||||
if (isActiveAccount)
|
||||
{
|
||||
|
@ -214,15 +237,27 @@ namespace Bit.Core.Services
|
|||
{
|
||||
await _stateService.SetVaultTimeoutAsync(timeout);
|
||||
await _stateService.SetVaultTimeoutActionAsync(action);
|
||||
await _cryptoService.ToggleKeyAsync();
|
||||
await _cryptoService.RefreshKeysAsync();
|
||||
await _tokenService.ToggleTokensAsync();
|
||||
}
|
||||
|
||||
public async Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null)
|
||||
public async Task<PinLockType> GetPinLockTypeAsync(string userId = null)
|
||||
{
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync(userId);
|
||||
var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId);
|
||||
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null);
|
||||
// we can't depend on only the protected pin being set because old
|
||||
// versions only used it for MP on Restart
|
||||
var isPinEnabled = await _stateService.GetProtectedPinAsync(userId) != null;
|
||||
var hasUserKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync(userId) != null;
|
||||
var hasOldUserKeyPin = await _stateService.GetPinProtectedAsync(userId) != null;
|
||||
|
||||
if (hasUserKeyPin || hasOldUserKeyPin)
|
||||
{
|
||||
return PinLockType.Persistent;
|
||||
}
|
||||
else if (isPinEnabled && !hasUserKeyPin && !hasOldUserKeyPin)
|
||||
{
|
||||
return PinLockType.Transient;
|
||||
}
|
||||
return PinLockType.Disabled;
|
||||
}
|
||||
|
||||
public async Task<bool> IsBiometricLockSetAsync(string userId = null)
|
||||
|
@ -233,8 +268,7 @@ namespace Bit.Core.Services
|
|||
|
||||
public async Task ClearAsync(string userId = null)
|
||||
{
|
||||
await _stateService.SetPinProtectedKeyAsync(null, userId);
|
||||
await _stateService.SetProtectedPinAsync(null, userId);
|
||||
await _cryptoService.ClearPinKeysAsync(userId);
|
||||
}
|
||||
|
||||
public async Task<int?> GetVaultTimeout(string userId = null)
|
||||
|
|
|
@ -53,11 +53,13 @@ namespace Bit.Core.Utilities
|
|||
cryptoFunctionService);
|
||||
searchService = new SearchService(cipherService, sendService);
|
||||
var policyService = new PolicyService(stateService, organizationService);
|
||||
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService,
|
||||
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService, cryptoFunctionService,
|
||||
organizationService);
|
||||
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService,
|
||||
cryptoService, stateService, keyConnectorService);
|
||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
|
||||
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||
keyConnectorService,
|
||||
userVerificationService,
|
||||
(extras) =>
|
||||
{
|
||||
messagingService.Send("locked", extras);
|
||||
|
@ -77,15 +79,15 @@ namespace Bit.Core.Utilities
|
|||
});
|
||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
|
||||
var totpService = new TotpService(cryptoFunctionService);
|
||||
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
|
||||
var passwordResetEnrollmentService = new PasswordResetEnrollmentService(apiService, cryptoService, organizationService, stateService);
|
||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||
keyConnectorService, passwordGenerationService, policyService);
|
||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService,
|
||||
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService);
|
||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
||||
var eventService = new EventService(apiService, stateService, organizationService, cipherService);
|
||||
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService,
|
||||
cryptoService);
|
||||
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
|
||||
var configService = new ConfigService(apiService, stateService, logger);
|
||||
|
||||
|
@ -102,6 +104,8 @@ namespace Bit.Core.Utilities
|
|||
Register<ISearchService>("searchService", searchService);
|
||||
Register<IPolicyService>("policyService", policyService);
|
||||
Register<ISyncService>("syncService", syncService);
|
||||
Register<IKeyConnectorService>("keyConnectorService", keyConnectorService);
|
||||
Register<IUserVerificationService>(userVerificationService);
|
||||
Register<IVaultTimeoutService>("vaultTimeoutService", vaultTimeoutService);
|
||||
Register<IPasswordGenerationService>("passwordGenerationService", passwordGenerationService);
|
||||
Register<ITotpService>("totpService", totpService);
|
||||
|
@ -110,10 +114,10 @@ namespace Bit.Core.Utilities
|
|||
Register<IAuditService>("auditService", auditService);
|
||||
Register<IEnvironmentService>("environmentService", environmentService);
|
||||
Register<IEventService>("eventService", eventService);
|
||||
Register<IKeyConnectorService>("keyConnectorService", keyConnectorService);
|
||||
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||
Register<IConfigService>(configService);
|
||||
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
||||
Register<IPasswordResetEnrollmentService>(passwordResetEnrollmentService);
|
||||
}
|
||||
|
||||
public static void Register<T>(string serviceName, T obj)
|
||||
|
|
|
@ -313,7 +313,7 @@ namespace Bit.iOS.Autofill
|
|||
// Add a timeout to resolve keyboard not always showing up.
|
||||
await Task.Delay(250);
|
||||
var passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
if (!await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
if (!await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync())
|
||||
{
|
||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
||||
|
@ -498,7 +498,7 @@ namespace Bit.iOS.Autofill
|
|||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email));
|
||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
|
||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
}
|
||||
|
@ -511,11 +511,11 @@ namespace Bit.iOS.Autofill
|
|||
LogoutIfAuthed();
|
||||
}
|
||||
|
||||
private void LaunchLoginWithDevice(string email = null)
|
||||
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false)
|
||||
{
|
||||
var appOptions = new AppOptions { IosExtension = true };
|
||||
var app = new App.App(appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions, authingWithSso);
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||
|
@ -545,6 +545,7 @@ namespace Bit.iOS.Autofill
|
|||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
}
|
||||
|
@ -567,6 +568,7 @@ namespace Bit.iOS.Autofill
|
|||
{
|
||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||
if (authingWithSso)
|
||||
{
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||
|
@ -621,6 +623,25 @@ namespace Bit.iOS.Autofill
|
|||
PresentViewController(updateTempPasswordController, true, null);
|
||||
}
|
||||
|
||||
private void LaunchDeviceApprovalOptionsFlow()
|
||||
{
|
||||
var loginApproveDevicePage = new LoginApproveDevicePage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
|
||||
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
|
||||
{
|
||||
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
|
||||
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email, true));
|
||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true));
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(loginApproveDevicePage);
|
||||
var loginApproveDeviceController = navigationPage.CreateViewController();
|
||||
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginApproveDeviceController, true, null);
|
||||
}
|
||||
|
||||
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
|
||||
public Task UpdateThemeAsync() => Task.CompletedTask;
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Bit.iOS.Autofill.Utilities
|
|||
return;
|
||||
}
|
||||
|
||||
if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await passwordRepromptService.ShowPasswordPromptAsync())
|
||||
if (!await passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(item.Reprompt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -8,11 +8,13 @@ using Bit.App.Utilities;
|
|||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using Bit.iOS.Core.Views;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.iOS.Core.Controllers
|
||||
|
@ -26,15 +28,14 @@ namespace Bit.iOS.Core.Controllers
|
|||
private IStorageService _secureStorageService;
|
||||
private IPlatformUtilsService _platformUtilsService;
|
||||
private IBiometricService _biometricService;
|
||||
private IKeyConnectorService _keyConnectorService;
|
||||
private IUserVerificationService _userVerificationService;
|
||||
private IAccountsManager _accountManager;
|
||||
private bool _isPinProtected;
|
||||
private bool _isPinProtectedWithKey;
|
||||
private bool _pinLock;
|
||||
private bool _biometricLock;
|
||||
private PinLockType _pinStatus;
|
||||
private bool _pinEnabled;
|
||||
private bool _biometricEnabled;
|
||||
private bool _biometricIntegrityValid = true;
|
||||
private bool _passwordReprompt = false;
|
||||
private bool _usesKeyConnector;
|
||||
private bool _hasMasterPassword;
|
||||
private bool _biometricUnlockOnly = false;
|
||||
private bool _checkingPassword;
|
||||
|
||||
|
@ -85,7 +86,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
}
|
||||
|
||||
public abstract UITableView TableView { get; }
|
||||
|
||||
|
||||
public override async void ViewDidLoad()
|
||||
{
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
|
@ -95,7 +96,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
||||
_accountManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||
|
||||
// We re-use the lock screen for autofill extension to verify master password
|
||||
|
@ -103,29 +104,32 @@ namespace Bit.iOS.Core.Controllers
|
|||
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
|
||||
{
|
||||
_passwordReprompt = true;
|
||||
_isPinProtected = false;
|
||||
_isPinProtectedWithKey = false;
|
||||
_pinLock = false;
|
||||
_biometricLock = false;
|
||||
_pinStatus = PinLockType.Disabled;
|
||||
_pinEnabled = false;
|
||||
_biometricEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||
_isPinProtectedWithKey;
|
||||
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||
await _cryptoService.HasKeyAsync();
|
||||
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
|
||||
|
||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
||||
?? await _stateService.GetPinProtectedKeyAsync();
|
||||
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||
_pinStatus == PinLockType.Persistent;
|
||||
|
||||
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
|
||||
&& await _cryptoService.HasEncryptedUserKeyAsync();
|
||||
_biometricIntegrityValid =
|
||||
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
||||
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
|
||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
||||
_biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled;
|
||||
}
|
||||
|
||||
if (_pinLock)
|
||||
if (_pinEnabled)
|
||||
{
|
||||
BaseNavItem.Title = AppResources.VerifyPIN;
|
||||
}
|
||||
else if (_usesKeyConnector)
|
||||
else if (!_hasMasterPassword)
|
||||
{
|
||||
BaseNavItem.Title = AppResources.UnlockVault;
|
||||
}
|
||||
|
@ -150,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
|
||||
if (!_biometricUnlockOnly)
|
||||
{
|
||||
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword;
|
||||
MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
|
||||
MasterPasswordCell.TextField.SecureTextEntry = true;
|
||||
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
|
||||
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||
|
@ -158,7 +162,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
CheckPasswordAsync().FireAndForget();
|
||||
return true;
|
||||
};
|
||||
if (_pinLock)
|
||||
if (_pinEnabled)
|
||||
{
|
||||
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
|
||||
}
|
||||
|
@ -177,7 +181,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
|
||||
base.ViewDidLoad();
|
||||
|
||||
if (_biometricLock)
|
||||
if (_biometricEnabled)
|
||||
{
|
||||
if (!_biometricIntegrityValid)
|
||||
{
|
||||
|
@ -196,20 +200,20 @@ namespace Bit.iOS.Core.Controllers
|
|||
base.ViewDidAppear(animated);
|
||||
|
||||
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||
if (_usesKeyConnector)
|
||||
if (!_hasMasterPassword)
|
||||
{
|
||||
if (!(_pinLock || _biometricLock) ||
|
||||
(_biometricLock && !_biometricIntegrityValid))
|
||||
if (!(_pinEnabled || _biometricEnabled) ||
|
||||
(_biometricEnabled && !_biometricIntegrityValid))
|
||||
{
|
||||
PromptSSO();
|
||||
}
|
||||
}
|
||||
else if (!_biometricLock || !_biometricIntegrityValid)
|
||||
else if (!_biometricEnabled || !_biometricIntegrityValid)
|
||||
{
|
||||
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected async Task CheckPasswordAsync()
|
||||
{
|
||||
if (_checkingPassword)
|
||||
|
@ -224,7 +228,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
{
|
||||
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired,
|
||||
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
|
||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
|
||||
AppResources.Ok);
|
||||
PresentViewController(alert, true, null);
|
||||
return;
|
||||
|
@ -246,33 +250,53 @@ namespace Bit.iOS.Core.Controllers
|
|||
return;
|
||||
}
|
||||
|
||||
if (_pinLock)
|
||||
if (_pinEnabled)
|
||||
{
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
if (_isPinProtected)
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (_pinStatus == PinLockType.Persistent)
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (_pinStatus == PinLockType.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockType.Transient,
|
||||
inputtedValue,
|
||||
email,
|
||||
kdfConfig,
|
||||
await _stateService.GetPinProtectedKeyAsync());
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
failed = decPin != inputtedValue;
|
||||
if (!failed)
|
||||
{
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
}
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||
kdfConfig);
|
||||
failed = false;
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
inputtedValue,
|
||||
email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
failed = decryptedPin != inputtedValue;
|
||||
if (!failed)
|
||||
{
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key2);
|
||||
await SetKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
@ -286,33 +310,27 @@ namespace Bit.iOS.Core.Controllers
|
|||
}
|
||||
else
|
||||
{
|
||||
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
|
||||
|
||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||
if (storedKeyHash == null)
|
||||
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||
if (storedPasswordHash == null)
|
||||
{
|
||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||
if (key2.KeyB64 == oldKey)
|
||||
if (masterKey.KeyB64 == oldKey)
|
||||
{
|
||||
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
|
||||
var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
|
||||
await _secureStorageService.RemoveAsync("oldKey");
|
||||
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
||||
}
|
||||
}
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
|
||||
if (passwordValid)
|
||||
{
|
||||
if (_isPinProtected)
|
||||
{
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key2);
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
|
||||
kdfConfig);
|
||||
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
|
||||
}
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key2, true);
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetKeyAndContinueAsync(userKey, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -339,12 +357,12 @@ namespace Bit.iOS.Core.Controllers
|
|||
|
||||
public async Task PromptBiometricAsync()
|
||||
{
|
||||
if (!_biometricLock || !_biometricIntegrityValid)
|
||||
if (!_biometricEnabled || !_biometricIntegrityValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
|
@ -371,12 +389,12 @@ namespace Bit.iOS.Core.Controllers
|
|||
PresentViewController(loginController, true, null);
|
||||
}
|
||||
|
||||
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
|
||||
private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
await _cryptoService.SetUserKeyAsync(userKey);
|
||||
}
|
||||
DoContinue(masterPassword);
|
||||
}
|
||||
|
@ -396,7 +414,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
private async Task EnableBiometricsIfNeeded()
|
||||
{
|
||||
// Re-enable biometrics if initial use
|
||||
if (_biometricLock & !_biometricIntegrityValid)
|
||||
if (_biometricEnabled & !_biometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
||||
}
|
||||
|
@ -405,7 +423,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
private void InvalidValue()
|
||||
{
|
||||
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
||||
string.Format(null, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
||||
AppResources.Ok, (a) =>
|
||||
{
|
||||
|
||||
|
@ -490,7 +508,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
return 0;
|
||||
}
|
||||
|
||||
return (!controller._biometricUnlockOnly && controller._biometricLock) ||
|
||||
return (!controller._biometricUnlockOnly && controller._biometricEnabled) ||
|
||||
controller._passwordReprompt
|
||||
? 2
|
||||
: 1;
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
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.App.Utilities;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using Bit.iOS.Core.Views;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.iOS.Core.Controllers
|
||||
|
@ -28,14 +29,13 @@ namespace Bit.iOS.Core.Controllers
|
|||
private IStorageService _secureStorageService;
|
||||
private IPlatformUtilsService _platformUtilsService;
|
||||
private IBiometricService _biometricService;
|
||||
private IKeyConnectorService _keyConnectorService;
|
||||
private bool _isPinProtected;
|
||||
private bool _isPinProtectedWithKey;
|
||||
private bool _pinLock;
|
||||
private bool _biometricLock;
|
||||
private IUserVerificationService _userVerificationService;
|
||||
private PinLockType _pinStatus;
|
||||
private bool _pinEnabled;
|
||||
private bool _biometricEnabled;
|
||||
private bool _biometricIntegrityValid = true;
|
||||
private bool _passwordReprompt = false;
|
||||
private bool _usesKeyConnector;
|
||||
private bool _hasMasterPassword;
|
||||
private bool _biometricUnlockOnly = false;
|
||||
|
||||
protected bool autofillExtension = false;
|
||||
|
@ -89,44 +89,47 @@ namespace Bit.iOS.Core.Controllers
|
|||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
||||
|
||||
// We re-use the lock screen for autofill extension to verify master password
|
||||
// when trying to access protected items.
|
||||
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
|
||||
{
|
||||
_passwordReprompt = true;
|
||||
_isPinProtected = false;
|
||||
_isPinProtectedWithKey = false;
|
||||
_pinLock = false;
|
||||
_biometricLock = false;
|
||||
_pinStatus = PinLockType.Disabled;
|
||||
_pinEnabled = false;
|
||||
_biometricEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||
_isPinProtectedWithKey;
|
||||
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||
await _cryptoService.HasKeyAsync();
|
||||
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
|
||||
|
||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
||||
?? await _stateService.GetPinProtectedKeyAsync();
|
||||
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||
_pinStatus == PinLockType.Persistent;
|
||||
|
||||
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
|
||||
&& await _cryptoService.HasEncryptedUserKeyAsync();
|
||||
_biometricIntegrityValid =
|
||||
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
||||
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
|
||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
||||
_biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled;
|
||||
}
|
||||
|
||||
if (_pinLock)
|
||||
if (_pinEnabled)
|
||||
{
|
||||
BaseNavItem.Title = AppResources.VerifyPIN;
|
||||
}
|
||||
else if (_usesKeyConnector)
|
||||
{
|
||||
BaseNavItem.Title = AppResources.UnlockVault;
|
||||
}
|
||||
else
|
||||
else if (_hasMasterPassword)
|
||||
{
|
||||
BaseNavItem.Title = AppResources.VerifyMasterPassword;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
BaseNavItem.Title = AppResources.UnlockVault;
|
||||
}
|
||||
|
||||
BaseCancelButton.Title = AppResources.Cancel;
|
||||
|
||||
if (_biometricUnlockOnly)
|
||||
|
@ -143,7 +146,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
|
||||
if (!_biometricUnlockOnly)
|
||||
{
|
||||
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword;
|
||||
MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
|
||||
MasterPasswordCell.TextField.SecureTextEntry = true;
|
||||
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
|
||||
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||
|
@ -151,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
CheckPasswordAsync().GetAwaiter().GetResult();
|
||||
return true;
|
||||
};
|
||||
if (_pinLock)
|
||||
if (_pinEnabled)
|
||||
{
|
||||
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
|
||||
}
|
||||
|
@ -165,7 +168,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
|
||||
base.ViewDidLoad();
|
||||
|
||||
if (_biometricLock)
|
||||
if (_biometricEnabled)
|
||||
{
|
||||
if (!_biometricIntegrityValid)
|
||||
{
|
||||
|
@ -183,16 +186,16 @@ namespace Bit.iOS.Core.Controllers
|
|||
{
|
||||
base.ViewDidAppear(animated);
|
||||
|
||||
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||
if (_usesKeyConnector)
|
||||
// Users without MP and without biometric or pin need SSO
|
||||
if (!_hasMasterPassword)
|
||||
{
|
||||
if (!(_pinLock || _biometricLock) ||
|
||||
(_biometricLock && !_biometricIntegrityValid))
|
||||
if (!(_pinEnabled || _biometricEnabled) ||
|
||||
(_biometricEnabled && !_biometricIntegrityValid))
|
||||
{
|
||||
PromptSSO();
|
||||
}
|
||||
}
|
||||
else if (!_biometricLock || !_biometricIntegrityValid)
|
||||
else if (!_biometricEnabled || !_biometricIntegrityValid)
|
||||
{
|
||||
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||
}
|
||||
|
@ -204,7 +207,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
{
|
||||
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired,
|
||||
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
|
||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
|
||||
AppResources.Ok);
|
||||
PresentViewController(alert, true, null);
|
||||
return;
|
||||
|
@ -214,33 +217,53 @@ namespace Bit.iOS.Core.Controllers
|
|||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
var inputtedValue = MasterPasswordCell.TextField.Text;
|
||||
|
||||
if (_pinLock)
|
||||
if (_pinEnabled)
|
||||
{
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
if (_isPinProtected)
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (_pinStatus == PinLockType.Persistent)
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (_pinStatus == PinLockType.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockType.Transient,
|
||||
inputtedValue,
|
||||
email,
|
||||
kdfConfig,
|
||||
await _stateService.GetPinProtectedKeyAsync());
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
failed = decPin != inputtedValue;
|
||||
if (!failed)
|
||||
{
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
}
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||
kdfConfig);
|
||||
failed = false;
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
inputtedValue,
|
||||
email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
failed = decryptedPin != inputtedValue;
|
||||
if (!failed)
|
||||
{
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key2);
|
||||
await SetKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
@ -260,33 +283,27 @@ namespace Bit.iOS.Core.Controllers
|
|||
}
|
||||
else
|
||||
{
|
||||
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
|
||||
|
||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||
if (storedKeyHash == null)
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
|
||||
|
||||
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||
if (storedPasswordHash == null)
|
||||
{
|
||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||
if (key2.KeyB64 == oldKey)
|
||||
if (masterKey.KeyB64 == oldKey)
|
||||
{
|
||||
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
|
||||
var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
|
||||
await _secureStorageService.RemoveAsync("oldKey");
|
||||
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
||||
}
|
||||
}
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
|
||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
|
||||
if (passwordValid)
|
||||
{
|
||||
if (_isPinProtected)
|
||||
{
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key2);
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
|
||||
kdfConfig);
|
||||
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
|
||||
}
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key2, true);
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetKeyAndContinueAsync(userKey, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -303,12 +320,12 @@ namespace Bit.iOS.Core.Controllers
|
|||
|
||||
public async Task PromptBiometricAsync()
|
||||
{
|
||||
if (!_biometricLock || !_biometricIntegrityValid)
|
||||
if (!_biometricEnabled || !_biometricIntegrityValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
|
@ -335,12 +352,12 @@ namespace Bit.iOS.Core.Controllers
|
|||
PresentViewController(loginController, true, null);
|
||||
}
|
||||
|
||||
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
|
||||
private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
await _cryptoService.SetUserKeyAsync(userKey);
|
||||
}
|
||||
DoContinue(masterPassword);
|
||||
}
|
||||
|
@ -360,7 +377,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
private async Task EnableBiometricsIfNeeded()
|
||||
{
|
||||
// Re-enable biometrics if initial use
|
||||
if (_biometricLock & !_biometricIntegrityValid)
|
||||
if (_biometricEnabled & !_biometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
||||
}
|
||||
|
@ -369,7 +386,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
private void InvalidValue()
|
||||
{
|
||||
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
||||
string.Format(null, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
||||
AppResources.Ok, (a) =>
|
||||
{
|
||||
|
||||
|
@ -378,7 +395,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
});
|
||||
PresentViewController(alert, true, null);
|
||||
}
|
||||
|
||||
|
||||
private async Task LogOutAsync()
|
||||
{
|
||||
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
|
||||
|
@ -444,7 +461,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
|
||||
public override nint NumberOfSections(UITableView tableView)
|
||||
{
|
||||
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
|
||||
return (!_controller._biometricUnlockOnly && _controller._biometricEnabled) ||
|
||||
_controller._passwordReprompt
|
||||
? 2
|
||||
: 1;
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
using System.Threading.Tasks;
|
||||
using Bit.App.Services;
|
||||
using Bit.Core.Abstractions;
|
||||
using Foundation;
|
||||
using LocalAuthentication;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class BiometricService : IBiometricService
|
||||
public class BiometricService : BaseBiometricService
|
||||
{
|
||||
private IStateService _stateService;
|
||||
|
||||
public BiometricService(IStateService stateService)
|
||||
public BiometricService(IStateService stateService, ICryptoService cryptoService)
|
||||
: base(stateService, cryptoService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||
public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
if (bioIntegritySrcKey == null)
|
||||
{
|
||||
|
@ -30,7 +29,7 @@ namespace Bit.iOS.Core.Services
|
|||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
var state = GetState();
|
||||
if (state == null)
|
||||
|
|
|
@ -112,9 +112,9 @@ namespace Bit.iOS.Core.Utilities
|
|||
var clipboardService = new ClipboardService(stateService);
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||
messagingService, broadcasterService);
|
||||
var biometricService = new BiometricService(stateService);
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
var biometricService = new BiometricService(stateService, cryptoService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||
|
||||
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
||||
|
@ -245,9 +245,9 @@ namespace Bit.iOS.Core.Utilities
|
|||
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
|
||||
|
||||
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
|
||||
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
|
||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
|
||||
ServiceContainer.Resolve<IUserVerificationService>());
|
||||
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||
|
||||
if (postBootstrapFunc != null)
|
||||
|
|
|
@ -20,6 +20,7 @@ using Bit.App.Pages;
|
|||
using Bit.App.Models;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.iOS.Core.Views;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.iOS.Extension
|
||||
{
|
||||
|
@ -519,7 +520,7 @@ namespace Bit.iOS.Extension
|
|||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email));
|
||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
|
||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
}
|
||||
|
@ -532,11 +533,11 @@ namespace Bit.iOS.Extension
|
|||
LogoutIfAuthed();
|
||||
}
|
||||
|
||||
private void LaunchLoginWithDevice(string email = null)
|
||||
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false)
|
||||
{
|
||||
var appOptions = new AppOptions { IosExtension = true };
|
||||
var app = new App.App(appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions, authingWithSso);
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||
|
@ -566,6 +567,7 @@ namespace Bit.iOS.Extension
|
|||
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
|
||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
|
||||
}
|
||||
|
@ -588,6 +590,7 @@ namespace Bit.iOS.Extension
|
|||
{
|
||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
|
||||
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||
if (authingWithSso)
|
||||
{
|
||||
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||
|
@ -641,5 +644,24 @@ namespace Bit.iOS.Extension
|
|||
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(updateTempPasswordController, true, null);
|
||||
}
|
||||
|
||||
private void LaunchDeviceApprovalOptionsFlow()
|
||||
{
|
||||
var loginApproveDevicePage = new LoginApproveDevicePage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
|
||||
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
|
||||
{
|
||||
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
|
||||
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email, true));
|
||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true));
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(loginApproveDevicePage);
|
||||
var loginApproveDeviceController = navigationPage.CreateViewController();
|
||||
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginApproveDeviceController, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ namespace Bit.iOS.Extension
|
|||
return;
|
||||
}
|
||||
|
||||
if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await _controller.PasswordRepromptService.ShowPasswordPromptAsync())
|
||||
if (!await _controller.PasswordRepromptService.PromptAndCheckPasswordIfNeededAsync(item.Reprompt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -339,7 +339,7 @@ namespace Bit.iOS.ShareExtension
|
|||
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||
vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(email));
|
||||
vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
|
||||
vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
}
|
||||
|
@ -348,9 +348,9 @@ namespace Bit.iOS.ShareExtension
|
|||
LogoutIfAuthed();
|
||||
}
|
||||
|
||||
private void LaunchLoginWithDevice(string email = null)
|
||||
private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null, bool authingWithSso = false)
|
||||
{
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, _appOptions.Value);
|
||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value, authingWithSso);
|
||||
SetupAppAndApplyResources(loginWithDevicePage);
|
||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||
{
|
||||
|
@ -373,6 +373,7 @@ namespace Bit.iOS.ShareExtension
|
|||
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
|
||||
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||
vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
|
||||
}
|
||||
|
@ -389,6 +390,7 @@ namespace Bit.iOS.ShareExtension
|
|||
{
|
||||
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
|
||||
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
|
||||
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
|
||||
if (authingWithSso)
|
||||
{
|
||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||
|
@ -427,6 +429,25 @@ namespace Bit.iOS.ShareExtension
|
|||
NavigateToPage(updateTempPasswordPage);
|
||||
}
|
||||
|
||||
private void LaunchDeviceApprovalOptionsFlow()
|
||||
{
|
||||
var loginApproveDevicePage = new LoginApproveDevicePage();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
|
||||
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
|
||||
{
|
||||
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
|
||||
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email, true));
|
||||
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email, true));
|
||||
}
|
||||
|
||||
var navigationPage = new NavigationPage(loginApproveDevicePage);
|
||||
var loginApproveDeviceController = navigationPage.CreateViewController();
|
||||
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
PresentViewController(loginApproveDeviceController, true, null);
|
||||
}
|
||||
|
||||
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||
{
|
||||
if (ExtNavigationController?.ViewControllers?.Any() ?? false)
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(encFileName);
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(data);
|
||||
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||
sutProvider.GetDependency<ICryptoService>().MakeDataEncKeyAsync(Arg.Any<UserKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||
.Returns(uploadDataResponse);
|
||||
|
||||
|
@ -50,7 +50,7 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(new EncString(fileName));
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(data);
|
||||
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||
sutProvider.GetDependency<ICryptoService>().MakeDataEncKeyAsync(Arg.Any<UserKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||
.Throws(new ApiException(new ErrorResponse { StatusCode = statusCode }));
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>())
|
||||
|
@ -70,7 +70,7 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(new EncString(fileName));
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(data);
|
||||
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>())
|
||||
sutProvider.GetDependency<ICryptoService>().MakeDataEncKeyAsync(Arg.Any<UserKey>())
|
||||
.Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||
var expectedException = new ApiException(new ErrorResponse { StatusCode = HttpStatusCode.BadRequest });
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||
|
|
|
@ -145,7 +145,7 @@ namespace Bit.Core.Test.Services
|
|||
return;
|
||||
|
||||
var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
sutProvider.GetDependency<ICryptoService>().HasKeyAsync().Returns(true);
|
||||
sutProvider.GetDependency<ICryptoService>().HasUserKeyAsync().Returns(true);
|
||||
ServiceContainer.Register("cryptoService", sutProvider.GetDependency<ICryptoService>());
|
||||
sutProvider.GetDependency<II18nService>().StringComparer.Returns(StringComparer.CurrentCulture);
|
||||
sutProvider.GetDependency<IStateService>().GetActiveUserIdAsync().Returns(userId);
|
||||
|
|
Loading…
Reference in a new issue