Skip to content

Commit

Permalink
Merge pull request #621 from /issues/620-android-biometry
Browse files Browse the repository at this point in the history
Store biometry key for each PowerAuthSDK instance
  • Loading branch information
hvge authored Sep 23, 2024
2 parents af4fc55 + 859dcff commit e1c8460
Show file tree
Hide file tree
Showing 16 changed files with 431 additions and 101 deletions.
9 changes: 9 additions & 0 deletions docs/Migration-from-1.6-to-1.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,12 @@ The behavior of `PowerAuthSDK.authenticateUsingBiometry()` has been slightly cha
### tvOS

The `PowerAuthSDK.authenticateUsingBiometry()` function is no longer available on tvOS platform.

## Changes in 1.7.10+

### Android

- The shared biometry-related encryption key is no longer supported in `PowerAuthSDK`. If an activation is already using the shared key, then it's in use until the activation or the biometry factor is removed. As part of this change, the following methods are now deprecated:
- Method `PowerAuthSDK.removeActivationLocal(Context, boolean)` is now deprecated. Use `removeActivationLocal(Context)` as a replacement.
- Method `PowerAuthKeychainConfiguration.getKeychainBiometryDefaultKey()` is now deprecated. Use `getKeychainKeyBiometry()` as a replacement.
- Method `PowerAuthKeychainConfiguration.Builder.keychainBiometryDefaultKey(String)` is now deprecated. Use `keychainKeyBiometry(String)` as a replacement.
12 changes: 11 additions & 1 deletion docs/Migration-from-1.7-to-1.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,14 @@ You can watch the following related issues:

