• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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