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

429 lines
13 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;
2016-05-02 09:52:09 +03:00
namespace Bit.App.Services
{
public class CryptoService : ICryptoService
{
private const string KeyKey = "key";
private const string PreviousKeyKey = "previousKey";
private const string PrivateKeyKey = "privateKey";
2017-04-20 17:20:24 +03:00
private const string OrgKeysKey = "orgKeys";
2016-05-02 09:52:09 +03:00
private const int InitializationVectorSize = 16;
private readonly ISecureStorageService _secureStorage;
private readonly IKeyDerivationService _keyDerivationService;
private CryptoKey _key;
private CryptoKey _legacyEtmKey;
private CryptoKey _previousKey;
private IDictionary<string, CryptoKey> _orgKeys;
private byte[] _privateKey;
2016-05-02 09:52:09 +03:00
public CryptoService(
ISecureStorageService secureStorage,
IKeyDerivationService keyDerivationService)
2016-05-02 09:52:09 +03:00
{
_secureStorage = secureStorage;
_keyDerivationService = keyDerivationService;
2016-05-02 09:52:09 +03:00
}
public CryptoKey Key
2016-05-02 09:52:09 +03:00
{
get
{
if(_key == null)
2016-05-02 09:52:09 +03:00
{
_key = new CryptoKey(_secureStorage.Retrieve(KeyKey));
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
{
PreviousKey = _key;
2016-05-03 00:50:16 +03:00
_secureStorage.Delete(KeyKey);
_key = null;
_legacyEtmKey = null;
2016-05-03 00:50:16 +03:00
}
}
}
public CryptoKey PreviousKey
{
get
{
if(_previousKey == null)
{
_previousKey = new CryptoKey(_secureStorage.Retrieve(PreviousKeyKey));
}
return _previousKey;
}
private set
{
if(value != null)
{
_secureStorage.Store(PreviousKeyKey, value.Key);
_previousKey = value;
}
}
}
public bool KeyChanged
{
get
{
if(Key == null)
{
2017-02-10 06:06:39 +03:00
return false;
}
if(PreviousKey == null)
{
return Key != null;
}
return !PreviousKey.Key.SequenceEqual(Key.Key);
}
}
public byte[] PrivateKey
{
get
{
if(_privateKey == null)
{
_privateKey = _secureStorage.Retrieve(PrivateKeyKey);
}
return _privateKey;
}
2017-04-20 17:29:18 +03:00
private set
{
if(value != null)
{
_secureStorage.Store(PrivateKeyKey, value);
_privateKey = value;
}
2017-04-20 17:20:24 +03:00
else
{
_secureStorage.Delete(PrivateKeyKey);
_privateKey = null;
}
}
}
public IDictionary<string, CryptoKey> OrgKeys
2017-04-20 17:20:24 +03:00
{
get
{
if(_orgKeys == null && _secureStorage.Contains(OrgKeysKey))
{
var orgKeysDictBytes = _secureStorage.Retrieve(OrgKeysKey);
if(orgKeysDictBytes != null)
{
var orgKeysDictJson = Encoding.UTF8.GetString(orgKeysDictBytes, 0, orgKeysDictBytes.Length);
if(!string.IsNullOrWhiteSpace(orgKeysDictJson))
{
_orgKeys = new Dictionary<string, CryptoKey>();
var orgKeysDict = JsonConvert.DeserializeObject<IDictionary<string, byte[]>>(orgKeysDictJson);
2017-04-20 17:20:24 +03:00
foreach(var item in orgKeysDict)
{
_orgKeys.Add(item.Key, new CryptoKey(item.Value));
}
}
}
}
return _orgKeys;
}
set
{
if(value != null && value.Any())
{
var dict = new Dictionary<string, byte[]>();
2017-04-20 17:20:24 +03:00
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, CryptoKey key)
{
var bytes = DecryptToBytes(privateKeyEnc, key);
PrivateKey = bytes;
}
public CryptoKey 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];
}
public void ClearOrgKey(string orgId)
2017-04-20 17:20:24 +03:00
{
var localOrgKeys = OrgKeys;
if(localOrgKeys == null || !localOrgKeys.ContainsKey(orgId))
{
return;
}
2017-04-20 17:20:24 +03:00
localOrgKeys.Remove(orgId);
// invoke setter
OrgKeys = localOrgKeys;
}
2017-04-20 17:20:24 +03:00
public void ClearKeys()
{
OrgKeys = null;
Key = null;
PrivateKey = null;
}
public CryptoKey AddOrgKey(string orgId, CipherString encOrgKey, byte[] privateKey)
{
try
{
2017-04-20 17:20:24 +03:00
var localOrgKeys = OrgKeys;
var decBytes = RsaDecryptToBytes(encOrgKey, privateKey);
2017-04-20 17:20:24 +03:00
var key = new CryptoKey(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.");
2017-04-20 17:20:24 +03:00
return null;
}
}
public CipherString Encrypt(string plaintextValue, CryptoKey key = null)
2016-05-02 09:52:09 +03:00
{
if(key == null)
2016-05-02 09:52:09 +03:00
{
key = Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
2016-05-02 09:52:09 +03:00
}
if(plaintextValue == null)
{
throw new ArgumentNullException(nameof(plaintextValue));
}
var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength);
var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plaintextBytes, iv);
var mac = key.MacKey != null ? ComputeMac(encryptedBytes, iv, key.MacKey) : null;
2016-12-11 06:05:52 +03:00
return new CipherString(key.EncryptionType, Convert.ToBase64String(iv), Convert.ToBase64String(encryptedBytes), mac);
2016-05-02 09:52:09 +03:00
}
public string Decrypt(CipherString encyptedValue, CryptoKey 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]";
}
}
public byte[] DecryptToBytes(CipherString encyptedValue, CryptoKey key = null)
{
if(key == null)
{
key = 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 == Enums.EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncryptionType == Enums.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 CryptoKey(key.Key, Enums.EncryptionType.AesCbc128_HmacSha256_B64);
2016-08-06 10:10:54 +03:00
}
2016-05-02 09:52:09 +03:00
key = _legacyEtmKey;
}
if(encyptedValue.EncryptionType != key.EncryptionType)
{
throw new ArgumentException("encType unavailable.");
}
if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
{
var computedMac = ComputeMac(encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, key.MacKey);
if(computedMac != encyptedValue.Mac)
{
throw new InvalidOperationException("MAC failed.");
}
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes);
return decryptedBytes;
}
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));
}
if(encyptedValue.EncryptionType != EncryptionType.RsaOaep_Sha256_B64)
{
throw new ArgumentException("encType unavailable.");
}
var provider = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaOaepSha256);
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
}
private string ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
2016-12-11 06:05:52 +03:00
{
if(macKey == null)
2016-12-11 06:05:52 +03:00
{
throw new ArgumentNullException(nameof(macKey));
2016-12-11 06:05:52 +03:00
}
if(ctBytes == null)
{
throw new ArgumentNullException(nameof(ctBytes));
}
if(ivBytes == null)
{
throw new ArgumentNullException(nameof(ivBytes));
}
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
2016-12-11 06:05:52 +03:00
hasher.Append(ivBytes.Concat(ctBytes).ToArray());
var mac = hasher.GetValueAndReset();
return Convert.ToBase64String(mac);
}
public CryptoKey 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);
return new CryptoKey(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
}
public byte[] HashPassword(CryptoKey key, string password)
2016-05-02 09:52:09 +03:00
{
if(key == null)
{
throw new ArgumentNullException(nameof(Key));
}
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
}
public string HashPasswordBase64(CryptoKey key, string password)
2016-05-02 09:52:09 +03:00
{
var hash = HashPassword(key, password);
return Convert.ToBase64String(hash);
}
}
}