1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

41 Commits

Author SHA1 Message Date
André Bispo
c25906206e [PM-2289] [PM-2293] TDE Login with device Admin Request (#2642) 2023-07-27 21:18:36 +01:00
André Bispo
dfc7c55b77 [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
2023-07-27 16:55:06 +01:00
André Bispo
080aabfe82 [PM-1208] Fix merge 2023-07-20 15:39:03 +01:00
André Bispo
c0688c584e [PM-1208] Fix merge 2023-07-20 15:37:08 +01:00
André Bispo
c09672ff88 [PM-1208] Fix app resource file 2023-07-20 15:30:29 +01:00
André Bispo
635b6bc184 Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs
#	src/App/Resources/AppResources.resx
#	src/Core/Abstractions/IStateService.cs
2023-07-20 14:56:40 +01:00
Jacob Fink
da7a1964ef Update crypto service api call to fix build 2023-07-20 09:28:56 -04:00
Jacob Fink
73b8d8e6b8 fix bad merge 2023-07-20 09:20:31 -04:00
Jacob Fink
c61f9f0357 Merge branch 'auth/pm-2713/drop-master-key-dependency' into feature/pm-1029-tde-login 2023-07-20 09:19:48 -04:00
Jacob Fink
a3183857b9 [PM-2713] set decrypt and set user key in login helper 2023-07-19 21:23:21 -04:00
Jacob Fink
bedbca841d [PM-2713] remove unused cached values in crypto service 2023-07-19 21:23:21 -04:00
Jacob Fink
0ff314f076 [PM-2713] use new crypto service api in auth service 2023-07-19 21:23:21 -04:00
Jacob Fink
c9a7c29190 [PM-2713] More conversions to crypto api 2023-07-19 21:23:20 -04:00
Jacob Fink
546bf8dcb1 [PM-2713] convert cipher service and others to crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7fdc5597fc [PM-2713] more conversions to new crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7c664f58b3 [PM-2713] add migration for pin on lock screens 2023-07-19 21:23:20 -04:00
Jacob Fink
bdfe806846 [PM-2713] converting calls to new crypto service api 2023-07-19 21:23:19 -04:00
Jacob Fink
5ed567ab90 [PM-2713] add toggle method to crypto service for keys 2023-07-19 21:23:19 -04:00
Jacob Fink
cd4f44e6f6 [PM-2713] use new MakeMasterKey method 2023-07-19 21:23:19 -04:00
Jacob Fink
d58f0b281b [PM-2713] refresh pin key when setting user key 2023-07-19 21:23:19 -04:00
Jacob Fink
5ba3fac0c0 [PM-2713] add make user key method to crypto service 2023-07-19 21:23:19 -04:00
Jacob Fink
1e30524985 [PM-2713] fix signature of GetUserKeyPin 2023-07-19 21:23:18 -04:00
Jacob Fink
515decb4c9 [PM-2713] add new pin methods to state service 2023-07-19 21:23:18 -04:00
Jacob Fink
bf28d373e9 [PM-2713] more updates to crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
69d38d4d75 [PM-2713] continue organizing crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
c1619536aa [PM-2713] rename key hash to password hash & begin add methods to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
079e02e4e5 [PM-271] add UserKey and MasterKey support to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
15d3da607b [PM-2713] add new state for new keys and obsolete old ones
- UserKey
- MasterKey
- UserKeyMasterKey (enc UserKey from User Table)
2023-07-19 21:23:17 -04:00
Jacob Fink
b5cf9fd79d [PM-2731] add user key and master key types 2023-07-19 21:23:17 -04:00
André Bispo
b688b85d0f [PM-1201] Change timeout actions available based on hasMasterPassword (#2610)
* [PM-1201] Change timeout actions available based on hasMasterPassword
2023-07-12 20:42:21 +01:00
André Bispo
a5df6c0c65 [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
2023-07-12 19:12:57 +01:00
André Bispo
c2d4fa4429 [PM-2583] Answer auth request with mp field as null if doesn't have it. (#2609) 2023-07-11 23:05:35 +01:00
André Bispo
548bd12a8e Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options 2023-07-10 12:37:13 +01:00
André Bispo
58542fd255 Merge branch 'master' into feature/pm-1029-tde-login 2023-07-10 12:36:59 +01:00
André Bispo
800b4c71de Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/Core/Models/Response/DeviceResponse.cs
#	src/Core/Services/ApiService.cs
2023-07-10 12:32:27 +01:00
Jake Fink
3053eaa036 [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
2023-07-05 16:13:20 -04:00
André Bispo
6268f0776b Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options 2023-07-03 10:34:53 +01:00
André Bispo
cbbc41be67 [PM-1208] Add continue button and not you option 2023-07-03 10:34:02 +01:00
André Bispo
e164fb9823 Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/App/Resources/AppResources.resx
#	src/Core/Abstractions/IApiService.cs
#	src/Core/Services/StateService.cs
2023-06-29 14:36:46 +01:00
André Bispo
87866304a6 [PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account. 2023-06-28 22:37:08 +01:00
André Bispo
84a82f0876 [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added. 2023-05-17 17:46:45 +01:00
61 changed files with 2517 additions and 1082 deletions

View File

@@ -46,7 +46,7 @@
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<Grid <Grid
StyleClass="box-row" StyleClass="box-row"
IsVisible="{Binding PinLock}" IsVisible="{Binding PinEnabled}"
Padding="0, 10, 0, 0"> Padding="0, 10, 0, 0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -89,7 +89,7 @@
<Grid <Grid
x:Name="_passwordGrid" x:Name="_passwordGrid"
StyleClass="box-row" StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}" IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0"> Padding="0, 10, 0, 0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />

View File

@@ -44,7 +44,7 @@ namespace Bit.App.Pages
{ {
get get
{ {
if (_vm?.PinLock ?? false) if (_vm?.PinEnabled ?? false)
{ {
return _pin; return _pin;
} }
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
public async Task PromptBiometricAfterResumeAsync() public async Task PromptBiometricAfterResumeAsync()
{ {
if (_vm.BiometricLock) if (_vm.BiometricEnabled)
{ {
await Task.Delay(500); await Task.Delay(500);
if (!_promptedAfterResume) if (!_promptedAfterResume)
@@ -91,13 +91,13 @@ namespace Bit.App.Pages
_vm.FocusSecretEntry += PerformFocusSecretEntry; _vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricLock) if (!_vm.BiometricEnabled)
{ {
RequestFocus(SecretEntry); RequestFocus(SecretEntry);
} }
else else
{ {
if (_vm.UsingKeyConnector && !_vm.PinLock) if (_vm.UsingKeyConnector && !_vm.PinEnabled)
{ {
_passwordGrid.IsVisible = false; _passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false; _unlockButton.IsVisible = false;

View File

@@ -33,21 +33,21 @@ namespace Bit.App.Pages
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>(); private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly IPasswordGenerationService _passwordGenerationService; private readonly IPasswordGenerationService _passwordGenerationService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ISyncService _syncService;
private string _email; private string _email;
private string _masterPassword; private string _masterPassword;
private string _pin; private string _pin;
private bool _showPassword; private bool _showPassword;
private bool _pinLock; private PinLockEnum _pinStatus;
private bool _biometricLock; private bool _pinEnabled;
private bool _biometricEnabled;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _biometricButtonVisible; private bool _biometricButtonVisible;
private bool _usingKeyConnector; private bool _usingKeyConnector;
private string _biometricButtonText; private string _biometricButtonText;
private string _loggedInAsText; private string _loggedInAsText;
private string _lockedVerifyText; private string _lockedVerifyText;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
public LockPageViewModel() public LockPageViewModel()
{ {
@@ -65,6 +65,8 @@ namespace Bit.App.Pages
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>(); _watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_policyService = ServiceContainer.Resolve<IPolicyService>(); _policyService = ServiceContainer.Resolve<IPolicyService>();
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(); _passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_syncService = ServiceContainer.Resolve<ISyncService>();
PageTitle = AppResources.VerifyMasterPassword; PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
@@ -100,10 +102,10 @@ namespace Bit.App.Pages
}); });
} }
public bool PinLock public bool PinEnabled
{ {
get => _pinLock; get => _pinEnabled;
set => SetProperty(ref _pinLock, value); set => SetProperty(ref _pinEnabled, value);
} }
public bool UsingKeyConnector public bool UsingKeyConnector
@@ -111,10 +113,10 @@ namespace Bit.App.Pages
get => _usingKeyConnector; get => _usingKeyConnector;
} }
public bool BiometricLock public bool BiometricEnabled
{ {
get => _biometricLock; get => _biometricEnabled;
set => SetProperty(ref _biometricLock, value); set => SetProperty(ref _biometricEnabled, value);
} }
public bool BiometricIntegrityValid public bool BiometricIntegrityValid
@@ -162,14 +164,18 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync(); ?? await _stateService.GetPinProtectedKeyAsync();
PinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync();
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
if (_usingKeyConnector && !(BiometricLock || PinLock)) if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
{ {
await _vaultTimeoutService.LogOutAsync(); await _vaultTimeoutService.LogOutAsync();
return; return;
@@ -188,7 +194,7 @@ namespace Bit.App.Pages
} }
var webVaultHostname = CoreHelpers.GetHostname(webVault); var webVaultHostname = CoreHelpers.GetHostname(webVault);
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname); LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
if (PinLock) if (PinEnabled)
{ {
PageTitle = AppResources.VerifyPIN; PageTitle = AppResources.VerifyPIN;
LockedVerifyText = AppResources.VaultLockedPIN; LockedVerifyText = AppResources.VaultLockedPIN;
@@ -207,7 +213,7 @@ namespace Bit.App.Pages
} }
} }
if (BiometricLock) if (BiometricEnabled)
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
@@ -229,14 +235,14 @@ namespace Bit.App.Pages
public async Task SubmitAsync() public async Task SubmitAsync()
{ {
if (PinLock && string.IsNullOrWhiteSpace(Pin)) if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN), string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
AppResources.Ok); AppResources.Ok);
return; return;
} }
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword)) if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
@@ -247,34 +253,54 @@ namespace Bit.App.Pages
ShowPassword = false; ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinLock) if (PinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, userKeyPin = await _stateService.GetUserKeyPinAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
Pin,
_email,
kdfConfig, kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected
var encKey = await _cryptoService.GetEncKeyAsync(key); );
}
else
{
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
Pin,
_email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync(); var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decPin != Pin; failed = decryptedPin != Pin;
if (!failed) if (!failed)
{ {
Pin = string.Empty; Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key); await SetKeyAndContinueAsync(userKey);
}
}
else
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
failed = false;
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
} }
} }
catch catch
@@ -295,19 +321,21 @@ namespace Bit.App.Pages
} }
else else
{ {
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedKeyHash = await _cryptoService.GetPasswordHashAsync();
var passwordValid = false; var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null; MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
if (storedKeyHash != null) if (storedKeyHash != null)
{ {
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key); // Offline unlock possible
passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(MasterPassword, masterKey);
} }
else else
{ {
// Online unlock required
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest(); var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash; request.MasterPasswordHash = keyHash;
@@ -316,8 +344,8 @@ namespace Bit.App.Pages
var response = await _apiService.PostAccountVerifyPasswordAsync(request); var response = await _apiService.PostAccountVerifyPasswordAsync(request);
enforcedMasterPasswordOptions = response.MasterPasswordPolicy; enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
passwordValid = true; passwordValid = true;
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetPasswordHashAsync(localKeyHash);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -327,15 +355,6 @@ namespace Bit.App.Pages
} }
if (passwordValid) 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)) if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
{ {
// Save the ForcePasswordResetReason to force a password reset after unlock // Save the ForcePasswordResetReason to force a password reset after unlock
@@ -345,10 +364,13 @@ namespace Bit.App.Pages
MasterPassword = string.Empty; MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey);
// Re-enable biometrics // Re-enable biometrics
if (BiometricLock & !BiometricIntegrityValid) if (BiometricEnabled & !BiometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(); await _biometricService.SetupBiometricAsync();
} }
@@ -425,7 +447,7 @@ namespace Bit.App.Pages
public void TogglePassword() public void TogglePassword()
{ {
ShowPassword = !ShowPassword; ShowPassword = !ShowPassword;
var secret = PinLock ? Pin : MasterPassword; var secret = PinEnabled ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry)); _secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
} }
@@ -433,12 +455,12 @@ namespace Bit.App.Pages
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid; BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricLock || !BiometricIntegrityValid) if (!BiometricEnabled || !BiometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword, PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry))); () => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -447,18 +469,20 @@ namespace Bit.App.Pages
} }
} }
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key) private async Task SetKeyAndContinueAsync(UserKey key)
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(key);
} }
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
await DoContinueAsync(); await DoContinueAsync();
} }
private async Task DoContinueAsync() private async Task DoContinueAsync()
{ {
_syncService.FullSyncAsync(false).FireAndForget();
await _stateService.SetBiometricLockedAsync(false); await _stateService.SetBiometricLockedAsync(false);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget(); _watchDeviceService.SyncDataToWatchAsync().FireAndForget();
_messagingService.Send("unlocked"); _messagingService.Send("unlocked");

View 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 ContinueEnabled}"/>
<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 Tapped="Cancel_Clicked" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</StackLayout>
</pages:BaseContentPage>

View File

@@ -0,0 +1,62 @@
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 = () => StartLogInWithMasterPassword().FireAndForget();
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
_vm.Page = this;
_appOptions = appOptions;
}
protected override void OnAppearing()
{
_vm.InitAsync();
}
private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.CloseAction();
}
}
private async Task StartLogInWithMasterPassword()
{
var page = new LockPage(_appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task StartLoginWithDeviceAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task RequestAdminApprovalAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
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.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 bool _continueEnabled;
private string _email;
private readonly IStateService _stateService;
private readonly IApiService _apiService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
public ICommand ApproveWithMyOtherDeviceCommand { get; }
public ICommand RequestAdminApprovalCommand { get; }
public ICommand ApproveWithMasterPasswordCommand { get; }
public ICommand ContinueCommand { get; }
public Action LogInWithMasterPasswordAction { get; set; }
public Action LogInWithDeviceAction { get; set; }
public Action RequestAdminApprovalAction { get; set; }
public Action CloseAction { get; set; }
public LoginApproveDeviceViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>();
_apiService = ServiceContainer.Resolve<IApiService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.LoggedIn;
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(InitAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}
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);
}
public bool ApproveWithMasterPasswordEnabled
{
get => _approveWithMasterPasswordEnabled;
set => SetProperty(ref _approveWithMasterPasswordEnabled, value);
}
public bool ContinueEnabled
{
get => _continueEnabled;
set => SetProperty(ref _continueEnabled, value);
}
public string Email
{
get => _email;
set => SetProperty(ref _email, value, additionalPropertyNames:
new string[] {
nameof(LoggingInAsText)
});
}
public async Task InitAsync()
{
try
{
Email = await _stateService.GetRememberedEmailAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
}
catch (Exception ex)
{
HandleException(ex);
}
// TODO: Change this expression to, Appear if the browser is trusted and shared the key with the app
ContinueEnabled = !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled && !ApproveWithMyOtherDeviceEnabled;
}
private async Task SetDeviceTrustAndInvokeAsync(Action action)
{
await _deviceTrustCryptoService.SetShouldTrustDeviceAsync(RememberThisDevice);
await Device.InvokeOnMainThreadAsync(action);
}
}
}

