diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index 427179776..cdd5915ba 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -63,5 +63,6 @@ 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); } } 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..ff090efe7 100644 --- a/src/Core/Models/Data/LoginUriData.cs +++ b/src/Core/Models/Data/LoginUriData.cs @@ -11,9 +11,12 @@ 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; } } } + \ No newline at end of file diff --git a/src/Core/Models/Domain/Cipher.cs b/src/Core/Models/Domain/Cipher.cs index 48aa3a761..9a818519d 100644 --- a/src/Core/Models/Domain/Cipher.cs +++ b/src/Core/Models/Domain/Cipher.cs @@ -93,6 +93,7 @@ namespace Bit.Core.Models.Domain public async Task DecryptAsync() { var model = new CipherView(this); + var bypassValidation = true; if (Key != null) { @@ -104,6 +105,7 @@ namespace Bit.Core.Models.Domain var key = await cryptoService.DecryptToBytesAsync(Key, orgKey); model.Key = new CipherKey(key); + bypassValidation = false; } await DecryptObjAsync(model, this, new HashSet @@ -115,7 +117,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, bypassValidation, 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..c6362ac6c 100644 --- a/src/Core/Models/Domain/Login.cs +++ b/src/Core/Models/Domain/Login.cs @@ -31,7 +31,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 bypassValidation, SymmetricCryptoKey key = null) { var view = await DecryptObjAsync(new LoginView(this), this, new HashSet { @@ -44,7 +44,10 @@ namespace Bit.Core.Models.Domain 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 (bypassValidation || (await uri.ValidateChecksum(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..118ca6b15 100644 --- a/src/Core/Models/Domain/LoginUri.cs +++ b/src/Core/Models/Domain/LoginUri.cs @@ -1,8 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.View; +using Bit.Core.Utilities; namespace Bit.Core.Models.Domain { @@ -23,6 +25,7 @@ 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) { @@ -35,5 +38,16 @@ namespace Bit.Core.Models.Domain BuildDataModel(this, u, _map, new HashSet { "Match" }); return u; } + + public async Task ValidateChecksum(string clearTextUri, string orgId, SymmetricCryptoKey key) + { + // HACK: I don't like resolving this here but I can't see a better way without + // refactoring a lot of things. + var cryptoService = ServiceContainer.Resolve(); + var localChecksum = await cryptoService.HashAsync(clearTextUri, CryptoHashAlgorithm.Sha256); + + var remoteChecksum = await this.UriChecksum.DecryptAsync(orgId, key); + return remoteChecksum == localChecksum; + } } } diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index c17c172e7..e49630f75 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -1153,6 +1153,8 @@ namespace Bit.Core.Services 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..3007cecec 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -1,4 +1,4 @@ -using System; + using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -730,6 +730,12 @@ namespace Bit.Core.Services } } + public async Task HashAsync(string value, CryptoHashAlgorithm hashAlgorithm) + { + var hashArray = await _cryptoFunctionService.HashAsync(value, hashAlgorithm); + return Convert.ToBase64String(hashArray); + } + // --HELPER METHODS-- private async Task StoreAdditionalKeysAsync(UserKey userKey, string userId = null)