Skip to content

Commit

Permalink
Android: Fix #354: StrongBox is now by default turned off for ALL dev…
Browse files Browse the repository at this point in the history
…ices (1.5.x branch)
  • Loading branch information
hvge committed Mar 16, 2021
1 parent 474b7f0 commit 8fe6b1f
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 24 deletions.
15 changes: 14 additions & 1 deletion docs/PowerAuth-SDK-for-Android.md
Expand Up @@ -148,10 +148,23 @@ The following levels of keychain protection are defined:

- `HARDWARE` - The content of the keychain is encrypted with key generated by Android KeyStore and the key is stored and managed by [Trusted Execution Environment](https://en.wikipedia.org/wiki/Trusted_execution_environment).

- `STRONGBOX` - The content of the keychain is encrypted with key generated by Android KeyStore and the key is stored inside of Secure Element (e.g. StrongBox). This is the highest level of Keychain protection currently available.
- `STRONGBOX` - The content of the keychain is encrypted with key generated by Android KeyStore and the key is stored inside of Secure Element (e.g. StrongBox). This is the highest level of Keychain protection currently available, but not enabled by default. See [note below](#strongbox-support-note).

Be aware, that enforcing the required level of protection must be properly reflected in your application's user interface. That means that you should inform the user in case that the device has an insufficient capabilities to run your application securely.

#### StrongBox support note

The StrongBox backed keys are by default turned-off due to poor reliability and low performance of StrongBox implementations on the current Android devices. If you want to turn support on in your application, then use the following code at your application's startup:

```java
try {
KeychainFactory.setStrongBoxEnabled(context, true);
} catch (PowerAuthErrorException e) {
// You must alter the configuration before any keychain is accessed.
// Basically, you should not create any PowerAuthSDK instance before the change.
}
```

## Activation

After you configure the SDK instance, you are ready to make your first activation.
Expand Down
Expand Up @@ -26,6 +26,8 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import io.getlime.security.powerauth.exception.PowerAuthErrorCodes;
import io.getlime.security.powerauth.exception.PowerAuthErrorException;
import io.getlime.security.powerauth.keychain.impl.BaseKeychainTest;
import io.getlime.security.powerauth.keychain.impl.DefaultStrongBoxSupport;
import io.getlime.security.powerauth.keychain.impl.EncryptedKeychain;
Expand Down Expand Up @@ -56,7 +58,7 @@ public class StrongBoxSupportTest extends BaseKeychainTest {
public void setUp() {
androidContext = InstrumentationRegistry.getInstrumentation().getContext();
assertNotNull(androidContext);
realStrongBoxSupport = new DefaultStrongBoxSupport(androidContext);
realStrongBoxSupport = new DefaultStrongBoxSupport(androidContext, false);
isLegacyOnly = KeychainFactory.getKeychainProtectionSupportedOnDevice(androidContext) == KeychainProtection.NONE;

setupTestData();
Expand All @@ -71,10 +73,16 @@ public void setUp() {
throw new IllegalStateException("Failed to acquire SymmetricKeyProvider for alias " + key);
}
}
// Cleanup keychains
eraseAllKeychainData(KEYCHAIN_NAME1);
eraseAllKeychainData(KEYCHAIN_NAME2);

// Reset possible cached keychains
KeychainFactory.setStrongBoxSupport(null);
}

/**
* Test validate whether EncryptedKeychain can select right key provider depending on
* Test whether EncryptedKeychain can select right key provider depending on
* StrongBox support.
*/
@Test
Expand Down Expand Up @@ -106,6 +114,92 @@ public void testSymmetricKeyProviderSelection() {
assertEquals(backupKP, determinedKP);
}

@Test
public void testStrongBoxEnabledDisabled() throws Exception {
if (isLegacyOnly) {
PA2Log.e("testStrongBoxEnabledDisabled - test is not supported on this device.");
return;
}
if (!realStrongBoxSupport.isStrongBoxSupported()) {
PA2Log.e("testStrongBoxEnabledDisabled - test require real StrongBox device.");
return;
}

// :::::::::
// :: 1 ::
// :::::::::

// By default, StrongBox is disabled.
assertFalse(KeychainFactory.isStrongBoxEnabled(androidContext));
// Setting false should do nothing.
KeychainFactory.setStrongBoxEnabled(androidContext, false);
// Still false, after change.
assertFalse(KeychainFactory.isStrongBoxEnabled(androidContext));
// By default, there's HARDWARE level of KeychainProtection
assertEquals(KeychainProtection.HARDWARE, KeychainFactory.getKeychainProtectionSupportedOnDevice(androidContext));

// Now acquire some keychain.
Keychain k1 = KeychainFactory.getKeychain(androidContext, KEYCHAIN_NAME1, KeychainProtection.NONE);
assertNotNull(k1);

try {
// The following statement must fail, even when there's no change in configuration.
KeychainFactory.setStrongBoxEnabled(androidContext, false);
fail("Must fail");
} catch (PowerAuthErrorException e) {
assertEquals(PowerAuthErrorCodes.PA2ErrorCodeWrongParameter, e.getPowerAuthErrorCode());
}

// :::::::::
// :: 2 ::
// :::::::::

// Reset factory to default state and try to turn support ON.
KeychainFactory.setStrongBoxSupport(null);

assertFalse(KeychainFactory.isStrongBoxEnabled(androidContext));

// Now enable support
KeychainFactory.setStrongBoxEnabled(androidContext, true);
// Must be true, after change
assertTrue(KeychainFactory.isStrongBoxEnabled(androidContext));
// After the change, the default keychain protection level must be STRONGBOX
assertEquals(KeychainProtection.STRONGBOX, KeychainFactory.getKeychainProtectionSupportedOnDevice(androidContext));

// Now acquire some keychain.
k1 = KeychainFactory.getKeychain(androidContext, KEYCHAIN_NAME1, KeychainProtection.NONE);
assertNotNull(k1);

try {
// The following statement must fail, even when there's no change in configuration.
KeychainFactory.setStrongBoxEnabled(androidContext, true);
fail("Must fail");
} catch (PowerAuthErrorException e) {
assertEquals(PowerAuthErrorCodes.PA2ErrorCodeWrongParameter, e.getPowerAuthErrorCode());
}

// :::::::::
// :: 3 ::
// :::::::::

// Reset factory to default state and test typical scenario, when application wants to
// change support, but there's already keychain created.

KeychainFactory.setStrongBoxSupport(null);

// Now acquire some keychain.
k1 = KeychainFactory.getKeychain(androidContext, KEYCHAIN_NAME1, KeychainProtection.NONE);
assertNotNull(k1);

try {
// The following statement must fail, even when there's no change in configuration.
KeychainFactory.setStrongBoxEnabled(androidContext, true);
fail("Must fail");
} catch (PowerAuthErrorException e) {
assertEquals(PowerAuthErrorCodes.PA2ErrorCodeWrongParameter, e.getPowerAuthErrorCode());
}
}

@Test
public void testMigrationFromV0toStrongBoxNotSupported() throws Exception {
if (isLegacyOnly) {
Expand Down Expand Up @@ -207,10 +301,6 @@ public void testStrongBoxSupportChange() throws Exception {
// This test simulates data migration between StrongBox support modes. The transparent data
// migration happens typically when next SDK version adds or removes support for StrongBox.

// Cleanup keychains
eraseAllKeychainData(KEYCHAIN_NAME1);
eraseAllKeychainData(KEYCHAIN_NAME2);

// Enable StrongBox support
KeychainFactory.setStrongBoxSupport(new FakeStrongBoxSupport(true, true));

Expand Down
Expand Up @@ -84,6 +84,44 @@ public static Keychain getKeychain(@NonNull Context context, @NonNull String ide
}
}

/**
* Determine whether StrongBox is enabled on this device and Keychain encrypts data with
* StrongBox-backed key. By default, StrongBox is disabled on all devices.
*
* @param context Android context.
* @return {@code true} in case that StrongBox is enabled on this device and Keychain encrypts
* data with StrongBox-backed key.
*/
public static boolean isStrongBoxEnabled(@NonNull Context context) {
synchronized (SharedData.class) {
return getSharedData().getStrongBoxSupport(context).isStrongBoxEnabled();
}
}

/**
* Enable or disable StrongBox support on this device. By default, StrongBox is disabled on all
* devices. It's required to alter the default configuration at application's startup and before
* you create any instance of {@link Keychain} or any {@code PowerAuthSDK} class. Otherwise the
* {@link PowerAuthErrorException} is produced.
*
* @param context Android context.
* @param enabled {@code true} to enable.
* @throws PowerAuthErrorException In case that {@code KeychainFactory} already created some {@link Keychain} instances.
*/
public static void setStrongBoxEnabled(@NonNull Context context, boolean enabled) throws PowerAuthErrorException {
synchronized (SharedData.class) {
final SharedData sharedData = getSharedData();
if (!sharedData.getKeychainMap().isEmpty()) {
throw new PowerAuthErrorException(PowerAuthErrorCodes.PA2ErrorCodeWrongParameter, "There are already created keychains in KeychainFactory.");
}
if (sharedData.getStrongBoxSupport(context).isStrongBoxEnabled() != enabled) {
final StrongBoxSupport newStrongBoxSupport = new DefaultStrongBoxSupport(context, enabled);
sharedData.setStrongBoxSupportAndResetSharedData(newStrongBoxSupport);
PA2Log.d("KeychainFactory: StrongBox support is now " + (enabled ? "enabled." : "disabled."));
}
}
}

/**
* Set alternate implementation of {@link StrongBoxSupport} used internally to determine current StrongBox
* support. The method is useful only for unit testing, so it's not declared as public. Be aware that
Expand Down Expand Up @@ -230,14 +268,17 @@ private void resetSharedData() {
@NonNull
StrongBoxSupport getStrongBoxSupport(@NonNull Context context) {
if (strongBoxSupport == null) {
strongBoxSupport = new DefaultStrongBoxSupport(context);
// StrongBox is disabled by default.
// Check https://github.com/wultra/powerauth-mobile-sdk/issues/354 for more details.
strongBoxSupport = new DefaultStrongBoxSupport(context, false);
}
return strongBoxSupport;
}

/**
* Change internal {@link StrongBoxSupport} implementation and reset shared data objet
* to default, non-initialized state. The method is useful only for unit testing purposes.
* Change internal {@link StrongBoxSupport} implementation and reset shared data object
* to default, non-initialized state. The method is useful only when application want's
* to change default StrongBox support or for an unit testing purposes.
*
* @param strongBoxSupport New {@link StrongBoxSupport} implementation.
*/
Expand Down
Expand Up @@ -22,33 +22,45 @@
import android.support.annotation.NonNull;

import io.getlime.security.powerauth.keychain.StrongBoxSupport;
import io.getlime.security.powerauth.system.PA2System;

/**
* The {@code DefaultStrongBoxSupport} implements {@link StrongBoxSupport} interface and reflects
* an actual support of StrongBox on device.
*/
public class DefaultStrongBoxSupport implements StrongBoxSupport {

private final @NonNull Context context;
private final boolean isSupported;
private final boolean isEnabled;

public DefaultStrongBoxSupport(@NonNull Context context) {
this.context = context;
/**
* Default object constructor allowing you to configure whether StrongBox is enabled on this device.
* @param context Android context
* @param enabled Enable or disable StrongBox support.
*/
public DefaultStrongBoxSupport(@NonNull Context context, boolean enabled) {
this.isSupported = getIsSupported(context);
this.isEnabled = enabled;
}

@Override
public boolean isStrongBoxSupported() {
/**
* Get information whether StrongBox is supported on the device.
* @param context Android context.
* @return {@code true} if StrongBox is supported on this device.
*/
private static boolean getIsSupported(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
}
return false;
}

@Override
public boolean isStrongBoxSupported() {
return isSupported;
}

@Override
public boolean isStrongBoxEnabled() {
if (!isStrongBoxSupported()) {
return false;
}
final String deviceInfo = PA2System.getDeviceInfo().toLowerCase();
if (deviceInfo.startsWith("google pixel")) {
return false;
}
return true;
return isSupported && isEnabled;
}
}

0 comments on commit 8fe6b1f

Please sign in to comment.