mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 23:25:45 +03:00
start writing crypto service
This commit is contained in:
parent
56b8bc1730
commit
2b2342bcad
9 changed files with 453 additions and 1 deletions
7
src/Core/Constants.cs
Normal file
7
src/Core/Constants.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Bit.Core
|
||||||
|
{
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public static string LockOptionKey = "lockOption";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Bit.Core.Enums
|
namespace Bit.Core.Enums
|
||||||
{
|
{
|
||||||
public enum CryptoHashAlgorithm
|
public enum CryptoHashAlgorithm : byte
|
||||||
{
|
{
|
||||||
Sha1 = 0,
|
Sha1 = 0,
|
||||||
Sha256 = 1,
|
Sha256 = 1,
|
||||||
|
|
13
src/Core/Enums/EncryptionType.cs
Normal file
13
src/Core/Enums/EncryptionType.cs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
9
src/Core/Enums/OrganizationUserStatusType.cs
Normal file
9
src/Core/Enums/OrganizationUserStatusType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum OrganizationUserStatusType : byte
|
||||||
|
{
|
||||||
|
Invited = 0,
|
||||||
|
Accepted = 1,
|
||||||
|
Confirmed = 2
|
||||||
|
}
|
||||||
|
}
|
10
src/Core/Enums/OrganizationUserType.cs
Normal file
10
src/Core/Enums/OrganizationUserType.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum OrganizationUserType : byte
|
||||||
|
{
|
||||||
|
Owner = 0,
|
||||||
|
Admin = 1,
|
||||||
|
User = 2,
|
||||||
|
Manager = 3,
|
||||||
|
}
|
||||||
|
}
|
109
src/Core/Models/Domain/CipherString.cs
Normal file
109
src/Core/Models/Domain/CipherString.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/Core/Models/Domain/SymmetricCryptoKey.cs
Normal file
77
src/Core/Models/Domain/SymmetricCryptoKey.cs
Normal file
|
@ -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<byte>(Key, 0, 16).ToArray();
|
||||||
|
MacKey = new ArraySegment<byte>(Key, 16, 16).ToArray();
|
||||||
|
}
|
||||||
|
else if(EncType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 34)
|
||||||
|
{
|
||||||
|
EncKey = new ArraySegment<byte>(Key, 0, 32).ToArray();
|
||||||
|
MacKey = new ArraySegment<byte>(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; }
|
||||||
|
}
|
||||||
|
}
|
25
src/Core/Models/Response/ProfileOrganizationResponse.cs
Normal file
25
src/Core/Models/Response/ProfileOrganizationResponse.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
202
src/Core/Services/CryptoService.cs
Normal file
202
src/Core/Services/CryptoService.cs
Normal file
|
@ -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<string, SymmetricCryptoKey> _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<int?>(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<ProfileOrganizationResponse> orgs)
|
||||||
|
{
|
||||||
|
var orgKeys = orgs.ToDictionary(org => org.Id, org => org.Key);
|
||||||
|
_orgKeys = null;
|
||||||
|
await _storageService.SaveAsync(Keys_EncOrgKeys, orgKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SymmetricCryptoKey> GetKeyAsync()
|
||||||
|
{
|
||||||
|
if(_key != null)
|
||||||
|
{
|
||||||
|
return _key;
|
||||||
|
}
|
||||||
|
var key = await _secureStorageService.GetAsync<string>(Keys_Key);
|
||||||
|
if(key != null)
|
||||||
|
{
|
||||||
|
_key = new SymmetricCryptoKey(Convert.FromBase64String(key));
|
||||||
|
}
|
||||||
|
return key == null ? null : _key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetKeyHashAsync()
|
||||||
|
{
|
||||||
|
if(_keyHash != null)
|
||||||
|
{
|
||||||
|
return _keyHash;
|
||||||
|
}
|
||||||
|
var keyHash = await _secureStorageService.GetAsync<string>(Keys_KeyHash);
|
||||||
|
if(keyHash != null)
|
||||||
|
{
|
||||||
|
_keyHash = keyHash;
|
||||||
|
}
|
||||||
|
return keyHash == null ? null : _keyHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SymmetricCryptoKey> GetEncKeyAsync()
|
||||||
|
{
|
||||||
|
if(_encKey != null)
|
||||||
|
{
|
||||||
|
return _encKey;
|
||||||
|
}
|
||||||
|
var encKey = await _storageService.GetAsync<string>(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<byte[]> 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<byte[]> GetPrivateKeyAsync()
|
||||||
|
{
|
||||||
|
if(_privateKey != null)
|
||||||
|
{
|
||||||
|
return _privateKey;
|
||||||
|
}
|
||||||
|
var encPrivateKey = await _storageService.GetAsync<string>(Keys_EncPrivateKey);
|
||||||
|
if(encPrivateKey == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
return _privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue