diff --git a/.idea/misc.xml b/.idea/misc.xml
index fbb68289..44bc9b2a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -37,7 +37,7 @@
-
+
diff --git a/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java b/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java
index dce7f57d..6132a5e3 100644
--- a/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java
+++ b/app/src/main/java/me/impy/aegis/CustomAuthenticatedSlide.java
@@ -1,30 +1,35 @@
package me.impy.aegis;
-import android.hardware.fingerprint.FingerprintManager;
-import android.os.Build;
-import android.support.annotation.RequiresApi;
-import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
+import android.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.RadioButton;
+import android.widget.EditText;
-import com.mattprecious.swirl.SwirlView;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
import agency.tango.materialintroscreen.SlideFragment;
-import me.impy.aegis.finger.FingerprintAuthenticationDialogFragment;
+import me.impy.aegis.crypto.CryptoUtils;
+import me.impy.aegis.crypto.slots.PasswordSlot;
+import me.impy.aegis.crypto.slots.Slot;
public class CustomAuthenticatedSlide extends SlideFragment {
- private CheckBox checkBox;
- private RadioButton passwordRadioButton;
+ private EditText textPassword;
+ private EditText textPasswordConfirm;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.fragment_authenticated_slide, container, false);
+ View view = inflater.inflate(R.layout.fragment_authenticated_slide, container, false);
+ textPassword = (EditText) view.findViewById(R.id.text_password);
+ textPasswordConfirm = (EditText) view.findViewById(R.id.text_password_confirm);
return view;
}
@@ -40,16 +45,42 @@ public class CustomAuthenticatedSlide extends SlideFragment {
@Override
public boolean canMoveFurther() {
- return true; //checkBox.isChecked();
- }
-
- public void onAuthenticated(FingerprintAuthenticationDialogFragment.Action action, FingerprintManager.CryptoObject obj) {
-
+ char[] password = getEditTextChars(textPassword);
+ char[] passwordConfirm = getEditTextChars(textPasswordConfirm);
+ boolean equal = password.length != 0 && Arrays.equals(password, passwordConfirm);
+ CryptoUtils.zero(password);
+ CryptoUtils.zero(passwordConfirm);
+ return equal;
}
@Override
public String cantMoveFurtherErrorMessage() {
- return "Ja bijna vriend";
- //return getString(R.string.error_message);
+ return "Passwords should be equal and non-empty";
+ }
+
+ public Cipher getCipher(PasswordSlot slot, int mode)
+ throws InvalidKeySpecException, NoSuchAlgorithmException,
+ InvalidKeyException, NoSuchPaddingException {
+ char[] password = getPassword(true);
+ byte[] salt = CryptoUtils.generateSalt();
+ SecretKey key = slot.deriveKey(password, salt, CryptoUtils.CRYPTO_ITERATION_COUNT);
+ CryptoUtils.zero(password);
+
+ return Slot.createCipher(key, mode);
+ }
+
+ 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 c3f1d3b8..17369f59 100644
--- a/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java
+++ b/app/src/main/java/me/impy/aegis/CustomAuthenticationSlide.java
@@ -1,53 +1,31 @@
package me.impy.aegis;
-import android.app.Activity;
-import android.content.Intent;
-import android.hardware.fingerprint.FingerprintManager;
-import android.os.Build;
import android.os.Bundle;
-import android.support.annotation.RequiresApi;
-import android.app.DialogFragment;
-import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.RadioGroup;
-
-import com.mattprecious.swirl.SwirlView;
+import android.widget.Toast;
import agency.tango.materialintroscreen.SlideFragment;
-import me.impy.aegis.finger.FingerprintAuthenticationDialogFragment;
-import me.impy.aegis.finger.SetFingerprintAuthenticationDialog;
-public class CustomAuthenticationSlide extends SlideFragment implements SetFingerprintAuthenticationDialog.InterfaceCommunicator{
- private CheckBox checkBox;
- private RadioButton fingerprintRadioButton;
- private RadioGroup authenticationMethodRadioGroup;
-
- public static final int DIALOG_FRAGMENT = 1;
+public class CustomAuthenticationSlide extends SlideFragment {
+ private RadioGroup buttonGroup;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_authentication_slide, container, false);
- final CustomAuthenticationSlide caller = this;
+ buttonGroup = (RadioGroup) view.findViewById(R.id.rg_authenticationMethod);
- fingerprintRadioButton = (RadioButton) view.findViewById(R.id.rb_fingerprint);
- fingerprintRadioButton.setOnClickListener(new View.OnClickListener() {
- @RequiresApi(api = Build.VERSION_CODES.M)
- @Override
- public void onClick(View view) {
- SetFingerprintAuthenticationDialog fragment = new SetFingerprintAuthenticationDialog();
- //fragment.setCryptoObject(new FingerprintManager.CryptoObject(cipher));
- fragment.setStage(SetFingerprintAuthenticationDialog.Stage.FINGERPRINT);
- fragment.setCaller(caller);
- //fragment.setAction(action);
- fragment.show(getActivity().getFragmentManager(), "dialog");
+ 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();
}
});
- authenticationMethodRadioGroup = (RadioGroup) view.findViewById(R.id.rg_authenticationMethod);
return view;
}
@@ -63,27 +41,11 @@ public class CustomAuthenticationSlide extends SlideFragment implements SetFinge
@Override
public boolean canMoveFurther() {
- return authenticationMethodRadioGroup.getCheckedRadioButtonId() != -1;
+ return buttonGroup.getCheckedRadioButtonId() != -1;
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
-
- }
-
-
@Override
public String cantMoveFurtherErrorMessage() {
return "Please select an authentication method";
- //return getString(R.string.error_message);
}
-
- @Override
- public void sendRequestCode(int code) {
- if (code == 1) {
-
- } else if (code == 0){
- authenticationMethodRadioGroup.clearCheck();
- }
- }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/me/impy/aegis/IntroActivity.java b/app/src/main/java/me/impy/aegis/IntroActivity.java
index a0d3e6be..c0a08a87 100644
--- a/app/src/main/java/me/impy/aegis/IntroActivity.java
+++ b/app/src/main/java/me/impy/aegis/IntroActivity.java
@@ -2,16 +2,32 @@ package me.impy.aegis;
import android.Manifest;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+
import agency.tango.materialintroscreen.MaterialIntroActivity;
import agency.tango.materialintroscreen.MessageButtonBehaviour;
import agency.tango.materialintroscreen.SlideFragmentBuilder;
+import me.impy.aegis.crypto.CryptResult;
+import me.impy.aegis.crypto.MasterKey;
+import me.impy.aegis.crypto.slots.PasswordSlot;
+import me.impy.aegis.crypto.slots.SlotCollection;
+import me.impy.aegis.db.Database;
+import me.impy.aegis.db.DatabaseFile;
public class IntroActivity extends MaterialIntroActivity {
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_EXCEPTION = 1;
+
+ private CustomAuthenticatedSlide authenticatedSlide;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -41,12 +57,64 @@ public class IntroActivity extends MaterialIntroActivity {
}, "Permission granted"));
addSlide(new CustomAuthenticationSlide());
- addSlide(new CustomAuthenticatedSlide());
+
+ authenticatedSlide = new CustomAuthenticatedSlide();
+ addSlide(authenticatedSlide);
+
+ addSlide(new SlideFragmentBuilder()
+ .backgroundColor(R.color.colorPrimary)
+ .buttonsColor(R.color.colorAccent)
+ .image(R.drawable.intro_shield)
+ .title("All done!")
+ .description("Aegis has been set up and is ready to go.")
+ .build());
+ }
+
+ private void setException(Exception e) {
+ Intent result = new Intent();
+ result.putExtra("exception", e);
+ setResult(RESULT_OK, result);
}
@Override
public void onFinish() {
super.onFinish();
+
+ // create the database and database file
+ Database database = new Database();
+ DatabaseFile databaseFile = new DatabaseFile();
+
+ MasterKey masterKey;
+ try {
+ // generate the master key
+ masterKey = MasterKey.generate();
+
+ // encrypt the master key with a key derived from the user's password
+ // and add it to the list of slots
+ SlotCollection slots = databaseFile.getSlots();
+ PasswordSlot slot = new PasswordSlot();
+ Cipher cipher = authenticatedSlide.getCipher(slot, Cipher.ENCRYPT_MODE);
+ masterKey.encryptSlot(slot, cipher);
+ slots.add(slot);
+
+ // finally, save the database
+ byte[] bytes = database.serialize();
+ CryptResult result = masterKey.encrypt(bytes);
+ databaseFile.setContent(result.Data);
+ databaseFile.setCryptParameters(result.Parameters);
+ databaseFile.save(getApplicationContext());
+ } catch (Exception e) {
+ setException(e);
+ return;
+ }
+
+ // send the master key back to the main activity
+ Intent result = new Intent();
+ result.putExtra("key", masterKey);
+ setResult(RESULT_OK, result);
+
+ // skip the intro from now on
+ // TODO: show the intro if we can't find any database files
SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE);
prefs.edit().putBoolean("passedIntro", true).apply();
}
diff --git a/app/src/main/java/me/impy/aegis/MainActivity.java b/app/src/main/java/me/impy/aegis/MainActivity.java
index c30cd0b2..735c578b 100644
--- a/app/src/main/java/me/impy/aegis/MainActivity.java
+++ b/app/src/main/java/me/impy/aegis/MainActivity.java
@@ -10,7 +10,6 @@ import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
-import android.hardware.fingerprint.FingerprintManager;
import android.preference.PreferenceManager;
import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.FloatingActionButton;
@@ -30,7 +29,6 @@ import android.widget.Toast;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -41,30 +39,26 @@ import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import me.impy.aegis.crypto.CryptResult;
-import me.impy.aegis.crypto.CryptoUtils;
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.otp.OTP;
import me.impy.aegis.crypto.slots.FingerprintSlot;
import me.impy.aegis.crypto.slots.PasswordSlot;
-import me.impy.aegis.crypto.slots.RawSlot;
import me.impy.aegis.crypto.slots.Slot;
import me.impy.aegis.crypto.slots.SlotCollection;
import me.impy.aegis.db.Database;
import me.impy.aegis.db.DatabaseFile;
-import me.impy.aegis.finger.FingerprintAuthenticationDialogFragment;
+import me.impy.aegis.db.DatabaseManager;
import me.impy.aegis.helpers.SimpleItemTouchHelperCallback;
public class MainActivity extends AppCompatActivity {
-
- static final int GET_KEYINFO = 1;
- static final int ADD_KEYINFO = 2;
+ private static final int CODE_GET_KEYINFO = 0;
+ private static final int CODE_ADD_KEYINFO = 1;
+ private static final int CODE_DO_INTRO = 2;
RecyclerView rvKeyProfiles;
KeyProfileAdapter mKeyProfileAdapter;
ArrayList mKeyProfiles = new ArrayList<>();
- MasterKey masterKey;
- Database database;
- DatabaseFile databaseFile;
+ private DatabaseManager db;
boolean nightMode = false;
int clickedItemPosition = -1;
@@ -72,11 +66,12 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ db = new DatabaseManager(getApplicationContext());
SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE);
if (!prefs.getBoolean("passedIntro", false)) {
Intent intro = new Intent(this, IntroActivity.class);
- startActivity(intro);
+ startActivityForResult(intro, CODE_DO_INTRO);
}
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@@ -95,7 +90,7 @@ public class MainActivity extends AppCompatActivity {
fab.setEnabled(true);
fab.setOnClickListener(view -> {
Intent scannerActivity = new Intent(getApplicationContext(), ScannerActivity.class);
- startActivityForResult(scannerActivity, GET_KEYINFO);
+ startActivityForResult(scannerActivity, CODE_GET_KEYINFO);
});
rvKeyProfiles = (RecyclerView) findViewById(R.id.rvKeyProfiles);
@@ -123,47 +118,81 @@ public class MainActivity extends AppCompatActivity {
}
};
Collections.sort(mKeyProfiles, comparator);
-
- loadDatabase();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == GET_KEYINFO) {
- if (resultCode == RESULT_OK) {
- final KeyProfile keyProfile = (KeyProfile)data.getSerializableExtra("KeyProfile");
-
- Intent intent = new Intent(this, AddProfileActivity.class);
- intent.putExtra("KeyProfile", keyProfile);
- startActivityForResult(intent, ADD_KEYINFO);
- }
+ switch (requestCode) {
+ case CODE_GET_KEYINFO:
+ onGetKeyInfoResult(resultCode, data);
+ break;
+ case CODE_ADD_KEYINFO:
+ onAddKeyInfoResult(resultCode, data);
+ break;
+ case CODE_DO_INTRO:
+ onDoIntroResult(resultCode, data);
+ break;
}
- else if (requestCode == ADD_KEYINFO) {
- if (resultCode == RESULT_OK) {
- final KeyProfile keyProfile = (KeyProfile) data.getSerializableExtra("KeyProfile");
+ }
- String otp;
- try {
- otp = OTP.generateOTP(keyProfile.Info);
- } catch (Exception e) {
- e.printStackTrace();
- return;
- }
+ private void onGetKeyInfoResult(int resultCode, Intent data) {
+ if (resultCode != RESULT_OK) {
+ return;
+ }
- keyProfile.Order = mKeyProfiles.size() + 1;
- keyProfile.Code = otp;
- try {
- database.addKey(keyProfile);
- } catch (Exception e) {
- e.printStackTrace();
- // TODO: feedback
- return;
- }
+ final KeyProfile keyProfile = (KeyProfile)data.getSerializableExtra("KeyProfile");
- mKeyProfiles.add(keyProfile);
- mKeyProfileAdapter.notifyDataSetChanged();
- saveDatabase();
+ Intent intent = new Intent(this, AddProfileActivity.class);
+ intent.putExtra("KeyProfile", keyProfile);
+ startActivityForResult(intent, CODE_ADD_KEYINFO);
+ }
+
+ private void onAddKeyInfoResult(int resultCode, Intent data) {
+ if (resultCode != RESULT_OK) {
+ return;
+ }
+
+ final KeyProfile keyProfile = (KeyProfile) data.getSerializableExtra("KeyProfile");
+
+ String otp;
+ try {
+ otp = OTP.generateOTP(keyProfile.Info);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return;
+ }
+
+ keyProfile.Order = mKeyProfiles.size() + 1;
+ keyProfile.Code = otp;
+ try {
+ db.addKey(keyProfile);
+ } catch (Exception e) {
+ e.printStackTrace();
+ // TODO: feedback
+ return;
+ }
+
+ mKeyProfiles.add(keyProfile);
+ mKeyProfileAdapter.notifyDataSetChanged();
+ saveDatabase();
+ }
+
+ private void onDoIntroResult(int resultCode, Intent data) {
+ if (resultCode == IntroActivity.RESULT_EXCEPTION) {
+ // TODO: user feedback
+ Exception e = (Exception) data.getSerializableExtra("exception");
+ throw new UndeclaredThrowableException(e);
+ }
+
+ MasterKey key = (MasterKey) data.getSerializableExtra("key");
+ try {
+ db.load();
+ if (!db.isDecrypted()) {
+ db.setMasterKey(key);
}
+ } catch (Exception e) {
+ // TODO: feedback
+ throw new UndeclaredThrowableException(e);
}
}
@@ -179,53 +208,16 @@ public class MainActivity extends AppCompatActivity {
// update order of keys
for (int i = 0; i < mKeyProfiles.size(); i++) {
try {
- database.updateKey(mKeyProfiles.get(i));
+ db.updateKey(mKeyProfiles.get(i));
} catch (Exception e) {
e.printStackTrace();
}
}
saveDatabase();
-
super.onPause();
}
- private void promptFingerPrint(FingerprintAuthenticationDialogFragment.Action action, Cipher cipher) {
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
- FingerprintAuthenticationDialogFragment fragment = new FingerprintAuthenticationDialogFragment();
- fragment.setCryptoObject(new FingerprintManager.CryptoObject(cipher));
- fragment.setStage(FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT);
- fragment.setAction(action);
- fragment.show(getFragmentManager(), "");
- }
- }
-
- public void onAuthenticated(FingerprintAuthenticationDialogFragment.Action action, FingerprintManager.CryptoObject obj) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- Cipher cipher = obj.getCipher();
- switch (action) {
- case SAVE:
- saveDatabase();
- break;
- case LOAD:
- loadDatabase();
- break;
- }
- }
- }
-
- private void saveDatabase() {
- try {
- byte[] bytes = database.serialize();
- CryptResult result = masterKey.encrypt(bytes);
- databaseFile.setContent(result.Data);
- databaseFile.setCryptParameters(result.Parameters);
- databaseFile.save(getApplicationContext());
- } catch (Exception e) {
- throw new UndeclaredThrowableException(e);
- }
- }
-
private BottomSheetDialog InitializeBottomSheet()
{
View bottomSheetView = getLayoutInflater ().inflate (R.layout.bottom_sheet_edit_profile, null);
@@ -271,7 +263,7 @@ public class MainActivity extends AppCompatActivity {
.setMessage("Are you sure you want to delete this profile?")
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
try {
- database.removeKey(profile);
+ db.removeKey(profile);
} catch (Exception e) {
e.printStackTrace();
//TODO: feedback
@@ -364,37 +356,12 @@ public class MainActivity extends AppCompatActivity {
}
}
- private void createDatabase() {
- database = new Database();
- databaseFile = new DatabaseFile();
-
- try {
- masterKey = MasterKey.generate();
- } catch (NoSuchAlgorithmException e) {
- // TODO: tell the user to stop using a weird platform
- throw new UndeclaredThrowableException(e);
- }
-
- SlotCollection slots = databaseFile.getSlots();
-
- try {
- PasswordSlot slot = new PasswordSlot();
- byte[] salt = CryptoUtils.generateSalt();
- SecretKey derivedKey = slot.deriveKey("testpassword".toCharArray(), salt, CryptoUtils.CRYPTO_ITERATION_COUNT);
- Cipher cipher = Slot.createCipher(derivedKey, Cipher.ENCRYPT_MODE);
- masterKey.encryptSlot(slot, cipher);
- slots.add(slot);
- } catch (Exception e) {
- throw new UndeclaredThrowableException(e);
- }
- }
-
- private void loadDatabase() {
+ /*private void loadDatabase() {
try {
databaseFile = DatabaseFile.load(getApplicationContext());
} catch (IOException e) {
// the database file doesn't exist yet
- createDatabase();
+ //createDatabase();
saveDatabase();
return;
} catch (Exception e) {
@@ -445,6 +412,15 @@ public class MainActivity extends AppCompatActivity {
} catch (Exception e) {
throw new UndeclaredThrowableException(e);
}
+ }*/
+
+ private void saveDatabase() {
+ try {
+ db.save();
+ } catch (Exception e) {
+ //TODO: feedback
+ throw new UndeclaredThrowableException(e);
+ }
}
private boolean causeIsKeyUserNotAuthenticated(Exception e) {
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 4938bbbe..0334fc0d 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 = 10000;
+ public static final short CRYPTO_ITERATION_COUNT = 2000;
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/MasterKey.java b/app/src/main/java/me/impy/aegis/crypto/MasterKey.java
index b3f268c0..84079d2b 100644
--- a/app/src/main/java/me/impy/aegis/crypto/MasterKey.java
+++ b/app/src/main/java/me/impy/aegis/crypto/MasterKey.java
@@ -1,6 +1,7 @@
package me.impy.aegis.crypto;
import java.io.IOException;
+import java.io.Serializable;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -13,7 +14,7 @@ import javax.crypto.SecretKey;
import me.impy.aegis.crypto.slots.Slot;
-public class MasterKey {
+public class MasterKey implements Serializable {
private SecretKey _key;
public MasterKey(SecretKey key) {
diff --git a/app/src/main/java/me/impy/aegis/db/DatabaseManager.java b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java
new file mode 100644
index 00000000..04af6f0a
--- /dev/null
+++ b/app/src/main/java/me/impy/aegis/db/DatabaseManager.java
@@ -0,0 +1,89 @@
+package me.impy.aegis.db;
+
+import android.content.Context;
+
+import java.util.List;
+
+import me.impy.aegis.KeyProfile;
+import me.impy.aegis.crypto.CryptParameters;
+import me.impy.aegis.crypto.CryptResult;
+import me.impy.aegis.crypto.MasterKey;
+
+public class DatabaseManager {
+ private MasterKey _key;
+ private DatabaseFile _file;
+ private Database _db;
+ private Context _context;
+
+ public DatabaseManager(Context context) {
+ _context = context;
+ }
+
+ public void load() throws Exception {
+ _file = DatabaseFile.load(_context);
+ if (!_file.isEncrypted()) {
+ byte[] bytes = _file.getContent();
+ _db = new Database();
+ _db.deserialize(bytes);
+ }
+ }
+
+ public void setMasterKey(MasterKey key) throws Exception {
+ byte[] encrypted = _file.getContent();
+ CryptParameters params = _file.getCryptParameters();
+ CryptResult result = key.decrypt(encrypted, params);
+ _db = new Database();
+ _db.deserialize(result.Data);
+ _key = key;
+ }
+
+ public void save() throws Exception {
+ assertDecrypted();
+ byte[] bytes = _db.serialize();
+ CryptResult result = _key.encrypt(bytes);
+ _file.setContent(result.Data);
+ _file.setCryptParameters(result.Parameters);
+ _file.save(_context);
+ }
+
+ public void addKey(KeyProfile profile) throws Exception {
+ assertDecrypted();
+ _db.addKey(profile);
+ }
+
+ public void updateKey(KeyProfile profile) throws Exception {
+ assertDecrypted();
+ _db.updateKey(profile);
+ }
+
+ public void removeKey(KeyProfile profile) throws Exception {
+ assertDecrypted();
+ _db.removeKey(profile);
+ }
+
+ public List getKeys() throws Exception {
+ assertDecrypted();
+ return _db.getKeys();
+ }
+
+ public boolean isLoaded() {
+ return _file != null;
+ }
+
+ public boolean isDecrypted() {
+ return _db != null;
+ }
+
+ private void assertLoaded() throws Exception {
+ if (!isLoaded()) {
+ throw new Exception("database file has not been loaded yet");
+ }
+ }
+
+ private void assertDecrypted() throws Exception {
+ assertLoaded();
+ if (!isDecrypted()) {
+ throw new Exception("database file has not been decrypted yet");
+ }
+ }
+}
diff --git a/app/src/main/java/me/impy/aegis/finger/FingerprintAuthenticationDialogFragment.java b/app/src/main/java/me/impy/aegis/finger/FingerprintAuthenticationDialogFragment.java
deleted file mode 100644
index fca10304..00000000
--- a/app/src/main/java/me/impy/aegis/finger/FingerprintAuthenticationDialogFragment.java
+++ /dev/null
@@ -1,277 +0,0 @@
-package me.impy.aegis.finger;
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-import android.app.Activity;
-import android.app.DialogFragment;
-import android.content.SharedPreferences;
-import android.hardware.fingerprint.FingerprintManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.annotation.RequiresApi;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import me.impy.aegis.MainActivity;
-import me.impy.aegis.R;
-
-/**
- * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
- * authentication if fingerprint is not available.
- */
-@RequiresApi(api = Build.VERSION_CODES.M)
-public class FingerprintAuthenticationDialogFragment extends DialogFragment
- implements TextView.OnEditorActionListener, FingerprintUiHelper.Callback {
-
- private Button mCancelButton;
- private Button mSecondDialogButton;
- private View mFingerprintContent;
- private View mBackupContent;
- private EditText mPassword;
- private CheckBox mUseFingerprintFutureCheckBox;
- private TextView mPasswordDescriptionTextView;
- private TextView mNewFingerprintEnrolledTextView;
-
- private Stage mStage = Stage.FINGERPRINT;
- private Action mAction;
-
- private FingerprintManager.CryptoObject mCryptoObject;
- private FingerprintUiHelper mFingerprintUiHelper;
- private MainActivity mActivity;
-
- private InputMethodManager mInputMethodManager;
- private SharedPreferences mSharedPreferences;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Do not create a new Fragment when the Activity is re-created such as orientation changes.
- setRetainInstance(true);
- setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- getDialog().setTitle(getString(R.string.sign_in));
- View v = inflater.inflate(R.layout.fingerprint_dialog_container, container, false);
- mCancelButton = (Button) v.findViewById(R.id.cancel_button);
- mCancelButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- dismiss();
- }
- });
-
- mSecondDialogButton = (Button) v.findViewById(R.id.second_dialog_button);
- mSecondDialogButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (mStage == Stage.FINGERPRINT) {
- goToBackup();
- } else {
- verifyPassword();
- }
- }
- });
- mFingerprintContent = v.findViewById(R.id.fingerprint_container);
- mBackupContent = v.findViewById(R.id.backup_container);
- mPassword = (EditText) v.findViewById(R.id.password);
- mPassword.setOnEditorActionListener(this);
- mPasswordDescriptionTextView = (TextView) v.findViewById(R.id.password_description);
- mUseFingerprintFutureCheckBox = (CheckBox)
- v.findViewById(R.id.use_fingerprint_in_future_check);
- mNewFingerprintEnrolledTextView = (TextView)
- v.findViewById(R.id.new_fingerprint_enrolled_description);
- mFingerprintUiHelper = new FingerprintUiHelper(
- mActivity.getSystemService(FingerprintManager.class),
- (ImageView) v.findViewById(R.id.fingerprint_icon),
- (TextView) v.findViewById(R.id.fingerprint_status), this);
- updateStage();
-
- // If fingerprint authentication is not available, switch immediately to the backup
- // (password) screen.
- if (!mFingerprintUiHelper.isFingerprintAuthAvailable()) {
- goToBackup();
- }
- return v;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mStage == Stage.FINGERPRINT) {
- mFingerprintUiHelper.startListening(mCryptoObject);
- }
- }
-
- public void setStage(Stage stage) {
- mStage = stage;
- }
-
- public void setAction(Action action) { mAction = action; }
-
- @Override
- public void onPause() {
- super.onPause();
- mFingerprintUiHelper.stopListening();
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mActivity = (MainActivity) activity;
- mInputMethodManager = mActivity.getSystemService(InputMethodManager.class);
- mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
- }
-
- /**
- * Sets the crypto object to be passed in when authenticating with fingerprint.
- */
- public void setCryptoObject(FingerprintManager.CryptoObject cryptoObject) {
- mCryptoObject = cryptoObject;
- }
-
- /**
- * Switches to backup (password) screen. This either can happen when fingerprint is not
- * available or the user chooses to use the password authentication method by pressing the
- * button. This can also happen when the user had too many fingerprint attempts.
- */
- private void goToBackup() {
- mStage = Stage.PASSWORD;
- updateStage();
- mPassword.requestFocus();
-
- // Show the keyboard.
- mPassword.postDelayed(mShowKeyboardRunnable, 500);
-
- // Fingerprint is not used anymore. Stop listening for it.
- mFingerprintUiHelper.stopListening();
- }
-
- /**
- * Checks whether the current entered password is correct, and dismisses the the dialog and
- * let's the activity know about the result.
- */
- private void verifyPassword() {
- if (!checkPassword(mPassword.getText().toString())) {
- return;
- }
- if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
- SharedPreferences.Editor editor = mSharedPreferences.edit();
- editor.putBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
- mUseFingerprintFutureCheckBox.isChecked());
- editor.apply();
-
- if (mUseFingerprintFutureCheckBox.isChecked()) {
- // Re-create the key so that fingerprints including new ones are validated.
- //mActivity.createKey(MainActivity.DEFAULT_KEY_NAME, true);
- mStage = Stage.FINGERPRINT;
- }
- }
- mPassword.setText("");
- //mActivity.onPurchased(, null);
- dismiss();
- }
-
- /**
- * @return true if {@code password} is correct, false otherwise
- */
- private boolean checkPassword(String password) {
- // Assume the password is always correct.
- // In the real world situation, the password needs to be verified in the server side.
- return password.length() > 0;
- }
-
- private final Runnable mShowKeyboardRunnable = new Runnable() {
- @Override
- public void run() {
- mInputMethodManager.showSoftInput(mPassword, 0);
- }
- };
-
- private void updateStage() {
- switch (mStage) {
- case FINGERPRINT:
- mCancelButton.setText(R.string.cancel);
- mSecondDialogButton.setText(R.string.use_password);
- mFingerprintContent.setVisibility(View.VISIBLE);
- mBackupContent.setVisibility(View.GONE);
- break;
- case NEW_FINGERPRINT_ENROLLED:
- // Intentional fall through
- case PASSWORD:
- mCancelButton.setText(R.string.cancel);
- mSecondDialogButton.setText(R.string.ok);
- mFingerprintContent.setVisibility(View.GONE);
- mBackupContent.setVisibility(View.VISIBLE);
- if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
- mPasswordDescriptionTextView.setVisibility(View.GONE);
- mNewFingerprintEnrolledTextView.setVisibility(View.VISIBLE);
- mUseFingerprintFutureCheckBox.setVisibility(View.VISIBLE);
- }
- break;
- }
- }
-
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_GO) {
- verifyPassword();
- return true;
- }
- return false;
- }
-
- @Override
- public void onAuthenticated() {
- // Callback from FingerprintUiHelper. Let the activity know that authentication was
- // successful.
- mActivity.onAuthenticated(mAction, mCryptoObject);
- dismiss();
- }
-
- @Override
- public void onError() {
- goToBackup();
- }
-
- /**
- * Enumeration to indicate which authentication method the user is trying to authenticate with.
- */
- public enum Stage {
- FINGERPRINT,
- NEW_FINGERPRINT_ENROLLED,
- PASSWORD
- }
-
- public enum Action {
- LOAD,
- SAVE
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/me/impy/aegis/finger/SetFingerprintAuthenticationDialog.java b/app/src/main/java/me/impy/aegis/finger/SetFingerprintAuthenticationDialog.java
deleted file mode 100644
index 9e44900e..00000000
--- a/app/src/main/java/me/impy/aegis/finger/SetFingerprintAuthenticationDialog.java
+++ /dev/null
@@ -1,172 +0,0 @@
-package me.impy.aegis.finger;
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-import android.app.Activity;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.hardware.fingerprint.FingerprintManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.annotation.RequiresApi;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import me.impy.aegis.CustomAuthenticationSlide;
-import me.impy.aegis.IntroActivity;
-import me.impy.aegis.MainActivity;
-import me.impy.aegis.R;
-
-/**
- * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
- * authentication if fingerprint is not available.
- */
-@RequiresApi(api = Build.VERSION_CODES.M)
-public class SetFingerprintAuthenticationDialog extends DialogFragment implements FingerprintUiHelper.Callback {
-
- private Button mCancelButton;
- private Button mSecondDialogButton;
- private View mFingerprintContent;
- private View mBackupContent;
- private EditText mPassword;
- private CheckBox mUseFingerprintFutureCheckBox;
- private TextView mPasswordDescriptionTextView;
- private TextView mNewFingerprintEnrolledTextView;
-
- private Stage mStage = Stage.FINGERPRINT;
- private Action mAction;
-
- private FingerprintManager.CryptoObject mCryptoObject;
- private FingerprintUiHelper mFingerprintUiHelper;
- private IntroActivity mIntroActivity;
- public InterfaceCommunicator interfaceCommunicator;
-
- private InputMethodManager mInputMethodManager;
- private SharedPreferences mSharedPreferences;
- private CustomAuthenticationSlide customAuthenticationSlide;
-
- public void setCaller(CustomAuthenticationSlide customAuthenticationSlide)
- {
- this.customAuthenticationSlide = customAuthenticationSlide;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setRetainInstance(true);
- setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- getDialog().setTitle(getString(R.string.sign_in));
- View v = inflater.inflate(R.layout.fingerprint_dialog_container, container, false);
- mCancelButton = (Button) v.findViewById(R.id.cancel_button);
- mCancelButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- customAuthenticationSlide.sendRequestCode(0);
- dismiss();
- }
- });
-
- mFingerprintContent = v.findViewById(R.id.fingerprint_container);
- mFingerprintUiHelper = new FingerprintUiHelper(
- mIntroActivity.getSystemService(FingerprintManager.class),
- (ImageView) v.findViewById(R.id.fingerprint_icon),
- (TextView) v.findViewById(R.id.fingerprint_status), this);
-
- return v;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mStage == Stage.FINGERPRINT) {
- mFingerprintUiHelper.startListening(mCryptoObject);
- }
- }
-
- public void setStage(Stage stage) {
- mStage = stage;
- }
-
- public void setAction(Action action) { mAction = action; }
-
- @Override
- public void onPause() {
- super.onPause();
- mFingerprintUiHelper.stopListening();
- }
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- super.onDismiss(dialog);
- customAuthenticationSlide.sendRequestCode(0);
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mIntroActivity = (IntroActivity) activity;
-
- }
-
- @Override
- public void onAuthenticated() {
- // Callback from FingerprintUiHelper. Let the activity know that authentication was
- // successful.
- customAuthenticationSlide.sendRequestCode(1);
- dismiss();
- }
-
- @Override
- public void onError() {
- customAuthenticationSlide.sendRequestCode(0);
- }
-
- public interface InterfaceCommunicator {
- void sendRequestCode(int code);
- }
-
- /**
- * Enumeration to indicate which authentication method the user is trying to authenticate with.
- */
- public enum Stage {
- FINGERPRINT,
- NEW_FINGERPRINT_ENROLLED,
- PASSWORD
- }
-
- public enum Action {
- LOAD,
- SAVE
- }
-}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_authenticated_slide.xml b/app/src/main/res/layout/fragment_authenticated_slide.xml
index dba6d31a..d872250a 100644
--- a/app/src/main/res/layout/fragment_authenticated_slide.xml
+++ b/app/src/main/res/layout/fragment_authenticated_slide.xml
@@ -42,7 +42,7 @@
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
- android:id="@+id/editText2"
+ android:id="@+id/text_password"
android:layout_below="@+id/textView3"
android:layout_alignParentStart="true"
android:layout_marginTop="10dp"
@@ -62,7 +62,7 @@
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
- android:id="@+id/editText"
+ android:id="@+id/text_password_confirm"
android:layout_marginTop="8dp"
android:layout_below="@+id/textView4"
android:layout_alignParentStart="true"