View File

@@ -4,6 +4,7 @@ using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
@@ -135,7 +136,7 @@ namespace Bit.App.Pages
private async Task StartLoginWithDeviceAsync() 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)); await Navigation.PushModalAsync(new NavigationPage(page));
} }

View File

@@ -21,17 +21,17 @@
<StackLayout <StackLayout
Padding="7, 0, 7, 20"> Padding="7, 0, 7, 20">
<Label <Label
Text="{u:I18n LogInInitiated}" Text="{Binding Tittle}"
FontSize="Title" FontSize="Title"
FontAttributes="Bold" FontAttributes="Bold"
Margin="0,14,0,21" Margin="0,14,0,21"
AutomationId="LogInInitiatedLabel" /> AutomationId="LogInInitiatedLabel" />
<Label <Label
Text="{u:I18n ANotificationHasBeenSentToYourDevice}" Text="{Binding SubTittle}"
FontSize="Small" FontSize="Small"
Margin="0,0,0,10"/> Margin="0,0,0,10"/>
<Label <Label
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}" Text="{Binding Description}"
FontSize="Small" FontSize="Small"
Margin="0,0,0,24"/> Margin="0,0,0,24"/>
<Label <Label
@@ -45,6 +45,7 @@
AutomationId="FingerprintPhraseValue" /> AutomationId="FingerprintPhraseValue" />
<Label <Label
Text="{u:I18n ResendNotification}" Text="{u:I18n ResendNotification}"
IsVisible="{Binding ResendNotificationVisible}"
StyleClass="text-md" StyleClass="text-md"
HorizontalOptions="Start" HorizontalOptions="Start"
Margin="0,40,0,0" Margin="0,40,0,0"
@@ -58,7 +59,7 @@
Orientation="Horizontal" Orientation="Horizontal"
Margin="0,30,0,0"> Margin="0,30,0,0">
<Label <Label
Text="{u:I18n NeedAnotherOption}" Text="{Binding OtherOptions}"
FontSize="Small" FontSize="Small"
VerticalTextAlignment="End"/> VerticalTextAlignment="End"/>
<Label <Label

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Enums;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -12,13 +13,14 @@ namespace Bit.App.Pages
private LoginPasswordlessRequestViewModel _vm; private LoginPasswordlessRequestViewModel _vm;
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null) public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null)
{ {
InitializeComponent(); InitializeComponent();
_appOptions = appOptions; _appOptions = appOptions;
_vm = BindingContext as LoginPasswordlessRequestViewModel; _vm = BindingContext as LoginPasswordlessRequestViewModel;
_vm.Page = this; _vm.Page = this;
_vm.Email = email; _vm.Email = email;
_vm.AuthRequestType = authRequestType;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync()); _vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync()); _vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); _vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -12,6 +13,7 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
@@ -32,6 +34,9 @@ namespace Bit.App.Pages
private IPlatformUtilsService _platformUtilsService; private IPlatformUtilsService _platformUtilsService;
private IEnvironmentService _environmentService; private IEnvironmentService _environmentService;
private ILogger _logger; private ILogger _logger;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly ICryptoService _cryptoService;
protected override II18nService i18nService => _i18nService; protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService; protected override IEnvironmentService environmentService => _environmentService;
@@ -44,6 +49,7 @@ namespace Bit.App.Pages
private string _email; private string _email;
private string _requestId; private string _requestId;
private string _requestAccessCode; private string _requestAccessCode;
private AuthRequestType _authRequestType;
// Item1 publicKey, Item2 privateKey // Item1 publicKey, Item2 privateKey
private Tuple<byte[], byte[]> _requestKeyPair; private Tuple<byte[], byte[]> _requestKeyPair;
@@ -57,6 +63,9 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>(); _i18nService = ServiceContainer.Resolve<II18nService>();
_stateService = ServiceContainer.Resolve<IStateService>(); _stateService = ServiceContainer.Resolve<IStateService>();
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.LogInWithAnotherDevice; PageTitle = AppResources.LogInWithAnotherDevice;
@@ -77,6 +86,70 @@ namespace Bit.App.Pages
public ICommand CreatePasswordlessLoginCommand { get; } public ICommand CreatePasswordlessLoginCommand { get; }
public ICommand CloseCommand { get; } public ICommand CloseCommand { get; }
public string Tittle
{
get
{
switch (_authRequestType)
{
case AuthRequestType.AuthenticateAndUnlock:
return AppResources.LogInInitiated;
case AuthRequestType.AdminApproval:
return AppResources.AdminApprovalRequested;
default:
return string.Empty;
};
}
}
public string SubTittle
{
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.NeedAnotherOption;
case AuthRequestType.AdminApproval:
return AppResources.TroubleLoggingIn;
default:
return string.Empty;
};
}
}
public string FingerprintPhrase public string FingerprintPhrase
{ {
get => _fingerprintPhrase; get => _fingerprintPhrase;
@@ -89,6 +162,21 @@ namespace Bit.App.Pages
set => SetProperty(ref _email, value); set => SetProperty(ref _email, value);
} }
public AuthRequestType AuthRequestType
{
get => _authRequestType;
set => SetProperty(ref _authRequestType, value, additionalPropertyNames: new string[]
{
nameof(Tittle),
nameof(SubTittle),
nameof(Description),
nameof(OtherOptions),
nameof(ResendNotificationVisible)
});
}
public bool ResendNotificationVisible => AuthRequestType == AuthRequestType.AuthenticateAndUnlock;
public void StartCheckLoginRequestStatus() public void StartCheckLoginRequestStatus()
{ {
try try
@@ -119,14 +207,22 @@ namespace Bit.App.Pages
private async Task CheckLoginRequestStatus() private async Task CheckLoginRequestStatus()
{ {
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode)) if (string.IsNullOrEmpty(_requestId))
{ {
return; return;
} }
try try
{ {
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode); PasswordlessLoginResponse response = null;
if (await _stateService.IsAuthenticatedAsync())
{
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
}
else
{
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
}
if (response.RequestApproved == null || !response.RequestApproved.Value) if (response.RequestApproved == null || !response.RequestApproved.Value)
{ {
@@ -138,6 +234,12 @@ namespace Bit.App.Pages
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash); var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await HandleLoginCompleteAsync();
return;
}
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus)) if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
{ {
return; return;
@@ -153,8 +255,7 @@ namespace Bit.App.Pages
} }
else else
{ {
_syncService.FullSyncAsync(true).FireAndForget(); await HandleLoginCompleteAsync();
LogInSuccessAction?.Invoke();
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -164,22 +265,66 @@ namespace Bit.App.Pages
} }
} }
private async Task HandleLoginCompleteAsync()
{
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
LogInSuccessAction?.Invoke();
}
private async Task CreatePasswordlessLoginAsync() private async Task CreatePasswordlessLoginAsync()
{ {
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading)); await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email); PasswordlessLoginResponse response = null;
if (response != null) var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null && _authRequestType == AuthRequestType.AdminApproval)
{ {
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;
}
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; FingerprintPhrase = response.FingerprintPhrase;
_requestId = response.Id; _requestId = response.Id;
_requestAccessCode = response.RequestAccessCode; _requestAccessCode = response.RequestAccessCode;
_requestKeyPair = response.RequestKeyPair; _requestKeyPair = response.RequestKeyPair;
} }
await _deviceActionService.HideLoadingAsync();
}
private void HandleException(Exception ex) private void HandleException(Exception ex)
{ {
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () => Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>

View File

@@ -29,6 +29,8 @@ namespace Bit.App.Pages
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync()); _vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
_vm.UpdateTempPasswordAction = _vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartDeviceApprovalOptionsAction =
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
_vm.CloseAction = async () => _vm.CloseAction = async () =>
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
@@ -106,10 +108,17 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task SsoAuthSuccessAsync() private async Task SsoAuthSuccessAsync()
{ {
RestoreAppOptionsFromCopy(); RestoreAppOptionsFromCopy();
await AppHelpers.ClearPreviousPage(); await AppHelpers.ClearPreviousPage();
if (await _vaultTimeoutService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions)); Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));

