diff --git a/app/build.gradle b/app/build.gradle index 371ec0d86..b0d0668be 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,6 +63,5 @@ dependencies { implementation 'org.bouncycastle:bcprov-jdk15on:1.57' implementation 'org.bouncycastle:bcpkix-jdk15on:1.57' implementation 'org.kamranzafar:jtar:2.3' - implementation 'com.google.android.gms:play-services-safetynet:9.0.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f179bf238..32d9847fa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -90,9 +90,10 @@ android:resource="@xml/file_paths" /> + + android:value="11400000" /> diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java b/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java index 19921fee0..314d90132 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java @@ -4,6 +4,7 @@ import android.app.NotificationManager; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.support.design.widget.Snackbar; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.CardView; @@ -19,7 +20,9 @@ import android.widget.ProgressBar; import android.widget.Spinner; import android.widget.TextView; +import com.topjohnwu.magisk.asyncs.CheckSafetyNet; import com.topjohnwu.magisk.asyncs.CheckUpdates; +import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.components.ExpandableView; import com.topjohnwu.magisk.components.Fragment; import com.topjohnwu.magisk.components.SnackbarMaker; @@ -39,6 +42,13 @@ import butterknife.Unbinder; public class MagiskFragment extends Fragment implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView { + public static final int CAUSE_SERVICE_DISCONNECTED = 0x00001; + public static final int CAUSE_NETWORK_LOST = 0x00010; + public static final int RESPONSE_ERR = 0x00100; + + public static final int BASIC_PASS = 0x01000; + public static final int CTS_PASS = 0x10000; + private Container expandableContainer = new Container(); private MagiskManager mm; @@ -85,11 +95,28 @@ public class MagiskFragment extends Fragment @OnClick(R.id.safetyNet_title) void safetyNet() { - safetyNetProgress.setVisibility(View.VISIBLE); - safetyNetRefreshIcon.setVisibility(View.GONE); - safetyNetStatusText.setText(R.string.checking_safetyNet_status); - Utils.checkSafetyNet(getActivity()); - collapse(); + Runnable task = () -> { + mm.snet_version = CheckSafetyNet.SNET_VER; + mm.prefs.edit().putInt("snet_version", CheckSafetyNet.SNET_VER).apply(); + safetyNetProgress.setVisibility(View.VISIBLE); + safetyNetRefreshIcon.setVisibility(View.GONE); + safetyNetStatusText.setText(R.string.checking_safetyNet_status); + new CheckSafetyNet(getActivity()).exec(); + collapse(); + }; + if (mm.snet_version < 0) { + // Show dialog + new AlertDialogBuilder(getActivity()) + .setTitle(R.string.proprietary_title) + .setMessage(R.string.proprietary_notice) + .setCancelable(true) + .setPositiveButton(R.string.yes, (d, i) -> task.run()) + .setNegativeButton(R.string.no_thanks, null) + .show(); + } else { + task.run(); + } + } @OnClick(R.id.install_button) @@ -158,11 +185,11 @@ public class MagiskFragment extends Fragment } @Override - public void onTopicPublished(Topic topic) { + public void onTopicPublished(Topic topic, Object result) { if (topic == mm.updateCheckDone) { updateCheckUI(); } else if (topic == mm.safetyNetDone) { - updateSafetyNetUI(); + updateSafetyNetUI((int) result); } } @@ -302,37 +329,41 @@ public class MagiskFragment extends Fragment mSwipeRefreshLayout.setRefreshing(false); } - private void updateSafetyNetUI() { - int image, color; + private void updateSafetyNetUI(int response) { safetyNetProgress.setVisibility(View.GONE); safetyNetRefreshIcon.setVisibility(View.VISIBLE); - if (mm.SNCheckResult.failed) { - safetyNetStatusText.setText(mm.SNCheckResult.errmsg); - collapse(); - } else { + if (response < 0) { + safetyNetStatusText.setText(R.string.safetyNet_api_error); + } else if ((response & 0x111) == 0) { safetyNetStatusText.setText(R.string.safetyNet_check_success); - if (mm.SNCheckResult.ctsProfile) { - color = colorOK; - image = R.drawable.ic_check_circle; - } else { - color = colorBad; - image = R.drawable.ic_cancel; - } - ctsStatusText.setText("ctsProfile: " + mm.SNCheckResult.ctsProfile); - ctsStatusIcon.setImageResource(image); - ctsStatusIcon.setColorFilter(color); - if (mm.SNCheckResult.basicIntegrity) { - color = colorOK; - image = R.drawable.ic_check_circle; - } else { - color = colorBad; - image = R.drawable.ic_cancel; - } - basicStatusText.setText("basicIntegrity: " + mm.SNCheckResult.basicIntegrity); - basicStatusIcon.setImageResource(image); - basicStatusIcon.setColorFilter(color); + boolean b; + b = (response & CTS_PASS) != 0; + ctsStatusText.setText("ctsProfile: " + b); + ctsStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel); + ctsStatusIcon.setColorFilter(b ? colorOK : colorBad); + + b = (response & BASIC_PASS) != 0; + basicStatusText.setText("basicIntegrity: " + b); + basicStatusIcon.setImageResource(b ? R.drawable.ic_check_circle : R.drawable.ic_cancel); + basicStatusIcon.setColorFilter(b ? colorOK : colorBad); + expand(); + } else { + @StringRes int resid; + switch (response) { + case CAUSE_SERVICE_DISCONNECTED: + resid = R.string.safetyNet_network_loss; + break; + case CAUSE_NETWORK_LOST: + resid = R.string.safetyNet_service_disconnected; + break; + case RESPONSE_ERR: + default: + resid = R.string.safetyNet_res_invalid; + break; + } + safetyNetStatusText.setText(resid); } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskHideFragment.java b/app/src/main/java/com/topjohnwu/magisk/MagiskHideFragment.java index 336e63862..85d565a75 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskHideFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskHideFragment.java @@ -84,7 +84,7 @@ public class MagiskHideFragment extends Fragment implements Topic.Subscriber { } @Override - public void onTopicPublished(Topic topic) { + public void onTopicPublished(Topic topic, Object result) { mSwipeRefreshLayout.setRefreshing(false); appAdapter.filter(lastFilter); } diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java b/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java index 5dad66674..349e8d0b2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java @@ -20,13 +20,12 @@ import com.topjohnwu.magisk.asyncs.DownloadBusybox; import com.topjohnwu.magisk.asyncs.LoadModules; import com.topjohnwu.magisk.asyncs.ParallelTask; import com.topjohnwu.magisk.asyncs.UpdateRepos; +import com.topjohnwu.magisk.container.Module; import com.topjohnwu.magisk.database.RepoDatabaseHelper; import com.topjohnwu.magisk.database.SuDatabaseHelper; -import com.topjohnwu.magisk.container.Module; import com.topjohnwu.magisk.services.UpdateCheckService; import com.topjohnwu.magisk.superuser.SuReceiver; import com.topjohnwu.magisk.superuser.SuRequestActivity; -import com.topjohnwu.magisk.utils.SafetyNetHelper; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Topic; import com.topjohnwu.magisk.utils.Utils; @@ -72,7 +71,6 @@ public class MagiskManager extends Application { public String remoteManagerVersionString; public int remoteManagerVersionCode = -1; public String managerLink; - public SafetyNetHelper.Result SNCheckResult; public String bootBlock = null; public boolean isSuClient = false; public String suVersion = null; @@ -103,6 +101,7 @@ public class MagiskManager extends Application { public String localeConfig; public int updateChannel; public String bootFormat; + public int snet_version; // Global resources public SharedPreferences prefs; @@ -184,6 +183,7 @@ public class MagiskManager extends Application { updateNotification = prefs.getBoolean("notification", true); updateChannel = Utils.getPrefsInt(prefs, "update_channel", CheckUpdates.STABLE_CHANNEL); bootFormat = prefs.getString("boot_format", ".img"); + snet_version = prefs.getInt("snet_version", -1); } public void toast(String msg, int duration) { diff --git a/app/src/main/java/com/topjohnwu/magisk/MainActivity.java b/app/src/main/java/com/topjohnwu/magisk/MainActivity.java index 5b0fe444b..5190601ee 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MainActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/MainActivity.java @@ -102,7 +102,7 @@ public class MainActivity extends Activity } @Override - public void onTopicPublished(Topic topic) { + public void onTopicPublished(Topic topic, Object result) { recreate(); } diff --git a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java index 35b090517..e654cc1ca 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java @@ -72,7 +72,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber { } @Override - public void onTopicPublished(Topic topic) { + public void onTopicPublished(Topic topic, Object result) { Logger.dev("ModulesFragment: UI refresh triggered"); updateUI(); } diff --git a/app/src/main/java/com/topjohnwu/magisk/ReposFragment.java b/app/src/main/java/com/topjohnwu/magisk/ReposFragment.java index c7ad8e4fb..fca67b71c 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ReposFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/ReposFragment.java @@ -69,7 +69,7 @@ public class ReposFragment extends Fragment implements Topic.Subscriber { } @Override - public void onTopicPublished(Topic topic) { + public void onTopicPublished(Topic topic, Object result) { mSwipeRefreshLayout.setRefreshing(false); recyclerView.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE); emptyRv.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java index b4263bd5c..198d4395d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java @@ -62,7 +62,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber { } @Override - public void onTopicPublished(Topic topic) { + public void onTopicPublished(Topic topic, Object result) { recreate(); } @@ -273,7 +273,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber { } @Override - public void onTopicPublished(Topic topic) { + public void onTopicPublished(Topic topic, Object result) { setLocalePreference((ListPreference) findPreference("locale")); } diff --git a/app/src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java b/app/src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java index da9a1def5..d3283128b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java +++ b/app/src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java @@ -15,8 +15,8 @@ import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.components.ExpandableView; import com.topjohnwu.magisk.components.SnackbarMaker; -import com.topjohnwu.magisk.database.SuDatabaseHelper; import com.topjohnwu.magisk.container.Policy; +import com.topjohnwu.magisk.database.SuDatabaseHelper; import java.util.HashSet; import java.util.List; diff --git a/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java b/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java index 714fc3d54..e5937f7b8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java +++ b/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java @@ -17,9 +17,9 @@ import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.asyncs.MarkDownWindow; import com.topjohnwu.magisk.asyncs.ProcessRepoZip; import com.topjohnwu.magisk.components.AlertDialogBuilder; -import com.topjohnwu.magisk.database.RepoDatabaseHelper; import com.topjohnwu.magisk.container.Module; import com.topjohnwu.magisk.container.Repo; +import com.topjohnwu.magisk.database.RepoDatabaseHelper; import com.topjohnwu.magisk.utils.Utils; import java.util.ArrayList; diff --git a/app/src/main/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java b/app/src/main/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java index 5a31cd569..a90d04706 100644 --- a/app/src/main/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java +++ b/app/src/main/java/com/topjohnwu/magisk/adapters/SuLogAdapter.java @@ -12,8 +12,8 @@ import android.widget.TextView; import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.components.ExpandableView; -import com.topjohnwu.magisk.database.SuDatabaseHelper; import com.topjohnwu.magisk.container.SuLogEntry; +import com.topjohnwu.magisk.database.SuDatabaseHelper; import java.util.Collections; import java.util.HashSet; diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java new file mode 100644 index 000000000..ff81a9677 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java @@ -0,0 +1,81 @@ +package com.topjohnwu.magisk.asyncs; + +import android.support.v4.app.FragmentActivity; + +import com.topjohnwu.magisk.container.ByteArrayStream; +import com.topjohnwu.magisk.utils.WebService; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.lang.reflect.Proxy; +import java.net.HttpURLConnection; + +import dalvik.system.DexClassLoader; + +public class CheckSafetyNet extends ParallelTask { + + public static final int SNET_VER = 1; + + // Test URL, will switch to proper URL + private static final String SNET_URL = "https://www.dropbox.com/s/i5vvbl5eenmag5q/snet-release-unsigned.apk?dl=1"; + private static final String PKG = "com.topjohnwu.snet"; + + private File dexPath; + private DexClassLoader loader; + + public CheckSafetyNet(FragmentActivity activity) { + super(activity); + dexPath = new File(activity.getCacheDir().getParent() + "/snet", "snet.apk"); + } + + @Override + protected void onPreExecute() { + if (getMagiskManager().snet_version < CheckSafetyNet.SNET_VER) { + getShell().sh("rm -rf " + dexPath.getParent()); + } + } + + @Override + protected Exception doInBackground(Void... voids) { + try { + if (!dexPath.exists()) { + HttpURLConnection conn = WebService.request(SNET_URL, null); + ByteArrayStream bas = new ByteArrayStream(); + bas.readFrom(conn.getInputStream()); + conn.disconnect(); + dexPath.getParentFile().mkdir(); + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath))) { + bas.writeTo(out); + out.flush(); + } + } + loader = new DexClassLoader(dexPath.toString(), dexPath.getParent(), + null, ClassLoader.getSystemClassLoader()); + } catch (Exception e) { + return e; + } + return null; + } + + @Override + protected void onPostExecute(Exception err) { + try { + if (err != null) throw err; + Class helperClazz = loader.loadClass(PKG + ".SafetyNetHelper"); + Class callbackClazz = loader.loadClass(PKG + ".SafetyNetCallback"); + Object helper = helperClazz.getConstructors()[0].newInstance( + getActivity(), Proxy.newProxyInstance( + loader, new Class[] { callbackClazz }, (proxy, method, args) -> { + getMagiskManager().safetyNetDone.publish(false, args[0]); + return null; + })); + helperClazz.getMethod("requestTest").invoke(helper); + } catch (Exception e) { + e.printStackTrace(); + getMagiskManager().safetyNetDone.publish(false, -1); + } + super.onPostExecute(err); + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java index ec78613b0..ce3d81bef 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/InstallMagisk.java @@ -8,8 +8,8 @@ import android.text.TextUtils; import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.container.AdaptiveList; -import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.container.TarEntry; +import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.ZipUtils; diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java index ef8a2f470..2c1f2d4ec 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java @@ -3,11 +3,10 @@ package com.topjohnwu.magisk.asyncs; import android.content.Context; import com.topjohnwu.magisk.MagiskManager; -import com.topjohnwu.magisk.container.BaseModule; import com.topjohnwu.magisk.container.Module; +import com.topjohnwu.magisk.container.ValueSortedMap; import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Utils; -import com.topjohnwu.magisk.container.ValueSortedMap; public class LoadModules extends ParallelTask { diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java index 7746d18d0..3afc8ac77 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/UpdateRepos.java @@ -5,7 +5,6 @@ import android.content.SharedPreferences; import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.ReposFragment; -import com.topjohnwu.magisk.container.BaseModule; import com.topjohnwu.magisk.container.Repo; import com.topjohnwu.magisk.database.RepoDatabaseHelper; import com.topjohnwu.magisk.utils.WebService; diff --git a/app/src/main/java/com/topjohnwu/magisk/container/BaseModule.java b/app/src/main/java/com/topjohnwu/magisk/container/BaseModule.java index c843999d7..f4e2a811a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/container/BaseModule.java +++ b/app/src/main/java/com/topjohnwu/magisk/container/BaseModule.java @@ -5,8 +5,6 @@ import android.content.ContentValues; import android.database.Cursor; import android.support.annotation.NonNull; -import com.topjohnwu.magisk.utils.Logger; - import java.util.List; public abstract class BaseModule implements Comparable { diff --git a/app/src/main/java/com/topjohnwu/magisk/container/JarMap.java b/app/src/main/java/com/topjohnwu/magisk/container/JarMap.java index f66d40b34..81a931316 100644 --- a/app/src/main/java/com/topjohnwu/magisk/container/JarMap.java +++ b/app/src/main/java/com/topjohnwu/magisk/container/JarMap.java @@ -1,8 +1,5 @@ package com.topjohnwu.magisk.container; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; diff --git a/app/src/main/java/com/topjohnwu/magisk/container/Module.java b/app/src/main/java/com/topjohnwu/magisk/container/Module.java index 229e3267a..cbb71a478 100644 --- a/app/src/main/java/com/topjohnwu/magisk/container/Module.java +++ b/app/src/main/java/com/topjohnwu/magisk/container/Module.java @@ -1,6 +1,5 @@ package com.topjohnwu.magisk.container; -import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; diff --git a/app/src/main/java/com/topjohnwu/magisk/container/Repo.java b/app/src/main/java/com/topjohnwu/magisk/container/Repo.java index 4583e401f..2dd460127 100644 --- a/app/src/main/java/com/topjohnwu/magisk/container/Repo.java +++ b/app/src/main/java/com/topjohnwu/magisk/container/Repo.java @@ -3,7 +3,6 @@ package com.topjohnwu.magisk.container; import android.content.ContentValues; import android.database.Cursor; -import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.WebService; import java.util.Date; diff --git a/app/src/main/java/com/topjohnwu/magisk/container/SuLogEntry.java b/app/src/main/java/com/topjohnwu/magisk/container/SuLogEntry.java index 35a01ff64..4b3518468 100644 --- a/app/src/main/java/com/topjohnwu/magisk/container/SuLogEntry.java +++ b/app/src/main/java/com/topjohnwu/magisk/container/SuLogEntry.java @@ -4,7 +4,6 @@ import android.content.ContentValues; import android.database.Cursor; import com.topjohnwu.magisk.MagiskManager; -import com.topjohnwu.magisk.container.Policy; import java.text.DateFormat; import java.text.SimpleDateFormat; diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/SafetyNetHelper.java b/app/src/main/java/com/topjohnwu/magisk/utils/SafetyNetHelper.java deleted file mode 100644 index 726fcdcc0..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/SafetyNetHelper.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.topjohnwu.magisk.utils; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; -import android.util.Base64; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.safetynet.SafetyNet; -import com.topjohnwu.magisk.R; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.security.SecureRandom; - -public abstract class SafetyNetHelper - implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks { - - private static boolean isRunning = false; - - private GoogleApiClient mGoogleApiClient; - private Result ret; - protected FragmentActivity mActivity; - - public SafetyNetHelper(FragmentActivity activity) { - ret = new Result(); - mActivity = activity; - } - - // Entry point to start test - public void requestTest() { - if (isRunning) - return; - // Connect Google Service - mGoogleApiClient = new GoogleApiClient.Builder(mActivity) - .enableAutoManage(mActivity, this) - .addApi(SafetyNet.API) - .addConnectionCallbacks(this) - .build(); - mGoogleApiClient.connect(); - isRunning = true; - } - - @Override - public void onConnectionFailed(@NonNull ConnectionResult result) { - Logger.dev("SN: Google API fail"); - ret.errmsg = result.getErrorMessage(); - handleResults(ret); - } - - @Override - public void onConnectionSuspended(int i) { - Logger.dev("SN: Google API Suspended"); - switch (i) { - case CAUSE_NETWORK_LOST: - ret.errmsg = mActivity.getString(R.string.safetyNet_network_loss); - break; - case CAUSE_SERVICE_DISCONNECTED: - ret.errmsg = mActivity.getString(R.string.safetyNet_service_disconnected); - break; - } - handleResults(ret); - } - - @Override - public void onConnected(@Nullable Bundle bundle) { - Logger.dev("SN: Google API Connected"); - // Create nonce - byte[] nonce = new byte[24]; - new SecureRandom().nextBytes(nonce); - - Logger.dev("SN: Check with nonce: " + Base64.encodeToString(nonce, Base64.DEFAULT)); - - // Call SafetyNet - SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce) - .setResultCallback(result -> { - Status status = result.getStatus(); - if (status.isSuccess()) { - String json = new String(Base64.decode(result.getJwsResult().split("\\.")[1], Base64.DEFAULT)); - Logger.dev("SN: Response: " + json); - try { - JSONObject decoded = new JSONObject(json); - ret.ctsProfile = decoded.getBoolean("ctsProfileMatch"); - ret.basicIntegrity = decoded.getBoolean("basicIntegrity"); - ret.failed = false; - } catch (JSONException e) { - ret.errmsg = mActivity.getString(R.string.safetyNet_res_invalid); - } - } else { - Logger.dev("SN: No response"); - ret.errmsg = mActivity.getString(R.string.safetyNet_no_response); - } - // Disconnect - mGoogleApiClient.stopAutoManage(mActivity); - mGoogleApiClient.disconnect(); - isRunning = false; - handleResults(ret); - }); - } - - // Callback function to save the results - public abstract void handleResults(Result result); - - public static class Result { - public boolean failed = true; - public String errmsg; - public boolean ctsProfile = false; - public boolean basicIntegrity = false; - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Topic.java b/app/src/main/java/com/topjohnwu/magisk/utils/Topic.java index 4864a63b8..66e8e2960 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Topic.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Topic.java @@ -31,15 +31,19 @@ public class Topic { } public void publish() { - publish(true); + publish(true, null); } public void publish(boolean record) { + publish(record, null); + } + + public void publish(boolean record, Object result) { hasPublished = record; if (subscribers != null) { for (WeakReference subscriber : subscribers) { if (subscriber.get() != null) - subscriber.get().onTopicPublished(this); + subscriber.get().onTopicPublished(this, result); } } } @@ -60,9 +64,12 @@ public class Topic { } } default void onTopicPublished() { - onTopicPublished(null); + onTopicPublished(null, null); } - void onTopicPublished(Topic topic); + default void onTopicPublished(Topic topic) { + onTopicPublished(topic, null); + } + void onTopicPublished(Topic topic, Object result); Topic[] getSubscription(); } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java index c3c566eb5..b049f988b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java @@ -22,7 +22,6 @@ import android.provider.OpenableColumns; import android.support.annotation.StringRes; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; -import android.support.v4.app.FragmentActivity; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.support.v4.content.ContextCompat; @@ -150,16 +149,6 @@ public class Utils { return (MagiskManager) context.getApplicationContext(); } - public static void checkSafetyNet(FragmentActivity activity) { - new SafetyNetHelper(activity) { - @Override - public void handleResults(Result result) { - getMagiskManager(mActivity).SNCheckResult = result; - getMagiskManager(mActivity).safetyNetDone.publish(false); - } - }.requestTest(); - } - public static void clearRepoCache(Context context) { MagiskManager mm = getMagiskManager(context); mm.prefs.edit().remove(UpdateRepos.ETAG_KEY).apply(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89871cbd2..54e858d2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,7 +22,8 @@ Not rooted Tap to start SafetyNet check Checking SafetyNet status… - SafetyNet Check Was Successful + SafetyNet Check Success + SafetyNet API Error Cannot verify SafetyNet, no internet? Network connection unavailable Service has been killed @@ -123,6 +124,8 @@ Restoration done! Stock backup does not exist! Uninstalling Magisk Manager in 5 seconds, please manually reboot afterwards + Download Proprietary Code + Magisk Manager is FOSS so doesn\'t contain Google\'s proprietary SafetyNet API code.\n\nDo you allow Magisk Manager to download an extension (contains GoogleApiClient) for SafetyNet checks? General diff --git a/build.gradle b/build.gradle index 998838f50..c84f2b629 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { jcenter() mavenCentral() maven { url "https://maven.google.com" } + google() } dependencies { classpath 'com.android.tools.build:gradle:3.0.0-beta7' @@ -18,6 +19,7 @@ allprojects { repositories { jcenter() maven { url "https://jitpack.io" } + google() } } diff --git a/settings.gradle b/settings.gradle index 1cf7f1e5e..3c11aabee 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':unhide', ':resource' \ No newline at end of file +include ':app', ':unhide', ':resource', ':snet' \ No newline at end of file diff --git a/snet/.gitignore b/snet/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/snet/.gitignore @@ -0,0 +1 @@ +/build diff --git a/snet/build.gradle b/snet/build.gradle new file mode 100644 index 000000000..e41372b04 --- /dev/null +++ b/snet/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + + defaultConfig { + applicationId "com.topjohnwu.sn" + minSdkVersion 21 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.google.android.gms:play-services-safetynet:11.4.2' +} diff --git a/snet/proguard-rules.pro b/snet/proguard-rules.pro new file mode 100644 index 000000000..d6e59f6f5 --- /dev/null +++ b/snet/proguard-rules.pro @@ -0,0 +1,24 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-keep class com.topjohnwu.snet.SafetyNet* { *; } +-dontwarn java.lang.invoke** diff --git a/snet/src/main/AndroidManifest.xml b/snet/src/main/AndroidManifest.xml new file mode 100644 index 000000000..4fa18362e --- /dev/null +++ b/snet/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java b/snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java new file mode 100644 index 000000000..fa680422d --- /dev/null +++ b/snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java @@ -0,0 +1,5 @@ +package com.topjohnwu.snet; + +public interface SafetyNetCallback { + void onResponse(int responseCode); +} diff --git a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java new file mode 100644 index 000000000..6cc35a192 --- /dev/null +++ b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java @@ -0,0 +1,118 @@ +package com.topjohnwu.snet; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Base64; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.safetynet.SafetyNet; +import com.google.android.gms.safetynet.SafetyNetApi; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Method; +import java.security.SecureRandom; + +public class SafetyNetHelper + implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + + public static final int CONNECTION_FAIL = -1; + + public static final int CAUSE_SERVICE_DISCONNECTED = 0x00001; + public static final int CAUSE_NETWORK_LOST = 0x00010; + public static final int RESPONSE_ERR = 0x00100; + + public static final int BASIC_PASS = 0x01000; + public static final int CTS_PASS = 0x10000; + + private GoogleApiClient mGoogleApiClient; + private Context mActivity; + private int responseCode; + private SafetyNetCallback cb; + + public SafetyNetHelper(Context context, SafetyNetCallback cb) { + mActivity = context; + this.cb = cb; + responseCode = 0; + } + + // Entry point to start test + public void requestTest() { + // Connect Google Service + GoogleApiClient.Builder builder = new GoogleApiClient.Builder(mActivity); + try { + // Use reflection to workaround FragmentActivity crap + Class clazz = Class.forName("com.google.android.gms.common.api.GoogleApiClient$Builder"); + for (Method m : clazz.getMethods()) { + if (m.getName().equals("enableAutoManage")) { + m.invoke(builder, mActivity, this); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + mGoogleApiClient = builder.addApi(SafetyNet.API).addConnectionCallbacks(this).build(); + mGoogleApiClient.connect(); + } + + @Override + public void onConnectionSuspended(int i) { + cb.onResponse(i); + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + cb.onResponse(CONNECTION_FAIL); + } + + @Override + public void onConnected(@Nullable Bundle bundle) { + // Create nonce + byte[] nonce = new byte[24]; + new SecureRandom().nextBytes(nonce); + + // Call SafetyNet + SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce) + .setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull SafetyNetApi.AttestationResult result) { + Status status = result.getStatus(); + try { + if (!status.isSuccess()) throw new JSONException(""); + String json = new String(Base64.decode( + result.getJwsResult().split("\\.")[1], Base64.DEFAULT)); + JSONObject decoded = new JSONObject(json); + responseCode |= decoded.getBoolean("ctsProfileMatch") ? CTS_PASS : 0; + responseCode |= decoded.getBoolean("basicIntegrity") ? BASIC_PASS : 0; + } catch (JSONException e) { + responseCode |= RESPONSE_ERR; + return; + } + // Disconnect + try { + // Use reflection to workaround FragmentActivity crap + Class clazz = Class.forName("com.google.android.gms.common.api.GoogleApiClient"); + for (Method m : clazz.getMethods()) { + if (m.getName().equals("stopAutoManage")) { + m.invoke(mGoogleApiClient, mActivity, this); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + mGoogleApiClient.disconnect(); + + // Return results + cb.onResponse(responseCode); + } + }); + } +}