1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 15:53:44 +00:00

[PM-2713] Final merge from Key Migration branch to TDE Feature branch (#2667)

* [PM-2713] add async to key connector service methods

* [PM-2713] rename ephemeral pin key

* add state for biometric key and accept UserKey instead of string for auto key

* Get UserKey from bio state on unlock

* PM-2713 Fix auto-migrating EncKeyEncrypted into MasterKey encrypted UserKey when requesting DecryptUserKeyWithMasterKeyAsync is called

* renaming bio key and fix build

* PM-3194 Fix biometrics button to be shown on upgrade when no UserKey is present yet

* revert removal of key connector service from auth service

* PM-2713 set user key when using KC

* clear enc user key after migration

* use is true for nullable bool

* PR feedback, refactor kc service

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
This commit is contained in:
Jake Fink
2023-08-10 14:09:53 -04:00
committed by GitHub
parent 9001fa1ccf
commit 62b6d21371
22 changed files with 164 additions and 76 deletions

View File

@@ -157,9 +157,9 @@ namespace Bit.Droid
messagingService, broadcasterService); messagingService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>()); platformUtilsService, new LazyResolve<IEventService>());
var biometricService = new BiometricService(stateService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var biometricService = new BiometricService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage); ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.OS; using Android.OS;
using Android.Security.Keystore; using Android.Security.Keystore;
using Bit.App.Services;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Java.Security; using Java.Security;
@@ -9,10 +10,8 @@ using Javax.Crypto;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
public class BiometricService : IBiometricService public class BiometricService : BaseBiometricService
{ {
private readonly IStateService _stateService;
private const string KeyName = "com.8bit.bitwarden.biometric_integrity"; private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
private const string KeyStoreName = "AndroidKeyStore"; private const string KeyStoreName = "AndroidKeyStore";
@@ -24,14 +23,14 @@ namespace Bit.Droid.Services
private readonly KeyStore _keystore; private readonly KeyStore _keystore;
public BiometricService(IStateService stateService) public BiometricService(IStateService stateService, ICryptoService cryptoService)
: base(stateService, cryptoService)
{ {
_stateService = stateService;
_keystore = KeyStore.GetInstance(KeyStoreName); _keystore = KeyStore.GetInstance(KeyStoreName);
_keystore.Load(null); _keystore.Load(null);
} }
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null) public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
{ {
if (Build.VERSION.SdkInt >= BuildVersionCodes.M) if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{ {
@@ -41,7 +40,7 @@ namespace Bit.Droid.Services
return true; return true;
} }
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null) public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
{ {
if (Build.VERSION.SdkInt < BuildVersionCodes.M) if (Build.VERSION.SdkInt < BuildVersionCodes.M)
{ {

View File

@@ -180,7 +180,7 @@ namespace Bit.App.Pages
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) || PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent; _pinStatus == PinLockType.Persistent;
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync(); BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync();
// Users without MP and without biometric or pin has no MP to unlock with // Users without MP and without biometric or pin has no MP to unlock with
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync(); _hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
@@ -305,7 +305,7 @@ namespace Bit.App.Pages
{ {
Pin = string.Empty; Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(userKey); await SetUserKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -372,7 +372,7 @@ namespace Bit.App.Pages
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey); var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey); await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey); await SetUserKeyAndContinueAsync(userKey);
// Re-enable biometrics // Re-enable biometrics
if (BiometricEnabled & !BiometricIntegrityValid) if (BiometricEnabled & !BiometricIntegrityValid)
@@ -470,11 +470,12 @@ namespace Bit.App.Pages
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
{ {
await DoContinueAsync(); var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
await SetUserKeyAndContinueAsync(userKey);
} }
} }
private async Task SetKeyAndContinueAsync(UserKey key) private async Task SetUserKeyAndContinueAsync(UserKey key)
{ {
var hasKey = await _cryptoService.HasUserKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)

View File

@@ -30,14 +30,14 @@ namespace Bit.App.Pages
public async Task Init() public async Task Init()
{ {
Organization = await _keyConnectorService.GetManagingOrganization(); Organization = await _keyConnectorService.GetManagingOrganizationAsync();
} }
public async Task MigrateAccount() public async Task MigrateAccount()
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _keyConnectorService.MigrateUser(); await _keyConnectorService.MigrateUserAsync();
await _syncService.FullSyncAsync(true); await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();

View File

@@ -94,7 +94,7 @@ namespace Bit.App.Pages
} }
}); });
await UpdateVaultButtonTitleAsync(); await UpdateVaultButtonTitleAsync();
if (await _keyConnectorService.UserNeedsMigration()) if (await _keyConnectorService.UserNeedsMigrationAsync())
{ {
_messagingService.Send("convertAccountToKeyConnector"); _messagingService.Send("convertAccountToKeyConnector");
} }