View File

@@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials; using Xamarin.Essentials;
@@ -29,6 +30,8 @@ namespace Bit.App.Pages
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ICryptoService _cryptoService;
private string _orgIdentifier; private string _orgIdentifier;
@@ -45,7 +48,8 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
_organizationService = ServiceContainer.Resolve<IOrganizationService>(); _organizationService = ServiceContainer.Resolve<IOrganizationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.Bitwarden; PageTitle = AppResources.Bitwarden;
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false); LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
@@ -61,6 +65,7 @@ namespace Bit.App.Pages
public Action StartTwoFactorAction { get; set; } public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; } public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; } public Action SsoAuthSuccessAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; } public Action UpdateTempPasswordAction { get; set; }
@@ -197,6 +202,7 @@ namespace Bit.App.Pages
try try
{ {
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId); var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier); await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
@@ -212,9 +218,31 @@ namespace Bit.App.Pages
{ {
UpdateTempPasswordAction?.Invoke(); UpdateTempPasswordAction?.Invoke();
} }
else if (decryptOptions?.TrustedDeviceOption != null)
{
// 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();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
else else
{ {
var task = Task.Run(async () => await _syncService.FullSyncAsync(true)); StartDeviceApprovalOptionsAction?.Invoke();
}
}
else
{
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke(); SsoAuthSuccessAction?.Invoke();
} }
} }

View File

@@ -177,25 +177,28 @@ namespace Bit.App.Pages
Name = string.IsNullOrWhiteSpace(Name) ? null : Name; Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
Email = Email.Trim().ToLower(); Email = Email.Trim().ToLower();
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig); var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
var encKey = await _cryptoService.MakeEncKeyAsync(key); var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key); newMasterKey,
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); await _cryptoService.MakeUserKeyAsync()
);
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey);
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new RegisterRequest var request = new RegisterRequest
{ {
Email = Email, Email = Email,
Name = Name, Name = Name,
MasterPasswordHash = hashedPassword, MasterPasswordHash = hashedPassword,
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Key = encKey.Item2.EncryptedString, Key = newProtectedUserKey.EncryptedString,
Kdf = kdfConfig.Type, Kdf = kdfConfig.Type,
KdfIterations = kdfConfig.Iterations, KdfIterations = kdfConfig.Iterations,
KdfMemory = kdfConfig.Memory, KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism, KdfParallelism = kdfConfig.Parallelism,
Keys = new KeysRequest Keys = new KeysRequest
{ {
PublicKey = keys.Item1, PublicKey = newPublicKey,
EncryptedPrivateKey = keys.Item2.EncryptedString EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
}, },
CaptchaResponse = _captchaToken, CaptchaResponse = _captchaToken,
}; };

View File

@@ -165,26 +165,18 @@ namespace Bit.App.Pages
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig); var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
Tuple<SymmetricCryptoKey, EncString> encKey; var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
var existingEncKey = await _cryptoService.GetEncKeyAsync(); await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
if (existingEncKey == null)
{
encKey = await _cryptoService.MakeEncKeyAsync(key);
}
else
{
encKey = await _cryptoService.RemakeEncKeyAsync(key);
}
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); var keys = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new SetPasswordRequest var request = new SetPasswordRequest
{ {
MasterPasswordHash = masterPasswordHash, MasterPasswordHash = masterPasswordHash,
Key = encKey.Item2.EncryptedString, Key = newProtectedUserKey.EncryptedString,
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256), Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations), KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
@@ -204,19 +196,19 @@ namespace Bit.App.Pages
// Set Password and relevant information // Set Password and relevant information
await _apiService.SetPasswordAsync(request); await _apiService.SetPasswordAsync(request);
await _stateService.SetKdfConfigurationAsync(kdfConfig); await _stateService.SetKdfConfigurationAsync(kdfConfig);
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetMasterKeyAsync(newMasterKey);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash); await _cryptoService.SetPasswordHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString); await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString); await _cryptoService.SetPrivateKeyAsync(keys.Item2.EncryptedString);
if (ResetPasswordAutoEnroll) if (ResetPasswordAutoEnroll)
{ {
// Grab Organization Keys // Grab Organization Keys
var response = await _apiService.GetOrganizationKeysAsync(OrgId); var response = await _apiService.GetOrganizationKeysAsync(OrgId);
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey); var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
// Grab user's Encryption Key and encrypt with Org Public Key // Grab User Key and encrypt with Org Public Key
var userEncKey = await _cryptoService.GetEncKeyAsync(); var userKey = await _cryptoService.GetUserKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey); var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
// Request // Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{ {

View File

@@ -37,6 +37,8 @@ namespace Bit.App.Pages
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync()); Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
_vm.UpdateTempPasswordAction = _vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartDeviceApprovalOptionsAction =
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync(); _vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView; DuoWebView = _duoWebView;
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
@@ -180,6 +182,12 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task TwoFactorAuthSuccessAsync() private async Task TwoFactorAuthSuccessAsync()
{ {
if (_authingWithSso) if (_authingWithSso)

View File

@@ -11,6 +11,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Newtonsoft.Json; using Newtonsoft.Json;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
@@ -33,7 +34,7 @@ namespace Bit.App.Pages
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService; private readonly IAppIdService _appIdService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private TwoFactorProviderType? _selectedProviderType; private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction; private string _totpInstruction;
private string _webVaultUrl = "https://vault.bitwarden.com"; private string _webVaultUrl = "https://vault.bitwarden.com";
@@ -55,6 +56,7 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService"); _appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_logger = ServiceContainer.Resolve<ILogger>(); _logger = ServiceContainer.Resolve<ILogger>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.TwoStepLogin; PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync()); SubmitCommand = new Command(async () => await SubmitAsync());
@@ -118,6 +120,7 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; } public Command SubmitCommand { get; }
public ICommand MoreCommand { get; } public ICommand MoreCommand { get; }
public Action TwoFactorAuthSuccessAction { get; set; } public Action TwoFactorAuthSuccessAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action StartSetPasswordAction { get; set; } public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; } public Action UpdateTempPasswordAction { get; set; }
@@ -315,6 +318,7 @@ namespace Bit.App.Pages
var task = Task.Run(() => _syncService.FullSyncAsync(true)); var task = Task.Run(() => _syncService.FullSyncAsync(true));
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
_messagingService.Send("listenYubiKeyOTP", false); _messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage)); _broadcasterService.Unsubscribe(nameof(TwoFactorPage));
@@ -326,6 +330,27 @@ namespace Bit.App.Pages
{ {
UpdateTempPasswordAction?.Invoke(); UpdateTempPasswordAction?.Invoke();
} }
else if (decryptOptions?.TrustedDeviceOption != null)
{
// 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();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
TwoFactorAuthSuccessAction?.Invoke();
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
}
else else
{ {
TwoFactorAuthSuccessAction?.Invoke(); TwoFactorAuthSuccessAction?.Invoke();

View File

@@ -93,12 +93,12 @@ namespace Bit.App.Pages
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
// Create new key and hash new password // Create new master key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey);
// Create new encKey for the User // Encrypt user key with new master key
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key); var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
// Initiate API action // Initiate API action
try try
@@ -108,10 +108,10 @@ namespace Bit.App.Pages
switch (_reason) switch (_reason)
{ {
case ForcePasswordResetReason.AdminForcePasswordReset: case ForcePasswordResetReason.AdminForcePasswordReset:
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString); await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break; break;
case ForcePasswordResetReason.WeakMasterPasswordOnLogin: case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString); await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();

View File

@@ -48,6 +48,7 @@ namespace Bit.App.Pages
private bool _reportLoggingEnabled; private bool _reportLoggingEnabled;
private bool _approvePasswordlessLoginRequests; private bool _approvePasswordlessLoginRequests;
private bool _shouldConnectToWatch; private bool _shouldConnectToWatch;
private bool _hasMasterPassword;
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions = private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
new List<KeyValuePair<string, int?>> new List<KeyValuePair<string, int?>>
{ {
@@ -100,12 +101,17 @@ namespace Bit.App.Pages
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false); 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 ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; } public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
public async Task InitAsync() public async Task InitAsync()
{ {
var decryptionOptions = await _stateService.GetAccountDecryptionOptions();
// set has true for backwards compatibility
_hasMasterPassword = decryptionOptions?.HasMasterPassword ?? true;
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync(); _supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
var lastSync = await _syncService.GetLastSyncAsync(); var lastSync = await _syncService.GetLastSyncAsync();
if (lastSync != null) if (lastSync != null)
@@ -124,7 +130,13 @@ namespace Bit.App.Pages
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key; _vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key; _vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
var action = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock; var savedVaultTimeoutAction = await _vaultTimeoutService.GetVaultTimeoutAction();
var action = savedVaultTimeoutAction ?? VaultTimeoutAction.Lock;
if (!_hasMasterPassword && savedVaultTimeoutAction == null)
{
action = VaultTimeoutAction.Logout;
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
}
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key; _vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout)) if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
@@ -139,7 +151,7 @@ namespace Bit.App.Pages
} }
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync(); var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2; _pin = pinSet != PinLockEnum.Disabled;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync(); _biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync(); _screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
@@ -387,8 +399,11 @@ namespace Bit.App.Pages
// do nothing if we have a policy set // do nothing if we have a policy set
return; 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, var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
AppResources.Cancel, null, options); AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel) if (selection == null || selection == AppResources.Cancel)
@@ -437,19 +452,20 @@ namespace Bit.App.Pages
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig); var pinKey = await _cryptoService.MakePinKeyAsync(pin, email, kdfConfig);
var key = await _cryptoService.GetKeyAsync(); var userKey = await _cryptoService.GetUserKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); var pinProtectedKey = await _cryptoService.EncryptAsync(userKey.Key, pinKey);
var encPin = await _cryptoService.EncryptAsync(pin);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
if (masterPassOnRestart) if (masterPassOnRestart)
{ {
var encPin = await _cryptoService.EncryptAsync(pin); await _stateService.SetUserKeyPinEphemeralAsync(pinProtectedKey);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
} }
else else
{ {
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString); await _stateService.SetUserKeyPinAsync(pinProtectedKey);
} }
} }
else else
@@ -459,7 +475,6 @@ namespace Bit.App.Pages
} }
if (!_pin) if (!_pin)
{ {
await _cryptoService.ClearPinProtectedKeyAsync();
await _vaultTimeoutService.ClearAsync(); await _vaultTimeoutService.ClearAsync();
} }
BuildList(); BuildList();
@@ -489,9 +504,10 @@ namespace Bit.App.Pages
else else
{ {
await _stateService.SetBiometricUnlockAsync(null); await _stateService.SetBiometricUnlockAsync(null);
await UpdateVaultTimeoutActionIfNeededAsync();
} }
await _stateService.SetBiometricLockedAsync(false); await _stateService.SetBiometricLockedAsync(false);
await _cryptoService.ToggleKeyAsync(); await _cryptoService.ToggleKeysAsync();
BuildList(); BuildList();
} }
@@ -835,9 +851,11 @@ namespace Bit.App.Pages
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value; 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() public async Task SetScreenCaptureAllowedAsync()
{ {
@@ -869,5 +887,17 @@ namespace Bit.App.Pages
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch); await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
BuildList(); 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);
}
} }
} }

