1 /* <lambda>null2 * Copyright (C) 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 com.android.settings.wifi.details2 18 19 import android.content.Context 20 import android.content.DialogInterface 21 import android.net.http.SslCertificate 22 import android.security.KeyChain 23 import android.security.keystore.KeyProperties 24 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter 25 import android.util.Log 26 import android.view.View 27 import android.widget.ArrayAdapter 28 import android.widget.LinearLayout 29 import android.widget.Spinner 30 import androidx.appcompat.app.AlertDialog 31 import androidx.compose.runtime.Composable 32 import androidx.compose.ui.platform.LocalContext 33 import androidx.compose.ui.res.stringResource 34 import com.android.settings.R 35 import com.android.settings.spa.preference.ComposePreferenceController 36 import com.android.settingslib.spa.widget.preference.Preference 37 import com.android.settingslib.spa.widget.preference.PreferenceModel 38 import com.android.wifi.flags.Flags 39 import com.android.wifitrackerlib.WifiEntry 40 import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING 41 import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA 42 import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE 43 import java.security.KeyStore 44 import java.security.cert.X509Certificate 45 46 class CertificateDetailsPreferenceController(context: Context, preferenceKey: String) : 47 ComposePreferenceController(context, preferenceKey) { 48 49 private lateinit var wifiEntry: WifiEntry 50 51 fun setWifiEntry(entry: WifiEntry) { 52 wifiEntry = entry 53 } 54 55 override fun getAvailabilityStatus(): Int { 56 return if (Flags.androidVWifiApi() && isCertificateDetailsAvailable(wifiEntry)) AVAILABLE 57 else CONDITIONALLY_UNAVAILABLE 58 } 59 60 @Composable 61 override fun Content() { 62 CertificateDetails() 63 } 64 65 @Composable 66 fun CertificateDetails() { 67 val context = LocalContext.current 68 69 val validationMethod = wifiEntry.certificateInfo!!.validationMethod 70 val certificateDetailsSummary = when (validationMethod) { 71 CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE -> 72 stringResource(R.string.wifi_certificate_summary_system) 73 74 CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA -> { 75 val aliasesSize = wifiEntry.certificateInfo?.caCertificateAliases?.size 76 if (aliasesSize == 1) stringResource(R.string.one_cacrt) 77 else 78 String.format( 79 stringResource(R.string.wifi_certificate_summary_Certificates), 80 aliasesSize 81 ) 82 } 83 84 else -> stringResource(R.string.wifi_certificate_summary_pinning) 85 } 86 87 Preference(object : PreferenceModel { 88 override val title = stringResource(com.android.internal.R.string.ssl_certificate) 89 override val summary = { certificateDetailsSummary } 90 override val onClick: () -> Unit = { 91 if (validationMethod == CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA) 92 getCertX509(wifiEntry)?.let { 93 createCertificateDetailsDialog( 94 context, 95 it 96 ) 97 } 98 } 99 }) 100 } 101 102 private fun getCertX509(wifiEntry: WifiEntry): X509Certificate? { 103 val certificateAliases = 104 wifiEntry.certificateInfo?.caCertificateAliases?.get(0) 105 ?: return null 106 return try { 107 val keyStore = KeyStore.getInstance("AndroidKeyStore") 108 keyStore.load(AndroidKeyStoreLoadStoreParameter(KeyProperties.NAMESPACE_WIFI)) 109 val cert = keyStore.getCertificate(certificateAliases) 110 KeyChain.toCertificate(cert.encoded) 111 } catch (e: Exception) { 112 Log.e(TAG, "Failed to open Android Keystore.", e) 113 null 114 } 115 } 116 117 private fun createCertificateDetailsDialog(context: Context, certX509: X509Certificate) { 118 val listener = 119 DialogInterface.OnClickListener { dialog, id -> 120 dialog.dismiss() 121 } 122 val titles = ArrayList<String>() 123 val sslCert = SslCertificate(certX509) 124 titles.add(sslCert.issuedTo.cName) 125 val arrayAdapter = ArrayAdapter( 126 context, 127 android.R.layout.simple_spinner_item, 128 titles 129 ) 130 arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) 131 val spinner = Spinner(context) 132 spinner.setAdapter(arrayAdapter) 133 134 val certLayout = LinearLayout(context) 135 certLayout.orientation = LinearLayout.VERTICAL 136 // Prevent content overlapping with spinner 137 certLayout.setClipChildren(true) 138 certLayout.addView(spinner) 139 140 val view = sslCert.inflateCertificateView(context) 141 view.visibility = View.VISIBLE 142 certLayout.addView(view) 143 certLayout.visibility = View.VISIBLE 144 145 val dialog = AlertDialog.Builder(context) 146 .setView(certLayout) 147 .setTitle(com.android.internal.R.string.ssl_certificate) 148 .setPositiveButton(R.string.wifi_settings_ssid_block_button_close, null) 149 .setNegativeButton(R.string.trusted_credentials_remove_label, listener).create() 150 dialog.show() 151 dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false) 152 } 153 154 private fun isCertificateDetailsAvailable(wifiEntry: WifiEntry): Boolean { 155 val validationMethod = wifiEntry.certificateInfo?.validationMethod 156 return validationMethod in listOf( 157 CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE, 158 CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA, 159 CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING 160 ) 161 } 162 163 companion object { 164 const val TAG = "CertificateDetailsPreferenceController" 165 } 166 }