diff --git a/src/App/Pages/Accounts/RegisterPageViewModel.cs b/src/App/Pages/Accounts/RegisterPageViewModel.cs index d7df5e75f..b742a65ba 100644 --- a/src/App/Pages/Accounts/RegisterPageViewModel.cs +++ b/src/App/Pages/Accounts/RegisterPageViewModel.cs @@ -177,25 +177,28 @@ namespace Bit.App.Pages Name = string.IsNullOrWhiteSpace(Name) ? null : Name; Email = Email.Trim().ToLower(); var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); - var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig); - var encKey = await _cryptoService.MakeEncKeyAsync(masterKey); - var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey); - var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); + var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig); + var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync( + newMasterKey, + await _cryptoService.MakeUserKeyAsync() + ); + var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey); + var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey); var request = new RegisterRequest { Email = Email, Name = Name, MasterPasswordHash = hashedPassword, MasterPasswordHint = Hint, - Key = encKey.Item2.EncryptedString, + Key = newProtectedUserKey.EncryptedString, Kdf = kdfConfig.Type, KdfIterations = kdfConfig.Iterations, KdfMemory = kdfConfig.Memory, KdfParallelism = kdfConfig.Parallelism, Keys = new KeysRequest { - PublicKey = keys.Item1, - EncryptedPrivateKey = keys.Item2.EncryptedString + PublicKey = newPublicKey, + EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString }, CaptchaResponse = _captchaToken, }; diff --git a/src/App/Pages/Vault/AttachmentsPageViewModel.cs b/src/App/Pages/Vault/AttachmentsPageViewModel.cs index 02e9b2ae6..2e4b99b34 100644 --- a/src/App/Pages/Vault/AttachmentsPageViewModel.cs +++ b/src/App/Pages/Vault/AttachmentsPageViewModel.cs @@ -74,7 +74,7 @@ namespace Bit.App.Pages _cipherDomain = await _cipherService.GetAsync(CipherId); Cipher = await _cipherDomain.DecryptAsync(); LoadAttachments(); - _hasUpdatedKey = await _cryptoService.HasEncKeyAsync(); + _hasUpdatedKey = await _cryptoService.HasUserKeyAsync(); var canAccessPremium = await _stateService.CanAccessPremiumAsync(); _canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null; if (!_canAccessAttachments) diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index a576637d1..bfd66b1ed 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -12,6 +12,7 @@ namespace Bit.Core.Abstractions Task ToggleKeysAsync(); Task SetUserKeyAsync(UserKey userKey, string userId = null); Task GetUserKeyAsync(string userId = null); + Task GetUserKeyWithLegacySupportAsync(string userId = null); Task HasUserKeyAsync(string userId = null); Task HasEncryptedUserKeyAsync(string userId = null); Task MakeUserKeyAsync(); @@ -23,6 +24,8 @@ namespace Bit.Core.Abstractions Task ClearMasterKeyAsync(string userId = null); Task> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null); Task DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null); + Task> MakeDataEncKeyAsync(UserKey key); + Task> MakeDataEncKeyAsync(OrgKey key); Task HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); Task SetPasswordHashAsync(string keyHash); Task GetPasswordHashAsync(); @@ -70,11 +73,9 @@ namespace Bit.Core.Abstractions void ClearCache(); Task GetEncKeyAsync(SymmetricCryptoKey key = null); Task GetKeyAsync(string userId = null); - Task HasEncKeyAsync(); Task HasKeyAsync(string userId = null); Task> MakeEncKeyAsync(SymmetricCryptoKey key); // TODO(Jake): This isn't used, delete - Task> MakeShareKeyAsync(); Task SetEncKeyAsync(string encKey); Task SetKeyAsync(SymmetricCryptoKey key); } diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index e289a1ae6..18ffd3c4f 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -557,9 +557,20 @@ namespace Bit.Core.Services public async Task SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data) { + SymmetricCryptoKey attachmentKey; + EncString protectedAttachmentKey; var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId); + if (orgKey != null) + { + (attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(orgKey); + } + else + { + var userKey = await _cryptoService.GetUserKeyWithLegacySupportAsync(); + (attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(userKey); + } + var encFileName = await _cryptoService.EncryptAsync(filename, orgKey); - var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey); var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey); CipherResponse response; @@ -567,7 +578,7 @@ namespace Bit.Core.Services { var request = new AttachmentRequest { - Key = orgEncAttachmentKey.EncryptedString, + Key = protectedAttachmentKey.EncryptedString, FileName = encFileName.EncryptedString, FileSize = encFileData.Buffer.Length, }; @@ -578,7 +589,7 @@ namespace Bit.Core.Services } catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed) { - response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey); + response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, protectedAttachmentKey); } var userId = await _stateService.GetActiveUserIdAsync(); @@ -807,14 +818,27 @@ namespace Bit.Core.Services var bytes = await attachmentResponse.Content.ReadAsByteArrayAsync(); var decBytes = await _cryptoService.DecryptFromBytesAsync(bytes, null); - var key = await _cryptoService.GetOrgKeyAsync(organizationId); - var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, key); - var dataEncKey = await _cryptoService.MakeEncKeyAsync(key); - var encData = await _cryptoService.EncryptToBytesAsync(decBytes, dataEncKey.Item1); + + SymmetricCryptoKey attachmentKey; + EncString protectedAttachmentKey; + var orgKey = await _cryptoService.GetOrgKeyAsync(organizationId); + if (orgKey != null) + { + (attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(orgKey); + } + else + { + var userKey = await _cryptoService.GetUserKeyWithLegacySupportAsync(); + (attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(userKey); + } + + var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, orgKey); + var encFileData = await _cryptoService.EncryptToBytesAsync(decBytes, attachmentKey); + var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks); var fd = new MultipartFormDataContent(boundary); - fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key"); - fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString); + fd.Add(new StringContent(protectedAttachmentKey.EncryptedString), "key"); + fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString); await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId); } diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 1aa5412c2..1c1349112 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -68,6 +68,19 @@ namespace Bit.Core.Services return await _stateService.GetUserKeyAsync(userId); } + public async Task GetUserKeyWithLegacySupportAsync(string userId = null) + { + var userKey = await GetUserKeyAsync(); + if (userKey != null) + { + return userKey; + } + + // Legacy support: encryption used to be done with the master key (derived from master password). + // Users who have not migrated will have a null user key and must use the master key instead. + return (SymmetricCryptoKey)await GetMasterKeyAsync() as UserKey; + } + public async Task HasUserKeyAsync(string userId = null) { return await GetUserKeyAsync(userId) != null; @@ -176,22 +189,22 @@ namespace Bit.Core.Services return new UserKey(decUserKey); } - public async Task> MakeDataEncKey(UserKey key) + public async Task> MakeDataEncKeyAsync(UserKey key) { if (key == null) { - throw new Exception("No key provided"); + throw new Exception("No user key provided"); } var newSymKey = await _cryptoFunctionService.RandomBytesAsync(64); return await BuildProtectedSymmetricKey(key, newSymKey); } - public async Task> MakeDataEncKey(OrgKey key) + public async Task> MakeDataEncKeyAsync(OrgKey key) { if (key == null) { - throw new Exception("No key provided"); + throw new Exception("No org key provided"); } var newSymKey = await _cryptoFunctionService.RandomBytesAsync(64); @@ -1152,12 +1165,6 @@ namespace Bit.Core.Services return key != null; } - public async Task HasEncKeyAsync() - { - var encKey = await _stateService.GetEncKeyEncryptedAsync(); - return encKey != null; - } - public async Task ClearKeyAsync(string userId = null) { await _stateService.SetKeyDecryptedAsync(null, userId); @@ -1193,17 +1200,6 @@ namespace Bit.Core.Services } - // TODO(Jake): This isn't used, delete - public async Task> MakeShareKeyAsync() - { - var shareKey = await _cryptoFunctionService.RandomBytesAsync(64); - var publicKey = await GetPublicKeyAsync(); - var encShareKey = await RsaEncryptAsync(shareKey, publicKey); - return new Tuple(encShareKey, new SymmetricCryptoKey(shareKey)); - } - - - public async Task> MakeEncKeyAsync(SymmetricCryptoKey key) diff --git a/test/Core.Test/Services/CipherServiceTests.cs b/test/Core.Test/Services/CipherServiceTests.cs index 494fa192c..f63d115c7 100644 --- a/test/Core.Test/Services/CipherServiceTests.cs +++ b/test/Core.Test/Services/CipherServiceTests.cs @@ -29,7 +29,7 @@ namespace Bit.Core.Test.Services .Returns(encFileName); sutProvider.GetDependency().EncryptToBytesAsync(data.Buffer, Arg.Any()) .Returns(data); - sutProvider.GetDependency().MakeEncKeyAsync(Arg.Any()).Returns(new Tuple(null, encKey)); + sutProvider.GetDependency().MakeDataEncKeyAsync(Arg.Any()).Returns(new Tuple(null, encKey)); sutProvider.GetDependency().PostCipherAttachmentAsync(cipher.Id, Arg.Any()) .Returns(uploadDataResponse); @@ -50,7 +50,7 @@ namespace Bit.Core.Test.Services .Returns(new EncString(fileName)); sutProvider.GetDependency().EncryptToBytesAsync(data.Buffer, Arg.Any()) .Returns(data); - sutProvider.GetDependency().MakeEncKeyAsync(Arg.Any()).Returns(new Tuple(null, encKey)); + sutProvider.GetDependency().MakeDataEncKeyAsync(Arg.Any()).Returns(new Tuple(null, encKey)); sutProvider.GetDependency().PostCipherAttachmentAsync(cipher.Id, Arg.Any()) .Throws(new ApiException(new ErrorResponse { StatusCode = statusCode })); sutProvider.GetDependency().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any()) @@ -70,7 +70,7 @@ namespace Bit.Core.Test.Services .Returns(new EncString(fileName)); sutProvider.GetDependency().EncryptToBytesAsync(data.Buffer, Arg.Any()) .Returns(data); - sutProvider.GetDependency().MakeEncKeyAsync(Arg.Any()) + sutProvider.GetDependency().MakeDataEncKeyAsync(Arg.Any()) .Returns(new Tuple(null, encKey)); var expectedException = new ApiException(new ErrorResponse { StatusCode = HttpStatusCode.BadRequest }); sutProvider.GetDependency().PostCipherAttachmentAsync(cipher.Id, Arg.Any())