using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Domain; using Bit.Core.Models.Response; using Bit.Core.Utilities; namespace Bit.Core.Services { public class CryptoService : ICryptoService { private const string RANDOM_STRING_CHARSET = "abcdefghijklmnopqrstuvwxyz1234567890"; private readonly IStateService _stateService; private readonly ICryptoFunctionService _cryptoFunctionService; private SymmetricCryptoKey _legacyEtmKey; private string _masterKeyHash; private byte[] _publicKey; private byte[] _privateKey; private Dictionary _orgKeys; private Task> _getOrgKeysTask; public CryptoService( IStateService stateService, ICryptoFunctionService cryptoFunctionService) { _stateService = stateService; _cryptoFunctionService = cryptoFunctionService; } public void ClearCache() { _legacyEtmKey = null; _masterKeyHash = null; _publicKey = null; _privateKey = null; _orgKeys = null; } public async Task RefreshKeysAsync() { // Refresh or clear additional keys such as // pin and auto unlock keys await SetUserKeyAsync(await GetUserKeyAsync()); } public async Task SetUserKeyAsync(UserKey userKey, string userId = null) { await _stateService.SetUserKeyAsync(userKey, userId); await StoreAdditionalKeysAsync(userKey, userId); } public Task GetUserKeyAsync(string userId = null) { return _stateService.GetUserKeyAsync(userId); } public async Task IsLegacyUserAsync(MasterKey masterKey = null, string userId = null) { masterKey ??= await GetMasterKeyAsync(userId); if (masterKey == null) { return false; } return await ValidateUserKeyAsync(new UserKey(masterKey.Key)); } public async Task GetUserKeyWithLegacySupportAsync(string userId = null) { var userKey = await GetUserKeyAsync(userId); 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 new UserKey((await GetMasterKeyAsync(userId)).Key); } public async Task HasUserKeyAsync(string userId = null) { return await GetUserKeyAsync(userId) != null; } public async Task HasEncryptedUserKeyAsync(string userId = null) { return await _stateService.GetMasterKeyEncryptedUserKeyAsync(userId) != null; } public async Task MakeUserKeyAsync() { return new UserKey(await _cryptoFunctionService.RandomBytesAsync(64)); } public Task ClearUserKeyAsync(string userId = null) { return _stateService.SetUserKeyAsync(null, userId); } public Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null) { return _stateService.SetMasterKeyEncryptedUserKeyAsync(value, userId); } public async Task GetAutoUnlockKeyAsync(string userId = null) { await MigrateAutoAndBioKeysIfNeededAsync(userId); return await _stateService.GetUserKeyAutoUnlockAsync(userId); } public async Task HasAutoUnlockKeyAsync(string userId = null) { return await GetAutoUnlockKeyAsync(userId) != null; } public async Task GetBiometricUnlockKeyAsync(string userId = null) { await MigrateAutoAndBioKeysIfNeededAsync(userId); return await _stateService.GetUserKeyBiometricUnlockAsync(userId); } public Task SetMasterKeyAsync(MasterKey masterKey, string userId = null) { return _stateService.SetMasterKeyAsync(masterKey, userId); } public async Task GetMasterKeyAsync(string userId = null) { var masterKey = await _stateService.GetMasterKeyAsync(userId); if (masterKey == null) { var masterKeyDecrypted = await _stateService.GetKeyDecryptedAsync(userId); if (masterKeyDecrypted == null) { return null; } // Migration support masterKey = new MasterKey(masterKeyDecrypted.Key); if (masterKey != null) { await SetMasterKeyAsync(masterKey, userId); } } return masterKey; } public Task MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig) { return MakeKeyAsync(password, email, kdfConfig, keyBytes => new MasterKey(keyBytes)); } public Task ClearMasterKeyAsync(string userId = null) { return _stateService.SetMasterKeyAsync(null, userId); } public async Task> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey, UserKey userKey = null) { userKey ??= await GetUserKeyAsync(); if (userKey == null) { throw new UserKeyNullException(); } return await BuildProtectedSymmetricKeyAsync(masterKey, userKey.Key, keyBytes => new UserKey(keyBytes)); } public async Task DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null) { masterKey ??= await GetMasterKeyAsync(userId); if (masterKey == null) { throw new MasterKeyNullException(); } if (encUserKey == null) { var userKeyMasterKey = await _stateService.GetMasterKeyEncryptedUserKeyAsync(userId); if (userKeyMasterKey is null) { // Migrate old key var oldEncUserKey = await _stateService.GetEncKeyEncryptedAsync(userId); if (oldEncUserKey is null) { throw new Exception("No encrypted user key nor old encKeyEncrypted found"); } var userKey = await DecryptUserKeyWithMasterKeyAsync( masterKey, new EncString(oldEncUserKey), userId ); await SetMasterKeyEncryptedUserKeyAsync(oldEncUserKey, userId); await _stateService.SetEncKeyEncryptedAsync(null, userId); return userKey; } encUserKey = new EncString(userKeyMasterKey); } byte[] decUserKey = null; if (encUserKey.EncryptionType == EncryptionType.AesCbc256_B64) { decUserKey = await DecryptToBytesAsync(encUserKey, masterKey); } else if (encUserKey.EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64) { var newKey = await StretchKeyAsync(masterKey, keyBytes => new MasterKey(keyBytes)); decUserKey = await DecryptToBytesAsync(encUserKey, newKey); } else { throw new Exception($"Unsupported encrypted user key type: {encUserKey.EncryptionType}"); } if (decUserKey == null) { return null; } return new UserKey(decUserKey); } public async Task> MakeDataEncKeyAsync(SymmetricCryptoKey key) { if (key is null) { throw new ArgumentNullException(nameof(key)); } if (!(key is UserKey) && !(key is OrgKey) && !(key is CipherKey)) { throw new ArgumentException($"Data encryption keys must be of type UserKey or OrgKey or CipherKey. {key.GetType().FullName} unsupported."); } var newSymKey = await _cryptoFunctionService.RandomBytesAsync(64); return await BuildProtectedSymmetricKeyAsync(key, newSymKey, keyBytes => new SymmetricCryptoKey(keyBytes)); } public async Task HashMasterKeyAsync(string password, MasterKey masterKey, HashPurpose hashPurpose = HashPurpose.ServerAuthorization) { if (password is null) { throw new ArgumentNullException(nameof(password)); } if (masterKey is null) { masterKey = await GetMasterKeyAsync(); if (masterKey is null) { throw new ArgumentNullException(nameof(masterKey)); } } var hash = await _cryptoFunctionService.Pbkdf2Async(masterKey.Key, password, CryptoHashAlgorithm.Sha256, (int)hashPurpose); return Convert.ToBase64String(hash); } public Task SetMasterKeyHashAsync(string keyHash) { _masterKeyHash = keyHash; return _stateService.SetKeyHashAsync(keyHash); } public async Task GetMasterKeyHashAsync() { if (_masterKeyHash != null) { return _masterKeyHash; } var passwordHash = await _stateService.GetKeyHashAsync(); if (passwordHash != null) { _masterKeyHash = passwordHash; } return _masterKeyHash; } public Task ClearMasterKeyHashAsync(string userId = null) { _masterKeyHash = null; return _stateService.SetKeyHashAsync(null, userId); } public async Task CompareAndUpdateKeyHashAsync(string masterPassword, MasterKey key) { var storedPasswordHash = await GetMasterKeyHashAsync(); if (masterPassword != null && storedPasswordHash != null) { var localPasswordHash = await HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization); if (localPasswordHash != null && storedPasswordHash == localPasswordHash) { return true; } var serverPasswordHash = await HashMasterKeyAsync(masterPassword, key, HashPurpose.ServerAuthorization); if (serverPasswordHash != null && storedPasswordHash == serverPasswordHash) { await SetMasterKeyHashAsync(localPasswordHash); return true; } } return false; } public Task SetOrgKeysAsync(IEnumerable orgs) { var orgKeys = orgs.ToDictionary(org => org.Id, org => org.Key); _orgKeys = null; return _stateService.SetOrgKeysEncryptedAsync(orgKeys); } public async Task GetOrgKeyAsync(string orgId) { if (string.IsNullOrWhiteSpace(orgId)) { return null; } var orgKeys = await GetOrgKeysAsync(); if (orgKeys == null || !orgKeys.ContainsKey(orgId)) { return null; } return orgKeys[orgId]; } public Task> GetOrgKeysAsync() { if (_orgKeys != null && _orgKeys.Count > 0) { return Task.FromResult(_orgKeys); } if (_getOrgKeysTask != null && !_getOrgKeysTask.IsCompleted && !_getOrgKeysTask.IsFaulted) { return _getOrgKeysTask; } async Task> doTask() { try { var encOrgKeys = await _stateService.GetOrgKeysEncryptedAsync(); if (encOrgKeys == null) { return null; } var orgKeys = new Dictionary(); var setKey = false; foreach (var org in encOrgKeys) { var decValue = await RsaDecryptAsync(org.Value); orgKeys.Add(org.Key, new OrgKey(decValue)); setKey = true; } if (setKey) { _orgKeys = orgKeys; } return _orgKeys; } finally { _getOrgKeysTask = null; } } _getOrgKeysTask = doTask(); return _getOrgKeysTask; } public async Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null) { _orgKeys = null; if (!memoryOnly) { await _stateService.SetOrgKeysEncryptedAsync(null, userId); } } public async Task GetUserPublicKeyAsync() { if (_publicKey != null) { return _publicKey; } var privateKey = await GetUserPrivateKeyAsync(); if (privateKey == null) { return null; } _publicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey); return _publicKey; } public async Task SetUserPrivateKeyAsync(string encPrivateKey) { if (encPrivateKey == null) { return; } await _stateService.SetPrivateKeyEncryptedAsync(encPrivateKey); _privateKey = null; } public async Task GetUserPrivateKeyAsync() { if (_privateKey != null) { return _privateKey; } var encPrivateKey = await _stateService.GetPrivateKeyEncryptedAsync(); if (encPrivateKey == null) { return null; } _privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), null); return _privateKey; } public async Task> GetFingerprintAsync(string userId, byte[] publicKey = null) { if (publicKey == null) { publicKey = await GetUserPublicKeyAsync(); } if (publicKey == null) { throw new Exception("No public key available."); } var keyFingerprint = await _cryptoFunctionService.HashAsync(publicKey, CryptoHashAlgorithm.Sha256); var userFingerprint = await _cryptoFunctionService.HkdfExpandAsync(keyFingerprint, Encoding.UTF8.GetBytes(userId), 32, HkdfAlgorithm.Sha256); return HashPhrase(userFingerprint); } public async Task> MakeKeyPairAsync(SymmetricCryptoKey key = null) { var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048); var publicB64 = Convert.ToBase64String(keyPair.Item1); var privateEnc = await EncryptAsync(keyPair.Item2, key); return new Tuple(publicB64, privateEnc); } public async Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null) { _publicKey = _privateKey = null; if (!memoryOnly) { await _stateService.SetPrivateKeyEncryptedAsync(null, userId); } } public async Task MakePinKeyAsync(string pin, string salt, KdfConfig config) { var pinKey = await MakeKeyAsync(pin, salt, config, keyBytes => new PinKey(keyBytes)); return await StretchKeyAsync(pinKey, keyBytes => new PinKey(keyBytes)); } public Task ClearPinKeysAsync(string userId = null) { return Task.WhenAll( _stateService.SetPinKeyEncryptedUserKeyAsync(null, userId), _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(null, userId), _stateService.SetProtectedPinAsync(null, userId), ClearDeprecatedPinKeysAsync(userId)); } public async Task DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null) { pinProtectedUserKey ??= await _stateService.GetPinKeyEncryptedUserKeyAsync(); pinProtectedUserKey ??= await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync(); if (pinProtectedUserKey == null) { throw new Exception("No PIN protected user key found."); } var pinKey = await MakePinKeyAsync(pin, salt, kdfConfig); var userKeyBytes = await DecryptToBytesAsync(pinProtectedUserKey, pinKey); return new UserKey(userKeyBytes); } // Only for migration purposes public async Task DecryptMasterKeyWithPinAsync( string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedMasterKey = null) { if (pinProtectedMasterKey == null) { var pinProtectedMasterKeyString = await _stateService.GetPinProtectedAsync(); if (pinProtectedMasterKeyString == null) { throw new Exception("No PIN protected master key found."); } pinProtectedMasterKey = new EncString(pinProtectedMasterKeyString); } var pinKey = await MakePinKeyAsync(pin, salt, kdfConfig); var masterKey = await DecryptToBytesAsync(pinProtectedMasterKey, pinKey); return new MasterKey(masterKey); } public async Task MakeSendKeyAsync(byte[] keyMaterial) { var sendKey = await _cryptoFunctionService.HkdfAsync(keyMaterial, "bitwarden-send", "send", 64, HkdfAlgorithm.Sha256); return new SymmetricCryptoKey(sendKey); } public async Task RsaEncryptAsync(byte[] data, byte[] publicKey = null) { if (publicKey == null) { publicKey = await GetUserPublicKeyAsync(); } if (publicKey == null) { throw new Exception("Public key unavailable."); } var encBytes = await _cryptoFunctionService.RsaEncryptAsync(data, publicKey, CryptoHashAlgorithm.Sha1); return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Convert.ToBase64String(encBytes)); } public async Task RsaDecryptAsync(string encValue, byte[] privateKey = null) { var headerPieces = encValue.Split('.'); EncryptionType? encType = null; string[] encPieces = null; if (headerPieces.Length == 1) { encType = EncryptionType.Rsa2048_OaepSha256_B64; encPieces = new string[] { headerPieces[0] }; } else if (headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out EncryptionType type)) { encType = type; encPieces = headerPieces[1].Split('|'); } if (!encType.HasValue) { throw new Exception("encType unavailable."); } if (encPieces == null || encPieces.Length == 0) { throw new Exception("encPieces unavailable."); } var data = Convert.FromBase64String(encPieces[0]); if (privateKey is null) { privateKey = await GetUserPrivateKeyAsync(); } if (privateKey == null) { throw new Exception("No private key."); } var alg = CryptoHashAlgorithm.Sha1; switch (encType.Value) { case EncryptionType.Rsa2048_OaepSha256_B64: case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: alg = CryptoHashAlgorithm.Sha256; break; case EncryptionType.Rsa2048_OaepSha1_B64: case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: break; default: throw new Exception("encType unavailable."); } return await _cryptoFunctionService.RsaDecryptAsync(data, privateKey, alg); } public async Task RandomNumberAsync(int min, int max) { // Make max inclusive max = max + 1; var diff = (long)max - min; var upperBound = uint.MaxValue / diff * diff; uint ui; do { ui = await _cryptoFunctionService.RandomNumberAsync(); } while (ui >= upperBound); return (int)(min + (ui % diff)); } /// /// Makes random string with length based on the charset /// public async Task RandomStringAsync(int length) { var sb = new StringBuilder(); for (var i = 0; i < length; i++) { var randomCharIndex = await RandomNumberAsync(0, RANDOM_STRING_CHARSET.Length - 1); sb.Append(RANDOM_STRING_CHARSET[randomCharIndex]); } return sb.ToString(); } // TODO: The following operations should be moved to a new encrypt service public async Task DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key) { if (encBytes == null) { throw new Exception("no encBytes."); } var encType = (EncryptionType)encBytes[0]; byte[] ctBytes = null; byte[] ivBytes = null; byte[] macBytes = null; switch (encType) { case EncryptionType.AesCbc128_HmacSha256_B64: case EncryptionType.AesCbc256_HmacSha256_B64: if (encBytes.Length < 49) // 1 + 16 + 32 + ctLength { return null; } ivBytes = new ArraySegment(encBytes, 1, 16).ToArray(); macBytes = new ArraySegment(encBytes, 17, 32).ToArray(); ctBytes = new ArraySegment(encBytes, 49, encBytes.Length - 49).ToArray(); break; case EncryptionType.AesCbc256_B64: if (encBytes.Length < 17) // 1 + 16 + ctLength { return null; } ivBytes = new ArraySegment(encBytes, 1, 16).ToArray(); ctBytes = new ArraySegment(encBytes, 17, encBytes.Length - 17).ToArray(); break; default: return null; } return await AesDecryptToBytesAsync(encType, ctBytes, ivBytes, macBytes, key); } public Task DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null) { var iv = Convert.FromBase64String(encString.Iv); var data = Convert.FromBase64String(encString.Data); var mac = !string.IsNullOrWhiteSpace(encString.Mac) ? Convert.FromBase64String(encString.Mac) : null; return AesDecryptToBytesAsync(encString.EncryptionType, data, iv, mac, key); } public Task DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null) { return AesDecryptToUtf8Async(encString.EncryptionType, encString.Data, encString.Iv, encString.Mac, key); } public async Task EncryptAsync(string plainValue, SymmetricCryptoKey key = null) { if (plainValue == null) { return null; } return await EncryptAsync(Encoding.UTF8.GetBytes(plainValue), key); } public async Task EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null) { if (plainValue == null) { return null; } var encObj = await AesEncryptAsync(plainValue, key); var iv = Convert.ToBase64String(encObj.Iv); var data = Convert.ToBase64String(encObj.Data); var mac = encObj.Mac != null ? Convert.ToBase64String(encObj.Mac) : null; return new EncString(encObj.Key.EncType, data, iv, mac); } public async Task EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null) { var encValue = await AesEncryptAsync(plainValue, key); var macLen = 0; if (encValue.Mac != null) { macLen = encValue.Mac.Length; } var encBytes = new byte[1 + encValue.Iv.Length + macLen + encValue.Data.Length]; Buffer.BlockCopy(new byte[] { (byte)encValue.Key.EncType }, 0, encBytes, 0, 1); Buffer.BlockCopy(encValue.Iv, 0, encBytes, 1, encValue.Iv.Length); if (encValue.Mac != null) { Buffer.BlockCopy(encValue.Mac, 0, encBytes, 1 + encValue.Iv.Length, encValue.Mac.Length); } Buffer.BlockCopy(encValue.Data, 0, encBytes, 1 + encValue.Iv.Length + macLen, encValue.Data.Length); return new EncByteArray(encBytes); } public async Task GetOrDeriveMasterKeyAsync(string password, string userId = null) { var masterKey = await GetMasterKeyAsync(userId); return masterKey ?? await this.MakeMasterKeyAsync( password, await _stateService.GetEmailAsync(userId), await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile))); } // --HELPER METHODS-- private async Task StoreAdditionalKeysAsync(UserKey userKey, string userId = null) { // Set, refresh, or clear the pin key if (await _stateService.GetProtectedPinAsync(userId) != null) { await UpdatePinKeyAsync(userKey, userId); } else { await _stateService.SetPinKeyEncryptedUserKeyAsync(null, userId); await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(null, userId); } // Set, refresh, or clear the auto unlock key if (await _stateService.GetVaultTimeoutAsync(userId) == null) { await _stateService.SetUserKeyAutoUnlockAsync(userKey, userId); } else { await _stateService.SetUserKeyAutoUnlockAsync(null, userId); } // Set, refresh, or clear the biometric unlock key if (await _stateService.GetBiometricUnlockAsync(userId) is true) { await _stateService.SetUserKeyBiometricUnlockAsync(userKey, userId); } else { await _stateService.SetUserKeyBiometricUnlockAsync(null, userId); } } private async Task UpdatePinKeyAsync(UserKey userKey, string userId = null) { var pin = await DecryptToUtf8Async(new EncString(await _stateService.GetProtectedPinAsync(userId))); var pinKey = await MakePinKeyAsync( pin, await _stateService.GetEmailAsync(userId), await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)) ); var encPin = await EncryptAsync(userKey.Key, pinKey); if (await _stateService.GetPinKeyEncryptedUserKeyAsync(userId) != null) { await _stateService.SetPinKeyEncryptedUserKeyAsync(encPin, userId); return; } await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(encPin, userId); } private async Task AesEncryptAsync(byte[] data, SymmetricCryptoKey key) { var obj = new EncryptedObject { Key = key ?? await GetUserKeyWithLegacySupportAsync(), Iv = await _cryptoFunctionService.RandomBytesAsync(16) }; obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey); if (obj.Key.MacKey != null) { var macData = new byte[obj.Iv.Length + obj.Data.Length]; Buffer.BlockCopy(obj.Iv, 0, macData, 0, obj.Iv.Length); Buffer.BlockCopy(obj.Data, 0, macData, obj.Iv.Length, obj.Data.Length); obj.Mac = await _cryptoFunctionService.HmacAsync(macData, obj.Key.MacKey, CryptoHashAlgorithm.Sha256); } return obj; } private async Task AesDecryptToUtf8Async(EncryptionType encType, string data, string iv, string mac, SymmetricCryptoKey key) { var keyForEnc = key ?? await GetUserKeyWithLegacySupportAsync(); var theKey = ResolveLegacyKey(encType, keyForEnc); if (theKey.MacKey != null && mac == null) { // Mac required. return null; } if (theKey.EncType != encType) { // encType unavailable. return null; } // "Fast params" conversion var encKey = theKey.EncKey; var dataBytes = Convert.FromBase64String(data); var ivBytes = Convert.FromBase64String(iv); var macDataBytes = new byte[ivBytes.Length + dataBytes.Length]; Buffer.BlockCopy(ivBytes, 0, macDataBytes, 0, ivBytes.Length); Buffer.BlockCopy(dataBytes, 0, macDataBytes, ivBytes.Length, dataBytes.Length); byte[] macKey = null; if (theKey.MacKey != null) { macKey = theKey.MacKey; } byte[] macBytes = null; if (mac != null) { macBytes = Convert.FromBase64String(mac); } // Compute mac if (macKey != null && macBytes != null) { var computedMac = await _cryptoFunctionService.HmacAsync(macDataBytes, macKey, CryptoHashAlgorithm.Sha256); var macsEqual = await _cryptoFunctionService.CompareAsync(macBytes, computedMac); if (!macsEqual) { // Mac failed return null; } } var decBytes = await _cryptoFunctionService.AesDecryptAsync(dataBytes, ivBytes, encKey); return Encoding.UTF8.GetString(decBytes); } private async Task AesDecryptToBytesAsync(EncryptionType encType, byte[] data, byte[] iv, byte[] mac, SymmetricCryptoKey key) { var keyForEnc = key ?? await GetUserKeyWithLegacySupportAsync(); var theKey = ResolveLegacyKey(encType, keyForEnc); if (theKey.MacKey != null && mac == null) { // Mac required. return null; } if (theKey.EncType != encType) { // encType unavailable. return null; } // Compute mac if (theKey.MacKey != null && mac != null) { var macData = new byte[iv.Length + data.Length]; Buffer.BlockCopy(iv, 0, macData, 0, iv.Length); Buffer.BlockCopy(data, 0, macData, iv.Length, data.Length); var computedMac = await _cryptoFunctionService.HmacAsync(macData, theKey.MacKey, CryptoHashAlgorithm.Sha256); if (computedMac == null) { return null; } var macsMatch = await _cryptoFunctionService.CompareAsync(mac, computedMac); if (!macsMatch) { // Mac failed return null; } } return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey); } private SymmetricCryptoKey ResolveLegacyKey(EncryptionType encKey, SymmetricCryptoKey key) { if (encKey == EncryptionType.AesCbc128_HmacSha256_B64 && key.EncType == EncryptionType.AesCbc256_B64) { // Old encrypt-then-mac scheme, make a new key if (_legacyEtmKey == null) { _legacyEtmKey = new SymmetricCryptoKey(key.Key, EncryptionType.AesCbc128_HmacSha256_B64); } return _legacyEtmKey; } return key; } // TODO: This needs to be moved into SymmetricCryptoKey model to remove the keyCreator hack private async Task StretchKeyAsync(SymmetricCryptoKey key, Func keyCreator) where TKey : SymmetricCryptoKey { var newKey = new byte[64]; var enc = await _cryptoFunctionService.HkdfExpandAsync(key.Key, Encoding.UTF8.GetBytes("enc"), 32, HkdfAlgorithm.Sha256); Buffer.BlockCopy(enc, 0, newKey, 0, 32); var mac = await _cryptoFunctionService.HkdfExpandAsync(key.Key, Encoding.UTF8.GetBytes("mac"), 32, HkdfAlgorithm.Sha256); Buffer.BlockCopy(mac, 0, newKey, 32, 32); return keyCreator(newKey); } private List HashPhrase(byte[] hash, int minimumEntropy = 64) { var wordLength = EEFLongWordList.Instance.List.Count; var entropyPerWord = Math.Log(wordLength) / Math.Log(2); var numWords = (int)Math.Ceiling(minimumEntropy / entropyPerWord); var entropyAvailable = hash.Length * 4; if (numWords * entropyPerWord > entropyAvailable) { throw new Exception("Output entropy of hash function is too small"); } var phrase = new List(); var hashHex = string.Concat("0", BitConverter.ToString(hash).Replace("-", "")); var hashNumber = BigInteger.Parse(hashHex, System.Globalization.NumberStyles.HexNumber); while (numWords-- > 0) { var remainder = (int)(hashNumber % wordLength); hashNumber = hashNumber / wordLength; phrase.Add(EEFLongWordList.Instance.List[remainder]); } return phrase; } // TODO: This needs to be moved into SymmetricCryptoKey model to remove the keyCreator hack private async Task> BuildProtectedSymmetricKeyAsync(SymmetricCryptoKey key, byte[] encKey, Func keyCreator) where TKey : SymmetricCryptoKey { EncString encKeyEnc = null; if (key.Key.Length == 32) { var newKey = await StretchKeyAsync(key, keyCreator); encKeyEnc = await EncryptAsync(encKey, newKey); } else if (key.Key.Length == 64) { encKeyEnc = await EncryptAsync(encKey, key); } else { throw new Exception("Invalid key size."); } return new Tuple(keyCreator(encKey), encKeyEnc); } // TODO: This needs to be moved into SymmetricCryptoKey model to remove the keyCreator hack private async Task MakeKeyAsync(string password, string salt, KdfConfig kdfConfig, Func keyCreator) where TKey : SymmetricCryptoKey { byte[] key; if (kdfConfig.Type == null || kdfConfig.Type == KdfType.PBKDF2_SHA256) { var iterations = kdfConfig.Iterations.GetValueOrDefault(5000); if (iterations < 5000) { throw new Exception("PBKDF2 iteration minimum is 5000."); } key = await _cryptoFunctionService.Pbkdf2Async(password, salt, CryptoHashAlgorithm.Sha256, iterations); } else if (kdfConfig.Type == KdfType.Argon2id) { var iterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Argon2Iterations); var memory = kdfConfig.Memory.GetValueOrDefault(Constants.Argon2MemoryInMB) * 1024; var parallelism = kdfConfig.Parallelism.GetValueOrDefault(Constants.Argon2Parallelism); if (kdfConfig.Iterations < 2) { throw new Exception("Argon2 iterations minimum is 2"); } if (kdfConfig.Memory < 16) { throw new Exception("Argon2 memory minimum is 16 MB"); } else if (kdfConfig.Memory > 1024) { throw new Exception("Argon2 memory maximum is 1024 MB"); } if (kdfConfig.Parallelism < 1) { throw new Exception("Argon2 parallelism minimum is 1"); } var saltHash = await _cryptoFunctionService.HashAsync(salt, CryptoHashAlgorithm.Sha256); key = await _cryptoFunctionService.Argon2Async(password, saltHash, iterations, memory, parallelism); } else { throw new Exception("Unknown kdf."); } return keyCreator(key); } private async Task ValidateUserKeyAsync(UserKey key, string userId = null) { if (key == null) { return false; } try { var encPrivateKey = await _stateService.GetPrivateKeyEncryptedAsync(userId); if (encPrivateKey == null) { return false; } var privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), key); await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey); return true; } catch { return false; } } private class EncryptedObject { public byte[] Iv { get; set; } public byte[] Data { get; set; } public byte[] Mac { get; set; } public SymmetricCryptoKey Key { get; set; } } // --MIGRATION METHODS-- // We previously used the master key for additional keys, but now we use the user key. // These methods support migrating the old keys to the new ones. private async Task MigrateAutoAndBioKeysIfNeededAsync(string userId = null) { var oldKey = await _stateService.GetKeyEncryptedAsync(userId); if (oldKey == null) { return; } // Decrypt var masterKey = new MasterKey(Convert.FromBase64String(oldKey)); if (await IsLegacyUserAsync(masterKey, userId)) { throw new LegacyUserException(); } var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId); if (encryptedUserKey == null) { return; } var userKey = await DecryptUserKeyWithMasterKeyAsync( masterKey, new EncString(encryptedUserKey), userId); // Migrate if (await _stateService.GetVaultTimeoutAsync(userId) == null) { await _stateService.SetUserKeyAutoUnlockAsync(userKey, userId); } if (await _stateService.GetBiometricUnlockAsync(userId) is true) { await _stateService.SetUserKeyBiometricUnlockAsync(userKey, userId); } await _stateService.SetKeyEncryptedAsync(null, userId); // Set encrypted user key just in case the user locks without syncing await SetMasterKeyEncryptedUserKeyAsync(encryptedUserKey); } public async Task DecryptAndMigrateOldPinKeyAsync( bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey) { // Decrypt var masterKey = await DecryptMasterKeyWithPinAsync( pin, email, kdfConfig, oldPinKey ); if (await IsLegacyUserAsync(masterKey)) { throw new LegacyUserException(); } var encUserKey = await _stateService.GetEncKeyEncryptedAsync(); var userKey = await DecryptUserKeyWithMasterKeyAsync( masterKey, new EncString(encUserKey) ); // Migrate var pinKey = await MakePinKeyAsync(pin, email, kdfConfig); var pinProtectedKey = await EncryptAsync(userKey.Key, pinKey); if (masterPasswordOnRestart) { await _stateService.SetPinProtectedKeyAsync(null); await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(pinProtectedKey); } else { await _stateService.SetPinProtectedAsync(null); await _stateService.SetPinKeyEncryptedUserKeyAsync(pinProtectedKey); // We previously only set the protected pin if MP on Restart was enabled // now we set it regardless var encPin = await EncryptAsync(pin, userKey); await _stateService.SetProtectedPinAsync(encPin.EncryptedString); } // Clear old key only if not needed for bio/auto migration if (await _stateService.GetKeyEncryptedAsync() != null) { await _stateService.SetEncKeyEncryptedAsync(null); } return userKey; } public Task ClearDeprecatedPinKeysAsync(string userId = null) { return Task.WhenAll( _stateService.SetPinProtectedAsync(null, userId), _stateService.SetPinProtectedKeyAsync(null, userId)); } } }