diff --git a/src/App/Abstractions/Repositories/ICipherApiRepository.cs b/src/App/Abstractions/Repositories/ICipherApiRepository.cs index e5de591e1..e51e13384 100644 --- a/src/App/Abstractions/Repositories/ICipherApiRepository.cs +++ b/src/App/Abstractions/Repositories/ICipherApiRepository.cs @@ -6,7 +6,7 @@ namespace Bit.App.Abstractions { public interface ICipherApiRepository : IApiRepository { - Task> PostAttachmentAsync(string cipherId, byte[] data, string fileName); + Task> PostAttachmentAsync(string cipherId, byte[] data, string key, string fileName); Task DeleteAttachmentAsync(string cipherId, string attachmentId); } } \ No newline at end of file diff --git a/src/App/Abstractions/Services/ICipherService.cs b/src/App/Abstractions/Services/ICipherService.cs index 501e491e5..6e4815115 100644 --- a/src/App/Abstractions/Services/ICipherService.cs +++ b/src/App/Abstractions/Services/ICipherService.cs @@ -19,7 +19,7 @@ namespace Bit.App.Abstractions Task UpsertDataAsync(CipherData cipher, bool sendMessage, bool created); Task DeleteAsync(string id); Task DeleteDataAsync(string id, bool sendMessage); - Task DownloadAndDecryptAttachmentAsync(string url, string orgId = null); + Task DownloadAndDecryptAttachmentAsync(string url, CipherString key, string orgId = null); Task> EncryptAndSaveAttachmentAsync(Cipher cipher, byte[] data, string fileName); Task UpsertAttachmentDataAsync(IEnumerable attachments); Task DeleteAttachmentAsync(Cipher cipher, string attachmentId); diff --git a/src/App/Abstractions/Services/ICryptoService.cs b/src/App/Abstractions/Services/ICryptoService.cs index 636587c49..04fa63f1f 100644 --- a/src/App/Abstractions/Services/ICryptoService.cs +++ b/src/App/Abstractions/Services/ICryptoService.cs @@ -1,6 +1,7 @@ using Bit.App.Enums; using Bit.App.Models; using Bit.App.Models.Api; +using System; using System.Collections.Generic; namespace Bit.App.Abstractions @@ -26,6 +27,6 @@ namespace Bit.App.Abstractions SymmetricCryptoKey MakeKeyFromPassword(string password, string salt, KdfType kdf, int kdfIterations); byte[] HashPassword(SymmetricCryptoKey key, string password); string HashPasswordBase64(SymmetricCryptoKey key, string password); - CipherString MakeEncKey(SymmetricCryptoKey key); + Tuple MakeEncKey(SymmetricCryptoKey key); } } \ No newline at end of file diff --git a/src/App/Models/Api/Response/AttachmentResponse.cs b/src/App/Models/Api/Response/AttachmentResponse.cs index 8695ec2f8..fe1f0baa4 100644 --- a/src/App/Models/Api/Response/AttachmentResponse.cs +++ b/src/App/Models/Api/Response/AttachmentResponse.cs @@ -5,6 +5,7 @@ public string Id { get; set; } public string Url { get; set; } public string FileName { get; set; } + public string Key { get; set; } public string Size { get; set; } public string SizeName { get; set; } } diff --git a/src/App/Models/Attachment.cs b/src/App/Models/Attachment.cs index 0c402b3a2..f3a5e0415 100644 --- a/src/App/Models/Attachment.cs +++ b/src/App/Models/Attachment.cs @@ -13,6 +13,7 @@ namespace Bit.App.Models Id = data.Id; Url = data.Url; FileName = data.FileName != null ? new CipherString(data.FileName) : null; + Key = data.Key != null ? new CipherString(data.Key) : null; SetSize(data.Size); SizeName = data.SizeName; } @@ -22,6 +23,7 @@ namespace Bit.App.Models Id = response.Id; Url = response.Url; FileName = response.FileName != null ? new CipherString(response.FileName) : null; + Key = response.Key != null ? new CipherString(response.Key) : null; SetSize(response.Size); SizeName = response.SizeName; } @@ -29,6 +31,7 @@ namespace Bit.App.Models public string Id { get; set; } public string Url { get; set; } public CipherString FileName { get; set; } + public CipherString Key { get; set; } public long Size { get; set; } public string SizeName { get; set; } diff --git a/src/App/Models/Data/AttachmentData.cs b/src/App/Models/Data/AttachmentData.cs index 5be55d0d3..47c657d2c 100644 --- a/src/App/Models/Data/AttachmentData.cs +++ b/src/App/Models/Data/AttachmentData.cs @@ -16,6 +16,7 @@ namespace Bit.App.Models.Data LoginId = cipherId; Url = attachment.Url; FileName = attachment.FileName?.EncryptedString; + Key = attachment.Key?.EncryptedString; Size = attachment.Size.ToString(); SizeName = attachment.SizeName; } @@ -26,6 +27,7 @@ namespace Bit.App.Models.Data LoginId = cipherId; Url = response.Url; FileName = response.FileName; + Key = response.Key; Size = response.Size; SizeName = response.SizeName; } @@ -37,6 +39,7 @@ namespace Bit.App.Models.Data public string LoginId { get; set; } public string Url { get; set; } public string FileName { get; set; } + public string Key { get; set; } public string Size { get; set; } public string SizeName { get; set; } diff --git a/src/App/Models/Page/VaultViewCipherPageModel.cs b/src/App/Models/Page/VaultViewCipherPageModel.cs index 9c7f82be7..5845a438e 100644 --- a/src/App/Models/Page/VaultViewCipherPageModel.cs +++ b/src/App/Models/Page/VaultViewCipherPageModel.cs @@ -579,6 +579,7 @@ namespace Bit.App.Models.Page { Id = attachment.Id, Name = attachment.FileName?.Decrypt(cipher.OrganizationId), + Key = attachment.Key, SizeName = attachment.SizeName, Size = attachment.Size, Url = attachment.Url @@ -670,6 +671,7 @@ namespace Bit.App.Models.Page { public string Id { get; set; } public string Name { get; set; } + public CipherString Key { get; set; } public string SizeName { get; set; } public long Size { get; set; } public string Url { get; set; } diff --git a/src/App/Pages/RegisterPage.cs b/src/App/Pages/RegisterPage.cs index 2d3620001..669e1a8a1 100644 --- a/src/App/Pages/RegisterPage.cs +++ b/src/App/Pages/RegisterPage.cs @@ -193,7 +193,7 @@ namespace Bit.App.Pages } var kdf = Enums.KdfType.PBKDF2_SHA256; - var kdfIterations = 5000; + var kdfIterations = 100000; var normalizedEmail = EmailCell.Entry.Text.ToLower().Trim(); var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, normalizedEmail, kdf, kdfIterations); var encKey = _cryptoService.MakeEncKey(key); @@ -203,7 +203,7 @@ namespace Bit.App.Pages MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text), MasterPasswordHint = !string.IsNullOrWhiteSpace(PasswordHintCell.Entry.Text) ? PasswordHintCell.Entry.Text : null, - Key = encKey.EncryptedString, + Key = encKey.Item2.EncryptedString, Kdf = kdf, KdfIterations = kdfIterations }; diff --git a/src/App/Pages/Vault/VaultViewCipherPage.cs b/src/App/Pages/Vault/VaultViewCipherPage.cs index 97fcca783..cd1bf19cd 100644 --- a/src/App/Pages/Vault/VaultViewCipherPage.cs +++ b/src/App/Pages/Vault/VaultViewCipherPage.cs @@ -501,7 +501,8 @@ namespace Bit.App.Pages } await _deviceActionService.ShowLoadingAsync(AppResources.Downloading); - var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment.Url, cipher.OrganizationId); + var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment.Url, attachment.Key, + cipher.OrganizationId); await _deviceActionService.HideLoadingAsync(); if(data == null) diff --git a/src/App/Repositories/CipherApiRepository.cs b/src/App/Repositories/CipherApiRepository.cs index 72a95b4c4..c527edcd9 100644 --- a/src/App/Repositories/CipherApiRepository.cs +++ b/src/App/Repositories/CipherApiRepository.cs @@ -21,7 +21,7 @@ namespace Bit.App.Repositories protected override string ApiRoute => "/ciphers"; public virtual async Task> PostAttachmentAsync(string cipherId, byte[] data, - string fileName) + string key, string fileName) { if(!Connectivity.IsConnected) { @@ -37,6 +37,7 @@ namespace Bit.App.Repositories using(var client = HttpService.ApiClient) using(var content = new MultipartFormDataContent("--BWMobileFormBoundary" + DateTime.UtcNow.Ticks)) { + content.Add(new StringContent(key), "key"); content.Add(new StreamContent(new MemoryStream(data)), "data", fileName); var requestMessage = new TokenHttpRequestMessage diff --git a/src/App/Services/CipherService.cs b/src/App/Services/CipherService.cs index 3d533ae42..de82a9179 100644 --- a/src/App/Services/CipherService.cs +++ b/src/App/Services/CipherService.cs @@ -310,7 +310,7 @@ namespace Bit.App.Services _appSettingsService.ClearCiphersCache = true; } - public async Task DownloadAndDecryptAttachmentAsync(string url, string orgId = null) + public async Task DownloadAndDecryptAttachmentAsync(string url, CipherString key, string orgId = null) { using(var client = new HttpClient()) { @@ -328,14 +328,20 @@ namespace Bit.App.Services return null; } - if(!string.IsNullOrWhiteSpace(orgId)) + SymmetricCryptoKey regularKey = !string.IsNullOrWhiteSpace(orgId) ? + _cryptoService.GetOrgKey(orgId) : null; + SymmetricCryptoKey dataKey = null; + if(key != null) { - return _cryptoService.DecryptToBytes(data, _cryptoService.GetOrgKey(orgId)); + var decDataKey = _cryptoService.DecryptToBytes(key, regularKey); + dataKey = new SymmetricCryptoKey(decDataKey); } else { - return _cryptoService.DecryptToBytes(data, null); + dataKey = regularKey; } + + return _cryptoService.DecryptToBytes(data, dataKey); } catch { @@ -346,10 +352,13 @@ namespace Bit.App.Services public async Task> EncryptAndSaveAttachmentAsync(Cipher cipher, byte[] data, string fileName) { + var key = cipher.OrganizationId != null ? _cryptoService.GetOrgKey(cipher.OrganizationId) : null; var encFileName = fileName.Encrypt(cipher.OrganizationId); - var encBytes = _cryptoService.EncryptToBytes(data, - cipher.OrganizationId != null ? _cryptoService.GetOrgKey(cipher.OrganizationId) : null); - var response = await _cipherApiRepository.PostAttachmentAsync(cipher.Id, encBytes, encFileName.EncryptedString); + + var dataKey = _cryptoService.MakeEncKey(key); + var encBytes = _cryptoService.EncryptToBytes(data, dataKey.Item1); + var response = await _cipherApiRepository.PostAttachmentAsync(cipher.Id, encBytes, + dataKey.Item2.EncryptedString, encFileName.EncryptedString); if(response.Succeeded) { diff --git a/src/App/Services/CryptoService.cs b/src/App/Services/CryptoService.cs index 5a429de46..f2502e304 100644 --- a/src/App/Services/CryptoService.cs +++ b/src/App/Services/CryptoService.cs @@ -483,18 +483,20 @@ namespace Bit.App.Services return Convert.ToBase64String(hash); } - public CipherString MakeEncKey(SymmetricCryptoKey key) + public Tuple MakeEncKey(SymmetricCryptoKey key) { + var theKey = key ?? EncKey ?? Key; var encKey = Crypto.RandomBytes(64); - // TODO: Remove hardcoded true/false when we're ready to enable key stretching - if(false && key.Key.Length == 32) + if(theKey.Key.Length == 32) { - var newKey = StretchKey(key); - return Encrypt(encKey, newKey); + var newKey = StretchKey(theKey); + return new Tuple( + new SymmetricCryptoKey(encKey), Encrypt(encKey, newKey)); } - else if(true || key.Key.Length == 64) + else if(theKey.Key.Length == 64) { - return Encrypt(encKey, key); + return new Tuple( + new SymmetricCryptoKey(encKey), Encrypt(encKey, theKey)); } throw new Exception("Invalid key size.");