1package com.peachpasswords.autofill23import android.content.Context4import android.content.pm.PackageManager5import android.content.pm.verify.domain.DomainVerificationManager6import android.content.pm.verify.domain.DomainVerificationUserState7import android.os.Build8import com.peachpasswords.shared.extractDomain9import java.security.MessageDigest101112object TrustedBrowsers {13 private const val GOOGLE_RELEASE_CERT =14 "f0fd6c5b410f25cb25c3b53346c8972fae30f8ee7411df910480ad6b2d60db83"15 private const val MOZILLA_RELEASE_CERT =16 "a78b62a5165b4494b2fead9e76a280d22d937fee6251aece599446b2ea319b04"17 private const val BRAVE_RELEASE_CERT =18 "9c2db70513515fdbfbbc585b3edf3d7123d4dc67c94ffd306361c1d79bbf18ac"1920 21 22 private val EXPECTED_CERTIFICATES = mapOf(23 "com.android.chrome" to setOf(GOOGLE_RELEASE_CERT),24 "com.chrome.beta" to setOf(GOOGLE_RELEASE_CERT),25 "com.chrome.dev" to setOf(GOOGLE_RELEASE_CERT),26 "org.mozilla.firefox" to setOf(MOZILLA_RELEASE_CERT),27 "org.mozilla.firefox_beta" to setOf(MOZILLA_RELEASE_CERT),28 "com.brave.browser" to setOf(BRAVE_RELEASE_CERT),29 )3031 @Suppress("DEPRECATION")32 fun isTrusted(context: Context, packageName: String?): Boolean {33 if (packageName !in EXPECTED_CERTIFICATES) return false34 return runCatching {35 context.packageManager36 .getPackageInfo(packageName!!, PackageManager.GET_SIGNATURES)37 .signatures38 .orEmpty()39 .any { hasExpectedCertificate(packageName, sha256(it.toByteArray())) }40 }.getOrDefault(false)41 }4243 internal fun hasExpectedCertificate(packageName: String?, certificateSha256: String): Boolean =44 certificateSha256.lowercase() in EXPECTED_CERTIFICATES[packageName].orEmpty()4546 private fun sha256(bytes: ByteArray): String =47 MessageDigest.getInstance("SHA-256").digest(bytes).joinToString("") {48 (it.toInt() and 0xff).toString(16).padStart(2, '0')49 }50}5152internal fun authenticateAutofillOrigin(context: Context, parsed: ParsedStructure): ParsedStructure {53 val claimedWebDomain = parsed.webDomain54 if (claimedWebDomain != null) {55 val domain = extractDomain(claimedWebDomain)56 ?: return parsed.copy(57 originTrust = AutofillOriginTrust.UNKNOWN,58 verifiedDomains = emptyList(),59 )60 if (TrustedBrowsers.isTrusted(context, parsed.packageName)) {61 return parsed.copy(62 originTrust = AutofillOriginTrust.TRUSTED_WEB,63 verifiedDomains = listOf(domain),64 )65 }66 val appDomains = verifiedAppLinkDomains(context, parsed.packageName)67 return if (domain in appDomains) {68 parsed.copy(69 originTrust = AutofillOriginTrust.TRUSTED_NATIVE,70 verifiedDomains = listOf(domain),71 )72 } else {73 parsed.copy(74 originTrust = AutofillOriginTrust.UNKNOWN,75 verifiedDomains = emptyList(),76 )77 }78 }7980 val appDomains = verifiedAppLinkDomains(context, parsed.packageName)81 return if (appDomains.isEmpty()) {82 parsed.copy(originTrust = AutofillOriginTrust.UNKNOWN, verifiedDomains = emptyList())83 } else {84 parsed.copy(85 originTrust = AutofillOriginTrust.TRUSTED_NATIVE,86 verifiedDomains = appDomains,87 )88 }89}909192internal fun verifiedAppLinkDomains(context: Context, packageName: String?): List<String> {93 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || packageName.isNullOrBlank()) {94 return emptyList()95 }96 return runCatching {97 val state = context.getSystemService(DomainVerificationManager::class.java)98 ?.getDomainVerificationUserState(packageName)99 state?.hostToStateMap.orEmpty()100 .filterValues { it == DomainVerificationUserState.DOMAIN_STATE_VERIFIED }101 .keys102 .mapNotNull { extractDomain(it.removePrefix("*.")) }103 .distinct()104 .sorted()105 }.getOrDefault(emptyList())106}