View File

@@ -74,7 +74,7 @@ namespace Bit.App.Pages
_cipherDomain = await _cipherService.GetAsync(CipherId); _cipherDomain = await _cipherService.GetAsync(CipherId);
Cipher = await _cipherDomain.DecryptAsync(); Cipher = await _cipherDomain.DecryptAsync();
LoadAttachments(); LoadAttachments();
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync(); _hasUpdatedKey = await _cryptoService.HasUserKeyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync(); var canAccessPremium = await _stateService.CanAccessPremiumAsync();
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null; _canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
if (!_canAccessAttachments) if (!_canAccessAttachments)

View File

@@ -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> /// <summary>
/// Looks up a localized string similar to All. /// Looks up a localized string similar to All.
/// </summary> /// </summary>
@@ -562,6 +571,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> /// <summary>
/// Looks up a localized string similar to April. /// Looks up a localized string similar to April.
/// </summary> /// </summary>
@@ -3658,6 +3685,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> /// <summary>
/// Looks up a localized string similar to Logged in as {0} on {1}.. /// Looks up a localized string similar to Logged in as {0} on {1}..
/// </summary> /// </summary>
@@ -3676,6 +3712,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> /// <summary>
/// Looks up a localized string similar to Logging in as {0} on {1}. /// Looks up a localized string similar to Logging in as {0} on {1}.
/// </summary> /// </summary>
@@ -5273,6 +5318,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> /// <summary>
/// Looks up a localized string similar to Remove. /// Looks up a localized string similar to Remove.
/// </summary> /// </summary>
@@ -5345,6 +5399,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> /// <summary>
/// Looks up a localized string similar to Request one-time password. /// Looks up a localized string similar to Request one-time password.
/// </summary> /// </summary>
@@ -6353,6 +6416,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> /// <summary>
/// Looks up a localized string similar to Try again. /// Looks up a localized string similar to Try again.
/// </summary> /// </summary>
@@ -6362,6 +6434,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> /// <summary>
/// Looks up a localized string similar to 20 seconds. /// Looks up a localized string similar to 20 seconds.
/// </summary> /// </summary>
@@ -6893,6 +6974,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> /// <summary>
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}.. /// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
/// </summary> /// </summary>
@@ -7262,6 +7352,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> /// <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&apos;s USB port, then touch its button.. /// 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&apos;s USB port, then touch its button..
/// </summary> /// </summary>

View File

@@ -2628,6 +2628,24 @@ Do you want to switch to this account?</value>
<data name="CurrentMasterPassword" xml:space="preserve"> <data name="CurrentMasterPassword" xml:space="preserve">
<value>Current master password</value> <value>Current master password</value>
</data> </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="MasterPasswordRePromptHelp" xml:space="preserve"> <data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Master password re-prompt help</value> <value>Master password re-prompt help</value>
</data> </data>
@@ -2640,6 +2658,24 @@ Do you want to switch to this account?</value>
<data name="InvalidAPIToken" xml:space="preserve"> <data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value> <value>Invalid API token</value>
</data> </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="BlockAutoFill" xml:space="preserve"> <data name="BlockAutoFill" xml:space="preserve">
<value>Block auto-fill</value> <value>Block auto-fill</value>
</data> </data>

View File

@@ -38,7 +38,7 @@ namespace Bit.App.Services
return false; return false;
}; };
return await _cryptoService.CompareAndUpdateKeyHashAsync(password, null); return await _cryptoService.CompareAndUpdatePasswordHashAsync(password, null);
} }
public async Task<bool> Enabled() public async Task<bool> Enabled()

View File

@@ -4,6 +4,7 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Models.Response; using Bit.Core.Models.Response;
@@ -70,7 +71,7 @@ namespace Bit.Core.Abstractions
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier); Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId, Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
OrganizationUserResetPasswordEnrollmentRequest request); OrganizationUserResetPasswordEnrollmentRequest request);
Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl); Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnector(string keyConnectorUrl);
Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request); Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request); Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnector(); Task PostConvertToKeyConnector();
@@ -90,9 +91,12 @@ namespace Bit.Core.Abstractions
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id); Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode); Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved); 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<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier);
Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest);
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email); Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
Task<ConfigResponse> GetConfigsAsync(); Task<ConfigResponse> GetConfigsAsync();
Task<string> GetFastmailAccountIdAsync(string apiKey); Task<string> GetFastmailAccountIdAsync(string apiKey);
} }

View File

@@ -32,9 +32,12 @@ namespace Bit.Core.Abstractions
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync(); Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync(); Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id); 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> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email); Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
void LogOut(Action callback); void LogOut(Action callback);
void Init(); void Init();

View File

@@ -9,49 +9,54 @@ namespace Bit.Core.Abstractions
{ {
public interface ICryptoService 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(); void ClearCache();
Task ToggleKeysAsync();
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 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(UserKey key);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeDataEncKeyAsync(OrgKey key);
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task SetPasswordHashAsync(string keyHash);
Task<string> GetPasswordHashAsync();
Task ClearPasswordHashAsync(string userId = null);
Task<bool> CompareAndUpdatePasswordHashAsync(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[]> GetPublicKeyAsync();
Task SetPrivateKeyAsync(string encPrivateKey);
Task<byte[]> GetPrivateKeyAsync();
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[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null); Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null);
Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null); Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null);
Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null); Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
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();
} }
} }

View File

@@ -0,0 +1,16 @@
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<bool> GetShouldTrustDeviceAsync();
Task SetShouldTrustDeviceAsync(bool value);
Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey);
Task<bool> IsDeviceTrustedAsync();
}
}

View File

@@ -13,6 +13,12 @@ namespace Bit.Core.Abstractions
public interface IStateService public interface IStateService
{ {
List<AccountView> AccountViews { get; } 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> GetUserKeyMasterKeyAsync(string userId = null);
Task SetUserKeyMasterKeyAsync(string value, string userId = null);
Task<string> GetActiveUserIdAsync(); Task<string> GetActiveUserIdAsync();
Task<string> GetActiveUserEmailAsync(); Task<string> GetActiveUserEmailAsync();
Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper); Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper);
@@ -36,26 +42,30 @@ namespace Bit.Core.Abstractions
Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null); Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null); Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
Task<bool> CanAccessPremiumAsync(string userId = null); Task<bool> CanAccessPremiumAsync(string userId = null);
Task SetPersonalPremiumAsync(bool value, string userId = null);
Task<string> GetProtectedPinAsync(string userId = null); Task<string> GetProtectedPinAsync(string userId = null);
Task SetPersonalPremiumAsync(bool value, string userId = null);
Task<EncString> GetUserKeyPinAsync(string userId = null);
Task SetUserKeyPinAsync(EncString value, string userId = null);
Task<EncString> GetUserKeyPinEphemeralAsync(string userId = null);
Task SetUserKeyPinEphemeralAsync(EncString value, string userId = null);
Task SetProtectedPinAsync(string value, string userId = null); Task SetProtectedPinAsync(string value, string userId = null);
[Obsolete("Use GetUserKeyPinAsync instead, left for migration purposes")]
Task<string> GetPinProtectedAsync(string userId = null); Task<string> GetPinProtectedAsync(string userId = null);
[Obsolete("Use SetUserKeyPinAsync instead")]
Task SetPinProtectedAsync(string value, string userId = null); Task SetPinProtectedAsync(string value, string userId = null);
[Obsolete("Use GetUserKeyPinEphemeralAsync instead, left for migration purposes")]
Task<EncString> GetPinProtectedKeyAsync(string userId = null); Task<EncString> GetPinProtectedKeyAsync(string userId = null);
[Obsolete("Use SetUserKeyPinEphemeralAsync instead")]
Task SetPinProtectedKeyAsync(EncString value, string userId = null); Task SetPinProtectedKeyAsync(EncString value, string userId = null);
Task SetKdfConfigurationAsync(KdfConfig config, 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<string> GetKeyHashAsync(string userId = null);
Task SetKeyHashAsync(string value, 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<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null);
Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null); Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
Task<string> GetPrivateKeyEncryptedAsync(string userId = null); Task<string> GetPrivateKeyEncryptedAsync(string userId = null);
Task SetPrivateKeyEncryptedAsync(string value, 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<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null);
Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null); Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
Task<bool?> GetAutofillTileAddedAsync(); Task<bool?> GetAutofillTileAddedAsync();
@@ -172,9 +182,26 @@ namespace Bit.Core.Abstractions
Task<string> GetAvatarColorAsync(string userId = null); Task<string> GetAvatarColorAsync(string userId = null);
Task<string> GetPreLoginEmailAsync(); Task<string> GetPreLoginEmailAsync();
Task SetPreLoginEmailAsync(string value); 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(); string GetLocale();
void SetLocale(string locale); void SetLocale(string locale);
ConfigResponse GetConfigs(); ConfigResponse GetConfigs();
void SetConfigs(ConfigResponse value); void SetConfigs(ConfigResponse value);
Task<bool> GetShouldTrustDeviceAsync();
Task SetShouldTrustDeviceAsync(bool value);
[Obsolete("Use GetUserKeyMasterKey instead")]
Task<string> GetEncKeyEncryptedAsync(string userId = null);
[Obsolete("Use SetUserKeyMasterKey instead")]
Task SetEncKeyEncryptedAsync(string value, string userId = null);
[Obsolete]
Task<string> GetKeyEncryptedAsync(string userId = null);
[Obsolete]
Task SetKeyEncryptedAsync(string value, string userId = null);
[Obsolete("Use GetMasterKey instead")]
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
[Obsolete("Use GetMasterKey instead")]
Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null);
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Services;
namespace Bit.Core.Abstractions namespace Bit.Core.Abstractions
{ {
@@ -16,7 +17,7 @@ namespace Bit.Core.Abstractions
Task<bool> ShouldLockAsync(string userId = null); Task<bool> ShouldLockAsync(string userId = null);
Task<bool> IsLoggedOutByTimeoutAsync(string userId = null); Task<bool> IsLoggedOutByTimeoutAsync(string userId = null);
Task<bool> ShouldLogOutByTimeoutAsync(string userId = null); Task<bool> ShouldLogOutByTimeoutAsync(string userId = null);
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null); Task<PinLockEnum> IsPinLockSetAsync(string userId = null);
Task<bool> IsBiometricLockSetAsync(string userId = null); Task<bool> IsBiometricLockSetAsync(string userId = null);
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null); Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
Task LogOutAsync(bool userInitiated = true, string userId = null); Task LogOutAsync(bool userInitiated = true, string userId = null);

View File

