From 2ef714664258e86739ec60853a8b1bf29ff2e306 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 12 Jan 2018 01:53:49 +0800 Subject: [PATCH] Add fingerprint authentication --- src/main/AndroidManifest.xml | 1 + .../com/topjohnwu/magisk/MagiskManager.java | 10 +-- .../topjohnwu/magisk/SettingsActivity.java | 9 +- .../topjohnwu/magisk/asyncs/CheckUpdates.java | 4 +- .../magisk/receivers/PackageReceiver.java | 3 +- .../magisk/superuser/RequestActivity.java | 66 +++++++++++++-- .../com/topjohnwu/magisk/utils/Const.java | 3 + .../magisk/utils/FingerprintHelper.java | 84 +++++++++++++++++++ src/main/res/drawable/ic_fingerprint.xml | 9 ++ src/main/res/layout/activity_request.xml | 21 +++-- src/main/res/values/strings.xml | 3 + src/main/res/xml/app_settings.xml | 5 ++ 12 files changed, 193 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/topjohnwu/magisk/utils/FingerprintHelper.java create mode 100644 src/main/res/drawable/ic_fingerprint.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index ae48e7309..2fff01c8c 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + Toast.makeText(weakSelf.get(), msg, duration).show()); } diff --git a/src/main/java/com/topjohnwu/magisk/SettingsActivity.java b/src/main/java/com/topjohnwu/magisk/SettingsActivity.java index 2c74eaba3..f714a9522 100644 --- a/src/main/java/com/topjohnwu/magisk/SettingsActivity.java +++ b/src/main/java/com/topjohnwu/magisk/SettingsActivity.java @@ -22,6 +22,7 @@ import com.topjohnwu.magisk.asyncs.CheckUpdates; import com.topjohnwu.magisk.asyncs.HideManager; import com.topjohnwu.magisk.components.Activity; import com.topjohnwu.magisk.utils.Const; +import com.topjohnwu.magisk.utils.FingerprintHelper; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Topic; import com.topjohnwu.magisk.utils.Utils; @@ -112,13 +113,14 @@ public class SettingsActivity extends Activity implements Topic.Subscriber { multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE); namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS); SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH); + SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT); updateChannel.setOnPreferenceChangeListener((pref, o) -> { mm.updateChannel = Integer.parseInt((String) o); if (mm.updateChannel == Const.Value.CUSTOM_CHANNEL) { View v = LayoutInflater.from(getActivity()).inflate(R.layout.custom_channel_dialog, null); EditText url = v.findViewById(R.id.custom_url); - url.setText(mm.customChannelUrl); + url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, "")); new AlertDialog.Builder(getActivity()) .setTitle(R.string.settings_update_custom) .setView(v) @@ -144,6 +146,11 @@ public class SettingsActivity extends Activity implements Topic.Subscriber { reauth.setSummary(R.string.android_o_not_support); } + // Remove fingerprint option if not possible + if (!FingerprintHelper.canUseFingerprint()) { + suCategory.removePreference(fingerprint); + } + if (mm.getPackageName().equals(Const.ORIG_PKG_NAME) && mm.magiskVersionCode >= 1440) { hideManager.setOnPreferenceClickListener((pref) -> { Utils.runWithPermission(getActivity(), diff --git a/src/main/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java b/src/main/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java index 15419d90c..575f26aa4 100644 --- a/src/main/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java +++ b/src/main/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java @@ -33,7 +33,7 @@ public class CheckUpdates extends ParallelTask { jsonStr = WebService.getString(Const.Url.BETA_URL); break; case Const.Value.CUSTOM_CHANNEL: - jsonStr = WebService.getString(mm.customChannelUrl); + jsonStr = WebService.getString(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, "")); break; } try { @@ -54,7 +54,7 @@ public class CheckUpdates extends ParallelTask { @Override protected void onPostExecute(Void v) { MagiskManager mm = MagiskManager.get(); - if (showNotification && mm.updateNotification) { + if (showNotification && mm.prefs.getBoolean(Const.Key.UPDATE_NOTIFICATION, true)) { if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) { ShowUI.managerUpdateNotification(); } else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) { diff --git a/src/main/java/com/topjohnwu/magisk/receivers/PackageReceiver.java b/src/main/java/com/topjohnwu/magisk/receivers/PackageReceiver.java index 40e6cb5d4..52bc9f932 100644 --- a/src/main/java/com/topjohnwu/magisk/receivers/PackageReceiver.java +++ b/src/main/java/com/topjohnwu/magisk/receivers/PackageReceiver.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import com.topjohnwu.magisk.MagiskManager; +import com.topjohnwu.magisk.utils.Const; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; @@ -18,7 +19,7 @@ public class PackageReceiver extends BroadcastReceiver { switch (intent.getAction()) { case Intent.ACTION_PACKAGE_REPLACED: // This will only work pre-O - if (mm.suReauth) { + if (mm.prefs.getBoolean(Const.Key.SU_REAUTH, false)) { mm.suDB.deletePolicy(pkg); } break; diff --git a/src/main/java/com/topjohnwu/magisk/superuser/RequestActivity.java b/src/main/java/com/topjohnwu/magisk/superuser/RequestActivity.java index f96b76460..31e2990c6 100644 --- a/src/main/java/com/topjohnwu/magisk/superuser/RequestActivity.java +++ b/src/main/java/com/topjohnwu/magisk/superuser/RequestActivity.java @@ -3,12 +3,14 @@ package com.topjohnwu.magisk.superuser; import android.content.ContentValues; import android.content.Intent; import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Bundle; import android.os.CountDownTimer; import android.os.FileObserver; import android.text.TextUtils; +import android.view.View; import android.view.Window; import android.widget.ArrayAdapter; import android.widget.Button; @@ -23,6 +25,7 @@ import com.topjohnwu.magisk.asyncs.ParallelTask; import com.topjohnwu.magisk.components.Activity; import com.topjohnwu.magisk.container.Policy; import com.topjohnwu.magisk.utils.Const; +import com.topjohnwu.magisk.utils.FingerprintHelper; import com.topjohnwu.magisk.utils.Utils; import java.io.DataInputStream; @@ -40,6 +43,8 @@ public class RequestActivity extends Activity { @BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.grant_btn) Button grant_btn; @BindView(R.id.deny_btn) Button deny_btn; + @BindView(R.id.fingerprint) ImageView fingerprintImg; + @BindView(R.id.warning) TextView warning; private String socketPath; private LocalSocket socket; @@ -49,6 +54,7 @@ public class RequestActivity extends Activity { private boolean hasTimeout; private Policy policy; private CountDownTimer timer; + private FingerprintHelper fingerprintHelper; @Override public int getDarkTheme() { @@ -80,6 +86,15 @@ public class RequestActivity extends Activity { new SocketManager(this).exec(); } + @Override + public void finish() { + if (timer != null) + timer.cancel(); + if (fingerprintHelper != null) + fingerprintHelper.cancel(); + super.finish(); + } + private boolean cancelTimeout() { timer.cancel(); deny_btn.setText(getString(R.string.deny)); @@ -128,11 +143,49 @@ public class RequestActivity extends Activity { } }; - grant_btn.setOnClickListener(v -> { - handleAction(Policy.ALLOW); - timer.cancel(); - }); - grant_btn.requestFocus(); + boolean useFingerprint = mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) && FingerprintHelper.canUseFingerprint(); + + if (useFingerprint) { + try { + fingerprintHelper = new FingerprintHelper() { + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + warning.setText(errString); + } + + @Override + public void onAuthenticationHelp(int helpCode, CharSequence helpString) { + warning.setText(helpString); + } + + @Override + public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { + handleAction(Policy.ALLOW); + } + + @Override + public void onAuthenticationFailed() { + warning.setText(R.string.auth_fail); + } + }; + fingerprintHelper.startAuth(); + } catch (Exception e) { + e.printStackTrace(); + useFingerprint = false; + } + } + + if (!useFingerprint) { + grant_btn.setOnClickListener(v -> { + handleAction(Policy.ALLOW); + timer.cancel(); + }); + grant_btn.requestFocus(); + } + + grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE); + fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE); + deny_btn.setOnClickListener(v -> { handleAction(Policy.DENY); timer.cancel(); @@ -151,8 +204,9 @@ public class RequestActivity extends Activity { public void onBackPressed() { if (policy != null) { handleAction(Policy.DENY); + } else { + finish(); } - finish(); } void handleAction() { diff --git a/src/main/java/com/topjohnwu/magisk/utils/Const.java b/src/main/java/com/topjohnwu/magisk/utils/Const.java index cfc52a8cd..0290fde98 100644 --- a/src/main/java/com/topjohnwu/magisk/utils/Const.java +++ b/src/main/java/com/topjohnwu/magisk/utils/Const.java @@ -23,6 +23,8 @@ public class Const { public static final String UTIL_FUNCTIONS= "util_functions.sh"; public static final String ANDROID_MANIFEST = "AndroidManifest.xml"; + public static final String SU_KEYSTORE_KEY = "su_key"; + // Paths public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk"; public static final String TMP_FOLDER_PATH = "/dev/tmp"; @@ -109,6 +111,7 @@ public class Const { public static final String SU_AUTO_RESPONSE = "su_auto_response"; public static final String SU_NOTIFICATION = "su_notification"; public static final String SU_REAUTH = "su_reauth"; + public static final String SU_FINGERPRINT = "su_fingerprint"; // intents public static final String OPEN_SECTION = "section"; diff --git a/src/main/java/com/topjohnwu/magisk/utils/FingerprintHelper.java b/src/main/java/com/topjohnwu/magisk/utils/FingerprintHelper.java new file mode 100644 index 000000000..e556489af --- /dev/null +++ b/src/main/java/com/topjohnwu/magisk/utils/FingerprintHelper.java @@ -0,0 +1,84 @@ +package com.topjohnwu.magisk.utils; + +import android.annotation.TargetApi; +import android.app.KeyguardManager; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Build; +import android.os.CancellationSignal; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; + +import com.topjohnwu.magisk.MagiskManager; + +import java.security.KeyStore; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +@TargetApi(Build.VERSION_CODES.M) +public abstract class FingerprintHelper extends FingerprintManager.AuthenticationCallback { + + private FingerprintManager manager; + private Cipher cipher; + private CancellationSignal cancel; + + public static boolean canUseFingerprint() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) + return false; + MagiskManager mm = MagiskManager.get(); + KeyguardManager km = mm.getSystemService(KeyguardManager.class); + FingerprintManager fm = mm.getSystemService(FingerprintManager.class); + return km.isKeyguardSecure() && fm.isHardwareDetected() && fm.hasEnrolledFingerprints(); + } + + protected FingerprintHelper() throws Exception { + MagiskManager mm = MagiskManager.get(); + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + manager = mm.getSystemService(FingerprintManager.class); + cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); + keyStore.load(null); + SecretKey key = (SecretKey) keyStore.getKey(Const.SU_KEYSTORE_KEY, null); + if (key == null) { + key = generateKey(); + } + try { + cipher.init(Cipher.ENCRYPT_MODE, key); + } catch (KeyPermanentlyInvalidatedException e) { + // Only happens on Marshmallow + key = generateKey(); + cipher.init(Cipher.ENCRYPT_MODE, key); + } + } + + public void startAuth() { + cancel = new CancellationSignal(); + FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher); + manager.authenticate(cryptoObject, cancel, 0, this, null); + } + + public void cancel() { + if (cancel != null) + cancel.cancel(); + } + + private SecretKey generateKey() throws Exception { + KeyGenerator keygen = KeyGenerator + .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); + KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder( + Const.SU_KEYSTORE_KEY, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setUserAuthenticationRequired(true) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + builder.setInvalidatedByBiometricEnrollment(false); + } + keygen.init(builder.build()); + return keygen.generateKey(); + } +} diff --git a/src/main/res/drawable/ic_fingerprint.xml b/src/main/res/drawable/ic_fingerprint.xml new file mode 100644 index 000000000..f650f7445 --- /dev/null +++ b/src/main/res/drawable/ic_fingerprint.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/layout/activity_request.xml b/src/main/res/layout/activity_request.xml index e891f2721..9a37d32e6 100644 --- a/src/main/res/layout/activity_request.xml +++ b/src/main/res/layout/activity_request.xml @@ -101,18 +101,27 @@