bitwarden-android/src/App/Services/CryptoService.cs

470 lines
15 KiB
C#
Raw Normal View History

2016-05-02 09:52:09 +03:00
using System;
using System.Diagnostics;
2016-05-02 09:52:09 +03:00
using System.Text;
using Bit.App.Abstractions;
using Bit.App.Models;
using PCLCrypto;
using System.Linq;
using Bit.App.Enums;
using System.Collections.Generic;
2017-04-20 17:20:24 +03:00
using Newtonsoft.Json;
using Plugin.Settings.Abstractions;
using Bit.App.Models.Api;
using Bit.App.Utilities;
2016-05-02 09:52:09 +03:00
namespace Bit.App.Services
{
public class CryptoService : ICryptoService
{
private const string KeyKey = "key";
private const string PrivateKeyKey = "encPrivateKey";
2017-06-01 05:47:19 +03:00
private const string EncKeyKey = "encKey";
private const string OrgKeysKey = "encOrgKeys";
2016-05-02 09:52:09 +03:00
private const int InitializationVectorSize = 16;
private readonly ISettings _settings;
2016-05-02 09:52:09 +03:00
private readonly ISecureStorageService _secureStorage;
private readonly IKeyDerivationService _keyDerivationService;
2017-04-22 21:36:31 +03:00
private SymmetricCryptoKey _key;
2017-06-01 05:47:19 +03:00
private SymmetricCryptoKey _encKey;
2017-04-22 21:36:31 +03:00
private SymmetricCryptoKey _legacyEtmKey;
private IDictionary<string, SymmetricCryptoKey> _orgKeys;
private byte[] _privateKey;
2016-05-02 09:52:09 +03:00
public CryptoService(
ISettings settings,
ISecureStorageService secureStorage,
IKeyDerivationService keyDerivationService)
2016-05-02 09:52:09 +03:00
{
_settings = settings;
2016-05-02 09:52:09 +03:00
_secureStorage = secureStorage;
_keyDerivationService = keyDerivationService;
2016-05-02 09:52:09 +03:00
}
2017-04-22 21:36:31 +03:00
public SymmetricCryptoKey Key
2016-05-02 09:52:09 +03:00
{
get
{
2017-04-20 21:23:58 +03:00
if(_key == null && _secureStorage.Contains(KeyKey))
2016-05-02 09:52:09 +03:00
{
2017-04-20 21:23:58 +03:00
var keyBytes = _secureStorage.Retrieve(KeyKey);
if(keyBytes != null)
{
2017-04-22 21:36:31 +03:00
_key = new SymmetricCryptoKey(keyBytes);
2017-04-20 21:23:58 +03:00
}
2016-05-02 09:52:09 +03:00
}
return _key;
2016-05-02 09:52:09 +03:00
}
set
{
2016-05-03 00:50:16 +03:00
if(value != null)
{
_secureStorage.Store(KeyKey, value.Key);
2016-05-03 00:50:16 +03:00
}
else
{
_secureStorage.Delete(KeyKey);
}
2017-05-25 19:50:39 +03:00
_key = value;
_legacyEtmKey = null;
2016-05-03 00:50:16 +03:00
}
}
2017-06-01 05:47:19 +03:00
public SymmetricCryptoKey EncKey
{
get
{
2017-06-01 05:47:19 +03:00
if(_encKey == null && _settings.Contains(EncKeyKey))
{
var encKey = _settings.GetValueOrDefault(EncKeyKey, null);
2017-06-01 05:47:19 +03:00
var encKeyCs = new CipherString(encKey);
try
2017-04-20 21:23:58 +03:00
{
2017-06-01 05:47:19 +03:00
var decBytes = DecryptToBytes(encKeyCs, Key);
_encKey = new SymmetricCryptoKey(decBytes);
}
catch
{
_encKey = null;
Debug.WriteLine($"Cannot set enc key. Decryption failed.");
2017-04-20 21:23:58 +03:00
}
}
2017-06-01 05:47:19 +03:00
return _encKey;
}
}
public byte[] PrivateKey
{
get
{
if(_privateKey == null && _settings.Contains(PrivateKeyKey))
{
var encPrivateKey = _settings.GetValueOrDefault(PrivateKeyKey, null);
var encPrivateKeyCs = new CipherString(encPrivateKey);
try
{
_privateKey = DecryptToBytes(encPrivateKeyCs);
}
catch
{
_privateKey = null;
Debug.WriteLine($"Cannot set private key. Decryption failed.");
}
}
return _privateKey;
}
2017-04-20 17:20:24 +03:00
}
2017-04-22 21:36:31 +03:00
public IDictionary<string, SymmetricCryptoKey> OrgKeys
2017-04-20 17:20:24 +03:00
{
get
{
if((!_orgKeys?.Any() ?? true) && _settings.Contains(OrgKeysKey))
2017-04-20 17:20:24 +03:00
{
var orgKeysEncDictJson = _settings.GetValueOrDefault(OrgKeysKey, null);
if(!string.IsNullOrWhiteSpace(orgKeysEncDictJson))
2017-04-20 17:20:24 +03:00
{
_orgKeys = new Dictionary<string, SymmetricCryptoKey>();
var orgKeysDict = JsonConvert.DeserializeObject<IDictionary<string, string>>(orgKeysEncDictJson);
foreach(var item in orgKeysDict)
2017-04-20 17:20:24 +03:00
{
try
{
var orgKeyCs = new CipherString(item.Value);
var decOrgKeyBytes = RsaDecryptToBytes(orgKeyCs, PrivateKey);
_orgKeys.Add(item.Key, new SymmetricCryptoKey(decOrgKeyBytes));
}
catch
2017-04-20 17:20:24 +03:00
{
Debug.WriteLine($"Cannot set org key {item.Key}. Decryption failed.");
2017-04-20 17:20:24 +03:00
}
}
}
}
return _orgKeys;
}
}
2017-06-01 05:47:19 +03:00
public void SetEncKey(CipherString encKeyEnc)
{
if(encKeyEnc != null)
{
_settings.AddOrUpdateValue(EncKeyKey, encKeyEnc.EncryptedString);
}
else if(_settings.Contains(EncKeyKey))
{
_settings.Remove(EncKeyKey);
}
_encKey = null;
}
public void SetPrivateKey(CipherString privateKeyEnc)
{
if(privateKeyEnc != null)
2017-04-20 17:20:24 +03:00
{
_settings.AddOrUpdateValue(PrivateKeyKey, privateKeyEnc.EncryptedString);
}
else if(_settings.Contains(PrivateKeyKey))
{
_settings.Remove(PrivateKeyKey);
}
2017-06-01 05:47:19 +03:00
_privateKey = null;
}
2017-04-20 17:20:24 +03:00
public void SetOrgKeys(ProfileResponse profile)
{
var orgKeysEncDict = new Dictionary<string, string>();
if(profile?.Organizations?.Any() ?? false)
{
foreach(var org in profile.Organizations)
2017-04-20 17:20:24 +03:00
{
orgKeysEncDict.Add(org.Id, org.Key);
2017-04-20 17:20:24 +03:00
}
}
SetOrgKeys(orgKeysEncDict);
}
public void SetOrgKeys(Dictionary<string, string> orgKeysEncDict)
{
if(orgKeysEncDict?.Any() ?? false)
{
var dictJson = JsonConvert.SerializeObject(orgKeysEncDict);
_settings.AddOrUpdateValue(OrgKeysKey, dictJson);
}
else if(_settings.Contains(OrgKeysKey))
{
_settings.Remove(OrgKeysKey);
}
2017-08-31 05:15:10 +03:00
_orgKeys = null;
}
2017-04-22 21:36:31 +03:00
public SymmetricCryptoKey GetOrgKey(string orgId)
{
2017-04-20 17:20:24 +03:00
if(OrgKeys == null || !OrgKeys.ContainsKey(orgId))
{
2017-04-20 17:20:24 +03:00
return null;
}
return OrgKeys[orgId];
}
2017-04-20 17:20:24 +03:00
public void ClearKeys()
{
SetOrgKeys((Dictionary<string, string>)null);
2017-04-20 17:20:24 +03:00
Key = null;
SetPrivateKey(null);
2017-06-01 05:47:19 +03:00
SetEncKey(null);
}
2017-04-22 21:36:31 +03:00
public CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null)
2017-06-01 05:47:19 +03:00
{
if(plaintextValue == null)
{
throw new ArgumentNullException(nameof(plaintextValue));
}
var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
return Encrypt(plaintextBytes, key);
}
public CipherString Encrypt(byte[] plainBytes, SymmetricCryptoKey key = null)
2016-05-02 09:52:09 +03:00
{
if(key == null)
2016-05-02 09:52:09 +03:00
{
2017-06-01 05:47:19 +03:00
key = EncKey ?? Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
2016-05-02 09:52:09 +03:00
}
2017-06-01 05:47:19 +03:00
if(plainBytes == null)
2016-05-02 09:52:09 +03:00
{
2017-06-01 05:47:19 +03:00
throw new ArgumentNullException(nameof(plainBytes));
2016-05-02 09:52:09 +03:00
}
return Crypto.AesCbcEncrypt(plainBytes, key);
2016-05-02 09:52:09 +03:00
}
2017-07-22 22:38:08 +03:00
public byte[] EncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key = null)
{
if(key == null)
{
key = EncKey ?? Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(plainBytes == null)
{
throw new ArgumentNullException(nameof(plainBytes));
}
return Crypto.AesCbcEncryptToBytes(plainBytes, key);
}
2017-04-22 21:36:31 +03:00
public string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null)
2016-05-02 09:52:09 +03:00
{
2016-08-06 10:10:54 +03:00
try
2016-05-02 09:52:09 +03:00
{
var bytes = DecryptToBytes(encyptedValue, key);
return Encoding.UTF8.GetString(bytes, 0, bytes.Length).TrimEnd('\0');
}
catch(Exception e)
{
Debug.WriteLine("Could not decrypt '{0}'. {1}", encyptedValue, e.Message);
return "[error: cannot decrypt]";
}
}
2017-04-22 21:36:31 +03:00
public byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null)
{
if(key == null)
{
2017-06-01 05:47:19 +03:00
key = EncKey ?? Key;
}
2016-08-06 10:10:54 +03:00
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
if(encyptedValue.EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncryptionType == EncryptionType.AesCbc256_B64)
{
// Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null)
2016-08-06 10:10:54 +03:00
{
_legacyEtmKey = new SymmetricCryptoKey(key.Key, EncryptionType.AesCbc128_HmacSha256_B64);
2016-08-06 10:10:54 +03:00
}
2016-05-02 09:52:09 +03:00
key = _legacyEtmKey;
}
return Crypto.AesCbcDecrypt(encyptedValue, key);
}
public byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null)
{
if(key == null)
{
key = EncKey ?? Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null || encyptedValue.Length == 0)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
byte[] ct, iv, mac = null;
var encType = (EncryptionType)encyptedValue[0];
switch(encType)
{
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if(encyptedValue.Length <= 49)
{
throw new InvalidOperationException("Invalid value length.");
}
iv = new ArraySegment<byte>(encyptedValue, 1, 16).ToArray();
mac = new ArraySegment<byte>(encyptedValue, 17, 32).ToArray();
ct = new ArraySegment<byte>(encyptedValue, 49, encyptedValue.Length - 49).ToArray();
break;
case EncryptionType.AesCbc256_B64:
if(encyptedValue.Length <= 17)
{
throw new InvalidOperationException("Invalid value length.");
}
iv = new ArraySegment<byte>(encyptedValue, 1, 16).ToArray();
ct = new ArraySegment<byte>(encyptedValue, 17, encyptedValue.Length - 17).ToArray();
break;
default:
throw new InvalidOperationException("Invalid encryption type.");
}
return Crypto.AesCbcDecrypt(encType, ct, iv, mac, key);
}
2016-05-02 09:52:09 +03:00
public byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey)
{
if(privateKey == null)
{
privateKey = PrivateKey;
}
if(privateKey == null)
{
throw new ArgumentNullException(nameof(privateKey));
}
2017-06-19 18:57:37 +03:00
if(EncKey?.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
{
var computedMacBytes = Crypto.ComputeMac(encyptedValue.CipherTextBytes, EncKey.MacKey);
if(!Crypto.MacsEqual(EncKey.MacKey, computedMacBytes, encyptedValue.MacBytes))
{
throw new InvalidOperationException("MAC failed.");
}
}
2017-04-21 20:40:29 +03:00
IAsymmetricKeyAlgorithmProvider provider = null;
switch(encyptedValue.EncryptionType)
{
2017-04-21 20:40:29 +03:00
case EncryptionType.Rsa2048_OaepSha256_B64:
2017-06-19 18:57:37 +03:00
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
2017-04-21 20:40:29 +03:00
provider = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaOaepSha256);
break;
case EncryptionType.Rsa2048_OaepSha1_B64:
2017-06-19 18:57:37 +03:00
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
2017-04-21 20:40:29 +03:00
provider = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaOaepSha1);
break;
default:
throw new ArgumentException("EncryptionType unavailable.");
}
var cryptoKey = provider.ImportKeyPair(privateKey, CryptographicPrivateKeyBlobType.Pkcs8RawPrivateKeyInfo);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes);
return decryptedBytes;
2016-05-02 09:52:09 +03:00
}
2017-04-22 21:36:31 +03:00
public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt)
2016-05-02 09:52:09 +03:00
{
if(password == null)
{
throw new ArgumentNullException(nameof(password));
}
if(salt == null)
{
throw new ArgumentNullException(nameof(salt));
}
var passwordBytes = Encoding.UTF8.GetBytes(password);
var saltBytes = Encoding.UTF8.GetBytes(salt);
var keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000);
2017-04-22 21:36:31 +03:00
return new SymmetricCryptoKey(keyBytes);
2016-05-02 09:52:09 +03:00
}
public string MakeKeyFromPasswordBase64(string password, string salt)
{
var key = MakeKeyFromPassword(password, salt);
return Convert.ToBase64String(key.Key);
2016-05-02 09:52:09 +03:00
}
2017-04-22 21:36:31 +03:00
public byte[] HashPassword(SymmetricCryptoKey key, string password)
2016-05-02 09:52:09 +03:00
{
if(key == null)
{
2017-06-01 05:47:19 +03:00
throw new ArgumentNullException(nameof(key));
2016-05-02 09:52:09 +03:00
}
if(password == null)
{
throw new ArgumentNullException(nameof(password));
}
var passwordBytes = Encoding.UTF8.GetBytes(password);
var hash = _keyDerivationService.DeriveKey(key.Key, passwordBytes, 1);
return hash;
2016-05-02 09:52:09 +03:00
}
2017-04-22 21:36:31 +03:00
public string HashPasswordBase64(SymmetricCryptoKey key, string password)
2016-05-02 09:52:09 +03:00
{
var hash = HashPassword(key, password);
return Convert.ToBase64String(hash);
}
2017-06-01 05:47:19 +03:00
public CipherString MakeEncKey(SymmetricCryptoKey key)
{
var bytes = Crypto.RandomBytes(512 / 8);
2017-06-01 05:47:19 +03:00
return Encrypt(bytes, key);
}
2016-05-02 09:52:09 +03:00
}
}