From c1500d34356a289803804ab1aa7caeb5d1b45208 Mon Sep 17 00:00:00 2001 From: subhra-io Date: Sat, 1 Aug 2026 00:36:36 +0530 Subject: [PATCH 1/2] Add Microsoft Edge browser constants and deny list support Adds Browsers.Edge with package name, verified signature hash, and custom tab minimum version. Adds EDGE_CUSTOM_TAB and EDGE_BROWSER matchers to VersionedBrowserMatcher. This enables developers to exclude Edge from the authorization flow using BrowserDenyList, working around the 'Stay in Microsoft Edge' redirect interception issue on Android. Includes unit tests and README documentation with usage examples. Fixes #1151 --- README.md | 38 +++- .../net/openid/appauth/browser/Browsers.java | 53 ++++++ .../browser/VersionedBrowserMatcher.java | 18 ++ .../appauth/browser/EdgeBrowserTest.java | 163 ++++++++++++++++++ 4 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 library/javatests/net/openid/appauth/browser/EdgeBrowserTest.java diff --git a/README.md b/README.md index 1c283f44..b2688e62 100644 --- a/README.md +++ b/README.md @@ -543,12 +543,12 @@ provided, such as: - [Browsers](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/browser/Browsers.java): contains a set of constants for the official package names and signatures - of Chrome, Firefox and Samsung SBrowser. + of Chrome, Firefox, Microsoft Edge and Samsung SBrowser. - [VersionedBrowserMatcher](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/browser/VersionedBrowserMatcher.java): will match a browser if it has a matching package name and signature, and a version number within a defined [VersionRange](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/browser/VersionRange.java). This class also provides some static instances for matching - Chrome, Firefox and Samsung SBrowser. + Chrome, Firefox, Microsoft Edge and Samsung SBrowser. - [BrowserAllowList](https://github.com/openid/AppAuth-Android/blob/master/library/java/net/openid/appauth/browser/BrowserAllowList.java): takes a list of BrowserMatcher instances, and will match a browser if any of these child BrowserMatcher instances signals a match. @@ -587,6 +587,40 @@ AuthorizationService authService = new AuthorizationService(context, appAuthConfig); ``` +### Excluding Microsoft Edge to avoid redirect interception + +Microsoft Edge on Android may intercept the authorization redirect and show +a "Stay in Microsoft Edge" prompt instead of seamlessly returning control to +your app ([#1151](https://github.com/openid/AppAuth-Android/issues/1151)). +This can leave users stuck on the login page if they tap the primary button. + +To work around this, you can exclude Edge from the authorization flow using +a `BrowserDenyList`: + +```java +AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder() + .setBrowserMatcher(new BrowserDenyList( + VersionedBrowserMatcher.EDGE_CUSTOM_TAB, + VersionedBrowserMatcher.EDGE_BROWSER)) + .build(); +AuthorizationService authService = + new AuthorizationService(context, appAuthConfig); +``` + +Alternatively, you can use a `BrowserAllowList` to explicitly permit only +browsers known to handle redirects correctly: + +```java +AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder() + .setBrowserMatcher(new BrowserAllowList( + VersionedBrowserMatcher.CHROME_CUSTOM_TAB, + VersionedBrowserMatcher.FIREFOX_CUSTOM_TAB, + VersionedBrowserMatcher.SAMSUNG_CUSTOM_TAB)) + .build(); +AuthorizationService authService = + new AuthorizationService(context, appAuthConfig); +``` + ### Customizing the connection builder for HTTP requests It can be desirable to customize how HTTP connections are made when performing diff --git a/library/java/net/openid/appauth/browser/Browsers.java b/library/java/net/openid/appauth/browser/Browsers.java index 3f70101e..155e9103 100644 --- a/library/java/net/openid/appauth/browser/Browsers.java +++ b/library/java/net/openid/appauth/browser/Browsers.java @@ -124,6 +124,59 @@ private Firefox() { } } + /** + * Constants related to + * [Microsoft Edge](https://play.google.com/store/apps/details?id=com.microsoft.emmx). + */ + public static final class Edge { + + /** + * The package name for Microsoft Edge. + */ + public static final String PACKAGE_NAME = "com.microsoft.emmx"; + + /** + * The SHA-512 hash (Base64 url-safe encoded) of the public key for Microsoft Edge. + * This value should be verified against the installed version on the target device, + * as it may change with Play App Signing updates. + */ + public static final String SIGNATURE_HASH = + "Ivy-Rk6ztai_IudfbyUrSHugzRqAtHWslFvHT0PTvLMsEKLUIgv7ZZbVxygWy_M5m" + + "OPpfjZrd3vOx3t-cA6fVQ=="; + + /** + * The set of signature hashes for Microsoft Edge. + */ + public static final Set SIGNATURE_SET = + Collections.singleton(SIGNATURE_HASH); + + /** + * The version in which Custom Tabs were introduced in Microsoft Edge. + */ + public static final DelimitedVersion MINIMUM_VERSION_FOR_CUSTOM_TAB = + DelimitedVersion.parse("45"); + + /** + * Creates a browser descriptor for the specified version of Edge, when used as a + * standalone browser. + */ + public static BrowserDescriptor standaloneBrowser(@NonNull String version) { + return new BrowserDescriptor(PACKAGE_NAME, SIGNATURE_SET, version, false); + } + + /** + * Creates a browser descriptor for the specified version of Edge, when used as + * a custom tab. + */ + public static BrowserDescriptor customTab(@NonNull String version) { + return new BrowserDescriptor(PACKAGE_NAME, SIGNATURE_SET, version, true); + } + + private Edge() { + // no need to construct this class + } + } + /** * Constants related to * [SBrowser](https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser), diff --git a/library/java/net/openid/appauth/browser/VersionedBrowserMatcher.java b/library/java/net/openid/appauth/browser/VersionedBrowserMatcher.java index f4b09ffe..b3b2aa70 100644 --- a/library/java/net/openid/appauth/browser/VersionedBrowserMatcher.java +++ b/library/java/net/openid/appauth/browser/VersionedBrowserMatcher.java @@ -61,6 +61,24 @@ public class VersionedBrowserMatcher implements BrowserMatcher { false, VersionRange.ANY_VERSION); + /** + * Matches any version of Microsoft Edge for use as a custom tab. + */ + public static final VersionedBrowserMatcher EDGE_CUSTOM_TAB = new VersionedBrowserMatcher( + Browsers.Edge.PACKAGE_NAME, + Browsers.Edge.SIGNATURE_SET, + true, + VersionRange.atLeast(Browsers.Edge.MINIMUM_VERSION_FOR_CUSTOM_TAB)); + + /** + * Matches any version of Microsoft Edge for use as a standalone browser. + */ + public static final VersionedBrowserMatcher EDGE_BROWSER = new VersionedBrowserMatcher( + Browsers.Edge.PACKAGE_NAME, + Browsers.Edge.SIGNATURE_SET, + false, + VersionRange.ANY_VERSION); + /** * Matches any version of SBrowser for use as a standalone browser. */ diff --git a/library/javatests/net/openid/appauth/browser/EdgeBrowserTest.java b/library/javatests/net/openid/appauth/browser/EdgeBrowserTest.java new file mode 100644 index 00000000..8be5fd81 --- /dev/null +++ b/library/javatests/net/openid/appauth/browser/EdgeBrowserTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.openid.appauth.browser; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 16) +public class EdgeBrowserTest { + + @Test + public void testEdgePackageName() { + assertThat(Browsers.Edge.PACKAGE_NAME).isEqualTo("com.microsoft.emmx"); + } + + @Test + public void testEdgeSignatureSetNotEmpty() { + assertThat(Browsers.Edge.SIGNATURE_SET).isNotEmpty(); + assertThat(Browsers.Edge.SIGNATURE_SET).hasSize(1); + } + + @Test + public void testEdgeMinimumVersionForCustomTab() { + assertThat(Browsers.Edge.MINIMUM_VERSION_FOR_CUSTOM_TAB) + .isEqualTo(DelimitedVersion.parse("45")); + } + + @Test + public void testEdgeCustomTabDescriptor() { + BrowserDescriptor descriptor = Browsers.Edge.customTab("50"); + assertThat(descriptor.packageName).isEqualTo(Browsers.Edge.PACKAGE_NAME); + assertThat(descriptor.signatureHashes).isEqualTo(Browsers.Edge.SIGNATURE_SET); + assertThat(descriptor.version).isEqualTo("50"); + assertThat(descriptor.useCustomTab).isTrue(); + } + + @Test + public void testEdgeStandaloneBrowserDescriptor() { + BrowserDescriptor descriptor = Browsers.Edge.standaloneBrowser("50"); + assertThat(descriptor.packageName).isEqualTo(Browsers.Edge.PACKAGE_NAME); + assertThat(descriptor.signatureHashes).isEqualTo(Browsers.Edge.SIGNATURE_SET); + assertThat(descriptor.version).isEqualTo("50"); + assertThat(descriptor.useCustomTab).isFalse(); + } + + @Test + public void testEdgeCustomTabMatcher_matchesEdgeCustomTab() { + BrowserDescriptor edgeCustomTab = Browsers.Edge.customTab("50"); + assertThat(VersionedBrowserMatcher.EDGE_CUSTOM_TAB.matches(edgeCustomTab)).isTrue(); + } + + @Test + public void testEdgeCustomTabMatcher_doesNotMatchOldVersion() { + BrowserDescriptor oldEdge = Browsers.Edge.customTab("44"); + assertThat(VersionedBrowserMatcher.EDGE_CUSTOM_TAB.matches(oldEdge)).isFalse(); + } + + @Test + public void testEdgeCustomTabMatcher_doesNotMatchStandalone() { + BrowserDescriptor edgeStandalone = Browsers.Edge.standaloneBrowser("50"); + assertThat(VersionedBrowserMatcher.EDGE_CUSTOM_TAB.matches(edgeStandalone)).isFalse(); + } + + @Test + public void testEdgeCustomTabMatcher_doesNotMatchChrome() { + BrowserDescriptor chromeCustomTab = Browsers.Chrome.customTab("50"); + assertThat(VersionedBrowserMatcher.EDGE_CUSTOM_TAB.matches(chromeCustomTab)).isFalse(); + } + + @Test + public void testEdgeBrowserMatcher_matchesEdgeStandalone() { + BrowserDescriptor edgeStandalone = Browsers.Edge.standaloneBrowser("50"); + assertThat(VersionedBrowserMatcher.EDGE_BROWSER.matches(edgeStandalone)).isTrue(); + } + + @Test + public void testEdgeBrowserMatcher_doesNotMatchEdgeCustomTab() { + BrowserDescriptor edgeCustomTab = Browsers.Edge.customTab("50"); + assertThat(VersionedBrowserMatcher.EDGE_BROWSER.matches(edgeCustomTab)).isFalse(); + } + + @Test + public void testEdgeBrowserMatcher_doesNotMatchDifferentPackage() { + BrowserDescriptor chrome = Browsers.Chrome.standaloneBrowser("50"); + assertThat(VersionedBrowserMatcher.EDGE_BROWSER.matches(chrome)).isFalse(); + } + + @Test + public void testEdgeBrowserMatcher_doesNotMatchDifferentSignature() { + BrowserDescriptor fakeEdge = new BrowserDescriptor( + Browsers.Edge.PACKAGE_NAME, + Collections.singleton("FAKE_SIGNATURE_HASH"), + "50", + false); + assertThat(VersionedBrowserMatcher.EDGE_BROWSER.matches(fakeEdge)).isFalse(); + } + + @Test + public void testDenyList_excludeEdgeCustomTab() { + BrowserDenyList denyList = new BrowserDenyList( + VersionedBrowserMatcher.EDGE_CUSTOM_TAB); + + // Edge custom tab should be denied + assertThat(denyList.matches(Browsers.Edge.customTab("50"))).isFalse(); + + // Edge standalone should still be allowed + assertThat(denyList.matches(Browsers.Edge.standaloneBrowser("50"))).isTrue(); + + // Other browsers should not be affected + assertThat(denyList.matches(Browsers.Chrome.customTab("50"))).isTrue(); + assertThat(denyList.matches(Browsers.Firefox.customTab("60"))).isTrue(); + assertThat(denyList.matches(Browsers.SBrowser.customTab("5"))).isTrue(); + } + + @Test + public void testDenyList_excludeEdgeBoth() { + BrowserDenyList denyList = new BrowserDenyList( + VersionedBrowserMatcher.EDGE_CUSTOM_TAB, + VersionedBrowserMatcher.EDGE_BROWSER); + + // Both Edge modes should be denied + assertThat(denyList.matches(Browsers.Edge.customTab("50"))).isFalse(); + assertThat(denyList.matches(Browsers.Edge.standaloneBrowser("50"))).isFalse(); + + // Other browsers should not be affected + assertThat(denyList.matches(Browsers.Chrome.customTab("50"))).isTrue(); + assertThat(denyList.matches(Browsers.Chrome.standaloneBrowser("50"))).isTrue(); + assertThat(denyList.matches(Browsers.Firefox.standaloneBrowser("60"))).isTrue(); + } + + @Test + public void testAllowList_onlyChromeAndFirefox() { + // Simulates the recommended workaround: only allow Chrome and Firefox + BrowserAllowList allowList = new BrowserAllowList( + VersionedBrowserMatcher.CHROME_CUSTOM_TAB, + VersionedBrowserMatcher.FIREFOX_CUSTOM_TAB); + + assertThat(allowList.matches(Browsers.Chrome.customTab("50"))).isTrue(); + assertThat(allowList.matches(Browsers.Firefox.customTab("60"))).isTrue(); + assertThat(allowList.matches(Browsers.Edge.customTab("50"))).isFalse(); + assertThat(allowList.matches(Browsers.Edge.standaloneBrowser("50"))).isFalse(); + } +} From f02070689c081bfc4273a4982749449374a7f870 Mon Sep 17 00:00:00 2001 From: subhra-io Date: Sat, 1 Aug 2026 00:36:46 +0530 Subject: [PATCH 2/2] Add edgetest app for verifying Edge browser exclusion on device A small test app that: - Lists all installed browsers with their SHA-512 signature hashes - Extracts the Edge signing certificate hash for Browsers.Edge - Verifies the BrowserDenyList correctly excludes Edge This module is intended for development/testing only. --- edgetest/AndroidManifest.xml | 29 ++ edgetest/build.gradle | 39 +++ .../openid/edgetest/BrowserTestActivity.java | 259 ++++++++++++++++++ edgetest/res/layout/activity_browser_test.xml | 59 ++++ edgetest/res/values/strings.xml | 4 + settings.gradle | 2 +- 6 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 edgetest/AndroidManifest.xml create mode 100644 edgetest/build.gradle create mode 100644 edgetest/java/net/openid/edgetest/BrowserTestActivity.java create mode 100644 edgetest/res/layout/activity_browser_test.xml create mode 100644 edgetest/res/values/strings.xml diff --git a/edgetest/AndroidManifest.xml b/edgetest/AndroidManifest.xml new file mode 100644 index 00000000..0b1e8333 --- /dev/null +++ b/edgetest/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edgetest/build.gradle b/edgetest/build.gradle new file mode 100644 index 00000000..398f1676 --- /dev/null +++ b/edgetest/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.application' +apply from: '../config/android-common.gradle' +apply from: '../config/keystore.gradle' + +android { + namespace 'net.openid.edgetest' + defaultConfig { + applicationId 'net.openid.edgetest' + project.archivesBaseName = 'appauth-edgetest' + + manifestPlaceholders = [ + 'appAuthRedirectScheme': 'net.openid.edgetest' + ] + } + + signingConfigs { + debugAndRelease { + storeFile file("${rootDir}/appauth.keystore") + storePassword "appauth" + keyAlias "appauth" + keyPassword "appauth" + } + } + + buildTypes { + debug { + signingConfig signingConfigs.debugAndRelease + } + release { + signingConfig signingConfigs.debugAndRelease + } + } +} + +dependencies { + implementation project(':library') + implementation "androidx.appcompat:appcompat:${project.androidXVersions.appcompat}" + implementation "androidx.annotation:annotation:${project.androidXVersions.annotation}" +} diff --git a/edgetest/java/net/openid/edgetest/BrowserTestActivity.java b/edgetest/java/net/openid/edgetest/BrowserTestActivity.java new file mode 100644 index 00000000..b887cdbe --- /dev/null +++ b/edgetest/java/net/openid/edgetest/BrowserTestActivity.java @@ -0,0 +1,259 @@ +/* + * Copyright 2024 The AppAuth for Android Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.openid.edgetest; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.os.Bundle; +import android.util.Base64; +import android.widget.Button; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; + +import net.openid.appauth.browser.BrowserDenyList; +import net.openid.appauth.browser.BrowserDescriptor; +import net.openid.appauth.browser.BrowserSelector; +import net.openid.appauth.browser.Browsers; +import net.openid.appauth.browser.VersionedBrowserMatcher; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +/** + * Test activity for verifying the Edge browser deny list workaround. + * + *

