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;
|
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;
|
|
|
|
|
private KdfType? _kdf;
|
|
|
|
|
private int? _kdfIterations;
|
|
|
|
|
|
|
|
|
|
public AuthService(
|
|
|
|
|
ICryptoService cryptoService,
|
|
|
|
|
IApiService apiService,
|
|
|
|
|
IUserService userService,
|
|
|
|
|
ITokenService tokenService,
|
|
|
|
|
IAppIdService appIdService,
|
|
|
|
|
II18nService i18nService,
|
|
|
|
|
IPlatformUtilsService platformUtilsService,
|
|
|
|
|
IMessagingService messagingService,
|
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;
|
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; }
|
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);
|
|
|
|
|
return await LogInHelperAsync(email, hashedPassword, key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
|
|
|
|
|
bool? remember = null)
|
|
|
|
|
{
|
|
|
|
|
return LogInHelperAsync(Email, MasterPasswordHash, _key, twoFactorProvider, twoFactorToken, remember);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
return await LogInHelperAsync(email, hashedPassword, key, twoFactorProvider, twoFactorToken, remember);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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>();
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (TwoFactorProvidersData == null)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
|
|
|
|
return providers;
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
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
|
|
|
|
}
|
2020-03-28 16:16:28 +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
|
|
|
|
}
|
2020-03-28 16:16:28 +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
|
|
|
|
}
|
2020-03-28 16:16:28 +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
|
|
|
|
}
|
2020-03-28 16:16:28 +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
|
|
|
|
}
|
2020-03-28 16:16:28 +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)
|
|
|
|
|
{
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (TwoFactorProvidersData == null)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
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;
|
2020-03-28 16:16:28 +03:00
|
|
|
|
foreach (var providerKvp in TwoFactorProvidersData)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
2020-03-28 16:16:28 +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];
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (provider.Priority > providerPriority)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
2020-03-28 16:16:28 +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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helpers
|
|
|
|
|
|
|
|
|
|
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
|
|
|
|
|
{
|
|
|
|
|
email = email.Trim().ToLower();
|
|
|
|
|
_kdf = null;
|
|
|
|
|
_kdfIterations = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var preloginResponse = await _apiService.PostPreloginAsync(new PreloginRequest { Email = email });
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (preloginResponse != null)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
|
|
|
|
_kdf = preloginResponse.Kdf;
|
|
|
|
|
_kdfIterations = preloginResponse.KdfIterations;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
catch (ApiException e)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
2020-03-28 16:16:28 +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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, SymmetricCryptoKey key,
|
|
|
|
|
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);
|
|
|
|
|
var request = new TokenRequest
|
|
|
|
|
{
|
|
|
|
|
Email = email,
|
|
|
|
|
MasterPasswordHash = hashedPassword,
|
|
|
|
|
Device = deviceRequest,
|
|
|
|
|
Remember = false
|
|
|
|
|
};
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (twoFactorToken != null && twoFactorProvider != null)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
|
|
|
|
request.Provider = twoFactorProvider;
|
|
|
|
|
request.Token = twoFactorToken;
|
|
|
|
|
request.Remember = remember.GetValueOrDefault();
|
|
|
|
|
}
|
2020-03-28 16:16:28 +03:00
|
|
|
|
else if (storedTwoFactorToken != null)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
|
|
|
|
request.Provider = TwoFactorProviderType.Remember;
|
|
|
|
|
request.Token = storedTwoFactorToken;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var response = await _apiService.PostIdentityTokenAsync(request);
|
|
|
|
|
ClearState();
|
|
|
|
|
var result = new AuthResult
|
|
|
|
|
{
|
|
|
|
|
TwoFactor = response.Item2 != null
|
|
|
|
|
};
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (result.TwoFactor)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
|
|
|
|
// Two factor required.
|
|
|
|
|
var twoFactorResponse = response.Item2;
|
|
|
|
|
Email = email;
|
|
|
|
|
MasterPasswordHash = hashedPassword;
|
|
|
|
|
_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;
|
2020-03-28 16:16:28 +03:00
|
|
|
|
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(),
|
|
|
|
|
_kdf.Value, _kdfIterations.Value);
|
2020-03-28 16:16:28 +03:00
|
|
|
|
if (_setCryptoKeys)
|
2019-04-18 16:45:31 +03:00
|
|
|
|
{
|
|
|
|
|
await _cryptoService.SetKeyAsync(key);
|
|
|
|
|
await _cryptoService.SetKeyHashAsync(hashedPassword);
|
|
|
|
|
await _cryptoService.SetEncKeyAsync(tokenResponse.Key);
|
|
|
|
|
|
|
|
|
|
// User doesn't have a key pair yet (old account), let's generate one for them.
|
2020-03-28 16:16:28 +03:00
|
|
|
|
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()
|
|
|
|
|
{
|
|
|
|
|
Email = null;
|
|
|
|
|
MasterPasswordHash = null;
|
2019-05-27 17:28:38 +03:00
|
|
|
|
TwoFactorProvidersData = null;
|
2019-04-18 16:45:31 +03:00
|
|
|
|
SelectedTwoFactorProviderType = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|