Skip to content

Commit

Permalink
Update SafetyNet extension implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
topjohnwu committed Jun 9, 2018
1 parent b0a5dbb commit 90d218e
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 77 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ out
*.apk
config.prop

# Manually dumped jars
snet/libs

# Built binaries
native/out

Expand Down
4 changes: 2 additions & 2 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 5 additions & 2 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']:
Expand Down Expand Up @@ -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():
Expand Down
7 changes: 4 additions & 3 deletions snet/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ android {

defaultConfig {
applicationId "com.topjohnwu.snet"
minSdkVersion 21
minSdkVersion 14
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1.0"
Expand All @@ -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 */
}
5 changes: 0 additions & 5 deletions snet/src/main/java/com/topjohnwu/snet/SafetyNetCallback.java

This file was deleted.

104 changes: 40 additions & 64 deletions snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,18 +9,19 @@
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<SafetyNetApi.AttestationResult> {

public static final int CAUSE_SERVICE_DISCONNECTED = 0x01;
public static final int CAUSE_NETWORK_LOST = 0x02;
Expand All @@ -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)
Expand All @@ -74,26 +61,15 @@ public void attest() {

@Override
public void onConnectionSuspended(int i) {
cb.onResponse(i);
callback.onResponse(i);
}

@Override
public void onConnectionFailed(@NonNull ConnectionResult result) {
Class<? extends Activity> 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
Expand All @@ -103,28 +79,28 @@ public void onConnected(@Nullable Bundle bundle) {
new SecureRandom().nextBytes(nonce);

// Call SafetyNet
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {
@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;
}

// Disconnect
mGoogleApiClient.disconnect();

// Return results
cb.onResponse(responseCode);
}
});
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce).setResultCallback(this);
}

@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;
}

// Disconnect
mGoogleApiClient.disconnect();

// Return results
callback.onResponse(code);
}
}

0 comments on commit 90d218e

Please sign in to comment.