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