Start hooking up the intro to the database code

Fingerprint stuff has been left out for now
This commit is contained in:
Alexander Bakker 2017-08-06 16:03:36 +02:00
parent f1b499f101
commit 53e86db187
11 changed files with 311 additions and 633 deletions

2
.idea/misc.xml generated
View file

@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" /> <ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" /> <ConfirmationsSetting value="0" id="Remove" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View file

@ -1,30 +1,35 @@
package me.impy.aegis; 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.os.Bundle;
import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.EditText;
import android.widget.RadioButton;
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.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import agency.tango.materialintroscreen.SlideFragment; 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 { public class CustomAuthenticatedSlide extends SlideFragment {
private CheckBox checkBox; private EditText textPassword;
private RadioButton passwordRadioButton; private EditText textPasswordConfirm;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 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; return view;
} }
@ -40,16 +45,42 @@ public class CustomAuthenticatedSlide extends SlideFragment {
@Override @Override
public boolean canMoveFurther() { public boolean canMoveFurther() {
return true; //checkBox.isChecked(); char[] password = getEditTextChars(textPassword);
} char[] passwordConfirm = getEditTextChars(textPasswordConfirm);
boolean equal = password.length != 0 && Arrays.equals(password, passwordConfirm);
public void onAuthenticated(FingerprintAuthenticationDialogFragment.Action action, FingerprintManager.CryptoObject obj) { CryptoUtils.zero(password);
CryptoUtils.zero(passwordConfirm);
return equal;
} }
@Override @Override
public String cantMoveFurtherErrorMessage() { public String cantMoveFurtherErrorMessage() {
return "Ja bijna vriend"; return "Passwords should be equal and non-empty";
//return getString(R.string.error_message); }
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;
} }
} }

View file

@ -1,53 +1,31 @@
package me.impy.aegis; 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.os.Bundle;
import android.support.annotation.RequiresApi;
import android.app.DialogFragment;
import android.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.RadioButton; import android.widget.RadioButton;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import android.widget.Toast;
import com.mattprecious.swirl.SwirlView;
import agency.tango.materialintroscreen.SlideFragment; 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{ public class CustomAuthenticationSlide extends SlideFragment {
private CheckBox checkBox; private RadioGroup buttonGroup;
private RadioButton fingerprintRadioButton;
private RadioGroup authenticationMethodRadioGroup;
public static final int DIALOG_FRAGMENT = 1;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_authentication_slide, container, false); 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); RadioButton button = (RadioButton) view.findViewById(R.id.rb_fingerprint);
fingerprintRadioButton.setOnClickListener(new View.OnClickListener() { button.setOnClickListener(v -> {
@RequiresApi(api = Build.VERSION_CODES.M) if (canMoveFurther()) {
@Override buttonGroup.clearCheck();
public void onClick(View view) { Toast.makeText(getActivity().getBaseContext(), "Fingerprint is not supported yet", Toast.LENGTH_SHORT).show();
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");
} }
}); });
authenticationMethodRadioGroup = (RadioGroup) view.findViewById(R.id.rg_authenticationMethod);
return view; return view;
} }
@ -63,27 +41,11 @@ public class CustomAuthenticationSlide extends SlideFragment implements SetFinge
@Override @Override
public boolean canMoveFurther() { public boolean canMoveFurther() {
return authenticationMethodRadioGroup.getCheckedRadioButtonId() != -1; return buttonGroup.getCheckedRadioButtonId() != -1;
} }
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
@Override @Override
public String cantMoveFurtherErrorMessage() { public String cantMoveFurtherErrorMessage() {
return "Please select an authentication method"; 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();
}
} }
} }

View file

@ -2,16 +2,32 @@ package me.impy.aegis;
import android.Manifest; import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.View; 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.MaterialIntroActivity;
import agency.tango.materialintroscreen.MessageButtonBehaviour; import agency.tango.materialintroscreen.MessageButtonBehaviour;
import agency.tango.materialintroscreen.SlideFragmentBuilder; 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 class IntroActivity extends MaterialIntroActivity {
public static final int RESULT_OK = 0;
public static final int RESULT_EXCEPTION = 1;
private CustomAuthenticatedSlide authenticatedSlide;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -41,12 +57,64 @@ public class IntroActivity extends MaterialIntroActivity {
}, "Permission granted")); }, "Permission granted"));
addSlide(new CustomAuthenticationSlide()); 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 @Override
public void onFinish() { public void onFinish() {
super.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); SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE);
prefs.edit().putBoolean("passedIntro", true).apply(); prefs.edit().putBoolean("passedIntro", true).apply();
} }

