diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/EncryptionHelper.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/EncryptionHelper.java index 57b37edb..437967ec 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/EncryptionHelper.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/EncryptionHelper.java @@ -23,10 +23,16 @@ package org.shadowice.flocke.andotp.Utilities; +import android.content.Context; + +import java.io.File; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.KeyPair; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -51,6 +57,7 @@ import static org.shadowice.flocke.andotp.Utilities.Constants.ALGORITHM_ASYMMETR import static org.shadowice.flocke.andotp.Utilities.Constants.ALGORITHM_SYMMETRIC; public class EncryptionHelper { + private final static int KEY_LENGTH = 16; private final static int IV_LENGTH = 12; public final static int PBKDF2_MIN_ITERATIONS = 1000; @@ -151,4 +158,31 @@ public class EncryptionHelper { return cipher.doFinal(cipherText); } + + /** + * Load our symmetric secret key. + * The symmetric secret key is stored securely on disk by wrapping + * it with a public/private key pair, possibly backed by hardware. + */ + public static SecretKey loadOrGenerateWrappedKey(Context context, File keyFile, KeyPair keyPair) + throws GeneralSecurityException, IOException { + final SecretKeyWrapper wrapper = new SecretKeyWrapper(keyPair); + + // Generate secret key if none exists + if (!keyFile.exists()) { + final byte[] raw = EncryptionHelper.generateRandom(KEY_LENGTH); + + final SecretKey key = new SecretKeySpec(raw, "AES"); + final byte[] wrapped = wrapper.wrap(key); + + + FileHelper.writeBytesToFile(keyFile, wrapped); + } + + // Even if we just generated the key, always read it back to ensure we + // can read it successfully. + final byte[] wrapped = FileHelper.readFileToBytes(keyFile); + + return wrapper.unwrap(wrapped); + } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/KeyStoreHelper.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/KeyStoreHelper.java index d8490d87..06a14b98 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/KeyStoreHelper.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/KeyStoreHelper.java @@ -42,7 +42,6 @@ import java.util.Calendar; import java.util.GregorianCalendar; import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import javax.security.auth.x500.X500Principal; public class KeyStoreHelper { @@ -50,8 +49,6 @@ public class KeyStoreHelper { public static final String KEYSTORE_ALIAS_WRAPPING = "settings"; - private final static int KEY_LENGTH = 16; - public static KeyPair loadOrGenerateAsymmetricKeyPair(Context context, String alias) throws GeneralSecurityException, IOException { final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); @@ -91,42 +88,25 @@ public class KeyStoreHelper { return new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()); } - /** - * Load our symmetric secret key. - * The symmetric secret key is stored securely on disk by wrapping - * it with a public/private key pair, possibly backed by hardware. - */ - public static SecretKey loadOrGenerateWrappedKey(Context context, File keyFile) - throws GeneralSecurityException, IOException { - final SecretKeyWrapper wrapper = new SecretKeyWrapper(context, KEYSTORE_ALIAS_WRAPPING); - - // Generate secret key if none exists - if (!keyFile.exists()) { - final byte[] raw = EncryptionHelper.generateRandom(KEY_LENGTH); - - final SecretKey key = new SecretKeySpec(raw, "AES"); - final byte[] wrapped = wrapper.wrap(key); - - - FileHelper.writeBytesToFile(keyFile, wrapped); - } - - // Even if we just generated the key, always read it back to ensure we - // can read it successfully. - final byte[] wrapped = FileHelper.readFileToBytes(keyFile); - - return wrapper.unwrap(wrapped); - } - public static SecretKey loadEncryptionKeyFromKeyStore(Context context) { + KeyPair pair = null; + try { - return loadOrGenerateWrappedKey(context, new File(context.getFilesDir() + "/" + KEY_FILE)); + pair = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, KEYSTORE_ALIAS_WRAPPING); } catch (GeneralSecurityException | IOException e) { e.printStackTrace(); - - UIHelper.showGenericDialog(context, R.string.dialog_title_keystore_error, R.string.dialog_msg_keystore_error); - - return null; + UIHelper.showGenericDialog(context, R.string.dialog_title_encryption_error, R.string.dialog_msg_keystore_error); } + + if (pair != null) { + try { + return EncryptionHelper.loadOrGenerateWrappedKey(context, new File(context.getFilesDir() + "/" + KEY_FILE), pair); + } catch (GeneralSecurityException | IOException e) { + e.printStackTrace(); + UIHelper.showGenericDialog(context, R.string.dialog_title_encryption_error, R.string.dialog_msg_unwrap_error); + } + } + + return null; } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/SecretKeyWrapper.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/SecretKeyWrapper.java index 0e6be63f..3b9a4bbf 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/SecretKeyWrapper.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/SecretKeyWrapper.java @@ -17,7 +17,6 @@ package org.shadowice.flocke.andotp.Utilities; import android.annotation.SuppressLint; -import android.content.Context; import java.io.IOException; import java.security.GeneralSecurityException; @@ -48,10 +47,10 @@ public class SecretKeyWrapper { * If no pair with that alias exists, it will be generated. */ @SuppressLint("GetInstance") - public SecretKeyWrapper(Context context, String alias) + public SecretKeyWrapper(KeyPair keyPair) throws GeneralSecurityException, IOException { mCipher = Cipher.getInstance(ALGORITHM_ASYMMETRIC); - mPair = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, alias); + mPair = keyPair; } /** diff --git a/app/src/main/res/values/strings_main.xml b/app/src/main/res/values/strings_main.xml index 5453bdd2..935db31a 100644 --- a/app/src/main/res/values/strings_main.xml +++ b/app/src/main/res/values/strings_main.xml @@ -63,7 +63,7 @@ Rename Security and Backups Last used - KeyStore error + Encryption error Please enter your device credentials to start andOTP. Are you sure you want do remove the account \"%1$s\"? @@ -84,6 +84,10 @@ In order for andOTP to recognize which token was used last you have to have \"tap to reveal\" enabled or use the copy button.\n\nThis message will not be shown again. - Failed to load the encryption key from the KeyStore. - You won\'t be able to use this app. Please contact the developer and provide a logcat! + + Failed to generate or load the wrapping key from the + KeyStore. You won\'t be able to use this app. Please contact the developer and provide a + logcat! + Failed to generate or unwrap the encryption key. You + won\'t be able to use this app. Please contact the developer and provide a logcat!