1 /*
2  * Copyright (C) 2021 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.utils
18 
19 import android.content.Context
20 import android.content.UriRelativeFilter
21 import android.content.UriRelativeFilter.FRAGMENT
22 import android.content.UriRelativeFilter.PATH
23 import android.content.UriRelativeFilter.QUERY
24 import android.content.UriRelativeFilterGroup
25 import android.content.UriRelativeFilterGroup.ACTION_ALLOW
26 import android.content.UriRelativeFilterGroup.ACTION_BLOCK
27 import android.content.pm.PackageManager
28 import android.util.Patterns
29 import com.android.statementservice.parser.parseMatchingExpression
30 import com.android.statementservice.retriever.DynamicAppLinkComponent
31 import com.android.statementservice.retriever.Relation
32 import java.net.URL
33 import java.security.MessageDigest
34 
35 internal object StatementUtils {
36 
37     /**
38      * Field name for namespace.
39      */
40     const val NAMESPACE_FIELD = "namespace"
41 
42     /**
43      * Supported asset namespaces.
44      */
45     const val NAMESPACE_WEB = "web"
46     const val NAMESPACE_ANDROID_APP = "android_app"
47 
48     /**
49      * Field names in a web asset descriptor.
50      */
51     const val WEB_ASSET_FIELD_SITE = "site"
52 
53     /**
54      * Field names in a Android app asset descriptor.
55      */
56     const val ANDROID_APP_ASSET_FIELD_PACKAGE_NAME = "package_name"
57     const val ANDROID_APP_ASSET_FIELD_CERT_FPS = "sha256_cert_fingerprints"
58 
59     /**
60      * Field names in a statement.
61      */
62     const val ASSET_DESCRIPTOR_FIELD_RELATION = "relation"
63     const val ASSET_DESCRIPTOR_FIELD_TARGET = "target"
64     const val ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS = "relation_extensions"
65     const val DELEGATE_FIELD_DELEGATE = "include"
66     const val RELATION_EXTENSION_FIELD_DAL_COMPONENTS = "dynamic_app_link_components"
67 
68     val HEX_DIGITS =
69         charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
70 
<lambda>null71     val RELATION by lazy { Relation.create("delegate_permission/common.handle_all_urls") }
72     private const val ANDROID_ASSET_FORMAT =
73         """{"namespace": "android_app", "package_name": "%s", "sha256_cert_fingerprints": [%s]}"""
74     private const val WEB_ASSET_FORMAT = """{"namespace": "web", "site": "%s"}"""
75 
<lambda>null76     private val digesterSha256 by lazy { tryOrNull { MessageDigest.getInstance("SHA-256") } }
77 
tryOrNullnull78     internal inline fun <T> tryOrNull(block: () -> T) =
79         try {
80             block()
81         } catch (ignored: Exception) {
82             null
83         }
84 
85     /**
86      * Returns the normalized sha-256 fingerprints of a given package according to the Android
87      * package manager.
88      */
getCertFingerprintsFromPackageManagernull89     fun getCertFingerprintsFromPackageManager(
90         context: Context,
91         packageName: String
92     ): Result<List<String>> {
93         val signingInfo = try {
94             context.packageManager.getPackageInfo(
95                 packageName,
96                 PackageManager.GET_SIGNING_CERTIFICATES or PackageManager.MATCH_ANY_USER
97             )
98                 .signingInfo
99         } catch (e: Exception) {
100             return Result.Failure(e)
101         }
102         checkNotNull(signingInfo)
103         return if (signingInfo.hasMultipleSigners()) {
104             signingInfo.apkContentsSigners
105         } else {
106             signingInfo.signingCertificateHistory
107         }.map {
108             val result = computeNormalizedSha256Fingerprint(it.toByteArray())
109             if (result is Result.Failure) {
110                 return result.asType()
111             } else {
112                 (result as Result.Success).value
113             }
114         }.let { Result.Success(it) }
115     }
116 
117     /**
118      * Computes the hash of the byte array using the specified algorithm, returning a hex string
119      * with a colon between each byte.
120      */
computeNormalizedSha256Fingerprintnull121     fun computeNormalizedSha256Fingerprint(signature: ByteArray) =
122         digesterSha256?.digest(signature)
123             ?.let(StatementUtils::bytesToHexString)
124             ?.let { Result.Success(it) }
125             ?: Result.Failure()
126 
bytesToHexStringnull127     private fun bytesToHexString(bytes: ByteArray): String {
128         val hexChars = CharArray(bytes.size * 3 - 1)
129         var bufIndex = 0
130         for (index in bytes.indices) {
131             val byte = bytes[index].toInt() and 0xFF
132             if (index > 0) {
133                 hexChars[bufIndex++] = ':'
134             }
135 
136             hexChars[bufIndex++] = HEX_DIGITS[byte ushr 4]
137             hexChars[bufIndex++] = HEX_DIGITS[byte and 0x0F]
138         }
139         return String(hexChars)
140     }
141 
createAndroidAssetStringnull142     fun createAndroidAssetString(context: Context, packageName: String): Result<String> {
143         val result = getCertFingerprintsFromPackageManager(context, packageName)
144         if (result is Result.Failure) {
145             return result.asType()
146         }
147         return Result.Success(
148             ANDROID_ASSET_FORMAT.format(
149                 packageName,
150                 (result as Result.Success).value.joinToString(separator = "\", \"")
151             )
152         )
153     }
154 
createAndroidAssetnull155     fun createAndroidAsset(packageName: String, certFingerprints: List<String>) =
156         String.format(
157             ANDROID_ASSET_FORMAT,
158             packageName,
159             certFingerprints.joinToString(separator = ", ") { "\"$it\"" })
160 
createWebAssetStringnull161     fun createWebAssetString(scheme: String, host: String): Result<String> {
162         if (!Patterns.DOMAIN_NAME.matcher(host).matches()) {
163             return Result.Failure("Input host is not valid.")
164         }
165         if (scheme != "http" && scheme != "https") {
166             return Result.Failure("Input scheme is not valid.")
167         }
168         return Result.Success(WEB_ASSET_FORMAT.format(URL(scheme, host, "").toString()))
169     }
170 
171     // Hosts with *. for wildcard subdomain support are verified against their root domain
createWebAssetStringnull172     fun createWebAssetString(host: String) =
173         WEB_ASSET_FORMAT.format(URL("https", host.removePrefix("*."), "").toString())
174 
175     fun createUriRelativeFilterGroup(component: DynamicAppLinkComponent): UriRelativeFilterGroup {
176         val group = UriRelativeFilterGroup(if (component.exclude) ACTION_BLOCK else ACTION_ALLOW)
177         component.fragment?.let {
178             val (type, filter) = parseMatchingExpression(it)
179             group.addUriRelativeFilter(UriRelativeFilter(FRAGMENT, type, filter))
180         }
181         component.path?.let {
182             val (type, filter) = parseMatchingExpression(it)
183             group.addUriRelativeFilter(UriRelativeFilter(PATH, type, filter))
184         }
185         component.query?.let {
186             for ((k, v) in it) {
187                 val (type, filter) = parseMatchingExpression(k + "=" + v)
188                 group.addUriRelativeFilter(UriRelativeFilter(QUERY, type, filter))
189             }
190         }
191         return group
192     }
193 }
194