1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 07:43:37 +00:00

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
This commit is contained in:
André Bispo
2023-07-10 12:32:27 +01:00
13 changed files with 161 additions and 30 deletions

View File

@@ -9,6 +9,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -19,6 +20,7 @@ namespace Bit.App.Pages
private bool _requestAdminApprovalEnabled; private bool _requestAdminApprovalEnabled;
private bool _approveWithMasterPasswordEnabled; private bool _approveWithMasterPasswordEnabled;
private bool _continueEnabled; private bool _continueEnabled;
private string _email;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IApiService _apiService; private readonly IApiService _apiService;
@@ -52,6 +54,8 @@ namespace Bit.App.Pages
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
} }
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
public bool RememberThisDevice public bool RememberThisDevice
{ {
get => _rememberThisDevice; get => _rememberThisDevice;
@@ -82,6 +86,15 @@ namespace Bit.App.Pages
set => SetProperty(ref _continueEnabled, value); set => SetProperty(ref _continueEnabled, value);
} }
public string Email
{
get => _email;
set => SetProperty(ref _email, value, additionalPropertyNames:
new string[] {
nameof(LoggingInAsText)
});
}
public async Task InitAsync() public async Task InitAsync()
{ {
// Appears if the browser is trusted and shared the key with the app // Appears if the browser is trusted and shared the key with the app

View File

@@ -3631,6 +3631,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>

View File

@@ -2661,4 +2661,7 @@ 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="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
</root> </root>

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;
@@ -92,9 +93,10 @@ namespace Bit.Core.Abstractions
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);
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<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
Task<DeviceResponse> PutUpdateTrustedDeviceKeys(UpdateTrustedDeviceKeysRequest request);
Task<ConfigResponse> GetConfigsAsync(); Task<ConfigResponse> GetConfigsAsync();
Task<string> GetFastmailAccountIdAsync(string apiKey); Task<string> GetFastmailAccountIdAsync(string apiKey);
} }

View File

@@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
namespace Bit.Core.Abstractions
{
public interface IDeviceTrustCryptoService
{
Task<SymmetricCryptoKey> GetDeviceKeyAsync();
Task<DeviceResponse> TrustDeviceAsync();
}
}

View File

@@ -56,6 +56,8 @@ namespace Bit.Core.Abstractions
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();

View File

@@ -91,6 +91,7 @@
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 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 PinProtectedKey(string userId) => $"pinProtectedKey_{userId}";
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}"; public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";

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

@@ -1,12 +0,0 @@
using System;
namespace Bit.Core.Models.Request
{
public class UpdateTrustedDeviceKeysRequest
{
public string DeviceIdentifier { get; set; }
public string DevicePublicKeyEncryptedUserKey { get; set; }
public string UserKeyEncryptedDevicePublicKey { get; set; }
public string DeviceKeyEncryptedDevicePrivateKey { get; set; }
}
}

View File

@@ -1,17 +1,13 @@
using System; using Bit.Core.Enums;
using Bit.Core.Enums;
namespace Bit.Core.Models.Response public class DeviceResponse
{ {
public class DeviceResponse
{
public string Id { get; set; } public string Id { get; set; }
public string UserId { get; set; } public int Name { get; set; }
public string Name { get; set; }
public string Identifier { get; set; } public string Identifier { get; set; }
public DeviceType Type { get; set; } public DeviceType Type { get; set; }
public string CreationDate { get; set; } public string CreationDate { get; set; }
public string RevisionDate { get; set; } public string EncryptedUserKey { get; set; }
} public string EncryptedPublicKey { get; set; }
public string EncryptedPrivateKey { get; set; }
} }

View File

@@ -419,10 +419,14 @@ namespace Bit.Core.Services
HttpMethod.Post, "/devices/exist-by-types", deviceTypes, true, true); HttpMethod.Post, "/devices/exist-by-types", deviceTypes, true, true);
} }
public Task<DeviceResponse> PutUpdateTrustedDeviceKeys(UpdateTrustedDeviceKeysRequest request) public Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier)
{ {
return SendAsync<UpdateTrustedDeviceKeysRequest, DeviceResponse>( return SendAsync<object, DeviceResponse>(HttpMethod.Get, $"/devices/identifier/{deviceIdentifier}", null, true, true);
HttpMethod.Put, $"/devices/${request.DeviceIdentifier}/keys", request, true, true); }
public Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest trustedDeviceKeysRequest)
{
return SendAsync<TrustedDeviceKeysRequest, DeviceResponse>(HttpMethod.Put, $"/devices/{deviceIdentifier}/keys", trustedDeviceKeysRequest, true, true);
} }
#endregion #endregion

View File

@@ -0,0 +1,81 @@

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.GetEncKeyAsync();
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.EncKey, 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);
}
}
}

View File

@@ -482,6 +482,17 @@ 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 deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(userId), true);
return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64));
}
public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null)
{
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(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 },