Refactor some functions in more appropriate classes

This commit is contained in:
Jakob Nixdorf 2017-12-29 14:11:40 +01:00
parent 6612095e8f
commit d68798e69a
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
4 changed files with 58 additions and 41 deletions

View file

@ -23,10 +23,16 @@
package org.shadowice.flocke.andotp.Utilities; package org.shadowice.flocke.andotp.Utilities;
import android.content.Context;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; 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; import static org.shadowice.flocke.andotp.Utilities.Constants.ALGORITHM_SYMMETRIC;
public class EncryptionHelper { public class EncryptionHelper {
private final static int KEY_LENGTH = 16;
private final static int IV_LENGTH = 12; private final static int IV_LENGTH = 12;
public final static int PBKDF2_MIN_ITERATIONS = 1000; public final static int PBKDF2_MIN_ITERATIONS = 1000;
@ -151,4 +158,31 @@ public class EncryptionHelper {
return cipher.doFinal(cipherText); 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);
}
} }

View file

@ -42,7 +42,6 @@ import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
public class KeyStoreHelper { public class KeyStoreHelper {
@ -50,8 +49,6 @@ public class KeyStoreHelper {
public static final String KEYSTORE_ALIAS_WRAPPING = "settings"; public static final String KEYSTORE_ALIAS_WRAPPING = "settings";
private final static int KEY_LENGTH = 16;
public static KeyPair loadOrGenerateAsymmetricKeyPair(Context context, String alias) public static KeyPair loadOrGenerateAsymmetricKeyPair(Context context, String alias)
throws GeneralSecurityException, IOException { throws GeneralSecurityException, IOException {
final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
@ -91,42 +88,25 @@ public class KeyStoreHelper {
return new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()); 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) { public static SecretKey loadEncryptionKeyFromKeyStore(Context context) {
KeyPair pair = null;
try { try {
return loadOrGenerateWrappedKey(context, new File(context.getFilesDir() + "/" + KEY_FILE)); pair = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, KEYSTORE_ALIAS_WRAPPING);
} catch (GeneralSecurityException | IOException e) { } catch (GeneralSecurityException | IOException e) {
e.printStackTrace(); e.printStackTrace();
UIHelper.showGenericDialog(context, R.string.dialog_title_encryption_error, R.string.dialog_msg_keystore_error);
UIHelper.showGenericDialog(context, R.string.dialog_title_keystore_error, R.string.dialog_msg_keystore_error);
return null;
} }
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;
} }
} }

View file

@ -17,7 +17,6 @@
package org.shadowice.flocke.andotp.Utilities; package org.shadowice.flocke.andotp.Utilities;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@ -48,10 +47,10 @@ public class SecretKeyWrapper {
* If no pair with that alias exists, it will be generated. * If no pair with that alias exists, it will be generated.
*/ */
@SuppressLint("GetInstance") @SuppressLint("GetInstance")
public SecretKeyWrapper(Context context, String alias) public SecretKeyWrapper(KeyPair keyPair)
throws GeneralSecurityException, IOException { throws GeneralSecurityException, IOException {
mCipher = Cipher.getInstance(ALGORITHM_ASYMMETRIC); mCipher = Cipher.getInstance(ALGORITHM_ASYMMETRIC);
mPair = KeyStoreHelper.loadOrGenerateAsymmetricKeyPair(context, alias); mPair = keyPair;
} }
/** /**

View file

@ -63,7 +63,7 @@
<string name="dialog_title_rename">Rename</string> <string name="dialog_title_rename">Rename</string>
<string name="dialog_title_security_backup">Security and Backups</string> <string name="dialog_title_security_backup">Security and Backups</string>
<string name="dialog_title_last_used">Last used</string> <string name="dialog_title_last_used">Last used</string>
<string name="dialog_title_keystore_error">KeyStore error</string> <string name="dialog_title_encryption_error">Encryption error</string>
<string name="dialog_msg_auth">Please enter your device credentials to start andOTP.</string> <string name="dialog_msg_auth">Please enter your device credentials to start andOTP.</string>
<string name="dialog_msg_confirm_delete">Are you sure you want do remove the account \"%1$s\"?</string> <string name="dialog_msg_confirm_delete">Are you sure you want do remove the account \"%1$s\"?</string>
@ -84,6 +84,10 @@
<string name="dialog_msg_last_used">In order for andOTP to recognize which token was used last <string name="dialog_msg_last_used">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 you have to have \"tap to reveal\" enabled or use the copy button.\n\nThis message will not
be shown again.</string> be shown again.</string>
<string name="dialog_msg_keystore_error">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!</string> <string name="dialog_msg_keystore_error">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!</string>
<string name="dialog_msg_unwrap_error">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!</string>
</resources> </resources>