From cb03aa29ffe1fb9ae674915628eb52561763afdc Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Thu, 5 Mar 2026 13:34:48 +0100 Subject: [PATCH] Introduce droid guard results into Google sign-in. We first try to make a droid guard auth flow. If it fails, we try a droid guard checkin flow, if it fails, we fallback on the "null" value. --- .../microg/gms/auth/login/LoginActivity.java | 192 ++++++++++++------ 1 file changed, 128 insertions(+), 64 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java index 8b55858e8e..2426bbc41c 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java @@ -47,6 +47,8 @@ import androidx.webkit.WebViewClientCompat; import com.google.android.gms.R; +import com.google.android.gms.droidguard.DroidGuardClient; +import com.google.android.gms.tasks.Tasks; import org.json.JSONArray; import org.microg.gms.accountaction.AccountNotificationKt; @@ -60,13 +62,18 @@ import org.microg.gms.common.Constants; import org.microg.gms.common.HttpFormClient; import org.microg.gms.common.Utils; +import org.microg.gms.droidguard.core.DroidGuardPreferences; import org.microg.gms.people.PeopleManager; import org.microg.gms.profile.Build; import org.microg.gms.profile.ProfileManager; import java.io.IOException; import java.security.MessageDigest; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE; import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; @@ -116,6 +123,9 @@ public class LoginActivity extends AssistantActivity { private int state = 0; private boolean isReAuth = false; private Account reAuthAccount; + private volatile boolean authDroidGuardResultLoaded = false; + @Nullable + private volatile String authDroidGuardResult; @SuppressLint("AddJavascriptInterface") @Override @@ -346,35 +356,39 @@ private void closeWeb(boolean programmaticAuth) { } private void retrieveRtToken(String oAuthToken) { - new AuthRequest().fromContext(this) - .appIsGms() - .callerIsGms() - .service("ac2dm") - .token(oAuthToken).isAccessToken() - .addAccount() - .getAccountId() - .droidguardResults("null" /*TODO*/) - .getResponseAsync(new HttpFormClient.Callback() { - @Override - public void onResponse(AuthResponse response) { - Account account = new Account(response.email, accountType); - if (isReAuth && reAuthAccount != null && reAuthAccount.name.equals(account.name)) { - accountManager.removeAccount(account, future -> saveAccount(account, response), null); - } else { - saveAccount(account, response); + new Thread(() -> { + String droidGuardResults = getAuthDroidGuardResultOrNullString(); + Log.i(TAG, "droidGuardResults:" + droidGuardResults); + new AuthRequest().fromContext(this) + .appIsGms() + .callerIsGms() + .service("ac2dm") + .token(oAuthToken).isAccessToken() + .addAccount() + .getAccountId() + .droidguardResults(droidGuardResults) + .getResponseAsync(new HttpFormClient.Callback() { + @Override + public void onResponse(AuthResponse response) { + Account account = new Account(response.email, accountType); + if (isReAuth && reAuthAccount != null && reAuthAccount.name.equals(account.name)) { + accountManager.removeAccount(account, future -> saveAccount(account, response), null); + } else { + saveAccount(account, response); + } } - } - - @Override - public void onException(Exception exception) { - Log.w(TAG, "onException", exception); - runOnUiThread(() -> { - showError(R.string.auth_general_error_desc); - setNextButtonText(android.R.string.ok); - }); - state = -2; - } - }); + + @Override + public void onException(Exception exception) { + Log.w(TAG, "onException", exception); + runOnUiThread(() -> { + showError(R.string.auth_general_error_desc); + setNextButtonText(android.R.string.ok); + }); + state = -2; + } + }); + }).start(); } private void saveAccount(Account account, AuthResponse response) { @@ -420,43 +434,46 @@ private void returnSuccessResponse(Account account){ private void retrieveGmsToken(final Account account) { final AuthManager authManager = new AuthManager(this, account.name, GMS_PACKAGE_NAME, "ac2dm"); authManager.setPermitted(true); - new AuthRequest().fromContext(this) - .appIsGms() - .callerIsGms() - .service(authManager.getService()) - .email(account.name) - .token(AccountManager.get(this).getPassword(account)) - .systemPartition(true) - .hasPermission(true) - .addAccount() - .getAccountId() - .droidguardResults("null") - .getResponseAsync(new HttpFormClient.Callback() { - @Override - public void onResponse(AuthResponse response) { - authManager.storeResponse(response); - String accountId = PeopleManager.loadUserInfo(LoginActivity.this, account); - if (!TextUtils.isEmpty(accountId)) - accountManager.setUserData(account, "GoogleUserId", accountId); - if (isAuthVisible(LoginActivity.this) && SDK_INT >= 26) { - accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE); + new Thread(() -> { + String droidGuardResults = getAuthDroidGuardResultOrNullString(); + new AuthRequest().fromContext(this) + .appIsGms() + .callerIsGms() + .service(authManager.getService()) + .email(account.name) + .token(AccountManager.get(this).getPassword(account)) + .systemPartition(true) + .hasPermission(true) + .addAccount() + .getAccountId() + .droidguardResults(droidGuardResults) + .getResponseAsync(new HttpFormClient.Callback() { + @Override + public void onResponse(AuthResponse response) { + authManager.storeResponse(response); + String accountId = PeopleManager.loadUserInfo(LoginActivity.this, account); + if (!TextUtils.isEmpty(accountId)) + accountManager.setUserData(account, "GoogleUserId", accountId); + if (isAuthVisible(LoginActivity.this) && SDK_INT >= 26) { + accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE); + } + checkin(true); + returnSuccessResponse(account); + notifyGcmGroupUpdate(account.name); + if (SDK_INT >= 21) { finishAndRemoveTask(); } else finish(); + } + + @Override + public void onException(Exception exception) { + Log.w(TAG, "onException", exception); + runOnUiThread(() -> { + showError(R.string.auth_general_error_desc); + setNextButtonText(android.R.string.ok); + }); + state = -2; } - checkin(true); - returnSuccessResponse(account); - notifyGcmGroupUpdate(account.name); - if (SDK_INT >= 21) { finishAndRemoveTask(); } else finish(); - } - - @Override - public void onException(Exception exception) { - Log.w(TAG, "onException", exception); - runOnUiThread(() -> { - showError(R.string.auth_general_error_desc); - setNextButtonText(android.R.string.ok); - }); - state = -2; - } - }); + }); + }).start(); } private void notifyGcmGroupUpdate(String accountName) { @@ -501,6 +518,53 @@ private String buildUrl(String tmpl, Locale locale) { return builder.build().toString(); } + private String getAuthDroidGuardResultOrNullString() { + String result = getAuthDroidGuardResult(); + return result != null ? result : "null"; + } + + @Nullable + private synchronized String getAuthDroidGuardResult() { + if (authDroidGuardResultLoaded) { + return authDroidGuardResult; + } + + authDroidGuardResultLoaded = true; + if (!DroidGuardPreferences.isAvailable(this)) { + return null; + } + + Map data = buildAuthDroidGuardData(); + authDroidGuardResult = tryDroidGuardResult("auth", data); + if (authDroidGuardResult == null) { + authDroidGuardResult = tryDroidGuardResult("checkin", data); + } + return authDroidGuardResult; + } + + @Nullable + private String tryDroidGuardResult(String flow, Map data) { + try { + return Tasks.await(DroidGuardClient.getResults(this, flow, data), 15, TimeUnit.SECONDS); + } catch (Exception e) { + Log.w(TAG, "DroidGuard auth flow failed: " + flow, e); + return null; + } + } + + private Map buildAuthDroidGuardData() { + Map data = new HashMap<>(); + long androidId = LastCheckinInfo.read(this).getAndroidId(); + if (androidId != 0 && androidId != -1) { + data.put("dg_androidId", Long.toHexString(androidId)); + } + + data.put("dg_gmsCoreVersion", Integer.toString(GMS_VERSION_CODE)); + data.put("dg_sdkVersion", Integer.toString(Build.VERSION.SDK_INT)); + data.put("dg_session", Long.toHexString(new Random().nextLong())); + return data; + } + private class JsBridge { @JavascriptInterface