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.pm.PackageManager 21 import android.util.Patterns 22 import com.android.statementservice.retriever.Relation 23 import java.net.URL 24 import java.security.MessageDigest 25 26 internal object StatementUtils { 27 28 /** 29 * Field name for namespace. 30 */ 31 const val NAMESPACE_FIELD = "namespace" 32 33 /** 34 * Supported asset namespaces. 35 */ 36 const val NAMESPACE_WEB = "web" 37 const val NAMESPACE_ANDROID_APP = "android_app" 38 39 /** 40 * Field names in a web asset descriptor. 41 */ 42 const val WEB_ASSET_FIELD_SITE = "site" 43 44 /** 45 * Field names in a Android app asset descriptor. 46 */ 47 const val ANDROID_APP_ASSET_FIELD_PACKAGE_NAME = "package_name" 48 const val ANDROID_APP_ASSET_FIELD_CERT_FPS = "sha256_cert_fingerprints" 49 50 /** 51 * Field names in a statement. 52 */ 53 const val ASSET_DESCRIPTOR_FIELD_RELATION = "relation" 54 const val ASSET_DESCRIPTOR_FIELD_TARGET = "target" 55 const val DELEGATE_FIELD_DELEGATE = "include" 56 57 val HEX_DIGITS = 58 charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F') 59 <lambda>null60 val RELATION by lazy { Relation.create("delegate_permission/common.handle_all_urls") } 61 private const val ANDROID_ASSET_FORMAT = 62 """{"namespace": "android_app", "package_name": "%s", "sha256_cert_fingerprints": [%s]}""" 63 private const val WEB_ASSET_FORMAT = """{"namespace": "web", "site": "%s"}""" 64 <lambda>null65 private val digesterSha256 by lazy { tryOrNull { MessageDigest.getInstance("SHA-256") } } 66 tryOrNullnull67 internal inline fun <T> tryOrNull(block: () -> T) = 68 try { 69 block() 70 } catch (ignored: Exception) { 71 null 72 } 73 74 /** 75 * Returns the normalized sha-256 fingerprints of a given package according to the Android 76 * package manager. 77 */ getCertFingerprintsFromPackageManagernull78 fun getCertFingerprintsFromPackageManager( 79 context: Context, 80 packageName: String 81 ): Result<List<String>> { 82 val signingInfo = try { 83 context.packageManager.getPackageInfo( 84 packageName, 85 PackageManager.GET_SIGNING_CERTIFICATES or PackageManager.MATCH_ANY_USER 86 ) 87 .signingInfo 88 } catch (e: Exception) { 89 return Result.Failure(e) 90 } 91 checkNotNull(signingInfo) 92 return if (signingInfo.hasMultipleSigners()) { 93 signingInfo.apkContentsSigners 94 } else { 95 signingInfo.signingCertificateHistory 96 }.map { 97 val result = computeNormalizedSha256Fingerprint(it.toByteArray()) 98 if (result is Result.Failure) { 99 return result.asType() 100 } else { 101 (result as Result.Success).value 102 } 103 }.let { Result.Success(it) } 104 } 105 106 /** 107 * Computes the hash of the byte array using the specified algorithm, returning a hex string 108 * with a colon between each byte. 109 */ computeNormalizedSha256Fingerprintnull110 fun computeNormalizedSha256Fingerprint(signature: ByteArray) = 111 digesterSha256?.digest(signature) 112 ?.let(StatementUtils::bytesToHexString) 113 ?.let { Result.Success(it) } 114 ?: Result.Failure() 115 bytesToHexStringnull116 private fun bytesToHexString(bytes: ByteArray): String { 117 val hexChars = CharArray(bytes.size * 3 - 1) 118 var bufIndex = 0 119 for (index in bytes.indices) { 120 val byte = bytes[index].toInt() and 0xFF 121 if (index > 0) { 122 hexChars[bufIndex++] = ':' 123 } 124 125 hexChars[bufIndex++] = HEX_DIGITS[byte ushr 4] 126 hexChars[bufIndex++] = HEX_DIGITS[byte and 0x0F] 127 } 128 return String(hexChars) 129 } 130 createAndroidAssetStringnull131 fun createAndroidAssetString(context: Context, packageName: String): Result<String> { 132 val result = getCertFingerprintsFromPackageManager(context, packageName) 133 if (result is Result.Failure) { 134 return result.asType() 135 } 136 return Result.Success( 137 ANDROID_ASSET_FORMAT.format( 138 packageName, 139 (result as Result.Success).value.joinToString(separator = "\", \"") 140 ) 141 ) 142 } 143 createAndroidAssetnull144 fun createAndroidAsset(packageName: String, certFingerprints: List<String>) = 145 String.format( 146 ANDROID_ASSET_FORMAT, 147 packageName, 148 certFingerprints.joinToString(separator = ", ") { "\"$it\"" }) 149 createWebAssetStringnull150 fun createWebAssetString(scheme: String, host: String): Result<String> { 151 if (!Patterns.DOMAIN_NAME.matcher(host).matches()) { 152 return Result.Failure("Input host is not valid.") 153 } 154 if (scheme != "http" && scheme != "https") { 155 return Result.Failure("Input scheme is not valid.") 156 } 157 return Result.Success(WEB_ASSET_FORMAT.format(URL(scheme, host, "").toString())) 158 } 159 160 // Hosts with *. for wildcard subdomain support are verified against their root domain createWebAssetStringnull161 fun createWebAssetString(host: String) = 162 WEB_ASSET_FORMAT.format(URL("https", host.removePrefix("*."), "").toString()) 163 } 164