mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-22 14:59:14 +00:00
Start hooking up the intro to the database code
Fingerprint stuff has been left out for now
This commit is contained in:
parent
f1b499f101
commit
53e86db187
11 changed files with 311 additions and 633 deletions
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
89
app/src/main/java/me/impy/aegis/db/DatabaseManager.java
Normal file
89
app/src/main/java/me/impy/aegis/db/DatabaseManager.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Reference in a new issue