From c8219b29c016d71a11d00ff2093fd07e73f93ec7 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 25 Apr 2017 16:05:13 -0400 Subject: [PATCH] encrypted private key and org keys at rest --- .../Repositories/IAccountsApiRepository.cs | 1 + .../Abstractions/Services/ICryptoService.cs | 9 +- src/App/App.csproj | 1 + src/App/Models/Api/Response/KeysResponse.cs | 8 + src/App/Repositories/AccountsApiRepository.cs | 40 ++++ src/App/Services/AuthService.cs | 21 +-- src/App/Services/CryptoService.cs | 172 +++++++++--------- src/App/Services/SyncService.cs | 36 ++-- test/App.Test/CryptoServiceTests.cs | 4 +- 9 files changed, 159 insertions(+), 133 deletions(-) create mode 100644 src/App/Models/Api/Response/KeysResponse.cs diff --git a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs index 980526eb2..257084337 100644 --- a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs +++ b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs @@ -10,5 +10,6 @@ namespace Bit.App.Abstractions Task PostPasswordHintAsync(PasswordHintRequest requestObj); Task> GetAccountRevisionDateAsync(); Task> GetProfileAsync(); + Task> GetKeys(); } } \ No newline at end of file diff --git a/src/App/Abstractions/Services/ICryptoService.cs b/src/App/Abstractions/Services/ICryptoService.cs index 338e361e5..e33816207 100644 --- a/src/App/Abstractions/Services/ICryptoService.cs +++ b/src/App/Abstractions/Services/ICryptoService.cs @@ -1,4 +1,5 @@ using Bit.App.Models; +using Bit.App.Models.Api; using System; using System.Collections.Generic; @@ -10,13 +11,13 @@ namespace Bit.App.Abstractions SymmetricCryptoKey PreviousKey { get; } bool KeyChanged { get; } byte[] PrivateKey { get; } - IDictionary OrgKeys { get; set; } + IDictionary OrgKeys { get; } - void SetPrivateKey(CipherString privateKeyEnc, SymmetricCryptoKey key); + void SetPrivateKey(CipherString privateKeyEnc); + void SetOrgKeys(ProfileResponse profile); + void SetOrgKeys(Dictionary orgKeysEncDict); SymmetricCryptoKey GetOrgKey(string orgId); - void ClearOrgKey(string orgId); void ClearKeys(); - SymmetricCryptoKey AddOrgKey(string orgId, CipherString encOrgKey, byte[] privateKey); string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null); byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null); byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey); diff --git a/src/App/App.csproj b/src/App/App.csproj index a84979ddc..9d98489e3 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -107,6 +107,7 @@ + diff --git a/src/App/Models/Api/Response/KeysResponse.cs b/src/App/Models/Api/Response/KeysResponse.cs new file mode 100644 index 000000000..46b465409 --- /dev/null +++ b/src/App/Models/Api/Response/KeysResponse.cs @@ -0,0 +1,8 @@ +namespace Bit.App.Models.Api +{ + public class KeysResponse + { + public string PublicKey { get; set; } + public string PrivateKey { get; set; } + } +} diff --git a/src/App/Repositories/AccountsApiRepository.cs b/src/App/Repositories/AccountsApiRepository.cs index 002bd1590..886db137a 100644 --- a/src/App/Repositories/AccountsApiRepository.cs +++ b/src/App/Repositories/AccountsApiRepository.cs @@ -173,5 +173,45 @@ namespace Bit.App.Repositories } } } + + public virtual async Task> GetKeys() + { + if(!Connectivity.IsConnected) + { + return HandledNotConnected(); + } + + var tokenStateResponse = await HandleTokenStateAsync(); + if(!tokenStateResponse.Succeeded) + { + return tokenStateResponse; + } + + using(var client = HttpService.Client) + { + var requestMessage = new TokenHttpRequestMessage() + { + Method = HttpMethod.Get, + RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/keys")), + }; + + try + { + var response = await client.SendAsync(requestMessage).ConfigureAwait(false); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response).ConfigureAwait(false); + } + + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } + catch + { + return HandledWebException(); + } + } + } } } diff --git a/src/App/Services/AuthService.cs b/src/App/Services/AuthService.cs index 2de4bfba7..258523adb 100644 --- a/src/App/Services/AuthService.cs +++ b/src/App/Services/AuthService.cs @@ -275,7 +275,7 @@ namespace Bit.App.Services { if(response.PrivateKey != null) { - _cryptoService.SetPrivateKey(new CipherString(response.PrivateKey), key); + _cryptoService.SetPrivateKey(new CipherString(response.PrivateKey)); } _cryptoService.Key = key; @@ -288,25 +288,10 @@ namespace Bit.App.Services if(response.PrivateKey != null) { var profile = await _accountsApiRepository.GetProfileAsync(); - var orgKeysDict = new Dictionary(); - - if(profile.Succeeded && (profile.Result.Organizations?.Any() ?? false)) + if(profile.Succeeded) { - foreach(var org in profile.Result.Organizations) - { - try - { - var decBytes = _cryptoService.RsaDecryptToBytes(new CipherString(org.Key), null); - orgKeysDict.Add(org.Id, new SymmetricCryptoKey(decBytes)); - } - catch - { - Debug.WriteLine($"Cannot set org key {org.Id}. Decryption failed."); - } - } + _cryptoService.SetOrgKeys(profile.Result); } - - _cryptoService.OrgKeys = orgKeysDict; } } } diff --git a/src/App/Services/CryptoService.cs b/src/App/Services/CryptoService.cs index 46d191b29..d5ef18aa9 100644 --- a/src/App/Services/CryptoService.cs +++ b/src/App/Services/CryptoService.cs @@ -8,6 +8,8 @@ using System.Linq; using Bit.App.Enums; using System.Collections.Generic; using Newtonsoft.Json; +using Plugin.Settings.Abstractions; +using Bit.App.Models.Api; namespace Bit.App.Services { @@ -15,10 +17,11 @@ namespace Bit.App.Services { private const string KeyKey = "key"; private const string PreviousKeyKey = "previousKey"; - private const string PrivateKeyKey = "privateKey"; - private const string OrgKeysKey = "orgKeys"; + private const string PrivateKeyKey = "encPrivateKey"; + private const string OrgKeysKey = "encOrgKeys"; private const int InitializationVectorSize = 16; + private readonly ISettings _settings; private readonly ISecureStorageService _secureStorage; private readonly IKeyDerivationService _keyDerivationService; private SymmetricCryptoKey _key; @@ -28,9 +31,11 @@ namespace Bit.App.Services private byte[] _privateKey; public CryptoService( + ISettings settings, ISecureStorageService secureStorage, IKeyDerivationService keyDerivationService) { + _settings = settings; _secureStorage = secureStorage; _keyDerivationService = keyDerivationService; } @@ -113,45 +118,47 @@ namespace Bit.App.Services { get { - if(_privateKey == null && _secureStorage.Contains(PrivateKeyKey)) + if(_privateKey == null && _settings.Contains(PrivateKeyKey)) { - _privateKey = _secureStorage.Retrieve(PrivateKeyKey); + var encPrivateKey = _settings.GetValueOrDefault(PrivateKeyKey); + var encPrivateKeyCs = new CipherString(encPrivateKey); + try + { + _privateKey = DecryptToBytes(encPrivateKeyCs); + } + catch + { + _privateKey = null; + Debug.WriteLine($"Cannot set private key. Decryption failed."); + } } return _privateKey; } - private set - { - if(value != null) - { - _secureStorage.Store(PrivateKeyKey, value); - _privateKey = value; - } - else - { - _secureStorage.Delete(PrivateKeyKey); - _privateKey = null; - } - } } public IDictionary OrgKeys { get { - if(_orgKeys == null && _secureStorage.Contains(OrgKeysKey)) + if((!_orgKeys?.Any() ?? true) && _settings.Contains(OrgKeysKey)) { - var orgKeysDictBytes = _secureStorage.Retrieve(OrgKeysKey); - if(orgKeysDictBytes != null) + var orgKeysEncDictJson = _settings.GetValueOrDefault(OrgKeysKey); + if(!string.IsNullOrWhiteSpace(orgKeysEncDictJson)) { - var orgKeysDictJson = Encoding.UTF8.GetString(orgKeysDictBytes, 0, orgKeysDictBytes.Length); - if(!string.IsNullOrWhiteSpace(orgKeysDictJson)) + _orgKeys = new Dictionary(); + var orgKeysDict = JsonConvert.DeserializeObject>(orgKeysEncDictJson); + foreach(var item in orgKeysDict) { - _orgKeys = new Dictionary(); - var orgKeysDict = JsonConvert.DeserializeObject>(orgKeysDictJson); - foreach(var item in orgKeysDict) + try { - _orgKeys.Add(item.Key, new SymmetricCryptoKey(item.Value)); + var orgKeyCs = new CipherString(item.Value); + var decOrgKeyBytes = RsaDecryptToBytes(orgKeyCs, PrivateKey); + _orgKeys.Add(item.Key, new SymmetricCryptoKey(decOrgKeyBytes)); + } + catch + { + Debug.WriteLine($"Cannot set org key {item.Key}. Decryption failed."); } } } @@ -159,33 +166,56 @@ namespace Bit.App.Services return _orgKeys; } - set - { - if(value != null && value.Any()) - { - var dict = new Dictionary(); - foreach(var item in value) - { - dict.Add(item.Key, item.Value.Key); - } + } - var dictJson = JsonConvert.SerializeObject(dict); - var dictBytes = Encoding.UTF8.GetBytes(dictJson); - _secureStorage.Store(OrgKeysKey, dictBytes); - _orgKeys = value; - } - else - { - _secureStorage.Delete(OrgKeysKey); - _orgKeys = null; - } + public void SetPrivateKey(CipherString privateKeyEnc) + { + if(privateKeyEnc != null) + { + _settings.AddOrUpdateValue(PrivateKeyKey, privateKeyEnc.EncryptedString); + } + else if(_settings.Contains(PrivateKeyKey)) + { + _settings.Remove(PrivateKeyKey); + _privateKey = null; + } + else + { + _privateKey = null; } } - public void SetPrivateKey(CipherString privateKeyEnc, SymmetricCryptoKey key) + public void SetOrgKeys(ProfileResponse profile) { - var bytes = DecryptToBytes(privateKeyEnc, key); - PrivateKey = bytes; + var orgKeysEncDict = new Dictionary(); + + if(profile?.Organizations?.Any() ?? false) + { + foreach(var org in profile.Organizations) + { + orgKeysEncDict.Add(org.Id, org.Key); + } + } + + SetOrgKeys(orgKeysEncDict); + } + + public void SetOrgKeys(Dictionary orgKeysEncDict) + { + if(orgKeysEncDict?.Any() ?? false) + { + var dictJson = JsonConvert.SerializeObject(orgKeysEncDict); + _settings.AddOrUpdateValue(OrgKeysKey, dictJson); + } + else if(_settings.Contains(OrgKeysKey)) + { + _settings.Remove(OrgKeysKey); + _orgKeys = null; + } + else + { + _orgKeys = null; + } } public SymmetricCryptoKey GetOrgKey(string orgId) @@ -198,51 +228,11 @@ namespace Bit.App.Services return OrgKeys[orgId]; } - public void ClearOrgKey(string orgId) - { - var localOrgKeys = OrgKeys; - if(localOrgKeys == null || !localOrgKeys.ContainsKey(orgId)) - { - return; - } - - localOrgKeys.Remove(orgId); - // invoke setter - OrgKeys = localOrgKeys; - } - public void ClearKeys() { - OrgKeys = null; + SetOrgKeys((Dictionary)null); Key = null; - PrivateKey = null; - } - - public SymmetricCryptoKey AddOrgKey(string orgId, CipherString encOrgKey, byte[] privateKey) - { - try - { - var localOrgKeys = OrgKeys; - var decBytes = RsaDecryptToBytes(encOrgKey, privateKey); - var key = new SymmetricCryptoKey(decBytes); - if(localOrgKeys.ContainsKey(orgId)) - { - localOrgKeys[orgId] = key; - } - else - { - localOrgKeys.Add(orgId, key); - } - - // invoke setter - OrgKeys = localOrgKeys; - return key; - } - catch - { - Debug.WriteLine("Cannot set org key. Decryption failed."); - return null; - } + SetPrivateKey(null); } public CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null) @@ -270,7 +260,7 @@ namespace Bit.App.Services var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plaintextBytes, iv); var mac = key.MacKey != null ? ComputeMac(encryptedBytes, iv, key.MacKey) : null; - return new CipherString(key.EncryptionType, Convert.ToBase64String(iv), + return new CipherString(key.EncryptionType, Convert.ToBase64String(iv), Convert.ToBase64String(encryptedBytes), mac); } diff --git a/src/App/Services/SyncService.cs b/src/App/Services/SyncService.cs index d39cf4549..2d9c5944b 100644 --- a/src/App/Services/SyncService.cs +++ b/src/App/Services/SyncService.cs @@ -187,6 +187,7 @@ namespace Bit.App.Services SyncCompleted(true); return true; } + public async Task SyncProfileAsync() { if(!_authService.IsAuthenticated) @@ -202,7 +203,7 @@ namespace Bit.App.Services return false; } - SyncOrgKeys(profile.Result); + await SyncOrgKeysAsync(profile.Result); SyncCompleted(true); return true; @@ -257,10 +258,11 @@ namespace Bit.App.Services var loginTask = SyncLoginsAsync(loginsDict); var folderTask = SyncFoldersAsync(foldersDict); var domainsTask = SyncDomainsAsync(domains.Result); - SyncOrgKeys(profile.Result); - await Task.WhenAll(loginTask, folderTask, domainsTask).ConfigureAwait(false); + var orgKeysTask = SyncOrgKeysAsync(profile.Result); + await Task.WhenAll(loginTask, folderTask, domainsTask, orgKeysTask).ConfigureAwait(false); - if(folderTask.Exception != null || loginTask.Exception != null || domainsTask.Exception != null) + if(folderTask.Exception != null || loginTask.Exception != null || domainsTask.Exception != null || + orgKeysTask.Exception != null) { SyncCompleted(false); return false; @@ -389,27 +391,23 @@ namespace Bit.App.Services catch(SQLite.SQLiteException) { } } - private void SyncOrgKeys(ProfileResponse profile) + private async Task SyncOrgKeysAsync(ProfileResponse profile) { - var orgKeysDict = new Dictionary(); - - if(profile.Organizations != null) + if(_cryptoService.PrivateKey == null) { - foreach(var org in profile.Organizations) + var keys = await _accountsApiRepository.GetKeys(); + if(!CheckSuccess(keys)) { - try - { - var decBytes = _cryptoService.RsaDecryptToBytes(new CipherString(org.Key), null); - orgKeysDict.Add(org.Id, new SymmetricCryptoKey(decBytes)); - } - catch - { - Debug.WriteLine($"Cannot set org key {org.Id}. Decryption failed."); - } + return; + } + + if(!string.IsNullOrWhiteSpace(keys.Result.PrivateKey)) + { + _cryptoService.SetPrivateKey(new CipherString(keys.Result.PrivateKey)); } } - _cryptoService.OrgKeys = orgKeysDict; + _cryptoService.SetOrgKeys(profile); } private void SyncStarted() diff --git a/test/App.Test/CryptoServiceTests.cs b/test/App.Test/CryptoServiceTests.cs index 1edfee16e..54013d7ea 100644 --- a/test/App.Test/CryptoServiceTests.cs +++ b/test/App.Test/CryptoServiceTests.cs @@ -3,6 +3,7 @@ using Bit.App.Abstractions; using Bit.App.Services; using NSubstitute; using Xunit; +using Plugin.Settings.Abstractions; namespace Bit.App.Test { @@ -26,11 +27,12 @@ namespace Bit.App.Test { var storageService = Substitute.For(); var keyService = Substitute.For(); + var settingsService = Substitute.For(); storageService.Contains("key").Returns(true); storageService.Retrieve("key").Returns( Convert.FromBase64String("QpSYI5k0bLQXEygUEHn4wMII3ERatuWDFBszk7JAhbQ=")); - var service = new CryptoService(storageService, keyService); + var service = new CryptoService(settingsService, storageService, keyService); var encryptedValue = service.Encrypt(value); return service.Decrypt(encryptedValue); }