From 8bc37adfcf33f977a313a74047b4f416ed7cd375 Mon Sep 17 00:00:00 2001 From: Adam Newman Date: Wed, 21 Sep 2016 19:22:47 -0500 Subject: [PATCH] OAEP support enabled only on API 23+ devices with associated testing changes. --- .../keys/storage/TestVaultOaepUpgrade.java | 1 + .../keys/storage/TestVaultUpgrade22To23.java | 86 ++++++++++++++ .../CompatSharedPrefKeyStorageFactory.java | 70 +++-------- .../hardware/AndroidKeystoreTester.java | 109 ++++++++++++++++++ .../storage/{ => hardware}/BadHardware.java | 2 +- .../hardware/LegacyAndroidKeystoreTester.java | 49 ++++++++ .../hardware/OaepAndroidKeystoreTester.java | 42 +++++++ .../hardware/Pkcs1AndroidKeystoreTester.java | 42 +++++++ ...stractAndroidKeystoreSecretKeyWrapper.java | 12 +- .../AndroidKeystoreSecretKeyWrapper.java | 4 - .../AndroidOaepKeystoreSecretKeyWrapper.java | 17 +-- CHANGELOG.md | 10 +- 12 files changed, 367 insertions(+), 77 deletions(-) create mode 100644 AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultUpgrade22To23.java create mode 100644 AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/AndroidKeystoreTester.java rename AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/{ => hardware}/BadHardware.java (94%) create mode 100644 AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/LegacyAndroidKeystoreTester.java create mode 100644 AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/OaepAndroidKeystoreTester.java create mode 100644 AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/Pkcs1AndroidKeystoreTester.java diff --git a/AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultOaepUpgrade.java b/AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultOaepUpgrade.java index 6861852..83d17e3 100644 --- a/AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultOaepUpgrade.java +++ b/AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultOaepUpgrade.java @@ -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); diff --git a/AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultUpgrade22To23.java b/AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultUpgrade22To23.java new file mode 100644 index 0000000..e298386 --- /dev/null +++ b/AndroidVault/vault/src/androidTest/java/com/bottlerocketstudios/vault/keys/storage/TestVaultUpgrade22To23.java @@ -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()); + } + +} diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/CompatSharedPrefKeyStorageFactory.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/CompatSharedPrefKeyStorageFactory.java index 150bc4f..c4e5337 100644 --- a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/CompatSharedPrefKeyStorageFactory.java +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/CompatSharedPrefKeyStorageFactory.java @@ -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; @@ -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; @@ -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; } @@ -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; } @@ -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; } @@ -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; } diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/AndroidKeystoreTester.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/AndroidKeystoreTester.java new file mode 100644 index 0000000..e26b7b5 --- /dev/null +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/AndroidKeystoreTester.java @@ -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; +} diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/BadHardware.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/BadHardware.java similarity index 94% rename from AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/BadHardware.java rename to AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/BadHardware.java index b8f6d4c..65c0ef1 100644 --- a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/BadHardware.java +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/BadHardware.java @@ -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; diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/LegacyAndroidKeystoreTester.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/LegacyAndroidKeystoreTester.java new file mode 100644 index 0000000..b3f1f79 --- /dev/null +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/LegacyAndroidKeystoreTester.java @@ -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)); + } +} diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/OaepAndroidKeystoreTester.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/OaepAndroidKeystoreTester.java new file mode 100644 index 0000000..f39843f --- /dev/null +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/OaepAndroidKeystoreTester.java @@ -0,0 +1,42 @@ +/* + * 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 com.bottlerocketstudios.vault.keys.wrapper.AbstractAndroidKeystoreSecretKeyWrapper; +import com.bottlerocketstudios.vault.keys.wrapper.AndroidOaepKeystoreSecretKeyWrapper; + +import java.security.GeneralSecurityException; + +/** + * Created on 9/21/16. + */ +public class OaepAndroidKeystoreTester extends AndroidKeystoreTester { + public OaepAndroidKeystoreTester(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 + ".oaep."; + } + + @Override + protected AbstractAndroidKeystoreSecretKeyWrapper createKeystoreSecretKeyWrapper(Context context, String testKeystoreAlias) throws GeneralSecurityException { + return new AndroidOaepKeystoreSecretKeyWrapper(context, testKeystoreAlias); + } +} diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/Pkcs1AndroidKeystoreTester.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/Pkcs1AndroidKeystoreTester.java new file mode 100644 index 0000000..95fcd71 --- /dev/null +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/storage/hardware/Pkcs1AndroidKeystoreTester.java @@ -0,0 +1,42 @@ +/* + * 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 com.bottlerocketstudios.vault.keys.wrapper.AbstractAndroidKeystoreSecretKeyWrapper; +import com.bottlerocketstudios.vault.keys.wrapper.AndroidKeystoreSecretKeyWrapper; + +import java.security.GeneralSecurityException; + +/** + * Created on 9/21/16. + */ +public class Pkcs1AndroidKeystoreTester extends AndroidKeystoreTester { + public Pkcs1AndroidKeystoreTester(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 + ".pkcs1."; + } + + @Override + protected AbstractAndroidKeystoreSecretKeyWrapper createKeystoreSecretKeyWrapper(Context context, String testKeystoreAlias) throws GeneralSecurityException { + return new AndroidKeystoreSecretKeyWrapper(context, testKeystoreAlias); + } +} diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AbstractAndroidKeystoreSecretKeyWrapper.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AbstractAndroidKeystoreSecretKeyWrapper.java index 7c90585..2f480af 100644 --- a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AbstractAndroidKeystoreSecretKeyWrapper.java +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AbstractAndroidKeystoreSecretKeyWrapper.java @@ -24,6 +24,7 @@ import android.security.keystore.KeyProperties; import com.bottlerocketstudios.vault.EncryptionConstants; +import com.bottlerocketstudios.vault.keys.generator.Aes256RandomKeyFactory; import com.bottlerocketstudios.vault.keys.storage.KeyStorageType; import java.io.IOException; @@ -33,6 +34,7 @@ import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; @@ -51,7 +53,6 @@ public abstract class AbstractAndroidKeystoreSecretKeyWrapper implements SecretK private final Context mContext; private KeyPair mKeyPair; private final String mAlias; - private String mDigests; /** * Create a wrapper using the public/private key pair with the given alias. @@ -160,7 +161,14 @@ public synchronized void clearKey(Context context) throws GeneralSecurityExcepti public boolean testKey() throws GeneralSecurityException, IOException { KeyPair keyPair = getKeyPair(); - return keyPair != null; + if (keyPair == null) return false; + + //Create a throwaway AES key to ensure that both wrap and unwrap operations work properly. + SecretKey secretKey = Aes256RandomKeyFactory.createKey(); + byte[] wrapped = wrap(secretKey); + SecretKey unwrapped = unwrap(wrapped, EncryptionConstants.AES_CIPHER); + + return unwrapped != null && Arrays.equals(unwrapped.getEncoded(), secretKey.getEncoded()); } @Override diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidKeystoreSecretKeyWrapper.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidKeystoreSecretKeyWrapper.java index 694c892..afd9869 100644 --- a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidKeystoreSecretKeyWrapper.java +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidKeystoreSecretKeyWrapper.java @@ -27,14 +27,10 @@ * the platform {@link java.security.KeyStore}. This allows us to protect symmetric keys with * hardware-backed crypto, if provided by the device. *

- * Deprecated in favor of AndroidOaepKeystoreSecretKeyWrapper - *

- *

* See key wrapping for more * details. *

*/ -@Deprecated public class AndroidKeystoreSecretKeyWrapper extends AbstractAndroidKeystoreSecretKeyWrapper { protected static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding"; protected static final String[] ENCRYPTION_PADDING; diff --git a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidOaepKeystoreSecretKeyWrapper.java b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidOaepKeystoreSecretKeyWrapper.java index a6c8537..0b423b1 100644 --- a/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidOaepKeystoreSecretKeyWrapper.java +++ b/AndroidVault/vault/src/main/java/com/bottlerocketstudios/vault/keys/wrapper/AndroidOaepKeystoreSecretKeyWrapper.java @@ -16,8 +16,8 @@ package com.bottlerocketstudios.vault.keys.wrapper; +import android.annotation.TargetApi; import android.content.Context; -import android.os.Build; import android.security.keystore.KeyProperties; import java.security.GeneralSecurityException; @@ -27,13 +27,14 @@ * the platform {@link java.security.KeyStore}. This allows us to protect symmetric keys with * hardware-backed crypto, if provided by the device. *

- * This version uses OAEP padding which is arguably more secure than PKCS1 + * This version uses OAEP padding which is only supported on API 23+ *

*

* See key wrapping for more * details. *

*/ +@TargetApi(23) public class AndroidOaepKeystoreSecretKeyWrapper extends AbstractAndroidKeystoreSecretKeyWrapper { protected static final String TRANSFORMATION = "RSA/ECB/OAEPwithSHA-256andMGF1Padding"; protected static final String[] ENCRYPTION_PADDING; @@ -41,15 +42,9 @@ public class AndroidOaepKeystoreSecretKeyWrapper extends AbstractAndroidKeystore protected static final String[] DIGESTS; static { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - ENCRYPTION_PADDING = new String[] {KeyProperties.ENCRYPTION_PADDING_RSA_OAEP}; - BLOCK_MODES = new String[] {KeyProperties.BLOCK_MODE_ECB}; - DIGESTS = new String[] {KeyProperties.DIGEST_SHA256}; - } else { - ENCRYPTION_PADDING = new String[] {"OAEPPadding"}; - BLOCK_MODES = new String[] {"ECB"}; - DIGESTS = new String[] {"SHA-256"}; - } + ENCRYPTION_PADDING = new String[] {KeyProperties.ENCRYPTION_PADDING_RSA_OAEP}; + BLOCK_MODES = new String[] {KeyProperties.BLOCK_MODE_ECB}; + DIGESTS = new String[] {KeyProperties.DIGEST_SHA256}; } /** diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d2ad8..aeeb49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Vault Changelog # -* 1.4.0 - Make OAEP padding default for wrapped keys. +* 1.4.0 - Make OAEP padding default for wrapped keys on API 23+ devices. * 1.3.1 - Catch any test failure -* 1.3.0 - Lock Screen and Memory Only - * Create a vault that can only be opened on API23+ devices which have been unlocked recently. - * Create a vault that requires a user supplied password to unlock. - * Create a method to determine which type of key storage a vault is using. +* 1.3.0 - Lock Screen and Memory Only + * Create a vault that can only be opened on API23+ devices which have been unlocked recently. + * Create a vault that requires a user supplied password to unlock. + * Create a method to determine which type of key storage a vault is using. * 1.2.5 - Open source release. * 1.2.3 - Key Caching. * Now caching the SecretKey in memory to further increase multithreaded performance with frequent reads. Better to have one SecretKey in memory than many garbage collected copies of it all over the heap.