Skip to content

Commit

Permalink
OAEP support enabled only on API 23+ devices with associated testing …
Browse files Browse the repository at this point in the history
…changes.
  • Loading branch information
Adam Newman committed Sep 22, 2016
1 parent 88ec791 commit 8bc37ad
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class TestVaultOaepUpgrade extends AndroidTestCase {
private static final String PRESHARED_SECRET_1 = "a;sdlfkja;asdfa4548w1211xji22e;l2ihjl9jl9dj9";

public void testUpgrade() {
assertTrue("This test will not pass below API 23", Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
try {
SecretKey originalKey = Aes256RandomKeyFactory.createKey();
KeyStorage keyStorageOld = getKeyStorage(CompatSharedPrefKeyStorageFactory.WRAPPER_TYPE_RSA_PKCS1, CompatSharedPrefKeyStorageFactory.WRAPPER_TYPE_RSA_PKCS1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2016. Bottle Rocket LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bottlerocketstudios.vault.keys.storage;

import android.os.Build;
import android.test.AndroidTestCase;
import android.util.Log;

import com.bottlerocketstudios.vault.EncryptionConstants;
import com.bottlerocketstudios.vault.keys.generator.Aes256RandomKeyFactory;
import com.bottlerocketstudios.vault.salt.PrngSaltGenerator;

import java.security.GeneralSecurityException;
import java.util.Arrays;

import javax.crypto.SecretKey;

/**
* Test transition to version 18 from a pre-18 device if it receives an OS upgrade.
*/
public class TestVaultUpgrade22To23 extends AndroidTestCase {
private static final String TAG = TestVaultUpgrade22To23.class.getSimpleName();

private static final String KEY_FILE_NAME = "upgrade22to23KeyFile";
private static final String KEY_ALIAS_1 = "upgrade22to23KeyAlias";
private static final int KEY_INDEX_1 = 1232734;
private static final String PRESHARED_SECRET_1 = "a;sdlfkja;asdfasds21222e;l2ihjl9jl9dj9";

public void testUpgrade() {
assertTrue("This test will not pass below API 23", Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
try {
SecretKey originalKey = Aes256RandomKeyFactory.createKey();
KeyStorage keyStorageOld = getKeyStorage(Build.VERSION_CODES.LOLLIPOP_MR1);
assertEquals("Incorrect KeyStorageType", KeyStorageType.ANDROID_KEYSTORE, keyStorageOld.getKeyStorageType());
keyStorageOld.clearKey(getContext());
keyStorageOld.saveKey(getContext(), originalKey);

SecretKey originalReadKey = keyStorageOld.loadKey(getContext());
assertNotNull("Key was null after creation and read from old storage.", originalReadKey);
assertTrue("Keys were not identical after creation and read from old storage", Arrays.equals(originalKey.getEncoded(), originalReadKey.getEncoded()));

KeyStorage keyStorageNew = getKeyStorage(Build.VERSION_CODES.M);
assertEquals("Incorrect KeyStorageType", KeyStorageType.ANDROID_KEYSTORE, keyStorageNew.getKeyStorageType());
SecretKey upgradedKey = keyStorageNew.loadKey(getContext());
assertNotNull("Key was null after upgrade.", upgradedKey);
assertTrue("Keys were not identical after upgrade", Arrays.equals(originalKey.getEncoded(), upgradedKey.getEncoded()));

KeyStorage keyStorageRead = getKeyStorage(Build.VERSION_CODES.M);
assertEquals("Incorrect KeyStorageType", KeyStorageType.ANDROID_KEYSTORE, keyStorageRead.getKeyStorageType());
SecretKey upgradedReadKey = keyStorageRead.loadKey(getContext());
assertNotNull("Key was null after upgrade and read from storage.", upgradedReadKey);
assertTrue("Keys were not identical after upgrade and read from storage", Arrays.equals(originalKey.getEncoded(), upgradedReadKey.getEncoded()));

} catch (GeneralSecurityException e) {
Log.e(TAG, "Caught java.security.GeneralSecurityException", e);
assertTrue("Exception when creating keystores", false);
}

}

private KeyStorage getKeyStorage(int sdkInt) throws GeneralSecurityException {
return CompatSharedPrefKeyStorageFactory.createKeyStorage(
getContext(),
sdkInt,
KEY_FILE_NAME,
KEY_ALIAS_1,
KEY_INDEX_1,
EncryptionConstants.AES_CIPHER,
PRESHARED_SECRET_1,
new PrngSaltGenerator());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
import android.os.Build;
import android.util.Log;

import com.bottlerocketstudios.vault.keys.wrapper.AbstractAndroidKeystoreSecretKeyWrapper;
import com.bottlerocketstudios.vault.keys.storage.hardware.AndroidKeystoreTester;
import com.bottlerocketstudios.vault.keys.storage.hardware.BadHardware;
import com.bottlerocketstudios.vault.keys.storage.hardware.LegacyAndroidKeystoreTester;
import com.bottlerocketstudios.vault.keys.storage.hardware.OaepAndroidKeystoreTester;
import com.bottlerocketstudios.vault.keys.storage.hardware.Pkcs1AndroidKeystoreTester;
import com.bottlerocketstudios.vault.keys.wrapper.AndroidKeystoreSecretKeyWrapper;
import com.bottlerocketstudios.vault.keys.wrapper.AndroidOaepKeystoreSecretKeyWrapper;
import com.bottlerocketstudios.vault.keys.wrapper.ObfuscatingSecretKeyWrapper;
Expand All @@ -40,7 +44,6 @@ public class CompatSharedPrefKeyStorageFactory {

private static final String PREF_COMPAT_FACTORY_WRAPPER_TYPE = "compatFactoryWrapperType.";
private static final String PREF_COMPAT_FACTORY_SDK_INT_ROOT = "compatFactorySdkInt.";
private static final String PREF_COMPAT_FACTORY_ANDROID_KEYSTORE_TEST_STATE_ROOT = "androidKeystoreTestState.";

static final int WRAPPER_TYPE_INVALID = 0;
static final int WRAPPER_TYPE_OBFUSCATED = 1;
Expand Down Expand Up @@ -106,12 +109,20 @@ private static int determineCurrentWrapperType(Context context, String prefFileN
}

private static int determineBestSupportedWrapperType(Context context, int currentSdkInt, String prefFileName, String keystoreAlias) {
if (currentSdkInt >= Build.VERSION_CODES.JELLY_BEAN_MR2 && !BadHardware.isBadHardware() && canUseAndroidKeystore(context, prefFileName, keystoreAlias, currentSdkInt)) {
return WRAPPER_TYPE_RSA_OAEP;
if (currentSdkInt >= Build.VERSION_CODES.JELLY_BEAN_MR2 && !BadHardware.isBadHardware()) {
if (currentSdkInt >= Build.VERSION_CODES.M && canUseAndroidKeystore(new OaepAndroidKeystoreTester(context, keystoreAlias, currentSdkInt), getSharedPreferences(context, prefFileName))) {
return WRAPPER_TYPE_RSA_OAEP;
} else if (currentSdkInt < Build.VERSION_CODES.M && canUseAndroidKeystore(new Pkcs1AndroidKeystoreTester(context, keystoreAlias, currentSdkInt), getSharedPreferences(context, prefFileName))) {
return WRAPPER_TYPE_RSA_PKCS1;
}
}
return WRAPPER_TYPE_OBFUSCATED;
}

private static boolean canUseAndroidKeystore(AndroidKeystoreTester androidKeystoreTester, SharedPreferences sharedPreferences) {
return androidKeystoreTester.canUseAndroidKeystore(sharedPreferences);
}

private static boolean doesRequireWrapperUpgrade(int oldWrapperType, int bestSupportedWrapperType) {
return oldWrapperType != WRAPPER_TYPE_INVALID && oldWrapperType != bestSupportedWrapperType;
}
Expand Down Expand Up @@ -142,7 +153,7 @@ private static int determineLegacyWrapperType(Context context, String prefFileNa
} else if (oldSdkInt > 0 && oldSdkInt < Build.VERSION_CODES.JELLY_BEAN_MR2) {
//This device is too old to have used the Android Keystore.
return WRAPPER_TYPE_OBFUSCATED;
} else if (AndroidKeystoreTestState.PASS.equals(readAndroidKeystoreTestState(context, prefFileName, keystoreAlias))) {
} else if (LegacyAndroidKeystoreTester.hasAlreadyPassedTest(context, keystoreAlias, oldSdkInt, getSharedPreferences(context, prefFileName))) {
//This device has a record of passing the Android Keystore test and must have been using the Android Keystore.
return WRAPPER_TYPE_RSA_PKCS1;
}
Expand All @@ -169,33 +180,6 @@ private static KeyStorage createKeyStorageForWrapperType(Context context, int wr
return new SharedPrefKeyStorage(secretKeyWrapper, prefFileName, keystoreAlias, cipherAlgorithm);
}

private static boolean canUseAndroidKeystore(Context context, String prefFileName, String keystoreAlias, int currentSdkInt) {
AndroidKeystoreTestState androidKeystoreTestState = readAndroidKeystoreTestState(context, prefFileName, keystoreAlias);
if (AndroidKeystoreTestState.UNTESTED.equals(androidKeystoreTestState)) {
androidKeystoreTestState = performAndroidKeystoreTest(context, keystoreAlias, currentSdkInt);
writeAndroidKeystoreTestState(context, prefFileName, keystoreAlias, androidKeystoreTestState);
}
return AndroidKeystoreTestState.PASS.equals(androidKeystoreTestState);
}

private static AndroidKeystoreTestState performAndroidKeystoreTest(Context context, String keystoreAlias, int currentSdkInt) {
AndroidKeystoreTestState androidKeystoreTestState = AndroidKeystoreTestState.FAIL;
if (currentSdkInt >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
try {
AbstractAndroidKeystoreSecretKeyWrapper androidKeystoreSecretKeyWrapper = new AndroidOaepKeystoreSecretKeyWrapper(context, keystoreAlias);
androidKeystoreTestState = androidKeystoreSecretKeyWrapper.testKey() ? AndroidKeystoreTestState.PASS : AndroidKeystoreTestState.FAIL;
} catch (Throwable t) {
Log.e(TAG, "Caught an exception while creating the AndroidKeystoreSecretKeyWrapper", t);
androidKeystoreTestState = AndroidKeystoreTestState.FAIL;
}
}

if (AndroidKeystoreTestState.FAIL.equals(androidKeystoreTestState)) {
Log.w(TAG, "This device failed the AndroidKeystoreSecretKeyWrapper test.");
}
return androidKeystoreTestState;
}

private static String getCurrentWrapperTypeSharedPreferenceKey(String keystoreAlias) {
return PREF_COMPAT_FACTORY_WRAPPER_TYPE + keystoreAlias;
}
Expand All @@ -211,28 +195,6 @@ private static int readWrapperType(Context context, String prefFileName, String
return sharedPreferences.getInt(getCurrentWrapperTypeSharedPreferenceKey(keystoreAlias), WRAPPER_TYPE_INVALID);
}

private static String getAndroidKeystoreTestStateSharedPreferenceKey(String keystoreAlias) {
return PREF_COMPAT_FACTORY_ANDROID_KEYSTORE_TEST_STATE_ROOT + keystoreAlias;
}

private static void writeAndroidKeystoreTestState(Context context, String prefFileName, String keystoreAlias, AndroidKeystoreTestState androidKeystoreTestState) {
getSharedPreferences(context, prefFileName).edit()
.putString(getAndroidKeystoreTestStateSharedPreferenceKey(keystoreAlias), androidKeystoreTestState.toString())
.apply();
}

private static AndroidKeystoreTestState readAndroidKeystoreTestState(Context context, String prefFileName, String keystoreAlias) {
String prefValue = getSharedPreferences(context, prefFileName).getString(getAndroidKeystoreTestStateSharedPreferenceKey(keystoreAlias), AndroidKeystoreTestState.UNTESTED.toString());
AndroidKeystoreTestState androidKeystoreTestState;
try {
androidKeystoreTestState = AndroidKeystoreTestState.valueOf(prefValue);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Failed to parse previous test state");
androidKeystoreTestState = AndroidKeystoreTestState.UNTESTED;
}
return androidKeystoreTestState;
}

private static String getCurrentSdkIntSharedPreferenceKey(String keystoreAlias) {
return PREF_COMPAT_FACTORY_SDK_INT_ROOT + keystoreAlias;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2016. Bottle Rocket LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bottlerocketstudios.vault.keys.storage.hardware;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;

import com.bottlerocketstudios.vault.keys.storage.AndroidKeystoreTestState;
import com.bottlerocketstudios.vault.keys.wrapper.AbstractAndroidKeystoreSecretKeyWrapper;

import java.security.GeneralSecurityException;

/**
* Created on 9/21/16.
*/
public abstract class AndroidKeystoreTester {
private static final String TAG = AndroidKeystoreTester.class.getSimpleName();

protected static final String PREF_COMPAT_FACTORY_ANDROID_KEYSTORE_TEST_STATE_ROOT = "androidKeystoreTestState.";

private final Context mContext;
private final String mKeystoreAlias;
private final int mCurrentSdkInt;
private final String mTestKeystoreAlias;

public AndroidKeystoreTester(Context context, String keystoreAlias, int currentSdkInt) {
mContext = context;
mKeystoreAlias = keystoreAlias;
mTestKeystoreAlias = keystoreAlias + "___TEST___";
mCurrentSdkInt = currentSdkInt;
}

public boolean canUseAndroidKeystore(SharedPreferences sharedPreferences) {
AndroidKeystoreTestState androidKeystoreTestState = readAndroidKeystoreTestState(sharedPreferences);
if (AndroidKeystoreTestState.UNTESTED.equals(androidKeystoreTestState)) {
androidKeystoreTestState = performAndroidKeystoreTest();
writeAndroidKeystoreTestState(sharedPreferences, androidKeystoreTestState);
}
return AndroidKeystoreTestState.PASS.equals(androidKeystoreTestState);
}

private AndroidKeystoreTestState performAndroidKeystoreTest() {
AndroidKeystoreTestState androidKeystoreTestState = AndroidKeystoreTestState.FAIL;
if (mCurrentSdkInt >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
AbstractAndroidKeystoreSecretKeyWrapper androidKeystoreSecretKeyWrapper = null;
try {
androidKeystoreSecretKeyWrapper = createKeystoreSecretKeyWrapper(mContext, mTestKeystoreAlias);
androidKeystoreTestState = androidKeystoreSecretKeyWrapper.testKey() ? AndroidKeystoreTestState.PASS : AndroidKeystoreTestState.FAIL;
} catch (Throwable t) {
Log.e(TAG, "Caught an exception while creating the AndroidKeystoreSecretKeyWrapper", t);
androidKeystoreTestState = AndroidKeystoreTestState.FAIL;
} finally {
if (androidKeystoreSecretKeyWrapper != null) {
try {
androidKeystoreSecretKeyWrapper.clearKey(mContext);
} catch (Throwable t) {
Log.e(TAG, "Caught an exception while cleaning up the AndroidKeystoreSecretKeyWrapper", t);
}
}
}
}

if (AndroidKeystoreTestState.FAIL.equals(androidKeystoreTestState)) {
Log.w(TAG, "This device failed the AndroidKeystoreSecretKeyWrapper test.");
}
return androidKeystoreTestState;
}

private void writeAndroidKeystoreTestState(SharedPreferences sharedPreferences, AndroidKeystoreTestState androidKeystoreTestState) {
sharedPreferences.edit()
.putString(getAndroidKeystoreTestStateSharedPreferenceKey(mKeystoreAlias), androidKeystoreTestState.toString())
.apply();
}

protected AndroidKeystoreTestState readAndroidKeystoreTestState(SharedPreferences sharedPreferences) {
String prefValue = sharedPreferences.getString(getAndroidKeystoreTestStateSharedPreferenceKey(mKeystoreAlias), AndroidKeystoreTestState.UNTESTED.toString());
return parseAndroidKeystoreTestState(prefValue);
}

private AndroidKeystoreTestState parseAndroidKeystoreTestState(String prefValue) {
AndroidKeystoreTestState androidKeystoreTestState;
try {
androidKeystoreTestState = AndroidKeystoreTestState.valueOf(prefValue);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Failed to parse previous test state");
androidKeystoreTestState = AndroidKeystoreTestState.UNTESTED;
}
return androidKeystoreTestState;
}

protected abstract String getAndroidKeystoreTestStateSharedPreferenceKey(String keystoreAlias);

protected abstract AbstractAndroidKeystoreSecretKeyWrapper createKeystoreSecretKeyWrapper(Context context, String testKeystoreAlias) throws GeneralSecurityException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* limitations under the License.
*/

package com.bottlerocketstudios.vault.keys.storage;
package com.bottlerocketstudios.vault.keys.storage.hardware;

import android.os.Build;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2016. Bottle Rocket LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bottlerocketstudios.vault.keys.storage.hardware;

import android.content.Context;
import android.content.SharedPreferences;

import com.bottlerocketstudios.vault.keys.storage.AndroidKeystoreTestState;
import com.bottlerocketstudios.vault.keys.wrapper.AbstractAndroidKeystoreSecretKeyWrapper;

import java.security.GeneralSecurityException;

/**
* Created on 9/21/16.
*/
public class LegacyAndroidKeystoreTester extends AndroidKeystoreTester {

public LegacyAndroidKeystoreTester(Context context, String keystoreAlias, int currentSdkInt) {
super(context, keystoreAlias, currentSdkInt);
}

@Override
protected String getAndroidKeystoreTestStateSharedPreferenceKey(String keystoreAlias) {
return PREF_COMPAT_FACTORY_ANDROID_KEYSTORE_TEST_STATE_ROOT + keystoreAlias;
}

@Override
protected AbstractAndroidKeystoreSecretKeyWrapper createKeystoreSecretKeyWrapper(Context context, String testKeystoreAlias) throws GeneralSecurityException {
throw new UnsupportedOperationException("Tests should not be performed. This class is provided to read legacy test status.");
}

public static boolean hasAlreadyPassedTest(Context context, String keystoreAlias, int currentSdkInt, SharedPreferences sharedPreferences) {
LegacyAndroidKeystoreTester tester = new LegacyAndroidKeystoreTester(context, keystoreAlias, currentSdkInt);
return AndroidKeystoreTestState.PASS.equals(tester.readAndroidKeystoreTestState(sharedPreferences));
}
}
Loading

0 comments on commit 8bc37ad

Please sign in to comment.