View file

@ -10,7 +10,6 @@ import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.hardware.fingerprint.FingerprintManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.design.widget.BottomSheetDialog; import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
@ -30,7 +29,6 @@ import android.widget.Toast;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -41,30 +39,26 @@ import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.CryptoUtils;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.otp.OTP; import me.impy.aegis.crypto.otp.OTP;
import me.impy.aegis.crypto.slots.FingerprintSlot; import me.impy.aegis.crypto.slots.FingerprintSlot;
import me.impy.aegis.crypto.slots.PasswordSlot; 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.Slot;
import me.impy.aegis.crypto.slots.SlotCollection; import me.impy.aegis.crypto.slots.SlotCollection;
import me.impy.aegis.db.Database; import me.impy.aegis.db.Database;
import me.impy.aegis.db.DatabaseFile; import me.impy.aegis.db.DatabaseFile;
import me.impy.aegis.finger.FingerprintAuthenticationDialogFragment; import me.impy.aegis.db.DatabaseManager;
import me.impy.aegis.helpers.SimpleItemTouchHelperCallback; import me.impy.aegis.helpers.SimpleItemTouchHelperCallback;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private static final int CODE_GET_KEYINFO = 0;
static final int GET_KEYINFO = 1; private static final int CODE_ADD_KEYINFO = 1;
static final int ADD_KEYINFO = 2; private static final int CODE_DO_INTRO = 2;
RecyclerView rvKeyProfiles; RecyclerView rvKeyProfiles;
KeyProfileAdapter mKeyProfileAdapter; KeyProfileAdapter mKeyProfileAdapter;
ArrayList<KeyProfile> mKeyProfiles = new ArrayList<>(); ArrayList<KeyProfile> mKeyProfiles = new ArrayList<>();
MasterKey masterKey; private DatabaseManager db;
Database database;
DatabaseFile databaseFile;
boolean nightMode = false; boolean nightMode = false;
int clickedItemPosition = -1; int clickedItemPosition = -1;
@ -72,11 +66,12 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
db = new DatabaseManager(getApplicationContext());
SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE); SharedPreferences prefs = this.getSharedPreferences("me.impy.aegis", Context.MODE_PRIVATE);
if (!prefs.getBoolean("passedIntro", false)) { if (!prefs.getBoolean("passedIntro", false)) {
Intent intro = new Intent(this, IntroActivity.class); Intent intro = new Intent(this, IntroActivity.class);
startActivity(intro); startActivityForResult(intro, CODE_DO_INTRO);
} }
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@ -95,7 +90,7 @@ public class MainActivity extends AppCompatActivity {
fab.setEnabled(true); fab.setEnabled(true);
fab.setOnClickListener(view -> { fab.setOnClickListener(view -> {
Intent scannerActivity = new Intent(getApplicationContext(), ScannerActivity.class); Intent scannerActivity = new Intent(getApplicationContext(), ScannerActivity.class);
startActivityForResult(scannerActivity, GET_KEYINFO); startActivityForResult(scannerActivity, CODE_GET_KEYINFO);
}); });
rvKeyProfiles = (RecyclerView) findViewById(R.id.rvKeyProfiles); rvKeyProfiles = (RecyclerView) findViewById(R.id.rvKeyProfiles);
@ -123,23 +118,40 @@ public class MainActivity extends AppCompatActivity {
} }
}; };
Collections.sort(mKeyProfiles, comparator); Collections.sort(mKeyProfiles, comparator);
loadDatabase();
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == GET_KEYINFO) { switch (requestCode) {
if (resultCode == RESULT_OK) { case CODE_GET_KEYINFO:
onGetKeyInfoResult(resultCode, data);
break;
case CODE_ADD_KEYINFO:
onAddKeyInfoResult(resultCode, data);
break;
case CODE_DO_INTRO:
onDoIntroResult(resultCode, data);
break;
}
}
private void onGetKeyInfoResult(int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
final KeyProfile keyProfile = (KeyProfile)data.getSerializableExtra("KeyProfile"); final KeyProfile keyProfile = (KeyProfile)data.getSerializableExtra("KeyProfile");
Intent intent = new Intent(this, AddProfileActivity.class); Intent intent = new Intent(this, AddProfileActivity.class);
intent.putExtra("KeyProfile", keyProfile); intent.putExtra("KeyProfile", keyProfile);
startActivityForResult(intent, ADD_KEYINFO); startActivityForResult(intent, CODE_ADD_KEYINFO);
} }
private void onAddKeyInfoResult(int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
} }
else if (requestCode == ADD_KEYINFO) {
if (resultCode == RESULT_OK) {
final KeyProfile keyProfile = (KeyProfile) data.getSerializableExtra("KeyProfile"); final KeyProfile keyProfile = (KeyProfile) data.getSerializableExtra("KeyProfile");
String otp; String otp;
@ -153,7 +165,7 @@ public class MainActivity extends AppCompatActivity {
keyProfile.Order = mKeyProfiles.size() + 1; keyProfile.Order = mKeyProfiles.size() + 1;
keyProfile.Code = otp; keyProfile.Code = otp;
try { try {
database.addKey(keyProfile); db.addKey(keyProfile);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
// TODO: feedback // TODO: feedback
@ -164,6 +176,23 @@ public class MainActivity extends AppCompatActivity {
mKeyProfileAdapter.notifyDataSetChanged(); mKeyProfileAdapter.notifyDataSetChanged();
saveDatabase(); 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 // update order of keys
for (int i = 0; i < mKeyProfiles.size(); i++) { for (int i = 0; i < mKeyProfiles.size(); i++) {
try { try {
database.updateKey(mKeyProfiles.get(i)); db.updateKey(mKeyProfiles.get(i));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
saveDatabase(); saveDatabase();
super.onPause(); 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() private BottomSheetDialog InitializeBottomSheet()
{ {
View bottomSheetView = getLayoutInflater ().inflate (R.layout.bottom_sheet_edit_profile, null); 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?") .setMessage("Are you sure you want to delete this profile?")
.setPositiveButton(android.R.string.yes, (dialog, which) -> { .setPositiveButton(android.R.string.yes, (dialog, which) -> {
try { try {
database.removeKey(profile); db.removeKey(profile);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
//TODO: feedback //TODO: feedback
@ -364,37 +356,12 @@ public class MainActivity extends AppCompatActivity {
} }
} }
private void createDatabase() { /*private void loadDatabase() {
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() {
try { try {
databaseFile = DatabaseFile.load(getApplicationContext()); databaseFile = DatabaseFile.load(getApplicationContext());
} catch (IOException e) { } catch (IOException e) {
// the database file doesn't exist yet // the database file doesn't exist yet
createDatabase(); //createDatabase();
saveDatabase(); saveDatabase();
return; return;
} catch (Exception e) { } catch (Exception e) {
@ -445,6 +412,15 @@ public class MainActivity extends AppCompatActivity {
} catch (Exception e) { } catch (Exception e) {
throw new UndeclaredThrowableException(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) { private boolean causeIsKeyUserNotAuthenticated(Exception e) {

View file

@ -26,7 +26,7 @@ public class CryptoUtils {
public static final byte CRYPTO_NONCE_SIZE = 12; public static final byte CRYPTO_NONCE_SIZE = 12;
public static final byte CRYPTO_SALT_SIZE = 32; public static final byte CRYPTO_SALT_SIZE = 32;
// TODO: decide on a 'secure-enough' iteration count // 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_RAW = "AES/ECB/NoPadding";
public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding"; public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding";
// TODO: use a separate library for an HMAC-SHA256 implementation // TODO: use a separate library for an HMAC-SHA256 implementation

View file

@ -1,6 +1,7 @@
package me.impy.aegis.crypto; package me.impy.aegis.crypto;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -13,7 +14,7 @@ import javax.crypto.SecretKey;
import me.impy.aegis.crypto.slots.Slot; import me.impy.aegis.crypto.slots.Slot;
public class MasterKey { public class MasterKey implements Serializable {
private SecretKey _key; private SecretKey _key;
public MasterKey(SecretKey key) { public MasterKey(SecretKey key) {

View file

@ -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<KeyProfile> 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");
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -42,7 +42,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textPassword" android:inputType="textPassword"
android:ems="10" android:ems="10"
android:id="@+id/editText2" android:id="@+id/text_password"
android:layout_below="@+id/textView3" android:layout_below="@+id/textView3"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
@ -62,7 +62,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textPassword" android:inputType="textPassword"
android:ems="10" android:ems="10"
android:id="@+id/editText" android:id="@+id/text_password_confirm"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_below="@+id/textView4" android:layout_below="@+id/textView4"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"