View File

@@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Bit.Core.Abstractions;
namespace Bit.App.Services
{
public abstract class BaseBiometricService : IBiometricService
{
protected readonly IStateService _stateService;
protected readonly ICryptoService _cryptoService;
protected BaseBiometricService(IStateService stateService, ICryptoService cryptoService)
{
_stateService = stateService;
_cryptoService = cryptoService;
}
public async Task<bool> CanUseBiometricsUnlockAsync()
{
return await _cryptoService.HasEncryptedUserKeyAsync() || await _stateService.GetKeyEncryptedAsync() != null;
}
public abstract Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
public abstract Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null);
}
}

View File

@@ -71,7 +71,7 @@ namespace Bit.Core.Abstractions
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier); Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId, Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
OrganizationUserResetPasswordEnrollmentRequest request); OrganizationUserResetPasswordEnrollmentRequest request);
Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnector(string keyConnectorUrl); Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl);
Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request); Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request); Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnector(); Task PostConvertToKeyConnector();

View File

@@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
{ {
public interface IBiometricService public interface IBiometricService
{ {
Task<bool> CanUseBiometricsUnlockAsync();
Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null); Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null);
Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null); Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
} }

View File

@@ -21,6 +21,7 @@ namespace Bit.Core.Abstractions
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null); Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
Task<UserKey> GetAutoUnlockKeyAsync(string userId = null); Task<UserKey> GetAutoUnlockKeyAsync(string userId = null);
Task<bool> HasAutoUnlockKeyAsync(string userId = null); Task<bool> HasAutoUnlockKeyAsync(string userId = null);
Task<UserKey> GetBiometricUnlockKeyAsync(string userId = null);
Task SetMasterKeyAsync(MasterKey masterKey, string userId = null); Task SetMasterKeyAsync(MasterKey masterKey, string userId = null);
Task<MasterKey> GetMasterKeyAsync(string userId = null); Task<MasterKey> GetMasterKeyAsync(string userId = null);
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig); Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig);

View File

@@ -6,12 +6,12 @@ namespace Bit.Core.Abstractions
{ {
public interface IKeyConnectorService public interface IKeyConnectorService
{ {
Task SetUsesKeyConnector(bool usesKeyConnector); Task SetUsesKeyConnectorAsync(bool usesKeyConnector);
Task<bool> GetUsesKeyConnectorAsync(); Task<bool> GetUsesKeyConnectorAsync();
Task<bool> UserNeedsMigration(); Task<bool> UserNeedsMigrationAsync();
Task MigrateUser(); Task MigrateUserAsync();
Task GetAndSetKeyAsync(string url); Task SetMasterKeyFromUrlAsync(string url);
Task<Organization> GetManagingOrganization(); Task<Organization> GetManagingOrganizationAsync();
Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse); Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse);
} }
} }

View File

@@ -20,7 +20,7 @@ namespace Bit.Core.Abstractions
Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null); Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null);
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null); Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null); Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null);
Task SetUserKeyAutoUnlockAsync(string value, string userId = null); Task SetUserKeyAutoUnlockAsync(UserKey value, string userId = null);
Task<string> GetActiveUserIdAsync(); Task<string> GetActiveUserIdAsync();
Task<string> GetActiveUserEmailAsync(); Task<string> GetActiveUserEmailAsync();
Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper); Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper);
@@ -35,6 +35,8 @@ namespace Bit.Core.Abstractions
Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync(); Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync();
Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value); Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value);
Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null); Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null);
Task<UserKey> GetUserKeyBiometricUnlockAsync(string userId = null);
Task SetUserKeyBiometricUnlockAsync(UserKey value, string userId = null);
Task<bool?> GetBiometricUnlockAsync(string userId = null); Task<bool?> GetBiometricUnlockAsync(string userId = null);
Task SetBiometricUnlockAsync(bool? value, string userId = null); Task SetBiometricUnlockAsync(bool? value, string userId = null);
Task<bool> GetBiometricLockedAsync(string userId = null); Task<bool> GetBiometricLockedAsync(string userId = null);

