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