Refactor some functions in more appropriate classes
This commit is contained in:
parent
6612095e8f
commit
d68798e69a
4 changed files with 58 additions and 41 deletions
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue