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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<string name="dialog_title_rename">Rename</string>
|
||||
<string name="dialog_title_security_backup">Security and Backups</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_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
|
||||
you have to have \"tap to reveal\" enabled or use the copy button.\n\nThis message will not
|
||||
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>
|
||||
|
|
Loading…
Reference in a new issue