From 8965b3989bede4b94e090981d444dfba0c43e1dc Mon Sep 17 00:00:00 2001 From: Tony Rankin Date: Tue, 26 Apr 2016 15:59:57 -0500 Subject: [PATCH 1/5] Code cleanup, no functional changes --- .../passcodelock/AbstractAppLock.java | 25 +++++++----- .../passcodelock/AppLockManager.java | 19 +++------- .../passcodelock/PasscodeUnlockActivity.java | 38 ++++++++----------- .../wordpress/passcodelock/StringUtils.java | 2 - 4 files changed, 37 insertions(+), 47 deletions(-) diff --git a/library/src/org/wordpress/passcodelock/AbstractAppLock.java b/library/src/org/wordpress/passcodelock/AbstractAppLock.java index 57540ec..16b3ee3 100644 --- a/library/src/org/wordpress/passcodelock/AbstractAppLock.java +++ b/library/src/org/wordpress/passcodelock/AbstractAppLock.java @@ -2,6 +2,20 @@ import android.app.Application; +/** + * Interface for AppLock implementations. + * + * There are situations where the AppLock should not be required within an app. Methods for tracking + * exempt {@link android.app.Activity}'s are provided and sub-class implementations are expected to + * comply with requested exemptions. + * @see #isExemptActivity(String) + * @see #setExemptActivities(String[]) + * @see #getExemptActivities() + * + * Applications can request a one-time delay in locking the app. This can be useful for activities + * that launch external applications with the expectation that the user will return to the calling + * application shortly. + */ public abstract class AbstractAppLock implements Application.ActivityLifecycleCallbacks { public static final int DEFAULT_TIMEOUT = 2; //2 seconds public static final int EXTENDED_TIMEOUT = 60; //60 seconds @@ -11,25 +25,18 @@ public abstract class AbstractAppLock implements Application.ActivityLifecycleCa protected int lockTimeOut = DEFAULT_TIMEOUT; protected String[] appLockDisabledActivities = new String[0]; - /* - * There are situations where an activity will start a different application with an intent. - * In these situations call this method right before leaving the app. - */ public void setOneTimeTimeout(int timeout) { this.lockTimeOut = timeout; } - /* - * There are situations where we don't want call the AppLock on activities (sharing items to out app for example). - */ public void setDisabledActivities( String[] disabledActs ) { this.appLockDisabledActivities = disabledActs; } - + public abstract void enable(); public abstract void disable(); public abstract void forcePasswordLock(); - public abstract boolean verifyPassword( String password ); + public abstract boolean verifyPassword(String password); public abstract boolean isPasswordLocked(); public abstract boolean setPassword(String password); } diff --git a/library/src/org/wordpress/passcodelock/AppLockManager.java b/library/src/org/wordpress/passcodelock/AppLockManager.java index 89b9e58..a50f85f 100644 --- a/library/src/org/wordpress/passcodelock/AppLockManager.java +++ b/library/src/org/wordpress/passcodelock/AppLockManager.java @@ -1,24 +1,23 @@ package org.wordpress.passcodelock; import android.app.Application; +import android.os.Build; public class AppLockManager { - private static AppLockManager instance; private AbstractAppLock currentAppLocker; - + public static AppLockManager getInstance() { if (instance == null) { instance = new AppLockManager(); } return instance; } - + public void enableDefaultAppLockIfAvailable(Application currentApp) { - if (android.os.Build.VERSION.SDK_INT >= 14) { - currentAppLocker = new DefaultAppLock(currentApp); - currentAppLocker.enable(); - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return; + currentAppLocker = new DefaultAppLock(currentApp); + currentAppLocker.enable(); } /** @@ -45,12 +44,6 @@ public AbstractAppLock getCurrentAppLock() { return currentAppLocker; } - /* - * Convenience method used to extend the default timeout. - * - * There are situations where an activity will start a different application with an intent. - * In these situations call this method right before leaving the app. - */ public void setExtendedTimeout(){ if ( currentAppLocker == null ) return; diff --git a/library/src/org/wordpress/passcodelock/PasscodeUnlockActivity.java b/library/src/org/wordpress/passcodelock/PasscodeUnlockActivity.java index 0eb7f14..84052cd 100644 --- a/library/src/org/wordpress/passcodelock/PasscodeUnlockActivity.java +++ b/library/src/org/wordpress/passcodelock/PasscodeUnlockActivity.java @@ -6,15 +6,13 @@ import android.view.View; public class PasscodeUnlockActivity extends AbstractPasscodeKeyboardActivity { - @Override public void onResume() { super.onResume(); - // Show fingerprint scanner if supported - if (mFingerprintManager.isHardwareDetected() - && mFingerprintManager.hasEnrolledFingerprints()) { - mFingerprintManager.authenticate(null, 0, mCancel = new CancellationSignal(), getFingerprintCallback(), null); + if (isFingerprintSupported()) { + mCancel = new CancellationSignal(); + mFingerprintManager.authenticate(null, 0, mCancel, getFingerprintCallback(), null); View view = findViewById(R.id.image_fingerprint); view.setVisibility(View.VISIBLE); } @@ -22,7 +20,7 @@ public void onResume() { @Override public void onBackPressed() { - AppLockManager.getInstance().getCurrentAppLock().forcePasswordLock(); + getAppLock().forcePasswordLock(); Intent i = new Intent(); i.setAction(Intent.ACTION_MAIN); i.addCategory(Intent.CATEGORY_HOME); @@ -33,7 +31,7 @@ public void onBackPressed() { @Override protected void onPinLockInserted() { String passLock = mPinCodeField.getText().toString(); - if (AppLockManager.getInstance().getCurrentAppLock().verifyPassword(passLock)) { + if (getAppLock().verifyPassword(passLock)) { authenticationSucceeded(); } else { authenticationFailed(); @@ -43,31 +41,25 @@ protected void onPinLockInserted() { @Override protected FingerprintManagerCompat.AuthenticationCallback getFingerprintCallback() { return new FingerprintManagerCompat.AuthenticationCallback() { - @Override - public void onAuthenticationError(int errMsgId, CharSequence errString) { - super.onAuthenticationError(errMsgId, errString); - } - - @Override - public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { - super.onAuthenticationHelp(helpMsgId, helpString); - } - @Override public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { - super.onAuthenticationSucceeded(result); - // Must be called to set internal state (lostFocusDate). Without the call to - // verifyPassword the unlock screen will show multiple times - AppLockManager.getInstance().getCurrentAppLock().verifyPassword( - AbstractAppLock.FINGERPRINT_VERIFICATION_BYPASS); + // without the call to verifyPassword the unlock screen will show multiple times + getAppLock().verifyPassword(AbstractAppLock.FINGERPRINT_VERIFICATION_BYPASS); authenticationSucceeded(); } @Override public void onAuthenticationFailed() { - super.onAuthenticationFailed(); authenticationFailed(); } + + @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { } + @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { } }; } + + private boolean isFingerprintSupported() { + return mFingerprintManager.isHardwareDetected() && + mFingerprintManager.hasEnrolledFingerprints(); + } } diff --git a/library/src/org/wordpress/passcodelock/StringUtils.java b/library/src/org/wordpress/passcodelock/StringUtils.java index e4af76a..83e7905 100644 --- a/library/src/org/wordpress/passcodelock/StringUtils.java +++ b/library/src/org/wordpress/passcodelock/StringUtils.java @@ -7,7 +7,6 @@ import android.util.Log; public class StringUtils { - public static String getMd5Hash(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); @@ -24,5 +23,4 @@ public static String getMd5Hash(String input) { return null; } } - } From 1cc6fe0811fc2c398cc1dbea152a34804ed58204 Mon Sep 17 00:00:00 2001 From: Tony Rankin Date: Tue, 26 Apr 2016 16:06:32 -0500 Subject: [PATCH 2/5] Renaming getCurrentAppLock, adding convenience getter in abstract keyboard activity --- .../passcodelock/AbstractAppLock.java | 6 ++--- .../AbstractPasscodeKeyboardActivity.java | 4 ++++ .../passcodelock/AppLockManager.java | 24 ++++++++++--------- .../passcodelock/DefaultAppLock.java | 2 +- .../PasscodeManagePasswordActivity.java | 10 ++++---- .../PasscodePreferenceFragment.java | 4 ++-- 6 files changed, 28 insertions(+), 22 deletions(-) diff --git a/library/src/org/wordpress/passcodelock/AbstractAppLock.java b/library/src/org/wordpress/passcodelock/AbstractAppLock.java index 16b3ee3..4c40de3 100644 --- a/library/src/org/wordpress/passcodelock/AbstractAppLock.java +++ b/library/src/org/wordpress/passcodelock/AbstractAppLock.java @@ -17,10 +17,10 @@ * application shortly. */ public abstract class AbstractAppLock implements Application.ActivityLifecycleCallbacks { - public static final int DEFAULT_TIMEOUT = 2; //2 seconds - public static final int EXTENDED_TIMEOUT = 60; //60 seconds + public static final String FINGERPRINT_VERIFICATION_BYPASS = "fingerprint-bypass__"; + public static final int DEFAULT_TIMEOUT_S = 2; + public static final int EXTENDED_TIMEOUT_S = 60; - protected static final String FINGERPRINT_VERIFICATION_BYPASS = "fingerprint-bypass__"; protected int lockTimeOut = DEFAULT_TIMEOUT; protected String[] appLockDisabledActivities = new String[0]; diff --git a/library/src/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java b/library/src/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java index afb0548..1695335 100644 --- a/library/src/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java +++ b/library/src/org/wordpress/passcodelock/AbstractPasscodeKeyboardActivity.java @@ -88,6 +88,10 @@ public void onPause() { } } + protected AbstractAppLock getAppLock() { + return AppLockManager.getInstance().getAppLock(); + } + private OnClickListener defaultButtonListener = new OnClickListener() { @Override public void onClick(View arg0) { diff --git a/library/src/org/wordpress/passcodelock/AppLockManager.java b/library/src/org/wordpress/passcodelock/AppLockManager.java index a50f85f..66b62b9 100644 --- a/library/src/org/wordpress/passcodelock/AppLockManager.java +++ b/library/src/org/wordpress/passcodelock/AppLockManager.java @@ -19,18 +19,17 @@ public void enableDefaultAppLockIfAvailable(Application currentApp) { currentAppLocker = new DefaultAppLock(currentApp); currentAppLocker.enable(); } + + public boolean isDefaultLock() { + return getAppLock() != null && getAppLock() instanceof DefaultAppLock; + } /** * Default App lock is available on Android-v14 or higher. * @return True if the Passcode Lock feature is available on the device */ - public boolean isAppLockFeatureEnabled(){ - if( currentAppLocker == null ) - return false; - if( currentAppLocker instanceof DefaultAppLock) - return (android.os.Build.VERSION.SDK_INT >= 14); - else - return true; + public boolean isAppLockFeatureEnabled() { + return getAppLock() != null && (!isDefaultLock() || isSupportedApi()); } public void setCurrentAppLock(AbstractAppLock newAppLocker) { @@ -40,13 +39,16 @@ public void setCurrentAppLock(AbstractAppLock newAppLocker) { currentAppLocker = newAppLocker; } - public AbstractAppLock getCurrentAppLock() { + public AbstractAppLock getAppLock() { return currentAppLocker; } public void setExtendedTimeout(){ - if ( currentAppLocker == null ) - return; - currentAppLocker.setOneTimeTimeout(AbstractAppLock.EXTENDED_TIMEOUT); + if (getAppLock() == null) return; + getAppLock().setOneTimeTimeout(AbstractAppLock.EXTENDED_TIMEOUT_S); + } + + private boolean isSupportedApi() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; } } diff --git a/library/src/org/wordpress/passcodelock/DefaultAppLock.java b/library/src/org/wordpress/passcodelock/DefaultAppLock.java index 9f2e5a3..159f3c4 100644 --- a/library/src/org/wordpress/passcodelock/DefaultAppLock.java +++ b/library/src/org/wordpress/passcodelock/DefaultAppLock.java @@ -24,7 +24,7 @@ public class DefaultAppLock extends AbstractAppLock { private SharedPreferences settings; private Date lostFocusDate; - //Add back-compatibility + private static final String UNLOCK_CLASS_NAME = PasscodeUnlockActivity.class.getName(); private static final String OLD_PASSWORD_SALT = "sadasauidhsuyeuihdahdiauhs"; private static final String OLD_APP_LOCK_PASSWORD_PREF_KEY = "wp_app_lock_password_key"; diff --git a/library/src/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java b/library/src/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java index 8a7468c..e9d509b 100644 --- a/library/src/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java +++ b/library/src/org/wordpress/passcodelock/PasscodeManagePasswordActivity.java @@ -43,8 +43,8 @@ protected void onPinLockInserted() { switch (type) { case PasscodePreferenceFragment.DISABLE_PASSLOCK: - if (AppLockManager.getInstance().getCurrentAppLock().verifyPassword(passLock)) { - AppLockManager.getInstance().getCurrentAppLock().setPassword(null); + if (AppLockManager.getInstance().getAppLock().verifyPassword(passLock)) { + AppLockManager.getInstance().getAppLock().setPassword(null); authenticationSucceeded(); } else { showPasswordError(); @@ -56,7 +56,7 @@ protected void onPinLockInserted() { unverifiedPasscode = passLock; } else { if (passLock.equals(unverifiedPasscode)) { - AppLockManager.getInstance().getCurrentAppLock().setPassword(passLock); + AppLockManager.getInstance().getAppLock().setPassword(passLock); authenticationSucceeded(); } else { unverifiedPasscode = null; @@ -67,7 +67,7 @@ protected void onPinLockInserted() { break; case PasscodePreferenceFragment.CHANGE_PASSWORD: //verify old password - if (AppLockManager.getInstance().getCurrentAppLock().verifyPassword(passLock)) { + if (AppLockManager.getInstance().getAppLock().verifyPassword(passLock)) { topMessage.setText(R.string.passcodelock_prompt_message); type = PasscodePreferenceFragment.ENABLE_PASSLOCK; } else { @@ -95,7 +95,7 @@ public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { @Override public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { super.onAuthenticationSucceeded(result); - AppLockManager.getInstance().getCurrentAppLock().setPassword(null); + AppLockManager.getInstance().getAppLock().setPassword(null); authenticationSucceeded(); } diff --git a/library/src/org/wordpress/passcodelock/PasscodePreferenceFragment.java b/library/src/org/wordpress/passcodelock/PasscodePreferenceFragment.java index 99c5406..48a50ba 100644 --- a/library/src/org/wordpress/passcodelock/PasscodePreferenceFragment.java +++ b/library/src/org/wordpress/passcodelock/PasscodePreferenceFragment.java @@ -61,7 +61,7 @@ public void setPreferences(Preference togglePreference, Preference changePrefere * always true to indicate that the request was handled */ private boolean handlePasscodeToggleClick() { - int type = AppLockManager.getInstance().getCurrentAppLock().isPasswordLocked() + int type = AppLockManager.getInstance().getAppLock().isPasswordLocked() ? DISABLE_PASSLOCK : ENABLE_PASSLOCK; Intent i = new Intent(getActivity(), PasscodeManagePasswordActivity.class); i.putExtra(PasscodeManagePasswordActivity.KEY_TYPE, type); @@ -94,7 +94,7 @@ private void refreshPreferenceState() { mTogglePasscodePreference.setOnPreferenceClickListener(this); mChangePasscodePreference.setOnPreferenceClickListener(this); - if (AppLockManager.getInstance().getCurrentAppLock().isPasswordLocked()) { + if (AppLockManager.getInstance().getAppLock().isPasswordLocked()) { mTogglePasscodePreference.setTitle(R.string.passcode_turn_off); mChangePasscodePreference.setEnabled(true); } else { From db8403c9e6f7dbeac8ef400e9ea53d7e222261b4 Mon Sep 17 00:00:00 2001 From: Tony Rankin Date: Tue, 26 Apr 2016 16:08:02 -0500 Subject: [PATCH 3/5] Replacing encryption-based security with hash-based --- library/gradle.properties-example | 1 - .../passcodelock/AbstractAppLock.java | 33 +- .../passcodelock/DefaultAppLock.java | 290 ++++++++---------- 3 files changed, 164 insertions(+), 160 deletions(-) diff --git a/library/gradle.properties-example b/library/gradle.properties-example index a33defd..a7fefd3 100644 --- a/library/gradle.properties-example +++ b/library/gradle.properties-example @@ -1,5 +1,4 @@ passcodelock.password_preference_key=passcode_lock_prefs_password_key -passcodelock.password_salt=11-maggio-2014-osvaldo-al-49novesimo! passcodelock.password_enc_secret=5-maggio-2002-Karel-Poborsky ossrhUsername=hello diff --git a/library/src/org/wordpress/passcodelock/AbstractAppLock.java b/library/src/org/wordpress/passcodelock/AbstractAppLock.java index 4c40de3..efd4944 100644 --- a/library/src/org/wordpress/passcodelock/AbstractAppLock.java +++ b/library/src/org/wordpress/passcodelock/AbstractAppLock.java @@ -1,6 +1,8 @@ package org.wordpress.passcodelock; +import android.annotation.TargetApi; import android.app.Application; +import android.os.Build; /** * Interface for AppLock implementations. @@ -16,21 +18,42 @@ * that launch external applications with the expectation that the user will return to the calling * application shortly. */ +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public abstract class AbstractAppLock implements Application.ActivityLifecycleCallbacks { public static final String FINGERPRINT_VERIFICATION_BYPASS = "fingerprint-bypass__"; public static final int DEFAULT_TIMEOUT_S = 2; public static final int EXTENDED_TIMEOUT_S = 60; + private int mLockTimeout = DEFAULT_TIMEOUT_S; + private String[] mExemptActivities; - protected int lockTimeOut = DEFAULT_TIMEOUT; - protected String[] appLockDisabledActivities = new String[0]; + public boolean isExemptActivity(String name) { + if (name == null) return false; + for (String activityName : getExemptActivities()) { + if (name.equals(activityName)) return true; + } + return false; + } + + public void setExemptActivities(String[] exemptActivities) { + mExemptActivities = exemptActivities; + } + + public String[] getExemptActivities() { + if (mExemptActivities == null) setExemptActivities(new String[0]); + return mExemptActivities; + } public void setOneTimeTimeout(int timeout) { - this.lockTimeOut = timeout; + mLockTimeout = timeout; + } + + public int getTimeout() { + return mLockTimeout; } - public void setDisabledActivities( String[] disabledActs ) { - this.appLockDisabledActivities = disabledActs; + protected boolean isFingerprintPassword(String password) { + return FINGERPRINT_VERIFICATION_BYPASS.equals(password); } public abstract void enable(); diff --git a/library/src/org/wordpress/passcodelock/DefaultAppLock.java b/library/src/org/wordpress/passcodelock/DefaultAppLock.java index 159f3c4..f823e9b 100644 --- a/library/src/org/wordpress/passcodelock/DefaultAppLock.java +++ b/library/src/org/wordpress/passcodelock/DefaultAppLock.java @@ -1,6 +1,5 @@ package org.wordpress.passcodelock; -import java.util.Arrays; import java.util.Date; import javax.crypto.Cipher; @@ -12,125 +11,183 @@ import android.app.Application; import android.content.Intent; import android.content.SharedPreferences; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; +import android.text.TextUtils; import android.util.Base64; -import org.wordpress.passcodelock.BuildConfig; - public class DefaultAppLock extends AbstractAppLock { - - private Application currentApp; //Keep a reference to the app that invoked the locker - private SharedPreferences settings; - private Date lostFocusDate; - private static final String UNLOCK_CLASS_NAME = PasscodeUnlockActivity.class.getName(); private static final String OLD_PASSWORD_SALT = "sadasauidhsuyeuihdahdiauhs"; private static final String OLD_APP_LOCK_PASSWORD_PREF_KEY = "wp_app_lock_password_key"; - public DefaultAppLock(Application currentApp) { + private Application mCurrentApp; + private SharedPreferences mSharedPreferences; + private Date mLostFocusDate; + + public DefaultAppLock(Application app) { super(); - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(currentApp); - this.settings = settings; - this.currentApp = currentApp; + mCurrentApp = app; + mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mCurrentApp); } - public void enable(){ - if (android.os.Build.VERSION.SDK_INT < 14) - return; + /** {@link PasscodeUnlockActivity} is always exempt. */ + @Override + public boolean isExemptActivity(String activityName) { + return !UNLOCK_CLASS_NAME.equals(activityName) && super.isExemptActivity(activityName); + } - if( isPasswordLocked() ) { - currentApp.unregisterActivityLifecycleCallbacks(this); - currentApp.registerActivityLifecycleCallbacks(this); + @Override + public void onActivityPaused(Activity activity) { + if (!isExemptActivity(activity.getClass().getName())) mLostFocusDate = new Date(); + } + + @Override + public void onActivityResumed(Activity activity) { + if (!isExemptActivity(activity.getClass().getName()) && shouldShowUnlockScreen()) { + Intent i = new Intent(activity.getApplicationContext(), PasscodeUnlockActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.getApplication().startActivity(i); } } - public void disable( ){ - if (android.os.Build.VERSION.SDK_INT < 14) - return; + @Override public void onActivityCreated(Activity arg0, Bundle arg1) {} + @Override public void onActivityDestroyed(Activity arg0) {} + @Override public void onActivitySaveInstanceState(Activity arg0, Bundle arg1) {} + @Override public void onActivityStarted(Activity arg0) {} + @Override public void onActivityStopped(Activity arg0) {} - currentApp.unregisterActivityLifecycleCallbacks(this); + public void enable() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return; + + if (isPasswordLocked()) { + mCurrentApp.unregisterActivityLifecycleCallbacks(this); + mCurrentApp.registerActivityLifecycleCallbacks(this); + } + } + + public void disable() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return; + mCurrentApp.unregisterActivityLifecycleCallbacks(this); + } + + public boolean isPasswordLocked() { + return mSharedPreferences.contains(BuildConfig.PASSWORD_PREFERENCE_KEY) || + mSharedPreferences.contains(OLD_APP_LOCK_PASSWORD_PREF_KEY); + } + + public boolean setPassword(String password) { + removePasswordFromPreferences(); + if (TextUtils.isEmpty(password)) { + disable(); + } else { + savePasswordToPreferences(password.hashCode()); + enable(); + } + return true; } - public void forcePasswordLock(){ - lostFocusDate = null; + public void forcePasswordLock() { + mLostFocusDate = null; } - public boolean verifyPassword( String password ) { - if (FINGERPRINT_VERIFICATION_BYPASS.equals(password)) { - lostFocusDate = new Date(); + public boolean verifyPassword(String password) { + if (TextUtils.isEmpty(password)) return false; + + // successful fingerprint scan bypasses PIN security + if (isFingerprintPassword(password)) { + mLostFocusDate = new Date(); return true; } String storedPassword = ""; - - if (settings.contains(OLD_APP_LOCK_PASSWORD_PREF_KEY)) { //add back-compatibility - //Check if the old value is available - storedPassword = settings.getString(OLD_APP_LOCK_PASSWORD_PREF_KEY, ""); - password = OLD_PASSWORD_SALT + password + OLD_PASSWORD_SALT; - password = StringUtils.getMd5Hash(password); - } else if (settings.contains(BuildConfig.PASSWORD_PREFERENCE_KEY)) { - //read the password from the new key - storedPassword = settings.getString(BuildConfig.PASSWORD_PREFERENCE_KEY, ""); - storedPassword = decryptPassword(storedPassword); - password = BuildConfig.PASSWORD_SALT + password + BuildConfig.PASSWORD_SALT; + String securePassword = null; + int updatedHash = -1; + + if (mSharedPreferences.contains(OLD_APP_LOCK_PASSWORD_PREF_KEY)) { + // backwards compatibility + storedPassword = getStoredLegacyPassword(OLD_APP_LOCK_PASSWORD_PREF_KEY); + securePassword = legacyPasswordHash(password); + } else if (mSharedPreferences.contains(BuildConfig.PASSWORD_PREFERENCE_KEY)) { + if (shouldUpdatePassword()) { + storedPassword = getStoredLegacyPassword(BuildConfig.PASSWORD_PREFERENCE_KEY); + storedPassword = decryptPassword(storedPassword); + storedPassword = stripSalt(storedPassword); + securePassword = password; + updatedHash = password.hashCode(); + } else { + int storedHash = getStoredPassword(); + storedPassword = String.valueOf(storedHash); + securePassword = String.valueOf(password.hashCode()); + } } - if( password.equalsIgnoreCase(storedPassword) ) { - lostFocusDate = new Date(); - return true; - } else { - return false; + if (!storedPassword.equalsIgnoreCase(securePassword)) return false; + + // password security updated, replace stored password with integer hash value + if (updatedHash != -1) { + removePasswordFromPreferences(); + savePasswordToPreferences(updatedHash); } + mLostFocusDate = new Date(); + return true; } - public boolean setPassword(String password){ - SharedPreferences.Editor editor = settings.edit(); + private String stripSalt(String saltedPassword) { + if (TextUtils.isEmpty(saltedPassword) || saltedPassword.length() < 4) return ""; + int middle = saltedPassword.length() / 2; + return saltedPassword.substring(middle - 2, middle + 2); + } - if(password == null) { - editor.remove(OLD_APP_LOCK_PASSWORD_PREF_KEY); - editor.remove(BuildConfig.PASSWORD_PREFERENCE_KEY); - editor.commit(); - this.disable(); - } else { - password = BuildConfig.PASSWORD_SALT + password + BuildConfig.PASSWORD_SALT; - password = encryptPassword(password); - editor.putString(BuildConfig.PASSWORD_PREFERENCE_KEY, password); - editor.remove(OLD_APP_LOCK_PASSWORD_PREF_KEY); - editor.commit(); - this.enable(); - } + /** Show the unlock screen if there is a saved password and the timeout period has elapsed. */ + private boolean shouldShowUnlockScreen() { + if(!isPasswordLocked()) return false; + if(mLostFocusDate == null) return true; + + int currentTimeOut = getTimeout(); + setOneTimeTimeout(DEFAULT_TIMEOUT_S); + if (timeSinceLocked() < currentTimeOut) return false; + mLostFocusDate = null; return true; } - //Check if we need to show the lock screen at startup - public boolean isPasswordLocked(){ + private int getStoredPassword() { + return mSharedPreferences.getInt(BuildConfig.PASSWORD_PREFERENCE_KEY, -1); + } - if (settings.contains(OLD_APP_LOCK_PASSWORD_PREF_KEY)) //Check if the old value is available - return true; + private void savePasswordToPreferences(int password) { + mSharedPreferences.edit().putInt(BuildConfig.PASSWORD_PREFERENCE_KEY, password).apply(); + } - if (settings.contains(BuildConfig.PASSWORD_PREFERENCE_KEY)) - return true; + private void removePasswordFromPreferences() { + mSharedPreferences.edit() + .remove(OLD_APP_LOCK_PASSWORD_PREF_KEY) + .remove(BuildConfig.PASSWORD_PREFERENCE_KEY) + .apply(); + } - return false; + private int timeSinceLocked() { + return Math.abs((int) ((new Date().getTime() - mLostFocusDate.getTime()) / 1000)); } - private String encryptPassword(String clearText) { - try { - DESKeySpec keySpec = new DESKeySpec( - BuildConfig.PASSWORD_ENC_SECRET.getBytes("UTF-8")); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - SecretKey key = keyFactory.generateSecret(keySpec); + // + // Legacy methods for backwards compatibility of passwords stored using deprecated security + // - Cipher cipher = Cipher.getInstance("DES"); - cipher.init(Cipher.ENCRYPT_MODE, key); - String encrypedPwd = Base64.encodeToString(cipher.doFinal(clearText - .getBytes("UTF-8")), Base64.DEFAULT); - return encrypedPwd; - } catch (Exception e) { - } - return clearText; + /** Update to hash-based security if password was stored using encryption-based security. */ + private boolean shouldUpdatePassword() { + Object storedValue = mSharedPreferences.getAll().get(BuildConfig.PASSWORD_PREFERENCE_KEY); + return storedValue != null && storedValue instanceof String; + } + + private String getStoredLegacyPassword(String key) { + return mSharedPreferences.getString(key, ""); + } + + private String legacyPasswordHash(String rawPassword) { + return StringUtils.getMd5Hash(OLD_PASSWORD_SALT + rawPassword + OLD_PASSWORD_SALT); } private String decryptPassword(String encryptedPwd) { @@ -148,79 +205,4 @@ private String decryptPassword(String encryptedPwd) { } return encryptedPwd; } - - private boolean mustShowUnlockSceen() { - - if( isPasswordLocked() == false) - return false; - - if( lostFocusDate == null ) - return true; //first startup or when we forced to show the password - - int currentTimeOut = lockTimeOut; //get a reference to the current password timeout and reset it to default - lockTimeOut = DEFAULT_TIMEOUT; - Date now = new Date(); - long now_ms = now.getTime(); - long lost_focus_ms = lostFocusDate.getTime(); - int secondsPassed = (int) (now_ms - lost_focus_ms)/(1000); - secondsPassed = Math.abs(secondsPassed); //Make sure changing the clock on the device to a time in the past doesn't by-pass PIN Lock - if (secondsPassed >= currentTimeOut) { - lostFocusDate = null; - return true; - } - - return false; - } - - @Override - public void onActivityPaused(Activity arg0) { - - if( arg0.getClass() == PasscodeUnlockActivity.class ) - return; - - if( ( this.appLockDisabledActivities != null ) && Arrays.asList(this.appLockDisabledActivities).contains( arg0.getClass().getName() ) ) - return; - - lostFocusDate = new Date(); - - } - - @Override - public void onActivityResumed(Activity arg0) { - - if( arg0.getClass() == PasscodeUnlockActivity.class ) - return; - - if( ( this.appLockDisabledActivities != null ) && Arrays.asList(this.appLockDisabledActivities).contains( arg0.getClass().getName() ) ) - return; - - if(mustShowUnlockSceen()) { - //uhhh ohhh! - Intent i = new Intent(arg0.getApplicationContext(), PasscodeUnlockActivity.class); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - arg0.getApplication().startActivity(i); - return; - } - - } - - @Override - public void onActivityCreated(Activity arg0, Bundle arg1) { - } - - @Override - public void onActivityDestroyed(Activity arg0) { - } - - @Override - public void onActivitySaveInstanceState(Activity arg0, Bundle arg1) { - } - - @Override - public void onActivityStarted(Activity arg0) { - } - - @Override - public void onActivityStopped(Activity arg0) { - } } From b715eefa718a1df8d29441803843aac1ab3f758f Mon Sep 17 00:00:00 2001 From: Tony Rankin Date: Wed, 27 Apr 2016 12:32:02 -0500 Subject: [PATCH 4/5] Moving API version check to static method in DefaultAppLock --- .../org/wordpress/passcodelock/AppLockManager.java | 9 ++------- .../org/wordpress/passcodelock/DefaultAppLock.java | 14 +++++++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/library/src/org/wordpress/passcodelock/AppLockManager.java b/library/src/org/wordpress/passcodelock/AppLockManager.java index 66b62b9..6dffe41 100644 --- a/library/src/org/wordpress/passcodelock/AppLockManager.java +++ b/library/src/org/wordpress/passcodelock/AppLockManager.java @@ -1,7 +1,6 @@ package org.wordpress.passcodelock; import android.app.Application; -import android.os.Build; public class AppLockManager { private static AppLockManager instance; @@ -15,7 +14,7 @@ public static AppLockManager getInstance() { } public void enableDefaultAppLockIfAvailable(Application currentApp) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return; + if (!DefaultAppLock.isSupportedApi()) return; currentAppLocker = new DefaultAppLock(currentApp); currentAppLocker.enable(); } @@ -29,7 +28,7 @@ public boolean isDefaultLock() { * @return True if the Passcode Lock feature is available on the device */ public boolean isAppLockFeatureEnabled() { - return getAppLock() != null && (!isDefaultLock() || isSupportedApi()); + return getAppLock() != null && (!isDefaultLock() || DefaultAppLock.isSupportedApi()); } public void setCurrentAppLock(AbstractAppLock newAppLocker) { @@ -47,8 +46,4 @@ public void setExtendedTimeout(){ if (getAppLock() == null) return; getAppLock().setOneTimeTimeout(AbstractAppLock.EXTENDED_TIMEOUT_S); } - - private boolean isSupportedApi() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; - } } diff --git a/library/src/org/wordpress/passcodelock/DefaultAppLock.java b/library/src/org/wordpress/passcodelock/DefaultAppLock.java index f823e9b..0b4028f 100644 --- a/library/src/org/wordpress/passcodelock/DefaultAppLock.java +++ b/library/src/org/wordpress/passcodelock/DefaultAppLock.java @@ -18,6 +18,10 @@ import android.util.Base64; public class DefaultAppLock extends AbstractAppLock { + public static boolean isSupportedApi() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + private static final String UNLOCK_CLASS_NAME = PasscodeUnlockActivity.class.getName(); private static final String OLD_PASSWORD_SALT = "sadasauidhsuyeuihdahdiauhs"; private static final String OLD_APP_LOCK_PASSWORD_PREF_KEY = "wp_app_lock_password_key"; @@ -59,17 +63,17 @@ public void onActivityResumed(Activity activity) { @Override public void onActivityStopped(Activity arg0) {} public void enable() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return; - - if (isPasswordLocked()) { + if (!isPasswordLocked()) return; + if (isSupportedApi()) { mCurrentApp.unregisterActivityLifecycleCallbacks(this); mCurrentApp.registerActivityLifecycleCallbacks(this); } } public void disable() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return; - mCurrentApp.unregisterActivityLifecycleCallbacks(this); + if (isSupportedApi()) { + mCurrentApp.unregisterActivityLifecycleCallbacks(this); + } } public boolean isPasswordLocked() { From 484c7785f4bf0d60aeaa1c63bb3c7a8eb92e0a76 Mon Sep 17 00:00:00 2001 From: Tony Rankin Date: Thu, 28 Apr 2016 15:53:30 -0500 Subject: [PATCH 5/5] Updating documentation --- library/src/org/wordpress/passcodelock/AppLockManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/org/wordpress/passcodelock/AppLockManager.java b/library/src/org/wordpress/passcodelock/AppLockManager.java index 6dffe41..dde0ea9 100644 --- a/library/src/org/wordpress/passcodelock/AppLockManager.java +++ b/library/src/org/wordpress/passcodelock/AppLockManager.java @@ -24,8 +24,8 @@ public boolean isDefaultLock() { } /** - * Default App lock is available on Android-v14 or higher. - * @return True if the Passcode Lock feature is available on the device + * @return true when an App lock is available. It could be either a the Default App lock on + * Android-v14 or higher, or a non default App lock */ public boolean isAppLockFeatureEnabled() { return getAppLock() != null && (!isDefaultLock() || DefaultAppLock.isSupportedApi());