diff --git a/src/App/Platforms/Android/MainApplication.cs b/src/App/Platforms/Android/MainApplication.cs index d38aa2ed1..f23363b42 100644 --- a/src/App/Platforms/Android/MainApplication.cs +++ b/src/App/Platforms/Android/MainApplication.cs @@ -158,7 +158,7 @@ namespace Bit.Droid var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, platformUtilsService, new LazyResolve()); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); - var cryptoService = new CryptoService(stateService, cryptoFunctionService); + var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger); var biometricService = new BiometricService(stateService, cryptoService); var userPinService = new UserPinService(stateService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService); diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index 427179776..ffb9f4c31 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -63,5 +63,7 @@ namespace Bit.Core.Abstractions Task DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey); Task GetOrDeriveMasterKeyAsync(string password, string userId = null); Task UpdateMasterKeyAndUserKeyAsync(MasterKey masterKey); + Task HashAsync(string value, CryptoHashAlgorithm hashAlgorithm); + Task ValidateUriChecksumAsync(EncString remoteUriChecksum, string rawUri, string orgId, SymmetricCryptoKey key); } } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 9b96deb4a..44de67eb0 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -70,7 +70,7 @@ namespace Bit.Core public const int Argon2Parallelism = 4; public const int MasterPasswordMinimumChars = 12; public const int CipherKeyRandomBytesLength = 64; - public const string CipherKeyEncryptionMinServerVersion = "2023.9.1"; + public const string CipherKeyEncryptionMinServerVersion = "2023.12.0"; public const string DefaultFido2CredentialType = "public-key"; public const string DefaultFido2CredentialAlgorithm = "ECDSA"; public const string DefaultFido2CredentialCurve = "P-256"; diff --git a/src/Core/Models/Api/LoginUriApi.cs b/src/Core/Models/Api/LoginUriApi.cs index d9e59c65d..a1253f36d 100644 --- a/src/Core/Models/Api/LoginUriApi.cs +++ b/src/Core/Models/Api/LoginUriApi.cs @@ -6,5 +6,6 @@ namespace Bit.Core.Models.Api { public string Uri { get; set; } public UriMatchType? Match { get; set; } + public string UriChecksum { get; set; } } } diff --git a/src/Core/Models/Data/LoginUriData.cs b/src/Core/Models/Data/LoginUriData.cs index 52294c523..a27344f43 100644 --- a/src/Core/Models/Data/LoginUriData.cs +++ b/src/Core/Models/Data/LoginUriData.cs @@ -11,9 +11,11 @@ namespace Bit.Core.Models.Data { Uri = data.Uri; Match = data.Match; + UriChecksum = data.UriChecksum; } public string Uri { get; set; } public UriMatchType? Match { get; set; } + public string UriChecksum { get; set; } } } diff --git a/src/Core/Models/Domain/Cipher.cs b/src/Core/Models/Domain/Cipher.cs index 48aa3a761..a7a2d6943 100644 --- a/src/Core/Models/Domain/Cipher.cs +++ b/src/Core/Models/Domain/Cipher.cs @@ -115,7 +115,7 @@ namespace Bit.Core.Models.Domain switch (Type) { case Enums.CipherType.Login: - model.Login = await Login.DecryptAsync(OrganizationId, model.Key); + model.Login = await Login.DecryptAsync(OrganizationId, Key == null, model.Key); break; case Enums.CipherType.SecureNote: model.SecureNote = await SecureNote.DecryptAsync(OrganizationId, model.Key); diff --git a/src/Core/Models/Domain/Login.cs b/src/Core/Models/Domain/Login.cs index ed1822c13..26aecf840 100644 --- a/src/Core/Models/Domain/Login.cs +++ b/src/Core/Models/Domain/Login.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Models.View; +using Bit.Core.Utilities; namespace Bit.Core.Models.Domain { @@ -31,7 +33,7 @@ namespace Bit.Core.Models.Domain public EncString Totp { get; set; } public List Fido2Credentials { get; set; } - public async Task DecryptAsync(string orgId, SymmetricCryptoKey key = null) + public async Task DecryptAsync(string orgId, bool bypassUriChecksumValidation, SymmetricCryptoKey key = null) { var view = await DecryptObjAsync(new LoginView(this), this, new HashSet { @@ -41,10 +43,15 @@ namespace Bit.Core.Models.Domain }, orgId, key); if (Uris != null) { + var cryptoService = ServiceContainer.Resolve(); view.Uris = new List(); foreach (var uri in Uris) { - view.Uris.Add(await uri.DecryptAsync(orgId, key)); + var loginUriView = await uri.DecryptAsync(orgId, key); + if (bypassUriChecksumValidation || await cryptoService.ValidateUriChecksumAsync(uri.UriChecksum, loginUriView.Uri, orgId, key)) + { + view.Uris.Add(loginUriView); + } } } if (Fido2Credentials != null) diff --git a/src/Core/Models/Domain/LoginUri.cs b/src/Core/Models/Domain/LoginUri.cs index e9b26d6d1..896570d73 100644 --- a/src/Core/Models/Domain/LoginUri.cs +++ b/src/Core/Models/Domain/LoginUri.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Models.Data; @@ -10,7 +11,8 @@ namespace Bit.Core.Models.Domain { private HashSet _map = new HashSet { - "Uri" + nameof(Uri), + nameof(UriChecksum) }; public LoginUri() { } @@ -23,10 +25,11 @@ namespace Bit.Core.Models.Domain public EncString Uri { get; set; } public UriMatchType? Match { get; set; } + public EncString UriChecksum { get; set; } public Task DecryptAsync(string orgId, SymmetricCryptoKey key = null) { - return DecryptObjAsync(new LoginUriView(this), this, _map, orgId, key); + return DecryptObjAsync(new LoginUriView(this), this, _map.Where(m => m != nameof(UriChecksum)).ToHashSet(), orgId, key); } public LoginUriData ToLoginUriData() diff --git a/src/Core/Models/Export/LoginUri.cs b/src/Core/Models/Export/LoginUri.cs index e3f215ff7..a4e161fed 100644 --- a/src/Core/Models/Export/LoginUri.cs +++ b/src/Core/Models/Export/LoginUri.cs @@ -17,10 +17,12 @@ namespace Bit.Core.Models.Export { Match = obj.Match; Uri = obj.Uri?.EncryptedString; + UriChecksum = obj.UriChecksum?.EncryptedString; } public UriMatchType? Match { get; set; } public string Uri { get; set; } + public string UriChecksum { get; set; } public static LoginUriView ToView(LoginUri req, LoginUriView view = null) { diff --git a/src/Core/Models/Request/CipherRequest.cs b/src/Core/Models/Request/CipherRequest.cs index f0b44d402..ae48b83b5 100644 --- a/src/Core/Models/Request/CipherRequest.cs +++ b/src/Core/Models/Request/CipherRequest.cs @@ -27,7 +27,7 @@ namespace Bit.Core.Models.Request Login = new LoginApi { Uris = cipher.Login.Uris?.Select( - u => new LoginUriApi { Match = u.Match, Uri = u.Uri?.EncryptedString }).ToList(), + u => new LoginUriApi { Match = u.Match, Uri = u.Uri?.EncryptedString, UriChecksum = u.UriChecksum?.EncryptedString }).ToList(), Username = cipher.Login.Username?.EncryptedString, Password = cipher.Login.Password?.EncryptedString, PasswordRevisionDate = cipher.Login.PasswordRevisionDate, diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index 776bbbf44..357458970 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -1147,13 +1147,15 @@ namespace Bit.Core.Services if (model.Login.Uris != null) { cipher.Login.Uris = new List(); - foreach (var uri in model.Login.Uris) + foreach (var uri in model.Login.Uris.Where(u => u.Uri != null)) { var loginUri = new LoginUri { Match = uri.Match }; await EncryptObjPropertyAsync(uri, loginUri, new HashSet { "Uri" }, key); + var uriHash = await _cryptoService.HashAsync(uri.Uri, CryptoHashAlgorithm.Sha256); + loginUri.UriChecksum = await _cryptoService.EncryptAsync(uriHash, key); cipher.Login.Uris.Add(loginUri); } } diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 7388f8e9a..5027cc853 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -19,6 +19,7 @@ namespace Bit.Core.Services private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; + private readonly ILogger _logger; private SymmetricCryptoKey _legacyEtmKey; private string _masterKeyHash; @@ -29,10 +30,12 @@ namespace Bit.Core.Services public CryptoService( IStateService stateService, - ICryptoFunctionService cryptoFunctionService) + ICryptoFunctionService cryptoFunctionService, + ILogger logger) { _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; + _logger = logger; } public void ClearCache() @@ -730,6 +733,33 @@ namespace Bit.Core.Services } } + public async Task HashAsync(string value, CryptoHashAlgorithm hashAlgorithm) + { + var hashArray = await _cryptoFunctionService.HashAsync(value, hashAlgorithm); + return Convert.ToBase64String(hashArray); + } + + public async Task ValidateUriChecksumAsync(EncString remoteUriChecksum, string rawUri, string orgId, SymmetricCryptoKey key) + { + try + { + if (remoteUriChecksum == null) + { + return false; + } + + var localChecksum = await HashAsync(rawUri, CryptoHashAlgorithm.Sha256); + + var remoteChecksum = await remoteUriChecksum.DecryptAsync(orgId, key); + return remoteChecksum == localChecksum; + } + catch (Exception ex) + { + _logger.Exception(ex); + return false; + } + } + // --HELPER METHODS-- private async Task StoreAdditionalKeysAsync(UserKey userKey, string userId = null) diff --git a/src/iOS.Core/Utilities/iOSCoreHelpers.cs b/src/iOS.Core/Utilities/iOSCoreHelpers.cs index c2efab343..cf679e9ab 100644 --- a/src/iOS.Core/Utilities/iOSCoreHelpers.cs +++ b/src/iOS.Core/Utilities/iOSCoreHelpers.cs @@ -157,7 +157,7 @@ namespace Bit.iOS.Core.Utilities var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService, messagingService, broadcasterService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); - var cryptoService = new CryptoService(stateService, cryptoFunctionService); + var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger); var biometricService = new BiometricService(stateService, cryptoService); var userPinService = new UserPinService(stateService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);