From 90d218ebc841f2794c404015766b1b6b69eac229 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sun, 10 Jun 2018 02:35:03 +0800 Subject: [PATCH] Update SafetyNet extension implementation --- .gitignore | 3 + README.MD | 4 +- app | 2 +- build.py | 7 +- snet/build.gradle | 7 +- .../com/topjohnwu/snet/SafetyNetCallback.java | 5 - .../com/topjohnwu/snet/SafetyNetHelper.java | 100 +++++++----------- 7 files changed, 53 insertions(+), 75 deletions(-) delete mode 100644 snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java diff --git a/.gitignore b/.gitignore index 3278c3c23..bcd5975c9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ out *.apk config.prop +# Manually dumped jars +snet/libs + # Built binaries native/out diff --git a/README.MD b/README.MD index 15a3a1675..433c1cb79 100644 --- a/README.MD +++ b/README.MD @@ -12,8 +12,8 @@ 1. Building is tested on macOS, Ubuntu, and Windows 10 using the latest stable NDK and NDK r10e. Officially released binaries were built with NDK r10e. 2. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example. 3. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h` -4. By default, `build.py` will build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (through the `--release` flag), you will need to place a Java Keystore file at `release-key.jks` to sign Magisk Manager's APK. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually). - +4. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `--release` flag), you need a Java Keystore file `release-key.jks` to sign Magisk Manager's APK. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually). +5. The SafetyNet extension pack requires the full Magisk Manager as a `compileOnly` dependency. Build the **release** APK, convert it back to Java `.class` files (I use [dex2jar](https://github.com/pxb1988/dex2jar)), and place the converted JAR under `snet/libs` before compiling. ## License diff --git a/app b/app index a0466085f..499a15794 160000 --- a/app +++ b/app @@ -1 +1 @@ -Subproject commit a0466085fe3b5eb8929c893d02459e6d94ecb115 +Subproject commit 499a157946f0063aea0795b204a1f2858591d8d3 diff --git a/build.py b/build.py index cd8d448aa..d8bdf5453 100755 --- a/build.py +++ b/build.py @@ -83,7 +83,6 @@ def build_all(args): build_apk(args) zip_main(args) zip_uninstaller(args) - build_snet(args) def collect_binary(): for arch in ['armeabi-v7a', 'x86']: @@ -235,7 +234,11 @@ def build_snet(args): error('Build snet extention failed!') source = os.path.join('snet', 'build', 'outputs', 'apk', 'release', 'snet-release-unsigned.apk') target = os.path.join(config['outdir'], 'snet.apk') - mv(source, target) + # Re-compress the whole APK for smaller size + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zout: + with zipfile.ZipFile(source) as zin: + for item in zin.infolist(): + zout.writestr(item.filename, zin.read(item)) header('Output: ' + target) def gen_update_binary(): diff --git a/snet/build.gradle b/snet/build.gradle index 9dcd17c98..a62b1bc36 100644 --- a/snet/build.gradle +++ b/snet/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "com.topjohnwu.snet" - minSdkVersion 21 + minSdkVersion 14 targetSdkVersion rootProject.ext.compileSdkVersion versionCode 1 versionName "1.0" @@ -15,12 +15,13 @@ android { buildTypes { release { minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) + compileOnly fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.google.android.gms:play-services-safetynet:7.0.0' /* The oldest version */ } diff --git a/snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java b/snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java deleted file mode 100644 index fa680422d..000000000 --- a/snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java +++ /dev/null @@ -1,5 +0,0 @@ -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 index 7e3d24fc1..70f46ac32 100644 --- a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java +++ b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java @@ -1,7 +1,5 @@ package com.topjohnwu.snet; -import android.app.Activity; -import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -11,18 +9,19 @@ import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; 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 com.topjohnwu.magisk.asyncs.CheckSafetyNet; +import com.topjohnwu.magisk.components.Activity; +import com.topjohnwu.magisk.utils.ISafetyNetHelper; import org.json.JSONException; import org.json.JSONObject; -import java.lang.reflect.Field; import java.security.SecureRandom; -public class SafetyNetHelper - implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { +public class SafetyNetHelper implements ISafetyNetHelper, GoogleApiClient.ConnectionCallbacks, + GoogleApiClient.OnConnectionFailedListener, ResultCallback { public static final int CAUSE_SERVICE_DISCONNECTED = 0x01; public static final int CAUSE_NETWORK_LOST = 0x02; @@ -32,36 +31,24 @@ public class SafetyNetHelper public static final int BASIC_PASS = 0x10; public static final int CTS_PASS = 0x20; - public static final int SNET_EXT_VER = 7; + public static final int SNET_EXT_VER = 8; private GoogleApiClient mGoogleApiClient; private Activity mActivity; - private int responseCode; - private SafetyNetCallback cb; - private String dexPath; - private boolean isDarkTheme; + private Callback callback; - public static int getVersion() { + @Override + public int getVersion() { return SNET_EXT_VER; } - public SafetyNetHelper(Activity activity, String dexPath, SafetyNetCallback cb) { + public SafetyNetHelper(Activity activity, Callback cb) { mActivity = activity; - this.cb = cb; - this.dexPath = dexPath; - responseCode = 0; - - // Get theme - try { - Context context = activity.getApplicationContext(); - Field theme = context.getClass().getField("isDarkTheme"); - isDarkTheme = (boolean) theme.get(context); - } catch (Exception e) { - e.printStackTrace(); - } + callback = cb; } // Entry point to start test + @Override public void attest() { // Connect Google Service mGoogleApiClient = new GoogleApiClient.Builder(mActivity) @@ -74,26 +61,15 @@ public class SafetyNetHelper @Override public void onConnectionSuspended(int i) { - cb.onResponse(i); + callback.onResponse(i); } @Override public void onConnectionFailed(@NonNull ConnectionResult result) { - Class clazz = mActivity.getClass(); - try { - // Use external resources - clazz.getMethod("swapResources", String.class, int.class).invoke(mActivity, dexPath, - isDarkTheme ? android.R.style.Theme_Material : android.R.style.Theme_Material_Light); - try { - GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), mActivity, 0).show(); - } catch (Exception e) { - e.printStackTrace(); - } - clazz.getMethod("restoreResources").invoke(mActivity); - } catch (Exception e) { - e.printStackTrace(); - } - cb.onResponse(CONNECTION_FAIL); + mActivity.swapResources(CheckSafetyNet.dexPath.getPath()); + GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), mActivity, 0).show(); + mActivity.restoreResources(); + callback.onResponse(CONNECTION_FAIL); } @Override @@ -103,28 +79,28 @@ public class SafetyNetHelper 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; - } + SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce).setResultCallback(this); + } - // Disconnect - mGoogleApiClient.disconnect(); + @Override + public void onResult(SafetyNetApi.AttestationResult result) { + int code = 0; + try { + if (!result.getStatus().isSuccess()) + throw new JSONException(""); + String jsonStr = new String(Base64.decode( + result.getJwsResult().split("\\.")[1], Base64.DEFAULT)); + JSONObject json = new JSONObject(jsonStr); + code |= json.getBoolean("ctsProfileMatch") ? CTS_PASS : 0; + code |= json.getBoolean("basicIntegrity") ? BASIC_PASS : 0; + } catch (JSONException e) { + code = RESPONSE_ERR; + } - // Return results - cb.onResponse(responseCode); - } - }); + // Disconnect + mGoogleApiClient.disconnect(); + + // Return results + callback.onResponse(code); } }