@@ -1,4 +1,6 @@
namespace Bit.Core using System;
namespace Bit.Core
{ {
public static class Constants public static class Constants
{ {
@@ -53,6 +55,7 @@
public const string AppLocaleKey = "appLocale"; public const string AppLocaleKey = "appLocale";
public const string ClearSensitiveFields = "clearSensitiveFields"; public const string ClearSensitiveFields = "clearSensitiveFields";
public const string ForceUpdatePassword = "forceUpdatePassword"; public const string ForceUpdatePassword = "forceUpdatePassword";
public const string ShouldTrustDevice = "shouldTrustDevice";
public const int SelectFileRequestCode = 42; public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43; public const int SelectFilePermissionRequestCode = 43;
public const int SaveFileRequestCode = 44; public const int SaveFileRequestCode = 44;
@@ -79,6 +82,7 @@
public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}"; public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}";
public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}"; public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}";
public static string UserKeyKey(string userId) => $"userKey_{userId}";
public static string CiphersKey(string userId) => $"ciphers_{userId}"; public static string CiphersKey(string userId) => $"ciphers_{userId}";
public static string FoldersKey(string userId) => $"folders_{userId}"; public static string FoldersKey(string userId) => $"folders_{userId}";
public static string CollectionsKey(string userId) => $"collections_{userId}"; public static string CollectionsKey(string userId) => $"collections_{userId}";
@@ -87,12 +91,11 @@
public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}"; public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}";
public static string SendsKey(string userId) => $"sends_{userId}"; public static string SendsKey(string userId) => $"sends_{userId}";
public static string PoliciesKey(string userId) => $"policies_{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 EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}";
public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{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 KeyHashKey(string userId) => $"keyHash_{userId}";
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}"; public static string UserKeyPinKey(string userId) => $"userKeyPin_{userId}";
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}"; public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";
public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}"; public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}";
public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}"; public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}";
@@ -121,5 +124,12 @@
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}"; public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}"; public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{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}";
} }
} }

View File

@@ -0,0 +1,11 @@
using System;
namespace Bit.Core.Enums
{
public enum AuthRequestType : byte
{
AuthenticateAndUnlock = 0,
Unlock = 1,
AdminApproval = 2
}
}

View File

@@ -1,4 +1,7 @@
namespace Bit.Core.Enums using System.Collections.Generic;
using System.Linq;
namespace Bit.Core.Enums
{ {
public enum DeviceType : byte public enum DeviceType : byte
{ {
@@ -24,4 +27,24 @@
VivaldiExtension = 19, VivaldiExtension = 19,
SafariExtension = 20 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();
}
} }

View File

@@ -53,6 +53,7 @@ namespace Bit.Core.Models.Domain
HasPremiumPersonally = copy.HasPremiumPersonally; HasPremiumPersonally = copy.HasPremiumPersonally;
AvatarColor = copy.AvatarColor; AvatarColor = copy.AvatarColor;
ForcePasswordResetReason = copy.ForcePasswordResetReason; ForcePasswordResetReason = copy.ForcePasswordResetReason;
UserDecryptionOptions = copy.UserDecryptionOptions;
} }
public string UserId; public string UserId;
@@ -68,6 +69,7 @@ namespace Bit.Core.Models.Domain
public bool? EmailVerified; public bool? EmailVerified;
public bool? HasPremiumPersonally; public bool? HasPremiumPersonally;
public ForcePasswordResetReason? ForcePasswordResetReason; public ForcePasswordResetReason? ForcePasswordResetReason;
public AccountDecryptionOptions UserDecryptionOptions;
} }
public class AccountTokens public class AccountTokens
@@ -117,9 +119,14 @@ namespace Bit.Core.Models.Domain
public class AccountVolatileData public class AccountVolatileData
{ {
public SymmetricCryptoKey Key; public UserKey UserKey;
public EncString PinProtectedKey; public MasterKey MasterKey;
public EncString UserKeyPinEphemeral;
public bool? BiometricLocked; 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;
} }
} }
} }

View File

@@ -0,0 +1,25 @@
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 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; }
}
}

View 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; }
}
}

View File

@@ -74,4 +74,32 @@ namespace Bit.Core.Models.Domain
public string EncKeyB64 { get; set; } public string EncKeyB64 { get; set; }
public string MacKeyB64 { 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)
{ }
}
} }

View File

@@ -1,4 +1,6 @@
using System; using System;
using Bit.Core.Enums;
namespace Bit.Core.Models.Request namespace Bit.Core.Models.Request
{ {
public class PasswordlessCreateLoginRequest public class PasswordlessCreateLoginRequest
@@ -25,10 +27,4 @@ namespace Bit.Core.Models.Request
public string FingerprintPhrase { get; set; } public string FingerprintPhrase { get; set; }
} }
public enum AuthRequestType : byte
{
AuthenticateAndUnlock = 0,
Unlock = 1
}
} }

View 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; }
}
}

View 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; }
}

View File

@@ -27,6 +27,7 @@ namespace Bit.Core.Models.Response
public bool ForcePasswordReset { get; set; } public bool ForcePasswordReset { get; set; }
public string KeyConnectorUrl { get; set; } public string KeyConnectorUrl { get; set; }
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; } public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
public AccountDecryptionOptions UserDecryptionOptions { get; set; }
[JsonIgnore] [JsonIgnore]
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism); public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
} }

View File

@@ -397,12 +397,38 @@ namespace Bit.Core.Services
#region Device APIs #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) public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
{ {
return SendAsync<DeviceTokenRequest, object>( return SendAsync<DeviceTokenRequest, object>(
HttpMethod.Put, $"/devices/identifier/{identifier}/token", request, true, false); 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 #endregion
#region Event APIs #region Event APIs
@@ -485,7 +511,7 @@ namespace Bit.Core.Services
#region Key Connector #region Key Connector
public async Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl) public async Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnector(string keyConnectorUrl)
{ {
using (var requestMessage = new HttpRequestMessage()) 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); 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) 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); 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 #endregion
#region Configs #region Configs

View File

@@ -27,10 +27,11 @@ namespace Bit.Core.Services
private readonly IKeyConnectorService _keyConnectorService; private readonly IKeyConnectorService _keyConnectorService;
private readonly IPasswordGenerationService _passwordGenerationService; private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly bool _setCryptoKeys; private readonly bool _setCryptoKeys;
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>(); private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
private SymmetricCryptoKey _key; private MasterKey _masterKey;
private string _authedUserId; private string _authedUserId;
private MasterPasswordPolicyOptions _masterPasswordPolicy; private MasterPasswordPolicyOptions _masterPasswordPolicy;
@@ -50,6 +51,7 @@ namespace Bit.Core.Services
IKeyConnectorService keyConnectorService, IKeyConnectorService keyConnectorService,
IPasswordGenerationService passwordGenerationService, IPasswordGenerationService passwordGenerationService,
IPolicyService policyService, IPolicyService policyService,
IDeviceTrustCryptoService deviceTrustCryptoService,
bool setCryptoKeys = true) bool setCryptoKeys = true)
{ {
_cryptoService = cryptoService; _cryptoService = cryptoService;
@@ -64,6 +66,7 @@ namespace Bit.Core.Services
_keyConnectorService = keyConnectorService; _keyConnectorService = keyConnectorService;
_passwordGenerationService = passwordGenerationService; _passwordGenerationService = passwordGenerationService;
_policyService = policyService; _policyService = policyService;
_deviceTrustCryptoService = deviceTrustCryptoService;
_setCryptoKeys = setCryptoKeys; _setCryptoKeys = setCryptoKeys;
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>(); TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
@@ -195,11 +198,34 @@ namespace Bit.Core.Services
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy); 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(string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
{ {
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey); var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, 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, // On SSO flow user is already AuthN
if (await _stateService.IsAuthenticatedAsync())
{
if (string.IsNullOrEmpty(masterKeyHash))
{
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
}
else
{
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(new MasterKey(decryptedKey));
await _cryptoService.SetUserKeyAsync(userKey);
}
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
return null;
}
if (string.IsNullOrEmpty(masterKeyHash) && decryptionKey != null)
{
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
return null;
}
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); null, null, authRequestId: authRequestId);
} }
@@ -216,7 +242,7 @@ namespace Bit.Core.Services
{ {
CaptchaToken = captchaToken; CaptchaToken = captchaToken;
} }
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key, var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _masterKey,
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId); twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId);
// If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync() // If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync()
@@ -337,7 +363,7 @@ namespace Bit.Core.Services
// Helpers // Helpers
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email) private async Task<MasterKey> MakePreloginKeyAsync(string masterPassword, string email)
{ {
email = email.Trim().ToLower(); email = email.Trim().ToLower();
KdfConfig kdfConfig = KdfConfig.Default; KdfConfig kdfConfig = KdfConfig.Default;
@@ -356,11 +382,11 @@ namespace Bit.Core.Services
throw; 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, 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, 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)
{ {
@@ -426,7 +452,7 @@ namespace Bit.Core.Services
Code = code; Code = code;
CodeVerifier = codeVerifier; CodeVerifier = codeVerifier;
SsoRedirectUrl = redirectUrl; SsoRedirectUrl = redirectUrl;
_key = _setCryptoKeys ? key : null; _masterKey = _setCryptoKeys ? masterKey : null;
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2; TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2; result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
CaptchaToken = response.TwoFactorResponse.CaptchaToken; CaptchaToken = response.TwoFactorResponse.CaptchaToken;
@@ -459,6 +485,7 @@ namespace Bit.Core.Services
ForcePasswordResetReason = result.ForcePasswordReset ForcePasswordResetReason = result.ForcePasswordReset
? ForcePasswordResetReason.AdminForcePasswordReset ? ForcePasswordResetReason.AdminForcePasswordReset
: (ForcePasswordResetReason?)null, : (ForcePasswordResetReason?)null,
UserDecryptionOptions = tokenResponse.UserDecryptionOptions,
}, },
new Account.AccountTokens() new Account.AccountTokens()
{ {
@@ -470,24 +497,46 @@ namespace Bit.Core.Services
_messagingService.Send("accountAdded"); _messagingService.Send("accountAdded");
if (_setCryptoKeys) if (_setCryptoKeys)
{ {
if (key != null)
{
await _cryptoService.SetKeyAsync(key);
}
if (localHashedPassword != null) if (localHashedPassword != null)
{ {
await _cryptoService.SetKeyHashAsync(localHashedPassword); await _cryptoService.SetPasswordHashAsync(localHashedPassword);
await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
} }
if (code == null || tokenResponse.Key != null) if (code == null || tokenResponse.Key != null)
{ {
if (tokenResponse.KeyConnectorUrl != null) var decryptOptions = await _stateService.GetAccountDecryptionOptions();
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
if (decryptOptions?.TrustedDeviceOption != null)
{ {
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl); var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey, decryptOptions.TrustedDeviceOption.EncryptedUserKey);
if (key != null)
{
await _cryptoService.SetUserKeyAsync(key);
}
}
else if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
{
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
if (masterKey != null)
{
await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
}
} }
await _cryptoService.SetEncKeyAsync(tokenResponse.Key); // Login with Device
if (masterKey != null && !string.IsNullOrEmpty(authRequestId))
{
await _cryptoService.SetMasterKeyAsync(masterKey);
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. // User doesn't have a key pair yet (old account), let's generate one for them.
if (tokenResponse.PrivateKey == null) if (tokenResponse.PrivateKey == null)
@@ -505,19 +554,22 @@ namespace Bit.Core.Services
catch { } catch { }
} }
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey); await _cryptoService.SetPrivateKeyAsync(tokenResponse.PrivateKey);
} }
else if (tokenResponse.KeyConnectorUrl != null) else if (tokenResponse.KeyConnectorUrl != null)
{ {
// SSO Key Connector Onboarding // SSO Key Connector Onboarding
var password = await _cryptoFunctionService.RandomBytesAsync(64); var password = await _cryptoFunctionService.RandomBytesAsync(64);
var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig); var newMasterKey = await _cryptoService.MakeMasterKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64); var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
await _cryptoService.SetKeyAsync(k); await _cryptoService.SetMasterKeyAsync(newMasterKey);
var encKey = await _cryptoService.MakeEncKeyAsync(k); var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString); newMasterKey,
var keyPair = await _cryptoService.MakeKeyPairAsync(); await _cryptoService.MakeUserKeyAsync());
await _cryptoService.SetUserKeyAsync(newUserKey);
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
try try
{ {
@@ -530,15 +582,14 @@ namespace Bit.Core.Services
var keys = new KeysRequest var keys = new KeysRequest
{ {
PublicKey = keyPair.Item1, PublicKey = newPublicKey,
EncryptedPrivateKey = keyPair.Item2.EncryptedString EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
}; };
var setPasswordRequest = new SetKeyConnectorKeyRequest( var setPasswordRequest = new SetKeyConnectorKeyRequest(
encKey.Item2.EncryptedString, keys, tokenResponse.KdfConfig, orgId newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
); );
await _apiService.PostSetKeyConnectorKey(setPasswordRequest); await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
} }
} }
_authedUserId = _tokenService.GetUserId(); _authedUserId = _tokenService.GetUserId();
@@ -549,7 +600,7 @@ namespace Bit.Core.Services
private void ClearState() private void ClearState()
{ {
_key = null; _masterKey = null;
Email = null; Email = null;
CaptchaToken = null; CaptchaToken = null;
MasterPasswordHash = null; MasterPasswordHash = null;
@@ -575,14 +626,14 @@ namespace Bit.Core.Services
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList(); var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
return await PopulateFingerprintPhrasesAsync(activeRequests); return await PopulateFingerprintPhrasesAsync(activeRequests);
} }
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id) public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
{ {
var response = await _apiService.GetAuthRequestAsync(id); var response = await _apiService.GetAuthRequestAsync(id);
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync()); return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
} }
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); return await _apiService.GetAuthResponseAsync(id, accessCode);
} }
@@ -590,15 +641,20 @@ namespace Bit.Core.Services
public async Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved) public async Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved)
{ {
var publicKey = CoreHelpers.Base64UrlDecode(pubKey); var publicKey = CoreHelpers.Base64UrlDecode(pubKey);
var masterKey = await _cryptoService.GetKeyAsync(); var masterKey = await _cryptoService.GetMasterKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey); var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey); var keyHash = await _stateService.GetKeyHashAsync();
EncString encryptedMasterPassword = null;
if (!string.IsNullOrEmpty(keyHash))
{
encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(keyHash), publicKey);
}
var deviceId = await _appIdService.GetAppIdAsync(); 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()); 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 deviceId = await _appIdService.GetAppIdAsync();
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048); var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
@@ -606,8 +662,8 @@ namespace Bit.Core.Services
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase); var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
var publicB64 = Convert.ToBase64String(keyPair.Item1); var publicB64 = Convert.ToBase64String(keyPair.Item1);
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25)); var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, AuthRequestType.AuthenticateAndUnlock, fingerprintPhrase); var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, authRequestType, fingerprintPhrase);
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest); var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest, authRequestType);
if (response != null) if (response != null)
{ {

View File

@@ -249,7 +249,7 @@ namespace Bit.Core.Services
{ {
try try
{ {
var hashKey = await _cryptoService.HasKeyAsync(); var hashKey = await _cryptoService.HasUserKeyAsync();
if (!hashKey) if (!hashKey)
{ {
throw new Exception("No key."); throw new Exception("No key.");
@@ -557,9 +557,20 @@ namespace Bit.Core.Services
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data) public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
{ {
SymmetricCryptoKey attachmentKey;
EncString protectedAttachmentKey;
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId); var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
if (orgKey != null)
{
(attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(orgKey);
}
else
{
var userKey = await _cryptoService.GetUserKeyWithLegacySupportAsync();
(attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(userKey);
}
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey); var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey); var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
CipherResponse response; CipherResponse response;
@@ -567,7 +578,7 @@ namespace Bit.Core.Services
{ {
var request = new AttachmentRequest var request = new AttachmentRequest
{ {
Key = orgEncAttachmentKey.EncryptedString, Key = protectedAttachmentKey.EncryptedString,
FileName = encFileName.EncryptedString, FileName = encFileName.EncryptedString,
FileSize = encFileData.Buffer.Length, FileSize = encFileData.Buffer.Length,
}; };
@@ -578,7 +589,7 @@ namespace Bit.Core.Services
} }
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed) 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(); var userId = await _stateService.GetActiveUserIdAsync();
@@ -807,14 +818,27 @@ namespace Bit.Core.Services
var bytes = await attachmentResponse.Content.ReadAsByteArrayAsync(); var bytes = await attachmentResponse.Content.ReadAsByteArrayAsync();
var decBytes = await _cryptoService.DecryptFromBytesAsync(bytes, null); var decBytes = await _cryptoService.DecryptFromBytesAsync(bytes, null);
var key = await _cryptoService.GetOrgKeyAsync(organizationId);
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, key); SymmetricCryptoKey attachmentKey;
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key); EncString protectedAttachmentKey;
var encData = await _cryptoService.EncryptToBytesAsync(decBytes, dataEncKey.Item1); var orgKey = await _cryptoService.GetOrgKeyAsync(organizationId);
if (orgKey != null)
{
(attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(orgKey);
}
else
{
var userKey = await _cryptoService.GetUserKeyWithLegacySupportAsync();
(attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(userKey);
}
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, orgKey);
var encFileData = await _cryptoService.EncryptToBytesAsync(decBytes, attachmentKey);
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks); var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
var fd = new MultipartFormDataContent(boundary); var fd = new MultipartFormDataContent(boundary);
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key"); fd.Add(new StringContent(protectedAttachmentKey.EncryptedString), "key");
fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString); fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString);
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId); await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
} }

