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