Update SafetyNet extension implementation

This commit is contained in:
topjohnwu 2018-06-10 02:35:03 +08:00
parent b0a5dbb4c2
commit 90d218ebc8
7 changed files with 53 additions and 75 deletions

3
.gitignore vendored
View File

@ -4,6 +4,9 @@ out
*.apk
config.prop
# Manually dumped jars
snet/libs
# Built binaries
native/out

View File

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

2
app

@ -1 +1 @@
Subproject commit a0466085fe3b5eb8929c893d02459e6d94ecb115
Subproject commit 499a157946f0063aea0795b204a1f2858591d8d3

View File

@ -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():

View File

@ -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 */
}

View File

@ -1,5 +0,0 @@
package com.topjohnwu.snet;
public interface SafetyNetCallback {
void onResponse(int responseCode);
}

View File

@ -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<SafetyNetApi.AttestationResult> {
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<? 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
@ -103,28 +79,28 @@ public class SafetyNetHelper
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;
}
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);
}
}