View File

@@ -101,7 +101,7 @@ namespace Bit.Core.Services
{ {
return _decryptedCollectionCache; return _decryptedCollectionCache;
} }
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
throw new Exception("No key."); throw new Exception("No key.");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@

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<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);
}
}
}

View File

@@ -77,7 +77,7 @@ namespace Bit.Core.Services
{ {
return _decryptedFolderCache; return _decryptedFolderCache;
} }
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
throw new Exception("No key."); throw new Exception("No key.");

View File

@@ -28,10 +28,10 @@ namespace Bit.Core.Services
{ {
try try
{ {
var userKeyResponse = await _apiService.GetUserKeyFromKeyConnector(url); var masterKeyResponse = await _apiService.GetMasterKeyFromKeyConnector(url);
var keyArr = Convert.FromBase64String(userKeyResponse.Key); var masterKeyArr = Convert.FromBase64String(masterKeyResponse.Key);
var k = new SymmetricCryptoKey(keyArr); var masterKey = new MasterKey(masterKeyArr);
await _cryptoService.SetKeyAsync(k); await _cryptoService.SetMasterKeyAsync(masterKey);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -60,11 +60,11 @@ namespace Bit.Core.Services
public async Task MigrateUser() public async Task MigrateUser()
{ {
var organization = await GetManagingOrganization(); var organization = await GetManagingOrganization();
var key = await _cryptoService.GetKeyAsync(); var masterKey = await _cryptoService.GetMasterKeyAsync();
try try
{ {
var keyConnectorRequest = new KeyConnectorUserKeyRequest(key.EncKeyB64); var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64);
await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest); await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest);
} }
catch (Exception e) catch (Exception e)

View File

@@ -247,7 +247,7 @@ namespace Bit.Core.Services
public async Task<List<GeneratedPasswordHistory>> GetHistoryAsync() public async Task<List<GeneratedPasswordHistory>> GetHistoryAsync()
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
return new List<GeneratedPasswordHistory>(); return new List<GeneratedPasswordHistory>();
@@ -262,7 +262,7 @@ namespace Bit.Core.Services
public async Task AddHistoryAsync(string password, CancellationToken token = default(CancellationToken)) public async Task AddHistoryAsync(string password, CancellationToken token = default(CancellationToken))
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
return; return;

View File

@@ -143,7 +143,7 @@ namespace Bit.Core.Services
return _decryptedSendsCache; return _decryptedSendsCache;
} }
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
throw new Exception("No Key."); throw new Exception("No Key.");

View File

@@ -302,6 +302,52 @@ namespace Bit.Core.Services
true, reconciledOptions); 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> GetUserKeyMasterKeyAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
return await _storageMediatorService.GetAsync<string>(Constants.UserKeyKey(reconciledOptions.UserId), false);
}
public async Task SetUserKeyMasterKeyAsync(string value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
await _storageMediatorService.SaveAsync(Constants.UserKeyKey(reconciledOptions.UserId), value, false);
}
public async Task<bool> CanAccessPremiumAsync(string userId = null) public async Task<bool> CanAccessPremiumAsync(string userId = null)
{ {
if (userId == null) if (userId == null)
@@ -353,6 +399,34 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions); await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions);
} }
public async Task<EncString> GetUserKeyPinAsync(string userId = null)
{
var key = await _storageMediatorService.GetAsync<string>(Constants.UserKeyPinKey(userId), false);
return key != null ? new EncString(key) : null;
}
public async Task SetUserKeyPinAsync(EncString value, string userId = null)
{
await _storageMediatorService.SaveAsync(Constants.UserKeyPinKey(userId), value?.EncryptedString, false);
}
public async Task<EncString> GetUserKeyPinEphemeralAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.UserKeyPinEphemeral;
}
public async Task SetUserKeyPinEphemeralAsync(EncString value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.UserKeyPinEphemeral = value;
await SaveAccountAsync(account, reconciledOptions);
}
[Obsolete("Use GetUserKeyPinAsync instead, left for migration purposes")]
public async Task<string> GetPinProtectedAsync(string userId = null) public async Task<string> GetPinProtectedAsync(string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -360,6 +434,7 @@ namespace Bit.Core.Services
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions); return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions);
} }
[Obsolete("Use SetUserKeyPinAsync instead")]
public async Task SetPinProtectedAsync(string value, string userId = null) public async Task SetPinProtectedAsync(string value, string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -367,6 +442,7 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions); await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
} }
[Obsolete("Use GetUserKeyPinEphemeralAsync instead, left for migration purposes")]
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null) public async Task<EncString> GetPinProtectedKeyAsync(string userId = null)
{ {
return (await GetAccountAsync( return (await GetAccountAsync(
@@ -374,6 +450,7 @@ namespace Bit.Core.Services
))?.VolatileData?.PinProtectedKey; ))?.VolatileData?.PinProtectedKey;
} }
[Obsolete("Use SetUserKeyPinEphemeralAsync instead")]
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null) public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -395,35 +472,6 @@ namespace Bit.Core.Services
await SaveAccountAsync(account, reconciledOptions); 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) public async Task<string> GetKeyHashAsync(string userId = null)
{ {
@@ -439,19 +487,6 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.KeyHashKey(reconciledOptions.UserId), value, reconciledOptions); 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) public async Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null)
{ {
@@ -482,6 +517,25 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions); 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) public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -1280,6 +1334,37 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.PreLoginEmailKey, value, options); 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)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await _storageMediatorService.GetAsync<PendingAdminAuthRequest>(Constants.PendingAdminAuthRequest(reconciledOptions.UserId), true);
}
public async Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await _storageMediatorService.SaveAsync(Constants.PendingAdminAuthRequest(reconciledOptions.UserId), value, true);
}
public ConfigResponse GetConfigs() public ConfigResponse GetConfigs()
{ {
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey); return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
@@ -1656,5 +1741,55 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.LastUserShouldConnectToWatchKey, await SetValueAsync(Constants.LastUserShouldConnectToWatchKey,
shouldConnect ?? await GetShouldConnectToWatchAsync(), await GetDefaultStorageOptionsAsync()); shouldConnect ?? await GetShouldConnectToWatchAsync(), await GetDefaultStorageOptionsAsync());
} }
[Obsolete]
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]
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]
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]
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]
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.Key;
}
[Obsolete]
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);
}
} }
} }

