bitwarden-android/src/Core/Services/AuthService.cs

386 lines
16 KiB
C#
Raw Normal View History

2019-04-18 16:45:31 +03:00
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Bit.Core.Services
{
public class AuthService : IAuthService
{
private readonly ICryptoService _cryptoService;
private readonly IApiService _apiService;
private readonly IUserService _userService;
private readonly ITokenService _tokenService;
private readonly IAppIdService _appIdService;
private readonly II18nService _i18nService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
[Auto Logout] Final review of feature (#932) * Initial commit of LockService name refactor (#831) * [Auto-Logout] Update Service layer logic (#835) * Initial commit of service logic update * Added default value for action * Updated ToggleTokensAsync conditional * Removed unused variables, updated action conditional * Initial commit: lockOption/lock refactor app layer (#840) * [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844) * Initial commit of app layer part 2 * Updated biometrics position * Reverted resource name refactor * LockOptions refactor revert * Updated method casing :: Removed VaultTimeout prefix for timeouts * Fixed dupe string resource (#854) * Updated dependency to use VaultTimeoutService (#896) * [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902) * fix typo in PINRequireMasterPasswordRestart (#900) * initial commit for xf usage in autofill * Fixed databinding for hint button * Updated Two Factor page launch - removed unused imports * First pass at broadcast/messenger implentation for autofill * setting theme in extension using theme manager * extension app resources * App resources from main app * fix ref to twoFactorPage * apply resources to page * load empty app for sytling in extension * move ios renderers to ios core * static ref to resources and GetResourceColor helper * fix method ref * move application.current.resources refs to helper * switch login page alerts to device action dialogs * run on main thread * showDialog with device action service * abstract action sheet to device action service * add support for yubikey * add yubikey iimages to extension * support close button action * add support to action extension * remove empty lines Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> * [Auto Logout] Update lock option to be default value (#929) * Initial commit - make lock action default * Removed extra whitespace Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2020-05-29 19:26:36 +03:00
private readonly IVaultTimeoutService _vaultTimeoutService;
2019-04-18 16:45:31 +03:00
private readonly bool _setCryptoKeys;
private SymmetricCryptoKey _key;
public AuthService(
ICryptoService cryptoService,
IApiService apiService,
IUserService userService,
ITokenService tokenService,
IAppIdService appIdService,
II18nService i18nService,
IPlatformUtilsService platformUtilsService,
IMessagingService messagingService,
[Auto Logout] Final review of feature (#932) * Initial commit of LockService name refactor (#831) * [Auto-Logout] Update Service layer logic (#835) * Initial commit of service logic update * Added default value for action * Updated ToggleTokensAsync conditional * Removed unused variables, updated action conditional * Initial commit: lockOption/lock refactor app layer (#840) * [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844) * Initial commit of app layer part 2 * Updated biometrics position * Reverted resource name refactor * LockOptions refactor revert * Updated method casing :: Removed VaultTimeout prefix for timeouts * Fixed dupe string resource (#854) * Updated dependency to use VaultTimeoutService (#896) * [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902) * fix typo in PINRequireMasterPasswordRestart (#900) * initial commit for xf usage in autofill * Fixed databinding for hint button * Updated Two Factor page launch - removed unused imports * First pass at broadcast/messenger implentation for autofill * setting theme in extension using theme manager * extension app resources * App resources from main app * fix ref to twoFactorPage * apply resources to page * load empty app for sytling in extension * move ios renderers to ios core * static ref to resources and GetResourceColor helper * fix method ref * move application.current.resources refs to helper * switch login page alerts to device action dialogs * run on main thread * showDialog with device action service * abstract action sheet to device action service * add support for yubikey * add yubikey iimages to extension * support close button action * add support to action extension * remove empty lines Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> * [Auto Logout] Update lock option to be default value (#929) * Initial commit - make lock action default * Removed extra whitespace Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2020-05-29 19:26:36 +03:00
IVaultTimeoutService vaultTimeoutService,
2019-04-18 16:45:31 +03:00
bool setCryptoKeys = true)
{
_cryptoService = cryptoService;
_apiService = apiService;
_userService = userService;
_tokenService = tokenService;
_appIdService = appIdService;
_i18nService = i18nService;
_platformUtilsService = platformUtilsService;
_messagingService = messagingService;
[Auto Logout] Final review of feature (#932) * Initial commit of LockService name refactor (#831) * [Auto-Logout] Update Service layer logic (#835) * Initial commit of service logic update * Added default value for action * Updated ToggleTokensAsync conditional * Removed unused variables, updated action conditional * Initial commit: lockOption/lock refactor app layer (#840) * [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844) * Initial commit of app layer part 2 * Updated biometrics position * Reverted resource name refactor * LockOptions refactor revert * Updated method casing :: Removed VaultTimeout prefix for timeouts * Fixed dupe string resource (#854) * Updated dependency to use VaultTimeoutService (#896) * [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902) * fix typo in PINRequireMasterPasswordRestart (#900) * initial commit for xf usage in autofill * Fixed databinding for hint button * Updated Two Factor page launch - removed unused imports * First pass at broadcast/messenger implentation for autofill * setting theme in extension using theme manager * extension app resources * App resources from main app * fix ref to twoFactorPage * apply resources to page * load empty app for sytling in extension * move ios renderers to ios core * static ref to resources and GetResourceColor helper * fix method ref * move application.current.resources refs to helper * switch login page alerts to device action dialogs * run on main thread * showDialog with device action service * abstract action sheet to device action service * add support for yubikey * add yubikey iimages to extension * support close button action * add support to action extension * remove empty lines Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> * [Auto Logout] Update lock option to be default value (#929) * Initial commit - make lock action default * Removed extra whitespace Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2020-05-29 19:26:36 +03:00
_vaultTimeoutService = vaultTimeoutService;
2019-04-18 16:45:31 +03:00
_setCryptoKeys = setCryptoKeys;
2019-05-27 17:28:38 +03:00
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
TwoFactorProviders.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider
2019-04-18 16:45:31 +03:00
{
Type = TwoFactorProviderType.Authenticator,
Priority = 1,
Sort = 1
});
2019-05-27 17:28:38 +03:00
TwoFactorProviders.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider
2019-04-18 16:45:31 +03:00
{
Type = TwoFactorProviderType.YubiKey,
Priority = 3,
Sort = 2,
Premium = true
});
2019-05-27 17:28:38 +03:00
TwoFactorProviders.Add(TwoFactorProviderType.Duo, new TwoFactorProvider
2019-04-18 16:45:31 +03:00
{
Type = TwoFactorProviderType.Duo,
Name = "Duo",
Priority = 2,
Sort = 3,
Premium = true
});
2019-05-27 17:28:38 +03:00
TwoFactorProviders.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider
2019-04-18 16:45:31 +03:00
{
Type = TwoFactorProviderType.OrganizationDuo,
Name = "Duo (Organization)",
Priority = 10,
Sort = 4
});
2019-05-27 17:28:38 +03:00
TwoFactorProviders.Add(TwoFactorProviderType.U2f, new TwoFactorProvider
2019-04-18 16:45:31 +03:00
{
Type = TwoFactorProviderType.U2f,
Priority = 4,
Sort = 5,
Premium = true
});
2019-05-27 17:28:38 +03:00
TwoFactorProviders.Add(TwoFactorProviderType.Email, new TwoFactorProvider
2019-04-18 16:45:31 +03:00
{
Type = TwoFactorProviderType.Email,
Priority = 0,
Sort = 6,
});
}
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
public string LocalMasterPasswordHash { get; set; }
public string Code { get; set; }
public string CodeVerifier { get; set; }
public string SsoRedirectUrl { get; set; }
2019-05-27 17:28:38 +03:00
public Dictionary<TwoFactorProviderType, TwoFactorProvider> TwoFactorProviders { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProvidersData { get; set; }
2019-04-18 16:45:31 +03:00
public TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
public void Init()
{
2019-05-28 17:12:51 +03:00
TwoFactorProviders[TwoFactorProviderType.Email].Name = _i18nService.T("Email");
2019-05-27 17:28:38 +03:00
TwoFactorProviders[TwoFactorProviderType.Email].Description = _i18nService.T("EmailDesc");
TwoFactorProviders[TwoFactorProviderType.Authenticator].Name = _i18nService.T("AuthenticatorAppTitle");
TwoFactorProviders[TwoFactorProviderType.Authenticator].Description =
2019-04-18 16:45:31 +03:00
_i18nService.T("AuthenticatorAppDesc");
2019-05-27 17:28:38 +03:00
TwoFactorProviders[TwoFactorProviderType.Duo].Description = _i18nService.T("DuoDesc");
TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].Name =
2019-04-18 16:45:31 +03:00
string.Format("Duo ({0})", _i18nService.T("Organization"));
2019-05-27 17:28:38 +03:00
TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].Description =
2019-04-18 16:45:31 +03:00
_i18nService.T("DuoOrganizationDesc");
2019-05-27 17:28:38 +03:00
TwoFactorProviders[TwoFactorProviderType.U2f].Name = _i18nService.T("U2fTitle");
TwoFactorProviders[TwoFactorProviderType.U2f].Description = _i18nService.T("U2fDesc");
TwoFactorProviders[TwoFactorProviderType.YubiKey].Name = _i18nService.T("YubiKeyTitle");
TwoFactorProviders[TwoFactorProviderType.YubiKey].Description = _i18nService.T("YubiKeyDesc");
2019-04-18 16:45:31 +03:00
}
public async Task<AuthResult> LogInAsync(string email, string masterPassword)
{
SelectedTwoFactorProviderType = null;
var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null);
}
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl)
{
SelectedTwoFactorProviderType = null;
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, null, null, null);
2019-04-18 16:45:31 +03:00
}
public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
bool? remember = null)
{
return LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
twoFactorProvider, twoFactorToken, remember);
2019-04-18 16:45:31 +03:00
}
public async Task<AuthResult> LogInCompleteAsync(string email, string masterPassword,
TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null)
{
SelectedTwoFactorProviderType = null;
var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, twoFactorProvider,
twoFactorToken, remember);
}
public async Task<AuthResult> LogInSsoCompleteAsync(string code, string codeVerifier, string redirectUrl,
TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null)
{
SelectedTwoFactorProviderType = null;
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, twoFactorProvider,
twoFactorToken, remember);
2019-04-18 16:45:31 +03:00
}
public void LogOut(Action callback)
{
callback.Invoke();
2019-04-19 19:29:37 +03:00
_messagingService.Send("loggedOut");
2019-04-18 16:45:31 +03:00
}
public List<TwoFactorProvider> GetSupportedTwoFactorProviders()
{
var providers = new List<TwoFactorProvider>();
if (TwoFactorProvidersData == null)
2019-04-18 16:45:31 +03:00
{
return providers;
}
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.OrganizationDuo) &&
2019-04-18 16:45:31 +03:00
_platformUtilsService.SupportsDuo())
{
2019-05-27 17:28:38 +03:00
providers.Add(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]);
2019-04-18 16:45:31 +03:00
}
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Authenticator))
2019-04-18 16:45:31 +03:00
{
2019-05-27 17:28:38 +03:00
providers.Add(TwoFactorProviders[TwoFactorProviderType.Authenticator]);
2019-04-18 16:45:31 +03:00
}
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.YubiKey))
2019-04-18 16:45:31 +03:00
{
2019-05-27 17:28:38 +03:00
providers.Add(TwoFactorProviders[TwoFactorProviderType.YubiKey]);
2019-04-18 16:45:31 +03:00
}
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Duo) && _platformUtilsService.SupportsDuo())
2019-04-18 16:45:31 +03:00
{
2019-05-27 17:28:38 +03:00
providers.Add(TwoFactorProviders[TwoFactorProviderType.Duo]);
2019-04-18 16:45:31 +03:00
}
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.U2f) && _platformUtilsService.SupportsU2f())
2019-04-18 16:45:31 +03:00
{
2019-05-27 17:28:38 +03:00
providers.Add(TwoFactorProviders[TwoFactorProviderType.U2f]);
2019-04-18 16:45:31 +03:00
}
if (TwoFactorProvidersData.ContainsKey(TwoFactorProviderType.Email))
2019-04-18 16:45:31 +03:00
{
2019-05-27 17:28:38 +03:00
providers.Add(TwoFactorProviders[TwoFactorProviderType.Email]);
2019-04-18 16:45:31 +03:00
}
return providers;
}
public TwoFactorProviderType? GetDefaultTwoFactorProvider(bool u2fSupported)
{
if (TwoFactorProvidersData == null)
2019-04-18 16:45:31 +03:00
{
return null;
}
if (SelectedTwoFactorProviderType != null &&
2019-05-27 17:28:38 +03:00
TwoFactorProvidersData.ContainsKey(SelectedTwoFactorProviderType.Value))
2019-04-18 16:45:31 +03:00
{
return SelectedTwoFactorProviderType.Value;
}
TwoFactorProviderType? providerType = null;
var providerPriority = -1;
foreach (var providerKvp in TwoFactorProvidersData)
2019-04-18 16:45:31 +03:00
{
if (TwoFactorProviders.ContainsKey(providerKvp.Key))
2019-04-18 16:45:31 +03:00
{
2019-05-27 17:28:38 +03:00
var provider = TwoFactorProviders[providerKvp.Key];
if (provider.Priority > providerPriority)
2019-04-18 16:45:31 +03:00
{
if (providerKvp.Key == TwoFactorProviderType.U2f && !u2fSupported)
2019-04-18 16:45:31 +03:00
{
continue;
}
providerType = providerKvp.Key;
providerPriority = provider.Priority;
}
}
}
return providerType;
}
public bool AuthingWithSso()
{
return Code != null && CodeVerifier != null && SsoRedirectUrl != null;
}
public bool AuthingWithPassword()
{
return Email != null && MasterPasswordHash != null;
}
2019-04-18 16:45:31 +03:00
// Helpers
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
{
email = email.Trim().ToLower();
KdfType? kdf = null;
int? kdfIterations = null;
2019-04-18 16:45:31 +03:00
try
{
var preloginResponse = await _apiService.PostPreloginAsync(new PreloginRequest { Email = email });
if (preloginResponse != null)
2019-04-18 16:45:31 +03:00
{
kdf = preloginResponse.Kdf;
kdfIterations = preloginResponse.KdfIterations;
2019-04-18 16:45:31 +03:00
}
}
catch (ApiException e)
2019-04-18 16:45:31 +03:00
{
if (e.Error == null || e.Error.StatusCode != System.Net.HttpStatusCode.NotFound)
2019-04-18 16:45:31 +03:00
{
throw e;
}
}
return await _cryptoService.MakeKeyAsync(masterPassword, email, kdf, kdfIterations);
2019-04-18 16:45:31 +03:00
}
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
2019-04-18 16:45:31 +03:00
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null)
{
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
var appId = await _appIdService.GetAppIdAsync();
var deviceRequest = new DeviceRequest(appId, _platformUtilsService);
string[] emailPassword;
string[] codeCodeVerifier;
if (email != null && hashedPassword != null)
{
emailPassword = new[] { email, hashedPassword };
}
else
2019-04-18 16:45:31 +03:00
{
emailPassword = null;
}
if (code != null && codeVerifier != null && redirectUrl != null)
{
codeCodeVerifier = new[] { code, codeVerifier, redirectUrl };
}
else
{
codeCodeVerifier = null;
}
TokenRequest request;
if (twoFactorToken != null && twoFactorProvider != null)
2019-04-18 16:45:31 +03:00
{
request = new TokenRequest(emailPassword, codeCodeVerifier, twoFactorProvider, twoFactorToken, remember,
deviceRequest);
2019-04-18 16:45:31 +03:00
}
else if (storedTwoFactorToken != null)
2019-04-18 16:45:31 +03:00
{
request = new TokenRequest(emailPassword, codeCodeVerifier, TwoFactorProviderType.Remember,
storedTwoFactorToken, false, deviceRequest);
}
else
{
request = new TokenRequest(emailPassword, codeCodeVerifier, null, null, false, deviceRequest);
2019-04-18 16:45:31 +03:00
}
var response = await _apiService.PostIdentityTokenAsync(request);
ClearState();
var result = new AuthResult { TwoFactor = response.Item2 != null };
if (result.TwoFactor)
2019-04-18 16:45:31 +03:00
{
// Two factor required.
var twoFactorResponse = response.Item2;
Email = email;
MasterPasswordHash = hashedPassword;
LocalMasterPasswordHash = localHashedPassword;
Code = code;
CodeVerifier = codeVerifier;
SsoRedirectUrl = redirectUrl;
2019-04-18 16:45:31 +03:00
_key = _setCryptoKeys ? key : null;
2019-05-27 17:28:38 +03:00
TwoFactorProvidersData = twoFactorResponse.TwoFactorProviders2;
2019-04-18 16:45:31 +03:00
result.TwoFactorProviders = twoFactorResponse.TwoFactorProviders2;
return result;
}
var tokenResponse = response.Item1;
result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
if (tokenResponse.TwoFactorToken != null)
2019-04-18 16:45:31 +03:00
{
await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email);
}
await _tokenService.SetTokensAsync(tokenResponse.AccessToken, tokenResponse.RefreshToken);
await _userService.SetInformationAsync(_tokenService.GetUserId(), _tokenService.GetEmail(),
tokenResponse.Kdf, tokenResponse.KdfIterations);
if (_setCryptoKeys)
2019-04-18 16:45:31 +03:00
{
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(localHashedPassword);
2019-04-18 16:45:31 +03:00
await _cryptoService.SetEncKeyAsync(tokenResponse.Key);
// User doesn't have a key pair yet (old account), let's generate one for them.
if (tokenResponse.PrivateKey == null)
2019-04-18 16:45:31 +03:00
{
try
{
var keyPair = await _cryptoService.MakeKeyPairAsync();
await _apiService.PostAccountKeysAsync(new KeysRequest
{
PublicKey = keyPair.Item1,
EncryptedPrivateKey = keyPair.Item2.EncryptedString
});
tokenResponse.PrivateKey = keyPair.Item2.EncryptedString;
}
catch { }
}
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
}
2020-06-08 15:25:13 +03:00
_vaultTimeoutService.BiometricLocked = false;
2019-04-19 19:29:37 +03:00
_messagingService.Send("loggedIn");
2019-04-18 16:45:31 +03:00
return result;
}
private void ClearState()
{
_key = null;
2019-04-18 16:45:31 +03:00
Email = null;
MasterPasswordHash = null;
Code = null;
CodeVerifier = null;
SsoRedirectUrl = null;
2019-05-27 17:28:38 +03:00
TwoFactorProvidersData = null;
2019-04-18 16:45:31 +03:00
SelectedTwoFactorProviderType = null;
}
}
}