Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<service
android:name=".services.VoiceSatelliteService"
android:enabled="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.example.ava.nsd.registerVoiceSatelliteNsd
import com.example.ava.settings.VoiceSatelliteSettings
import com.example.ava.settings.VoiceSatelliteSettingsStore
import com.example.ava.tasker.observeTaskerState
import com.example.ava.utils.setTrustAllSSLCertificates
import com.example.ava.utils.translate
import com.example.ava.wakelocks.WifiWakeLock
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -75,6 +76,7 @@ class VoiceSatelliteService() : LifecycleService() {
wifiWakeLock.create(applicationContext, TAG)
createVoiceSatelliteServiceNotificationChannel(this)
updateNotificationOnStateChanges()
startNetworkSettingsObserver()
startTaskerStateObserver()
}

Expand Down Expand Up @@ -110,6 +112,10 @@ class VoiceSatelliteService() : LifecycleService() {
return super.onStartCommand(intent, flags, startId)
}

fun startNetworkSettingsObserver() = satelliteSettingsStore.trustAllSSLCerts.onEach {
setTrustAllSSLCertificates(it)
}.launchIn(lifecycleScope)

private fun startTaskerStateObserver() = lifecycleScope.launch {
_voiceSatellite.collectLatest { it?.observeTaskerState(this@VoiceSatelliteService) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ data class VoiceSatelliteSettings(
val name: String = "Android Voice Assistant",
val serverPort: Int = DEFAULT_SERVER_PORT,
val macAddress: String = DEFAULT_MAC_ADDRESS,
val autoStart: Boolean = false
val autoStart: Boolean = false,
val trustAllSSLCerts: Boolean = false,
)

private val DEFAULT = VoiceSatelliteSettings()
Expand Down Expand Up @@ -69,6 +70,14 @@ interface VoiceSatelliteSettingsStore : SettingsStore<VoiceSatelliteSettings> {
val autoStart: SettingState<Boolean>
get() = setting(get = { autoStart }, set = { copy(autoStart = it) })

/**
* Whether to trust all SSL certificates.
*/
val trustAllSSLCerts: SettingState<Boolean>
get() = setting(
get = { this.trustAllSSLCerts },
set = { copy(trustAllSSLCerts = it) })

/**
* Ensures that a mac address has been generated and persisted.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<WakeWordWithId>) =
viewModelScope.launch {
if (availableWakeWords.any { it.id == wakeWordId }) {
Expand Down
42 changes: 42 additions & 0 deletions app/src/main/java/com/example/ava/utils/NetworkUtils.kt
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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<out X509Certificate>?, authType: String?) {
}

@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
}

override fun getAcceptedIssuers(): Array<out X509Certificate?> = 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<TrustManager>(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")
}
}
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
<string name="description_voice_satellite_enable_error_sound">Play a sound when a voice assistant error occurs</string>
<string name="label_custom_error_sound">Custom error sound</string>
<string name="description_custom_error_sound_location">Specify a different file to play when a voice assistant error occurs</string>
<string name="label_trust_all_ssl_certs">Trust all SSL certificates</string>
<string name="description_trust_all_ssl_certs">Allow connections using untrusted self-signed certificates</string>

<string name="label_audio_processing">Audio processing</string>
<string name="description_audio_processing">Configure advanced microphone audio processing settings</string>
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>

<network-security-config xmlns:tools="http://schemas.android.com/tools">
<!-- We need to allow clear traffic for those who don't have SSL setup. -->
<base-config cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration">
<trust-anchors>
<!-- Trust preinstalled CAs -->
<certificates src="system"/>
<!-- Additionally trust user added CAs -->
<certificates src="user"
tools:ignore="AcceptsUserCertificates" />
</trust-anchors>
</base-config>
</network-security-config>
Loading