View File

@@ -327,8 +327,8 @@ namespace Bit.Core.Services
} }
return; return;
} }
await _cryptoService.SetEncKeyAsync(response.Key); await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(response.Key);
await _cryptoService.SetEncPrivateKeyAsync(response.PrivateKey); await _cryptoService.SetPrivateKeyAsync(response.PrivateKey);
await _cryptoService.SetOrgKeysAsync(response.Organizations); await _cryptoService.SetOrgKeysAsync(response.Organizations);
await _stateService.SetSecurityStampAsync(response.SecurityStamp); await _stateService.SetSecurityStampAsync(response.SecurityStamp);
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o)); var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));

View File

@@ -44,7 +44,7 @@ namespace Bit.Core.Services
} }
else else
{ {
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(secret, null); var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(secret, null);
if (!passwordValid) if (!passwordValid)
{ {
await InvalidSecretErrorAsync(verificationType); await InvalidSecretErrorAsync(verificationType);

View File

@@ -7,6 +7,13 @@ using Bit.Core.Models.Domain;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public enum PinLockEnum
{
Disabled,
Persistent,
Transient
}
public class VaultTimeoutService : IVaultTimeoutService public class VaultTimeoutService : IVaultTimeoutService
{ {
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
@@ -54,7 +61,7 @@ namespace Bit.Core.Services
public async Task<bool> IsLockedAsync(string userId = null) public async Task<bool> IsLockedAsync(string userId = null)
{ {
var hasKey = await _cryptoService.HasKeyAsync(userId); var hasKey = await _cryptoService.HasUserKeyAsync(userId);
if (hasKey) if (hasKey)
{ {
var biometricSet = await IsBiometricLockSetAsync(userId); var biometricSet = await IsBiometricLockSetAsync(userId);
@@ -165,11 +172,13 @@ namespace Bit.Core.Services
if (await _keyConnectorService.GetUsesKeyConnector()) if (await _keyConnectorService.GetUsesKeyConnector())
{ {
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId); var pinStatus = await IsPinLockSetAsync(userId);
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) || var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
isPinProtectedWithKey; ?? await _stateService.GetPinProtectedKeyAsync();
var pinEnabled = (pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
pinStatus == PinLockEnum.Persistent;
if (!pinLock && !await IsBiometricLockSetAsync()) if (!pinEnabled && !await IsBiometricLockSetAsync())
{ {
await LogOutAsync(userInitiated, userId); await LogOutAsync(userInitiated, userId);
return; return;
@@ -187,10 +196,10 @@ namespace Bit.Core.Services
} }
} }
await Task.WhenAll( await Task.WhenAll(
_cryptoService.ClearKeyAsync(userId), _cryptoService.ClearUserKeyAsync(userId),
_cryptoService.ClearMasterKeyAsync(userId),
_cryptoService.ClearOrgKeysAsync(true, userId), _cryptoService.ClearOrgKeysAsync(true, userId),
_cryptoService.ClearKeyPairAsync(true, userId), _cryptoService.ClearKeyPairAsync(true, userId));
_cryptoService.ClearEncKeyAsync(true, userId));
if (isActiveAccount) if (isActiveAccount)
{ {
@@ -214,15 +223,30 @@ namespace Bit.Core.Services
{ {
await _stateService.SetVaultTimeoutAsync(timeout); await _stateService.SetVaultTimeoutAsync(timeout);
await _stateService.SetVaultTimeoutActionAsync(action); await _stateService.SetVaultTimeoutActionAsync(action);
await _cryptoService.ToggleKeyAsync(); await _cryptoService.ToggleKeysAsync();
await _tokenService.ToggleTokensAsync(); await _tokenService.ToggleTokensAsync();
} }
public async Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null) public async Task<PinLockEnum> IsPinLockSetAsync(string userId = null)
{ {
var protectedPin = await _stateService.GetProtectedPinAsync(userId); // we can't depend on only the protected pin being set because old
var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId); // versions only used it for MP on Restart
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null); var pinIsEnabled = await _stateService.GetProtectedPinAsync(userId);
var userKeyPin = await _stateService.GetUserKeyPinAsync(userId);
var oldUserKeyPin = await _stateService.GetPinProtectedAsync(userId);
if (userKeyPin != null || oldUserKeyPin != null)
{
return PinLockEnum.Persistent;
}
else if (pinIsEnabled != null && userKeyPin == null && oldUserKeyPin == null)
{
return PinLockEnum.Transient;
}
else
{
return PinLockEnum.Disabled;
}
} }
public async Task<bool> IsBiometricLockSetAsync(string userId = null) public async Task<bool> IsBiometricLockSetAsync(string userId = null)
@@ -233,8 +257,7 @@ namespace Bit.Core.Services
public async Task ClearAsync(string userId = null) public async Task ClearAsync(string userId = null)
{ {
await _stateService.SetPinProtectedKeyAsync(null, userId); await _cryptoService.ClearPinKeysAsync(userId);
await _stateService.SetProtectedPinAsync(null, userId);
} }
public async Task<int?> GetVaultTimeout(string userId = null) public async Task<int?> GetVaultTimeout(string userId = null)

View File

@@ -77,9 +77,10 @@ namespace Bit.Core.Utilities
}); });
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService); var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
var totpService = new TotpService(cryptoFunctionService); var totpService = new TotpService(cryptoFunctionService);
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService, var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
keyConnectorService, passwordGenerationService, policyService); keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService);
var exportService = new ExportService(folderService, cipherService, cryptoService); var exportService = new ExportService(folderService, cipherService, cryptoService);
var auditService = new AuditService(cryptoFunctionService, apiService); var auditService = new AuditService(cryptoFunctionService, apiService);
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner); var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
@@ -114,6 +115,7 @@ namespace Bit.Core.Utilities
Register<IUserVerificationService>("userVerificationService", userVerificationService); Register<IUserVerificationService>("userVerificationService", userVerificationService);
Register<IUsernameGenerationService>(usernameGenerationService); Register<IUsernameGenerationService>(usernameGenerationService);
Register<IConfigService>(configService); Register<IConfigService>(configService);
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
} }
public static void Register<T>(string serviceName, T obj) public static void Register<T>(string serviceName, T obj)

View File

@@ -498,7 +498,7 @@ namespace Bit.iOS.Autofill
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email)); vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
vm.LogInSuccessAction = () => DismissLockAndContinue(); vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -511,11 +511,11 @@ namespace Bit.iOS.Autofill
LogoutIfAuthed(); LogoutIfAuthed();
} }
private void LaunchLoginWithDevice(string email = null) private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
{ {
var appOptions = new AppOptions { IosExtension = true }; var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions); var app = new App.App(appOptions);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions); var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
ThemeManager.SetTheme(app.Resources); ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(loginWithDevicePage); ThemeManager.ApplyResourcesTo(loginWithDevicePage);
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
@@ -545,6 +545,7 @@ namespace Bit.iOS.Autofill
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -567,6 +568,7 @@ namespace Bit.iOS.Autofill
{ {
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
if (authingWithSso) if (authingWithSso)
{ {
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
@@ -621,6 +623,26 @@ namespace Bit.iOS.Autofill
PresentViewController(updateTempPasswordController, true, null); 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));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(loginApproveDevicePage);
var loginApproveDeviceController = navigationPage.CreateViewController();
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginApproveDeviceController, true, null);
}
public Task SetPreviousPageInfoAsync() => Task.CompletedTask; public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
public Task UpdateThemeAsync() => Task.CompletedTask; public Task UpdateThemeAsync() => Task.CompletedTask;

View File

@@ -8,11 +8,13 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views; using Bit.iOS.Core.Views;
using Foundation; using Foundation;
using UIKit; using UIKit;
using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
@@ -28,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IBiometricService _biometricService; private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService; private IKeyConnectorService _keyConnectorService;
private IAccountsManager _accountManager; private IAccountsManager _accountManager;
private bool _isPinProtected; private PinLockEnum _pinStatus;
private bool _isPinProtectedWithKey; private bool _pinEnabled;
private bool _pinLock; private bool _biometricEnabled;
private bool _biometricLock;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false; private bool _passwordReprompt = false;
private bool _usesKeyConnector; private bool _usesKeyConnector;
@@ -103,25 +104,28 @@ namespace Bit.iOS.Core.Controllers
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{ {
_passwordReprompt = true; _passwordReprompt = true;
_isPinProtected = false; _pinStatus = PinLockEnum.Disabled;
_isPinProtectedWithKey = false; _pinEnabled = false;
_pinLock = false; _biometricEnabled = false;
_biometricLock = false;
} }
else else
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && ?? await _stateService.GetPinProtectedKeyAsync();
await _cryptoService.HasKeyAsync(); _pinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _cryptoService.HasEncryptedUserKeyAsync();
_biometricIntegrityValid = _biometricIntegrityValid =
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
} }
if (_pinLock) if (_pinEnabled)
{ {
BaseNavItem.Title = AppResources.VerifyPIN; BaseNavItem.Title = AppResources.VerifyPIN;
} }
@@ -150,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
if (!_biometricUnlockOnly) if (!_biometricUnlockOnly)
{ {
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
@@ -158,7 +162,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().FireAndForget(); CheckPasswordAsync().FireAndForget();
return true; return true;
}; };
if (_pinLock) if (_pinEnabled)
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
@@ -177,7 +181,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad(); base.ViewDidLoad();
if (_biometricLock) if (_biometricEnabled)
{ {
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
{ {
@@ -198,13 +202,13 @@ namespace Bit.iOS.Core.Controllers
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector) if (_usesKeyConnector)
{ {
if (!(_pinLock || _biometricLock) || if (!(_pinEnabled || _biometricEnabled) ||
(_biometricLock && !_biometricIntegrityValid)) (_biometricEnabled && !_biometricIntegrityValid))
{ {
PromptSSO(); PromptSSO();
} }
} }
else if (!_biometricLock || !_biometricIntegrityValid) else if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
MasterPasswordCell.TextField.BecomeFirstResponder(); MasterPasswordCell.TextField.BecomeFirstResponder();
} }
@@ -224,7 +228,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword), _pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok); AppResources.Ok);
PresentViewController(alert, true, null); PresentViewController(alert, true, null);
return; return;
@@ -246,33 +250,53 @@ namespace Bit.iOS.Core.Controllers
return; return;
} }
if (_pinLock) if (_pinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKeyPin = await _stateService.GetUserKeyPinAsync();
kdfConfig, var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
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);
} }
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
inputtedValue,
email,
kdfConfig,
oldPinProtected
);
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
kdfConfig); inputtedValue,
failed = false; 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 AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -286,33 +310,27 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedPasswordHash = await _cryptoService.GetPasswordHashAsync();
if (storedKeyHash == null) if (storedPasswordHash == null)
{ {
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); 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.HashPasswordAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetPasswordHashAsync(localPasswordHash);
} }
} }
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(inputtedValue, masterKey);
if (passwordValid) 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 AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey, true);
} }
else else
{ {
@@ -339,12 +357,12 @@ namespace Bit.iOS.Core.Controllers
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{ {
if (!_biometricLock || !_biometricIntegrityValid) if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -371,12 +389,12 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(loginController, true, null); 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) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(userKey);
} }
DoContinue(masterPassword); DoContinue(masterPassword);
} }
@@ -396,7 +414,7 @@ namespace Bit.iOS.Core.Controllers
private async Task EnableBiometricsIfNeeded() private async Task EnableBiometricsIfNeeded()
{ {
// Re-enable biometrics if initial use // Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid) if (_biometricEnabled & !_biometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
} }
@@ -405,7 +423,7 @@ namespace Bit.iOS.Core.Controllers
private void InvalidValue() private void InvalidValue()
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, 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) => AppResources.Ok, (a) =>
{ {
@@ -490,7 +508,7 @@ namespace Bit.iOS.Core.Controllers
return 0; return 0;
} }
return (!controller._biometricUnlockOnly && controller._biometricLock) || return (!controller._biometricUnlockOnly && controller._biometricEnabled) ||
controller._passwordReprompt controller._passwordReprompt
? 2 ? 2
: 1; : 1;

