1 /* <lambda>null2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.statementservice.domain 18 19 import android.content.Context 20 import android.content.pm.verify.domain.DomainVerificationManager 21 import android.net.Network 22 import android.util.Log 23 import androidx.collection.LruCache 24 import com.android.statementservice.network.retriever.StatementRetriever 25 import com.android.statementservice.retriever.AbstractAsset 26 import com.android.statementservice.retriever.AbstractAssetMatcher 27 import com.android.statementservice.retriever.Statement 28 import com.android.statementservice.utils.Result 29 import com.android.statementservice.utils.StatementUtils 30 import com.android.statementservice.utils.component1 31 import com.android.statementservice.utils.component2 32 import com.android.statementservice.utils.component3 33 import java.net.HttpURLConnection 34 import java.util.Optional 35 import java.util.UUID 36 37 private typealias WorkResult = androidx.work.ListenableWorker.Result 38 39 class DomainVerifier private constructor( 40 private val appContext: Context, 41 private val manager: DomainVerificationManager 42 ) { 43 companion object { 44 private val TAG = DomainVerifier::class.java.simpleName 45 private const val DEBUG = false 46 47 private var singleton: DomainVerifier? = null 48 49 fun getInstance(context: Context) = when { 50 singleton != null -> singleton!! 51 else -> synchronized(this) { 52 if (singleton == null) { 53 val appContext = context.applicationContext 54 val manager = 55 appContext.getSystemService(DomainVerificationManager::class.java)!! 56 singleton = DomainVerifier(appContext, manager) 57 } 58 singleton!! 59 } 60 } 61 } 62 63 private val retriever = StatementRetriever() 64 65 private val targetAssetCache = AssetLruCache() 66 67 fun collectHosts(packageNames: Iterable<String>, statusFilter: (Int) -> Boolean): 68 Iterable<Triple<UUID, String, Iterable<String>>> { 69 return packageNames.mapNotNull { packageName -> 70 val (domainSetId, _, hostToStateMap) = try { 71 manager.getDomainVerificationInfo(packageName) 72 } catch (ignored: Exception) { 73 // Package disappeared, assume it will be rescheduled if the package reappears 74 null 75 } ?: return@mapNotNull null 76 77 val hostsToRetry = hostToStateMap 78 .filterValues(statusFilter) 79 .takeIf { it.isNotEmpty() } 80 ?.map { it.key } 81 ?: return@mapNotNull null 82 83 Triple(domainSetId, packageName, hostsToRetry) 84 } 85 } 86 87 suspend fun verifyHost( 88 host: String, 89 packageName: String, 90 network: Network? = null 91 ): Triple<WorkResult, VerifyStatus, Statement?> { 92 val assetMatcher = synchronized(targetAssetCache) { targetAssetCache[packageName] } 93 .takeIf { it!!.isPresent } 94 ?: return Triple(WorkResult.failure(), VerifyStatus.FAILURE_PACKAGE_MANAGER, null) 95 return verifyHost(host, assetMatcher.get(), network) 96 } 97 98 private suspend fun verifyHost( 99 host: String, 100 assetMatcher: AbstractAssetMatcher, 101 network: Network? = null 102 ): Triple<WorkResult, VerifyStatus, Statement?> { 103 var exception: Exception? = null 104 val resultAndStatus = try { 105 val sourceAsset = StatementUtils.createWebAssetString(host) 106 .let(AbstractAsset::create) 107 val result = retriever.retrieve(sourceAsset, network) 108 ?: return Triple(WorkResult.success(), VerifyStatus.FAILURE_UNKNOWN, null) 109 when (result.responseCode) { 110 HttpURLConnection.HTTP_MOVED_PERM, 111 HttpURLConnection.HTTP_MOVED_TEMP -> { 112 Triple(WorkResult.failure(), VerifyStatus.FAILURE_REDIRECT, null) 113 } 114 else -> { 115 val statement = result.statements.firstOrNull { statement -> 116 (StatementUtils.RELATION.matches(statement.relation) && 117 assetMatcher.matches(statement.target)) 118 } 119 120 if (statement != null) { 121 Triple(WorkResult.success(), VerifyStatus.SUCCESS, statement) 122 } else { 123 Triple(WorkResult.failure(), VerifyStatus.FAILURE_REJECTED_BY_SERVER, statement) 124 } 125 } 126 } 127 } catch (e: Exception) { 128 exception = e 129 Triple(WorkResult.retry(), VerifyStatus.FAILURE_UNKNOWN, null) 130 } 131 132 if (DEBUG) { 133 Log.d(TAG, "Verifying $host: ${resultAndStatus.second}", exception) 134 } 135 136 return resultAndStatus 137 } 138 139 private inner class AssetLruCache : LruCache<String, Optional<AbstractAssetMatcher>>(50) { 140 override fun create(packageName: String) = 141 StatementUtils.getCertFingerprintsFromPackageManager(appContext, packageName) 142 .let { (it as? Result.Success)?.value } 143 ?.let { StatementUtils.createAndroidAsset(packageName, it) } 144 ?.let(AbstractAssetMatcher::createMatcher) 145 .let { Optional.ofNullable(it) } 146 } 147 } 148