View File

@@ -87,6 +87,7 @@ namespace Bit.Core
public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}"; public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}";
public static string MasterKeyEncryptedUserKeyKey(string userId) => $"masterKeyEncryptedUserKey_{userId}"; public static string MasterKeyEncryptedUserKeyKey(string userId) => $"masterKeyEncryptedUserKey_{userId}";
public static string UserKeyAutoUnlockKey(string userId) => $"autoUnlock_{userId}"; public static string UserKeyAutoUnlockKey(string userId) => $"autoUnlock_{userId}";
public static string UserKeyBiometricUnlockKey(string userId) => $"biometricUnlock_{userId}";
public static string CiphersKey(string userId) => $"ciphers_{userId}"; public static string CiphersKey(string userId) => $"ciphers_{userId}";
public static string FoldersKey(string userId) => $"folders_{userId}"; public static string FoldersKey(string userId) => $"folders_{userId}";
public static string CollectionsKey(string userId) => $"collections_{userId}"; public static string CollectionsKey(string userId) => $"collections_{userId}";

View File

@@ -121,7 +121,7 @@ namespace Bit.Core.Models.Domain
{ {
public UserKey UserKey; public UserKey UserKey;
public MasterKey MasterKey; public MasterKey MasterKey;
public EncString UserKeyPinEphemeral; public EncString PinKeyEncryptedUserKeyEphemeral;
public bool? BiometricLocked; public bool? BiometricLocked;
[Obsolete("Jul 6 2023: Key has been deprecated. We will use the User Key in the future. It remains here for migration during app upgrade.")] [Obsolete("Jul 6 2023: Key has been deprecated. We will use the User Key in the future. It remains here for migration during app upgrade.")]
public SymmetricCryptoKey Key; public SymmetricCryptoKey Key;

View File

@@ -511,7 +511,7 @@ namespace Bit.Core.Services
#region Key Connector #region Key Connector
public async Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnector(string keyConnectorUrl) public async Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl)
{ {
using (var requestMessage = new HttpRequestMessage()) using (var requestMessage = new HttpRequestMessage())
{ {

View File

@@ -48,7 +48,6 @@ namespace Bit.Core.Services
II18nService i18nService, II18nService i18nService,
IPlatformUtilsService platformUtilsService, IPlatformUtilsService platformUtilsService,
IMessagingService messagingService, IMessagingService messagingService,
IVaultTimeoutService vaultTimeoutService,
IKeyConnectorService keyConnectorService, IKeyConnectorService keyConnectorService,
IPasswordGenerationService passwordGenerationService, IPasswordGenerationService passwordGenerationService,
IPolicyService policyService, IPolicyService policyService,
@@ -510,6 +509,7 @@ namespace Bit.Core.Services
await _cryptoService.SetUserKeyAsync(userKey); await _cryptoService.SetUserKeyAsync(userKey);
} }
// Trusted Device
var decryptOptions = await _stateService.GetAccountDecryptionOptions(); var decryptOptions = await _stateService.GetAccountDecryptionOptions();
var hasUserKey = await _cryptoService.HasUserKeyAsync(); var hasUserKey = await _cryptoService.HasUserKeyAsync();
if (decryptOptions?.TrustedDeviceOption != null && !hasUserKey) if (decryptOptions?.TrustedDeviceOption != null && !hasUserKey)
@@ -524,22 +524,24 @@ namespace Bit.Core.Services
if (code == null || tokenResponse.Key != null) if (code == null || tokenResponse.Key != null)
{ {
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key); await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
// Key Connector
if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl)) if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
{ {
var url = tokenResponse.KeyConnectorUrl ?? decryptOptions.KeyConnectorOption.KeyConnectorUrl;
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key); await _keyConnectorService.SetMasterKeyFromUrlAsync(url);
if (masterKey != null)
{
await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
}
} }
// Login with Device // Login with Device
if (masterKey != null && !string.IsNullOrEmpty(authRequestId)) if (masterKey != null && !string.IsNullOrEmpty(authRequestId))
{ {
await _cryptoService.SetMasterKeyAsync(masterKey); await _cryptoService.SetMasterKeyAsync(masterKey);
}
// Decrypt UserKey with MasterKey
masterKey ??= await _stateService.GetMasterKeyAsync();
if (masterKey != null)
{
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey); var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey); await _cryptoService.SetUserKeyAsync(userKey);
} }
@@ -571,7 +573,7 @@ namespace Bit.Core.Services
} }
else else
{ {
await _keyConnectorService.GetAndSetKeyAsync(tokenResponse.KeyConnectorUrl); await _keyConnectorService.SetMasterKeyFromUrlAsync(tokenResponse.KeyConnectorUrl);
} }
} }
} }

