Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Invalidate biometric on change (#1026)
* Initial working version for Android * Add a fallback for when upgrading from older app version. * Ensure biometric validity is re-checked on focus * Only setup biometric integrity key if biometric is turned on. * Fix styling according to comments * Fallback for Android 5. * Improve comment * Add boilerplate for iOS * Change BiometricService to public * Untested iOS implementation. * Convert IBiometricService to async. Fix code style for iOS. * Base64 NSData. * Review comments for Android BiometricService. * Rename methods in BiometricService to append Async * Ensure we wait for async SetupBiometricAsync. * Update BiometricService.cs Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
- Loading branch information
Showing
12 changed files
with
218 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Android.OS; | ||
using Android.Security.Keystore; | ||
using Bit.Core.Abstractions; | ||
using Java.Security; | ||
using Javax.Crypto; | ||
|
||
namespace Bit.Droid.Services | ||
{ | ||
public class BiometricService : IBiometricService | ||
{ | ||
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() | ||
{ | ||
_keystore = KeyStore.GetInstance(KeyStoreName); | ||
_keystore.Load(null); | ||
} | ||
|
||
public Task<bool> SetupBiometricAsync() | ||
{ | ||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M) | ||
{ | ||
CreateKey(); | ||
} | ||
|
||
return Task.FromResult(true); | ||
} | ||
|
||
public Task<bool> ValidateIntegrityAsync() | ||
{ | ||
if (Build.VERSION.SdkInt < BuildVersionCodes.M) | ||
{ | ||
return Task.FromResult(true); | ||
} | ||
|
||
_keystore.Load(null); | ||
IKey key = _keystore.GetKey(KeyName, null); | ||
Cipher cipher = Cipher.GetInstance(Transformation); | ||
|
||
try | ||
{ | ||
cipher.Init(CipherMode.EncryptMode, key); | ||
} | ||
catch (KeyPermanentlyInvalidatedException e) | ||
{ | ||
// Biometric has changed | ||
return Task.FromResult(false); | ||
} | ||
catch (UnrecoverableKeyException e) | ||
{ | ||
// Biometric was disabled and re-enabled | ||
return Task.FromResult(false); | ||
} | ||
catch (InvalidKeyException e) | ||
{ | ||
// Fallback for old bitwarden users without a key | ||
CreateKey(); | ||
} | ||
|
||
return Task.FromResult(false); | ||
} | ||
|
||
private void CreateKey() | ||
{ | ||
KeyGenerator keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName); | ||
KeyGenParameterSpec keyGenSpec = | ||
new KeyGenParameterSpec.Builder(KeyName, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt) | ||
.SetBlockModes(BlockMode) | ||
.SetEncryptionPaddings(EncryptionPadding) | ||
.SetUserAuthenticationRequired(true) | ||
.Build(); | ||
keyGen.Init(keyGenSpec); | ||
keyGen.GenerateKey(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Threading.Tasks; | ||
|
||
namespace Bit.Core.Abstractions | ||
{ | ||
public interface IBiometricService | ||
{ | ||
Task<bool> SetupBiometricAsync(); | ||
Task<bool> ValidateIntegrityAsync(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
using System.Threading.Tasks; | ||
using Bit.Core.Abstractions; | ||
using Foundation; | ||
using LocalAuthentication; | ||
|
||
namespace Bit.iOS.Core.Services | ||
{ | ||
public class BiometricService : IBiometricService | ||
{ | ||
private IStorageService _storageService; | ||
|
||
public BiometricService(IStorageService storageService) | ||
{ | ||
_storageService = storageService; | ||
} | ||
|
||
public async Task<bool> SetupBiometricAsync() | ||
{ | ||
var state = GetState(); | ||
await _storageService.SaveAsync("biometricState", ToBase64(state)); | ||
|
||
return true; | ||
} | ||
|
||
public async Task<bool> ValidateIntegrityAsync() | ||
{ | ||
var oldState = await _storageService.GetAsync<string>("biometricState"); | ||
if (oldState == null) | ||
{ | ||
// Fallback for upgraded devices | ||
await SetupBiometricAsync(); | ||
|
||
return true; | ||
} | ||
else | ||
{ | ||
var state = GetState(); | ||
|
||
return FromBase64(oldState) == state; | ||
} | ||
} | ||
|
||
private NSData GetState() | ||
{ | ||
var context = new LAContext(); | ||
context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out _); | ||
|
||
return context.EvaluatedPolicyDomainState; | ||
} | ||
|
||
private string ToBase64(NSData data) | ||
{ | ||
return System.Convert.ToBase64String(data.ToArray()); | ||
} | ||
|
||
private NSData FromBase64(string data) | ||
{ | ||
var bytes = System.Convert.FromBase64String(data); | ||
return NSData.FromArray(bytes); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.