- [wultra/powerauth-mobile-sdk#551](https://github.com/wultra/powerauth-mobile-sdk/issues/551)
- [wultra/powerauth-mobile-watch-sdk#7](https://github.com/wultra/powerauth-mobile-watch-sdk/issues/7)
- [wultra/powerauth-mobile-extensions-sdk#7](https://github.com/wultra/powerauth-mobile-extensions-sdk/issues/7)
- [wultra/powerauth-mobile-extensions-sdk#7](https://github.com/wultra/powerauth-mobile-extensions-sdk/issues/7)

## Changes in 1.8.3+

### Android

- The shared biometry-related encryption key is no longer supported in `PowerAuthSDK`. If an activation is already using the shared key, then it's in use until the activation or the biometry factor is removed. As part of this change, the following methods are now deprecated:
- Method `PowerAuthSDK.removeActivationLocal(Context, boolean)` is now deprecated. Use `removeActivationLocal(Context)` as a replacement.
- Method `PowerAuthKeychainConfiguration.getKeychainBiometryDefaultKey()` is now deprecated. Use `getKeychainKeyBiometry()` as a replacement.
- Method `PowerAuthKeychainConfiguration.Builder.keychainBiometryDefaultKey(String)` is now deprecated. Use `keychainKeyBiometry(String)` as a replacement.

5 changes: 5 additions & 0 deletions docs/Migration-from-1.8-to-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ PowerAuth Mobile SDK in version `1.9.0` provides the following improvements:
- Synchronous method `getEciesEncryptorForApplicationScope()` is replaced with asynchronous variant that guarantees the temporary encryption key is prepared.
- Synchronous method `getEciesEncryptorForActivationScope()` is replaced with asynchronous variant that guarantees the temporary encryption key is prepared.

- The shared biometry-related encryption key is no longer supported in `PowerAuthSDK`. If an activation is already using the shared key, then it's in use until the activation or the biometry factor is removed. As part of this change, the following methods are now deprecated:
- Method `PowerAuthSDK.removeActivationLocal(Context, boolean)` is now deprecated. Use `removeActivationLocal(Context)` as a replacement.
- Method `PowerAuthKeychainConfiguration.getKeychainBiometryDefaultKey()` is now deprecated. Use `getKeychainKeyBiometry()` as a replacement.
- Method `PowerAuthKeychainConfiguration.Builder.keychainBiometryDefaultKey(String)` is now deprecated. Use `keychainKeyBiometry(String)` as a replacement.

- Removed all interfaces deprecated in release `1.8.x`

### Other changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public Builder(@NonNull Context context, @NonNull PowerAuthTestConfig testConfig
if (sdk.hasValidActivation()) {
Logger.e("Shared PowerAuthSDK has a valid activation at test initialization.");
}
sdk.removeActivationLocal(context, true);
sdk.removeActivationLocal(context);
} else {
if (!sdk.hasValidActivation()) {
Logger.e("Shared PowerAuthSDK doesn't have a valid activation at test initialization.");
Expand Down Expand Up @@ -449,7 +449,7 @@ private PowerAuthTestHelper(
.keychainConfiguration(getSharedPowerAuthKeychainConfiguration())
.build(getContext());
if (resetActivation && sdk.hasValidActivation()) {
sdk.removeActivationLocal(getContext(), true);
sdk.removeActivationLocal(getContext());
}
return sdk;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,15 @@ public void removeActivation(boolean revokeRecoveryCodes) throws Exception {
invalidAuthentication = null;
createActivationResult = null;
}
removeActivationLocal(true);
removeActivationLocal();
}

/**
* Remove activation locally.
*
* @param removeSharedBiometryKey If true, then also remove a shared biometry key.
*/
public void removeActivationLocal(boolean removeSharedBiometryKey) {
public void removeActivationLocal() {
if (powerAuthSDK.hasValidActivation()) {
powerAuthSDK.removeActivationLocal(testHelper.getContext(), removeSharedBiometryKey);
powerAuthSDK.removeActivationLocal(testHelper.getContext());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public void onActivationCreateFailed(@NonNull Throwable t) {
public void testRemoveActivationLocal() throws Exception {
activationHelper.createStandardActivation(true, null);
// Remove activation
powerAuthSDK.removeActivationLocal(testHelper.getContext(), true);
powerAuthSDK.removeActivationLocal(testHelper.getContext());
// Back to Initial expectations
assertFalse(powerAuthSDK.hasValidActivation());
assertFalse(powerAuthSDK.hasPendingActivation());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void testDefaultParameters() throws Exception {
assertEquals(PowerAuthKeychainConfiguration.KEYCHAIN_ID_BIOMETRY, configuration.getKeychainBiometryId());
assertEquals(PowerAuthKeychainConfiguration.KEYCHAIN_ID_STATUS, configuration.getKeychainStatusId());
assertEquals(PowerAuthKeychainConfiguration.KEYCHAIN_ID_TOKEN_STORE, configuration.getKeychainTokenStoreId());
assertEquals(PowerAuthKeychainConfiguration.KEYCHAIN_KEY_BIOMETRY_DEFAULT, configuration.getKeychainBiometryDefaultKey());
assertNull(configuration.getKeychainKeyBiometry());
assertEquals(KeychainProtection.NONE, configuration.getMinimalRequiredKeychainProtection());
assertFalse(configuration.isConfirmBiometricAuthentication());
assertTrue(configuration.isLinkBiometricItemsToCurrentSet());
Expand All @@ -50,14 +50,14 @@ public void testCustomParameters() throws Exception {
.keychainBiometryId("keychain.biometry")
.keychainStatusId("keychain.status")
.keychainTokenStoreId("keychain.tokens")
.keychainBiometryDefaultKey("biometryKey")
.keychainKeyBiometry("biometryKey")
.minimalRequiredKeychainProtection(KeychainProtection.HARDWARE)
.authenticateOnBiometricKeySetup(false)
.build();
assertEquals("keychain.biometry", configuration.getKeychainBiometryId());
assertEquals("keychain.status", configuration.getKeychainStatusId());
assertEquals("keychain.tokens", configuration.getKeychainTokenStoreId());
assertEquals("biometryKey", configuration.getKeychainBiometryDefaultKey());
assertEquals("biometryKey", configuration.getKeychainKeyBiometry());
assertEquals(KeychainProtection.HARDWARE, configuration.getMinimalRequiredKeychainProtection());
assertTrue(configuration.isConfirmBiometricAuthentication());
assertFalse(configuration.isLinkBiometricItemsToCurrentSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void onCompletion() {
@Override
public void onBiometricKeyUnavailable() {
// Remove the default key, because the biometric key is no longer available.
device.getBiometricKeystore().removeBiometricKeyEncryptor();
device.getBiometricKeystore().removeBiometricKeyEncryptor(request.getKeystoreAlias());
}
});
final IBiometricKeyEncryptorProvider biometricKeyEncryptorProvider = new DefaultBiometricKeyEncryptorProvider(request, getBiometricKeystore());
Expand Down Expand Up @@ -170,7 +170,7 @@ public void onBiometricKeyUnavailable() {
}
// Failed to use biometric authentication. At first, we should cleanup the possible stored
// biometric key.
device.getBiometricKeystore().removeBiometricKeyEncryptor();
device.getBiometricKeystore().removeBiometricKeyEncryptor(request.getKeystoreAlias());

// Now show the error dialog, and report the exception later.
if (exception == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class BiometricAuthenticationRequest {
private final @NonNull CharSequence title;
private final @Nullable CharSequence subtitle;
private final @NonNull CharSequence description;
private final @NonNull String keystoreAlias;
private final boolean forceGenerateNewKey;
private final boolean invalidateByBiometricEnrollment;
private final boolean userConfirmationRequired;
Expand All @@ -52,6 +53,7 @@ private BiometricAuthenticationRequest(
@NonNull CharSequence description,
@Nullable Fragment fragment,
@Nullable FragmentActivity fragmentActivity,
@NonNull String keystoreAlias,
boolean forceGenerateNewKey,
boolean invalidateByBiometricEnrollment,
boolean userConfirmationRequired,
Expand All @@ -64,6 +66,7 @@ private BiometricAuthenticationRequest(
this.description = description;
this.fragment = fragment;
this.fragmentActivity = fragmentActivity;
this.keystoreAlias = keystoreAlias;
this.forceGenerateNewKey = forceGenerateNewKey;
this.invalidateByBiometricEnrollment = invalidateByBiometricEnrollment;
this.userConfirmationRequired = userConfirmationRequired;
Expand Down Expand Up @@ -108,6 +111,14 @@ private BiometricAuthenticationRequest(
return fragmentActivity;
}

/**
* @return Alias to Android Keystore for the existing, or the new created key.
*/
@NonNull
public String getKeystoreAlias() {
return keystoreAlias;
}

/**
* @return true whether the new biometric key has to be generated as a part of the operation.
*/
Expand Down Expand Up @@ -173,6 +184,7 @@ public static class Builder {
private Fragment fragment;
private FragmentActivity fragmentActivity;

private String keystoreAlias;
private boolean forceGenerateNewKey = false;
private boolean invalidateByBiometricEnrollment = true;
private boolean userConfirmationRequired = false;
Expand Down Expand Up @@ -200,6 +212,9 @@ public BiometricAuthenticationRequest build() {
if (TextUtils.isEmpty(title) || TextUtils.isEmpty(description)) {
throw new IllegalArgumentException("Title and description is required.");
}
if (keystoreAlias == null) {
throw new IllegalArgumentException("KeyStore alias is required.");
}
if (rawKeyData == null) {
throw new IllegalArgumentException("RawKeyData is required.");
}
Expand All @@ -218,6 +233,7 @@ public BiometricAuthenticationRequest build() {
description,
fragment,
fragmentActivity,
keystoreAlias,
forceGenerateNewKey,
invalidateByBiometricEnrollment,
userConfirmationRequired,
Expand Down Expand Up @@ -317,6 +333,16 @@ public Builder setFragmentActivity(@NonNull FragmentActivity fragmentActivity) {
return this;
}

/**
* Required: Set alias for a new or existing key stored in the Android Keystore.
* @param keystoreAlias Alias to key to create or access.
* @return This value will never be {@code null}.
*/
public Builder setKeystoreAlias(@NonNull String keystoreAlias) {
this.keystoreAlias = keystoreAlias;
return this;
}

/**
* @param forceGenerateNewKey If true then the new biometric key will be generated as a
* part of the process.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.getlime.security.powerauth.biometry;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
Expand All @@ -34,11 +35,12 @@ public interface IBiometricKeystore {
/**
* Check if a key for biometric key encryptor is present in Keystore and {@link IBiometricKeyEncryptor}
* can be acquired.
* @param keyId Key identifier.
*
* @return {@code true} in case a key for biometric key encryptor is present, false otherwise.
* Method returns false in case Keystore is not properly initialized (call {@link #isKeystoreReady()}).
*/
boolean containsBiometricKeyEncryptor();
boolean containsBiometricKeyEncryptor(@NonNull String keyId);

/**
* Generate a new biometry related Keystore key and return object that provide KEK encryption and decryption.
Expand All @@ -48,21 +50,32 @@ public interface IBiometricKeystore {
*
* @param invalidateByBiometricEnrollment Sets whether the new key should be invalidated on biometric enrollment.
* @param useSymmetricKey Sets whether symmetric key should be created.
* @param keyId Key identifier.
*
* @return New generated {@link IBiometricKeyEncryptor} key or {@code null} in case of failure.
*/
@Nullable
IBiometricKeyEncryptor createBiometricKeyEncryptor(boolean invalidateByBiometricEnrollment, boolean useSymmetricKey);
IBiometricKeyEncryptor createBiometricKeyEncryptor(@NonNull String keyId, boolean invalidateByBiometricEnrollment, boolean useSymmetricKey);

/**
* Removes an encryption key from Keystore.
* @param keyId Key identifier.
*/
void removeBiometricKeyEncryptor();
void removeBiometricKeyEncryptor(@NonNull String keyId);

/**
* Get implementation of {@link IBiometricKeyEncryptor} constructed with key stored in KeyStore.
* @param keyId Key identifier.
* @return {@link IBiometricKeyEncryptor} constructed with key stored in KeyStore or {@code null}
* if no such key is stored.
*/
@Nullable
IBiometricKeyEncryptor getBiometricKeyEncryptor();
IBiometricKeyEncryptor getBiometricKeyEncryptor(@NonNull String keyId);

/**
* Return identifier of legacy key shared between multiple PowerAuthSDK instances.
* @return Identifier of shared legacy key.
*/
@NonNull
String getLegacySharedKeyId();
}
Loading

0 comments on commit e1c8460

Please sign in to comment.