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 }