diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs new file mode 100644 index 000000000..4971dbfa3 --- /dev/null +++ b/src/Core/Constants.cs @@ -0,0 +1,7 @@ +namespace Bit.Core +{ + public static class Constants + { + public static string LockOptionKey = "lockOption"; + } +} diff --git a/src/Core/Enums/CryptoHashAlgorithm.cs b/src/Core/Enums/CryptoHashAlgorithm.cs index 0c1df3101..eacdfdfee 100644 --- a/src/Core/Enums/CryptoHashAlgorithm.cs +++ b/src/Core/Enums/CryptoHashAlgorithm.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Enums { - public enum CryptoHashAlgorithm + public enum CryptoHashAlgorithm : byte { Sha1 = 0, Sha256 = 1, diff --git a/src/Core/Enums/EncryptionType.cs b/src/Core/Enums/EncryptionType.cs new file mode 100644 index 000000000..2b6eaf086 --- /dev/null +++ b/src/Core/Enums/EncryptionType.cs @@ -0,0 +1,13 @@ +namespace Bit.Core.Enums +{ + public enum EncryptionType : byte + { + AesCbc256_B64 = 0, + AesCbc128_HmacSha256_B64 = 1, + AesCbc256_HmacSha256_B64 = 2, + Rsa2048_OaepSha256_B64 = 3, + Rsa2048_OaepSha1_B64 = 4, + Rsa2048_OaepSha256_HmacSha256_B64 = 5, + Rsa2048_OaepSha1_HmacSha256_B64 = 6 + } +} diff --git a/src/Core/Enums/OrganizationUserStatusType.cs b/src/Core/Enums/OrganizationUserStatusType.cs new file mode 100644 index 000000000..7d2246dc8 --- /dev/null +++ b/src/Core/Enums/OrganizationUserStatusType.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Enums +{ + public enum OrganizationUserStatusType : byte + { + Invited = 0, + Accepted = 1, + Confirmed = 2 + } +} diff --git a/src/Core/Enums/OrganizationUserType.cs b/src/Core/Enums/OrganizationUserType.cs new file mode 100644 index 000000000..7021952ef --- /dev/null +++ b/src/Core/Enums/OrganizationUserType.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Enums +{ + public enum OrganizationUserType : byte + { + Owner = 0, + Admin = 1, + User = 2, + Manager = 3, + } +} diff --git a/src/Core/Models/Domain/CipherString.cs b/src/Core/Models/Domain/CipherString.cs new file mode 100644 index 000000000..b76e972e3 --- /dev/null +++ b/src/Core/Models/Domain/CipherString.cs @@ -0,0 +1,109 @@ +using Bit.Core.Enums; +using System; + +namespace Bit.Core.Models.Domain +{ + public class CipherString + { + private string _decryptedValue; + + public CipherString(EncryptionType encryptionType, string data, string iv, string mac) + { + if(string.IsNullOrWhiteSpace(data)) + { + throw new ArgumentNullException(nameof(data)); + } + + if(!string.IsNullOrWhiteSpace(iv)) + { + EncryptedString = string.Format("{0}.{1}|{2}", (byte)encryptionType, iv, data); + } + else + { + EncryptedString = string.Format("{0}.{1}", (byte)encryptionType, data); + } + + if(!string.IsNullOrWhiteSpace(mac)) + { + EncryptedString = string.Format("{0}|{1}", EncryptedString, mac); + } + + EncryptionType = encryptionType; + Data = data; + Iv = iv; + Mac = mac; + } + + public CipherString(string encryptedString) + { + if(string.IsNullOrWhiteSpace(encryptedString)) + { + throw new ArgumentException(nameof(encryptedString)); + } + + EncryptedString = encryptedString; + var headerPieces = EncryptedString.Split('.'); + string[] encPieces; + + if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out EncryptionType encType)) + { + EncryptionType = encType; + encPieces = headerPieces[1].Split('|'); + } + else + { + encPieces = EncryptedString.Split('|'); + EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 : + EncryptionType.AesCbc256_B64; + } + + switch(EncryptionType) + { + case EncryptionType.AesCbc128_HmacSha256_B64: + case EncryptionType.AesCbc256_HmacSha256_B64: + if(encPieces.Length != 3) + { + return; + } + Iv = encPieces[0]; + Data = encPieces[1]; + Mac = encPieces[2]; + break; + case EncryptionType.AesCbc256_B64: + if(encPieces.Length != 2) + { + return; + } + Iv = encPieces[0]; + Data = encPieces[1]; + break; + case EncryptionType.Rsa2048_OaepSha256_B64: + case EncryptionType.Rsa2048_OaepSha1_B64: + if(encPieces.Length != 1) + { + return; + } + Data = encPieces[0]; + break; + default: + return; + } + } + + public EncryptionType EncryptionType { get; private set; } + public string EncryptedString { get; private set; } + public string Iv { get; private set; } + public string Data { get; private set; } + public string Mac { get; private set; } + + public string Decrypt(string orgId = null) + { + if(_decryptedValue == null) + { + // TODO + } + + return _decryptedValue; + } + } +} diff --git a/src/Core/Models/Domain/SymmetricCryptoKey.cs b/src/Core/Models/Domain/SymmetricCryptoKey.cs new file mode 100644 index 000000000..4cd299090 --- /dev/null +++ b/src/Core/Models/Domain/SymmetricCryptoKey.cs @@ -0,0 +1,77 @@ +using Bit.Core.Enums; +using System; +using System.Linq; + +namespace Bit.Core.Models.Domain +{ + public class SymmetricCryptoKey + { + public SymmetricCryptoKey(byte[] key, EncryptionType? encType = null) + { + if(key == null) + { + throw new Exception("Must provide key."); + } + + if(encType == null) + { + if(key.Length == 32) + { + encType = EncryptionType.AesCbc256_B64; + } + else if(key.Length == 64) + { + encType = EncryptionType.AesCbc256_HmacSha256_B64; + } + else + { + throw new Exception("Unable to determine encType."); + } + } + + Key = key; + EncType = encType.Value; + + if(EncType == EncryptionType.AesCbc256_B64 && Key.Length == 32) + { + EncKey = Key; + MacKey = null; + } + else if(EncType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32) + { + EncKey = new ArraySegment(Key, 0, 16).ToArray(); + MacKey = new ArraySegment(Key, 16, 16).ToArray(); + } + else if(EncType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 34) + { + EncKey = new ArraySegment(Key, 0, 32).ToArray(); + MacKey = new ArraySegment(Key, 32, 32).ToArray(); + } + else + { + throw new Exception("Unsupported encType/key length."); + } + + if(Key != null) + { + KeyB64 = Convert.ToBase64String(Key); + } + if(EncKey != null) + { + EncKeyB64 = Convert.ToBase64String(EncKey); + } + if(MacKey != null) + { + MacKeyB64 = Convert.ToBase64String(MacKey); + } + } + + public byte[] Key { get; set; } + public byte[] EncKey { get; set; } + public byte[] MacKey { get; set; } + public EncryptionType EncType { get; set; } + public string KeyB64 { get; set; } + public string EncKeyB64 { get; set; } + public string MacKeyB64 { get; set; } + } +} diff --git a/src/Core/Models/Response/ProfileOrganizationResponse.cs b/src/Core/Models/Response/ProfileOrganizationResponse.cs new file mode 100644 index 000000000..c1b6b8826 --- /dev/null +++ b/src/Core/Models/Response/ProfileOrganizationResponse.cs @@ -0,0 +1,25 @@ +using Bit.Core.Enums; + +namespace Bit.Core.Models.Response +{ + public class ProfileOrganizationResponse + { + public string Id { get; set; } + public string Name { get; set; } + public bool UseGroups { get; set; } + public bool UseDirectory { get; set; } + public bool UseEvents { get; set; } + public bool UseTotp { get; set; } + public bool Use2fa { get; set; } + public bool UseApi { get; set; } + public bool UsersGetPremium { get; set; } + public bool SelfHost { get; set; } + public int Seats { get; set; } + public int MaxCollections { get; set; } + public short? MaxStorageGb { get; set; } + public string Key { get; set; } + public OrganizationUserStatusType Status { get; set; } + public OrganizationUserType Type { get; set; } + public bool Enabled { get; set; } + } +} diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs new file mode 100644 index 000000000..3adfc7b27 --- /dev/null +++ b/src/Core/Services/CryptoService.cs @@ -0,0 +1,202 @@ +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Models.Domain; +using Bit.Core.Models.Response; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Bit.Core.Services +{ + public class CryptoService + { + private readonly IStorageService _storageService; + private readonly IStorageService _secureStorageService; + private readonly ICryptoFunctionService _cryptoFunctionService; + + private SymmetricCryptoKey _key; + private SymmetricCryptoKey _encKey; + private SymmetricCryptoKey _legacyEtmKey; + private string _keyHash; + private byte[] _publicKey; + private byte[] _privateKey; + private Dictionary _orgKeys; + + private const string Keys_Key = "key"; + private const string Keys_EncOrgKeys = "encOrgKeys"; + private const string Keys_EncPrivateKey = "encPrivateKey"; + private const string Keys_EncKey = "encKey"; + private const string Keys_KeyHash = "keyHash"; + + public CryptoService( + IStorageService storageService, + IStorageService secureStorageService, + ICryptoFunctionService cryptoFunctionService) + { + _storageService = storageService; + _secureStorageService = secureStorageService; + _cryptoFunctionService = cryptoFunctionService; + } + + public async Task SetKeyAsync(SymmetricCryptoKey key) + { + _key = key; + var option = await _storageService.GetAsync(Constants.LockOptionKey); + if(option.HasValue) + { + // If we have a lock option set, we do not store the key + return; + } + await _secureStorageService.SaveAsync(Keys_Key, key.KeyB64); + } + + public async Task SetKeyHashAsync(string keyHash) + { + _keyHash = keyHash; + await _storageService.SaveAsync(Keys_KeyHash, keyHash); + } + + public async Task SetEncKeyAsync(string encKey) + { + if(encKey == null) + { + return; + } + await _storageService.SaveAsync(Keys_EncKey, encKey); + _encKey = null; + } + + public async Task SetEncPrivateKeyAsync(string encPrivateKey) + { + if(encPrivateKey == null) + { + return; + } + await _storageService.SaveAsync(Keys_EncPrivateKey, encPrivateKey); + _privateKey = null; + } + + public async Task SetOrgKeysAsync(IEnumerable orgs) + { + var orgKeys = orgs.ToDictionary(org => org.Id, org => org.Key); + _orgKeys = null; + await _storageService.SaveAsync(Keys_EncOrgKeys, orgKeys); + } + + public async Task GetKeyAsync() + { + if(_key != null) + { + return _key; + } + var key = await _secureStorageService.GetAsync(Keys_Key); + if(key != null) + { + _key = new SymmetricCryptoKey(Convert.FromBase64String(key)); + } + return key == null ? null : _key; + } + + public async Task GetKeyHashAsync() + { + if(_keyHash != null) + { + return _keyHash; + } + var keyHash = await _secureStorageService.GetAsync(Keys_KeyHash); + if(keyHash != null) + { + _keyHash = keyHash; + } + return keyHash == null ? null : _keyHash; + } + + public async Task GetEncKeyAsync() + { + if(_encKey != null) + { + return _encKey; + } + var encKey = await _storageService.GetAsync(Keys_EncKey); + if(encKey == null) + { + return null; + } + + var key = await GetKeyAsync(); + if(key == null) + { + return null; + } + + byte[] decEncKey = null; + var encKeyCipher = new CipherString(encKey); + if(encKeyCipher.EncryptionType == EncryptionType.AesCbc256_B64) + { + // TODO + } + else if(encKeyCipher.EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64) + { + // TODO + } + else + { + throw new Exception("Unsupported encKey type."); + } + + if(decEncKey == null) + { + return null; + } + _encKey = new SymmetricCryptoKey(decEncKey); + return _encKey; + } + + public async Task GetPublicKeyAsync() + { + if(_publicKey != null) + { + return _publicKey; + } + var privateKey = await GetPrivateKeyAsync(); + if(privateKey == null) + { + return null; + } + _publicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey); + return _publicKey; + } + + public async Task GetPrivateKeyAsync() + { + if(_privateKey != null) + { + return _privateKey; + } + var encPrivateKey = await _storageService.GetAsync(Keys_EncPrivateKey); + if(encPrivateKey == null) + { + return null; + } + // TODO + return _privateKey; + } + + public async Task> GetFingerprintAsync(string userId, byte[] publicKey = null) + { + if(publicKey == null) + { + publicKey = await GetPublicKeyAsync(); + } + if(publicKey == null) + { + throw new Exception("No public key available."); + } + var keyFingerprint = await _cryptoFunctionService.HashAsync(publicKey, CryptoHashAlgorithm.Sha256); + // TODO + return null; + } + + } +}