Loading src/com/android/settings/wifi/details2/CertificateDetailsPreferenceController.kt +48 −12 Original line number Diff line number Diff line Loading @@ -37,6 +37,9 @@ import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.wifi.flags.Flags import com.android.wifitrackerlib.WifiEntry import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE import java.security.KeyStore import java.security.cert.X509Certificate Loading @@ -44,15 +47,13 @@ class CertificateDetailsPreferenceController(context: Context, preferenceKey: St ComposePreferenceController(context, preferenceKey) { private lateinit var wifiEntry: WifiEntry lateinit var certificateAliases: String lateinit var certX509: X509Certificate fun setWifiEntry(entry: WifiEntry) { wifiEntry = entry } override fun getAvailabilityStatus(): Int { return if (Flags.androidVWifiApi() && getCertX509(wifiEntry)) AVAILABLE return if (Flags.androidVWifiApi() && isCertificateDetailsAvailable(wifiEntry)) AVAILABLE else CONDITIONALLY_UNAVAILABLE } Loading @@ -64,26 +65,52 @@ class CertificateDetailsPreferenceController(context: Context, preferenceKey: St @Composable fun CertificateDetails() { val context = LocalContext.current val validationMethod = wifiEntry.certificateInfo!!.validationMethod val certificateDetailsSummary = when (validationMethod) { CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE -> stringResource(R.string.wifi_certificate_summary_system) CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA -> { val aliasesSize = wifiEntry.certificateInfo?.caCertificateAliases?.size if (aliasesSize == 1) stringResource(R.string.one_cacrt) else String.format( stringResource(R.string.wifi_certificate_summary_Certificates), aliasesSize ) } else -> stringResource(R.string.wifi_certificate_summary_pinning) } Preference(object : PreferenceModel { override val title = stringResource(com.android.internal.R.string.ssl_certificate) override val summary = { certificateAliases } override val onClick: () -> Unit = { createCertificateDetailsDialog(context, certX509) } override val summary = { certificateDetailsSummary } override val onClick: () -> Unit = { if (validationMethod == CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA) getCertX509(wifiEntry)?.let { createCertificateDetailsDialog( context, it ) } } }) } private fun getCertX509(wifiEntry: WifiEntry): Boolean { certificateAliases = wifiEntry.wifiConfiguration?.enterpriseConfig?.caCertificateAliases?.get(0) ?: return false private fun getCertX509(wifiEntry: WifiEntry): X509Certificate? { val certificateAliases = wifiEntry.certificateInfo?.caCertificateAliases?.get(0) ?: return null return try { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(AndroidKeyStoreLoadStoreParameter(KeyProperties.NAMESPACE_WIFI)) val cert = keyStore.getCertificate(certificateAliases) certX509 = KeyChain.toCertificate(cert.encoded) true KeyChain.toCertificate(cert.encoded) } catch (e: Exception) { Log.e(TAG, "Failed to open Android Keystore.", e) false null } } Loading Loading @@ -124,6 +151,15 @@ class CertificateDetailsPreferenceController(context: Context, preferenceKey: St dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false) } private fun isCertificateDetailsAvailable(wifiEntry: WifiEntry): Boolean { val validationMethod = wifiEntry.certificateInfo?.validationMethod return validationMethod in listOf( CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE, CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA, CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING ) } companion object { const val TAG = "CertificateDetailsPreferenceController" } Loading tests/spa_unit/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceControllerTest.kt +29 −8 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.settings.wifi.details2 import android.content.Context import android.platform.test.annotations.RequiresFlagsEnabled import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed Loading @@ -24,13 +25,15 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import java.security.cert.X509Certificate import com.android.settings.R import com.android.wifitrackerlib.WifiEntry import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doNothing import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever Loading @@ -40,21 +43,26 @@ class CertificateDetailsPreferenceControllerTest { @get:Rule val composeTestRule = createComposeRule() private val mockCertX509 = mock<X509Certificate> {} private val context: Context = spy(ApplicationProvider.getApplicationContext()) { doNothing().whenever(mock).startActivity(any()) } private val controller = CertificateDetailsPreferenceController(context, TEST_KEY) private val mockCertificateInfo = mock<WifiEntry.CertificateInfo> { it.validationMethod = WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA it.caCertificateAliases = arrayOf(MOCK_CA) } private val mockWifiEntry = mock<WifiEntry> { on { certificateInfo } doReturn mockCertificateInfo } @Before fun setUp() { controller.certificateAliases = MOCK_CA controller.certX509 = mockCertX509 controller.setWifiEntry(mockWifiEntry) } @Test @RequiresFlagsEnabled(com.android.wifi.flags.Flags.FLAG_ANDROID_V_WIFI_API) fun title_isDisplayed() { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { Loading @@ -62,8 +70,21 @@ class CertificateDetailsPreferenceControllerTest { } } composeTestRule.onNodeWithText(context.getString(com.android.internal.R.string.ssl_certificate)) .assertIsDisplayed() composeTestRule.onNodeWithText( context.getString(com.android.internal.R.string.ssl_certificate) ).assertIsDisplayed() } @Test @RequiresFlagsEnabled(com.android.wifi.flags.Flags.FLAG_ANDROID_V_WIFI_API) fun one_caCertificate_summary() { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { controller.Content() } } composeTestRule.onNodeWithText(context.getString(R.string.one_cacrt)).assertIsDisplayed() } private companion object { Loading Loading
src/com/android/settings/wifi/details2/CertificateDetailsPreferenceController.kt +48 −12 Original line number Diff line number Diff line Loading @@ -37,6 +37,9 @@ import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.wifi.flags.Flags import com.android.wifitrackerlib.WifiEntry import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA import com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE import java.security.KeyStore import java.security.cert.X509Certificate Loading @@ -44,15 +47,13 @@ class CertificateDetailsPreferenceController(context: Context, preferenceKey: St ComposePreferenceController(context, preferenceKey) { private lateinit var wifiEntry: WifiEntry lateinit var certificateAliases: String lateinit var certX509: X509Certificate fun setWifiEntry(entry: WifiEntry) { wifiEntry = entry } override fun getAvailabilityStatus(): Int { return if (Flags.androidVWifiApi() && getCertX509(wifiEntry)) AVAILABLE return if (Flags.androidVWifiApi() && isCertificateDetailsAvailable(wifiEntry)) AVAILABLE else CONDITIONALLY_UNAVAILABLE } Loading @@ -64,26 +65,52 @@ class CertificateDetailsPreferenceController(context: Context, preferenceKey: St @Composable fun CertificateDetails() { val context = LocalContext.current val validationMethod = wifiEntry.certificateInfo!!.validationMethod val certificateDetailsSummary = when (validationMethod) { CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE -> stringResource(R.string.wifi_certificate_summary_system) CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA -> { val aliasesSize = wifiEntry.certificateInfo?.caCertificateAliases?.size if (aliasesSize == 1) stringResource(R.string.one_cacrt) else String.format( stringResource(R.string.wifi_certificate_summary_Certificates), aliasesSize ) } else -> stringResource(R.string.wifi_certificate_summary_pinning) } Preference(object : PreferenceModel { override val title = stringResource(com.android.internal.R.string.ssl_certificate) override val summary = { certificateAliases } override val onClick: () -> Unit = { createCertificateDetailsDialog(context, certX509) } override val summary = { certificateDetailsSummary } override val onClick: () -> Unit = { if (validationMethod == CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA) getCertX509(wifiEntry)?.let { createCertificateDetailsDialog( context, it ) } } }) } private fun getCertX509(wifiEntry: WifiEntry): Boolean { certificateAliases = wifiEntry.wifiConfiguration?.enterpriseConfig?.caCertificateAliases?.get(0) ?: return false private fun getCertX509(wifiEntry: WifiEntry): X509Certificate? { val certificateAliases = wifiEntry.certificateInfo?.caCertificateAliases?.get(0) ?: return null return try { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(AndroidKeyStoreLoadStoreParameter(KeyProperties.NAMESPACE_WIFI)) val cert = keyStore.getCertificate(certificateAliases) certX509 = KeyChain.toCertificate(cert.encoded) true KeyChain.toCertificate(cert.encoded) } catch (e: Exception) { Log.e(TAG, "Failed to open Android Keystore.", e) false null } } Loading Loading @@ -124,6 +151,15 @@ class CertificateDetailsPreferenceController(context: Context, preferenceKey: St dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(false) } private fun isCertificateDetailsAvailable(wifiEntry: WifiEntry): Boolean { val validationMethod = wifiEntry.certificateInfo?.validationMethod return validationMethod in listOf( CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE, CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA, CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING ) } companion object { const val TAG = "CertificateDetailsPreferenceController" } Loading
tests/spa_unit/src/com/android/settings/wifi/details2/CertificateDetailsPreferenceControllerTest.kt +29 −8 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.settings.wifi.details2 import android.content.Context import android.platform.test.annotations.RequiresFlagsEnabled import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed Loading @@ -24,13 +25,15 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import java.security.cert.X509Certificate import com.android.settings.R import com.android.wifitrackerlib.WifiEntry import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doNothing import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.whenever Loading @@ -40,21 +43,26 @@ class CertificateDetailsPreferenceControllerTest { @get:Rule val composeTestRule = createComposeRule() private val mockCertX509 = mock<X509Certificate> {} private val context: Context = spy(ApplicationProvider.getApplicationContext()) { doNothing().whenever(mock).startActivity(any()) } private val controller = CertificateDetailsPreferenceController(context, TEST_KEY) private val mockCertificateInfo = mock<WifiEntry.CertificateInfo> { it.validationMethod = WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA it.caCertificateAliases = arrayOf(MOCK_CA) } private val mockWifiEntry = mock<WifiEntry> { on { certificateInfo } doReturn mockCertificateInfo } @Before fun setUp() { controller.certificateAliases = MOCK_CA controller.certX509 = mockCertX509 controller.setWifiEntry(mockWifiEntry) } @Test @RequiresFlagsEnabled(com.android.wifi.flags.Flags.FLAG_ANDROID_V_WIFI_API) fun title_isDisplayed() { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { Loading @@ -62,8 +70,21 @@ class CertificateDetailsPreferenceControllerTest { } } composeTestRule.onNodeWithText(context.getString(com.android.internal.R.string.ssl_certificate)) .assertIsDisplayed() composeTestRule.onNodeWithText( context.getString(com.android.internal.R.string.ssl_certificate) ).assertIsDisplayed() } @Test @RequiresFlagsEnabled(com.android.wifi.flags.Flags.FLAG_ANDROID_V_WIFI_API) fun one_caCertificate_summary() { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { controller.Content() } } composeTestRule.onNodeWithText(context.getString(R.string.one_cacrt)).assertIsDisplayed() } private companion object { Loading