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