From 722ea50b685373d71ce2712fca239edc7c6e79cf Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 6 Aug 2017 18:15:47 +0200 Subject: [PATCH] Add an activity to decrypt the database --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 2 + .../main/java/me/impy/aegis/AuthActivity.java | 79 ++++++++++++++ .../impy/aegis/CustomAuthenticationSlide.java | 12 +- .../main/java/me/impy/aegis/MainActivity.java | 103 ++++++++---------- .../me/impy/aegis/crypto/CryptParameters.java | 2 +- .../me/impy/aegis/crypto/CryptoUtils.java | 2 +- .../java/me/impy/aegis/crypto/slots/Slot.java | 3 +- .../aegis/crypto/slots/SlotCollection.java | 3 +- .../me/impy/aegis/db/DatabaseManager.java | 4 + app/src/main/res/layout/activity_auth.xml | 45 ++++++++ app/src/main/res/values/strings.xml | 2 + 12 files changed, 192 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/me/impy/aegis/AuthActivity.java create mode 100644 app/src/main/res/layout/activity_auth.xml diff --git a/app/build.gradle b/app/build.gradle index 1288d2b5..2827efc5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ android { targetSdkVersion 25 versionCode 1 versionName "1.0" - jackOptions{ + jackOptions { enabled true } } @@ -29,6 +29,7 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:recyclerview-v7:25.0.0' compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.android.support:design:25.0.0' compile 'agency.tango.android:material-intro-screen:0.0.3' @@ -37,7 +38,6 @@ dependencies { compile 'com.android.support:cardview-v7:25.0.0' compile 'com.android.support:recyclerview-v7:25.0.0' compile 'com.android.support:support-v4:25.0.0' - compile 'com.android.support:recyclerview-v7:25.0.0' compile 'com.yarolegovich:lovely-dialog:1.0.4' compile 'com.mattprecious.swirl:swirl:1.0.0' testCompile 'junit:junit:4.12' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bca640b5..55887724 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,8 @@ android:label="@string/title_activity_intro" android:theme="@style/Theme.Intro"> + + diff --git a/app/src/main/java/me/impy/aegis/AuthActivity.java b/app/src/main/java/me/impy/aegis/AuthActivity.java new file mode 100644 index 00000000..d0cf80a3 --- /dev/null +++ b/app/src/main/java/me/impy/aegis/AuthActivity.java @@ -0,0 +1,79 @@ +package me.impy.aegis; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.Editable; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import java.lang.reflect.UndeclaredThrowableException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; + +import me.impy.aegis.crypto.CryptoUtils; +import me.impy.aegis.crypto.MasterKey; +import me.impy.aegis.crypto.slots.PasswordSlot; +import me.impy.aegis.crypto.slots.Slot; +import me.impy.aegis.crypto.slots.SlotCollection; + +public class AuthActivity extends AppCompatActivity { + public static final int RESULT_OK = 0; + public static final int RESULT_EXCEPTION = 1; + + private EditText textPassword; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_auth); + textPassword = (EditText) findViewById(R.id.text_password); + + Intent intent = getIntent(); + final SlotCollection slots = (SlotCollection) intent.getSerializableExtra("slots"); + + Button button = (Button) findViewById(R.id.button_decrypt); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MasterKey masterKey = null; + try { + if (slots.has(PasswordSlot.class)) { + PasswordSlot slot = slots.find(PasswordSlot.class); + char[] password = getPassword(true); + SecretKey key = slot.deriveKey(password); + CryptoUtils.zero(password); + Cipher cipher = Slot.createCipher(key, Cipher.DECRYPT_MODE); + masterKey = MasterKey.decryptSlot(slot, cipher); + } + } catch (Exception e) { + // TODO: feedback + throw new UndeclaredThrowableException(e); + } + + // send the master key back to the main activity + Intent result = new Intent(); + result.putExtra("key", masterKey); + setResult(RESULT_OK, result); + finish(); + } + }); + } + + private char[] getPassword(boolean clear) { + char[] password = getEditTextChars(textPassword); + if (clear) { + textPassword.getText().clear(); + } + return password; + } + + private static char[] getEditTextChars(EditText text) { + Editable editable = text.getText(); + char[] chars = new char[editable.length()]; + editable.getChars(0, editable.length(), chars, 0); + return chars; + } +} diff --git a/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java b/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java index 17369f59..e9515886 100644 --- a/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java +++ b/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java @@ -1,5 +1,6 @@ package me.impy.aegis; +import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -19,10 +20,13 @@ public class CustomAuthenticationSlide extends SlideFragment { buttonGroup = (RadioGroup) view.findViewById(R.id.rg_authenticationMethod); RadioButton button = (RadioButton) view.findViewById(R.id.rb_fingerprint); - button.setOnClickListener(v -> { - if (canMoveFurther()) { - buttonGroup.clearCheck(); - Toast.makeText(getActivity().getBaseContext(), "Fingerprint is not supported yet", Toast.LENGTH_SHORT).show(); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (canMoveFurther()) { + buttonGroup.clearCheck(); + Toast.makeText(CustomAuthenticationSlide.this.getActivity(), "Fingerprint is not supported yet", Toast.LENGTH_SHORT).show(); + } } }); diff --git a/app/src/main/java/me/impy/aegis/MainActivity.java b/app/src/main/java/me/impy/aegis/MainActivity.java index 735c578b..d7518612 100644 --- a/app/src/main/java/me/impy/aegis/MainActivity.java +++ b/app/src/main/java/me/impy/aegis/MainActivity.java @@ -54,6 +54,7 @@ public class MainActivity extends AppCompatActivity { private static final int CODE_GET_KEYINFO = 0; private static final int CODE_ADD_KEYINFO = 1; private static final int CODE_DO_INTRO = 2; + private static final int CODE_DECRYPT = 3; RecyclerView rvKeyProfiles; KeyProfileAdapter mKeyProfileAdapter; @@ -72,6 +73,18 @@ public class MainActivity extends AppCompatActivity { if (!prefs.getBoolean("passedIntro", false)) { Intent intro = new Intent(this, IntroActivity.class); startActivityForResult(intro, CODE_DO_INTRO); + } else { + try { + db.load(); + } catch (Exception e) { + // TODO: feedback + throw new UndeclaredThrowableException(e); + } + if (!db.isDecrypted()) { + Intent intent = new Intent(this, AuthActivity.class); + intent.putExtra("slots", db.getFile().getSlots()); + startActivityForResult(intent, CODE_DECRYPT); + } } SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -132,6 +145,9 @@ public class MainActivity extends AppCompatActivity { case CODE_DO_INTRO: onDoIntroResult(resultCode, data); break; + case CODE_DECRYPT: + onDecryptResult(resultCode, data); + break; } } @@ -194,6 +210,20 @@ public class MainActivity extends AppCompatActivity { // TODO: feedback throw new UndeclaredThrowableException(e); } + + loadKeyProfiles(); + } + + private void onDecryptResult(int resultCode, Intent data) { + MasterKey key = (MasterKey) data.getSerializableExtra("key"); + try { + db.setMasterKey(key); + } catch (Exception e) { + // TODO: feedback + throw new UndeclaredThrowableException(e); + } + + loadKeyProfiles(); } @Override @@ -304,7 +334,7 @@ public class MainActivity extends AppCompatActivity { { Log.println(Log.DEBUG, "OKK ", "OKKK"); Intent scannerActivity = new Intent(getApplicationContext(), ScannerActivity.class); - startActivityForResult(scannerActivity, GET_KEYINFO); + startActivityForResult(scannerActivity, CODE_GET_KEYINFO); } } @@ -356,65 +386,11 @@ public class MainActivity extends AppCompatActivity { } } - /*private void loadDatabase() { - try { - databaseFile = DatabaseFile.load(getApplicationContext()); - } catch (IOException e) { - // the database file doesn't exist yet - //createDatabase(); - saveDatabase(); - return; - } catch (Exception e) { - // something else went wrong - throw new UndeclaredThrowableException(e); - } - - byte[] content = databaseFile.getContent(); - if (databaseFile.isEncrypted()) { - try { - SlotCollection slots = databaseFile.getSlots(); - // look up slots in order of preference - if (slots.has(FingerprintSlot.class)) { - FingerprintSlot slot = slots.find(FingerprintSlot.class); - } else if (slots.has(PasswordSlot.class)) { - PasswordSlot slot = slots.find(PasswordSlot.class); - SecretKey derivedKey = slot.deriveKey("testpassword".toCharArray()); - Cipher cipher = Slot.createCipher(derivedKey, Cipher.DECRYPT_MODE); - masterKey = MasterKey.decryptSlot(slot, cipher); - //} else if (slots.has(RawSlot.class)) { - } else { - throw new Exception("the slot collection doesn't contain any supported slot types"); - } - } catch (Exception e) { - throw new UndeclaredThrowableException(e); - } - - CryptResult result; - try { - result = masterKey.decrypt(content, databaseFile.getCryptParameters()); - } catch (Exception e) { - throw new UndeclaredThrowableException(e); - } - - content = result.Data; - } - - database = new Database(); - try { - database.deserialize(content); - } catch (Exception e) { - throw new UndeclaredThrowableException(e); - } - - try { - mKeyProfiles.addAll(database.getKeys()); - mKeyProfileAdapter.notifyDataSetChanged(); - } catch (Exception e) { - throw new UndeclaredThrowableException(e); - } - }*/ - private void saveDatabase() { + if (!db.isDecrypted()) { + return; + } + try { db.save(); } catch (Exception e) { @@ -423,6 +399,15 @@ public class MainActivity extends AppCompatActivity { } } + private void loadKeyProfiles() { + try { + mKeyProfiles.addAll(db.getKeys()); + mKeyProfileAdapter.notifyDataSetChanged(); + } catch (Exception e) { + e.printStackTrace(); + } + } + private boolean causeIsKeyUserNotAuthenticated(Exception e) { // TODO: is there a way to catch "Key user not authenticated" specifically aside from checking the exception message? return e.getCause().getMessage().equals("Key user not authenticated"); diff --git a/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java b/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java index 4c3140da..c5971102 100644 --- a/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java +++ b/app/src/main/java/me/impy/aegis/crypto/CryptParameters.java @@ -3,4 +3,4 @@ package me.impy.aegis.crypto; public class CryptParameters { public byte[] Nonce; public byte[] Tag; -} \ No newline at end of file +} diff --git a/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java b/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java index 0334fc0d..4938bbbe 100644 --- a/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java +++ b/app/src/main/java/me/impy/aegis/crypto/CryptoUtils.java @@ -26,7 +26,7 @@ public class CryptoUtils { public static final byte CRYPTO_NONCE_SIZE = 12; public static final byte CRYPTO_SALT_SIZE = 32; // TODO: decide on a 'secure-enough' iteration count - public static final short CRYPTO_ITERATION_COUNT = 2000; + public static final short CRYPTO_ITERATION_COUNT = 10000; public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding"; public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding"; // TODO: use a separate library for an HMAC-SHA256 implementation diff --git a/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java b/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java index 84876020..11b8d2ec 100644 --- a/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java +++ b/app/src/main/java/me/impy/aegis/crypto/slots/Slot.java @@ -2,6 +2,7 @@ package me.impy.aegis.crypto.slots; import android.annotation.SuppressLint; +import java.io.Serializable; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -14,7 +15,7 @@ import javax.crypto.spec.SecretKeySpec; import me.impy.aegis.crypto.CryptoUtils; -public abstract class Slot { +public abstract class Slot implements Serializable { public final static byte TYPE_RAW = 0x00; public final static byte TYPE_DERIVED = 0x01; public final static byte TYPE_FINGERPRINT = 0x02; diff --git a/app/src/main/java/me/impy/aegis/crypto/slots/SlotCollection.java b/app/src/main/java/me/impy/aegis/crypto/slots/SlotCollection.java index 7422c568..c88ca69a 100644 --- a/app/src/main/java/me/impy/aegis/crypto/slots/SlotCollection.java +++ b/app/src/main/java/me/impy/aegis/crypto/slots/SlotCollection.java @@ -1,12 +1,13 @@ package me.impy.aegis.crypto.slots; +import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import me.impy.aegis.util.LittleByteBuffer; -public class SlotCollection implements Iterable { +public class SlotCollection implements Iterable, Serializable { private List _slots = new ArrayList<>(); public static byte[] serialize(SlotCollection slots) { diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java index 04af6f0a..981fd5f1 100644 --- a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java +++ b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java @@ -66,6 +66,10 @@ public class DatabaseManager { return _db.getKeys(); } + public DatabaseFile getFile() { + return _file; + } + public boolean isLoaded() { return _file != null; } diff --git a/app/src/main/res/layout/activity_auth.xml b/app/src/main/res/layout/activity_auth.xml new file mode 100644 index 00000000..8dcd4e08 --- /dev/null +++ b/app/src/main/res/layout/activity_auth.xml @@ -0,0 +1,45 @@ + + + + + + + +