View File

@@ -101,7 +101,7 @@ namespace Bit.Core.Services
public async Task<UserKey> GetAutoUnlockKeyAsync(string userId = null) public async Task<UserKey> GetAutoUnlockKeyAsync(string userId = null)
{ {
await MigrateAutoUnlockKeyIfNeededAsync(userId); await MigrateAutoAndBioKeysIfNeededAsync(userId);
return await _stateService.GetUserKeyAutoUnlockAsync(userId); return await _stateService.GetUserKeyAutoUnlockAsync(userId);
} }
@@ -110,6 +110,12 @@ namespace Bit.Core.Services
return await GetAutoUnlockKeyAsync(userId) != null; return await GetAutoUnlockKeyAsync(userId) != null;
} }
public async Task<UserKey> GetBiometricUnlockKeyAsync(string userId = null)
{
await MigrateAutoAndBioKeysIfNeededAsync(userId);
return await _stateService.GetUserKeyBiometricUnlockAsync(userId);
}
public Task SetMasterKeyAsync(MasterKey masterKey, string userId = null) public Task SetMasterKeyAsync(MasterKey masterKey, string userId = null)
{ {
return _stateService.SetMasterKeyAsync(masterKey, userId); return _stateService.SetMasterKeyAsync(masterKey, userId);
@@ -149,7 +155,7 @@ namespace Bit.Core.Services
public async Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey) public async Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey)
{ {
var userKey = await GetUserKeyAsync() ?? await MakeUserKeyAsync(); var userKey = await GetUserKeyAsync() ?? await MakeUserKeyAsync();
return await BuildProtectedSymmetricKey(masterKey, userKey.Key, keyBytes => new UserKey(keyBytes)); return await BuildProtectedSymmetricKeyAsync(masterKey, userKey.Key, keyBytes => new UserKey(keyBytes));
} }
public async Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null) public async Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null)
@@ -163,10 +169,27 @@ namespace Bit.Core.Services
if (encUserKey == null) if (encUserKey == null)
{ {
var userKeyMasterKey = await _stateService.GetMasterKeyEncryptedUserKeyAsync(userId); var userKeyMasterKey = await _stateService.GetMasterKeyEncryptedUserKeyAsync(userId);
if (userKeyMasterKey == null)
if (userKeyMasterKey is null)
{ {
throw new Exception("No encrypted user key found"); // 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); encUserKey = new EncString(userKeyMasterKey);
} }
@@ -204,7 +227,7 @@ namespace Bit.Core.Services
} }
var newSymKey = await _cryptoFunctionService.RandomBytesAsync(64); var newSymKey = await _cryptoFunctionService.RandomBytesAsync(64);
return await BuildProtectedSymmetricKey(key, newSymKey, keyBytes => new SymmetricCryptoKey(keyBytes)); return await BuildProtectedSymmetricKeyAsync(key, newSymKey, keyBytes => new SymmetricCryptoKey(keyBytes));
} }
public async Task<string> HashMasterKeyAsync(string password, MasterKey masterKey, HashPurpose hashPurpose = HashPurpose.ServerAuthorization) public async Task<string> HashMasterKeyAsync(string password, MasterKey masterKey, HashPurpose hashPurpose = HashPurpose.ServerAuthorization)
@@ -676,10 +699,10 @@ namespace Bit.Core.Services
private async Task StoreAdditionalKeysAsync(UserKey userKey, string userId = null) private async Task StoreAdditionalKeysAsync(UserKey userKey, string userId = null)
{ {
// Refresh, set, or clear the pin key // Set, refresh, or clear the pin key
if (await _stateService.GetProtectedPinAsync(userId) != null) if (await _stateService.GetProtectedPinAsync(userId) != null)
{ {
await UpdateUserKeyPinAsync(userKey, userId); await UpdatePinKeyAsync(userKey, userId);
} }
else else
{ {
@@ -687,18 +710,28 @@ namespace Bit.Core.Services
await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(null, userId); await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(null, userId);
} }
// Refresh, set, or clear the auto key // Set, refresh, or clear the auto unlock key
if (await _stateService.GetVaultTimeoutAsync(userId) == null) if (await _stateService.GetVaultTimeoutAsync(userId) == null)
{ {
await _stateService.SetUserKeyAutoUnlockAsync(userKey.KeyB64, userId); await _stateService.SetUserKeyAutoUnlockAsync(userKey, userId);
} }
else else
{ {
await _stateService.SetUserKeyAutoUnlockAsync(null, userId); 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 UpdateUserKeyPinAsync(UserKey userKey, string userId = null) private async Task UpdatePinKeyAsync(UserKey userKey, string userId = null)
{ {
var pin = await DecryptToUtf8Async(new EncString(await _stateService.GetProtectedPinAsync(userId))); var pin = await DecryptToUtf8Async(new EncString(await _stateService.GetProtectedPinAsync(userId)));
var pinKey = await MakePinKeyAsync( var pinKey = await MakePinKeyAsync(
@@ -880,7 +913,7 @@ namespace Bit.Core.Services
} }
// TODO: This needs to be moved into SymmetricCryptoKey model to remove the keyCreator hack // TODO: This needs to be moved into SymmetricCryptoKey model to remove the keyCreator hack
private async Task<Tuple<TKey, EncString>> BuildProtectedSymmetricKey<TKey>(SymmetricCryptoKey key, private async Task<Tuple<TKey, EncString>> BuildProtectedSymmetricKeyAsync<TKey>(SymmetricCryptoKey key,
byte[] encKey, Func<byte[], TKey> keyCreator) where TKey : SymmetricCryptoKey byte[] encKey, Func<byte[], TKey> keyCreator) where TKey : SymmetricCryptoKey
{ {
EncString encKeyEnc = null; EncString encKeyEnc = null;
@@ -904,7 +937,7 @@ namespace Bit.Core.Services
private async Task<TKey> MakeKeyAsync<TKey>(string password, string salt, KdfConfig kdfConfig, Func<byte[], TKey> keyCreator) private async Task<TKey> MakeKeyAsync<TKey>(string password, string salt, KdfConfig kdfConfig, Func<byte[], TKey> keyCreator)
where TKey : SymmetricCryptoKey where TKey : SymmetricCryptoKey
{ {
byte[] key = null; byte[] key;
if (kdfConfig.Type == null || kdfConfig.Type == KdfType.PBKDF2_SHA256) if (kdfConfig.Type == null || kdfConfig.Type == KdfType.PBKDF2_SHA256)
{ {
var iterations = kdfConfig.Iterations.GetValueOrDefault(5000); var iterations = kdfConfig.Iterations.GetValueOrDefault(5000);
@@ -962,23 +995,33 @@ namespace Bit.Core.Services
// We previously used the master key for additional keys, but now we use the user key. // 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. // These methods support migrating the old keys to the new ones.
private async Task MigrateAutoUnlockKeyIfNeededAsync(string userId = null) private async Task MigrateAutoAndBioKeysIfNeededAsync(string userId = null)
{ {
var oldAutoKey = await _stateService.GetKeyEncryptedAsync(userId); var oldKey = await _stateService.GetKeyEncryptedAsync(userId);
if (oldAutoKey == null) if (oldKey == null)
{ {
return; return;
} }
// Decrypt // Decrypt
var masterKey = new MasterKey(Convert.FromBase64String(oldAutoKey)); var masterKey = new MasterKey(Convert.FromBase64String(oldKey));
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId); var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
var userKey = await DecryptUserKeyWithMasterKeyAsync( var userKey = await DecryptUserKeyWithMasterKeyAsync(
masterKey, masterKey,
new EncString(encryptedUserKey), new EncString(encryptedUserKey),
userId); userId);
// Migrate // Migrate
await _stateService.SetUserKeyAutoUnlockAsync(userKey.KeyB64, userId); 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); await _stateService.SetKeyEncryptedAsync(null, userId);
// Set encrypted user key just in case the user locks without syncing // Set encrypted user key just in case the user locks without syncing
await SetMasterKeyEncryptedUserKeyAsync(encryptedUserKey); await SetMasterKeyEncryptedUserKeyAsync(encryptedUserKey);
} }

View File

@@ -27,13 +27,13 @@ namespace Bit.Core.Services
_organizationService = organizationService; _organizationService = organizationService;
} }
public async Task GetAndSetKeyAsync(string url) public async Task SetMasterKeyFromUrlAsync(string url)
{ {
try try
{ {
var masterKeyResponse = await _apiService.GetMasterKeyFromKeyConnector(url); var masterKeyResponse = await _apiService.GetMasterKeyFromKeyConnectorAsync(url);
var masterKeyArr = Convert.FromBase64String(masterKeyResponse.Key); var masterKeyBytes = Convert.FromBase64String(masterKeyResponse.Key);
var masterKey = new MasterKey(masterKeyArr); var masterKey = new MasterKey(masterKeyBytes);
await _cryptoService.SetMasterKeyAsync(masterKey); await _cryptoService.SetMasterKeyAsync(masterKey);
} }
catch (Exception e) catch (Exception e)
@@ -42,7 +42,7 @@ namespace Bit.Core.Services
} }
} }
public async Task SetUsesKeyConnector(bool usesKeyConnector) public async Task SetUsesKeyConnectorAsync(bool usesKeyConnector)
{ {
await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector); await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector);
} }
@@ -52,7 +52,7 @@ namespace Bit.Core.Services
return await _stateService.GetUsesKeyConnectorAsync(); return await _stateService.GetUsesKeyConnectorAsync();
} }
public async Task<Organization> GetManagingOrganization() public async Task<Organization> GetManagingOrganizationAsync()
{ {
var orgs = await _organizationService.GetAllAsync(); var orgs = await _organizationService.GetAllAsync();
return orgs.Find(o => return orgs.Find(o =>
@@ -60,9 +60,9 @@ namespace Bit.Core.Services
!o.IsAdmin); !o.IsAdmin);
} }
public async Task MigrateUser() public async Task MigrateUserAsync()
{ {
var organization = await GetManagingOrganization(); var organization = await GetManagingOrganizationAsync();
var masterKey = await _cryptoService.GetMasterKeyAsync(); var masterKey = await _cryptoService.GetMasterKeyAsync();
try try
@@ -78,10 +78,10 @@ namespace Bit.Core.Services
await _apiService.PostConvertToKeyConnector(); await _apiService.PostConvertToKeyConnector();
} }
public async Task<bool> UserNeedsMigration() public async Task<bool> UserNeedsMigrationAsync()
{ {
var loggedInUsingSso = await _tokenService.GetIsExternal(); var loggedInUsingSso = await _tokenService.GetIsExternal();
var requiredByOrganization = await GetManagingOrganization() != null; var requiredByOrganization = await GetManagingOrganizationAsync() != null;
var userIsNotUsingKeyConnector = !await GetUsesKeyConnectorAsync(); var userIsNotUsingKeyConnector = !await GetUsesKeyConnectorAsync();
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector; return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;

View File

@@ -241,6 +241,19 @@ namespace Bit.Core.Services
))?.Settings?.EnvironmentUrls; ))?.Settings?.EnvironmentUrls;
} }
public async Task<UserKey> GetUserKeyBiometricUnlockAsync(string userId = null)
{
var keyB64 = await _storageMediatorService.GetAsync<string>(
await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), true);
return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64));
}
public async Task SetUserKeyBiometricUnlockAsync(UserKey value, string userId = null)
{
await _storageMediatorService.SaveAsync(
await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), value?.KeyB64, true);
}
public async Task<bool?> GetBiometricUnlockAsync(string userId = null) public async Task<bool?> GetBiometricUnlockAsync(string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -353,10 +366,10 @@ namespace Bit.Core.Services
return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64)); return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64));
} }
public async Task SetUserKeyAutoUnlockAsync(string value, string userId = null) public async Task SetUserKeyAutoUnlockAsync(UserKey value, string userId = null)
{ {
await _storageMediatorService.SaveAsync( await _storageMediatorService.SaveAsync(
await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), value, true); await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), value?.KeyB64, true);
} }
public async Task<bool> CanAccessPremiumAsync(string userId = null) public async Task<bool> CanAccessPremiumAsync(string userId = null)
@@ -428,7 +441,7 @@ namespace Bit.Core.Services
{ {
return (await GetAccountAsync( return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.UserKeyPinEphemeral; ))?.VolatileData?.PinKeyEncryptedUserKeyEphemeral;
} }
public async Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null) public async Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null)
@@ -436,7 +449,7 @@ namespace Bit.Core.Services
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync()); await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions); var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.UserKeyPinEphemeral = value; account.VolatileData.PinKeyEncryptedUserKeyEphemeral = value;
await SaveAccountAsync(account, reconciledOptions); await SaveAccountAsync(account, reconciledOptions);
} }
@@ -1520,6 +1533,7 @@ namespace Bit.Core.Services
// Non-state storage // Non-state storage
await Task.WhenAll( await Task.WhenAll(
SetUserKeyAutoUnlockAsync(null, userId), SetUserKeyAutoUnlockAsync(null, userId),
SetUserKeyBiometricUnlockAsync(null, userId),
SetProtectedPinAsync(null, userId), SetProtectedPinAsync(null, userId),
SetKeyHashAsync(null, userId), SetKeyHashAsync(null, userId),
SetOrgKeysEncryptedAsync(null, userId), SetOrgKeysEncryptedAsync(null, userId),

View File

@@ -337,7 +337,7 @@ namespace Bit.Core.Services
await _stateService.SetNameAsync(response.Name); await _stateService.SetNameAsync(response.Name);
await _stateService.SetPersonalPremiumAsync(response.Premium); await _stateService.SetPersonalPremiumAsync(response.Premium);
await _stateService.SetAvatarColorAsync(response.AvatarColor); await _stateService.SetAvatarColorAsync(response.AvatarColor);
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector); await _keyConnectorService.SetUsesKeyConnectorAsync(response.UsesKeyConnector);
} }
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response) private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)