View File

@@ -14,6 +14,7 @@ using Bit.Core.Enums;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Models; using Bit.App.Models;
using Xamarin.Forms; using Xamarin.Forms;
using Bit.Core.Services;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
{ {
@@ -29,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IPlatformUtilsService _platformUtilsService; private IPlatformUtilsService _platformUtilsService;
private IBiometricService _biometricService; private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService; private IKeyConnectorService _keyConnectorService;
private bool _isPinProtected; private PinLockEnum _pinStatus;
private bool _isPinProtectedWithKey; private bool _pinEnabled;
private bool _pinLock; private bool _biometricEnabled;
private bool _biometricLock;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false; private bool _passwordReprompt = false;
private bool _usesKeyConnector; private bool _usesKeyConnector;
@@ -96,25 +96,28 @@ namespace Bit.iOS.Core.Controllers
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{ {
_passwordReprompt = true; _passwordReprompt = true;
_isPinProtected = false; _pinStatus = PinLockEnum.Disabled;
_isPinProtectedWithKey = false; _pinEnabled = false;
_pinLock = false; _biometricEnabled = false;
_biometricLock = false;
} }
else else
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && ?? await _stateService.GetPinProtectedKeyAsync();
await _cryptoService.HasKeyAsync(); _pinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _cryptoService.HasEncryptedUserKeyAsync();
_biometricIntegrityValid = _biometricIntegrityValid =
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
} }
if (_pinLock) if (_pinEnabled)
{ {
BaseNavItem.Title = AppResources.VerifyPIN; BaseNavItem.Title = AppResources.VerifyPIN;
} }
@@ -143,7 +146,7 @@ namespace Bit.iOS.Core.Controllers
if (!_biometricUnlockOnly) if (!_biometricUnlockOnly)
{ {
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
@@ -151,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().GetAwaiter().GetResult(); CheckPasswordAsync().GetAwaiter().GetResult();
return true; return true;
}; };
if (_pinLock) if (_pinEnabled)
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
@@ -165,7 +168,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad(); base.ViewDidLoad();
if (_biometricLock) if (_biometricEnabled)
{ {
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
{ {
@@ -186,13 +189,13 @@ namespace Bit.iOS.Core.Controllers
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector) if (_usesKeyConnector)
{ {
if (!(_pinLock || _biometricLock) || if (!(_pinEnabled || _biometricEnabled) ||
(_biometricLock && !_biometricIntegrityValid)) (_biometricEnabled && !_biometricIntegrityValid))
{ {
PromptSSO(); PromptSSO();
} }
} }
else if (!_biometricLock || !_biometricIntegrityValid) else if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
MasterPasswordCell.TextField.BecomeFirstResponder(); MasterPasswordCell.TextField.BecomeFirstResponder();
} }
@@ -204,7 +207,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword), _pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok); AppResources.Ok);
PresentViewController(alert, true, null); PresentViewController(alert, true, null);
return; return;
@@ -214,33 +217,53 @@ namespace Bit.iOS.Core.Controllers
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var inputtedValue = MasterPasswordCell.TextField.Text; var inputtedValue = MasterPasswordCell.TextField.Text;
if (_pinLock) if (_pinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKeyPin = await _stateService.GetUserKeyPinAsync();
kdfConfig, var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
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);
} }
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
inputtedValue,
email,
kdfConfig,
oldPinProtected
);
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
kdfConfig); inputtedValue,
failed = false; 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 AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -260,33 +283,27 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedPasswordHash = await _cryptoService.GetPasswordHashAsync();
if (storedKeyHash == null) if (storedPasswordHash == null)
{ {
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); 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.HashPasswordAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetPasswordHashAsync(localPasswordHash);
} }
} }
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); var passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(inputtedValue, masterKey);
if (passwordValid) 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 AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey, true);
} }
else else
{ {
@@ -303,12 +320,12 @@ namespace Bit.iOS.Core.Controllers
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{ {
if (!_biometricLock || !_biometricIntegrityValid) if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -335,12 +352,12 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(loginController, true, null); 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) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(userKey);
} }
DoContinue(masterPassword); DoContinue(masterPassword);
} }
@@ -360,7 +377,7 @@ namespace Bit.iOS.Core.Controllers
private async Task EnableBiometricsIfNeeded() private async Task EnableBiometricsIfNeeded()
{ {
// Re-enable biometrics if initial use // Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid) if (_biometricEnabled & !_biometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
} }
@@ -369,7 +386,7 @@ namespace Bit.iOS.Core.Controllers
private void InvalidValue() private void InvalidValue()
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, 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) => AppResources.Ok, (a) =>
{ {
@@ -444,7 +461,7 @@ namespace Bit.iOS.Core.Controllers
public override nint NumberOfSections(UITableView tableView) public override nint NumberOfSections(UITableView tableView)
{ {
return (!_controller._biometricUnlockOnly && _controller._biometricLock) || return (!_controller._biometricUnlockOnly && _controller._biometricEnabled) ||
_controller._passwordReprompt _controller._passwordReprompt
? 2 ? 2
: 1; : 1;

View File

@@ -20,6 +20,7 @@ using Bit.App.Pages;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.iOS.Core.Views; using Bit.iOS.Core.Views;
using Bit.Core.Enums;
namespace Bit.iOS.Extension namespace Bit.iOS.Extension
{ {
@@ -519,7 +520,7 @@ namespace Bit.iOS.Extension
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email)); vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
vm.LogInSuccessAction = () => DismissLockAndContinue(); vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -532,11 +533,11 @@ namespace Bit.iOS.Extension
LogoutIfAuthed(); LogoutIfAuthed();
} }
private void LaunchLoginWithDevice(string email = null) private void LaunchLoginWithDevice(AuthRequestType authRequestType,string email = null)
{ {
var appOptions = new AppOptions { IosExtension = true }; var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions); var app = new App.App(appOptions);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions); var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
ThemeManager.SetTheme(app.Resources); ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(loginWithDevicePage); ThemeManager.ApplyResourcesTo(loginWithDevicePage);
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
@@ -566,6 +567,7 @@ namespace Bit.iOS.Extension
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -588,6 +590,7 @@ namespace Bit.iOS.Extension
{ {
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
if (authingWithSso) if (authingWithSso)
{ {
vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.CloseAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
@@ -641,5 +644,25 @@ namespace Bit.iOS.Extension
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(updateTempPasswordController, true, null); 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));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(loginApproveDevicePage);
var loginApproveDeviceController = navigationPage.CreateViewController();
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginApproveDeviceController, true, null);
}
} }
} }

View File

@@ -339,7 +339,7 @@ namespace Bit.iOS.ShareExtension
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false)); vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow()); vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(email)); vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
vm.LogInSuccessAction = () => { DismissLockAndContinue(); }; vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage()); vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
} }
@@ -348,9 +348,9 @@ namespace Bit.iOS.ShareExtension
LogoutIfAuthed(); LogoutIfAuthed();
} }
private void LaunchLoginWithDevice(string email = null) private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
{ {
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, _appOptions.Value); var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value);
SetupAppAndApplyResources(loginWithDevicePage); SetupAppAndApplyResources(loginWithDevicePage);
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
{ {
@@ -373,6 +373,7 @@ namespace Bit.iOS.ShareExtension
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true)); vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage()); vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
} }
@@ -389,6 +390,7 @@ namespace Bit.iOS.ShareExtension
{ {
vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue(); vm.TwoFactorAuthSuccessAction = () => DismissLockAndContinue();
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
if (authingWithSso) if (authingWithSso)
{ {
vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow()); vm.CloseAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
@@ -427,6 +429,26 @@ namespace Bit.iOS.ShareExtension
NavigateToPage(updateTempPasswordPage); 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));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
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) public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
{ {
if (ExtNavigationController?.ViewControllers?.Any() ?? false) if (ExtNavigationController?.ViewControllers?.Any() ?? false)

View File

@@ -29,7 +29,7 @@ namespace Bit.Core.Test.Services
.Returns(encFileName); .Returns(encFileName);
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>()) sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
.Returns(data); .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>()) sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
.Returns(uploadDataResponse); .Returns(uploadDataResponse);
@@ -50,7 +50,7 @@ namespace Bit.Core.Test.Services
.Returns(new EncString(fileName)); .Returns(new EncString(fileName));
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>()) sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
.Returns(data); .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>()) sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
.Throws(new ApiException(new ErrorResponse { StatusCode = statusCode })); .Throws(new ApiException(new ErrorResponse { StatusCode = statusCode }));
sutProvider.GetDependency<IApiService>().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>()) sutProvider.GetDependency<IApiService>().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>())
@@ -70,7 +70,7 @@ namespace Bit.Core.Test.Services
.Returns(new EncString(fileName)); .Returns(new EncString(fileName));
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>()) sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
.Returns(data); .Returns(data);
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()) sutProvider.GetDependency<ICryptoService>().MakeDataEncKeyAsync(Arg.Any<UserKey>())
.Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey)); .Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
var expectedException = new ApiException(new ErrorResponse { StatusCode = HttpStatusCode.BadRequest }); var expectedException = new ApiException(new ErrorResponse { StatusCode = HttpStatusCode.BadRequest });
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>()) sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())

View File

@@ -145,7 +145,7 @@ namespace Bit.Core.Test.Services
return; return;
var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d); 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>()); ServiceContainer.Register("cryptoService", sutProvider.GetDependency<ICryptoService>());
sutProvider.GetDependency<II18nService>().StringComparer.Returns(StringComparer.CurrentCulture); sutProvider.GetDependency<II18nService>().StringComparer.Returns(StringComparer.CurrentCulture);
sutProvider.GetDependency<IStateService>().GetActiveUserIdAsync().Returns(userId); sutProvider.GetDependency<IStateService>().GetActiveUserIdAsync().Returns(userId);