diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 95c5c8d..b83a63e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,7 +21,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Ava" - android:usesCleartextTraffic="true"> + android:networkSecurityConfig="@xml/network_security_config"> { val autoStart: SettingState get() = setting(get = { autoStart }, set = { copy(autoStart = it) }) + /** + * Whether to trust all SSL certificates. + */ + val trustAllSSLCerts: SettingState + get() = setting( + get = { this.trustAllSSLCerts }, + set = { copy(trustAllSSLCerts = it) }) + /** * Ensures that a mac address has been generated and persisted. */ diff --git a/app/src/main/java/com/example/ava/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/example/ava/ui/screens/settings/SettingsScreen.kt index e9fb926..3d686de 100644 --- a/app/src/main/java/com/example/ava/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/example/ava/ui/screens/settings/SettingsScreen.kt @@ -214,5 +214,17 @@ fun SettingsScreen( onClearRequest = { viewModel.resetErrorSound() } ) } + item { + HorizontalDivider() + } + item { + SwitchSetting( + name = stringResource(R.string.label_trust_all_ssl_certs), + description = stringResource(R.string.description_trust_all_ssl_certs), + value = satelliteState?.trustAllSSLCerts ?: false, + enabled = enabled, + onCheckedChange = { viewModel.saveTrustAllSSLCerts(it) } + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/ava/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/example/ava/ui/screens/settings/SettingsViewModel.kt index cad8368..f58dd2a 100644 --- a/app/src/main/java/com/example/ava/ui/screens/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/example/ava/ui/screens/settings/SettingsViewModel.kt @@ -76,6 +76,10 @@ class SettingsViewModel @Inject constructor( satelliteSettingsStore.autoStart.set(autoStart) } + fun saveTrustAllSSLCerts(trustAllSSlCerts: Boolean) = viewModelScope.launch { + satelliteSettingsStore.trustAllSSLCerts.set(trustAllSSlCerts) + } + fun saveWakeWord(wakeWordId: String, availableWakeWords: List) = viewModelScope.launch { if (availableWakeWords.any { it.id == wakeWordId }) { diff --git a/app/src/main/java/com/example/ava/utils/NetworkUtils.kt b/app/src/main/java/com/example/ava/utils/NetworkUtils.kt index d53fab1..8e4aeee 100644 --- a/app/src/main/java/com/example/ava/utils/NetworkUtils.kt +++ b/app/src/main/java/com/example/ava/utils/NetworkUtils.kt @@ -1,5 +1,13 @@ package com.example.ava.utils +import android.annotation.SuppressLint +import timber.log.Timber +import java.security.SecureRandom +import java.security.cert.X509Certificate +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager import kotlin.random.Random @OptIn(ExperimentalStdlibApi::class) @@ -10,4 +18,38 @@ fun getRandomMacAddressString(): String { upperCase = true bytes { byteSeparator = ":" } }) +} + +/** + * A TrustManager that trusts all certificates. + */ +@SuppressLint("CustomX509TrustManager") +private object TrustAllTrustManager : X509TrustManager { + @SuppressLint("TrustAllX509TrustManager") + override fun checkClientTrusted(chain: Array?, authType: String?) { + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkServerTrusted(chain: Array?, authType: String?) { + } + + override fun getAcceptedIssuers(): Array = arrayOf() +} + +/** + * Sets whether SSL connections should trust all certificates, or use the default trust manager. + * Can be used as an unsafe workaround when using self-signed certificates. + */ +fun setTrustAllSSLCertificates(trustAll: Boolean) { + // If disabling, use a custom TrustManager that trusts all certificates, + // else set to null to use the default trust manager to [re]enable verification. + val trustManager = if (trustAll) arrayOf(TrustAllTrustManager) else null + + runCatching { + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, trustManager, SecureRandom()) + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) + }.onFailure { t -> + Timber.e(t, "Failed to ${if (trustAll) "disable" else "enable"} SSL verification") + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c4f5656..74acf98 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,6 +28,8 @@ Play a sound when a voice assistant error occurs Custom error sound Specify a different file to play when a voice assistant error occurs + Trust all SSL certificates + Allow connections using untrusted self-signed certificates Audio processing Configure advanced microphone audio processing settings diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..c964845 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file