From 3053eaa0367a905411a82396eff831bca1dcd8f0 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Wed, 5 Jul 2023 16:13:20 -0400 Subject: [PATCH] [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 --- src/Core/Abstractions/IApiService.cs | 2 + .../Abstractions/IDeviceTrustCryptoService.cs | 11 +++ src/Core/Abstractions/IStateService.cs | 2 + src/Core/Constants.cs | 1 + .../Request/TrustedDeviceKeysRequest.cs | 10 +++ src/Core/Models/Response/DeviceResponse.cs | 13 +++ src/Core/Services/ApiService.cs | 10 +++ src/Core/Services/DeviceTrustCryptoService.cs | 81 +++++++++++++++++++ src/Core/Services/StateService.cs | 11 +++ 9 files changed, 141 insertions(+) create mode 100644 src/Core/Abstractions/IDeviceTrustCryptoService.cs create mode 100644 src/Core/Models/Request/TrustedDeviceKeysRequest.cs create mode 100644 src/Core/Models/Response/DeviceResponse.cs create mode 100644 src/Core/Services/DeviceTrustCryptoService.cs diff --git a/src/Core/Abstractions/IApiService.cs b/src/Core/Abstractions/IApiService.cs index b073b227c..5f9c530ff 100644 --- a/src/Core/Abstractions/IApiService.cs +++ b/src/Core/Abstractions/IApiService.cs @@ -92,6 +92,8 @@ namespace Bit.Core.Abstractions Task PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved); Task PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest); Task GetKnownDeviceAsync(string email, string deviceIdentifier); + Task GetDeviceByIdentifierAsync(string deviceIdentifier); + Task UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest); Task GetOrgDomainSsoDetailsAsync(string email); Task GetConfigsAsync(); Task GetFastmailAccountIdAsync(string apiKey); diff --git a/src/Core/Abstractions/IDeviceTrustCryptoService.cs b/src/Core/Abstractions/IDeviceTrustCryptoService.cs new file mode 100644 index 000000000..d4fc273a0 --- /dev/null +++ b/src/Core/Abstractions/IDeviceTrustCryptoService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Bit.Core.Models.Domain; + +namespace Bit.Core.Abstractions +{ + public interface IDeviceTrustCryptoService + { + Task GetDeviceKeyAsync(); + Task TrustDeviceAsync(); + } +} diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index 355acd093..0cda3a7a4 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -56,6 +56,8 @@ namespace Bit.Core.Abstractions Task SetOrgKeysEncryptedAsync(Dictionary value, string userId = null); Task GetPrivateKeyEncryptedAsync(string userId = null); Task SetPrivateKeyEncryptedAsync(string value, string userId = null); + Task GetDeviceKeyAsync(string userId = null); + Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null); Task> GetAutofillBlacklistedUrisAsync(string userId = null); Task SetAutofillBlacklistedUrisAsync(List value, string userId = null); Task GetAutofillTileAddedAsync(); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 73863f580..2e4ba55e9 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -91,6 +91,7 @@ public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{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 PinProtectedKey(string userId) => $"pinProtectedKey_{userId}"; public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}"; diff --git a/src/Core/Models/Request/TrustedDeviceKeysRequest.cs b/src/Core/Models/Request/TrustedDeviceKeysRequest.cs new file mode 100644 index 000000000..77e52b18c --- /dev/null +++ b/src/Core/Models/Request/TrustedDeviceKeysRequest.cs @@ -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; } + } +} diff --git a/src/Core/Models/Response/DeviceResponse.cs b/src/Core/Models/Response/DeviceResponse.cs new file mode 100644 index 000000000..331d0562a --- /dev/null +++ b/src/Core/Models/Response/DeviceResponse.cs @@ -0,0 +1,13 @@ +using Bit.Core.Enums; + +public class DeviceResponse +{ + public string Id { get; set; } + public int 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; } +} diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs index 6b2dbe48e..7b340609f 100644 --- a/src/Core/Services/ApiService.cs +++ b/src/Core/Services/ApiService.cs @@ -585,6 +585,16 @@ namespace Bit.Core.Services }); } + public Task GetDeviceByIdentifierAsync(string deviceIdentifier) + { + return SendAsync(HttpMethod.Get, $"/devices/identifier/{deviceIdentifier}", null, true, true); + } + + public Task UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest trustedDeviceKeysRequest) + { + return SendAsync(HttpMethod.Put, $"/devices/{deviceIdentifier}/keys", trustedDeviceKeysRequest, true, true); + } + #endregion #region Configs diff --git a/src/Core/Services/DeviceTrustCryptoService.cs b/src/Core/Services/DeviceTrustCryptoService.cs new file mode 100644 index 000000000..fac44276b --- /dev/null +++ b/src/Core/Services/DeviceTrustCryptoService.cs @@ -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 GetDeviceKeyAsync() + { + return await _stateService.GetDeviceKeyAsync(); + } + + private async Task SetDeviceKeyAsync(SymmetricCryptoKey deviceKey) + { + await _stateService.SetDeviceKeyAsync(deviceKey); + } + + public async Task 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 MakeDeviceKeyAsync() + { + // Create 512-bit device key + var randomBytes = await _cryptoFunctionService.RandomBytesAsync(DEVICE_KEY_SIZE); + return new SymmetricCryptoKey(randomBytes); + } + } +} diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index ae2d62d8e..0723e5e9e 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -482,6 +482,17 @@ namespace Bit.Core.Services await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions); } + public async Task GetDeviceKeyAsync(string userId = null) + { + var deviceKeyB64 = await _storageMediatorService.GetAsync(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> GetAutofillBlacklistedUrisAsync(string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },