From ef1be861f21376a9e9f10dc60ec624158294d6ff Mon Sep 17 00:00:00 2001 From: vijaya sekhar pasupuleti Date: Tue, 12 May 2026 22:49:40 +0530 Subject: [PATCH 1/5] Auth: add package override support to getTokenWithAccount (#3278) Mirrors the override logic from AccountAuthenticator.getAuthToken into AuthManagerServiceImpl.getTokenWithAccount so that apps calling this path directly (e.g. patched YouTube flavors) also benefit from package overrides, fixing UNREGISTERED_ON_API_CONSOLE errors. --- .../gms/auth/AuthManagerServiceImpl.java | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index e0ae41cc9e..0eb27ec0a3 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -34,6 +34,7 @@ import com.google.android.auth.IAuthManagerService; import com.google.android.gms.R; +import com.google.android.gms.common.internal.CertData; import com.google.android.gms.auth.AccountChangeEventsRequest; import com.google.android.gms.auth.AccountChangeEventsResponse; import com.google.android.gms.auth.GetHubTokenInternalResponse; @@ -42,8 +43,10 @@ import com.google.android.gms.auth.TokenData; import com.google.android.gms.common.api.Scope; +import org.microg.gms.auth.loginservice.AccountAuthenticator; import org.microg.gms.common.GooglePackagePermission; import org.microg.gms.common.PackageUtils; +import org.microg.gms.utils.PackageManagerUtilsKt; import java.io.IOException; import java.util.ArrayList; @@ -118,6 +121,9 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false); + String effectivePackageName = packageName; + boolean isOverrideAllowed = false; + CertData overrideCert = null; scope = Objects.equals(AuthConstants.SCOPE_OAUTH2, scope) ? AuthConstants.SCOPE_EM_OP_PRO : scope; @@ -132,7 +138,46 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) */ scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", ""); - AuthManager authManager = new AuthManager(context, account.name, packageName, scope); + if (extras.containsKey(AccountAuthenticator.KEY_OVERRIDE_PACKAGE) || extras.containsKey(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE)) { + String overridePackage = extras.getString(AccountAuthenticator.KEY_OVERRIDE_PACKAGE, packageName); + CertData requestingCert = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName).get(0); + byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); + if (overrideCertificateBytes != null) { + overrideCert = new CertData(overrideCertificateBytes); + } else { + overrideCert = requestingCert; + } + if (packageName.equals(context.getPackageName())) { + isOverrideAllowed = true; + } else { + String requestingDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(requestingCert, "SHA-256"), ""); + String overrideCertificateDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-256"), ""); + String overrideUserDataKey = "override." + packageName + ":" + requestingDigestString + ":" + overridePackage + ":" + overrideCertificateDigestString; + String hasOverride = AccountManager.get(context).getUserData(account, overrideUserDataKey); + isOverrideAllowed = "1".equals(hasOverride); + } + if (isOverrideAllowed) { + effectivePackageName = overridePackage; + Log.d(TAG, "getTokenWithAccount: using package override " + packageName + " -> " + effectivePackageName); + } else { + Bundle result = new Bundle(); + result.putString(KEY_ERROR, "NeedPermission"); + Intent i = new Intent(context, AskPackageOverrideActivity.class); + i.putExtras(extras); + i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); + i.putExtra(KEY_ACCOUNT_TYPE, account.type); + i.putExtra(KEY_ACCOUNT_NAME, account.name); + i.putExtra(AccountAuthenticator.KEY_OVERRIDE_PACKAGE, overridePackage); + i.putExtra(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE, overrideCert.getBytes()); + result.putParcelable(KEY_USER_RECOVERY_INTENT, i); + return result; + } + } + + AuthManager authManager = new AuthManager(context, account.name, effectivePackageName, scope); + if (isOverrideAllowed && overrideCert != null) { + authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA1"), "")); + } if (extras.containsKey(KEY_DELEGATION_TYPE) && extras.getInt(KEY_DELEGATION_TYPE) != 0 ) { authManager.setDelegation(extras.getInt(KEY_DELEGATION_TYPE), extras.getString("delegatee_user_id")); } From bc8bb94180a716f92a006edcf6cda33c356dc0c3 Mon Sep 17 00:00:00 2001 From: vijaya sekhar pasupuleti Date: Tue, 12 May 2026 23:18:20 +0530 Subject: [PATCH 2/5] fix: Harden package override authorization in AuthManagerServiceImpl Addresses all code review issues from PR #3468: Critical Fixes: - Fix overridePackage fallback default that triggered override for all callers - Add bounds check for getCertificates().get(0) to handle empty cert lists - Include KEY_ACCOUNT_NAME and KEY_ACCOUNT_TYPE in NeedPermission result Medium Fixes: - Fix override trigger condition: only check KEY_OVERRIDE_PACKAGE (not OR) - Use 'SHA-1' (with hyphen) instead of 'SHA1' for Java MessageDigest standard - Remove putExtras(extras) that leaked sensitive caller data to activity Minor Improvements: - Clean up variable scoping for override-related variables - Add defensive error handling for edge cases All changes maintain backward compatibility and no API modifications. Package override logic now properly mirrors AccountAuthenticator.getAuthToken behavior. Fixes: #3468 --- .../gms/auth/AuthManagerServiceImpl.java | 108 ++++++++++++------ 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index 0eb27ec0a3..7f1140427b 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -121,9 +121,6 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false); - String effectivePackageName = packageName; - boolean isOverrideAllowed = false; - CertData overrideCert = null; scope = Objects.equals(AuthConstants.SCOPE_OAUTH2, scope) ? AuthConstants.SCOPE_EM_OP_PRO : scope; @@ -138,45 +135,80 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) */ scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", ""); - if (extras.containsKey(AccountAuthenticator.KEY_OVERRIDE_PACKAGE) || extras.containsKey(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE)) { - String overridePackage = extras.getString(AccountAuthenticator.KEY_OVERRIDE_PACKAGE, packageName); - CertData requestingCert = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName).get(0); - byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); - if (overrideCertificateBytes != null) { - overrideCert = new CertData(overrideCertificateBytes); - } else { - overrideCert = requestingCert; - } - if (packageName.equals(context.getPackageName())) { - isOverrideAllowed = true; - } else { - String requestingDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(requestingCert, "SHA-256"), ""); - String overrideCertificateDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-256"), ""); - String overrideUserDataKey = "override." + packageName + ":" + requestingDigestString + ":" + overridePackage + ":" + overrideCertificateDigestString; - String hasOverride = AccountManager.get(context).getUserData(account, overrideUserDataKey); - isOverrideAllowed = "1".equals(hasOverride); - } - if (isOverrideAllowed) { - effectivePackageName = overridePackage; - Log.d(TAG, "getTokenWithAccount: using package override " + packageName + " -> " + effectivePackageName); - } else { - Bundle result = new Bundle(); - result.putString(KEY_ERROR, "NeedPermission"); - Intent i = new Intent(context, AskPackageOverrideActivity.class); - i.putExtras(extras); - i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); - i.putExtra(KEY_ACCOUNT_TYPE, account.type); - i.putExtra(KEY_ACCOUNT_NAME, account.name); - i.putExtra(AccountAuthenticator.KEY_OVERRIDE_PACKAGE, overridePackage); - i.putExtra(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE, overrideCert.getBytes()); - result.putParcelable(KEY_USER_RECOVERY_INTENT, i); - return result; + // Issue #4: Only check KEY_OVERRIDE_PACKAGE (not OR with certificate key) + String effectivePackageName = packageName; + if (extras.containsKey(AccountAuthenticator.KEY_OVERRIDE_PACKAGE)) { + // Issue #1: Don't use packageName as fallback default + String overridePackage = extras.getString(AccountAuthenticator.KEY_OVERRIDE_PACKAGE); + if (overridePackage != null && !overridePackage.isEmpty()) { + // Issue #2: Safely get requesting cert with bounds check + List certs = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName); + if (certs.isEmpty()) { + Log.w(TAG, "getTokenWithAccount: no certificates found for requesting package " + packageName); + Bundle result = new Bundle(); + result.putString(KEY_ERROR, "NeedPermission"); + return result; + } + CertData requestingCert = certs.get(0); + + // Get override cert from extras or use requesting cert + byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); + CertData overrideCert = (overrideCertificateBytes != null) + ? new CertData(overrideCertificateBytes) + : requestingCert; + + boolean isOverrideAllowed; + if (packageName.equals(context.getPackageName())) { + isOverrideAllowed = true; + } else { + String requestingDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(requestingCert, "SHA-256"), ""); + String overrideCertificateDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-256"), ""); + String overrideUserDataKey = "override." + packageName + ":" + requestingDigestString + ":" + overridePackage + ":" + overrideCertificateDigestString; + String hasOverride = AccountManager.get(context).getUserData(account, overrideUserDataKey); + isOverrideAllowed = "1".equals(hasOverride); + } + + if (isOverrideAllowed) { + effectivePackageName = overridePackage; + Log.d(TAG, "getTokenWithAccount: using package override " + packageName + " -> " + effectivePackageName); + // Will set signature below after AuthManager creation + } else { + // Issue #3: Add KEY_ACCOUNT_NAME and KEY_ACCOUNT_TYPE to result + Bundle result = new Bundle(); + result.putString(KEY_ERROR, "NeedPermission"); + result.putString(KEY_ACCOUNT_NAME, account.name); + result.putString(KEY_ACCOUNT_TYPE, account.type); + + // Issue #6: Only pass necessary keys to AskPackageOverrideActivity, not full extras + Intent i = new Intent(context, AskPackageOverrideActivity.class); + i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); + i.putExtra(KEY_ACCOUNT_TYPE, account.type); + i.putExtra(KEY_ACCOUNT_NAME, account.name); + i.putExtra(AccountAuthenticator.KEY_OVERRIDE_PACKAGE, overridePackage); + i.putExtra(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE, overrideCert.getBytes()); + result.putParcelable(KEY_USER_RECOVERY_INTENT, i); + return result; + } } } AuthManager authManager = new AuthManager(context, account.name, effectivePackageName, scope); - if (isOverrideAllowed && overrideCert != null) { - authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA1"), "")); + // Set override signature if override was approved + if (!effectivePackageName.equals(packageName)) { + // Override is in effect; set signature from override cert + try { + List certs = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName); + if (!certs.isEmpty()) { + byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); + CertData overrideCert = (overrideCertificateBytes != null) + ? new CertData(overrideCertificateBytes) + : certs.get(0); + // Issue #5: Use "SHA-1" (with hyphen) for consistency with Java MessageDigest + authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-1"), "")); + } + } catch (Exception e) { + Log.w(TAG, "getTokenWithAccount: error setting override signature", e); + } } if (extras.containsKey(KEY_DELEGATION_TYPE) && extras.getInt(KEY_DELEGATION_TYPE) != 0 ) { authManager.setDelegation(extras.getInt(KEY_DELEGATION_TYPE), extras.getString("delegatee_user_id")); From 7b1c5b7acf1ae43d589af86bdcc49e7f90232386 Mon Sep 17 00:00:00 2001 From: vijaya sekhar pasupuleti Date: Tue, 12 May 2026 23:40:00 +0530 Subject: [PATCH 3/5] fix: Make second certs.get(0) explicitly null-safe in override signature block The setPackageSignature block had certs.get(0) inside an isEmpty() guard, but the safety was implicit and fragile. Restructure to: - Fetch overrideCert from extras bytes if present (no cert lookup needed) - Otherwise call getCertificates and use 'certs.isEmpty() ? null : certs.get(0)' - Null-check overrideCert before calling setPackageSignature This makes null safety self-evident without relying on an outer isEmpty() wrapper, addressing code review feedback on PR #3468. --- .../gms/auth/AuthManagerServiceImpl.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index 7f1140427b..bfb6075788 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -197,13 +197,18 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) if (!effectivePackageName.equals(packageName)) { // Override is in effect; set signature from override cert try { - List certs = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName); - if (!certs.isEmpty()) { - byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); - CertData overrideCert = (overrideCertificateBytes != null) - ? new CertData(overrideCertificateBytes) - : certs.get(0); - // Issue #5: Use "SHA-1" (with hyphen) for consistency with Java MessageDigest + byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); + CertData overrideCert; + if (overrideCertificateBytes != null) { + overrideCert = new CertData(overrideCertificateBytes); + } else { + // Explicitly null-safe: getCertificates may return empty list + List certs = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName); + overrideCert = certs.isEmpty() ? null : certs.get(0); + } + // Only set signature if overrideCert is non-null + if (overrideCert != null) { + // Use "SHA-1" (with hyphen) for consistency with Java MessageDigest standard authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-1"), "")); } } catch (Exception e) { From 4e62aa9ec205ab4f9f31ae94dfaeaf319c1db36f Mon Sep 17 00:00:00 2001 From: vijaya sekhar pasupuleti Date: Tue, 12 May 2026 23:43:50 +0530 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20Remove=20redundant=20certs=20block,?= =?UTF-8?q?=20use=20single=20null-safe=20overrideCert=20resolution=20The?= =?UTF-8?q?=20override=20cert=20was=20computed=20twice=20=E2=80=94=20once?= =?UTF-8?q?=20in=20the=20isOverrideAllowed=20block=20and=20again=20below?= =?UTF-8?q?=20for=20setPackageSignature.=20Consolidate=20into=20a=20single?= =?UTF-8?q?=20resolution:=20-=20When=20overridePackage=20is=20provided,=20?= =?UTF-8?q?resolve=20and=20store=20overrideCert=20once=20-=20Pass=20it=20d?= =?UTF-8?q?own=20for=20both=20the=20userdata=20key=20check=20and=20setPack?= =?UTF-8?q?ageSignature=20-=20Use=20'certs.isEmpty()=20=3F=20null=20:=20ce?= =?UTF-8?q?rts.get(0)'=20explicitly=20(no=20outer=20guard)=20-=20Null-chec?= =?UTF-8?q?k=20overrideCert=20before=20calling=20setPackageSignature=20Add?= =?UTF-8?q?resses=20follow-up=20review=20comment=20on=20PR=20#3468.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/microg/gms/auth/AuthManagerServiceImpl.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index bfb6075788..ab5712a9f5 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -135,13 +135,10 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) */ scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", ""); - // Issue #4: Only check KEY_OVERRIDE_PACKAGE (not OR with certificate key) String effectivePackageName = packageName; if (extras.containsKey(AccountAuthenticator.KEY_OVERRIDE_PACKAGE)) { - // Issue #1: Don't use packageName as fallback default String overridePackage = extras.getString(AccountAuthenticator.KEY_OVERRIDE_PACKAGE); if (overridePackage != null && !overridePackage.isEmpty()) { - // Issue #2: Safely get requesting cert with bounds check List certs = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName); if (certs.isEmpty()) { Log.w(TAG, "getTokenWithAccount: no certificates found for requesting package " + packageName); @@ -151,7 +148,6 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) } CertData requestingCert = certs.get(0); - // Get override cert from extras or use requesting cert byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); CertData overrideCert = (overrideCertificateBytes != null) ? new CertData(overrideCertificateBytes) @@ -171,15 +167,12 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) if (isOverrideAllowed) { effectivePackageName = overridePackage; Log.d(TAG, "getTokenWithAccount: using package override " + packageName + " -> " + effectivePackageName); - // Will set signature below after AuthManager creation } else { - // Issue #3: Add KEY_ACCOUNT_NAME and KEY_ACCOUNT_TYPE to result Bundle result = new Bundle(); result.putString(KEY_ERROR, "NeedPermission"); result.putString(KEY_ACCOUNT_NAME, account.name); result.putString(KEY_ACCOUNT_TYPE, account.type); - // Issue #6: Only pass necessary keys to AskPackageOverrideActivity, not full extras Intent i = new Intent(context, AskPackageOverrideActivity.class); i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); i.putExtra(KEY_ACCOUNT_TYPE, account.type); @@ -193,22 +186,18 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) } AuthManager authManager = new AuthManager(context, account.name, effectivePackageName, scope); - // Set override signature if override was approved + if (!effectivePackageName.equals(packageName)) { - // Override is in effect; set signature from override cert try { byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); CertData overrideCert; if (overrideCertificateBytes != null) { overrideCert = new CertData(overrideCertificateBytes); } else { - // Explicitly null-safe: getCertificates may return empty list List certs = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName); overrideCert = certs.isEmpty() ? null : certs.get(0); } - // Only set signature if overrideCert is non-null if (overrideCert != null) { - // Use "SHA-1" (with hyphen) for consistency with Java MessageDigest standard authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-1"), "")); } } catch (Exception e) { From 9f058499832930f4324de0a704a2ee2e5bdd4c9a Mon Sep 17 00:00:00 2001 From: vijaya sekhar pasupuleti Date: Wed, 13 May 2026 08:05:48 +0530 Subject: [PATCH 5/5] Refactor AuthManagerServiceImpl: extract constants and document signature handling --- .../gms/auth/AuthManagerServiceImpl.java | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index ab5712a9f5..b72115f195 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -80,6 +80,14 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { public static final String KEY_ERROR = "Error"; public static final String KEY_USER_RECOVERY_INTENT = "userRecoveryIntent"; + // Error value constants — extracted to avoid magic strings and typos + private static final String ERROR_NEED_PERMISSION = "NeedPermission"; + private static final String ERROR_NETWORK_ERROR = "NetworkError"; + private static final String ERROR_OK = "OK"; + + // The value stored in account userData when a package override has been granted + private static final String OVERRIDE_ALLOWED_VALUE = "1"; + private final Context context; public AuthManagerServiceImpl(Context context) { @@ -143,16 +151,22 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) if (certs.isEmpty()) { Log.w(TAG, "getTokenWithAccount: no certificates found for requesting package " + packageName); Bundle result = new Bundle(); - result.putString(KEY_ERROR, "NeedPermission"); + result.putString(KEY_ERROR, ERROR_NEED_PERMISSION); return result; } + // Use certs.get(0) — the oldest (leaf) signing certificate — as the stable identity + // for the requesting package. With APK Signature Scheme v3 key rotation, newer + // certificates may be present but the original cert is what was recorded when the + // override permission was granted (stored in account userData using this digest), + // so we must use the same cert to reproduce the matching key. This is consistent + // with how AccountAuthenticator.getAuthToken resolves the requesting certificate. CertData requestingCert = certs.get(0); - + byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); - CertData overrideCert = (overrideCertificateBytes != null) - ? new CertData(overrideCertificateBytes) + CertData overrideCert = (overrideCertificateBytes != null) + ? new CertData(overrideCertificateBytes) : requestingCert; - + boolean isOverrideAllowed; if (packageName.equals(context.getPackageName())) { isOverrideAllowed = true; @@ -161,18 +175,18 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) String overrideCertificateDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-256"), ""); String overrideUserDataKey = "override." + packageName + ":" + requestingDigestString + ":" + overridePackage + ":" + overrideCertificateDigestString; String hasOverride = AccountManager.get(context).getUserData(account, overrideUserDataKey); - isOverrideAllowed = "1".equals(hasOverride); + isOverrideAllowed = OVERRIDE_ALLOWED_VALUE.equals(hasOverride); } - + if (isOverrideAllowed) { effectivePackageName = overridePackage; Log.d(TAG, "getTokenWithAccount: using package override " + packageName + " -> " + effectivePackageName); } else { Bundle result = new Bundle(); - result.putString(KEY_ERROR, "NeedPermission"); + result.putString(KEY_ERROR, ERROR_NEED_PERMISSION); result.putString(KEY_ACCOUNT_NAME, account.name); result.putString(KEY_ACCOUNT_TYPE, account.type); - + Intent i = new Intent(context, AskPackageOverrideActivity.class); i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); i.putExtra(KEY_ACCOUNT_TYPE, account.type); @@ -188,31 +202,35 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) AuthManager authManager = new AuthManager(context, account.name, effectivePackageName, scope); if (!effectivePackageName.equals(packageName)) { + // SHA-1 is used here intentionally: Google's Auth servers require the package + // signature in SHA-1 hex form for legacy compatibility. This is not used for + // security decisions — the authorization check above uses SHA-256. try { byte[] overrideCertificateBytes = extras.getByteArray(AccountAuthenticator.KEY_OVERRIDE_CERTIFICATE); CertData overrideCert; if (overrideCertificateBytes != null) { overrideCert = new CertData(overrideCertificateBytes); } else { + // See comment above about certs.get(0) and APK Signature Scheme v3 List certs = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), packageName); overrideCert = certs.isEmpty() ? null : certs.get(0); } if (overrideCert != null) { authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA-1"), "")); } - } catch (Exception e) { - Log.w(TAG, "getTokenWithAccount: error setting override signature", e); + } catch (java.security.NoSuchAlgorithmException e) { + Log.w(TAG, "getTokenWithAccount: unsupported digest algorithm when setting override signature", e); } } - if (extras.containsKey(KEY_DELEGATION_TYPE) && extras.getInt(KEY_DELEGATION_TYPE) != 0 ) { - authManager.setDelegation(extras.getInt(KEY_DELEGATION_TYPE), extras.getString("delegatee_user_id")); + if (extras.containsKey(KEY_DELEGATION_TYPE) && extras.getInt(KEY_DELEGATION_TYPE) != 0) { + authManager.setDelegation(extras.getInt(KEY_DELEGATION_TYPE), extras.getString(KEY_DELEGATEE_USER_ID)); } authManager.setOauth2Foreground(notify ? "0" : "1"); Bundle result = new Bundle(); result.putString(KEY_ACCOUNT_NAME, account.name); result.putString(KEY_ACCOUNT_TYPE, authManager.getAccountType()); if (!authManager.accountExists()) { - result.putString(KEY_ERROR, "NetworkError"); + result.putString(KEY_ERROR, ERROR_NETWORK_ERROR); return result; } try { @@ -224,9 +242,9 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) Bundle details = new Bundle(); details.putParcelable("TokenData", new TokenData(res.auth, res.expiry, scope.startsWith("oauth2:"), getScopes(res.grantedScopes != null ? res.grantedScopes : scope))); result.putBundle("tokenDetails", details); - result.putString(KEY_ERROR, "OK"); + result.putString(KEY_ERROR, ERROR_OK); } else { - result.putString(KEY_ERROR, "NeedPermission"); + result.putString(KEY_ERROR, ERROR_NEED_PERMISSION); Intent i = new Intent(context, AskPermissionActivity.class); i.putExtras(extras); i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); @@ -254,7 +272,7 @@ public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) } } catch (IOException e) { Log.w(TAG, e); - result.putString(KEY_ERROR, "NetworkError"); + result.putString(KEY_ERROR, ERROR_NETWORK_ERROR); } return result; }