1 /*
2  * Copyright 2024 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 androidx.security.state
18 
19 import android.annotation.SuppressLint
20 import android.content.Context
21 import android.content.pm.PackageManager
22 import android.os.Build
23 import android.os.Bundle
24 import android.system.Os
25 import androidx.annotation.RequiresApi
26 import androidx.security.state.SecurityPatchState.Companion.USE_VENDOR_SPL
27 import androidx.webkit.WebViewCompat
28 import java.util.regex.Pattern
29 
30 /**
31  * This class is a wrapper around AOSP [android.os.SecurityStateManager] service API added in
32  * SDK 35. Support for features on older SDKs is provided on a best effort basis.
33  *
34  * Manages the retrieval and storage of security patch levels and module information for an Android
35  * device. This class provides methods to fetch the current security state of the system, including
36  * patch levels for the system, vendor, and kernel as well as module updates available through
37  * Android's update system.
38  *
39  * It utilizes Android's PackageManager and other system services to retrieve detailed
40  * security-related information, which is crucial for maintaining the security integrity of the
41  * device.
42  */
43 public open class SecurityStateManagerCompat(private val context: Context) {
44 
45     public companion object {
46         private const val TAG = "SecurityStateManager"
47         private val kernelReleasePattern: Pattern = Pattern.compile("(\\d+\\.\\d+\\.\\d+)(.*)")
48 
49         private const val VENDOR_SECURITY_PATCH_PROPERTY_KEY: String =
50             "ro.vendor.build.security_patch"
51         private const val ANDROID_MODULE_METADATA_PROVIDER: String = "com.android.modulemetadata"
52 
53         /**
54          * The system SPL key returned as part of the {@code Bundle} from {@code
55          * getGlobalSecurityState}.
56          */
57         public const val KEY_SYSTEM_SPL: String = "system_spl"
58 
59         /**
60          * The vendor SPL key returned as part of the {@code Bundle} from {@code
61          * getGlobalSecurityState}.
62          */
63         public const val KEY_VENDOR_SPL: String = "vendor_spl"
64 
65         /**
66          * The kernel version key returned as part of the {@code Bundle} from {@code
67          * getGlobalSecurityState}.
68          */
69         public const val KEY_KERNEL_VERSION: String = "kernel_version"
70     }
71 
72     private val packageManager: PackageManager = context.packageManager
73 
74     /**
75      * Retrieves the global security state of the device, compiling various security patch levels
76      * and module information into a Bundle. This method can optionally use Google's module metadata
77      * providers to enhance the data returned.
78      *
79      * @param moduleMetadataProviderPackageName Specifies package name for system modules metadata.
80      * @return A Bundle containing keys and values representing the security state of the system,
81      *   vendor, and kernel.
82      */
getGlobalSecurityStatenull83     public open fun getGlobalSecurityState(
84         moduleMetadataProviderPackageName: String = ANDROID_MODULE_METADATA_PROVIDER
85     ): Bundle {
86         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
87             return getGlobalSecurityStateFromService()
88         }
89         return Bundle().apply {
90             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
91                 putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH)
92                 if (USE_VENDOR_SPL) {
93                     putString(KEY_VENDOR_SPL, getVendorSpl())
94                 }
95             }
96             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
97                 if (moduleMetadataProviderPackageName.isNotEmpty()) {
98                     putString(
99                         moduleMetadataProviderPackageName,
100                         getPackageVersion(moduleMetadataProviderPackageName)
101                     )
102                 }
103             }
104             val kernelVersion = getKernelVersion()
105             if (kernelVersion.isNotEmpty()) {
106                 putString(KEY_KERNEL_VERSION, kernelVersion)
107             }
108             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
109                 addWebViewPackages(this)
110             }
111         }
112     }
113 
114     /**
115      * Returns the current global security state from the system service on SDK 35+.
116      *
117      * @return A [Bundle] that contains the global security state information as string-to-string
118      *   key-value pairs.
119      */
120     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
121     @SuppressLint("WrongConstant")
getGlobalSecurityStateFromServicenull122     private fun getGlobalSecurityStateFromService(): Bundle {
123         val securityStateManagerService =
124             context.getSystemService(Context.SECURITY_STATE_SERVICE)
125                 as android.os.SecurityStateManager
126         val globalSecurityState = securityStateManagerService.globalSecurityState
127         if (!USE_VENDOR_SPL) {
128             globalSecurityState.remove(KEY_VENDOR_SPL)
129         }
130         return globalSecurityState
131     }
132 
133     /**
134      * Fetches the security patch level (SPL) for a specific package by its package name. This is
135      * typically used to get version information for modules that may have their own update cycles
136      * independent of the system OS.
137      *
138      * @param packageName The package name for which to retrieve the SPL.
139      * @return A string representing the version, or an empty string if not available or an error
140      *   occurs.
141      * @throws PackageManager.NameNotFoundException if the package name provided does not exist.
142      */
getPackageVersionnull143     internal open fun getPackageVersion(packageName: String): String {
144         if (packageName.isNotEmpty()) {
145             return try {
146                 packageManager.getPackageInfo(packageName, 0).versionName ?: ""
147             } catch (e: PackageManager.NameNotFoundException) {
148                 ""
149             }
150         }
151         return ""
152     }
153 
154     /**
155      * Adds information about the webview packages to a bundle. This is used to track updates and
156      * versions for the webview, which can have security implications.
157      *
158      * @param bundle The bundle to which the webview package information will be added.
159      */
160     @RequiresApi(26)
addWebViewPackagesnull161     private fun addWebViewPackages(bundle: Bundle) {
162         val packageName = getCurrentWebViewPackageName()
163         if (packageName.isNotEmpty()) {
164             bundle.putString(packageName, getPackageVersion(packageName))
165         }
166     }
167 
168     /**
169      * Retrieves the current webview package name used by the system. This method checks the
170      * system's default webview provider and extracts the package name, which is essential for
171      * managing updates and security patches for components relying on webview.
172      *
173      * @return A string representing the current webview package name, or an empty string if it
174      *   cannot be determined.
175      */
getCurrentWebViewPackageNamenull176     private fun getCurrentWebViewPackageName(): String {
177         val webViewPackageInfo =
178             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
179                 WebViewCompat.getCurrentWebViewPackage(context)
180             } else {
181                 null
182             }
183         return webViewPackageInfo?.packageName ?: ""
184     }
185 
186     /**
187      * Safely retrieves the current security patch level of the device's operating system. This
188      * method ensures compatibility by checking the Android version before attempting to access APIs
189      * that are not available on older versions.
190      *
191      * @return A string representing the current security patch level, or empty string if it cannot
192      *   be retrieved.
193      */
getSecurityPatchLevelSafenull194     internal fun getSecurityPatchLevelSafe(): String {
195         return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
196             Build.VERSION.SECURITY_PATCH
197         } else {
198             ""
199         }
200     }
201 
202     /**
203      * Attempts to retrieve the kernel version of the device using the system's uname() command.
204      * This method is used to determine the current kernel version which is a part of the security
205      * state assessment.
206      *
207      * @return A string containing the kernel version, or an empty string if an error occurs or the
208      *   data cannot be reliably parsed.
209      */
getKernelVersionnull210     private fun getKernelVersion(): String {
211         try {
212             val matcher = kernelReleasePattern.matcher(Os.uname().release)
213             return if (matcher.matches()) matcher.group(1)!! else ""
214         } catch (e: Exception) {
215             return ""
216         }
217     }
218 
219     /**
220      * Uses reflection to access Android's SystemProperties to retrieve the vendor security patch
221      * level. This approach is necessary on Android versions where the vendor patch level is not
222      * directly accessible via the public API.
223      *
224      * @return A string representing the vendor's security patch level, or an empty string if it
225      *   cannot be retrieved.
226      */
227     @Suppress("BanUncheckedReflection") // For accessing vendor SPL on SDK older than 35.
getVendorSplnull228     private fun getVendorSpl(): String {
229         try {
230             // This is the only way to get vendor SPL from public API level on Android 14 or older
231             // devices.
232             val systemProperties = Class.forName("android.os.SystemProperties")
233             val getMethod =
234                 systemProperties.getMethod("get", String::class.java, String::class.java)
235             return getMethod.invoke(systemProperties, VENDOR_SECURITY_PATCH_PROPERTY_KEY, "")
236                 as String
237         } catch (e: Exception) {
238             return ""
239         }
240     }
241 }
242