mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
* Change bio integrity validation to work at account-level * biometric state migration * fix account bio valid key storage location during migration * comment clarification * fix for iOS extensions not using custom avatar color
125 lines
4.3 KiB
C#
125 lines
4.3 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using Android.OS;
|
|
using Android.Security.Keystore;
|
|
using Bit.Core.Abstractions;
|
|
using Bit.Core.Services;
|
|
using Java.Security;
|
|
using Javax.Crypto;
|
|
|
|
namespace Bit.Droid.Services
|
|
{
|
|
public class BiometricService : IBiometricService
|
|
{
|
|
private readonly IStateService _stateService;
|
|
|
|
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
|
|
|
|
private const string KeyStoreName = "AndroidKeyStore";
|
|
|
|
private const string KeyAlgorithm = KeyProperties.KeyAlgorithmAes;
|
|
private const string BlockMode = KeyProperties.BlockModeCbc;
|
|
private const string EncryptionPadding = KeyProperties.EncryptionPaddingPkcs7;
|
|
private const string Transformation = KeyAlgorithm + "/" + BlockMode + "/" + EncryptionPadding;
|
|
|
|
private readonly KeyStore _keystore;
|
|
|
|
public BiometricService(IStateService stateService)
|
|
{
|
|
_stateService = stateService;
|
|
_keystore = KeyStore.GetInstance(KeyStoreName);
|
|
_keystore.Load(null);
|
|
}
|
|
|
|
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
|
{
|
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
|
{
|
|
await CreateKeyAsync(bioIntegritySrcKey);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
|
{
|
|
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
try
|
|
{
|
|
_keystore.Load(null);
|
|
var key = _keystore.GetKey(KeyName, null);
|
|
var cipher = Cipher.GetInstance(Transformation);
|
|
|
|
if (key == null || cipher == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
cipher.Init(CipherMode.EncryptMode, key);
|
|
}
|
|
catch (KeyPermanentlyInvalidatedException e)
|
|
{
|
|
// Biometric has changed
|
|
await ClearStateAsync(bioIntegritySrcKey);
|
|
return false;
|
|
}
|
|
catch (UnrecoverableKeyException e)
|
|
{
|
|
// Biometric was disabled and re-enabled
|
|
await ClearStateAsync(bioIntegritySrcKey);
|
|
return false;
|
|
}
|
|
catch (InvalidKeyException e)
|
|
{
|
|
// Fallback for old bitwarden users without a key
|
|
LoggerHelper.LogEvenIfCantBeResolved(e);
|
|
await CreateKeyAsync(bioIntegritySrcKey);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private async Task CreateKeyAsync(string bioIntegritySrcKey = null)
|
|
{
|
|
bioIntegritySrcKey ??= Core.Constants.BiometricIntegritySourceKey;
|
|
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey,
|
|
await GetStateAsync(bioIntegritySrcKey));
|
|
await _stateService.SetAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
|
|
|
|
try
|
|
{
|
|
var keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName);
|
|
var keyGenSpec =
|
|
new KeyGenParameterSpec.Builder(KeyName, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
|
|
.SetBlockModes(BlockMode)
|
|
.SetEncryptionPaddings(EncryptionPadding)
|
|
.SetUserAuthenticationRequired(true)
|
|
.Build();
|
|
keyGen.Init(keyGenSpec);
|
|
keyGen.GenerateKey();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Catch silently to allow biometrics to function on devices that are in a state where key generation
|
|
// is not functioning
|
|
LoggerHelper.LogEvenIfCantBeResolved(e);
|
|
}
|
|
}
|
|
|
|
private async Task<string> GetStateAsync(string bioIntegritySrcKey)
|
|
{
|
|
return await _stateService.GetSystemBiometricIntegrityState(bioIntegritySrcKey) ??
|
|
Guid.NewGuid().ToString();
|
|
}
|
|
|
|
private async Task ClearStateAsync(string bioIntegritySrcKey)
|
|
{
|
|
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey, null);
|
|
}
|
|
}
|
|
}
|