1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-18 08:23:15 +00:00

[PM-2289] [PM-2293] TDE Login with device Admin Request (#2642)

This commit is contained in:
André Bispo
2023-07-27 21:18:36 +01:00
committed by GitHub
parent dfc7c55b77
commit c25906206e
15 changed files with 193 additions and 27 deletions

View File

@@ -91,7 +91,7 @@ namespace Bit.Core.Abstractions
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType);
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier);
Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest);

View File

@@ -32,7 +32,10 @@ namespace Bit.Core.Abstractions
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
/// <summary>
/// Gets a passwordless login request by <paramref name="id"/> and <paramref name="accessCode"/>. No authentication required.
/// </summary>
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);

View File

@@ -183,6 +183,8 @@ namespace Bit.Core.Abstractions
Task<string> GetPreLoginEmailAsync();
Task SetPreLoginEmailAsync(string value);
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null);
Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null);
string GetLocale();
void SetLocale(string locale);
ConfigResponse GetConfigs();

View File

@@ -124,6 +124,7 @@ namespace Bit.Core
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}";
[Obsolete]
public static string KeyKey(string userId) => $"key_{userId}";
[Obsolete]

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

@@ -591,9 +591,9 @@ namespace Bit.Core.Services
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true);
}
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest)
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType)
{
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, $"/auth-requests", passwordlessCreateLoginRequest, false, true);
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, authRequestType == AuthRequestType.AdminApproval ? "/auth-requests/admin-request" : "/auth-requests", passwordlessCreateLoginRequest, authRequestType == AuthRequestType.AdminApproval, true);
}
public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved)

View File

@@ -198,11 +198,34 @@ namespace Bit.Core.Services
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
}
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered)
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
{
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey);
var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey);
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new MasterKey(decKey), null, null,
var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, decryptionKey);
// 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);
}
@@ -474,10 +497,12 @@ namespace Bit.Core.Services
_messagingService.Send("accountAdded");
if (_setCryptoKeys)
{
if (localHashedPassword != null)
{
await _cryptoService.SetPasswordHashAsync(localHashedPassword);
await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
}
if (code == null || tokenResponse.Key != null)
@@ -505,6 +530,14 @@ namespace Bit.Core.Services
}
}
// 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.
if (tokenResponse.PrivateKey == null)
{
@@ -557,7 +590,6 @@ namespace Bit.Core.Services
);
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
}
}
_authedUserId = _tokenService.GetUserId();
@@ -594,14 +626,14 @@ namespace Bit.Core.Services
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
return await PopulateFingerprintPhrasesAsync(activeRequests);
}
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
{
var response = await _apiService.GetAuthRequestAsync(id);
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
}
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);
}
@@ -631,7 +663,7 @@ namespace Bit.Core.Services
var publicB64 = Convert.ToBase64String(keyPair.Item1);
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
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)
{

View File

@@ -336,12 +336,16 @@ namespace Bit.Core.Services
public async Task<string> GetUserKeyMasterKeyAsync(string userId = null)
{
return await _storageMediatorService.GetAsync<string>(Constants.UserKeyKey(userId), false);
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)
{
await _storageMediatorService.SaveAsync(Constants.UserKeyKey(userId), value, false);
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)
@@ -1347,6 +1351,20 @@ namespace Bit.Core.Services
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()
{
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);