View File

@@ -82,7 +82,7 @@ namespace Bit.Core.Utilities
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService); var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
var passwordResetEnrollmentService = new PasswordResetEnrollmentService(apiService, cryptoService, organizationService, stateService); var passwordResetEnrollmentService = new PasswordResetEnrollmentService(apiService, cryptoService, organizationService, stateService);
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService, var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, tokenService, appIdService, i18nService, platformUtilsService, messagingService,
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService); keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService);
var exportService = new ExportService(folderService, cipherService, cryptoService); var exportService = new ExportService(folderService, cipherService, cryptoService);
var auditService = new AuditService(cryptoFunctionService, apiService); var auditService = new AuditService(cryptoFunctionService, apiService);

View File

@@ -1,20 +1,19 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Services;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Foundation; using Foundation;
using LocalAuthentication; using LocalAuthentication;
namespace Bit.iOS.Core.Services namespace Bit.iOS.Core.Services
{ {
public class BiometricService : IBiometricService public class BiometricService : BaseBiometricService
{ {
private IStateService _stateService; public BiometricService(IStateService stateService, ICryptoService cryptoService)
: base(stateService, cryptoService)
public BiometricService(IStateService stateService)
{ {
_stateService = stateService;
} }
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null) public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
{ {
if (bioIntegritySrcKey == null) if (bioIntegritySrcKey == null)
{ {
@@ -30,7 +29,7 @@ namespace Bit.iOS.Core.Services
return true; return true;
} }
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null) public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
{ {
var state = GetState(); var state = GetState();
if (state == null) if (state == null)

View File

@@ -112,9 +112,9 @@ namespace Bit.iOS.Core.Utilities
var clipboardService = new ClipboardService(stateService); var clipboardService = new ClipboardService(stateService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService, var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService); messagingService, broadcasterService);
var biometricService = new BiometricService(stateService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var biometricService = new BiometricService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage); ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);