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.utils.Result 28 import com.android.statementservice.utils.StatementUtils 29 import com.android.statementservice.utils.component1 30 import com.android.statementservice.utils.component2 31 import com.android.statementservice.utils.component3 32 import java.net.HttpURLConnection 33 import java.util.Optional 34 import java.util.UUID 35 36 private typealias WorkResult = androidx.work.ListenableWorker.Result 37 38 class DomainVerifier private constructor( 39 private val appContext: Context, 40 private val manager: DomainVerificationManager 41 ) { 42 companion object { 43 private val TAG = DomainVerifier::class.java.simpleName 44 private const val DEBUG = false 45 46 private var singleton: DomainVerifier? = null 47 48 fun getInstance(context: Context) = when { 49 singleton != null -> singleton!! 50 else -> synchronized(this) { 51 if (singleton == null) { 52 val appContext = context.applicationContext 53 val manager = 54 appContext.getSystemService(DomainVerificationManager::class.java)!! 55 singleton = DomainVerifier(appContext, manager) 56 } 57 singleton!! 58 } 59 } 60 } 61 62 private val retriever = StatementRetriever() 63 64 private val targetAssetCache = AssetLruCache() 65 66 fun collectHosts(packageNames: Iterable<String>): Iterable<Triple<UUID, String, String>> { 67 return packageNames.mapNotNull { packageName -> 68 val (domainSetId, _, hostToStateMap) = try { 69 manager.getDomainVerificationInfo(packageName) 70 } catch (ignored: Exception) { 71 // Package disappeared, assume it will be rescheduled if the package reappears 72 null 73 } ?: return@mapNotNull null 74 75 val hostsToRetry = hostToStateMap 76 .filterValues(VerifyStatus::shouldRetry) 77 .takeIf { it.isNotEmpty() } 78 ?.map { it.key } 79 ?: return@mapNotNull null 80 81 hostsToRetry.map { Triple(domainSetId, packageName, it) } 82 } 83 .flatten() 84 } 85 86 suspend fun verifyHost( 87 host: String, 88 packageName: String, 89 network: Network? = null 90 ): Pair<WorkResult, VerifyStatus> { 91 val assetMatcher = synchronized(targetAssetCache) { targetAssetCache[packageName] } 92 .takeIf { it!!.isPresent } 93 ?: return WorkResult.failure() to VerifyStatus.FAILURE_PACKAGE_MANAGER 94 return verifyHost(host, assetMatcher.get(), network) 95 } 96 97 private suspend fun verifyHost( 98 host: String, 99 assetMatcher: AbstractAssetMatcher, 100 network: Network? = null 101 ): Pair<WorkResult, VerifyStatus> { 102 var exception: Exception? = null 103 val resultAndStatus = try { 104 val sourceAsset = StatementUtils.createWebAssetString(host) 105 .let(AbstractAsset::create) 106 val result = retriever.retrieve(sourceAsset, network) 107 ?: return WorkResult.success() to VerifyStatus.FAILURE_UNKNOWN 108 when (result.responseCode) { 109 HttpURLConnection.HTTP_MOVED_PERM, 110 HttpURLConnection.HTTP_MOVED_TEMP -> { 111 WorkResult.failure() to VerifyStatus.FAILURE_REDIRECT 112 } 113 else -> { 114 val isVerified = result.statements.any { statement -> 115 (StatementUtils.RELATION.matches(statement.relation) && 116 assetMatcher.matches(statement.target)) 117 } 118 119 if (isVerified) { 120 WorkResult.success() to VerifyStatus.SUCCESS 121 } else { 122 WorkResult.failure() to VerifyStatus.FAILURE_REJECTED_BY_SERVER 123 } 124 } 125 } 126 } catch (e: Exception) { 127 exception = e 128 WorkResult.retry() to VerifyStatus.FAILURE_UNKNOWN 129 } 130 131 if (DEBUG) { 132 Log.d(TAG, "Verifying $host: ${resultAndStatus.second}", exception) 133 } 134 135 return resultAndStatus 136 } 137 138 private inner class AssetLruCache : LruCache<String, Optional<AbstractAssetMatcher>>(50) { 139 override fun create(packageName: String) = 140 StatementUtils.getCertFingerprintsFromPackageManager(appContext, packageName) 141 .let { (it as? Result.Success)?.value } 142 ?.let { StatementUtils.createAndroidAsset(packageName, it) } 143 ?.let(AbstractAssetMatcher::createMatcher) 144 .let { Optional.ofNullable(it) } 145 } 146 } 147