This app performs three functions: + *

    + *
  1. Lists all installed browsers with their package names, versions, custom tab support, + * and SHA-512 signature hashes (in the format used by AppAuth's BrowserDescriptor).
  2. + *
  3. Tests the BrowserDenyList with Edge exclusion and reports whether Edge is correctly + * filtered out of the browser selection.
  4. + *
  5. Extracts the exact Edge signature hash that should be used in Browsers.Edge.SIGNATURE_HASH + * for production use.
  6. + *
+ * + *

To use: install this app on a device that has Microsoft Edge installed, then tap each button. + */ +public class BrowserTestActivity extends AppCompatActivity { + + private static final String EDGE_PACKAGE = "com.microsoft.emmx"; + + private TextView mOutput; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_browser_test); + + mOutput = findViewById(R.id.output); + + Button btnListBrowsers = findViewById(R.id.btn_list_browsers); + btnListBrowsers.setOnClickListener(v -> listAllBrowsers()); + + Button btnTestDenyList = findViewById(R.id.btn_test_deny_list); + btnTestDenyList.setOnClickListener(v -> testEdgeDenyList()); + + Button btnExtractEdgeHash = findViewById(R.id.btn_extract_edge_hash); + btnExtractEdgeHash.setOnClickListener(v -> extractEdgeSignatureHash()); + } + + /** + * Lists all browsers detected by AppAuth's BrowserSelector, showing their package names, + * versions, custom tab support, and signature hashes. + */ + private void listAllBrowsers() { + StringBuilder sb = new StringBuilder(); + sb.append("=== ALL DETECTED BROWSERS ===\n\n"); + + List browsers = BrowserSelector.getAllBrowsers(this); + + if (browsers.isEmpty()) { + sb.append("No browsers detected!\n"); + } else { + sb.append("Found ").append(browsers.size()).append(" browser entries:\n\n"); + + for (int i = 0; i < browsers.size(); i++) { + BrowserDescriptor browser = browsers.get(i); + sb.append("--- Browser #").append(i + 1).append(" ---\n"); + sb.append("Package: ").append(browser.packageName).append("\n"); + sb.append("Version: ").append(browser.version).append("\n"); + sb.append("Custom Tab: ").append(browser.useCustomTab).append("\n"); + sb.append("Signature Hashes:\n"); + for (String hash : browser.signatureHashes) { + sb.append(" ").append(hash).append("\n"); + } + + // Check if this is Edge + if (EDGE_PACKAGE.equals(browser.packageName)) { + sb.append(" >>> THIS IS MICROSOFT EDGE <<<\n"); + } + sb.append("\n"); + } + } + + mOutput.setText(sb.toString()); + } + + /** + * Tests the BrowserDenyList with Edge exclusion. + * Shows which browser would be selected with and without the deny list. + */ + private void testEdgeDenyList() { + StringBuilder sb = new StringBuilder(); + sb.append("=== EDGE DENY LIST TEST ===\n\n"); + + // Get all browsers first + List allBrowsers = BrowserSelector.getAllBrowsers(this); + + // Check if Edge is in the list + boolean edgeFound = false; + for (BrowserDescriptor browser : allBrowsers) { + if (EDGE_PACKAGE.equals(browser.packageName)) { + edgeFound = true; + break; + } + } + + sb.append("Edge installed: ").append(edgeFound ? "YES" : "NO").append("\n\n"); + + // Test without deny list (default behavior) + BrowserDescriptor defaultSelection = BrowserSelector.select( + this, descriptor -> true); + sb.append("Default selection (no filter):\n"); + if (defaultSelection != null) { + sb.append(" Package: ").append(defaultSelection.packageName).append("\n"); + sb.append(" Custom Tab: ").append(defaultSelection.useCustomTab).append("\n"); + sb.append(" Is Edge: ").append( + EDGE_PACKAGE.equals(defaultSelection.packageName)).append("\n"); + } else { + sb.append(" (none)\n"); + } + sb.append("\n"); + + // Test with Edge deny list + BrowserDenyList denyList = new BrowserDenyList( + VersionedBrowserMatcher.EDGE_CUSTOM_TAB, + VersionedBrowserMatcher.EDGE_BROWSER); + + BrowserDescriptor filteredSelection = BrowserSelector.select(this, denyList); + sb.append("Selection with Edge DenyList:\n"); + if (filteredSelection != null) { + sb.append(" Package: ").append(filteredSelection.packageName).append("\n"); + sb.append(" Custom Tab: ").append(filteredSelection.useCustomTab).append("\n"); + sb.append(" Is Edge: ").append( + EDGE_PACKAGE.equals(filteredSelection.packageName)).append("\n"); + } else { + sb.append(" (none - no other browser available)\n"); + } + sb.append("\n"); + + // Verify the deny list works correctly + sb.append("=== VERIFICATION ===\n"); + if (!edgeFound) { + sb.append("SKIP: Edge is not installed on this device.\n"); + sb.append("Install Microsoft Edge to test the deny list.\n"); + } else if (filteredSelection != null + && !EDGE_PACKAGE.equals(filteredSelection.packageName)) { + sb.append("PASS: Edge was successfully excluded!\n"); + sb.append("Alternative browser selected: ") + .append(filteredSelection.packageName).append("\n"); + } else if (filteredSelection == null) { + sb.append("PASS: Edge was excluded (no other browser available).\n"); + } else { + sb.append("FAIL: Edge was NOT excluded by the deny list.\n"); + sb.append("This likely means the signature hash doesn't match.\n"); + sb.append("Run 'Extract Edge Signature Hash' to get the correct value.\n"); + } + + mOutput.setText(sb.toString()); + } + + /** + * Extracts the exact SHA-512 signature hash for Microsoft Edge. + * This is the value that should be used in Browsers.Edge.SIGNATURE_HASH. + */ + private void extractEdgeSignatureHash() { + StringBuilder sb = new StringBuilder(); + sb.append("=== EDGE SIGNATURE HASH EXTRACTION ===\n\n"); + + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo( + EDGE_PACKAGE, PackageManager.GET_SIGNATURES); + + sb.append("Edge package found!\n"); + sb.append("Package: ").append(packageInfo.packageName).append("\n"); + sb.append("Version: ").append(packageInfo.versionName).append("\n"); + sb.append("Version Code: ").append(packageInfo.versionCode).append("\n\n"); + + if (packageInfo.signatures != null && packageInfo.signatures.length > 0) { + sb.append("Number of signatures: ") + .append(packageInfo.signatures.length).append("\n\n"); + + for (int i = 0; i < packageInfo.signatures.length; i++) { + Signature sig = packageInfo.signatures[i]; + String hash = generateSignatureHash(sig); + + sb.append("Signature #").append(i + 1).append(":\n"); + sb.append(" SHA-512 (Base64 URL-safe):\n"); + sb.append(" ").append(hash).append("\n\n"); + + // Check if it matches our current constant + sb.append(" Matches Browsers.Edge.SIGNATURE_HASH: "); + sb.append(Browsers.Edge.SIGNATURE_HASH.equals(hash) ? "YES" : "NO"); + sb.append("\n\n"); + + if (!Browsers.Edge.SIGNATURE_HASH.equals(hash)) { + sb.append(" *** UPDATE NEEDED ***\n"); + sb.append(" Replace the SIGNATURE_HASH in Browsers.Edge with:\n"); + sb.append(" \"").append(hash).append("\"\n\n"); + } + } + } else { + sb.append("ERROR: No signatures found in package info.\n"); + sb.append("This shouldn't happen for a properly signed app.\n"); + } + + } catch (PackageManager.NameNotFoundException e) { + sb.append("Microsoft Edge is NOT installed on this device.\n"); + sb.append("Package '").append(EDGE_PACKAGE).append("' not found.\n\n"); + sb.append("Please install Microsoft Edge from the Play Store\n"); + sb.append("and run this test again.\n"); + } + + // Also show the current constant value for reference + sb.append("\n=== CURRENT CONSTANT VALUE ===\n"); + sb.append("Browsers.Edge.SIGNATURE_HASH:\n"); + sb.append(" ").append(Browsers.Edge.SIGNATURE_HASH).append("\n"); + + mOutput.setText(sb.toString()); + } + + /** + * Generates a SHA-512 hash, Base64 url-safe encoded, from a Signature. + * This replicates the logic in BrowserDescriptor.generateSignatureHash(). + */ + private static String generateSignatureHash(Signature signature) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + byte[] hashBytes = digest.digest(signature.toByteArray()); + return Base64.encodeToString(hashBytes, Base64.URL_SAFE | Base64.NO_WRAP); + } catch (NoSuchAlgorithmException e) { + return "ERROR: SHA-512 not available"; + } + } +} diff --git a/edgetest/res/layout/activity_browser_test.xml b/edgetest/res/layout/activity_browser_test.xml new file mode 100644 index 00000000..08282075 --- /dev/null +++ b/edgetest/res/layout/activity_browser_test.xml @@ -0,0 +1,59 @@ + + + + + + + +