From 08f3bd7dd2418056a8b822f624377f512615f769 Mon Sep 17 00:00:00 2001 From: ohassine Date: Tue, 11 Aug 2026 15:07:01 +0200 Subject: [PATCH 01/14] feat: show security providers info screen --- .../di/metro/WireMetroViewModelBindings.kt | 7 + .../wire/android/ui/debug/DebugDataOptions.kt | 13 ++ .../ui/debug/DebugInfoViewModelFactory.kt | 7 + .../ui/debug/DebugInfoViewModelGraph.kt | 5 + .../com/wire/android/ui/debug/DebugScreen.kt | 5 + .../securityproviders/AppPathsProvider.kt | 51 +++++++ .../securityproviders/SecurityProvider.kt | 30 ++++ .../SecurityProviderListItem.kt | 128 ++++++++++++++++++ .../SecurityProvidersScreen.kt | 92 +++++++++++++ .../SecurityProvidersViewModel.kt | 69 ++++++++++ app/src/main/res/values/strings.xml | 10 ++ 11 files changed, 417 insertions(+) create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvider.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt diff --git a/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt b/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt index bc4c84b5bd7..4f64fa35e47 100644 --- a/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt +++ b/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt @@ -31,6 +31,7 @@ import com.wire.android.ui.debug.UserDebugViewModel import com.wire.android.ui.debug.conversation.DebugConversationViewModel import com.wire.android.ui.debug.cryptostats.ConversationCryptoStatsViewModel import com.wire.android.ui.debug.featureflags.DebugFeatureFlagsViewModel +import com.wire.android.ui.debug.securityproviders.SecurityProvidersViewModel import com.wire.android.ui.MiscViewModelFactory import com.wire.android.ui.analytics.AnalyticsUsageViewModel import com.wire.android.ui.authentication.AuthenticationManualViewModelFactory @@ -365,6 +366,12 @@ object WireMetroViewModelBindings { fun debugFeatureFlagsViewModel(factory: DebugInfoViewModelFactory): ViewModel = factory.debugFeatureFlagsViewModel() + @Provides + @IntoMap + @ViewModelKey(SecurityProvidersViewModel::class) + fun securityProvidersViewModel(factory: DebugInfoViewModelFactory): ViewModel = + factory.securityProvidersViewModel() + @Provides @IntoMap @ViewModelKey(WhatsNewViewModel::class) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt b/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt index bd768c96a3f..9346d3d7520 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt @@ -69,6 +69,7 @@ fun DebugDataOptions( onCopyText: (String) -> Unit, onShowFeatureFlags: () -> Unit, onShowCryptoStats: () -> Unit, + onShowSecurityProviders: () -> Unit, viewModel: DebugDataOptionsViewModel, ) { LocalSnackbarHostState.current.collectAndShowSnackbar(snackbarFlow = viewModel.infoMessage) @@ -90,6 +91,7 @@ fun DebugDataOptions( onResendFCMToken = viewModel::forceSendFCMToken, onShowFeatureFlags = onShowFeatureFlags, onShowCryptoStats = onShowCryptoStats, + onShowSecurityProviders = onShowSecurityProviders, onRepairFaultyRemovalKeys = viewModel::repairFaultRemovalKeys, ) } @@ -114,6 +116,7 @@ fun DebugDataOptionsContent( onResendFCMToken: () -> Unit, onShowFeatureFlags: () -> Unit, onShowCryptoStats: () -> Unit, + onShowSecurityProviders: () -> Unit, onRepairFaultyRemovalKeys: () -> Unit, modifier: Modifier = Modifier, ) { @@ -210,6 +213,15 @@ fun DebugDataOptionsContent( trailingIcon = commonR.drawable.ic_arrow_right, ) + SettingsItem( + text = stringResource(R.string.debug_settings_security_providers), + onRowPressed = Clickable( + enabled = true, + onClick = onShowSecurityProviders + ), + trailingIcon = commonR.drawable.ic_arrow_right, + ) + if (BuildConfig.PRIVATE_BUILD && BuildConfig.DEBUG_SCREEN_ENABLED) { E2EICertificateEnrollmentSection( expirationInputState = e2eiCertificateExpirationInputState, @@ -414,6 +426,7 @@ fun PreviewOtherDebugOptions() = WireTheme { onResendFCMToken = {}, onShowFeatureFlags = {}, onShowCryptoStats = {}, + onShowSecurityProviders = {}, onRepairFaultyRemovalKeys = {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelFactory.kt b/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelFactory.kt index 8f32c033419..40e3cf0e9d9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelFactory.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelFactory.kt @@ -25,6 +25,8 @@ import com.wire.android.di.CurrentAccount import com.wire.android.ui.debug.conversation.DebugConversationViewModel import com.wire.android.ui.debug.cryptostats.ConversationCryptoStatsViewModel import com.wire.android.ui.debug.featureflags.DebugFeatureFlagsViewModel +import com.wire.android.ui.debug.securityproviders.AppPathsProvider +import com.wire.android.ui.debug.securityproviders.SecurityProvidersViewModel import com.wire.android.ui.home.settings.about.dependencies.DependenciesViewModel import com.wire.android.ui.home.settings.about.licenses.LicensesViewModel import com.wire.android.ui.home.whatsnew.WhatsNewViewModel @@ -151,6 +153,11 @@ class DebugInfoViewModelFactory @Inject constructor( getFeatureConfig = getFeatureConfig, ) + fun securityProvidersViewModel() = SecurityProvidersViewModel( + appPathsProvider = AppPathsProvider(context = context, currentAccount = currentAccount), + dispatcherProvider = dispatcherProvider, + ) + fun whatsNewViewModel() = WhatsNewViewModel(context = context) fun aboutThisAppViewModel() = AboutThisAppViewModel(context = context) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelGraph.kt b/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelGraph.kt index 315422bdc17..9be43bccc14 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelGraph.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/DebugInfoViewModelGraph.kt @@ -26,6 +26,7 @@ import com.wire.android.di.metro.sessionKeyedMetroViewModel import com.wire.android.ui.debug.conversation.DebugConversationViewModel import com.wire.android.ui.debug.cryptostats.ConversationCryptoStatsViewModel import com.wire.android.ui.debug.featureflags.DebugFeatureFlagsViewModel +import com.wire.android.ui.debug.securityproviders.SecurityProvidersViewModel import com.wire.android.ui.home.settings.about.dependencies.DependenciesViewModel import com.wire.android.ui.home.settings.about.licenses.LicensesViewModel import com.wire.android.ui.home.whatsnew.WhatsNewViewModel @@ -75,6 +76,10 @@ fun conversationCryptoStatsViewModel(): ConversationCryptoStatsViewModel = fun debugFeatureFlagsViewModel(): DebugFeatureFlagsViewModel = debugInfoViewModel() +@Composable +fun securityProvidersViewModel(): SecurityProvidersViewModel = + debugInfoViewModel() + @Composable fun whatsNewViewModel(): WhatsNewViewModel = debugInfoViewModel() diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/DebugScreen.kt b/app/src/main/kotlin/com/wire/android/ui/debug/DebugScreen.kt index 987027dd6e4..16d139590f3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/DebugScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/DebugScreen.kt @@ -53,6 +53,7 @@ import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar import com.ramcosta.composedestinations.generated.app.destinations.ConversationCryptoStatsScreenDestination import com.ramcosta.composedestinations.generated.app.destinations.DebugFeatureFlagsScreenDestination import com.ramcosta.composedestinations.generated.app.destinations.ImportMediaScreenDestination +import com.ramcosta.composedestinations.generated.app.destinations.SecurityProvidersScreenDestination import com.wire.android.ui.common.rowitem.SectionHeader import com.wire.android.ui.home.settings.SettingsItem import com.wire.android.ui.home.settings.backup.BackupAndRestoreDialog @@ -92,6 +93,9 @@ fun DebugScreen( onShowCryptoStats = { navigator.navigate(NavigationCommand(ConversationCryptoStatsScreenDestination)) }, + onShowSecurityProviders = { + navigator.navigate(NavigationCommand(SecurityProvidersScreenDestination)) + }, viewModel = debugDataOptionsViewModel, ) }, @@ -293,6 +297,7 @@ internal fun PreviewUserDebugContent() = WireTheme { onCopyText = it::copyToClipboard, onShowFeatureFlags = {}, onShowCryptoStats = {}, + onShowSecurityProviders = {}, viewModel = object : DebugDataOptionsViewModel {}, ) }, diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt new file mode 100644 index 00000000000..06061075724 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt @@ -0,0 +1,51 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +import android.content.Context +import androidx.annotation.StringRes +import com.wire.android.R +import com.wire.kalium.logic.data.user.UserId + +class AppPathsProvider( + private val context: Context, + private val currentAccount: UserId, +) { + operator fun invoke(): List = with(context) { + val accountSuffix = "${currentAccount.domain}/${currentAccount.value}" + listOf( + AppPathEntry(R.string.debug_settings_app_path_assets, "$filesDir/$accountSuffix"), + AppPathEntry(R.string.debug_settings_app_path_cache, "$cacheDir/$accountSuffix"), + AppPathEntry(R.string.debug_settings_app_path_files_dir, filesDir.absolutePath), + AppPathEntry(R.string.debug_settings_app_path_cache_dir, cacheDir.absolutePath), + AppPathEntry(R.string.debug_settings_app_path_databases_dir, getDatabasePath(DATABASE_NAME_PROBE).parent.orEmpty()), + AppPathEntry(R.string.debug_settings_app_path_no_backup_dir, noBackupFilesDir.absolutePath), + AppPathEntry(R.string.debug_settings_app_path_external_files_dir, getExternalFilesDir(null)?.absolutePath.orEmpty()), + ) + } + + private companion object { + /** Only used to resolve the databases directory, the file itself never has to exist. */ + const val DATABASE_NAME_PROBE = "probe" + } +} + +data class AppPathEntry( + @StringRes val labelRes: Int, + val path: String, +) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvider.kt new file mode 100644 index 00000000000..d695c13128a --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvider.kt @@ -0,0 +1,30 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +data class SecurityProvider( + val name: String, + val version: String, + val info: String, + val entries: List, +) + +data class KeyValueEntry( + val key: String, + val value: String, +) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt new file mode 100644 index 00000000000..65937f4808d --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt @@ -0,0 +1,128 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.wire.android.R +import com.wire.android.model.Clickable +import com.wire.android.ui.common.R as commonR +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.rowitem.RowItem +import com.wire.android.ui.common.typography +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.ui.PreviewMultipleThemes + +@Composable +fun SecurityProviderListItem( + provider: SecurityProvider, + modifier: Modifier = Modifier, +) { + var expanded by remember { mutableStateOf(false) } + + Column(modifier = modifier.fillMaxWidth()) { + RowItem( + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = dimensions().spacing48x) + .padding(dimensions().spacing16x), + clickable = Clickable { expanded = !expanded } + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Text( + text = listOf(provider.name, provider.version).filter(String::isNotBlank).joinToString(" "), + style = typography().body02, + color = colorsScheme().onBackground, + ) + Text( + text = provider.info, + style = typography().label01, + color = colorsScheme().secondaryText, + ) + Text( + text = stringResource(R.string.debug_settings_security_providers_entry_count, provider.entries.size), + style = typography().label01, + color = colorsScheme().secondaryText, + ) + } + + Icon( + painter = painterResource( + if (expanded) commonR.drawable.ic_collapse else commonR.drawable.ic_expand_more + ), + contentDescription = null, + tint = colorsScheme().onSurfaceVariant, + ) + } + + AnimatedVisibility(expanded) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + start = dimensions().spacing16x, + end = dimensions().spacing16x, + bottom = dimensions().spacing16x, + ) + ) { + provider.entries.forEach { entry -> + Text( + text = "${entry.key}: ${entry.value}", + modifier = Modifier.fillMaxWidth(), + style = typography().label01, + color = colorsScheme().secondaryText, + ) + } + } + } + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewSecurityProviderListItem() = WireTheme { + SecurityProviderListItem( + provider = SecurityProvider( + name = "AndroidKeyStore", + version = "1.0", + info = "Android KeyStore security provider", + entries = listOf( + KeyValueEntry("KeyStore.AndroidKeyStore", "android.security.keystore2.AndroidKeyStoreProvider"), + KeyValueEntry("Signature.SHA256withECDSA", "android.security.keystore2.AndroidKeyStoreSignatureSpi\$ECDSA"), + ) + ) + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt new file mode 100644 index 00000000000..2c561a7f68a --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt @@ -0,0 +1,92 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.wire.android.R +import com.wire.android.navigation.Navigator +import com.wire.android.navigation.annotation.app.WireRootDestination +import com.wire.android.ui.common.rememberTopBarElevationState +import com.wire.android.ui.common.rowitem.SectionHeader +import com.wire.android.ui.common.scaffold.WireScaffold +import com.wire.android.ui.common.topappbar.NavigationIconType +import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.ui.common.topappbar.WireTopAppBarTitle +import com.wire.android.ui.common.typography +import com.wire.android.ui.debug.securityProvidersViewModel +import com.wire.android.ui.home.settings.SettingsItem + +@WireRootDestination +@Composable +fun SecurityProvidersScreen( + navigator: Navigator, + modifier: Modifier = Modifier, + viewModel: SecurityProvidersViewModel = securityProvidersViewModel(), +) { + val scrollState = rememberScrollState() + + WireScaffold( + modifier = modifier, + topBar = { + WireCenterAlignedTopAppBar( + elevation = scrollState.rememberTopBarElevationState().value, + titleContent = { + WireTopAppBarTitle( + title = stringResource(R.string.debug_settings_security_providers), + style = typography().title01, + maxLines = 2 + ) + }, + navigationIconType = NavigationIconType.Close(R.string.content_description_conversation_details_close_btn), + onNavigationPressed = { + navigator.navigateBack() + } + ) + }, + content = { paddingValues -> + + val state by viewModel.state.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(scrollState) + ) { + SectionHeader(stringResource(R.string.debug_settings_app_paths)) + state.appPaths.forEach { entry -> + SettingsItem(title = stringResource(entry.labelRes), text = entry.path) + } + + SectionHeader(stringResource(R.string.debug_settings_security_providers)) + state.providers?.forEach { provider -> + SecurityProviderListItem(provider) + } + } + } + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt new file mode 100644 index 00000000000..7534cd35a18 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -0,0 +1,69 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.wire.android.util.dispatchers.DispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.security.Provider +import java.security.Security + +class SecurityProvidersViewModel( + private val appPathsProvider: AppPathsProvider, + private val dispatcherProvider: DispatcherProvider, +) : ViewModel() { + + private val _state = MutableStateFlow(SecurityProvidersViewState()) + val state = _state.asStateFlow() + + init { + viewModelScope.launch { + val providers = withContext(dispatcherProvider.default()) { + Security.getProviders().map { provider -> + SecurityProvider( + name = provider.name, + version = provider.versionString(), + info = provider.info, + entries = provider.entries + .map { (key, value) -> KeyValueEntry(key.toString(), value.toString()) } + .sortedBy(KeyValueEntry::key) + ) + } + } + _state.update { current -> current.copy(appPaths = appPathsProvider(), providers = providers) } + } + } +} + +/** + * `Provider.getVersionStr()` needs API 28 and `Provider.getVersion()` is deprecated, so read the version + * straight out of the provider's own property map, where it is registered under this key. + */ +private const val PROVIDER_VERSION_PROPERTY = "Provider.id version" + +private fun Provider.versionString(): String = getProperty(PROVIDER_VERSION_PROPERTY).orEmpty() + +data class SecurityProvidersViewState( + val appPaths: List = emptyList(), + val providers: List? = null, +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dd67d3aeae8..b476f936541 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1785,6 +1785,16 @@ In group conversations, the group admin can overwrite this setting. Reset Feature Flags Conversation Crypto Stats + Security Providers + App Paths + Assets path + Cache path + Files dir + Cache dir + Databases dir + No backup dir + External files dir + %1$d entries Step %1$d of 4 From ab16e8fbc3b58769e159721672d240322aaa8012 Mon Sep 17 00:00:00 2001 From: ohassine Date: Tue, 11 Aug 2026 15:35:23 +0200 Subject: [PATCH 02/14] chore: conflicts --- app/src/main/kotlin/com/wire/android/di/AppModule.kt | 9 +++++++++ .../wire/android/di/metro/WireMetroViewModelBindings.kt | 3 +-- .../securityproviders/SecurityProvidersViewModel.kt | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/di/AppModule.kt b/app/src/main/kotlin/com/wire/android/di/AppModule.kt index 4e0d583b11e..35515e12218 100644 --- a/app/src/main/kotlin/com/wire/android/di/AppModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/AppModule.kt @@ -30,6 +30,7 @@ import com.wire.android.feature.analytics.AnonymousAnalyticsManager import com.wire.android.feature.analytics.AnonymousAnalyticsManagerImpl import com.wire.android.mapper.MessageResourceProvider import com.wire.android.ui.analytics.AnalyticsConfiguration +import com.wire.android.ui.debug.securityproviders.AppPathsProvider import com.wire.android.ui.home.conversations.MessageSharedState import com.wire.android.ui.home.messagecomposer.location.LocationPickerParameters import com.wire.android.util.CurrentTimeProvider @@ -39,6 +40,7 @@ import com.wire.android.util.dispatchers.DefaultDispatcherProvider import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.AndroidUiTextResolver import com.wire.android.util.ui.UiTextResolver +import com.wire.kalium.logic.data.user.UserId import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.Provides import dev.zacsweers.metro.Named @@ -133,4 +135,11 @@ object AppModule { @Provides fun provideGetMediaMetadataUseCase(): GetMediaMetadataUseCase = GetMediaMetadataUseCaseImpl() + + @Provides + fun provideAppPathsProvider(@ApplicationContext context: Context, @CurrentAccount currentAccount: UserId): AppPathsProvider = + AppPathsProvider( + context = context, + currentAccount = currentAccount + ) } diff --git a/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt b/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt index 1e0b4dd63a0..3496c60be76 100644 --- a/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt +++ b/app/src/main/kotlin/com/wire/android/di/metro/WireMetroViewModelBindings.kt @@ -364,8 +364,7 @@ object WireMetroViewModelBindings { @Provides @IntoMap @ViewModelKey(SecurityProvidersViewModel::class) - fun securityProvidersViewModel(viewModel: DebugFeatureFlagsViewModel): ViewModel = - factory.securityProvidersViewModel() + fun securityProvidersViewModel(viewModel: SecurityProvidersViewModel): ViewModel = viewModel @Provides @IntoMap diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index 7534cd35a18..b8bd59b0d39 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -20,6 +20,7 @@ package com.wire.android.ui.debug.securityproviders import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wire.android.util.dispatchers.DispatcherProvider +import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -28,7 +29,7 @@ import kotlinx.coroutines.withContext import java.security.Provider import java.security.Security -class SecurityProvidersViewModel( +class SecurityProvidersViewModel @Inject constructor( private val appPathsProvider: AppPathsProvider, private val dispatcherProvider: DispatcherProvider, ) : ViewModel() { From d80b7c54e64a579d6dec32a50c9ebe1ea2606cb3 Mon Sep 17 00:00:00 2001 From: ohassine Date: Thu, 13 Aug 2026 11:37:23 +0200 Subject: [PATCH 03/14] feat: show show crypto services --- .../android/datastore/EncryptionManager.kt | 48 ++++++++++- .../android/di/accountScoped/DebugModule.kt | 5 ++ .../wire/android/feature/e2ei/OAuthUseCase.kt | 29 ++++++- .../AppCryptoServicesProvider.kt | 60 +++++++++++++ .../securityproviders/AppPathsProvider.kt | 31 ++++--- .../CryptoServiceListItem.kt | 84 +++++++++++++++++++ .../debug/securityproviders/LabelledValue.kt | 26 ++++++ .../SecurityProvidersScreen.kt | 10 ++- .../SecurityProvidersViewModel.kt | 49 ++++++++++- .../util/crypto/AppCryptoServiceRegistry.kt | 67 +++++++++++++++ .../android/util/crypto/AppCryptoUsage.kt | 36 ++++++++ app/src/main/res/values/strings.xml | 12 +++ kalium | 2 +- 13 files changed, 434 insertions(+), 25 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/LabelledValue.kt create mode 100644 app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt create mode 100644 app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt diff --git a/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt b/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt index b317d98cee8..07956e683b1 100644 --- a/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt +++ b/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt @@ -20,6 +20,8 @@ package com.wire.android.datastore import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 +import com.wire.android.util.crypto.AppCryptoServiceRegistry +import com.wire.android.util.crypto.AppCryptoUsage import java.io.UnsupportedEncodingException import java.nio.charset.Charset import java.security.InvalidKeyException @@ -38,11 +40,47 @@ object EncryptionManager { private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM private const val PADDING = KeyProperties.ENCRYPTION_PADDING_NONE private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING" + private const val ANDROID_KEY_STORE = "AndroidKeyStore" - private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } - private val cipher = Cipher.getInstance(TRANSFORMATION) + private val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply { + load(null) + AppCryptoServiceRegistry.record( + usage = AppCryptoUsage.DATASTORE_KEYSTORE, + lookup = "KeyStore.getInstance(\"$ANDROID_KEY_STORE\")", + algorithm = type, + provider = provider + ) + } + private val cipher = Cipher.getInstance(TRANSFORMATION).also { + AppCryptoServiceRegistry.record( + usage = AppCryptoUsage.DATASTORE_CIPHER, + lookup = "Cipher.getInstance(\"$TRANSFORMATION\")", + algorithm = it.algorithm, + provider = it.provider + ) + } private val charset = Charset.defaultCharset() + /** + * Resolves the providers backing this manager, for the security providers debug screen. + * + * Touching [keyStore] and [cipher] initialises them, which records what served them. The key + * generator is only resolved, never initialised with a [KeyGenParameterSpec] or asked for a key, so + * nothing is written to the Android keystore. + */ + fun probeCryptoServices() { + keyStore + cipher + KeyGenerator.getInstance(ALGORITHM).also { + AppCryptoServiceRegistry.record( + usage = AppCryptoUsage.DATASTORE_KEY_GENERATION, + lookup = "KeyGenerator.getInstance(\"$ALGORITHM\")", + algorithm = it.algorithm, + provider = it.provider + ) + } + } + private fun getKey(keyAlias: String): SecretKey { val existingKey = keyStore.getEntry(keyAlias, null) as? KeyStore.SecretKeyEntry return existingKey?.secretKey ?: createKey(keyAlias) @@ -50,6 +88,12 @@ object EncryptionManager { private fun createKey(keyAlias: String): SecretKey { return KeyGenerator.getInstance(ALGORITHM).apply { + AppCryptoServiceRegistry.record( + usage = AppCryptoUsage.DATASTORE_KEY_GENERATION, + lookup = "KeyGenerator.getInstance(\"$ALGORITHM\")", + algorithm = algorithm, + provider = provider + ) init( KeyGenParameterSpec.Builder( keyAlias, diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt index 0c99f2cc675..358946220e2 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt @@ -27,6 +27,7 @@ import com.wire.kalium.logic.feature.debug.DebugFeedConversationUseCase import com.wire.kalium.logic.feature.debug.DebugScope import com.wire.kalium.logic.feature.debug.DisableEventProcessingUseCase import com.wire.kalium.logic.feature.debug.GetConversationCryptoStatsUseCase +import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase import com.wire.kalium.logic.feature.debug.GetConversationEpochFromCCUseCase import com.wire.kalium.logic.feature.debug.GetDebugE2EICertificateExpirationUseCase import com.wire.kalium.logic.feature.debug.GetFeatureConfigUseCase @@ -72,6 +73,10 @@ class DebugModule { @Provides fun provideFeatureConfigUseCase(debugScope: DebugScope): GetFeatureConfigUseCase = debugScope.getFeatureConfig + @Provides + fun provideGetCryptoServiceReportUseCase(debugScope: DebugScope): GetCryptoServiceReportUseCase = + debugScope.getCryptoServiceReport + @Provides fun provideGetDebugE2EICertificateExpirationUseCase(debugScope: DebugScope): GetDebugE2EICertificateExpirationUseCase = debugScope.getDebugE2EICertificateExpiration diff --git a/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt b/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt index bceacb747f5..e2c174aa13b 100644 --- a/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt @@ -42,6 +42,8 @@ import net.openid.appauth.ResponseTypeValues import org.json.JSONObject import java.net.URI import java.security.MessageDigest +import com.wire.android.util.crypto.AppCryptoServiceRegistry +import com.wire.android.util.crypto.AppCryptoUsage import java.security.SecureRandom class OAuthUseCase( @@ -184,7 +186,7 @@ class OAuthUseCase( @Suppress("MagicNumber") private fun getCodeVerifier(): String { - val secureRandom = SecureRandom() + val secureRandom = pkceRandom() val bytes = ByteArray(64) secureRandom.nextBytes(bytes) return Base64.encodeToString(bytes, ENCODING) @@ -209,7 +211,30 @@ class OAuthUseCase( const val CLIENT_ID_QUERY_PARAM = "client_id" const val CODE_VERIFIER_CHALLENGE_METHOD = "S256" const val MESSAGE_DIGEST_ALGORITHM = "SHA-256" - val MESSAGE_DIGEST = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM) + val MESSAGE_DIGEST = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM).also { + AppCryptoServiceRegistry.record( + AppCryptoUsage.OAUTH_PKCE_CHALLENGE, + "MessageDigest.getInstance(\"$MESSAGE_DIGEST_ALGORITHM\")", + it.algorithm, + it.provider, + ) + } + + /** The randomness behind the PKCE code verifier, noted for the security providers debug screen. */ + fun pkceRandom(): SecureRandom = SecureRandom().also { + AppCryptoServiceRegistry.record( + usage = AppCryptoUsage.OAUTH_PKCE_VERIFIER, + lookup = "SecureRandom()", + algorithm = it.algorithm, + provider = it.provider + ) + } + + /** Resolves the providers backing PKCE, for the security providers debug screen. */ + fun probeCryptoServices() { + pkceRandom() + MESSAGE_DIGEST + } const val ENCODING = Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP val URL_AUTH_REDIRECT: Uri = Uri.Builder().scheme(DeepLinkProcessor.DEEP_LINK_SCHEME) .authority(DeepLinkProcessor.E2EI_DEEPLINK_HOST) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt new file mode 100644 index 00000000000..409a3498f13 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt @@ -0,0 +1,60 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +import androidx.annotation.StringRes +import com.wire.android.R +import com.wire.android.datastore.EncryptionManager +import com.wire.android.feature.e2ei.OAuthUseCase +import com.wire.android.util.crypto.AppCryptoServiceRegistry +import com.wire.android.util.crypto.AppCryptoUsage +import dev.zacsweers.metro.Inject + +/** + * Reports which security provider served each cryptographic call site in the app module. + * + * Kalium's own call sites come from `GetCryptoServiceReportUseCase`; this covers the ones the app makes + * directly, which kalium cannot see. Call sites that have not run yet are probed by performing the very + * same lookup they perform, so every row is a provider the platform actually handed back. + */ +class AppCryptoServicesProvider @Inject constructor() { + + operator fun invoke(): List { + // Neither path runs on app start, so resolve them the same way the call sites do. + EncryptionManager.probeCryptoServices() + OAuthUseCase.probeCryptoServices() + return AppCryptoServiceRegistry.recorded().map { (usage, record) -> + CryptoServiceRow( + labelRes = usage.labelRes(), + lookup = record.lookup, + algorithm = record.algorithm, + providerName = record.providerName, + providerVersion = record.providerVersion, + ) + } + } + + @StringRes + private fun AppCryptoUsage.labelRes(): Int = when (this) { + AppCryptoUsage.DATASTORE_KEYSTORE -> R.string.debug_settings_crypto_datastore_keystore + AppCryptoUsage.DATASTORE_KEY_GENERATION -> R.string.debug_settings_crypto_datastore_key_generator + AppCryptoUsage.DATASTORE_CIPHER -> R.string.debug_settings_crypto_datastore_cipher + AppCryptoUsage.OAUTH_PKCE_VERIFIER -> R.string.debug_settings_crypto_oauth_verifier + AppCryptoUsage.OAUTH_PKCE_CHALLENGE -> R.string.debug_settings_crypto_oauth_challenge + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt index 06061075724..be64cb098a9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt @@ -18,24 +18,26 @@ package com.wire.android.ui.debug.securityproviders import android.content.Context -import androidx.annotation.StringRes import com.wire.android.R +import com.wire.android.di.ApplicationContext +import com.wire.android.di.CurrentAccount import com.wire.kalium.logic.data.user.UserId +import dev.zacsweers.metro.Inject -class AppPathsProvider( - private val context: Context, - private val currentAccount: UserId, +class AppPathsProvider @Inject constructor( + @ApplicationContext private val context: Context, + @CurrentAccount private val currentAccount: UserId, ) { - operator fun invoke(): List = with(context) { + operator fun invoke(): List = with(context) { val accountSuffix = "${currentAccount.domain}/${currentAccount.value}" listOf( - AppPathEntry(R.string.debug_settings_app_path_assets, "$filesDir/$accountSuffix"), - AppPathEntry(R.string.debug_settings_app_path_cache, "$cacheDir/$accountSuffix"), - AppPathEntry(R.string.debug_settings_app_path_files_dir, filesDir.absolutePath), - AppPathEntry(R.string.debug_settings_app_path_cache_dir, cacheDir.absolutePath), - AppPathEntry(R.string.debug_settings_app_path_databases_dir, getDatabasePath(DATABASE_NAME_PROBE).parent.orEmpty()), - AppPathEntry(R.string.debug_settings_app_path_no_backup_dir, noBackupFilesDir.absolutePath), - AppPathEntry(R.string.debug_settings_app_path_external_files_dir, getExternalFilesDir(null)?.absolutePath.orEmpty()), + LabelledValue(R.string.debug_settings_app_path_assets, "$filesDir/$accountSuffix"), + LabelledValue(R.string.debug_settings_app_path_cache, "$cacheDir/$accountSuffix"), + LabelledValue(R.string.debug_settings_app_path_files_dir, filesDir.absolutePath), + LabelledValue(R.string.debug_settings_app_path_cache_dir, cacheDir.absolutePath), + LabelledValue(R.string.debug_settings_app_path_databases_dir, getDatabasePath(DATABASE_NAME_PROBE).parent.orEmpty()), + LabelledValue(R.string.debug_settings_app_path_no_backup_dir, noBackupFilesDir.absolutePath), + LabelledValue(R.string.debug_settings_app_path_external_files_dir, getExternalFilesDir(null)?.absolutePath.orEmpty()), ) } @@ -44,8 +46,3 @@ class AppPathsProvider( const val DATABASE_NAME_PROBE = "probe" } } - -data class AppPathEntry( - @StringRes val labelRes: Int, - val path: String, -) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt new file mode 100644 index 00000000000..654d9a3aae3 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt @@ -0,0 +1,84 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.wire.android.R +import com.wire.android.model.Clickable +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.rowitem.RowItem +import com.wire.android.ui.common.typography +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.ui.PreviewMultipleThemes + +@Composable +fun CryptoServiceListItem( + row: CryptoServiceRow, + modifier: Modifier = Modifier, +) { + RowItem( + modifier = modifier + .fillMaxWidth() + .padding(dimensions().spacing16x), + clickable = Clickable(enabled = false), + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = stringResource(row.labelRes), + style = typography().body02, + color = colorsScheme().onBackground, + ) + Text( + text = row.lookup, + style = typography().label01, + color = colorsScheme().secondaryText, + ) + Text( + text = stringResource( + R.string.debug_settings_crypto_service_resolved, + row.algorithm, + row.providerName, + row.providerVersion, + ), + style = typography().body02, + color = colorsScheme().onBackground, + ) + } + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewCryptoServiceListItem() = WireTheme { + CryptoServiceListItem( + row = CryptoServiceRow( + labelRes = R.string.debug_settings_crypto_asset_key, + lookup = "KeyGenerator.getInstance(\"AES\")", + algorithm = "AES", + providerName = "AndroidOpenSSL", + providerVersion = "1.0", + ) + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/LabelledValue.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/LabelledValue.kt new file mode 100644 index 00000000000..630e633080d --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/LabelledValue.kt @@ -0,0 +1,26 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.debug.securityproviders + +import androidx.annotation.StringRes + +/** A debug row whose label comes from resources and whose value is resolved at runtime. */ +data class LabelledValue( + @StringRes val labelRes: Int, + val value: String, +) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt index 2c561a7f68a..8569621f587 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt @@ -79,7 +79,15 @@ fun SecurityProvidersScreen( ) { SectionHeader(stringResource(R.string.debug_settings_app_paths)) state.appPaths.forEach { entry -> - SettingsItem(title = stringResource(entry.labelRes), text = entry.path) + SettingsItem(title = stringResource(entry.labelRes), text = entry.value) + } + + SectionHeader(stringResource(R.string.debug_settings_crypto_services)) + if (state.cryptoServices.isEmpty()) { + SettingsItem(text = stringResource(R.string.debug_settings_crypto_services_empty)) + } + state.cryptoServices.forEach { row -> + CryptoServiceListItem(row) } SectionHeader(stringResource(R.string.debug_settings_security_providers)) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index b8bd59b0d39..09b14f34afa 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -17,9 +17,15 @@ */ package com.wire.android.ui.debug.securityproviders +import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.wire.android.R import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.kalium.logic.feature.debug.CryptoUsage +import com.wire.kalium.logic.feature.debug.CryptoServiceUsage +import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase +import com.wire.kalium.util.DebugKaliumApi import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -29,8 +35,11 @@ import kotlinx.coroutines.withContext import java.security.Provider import java.security.Security +@OptIn(DebugKaliumApi::class) class SecurityProvidersViewModel @Inject constructor( private val appPathsProvider: AppPathsProvider, + private val appCryptoServicesProvider: AppCryptoServicesProvider, + private val getCryptoServiceReport: GetCryptoServiceReportUseCase, private val dispatcherProvider: DispatcherProvider, ) : ViewModel() { @@ -51,11 +60,46 @@ class SecurityProvidersViewModel @Inject constructor( ) } } - _state.update { current -> current.copy(appPaths = appPathsProvider(), providers = providers) } + val appCryptoServices = withContext(dispatcherProvider.io()) { appCryptoServicesProvider() } + val cryptoServices = getCryptoServiceReport().map(CryptoServiceUsage::toRow) + appCryptoServices + _state.update { current -> + current.copy( + appPaths = appPathsProvider(), + cryptoServices = cryptoServices, + providers = providers, + ) + } } } } +@OptIn(DebugKaliumApi::class) +private fun CryptoServiceUsage.toRow() = CryptoServiceRow( + labelRes = usage.labelRes(), + lookup = lookup, + algorithm = algorithm, + providerName = providerName, + providerVersion = providerVersion, +) + +@OptIn(DebugKaliumApi::class) +@StringRes +private fun CryptoUsage.labelRes(): Int = when (this) { + CryptoUsage.ASSET_ENCRYPTION_IV -> R.string.debug_settings_crypto_asset_iv + CryptoUsage.ASSET_KEY -> R.string.debug_settings_crypto_asset_key + CryptoUsage.ASSET_CIPHER -> R.string.debug_settings_crypto_asset_cipher + CryptoUsage.DATABASE_SECRET -> R.string.debug_settings_crypto_database_secret +} + +/** One cryptographic call site, and the provider that served it on this device. */ +data class CryptoServiceRow( + @StringRes val labelRes: Int, + val lookup: String, + val algorithm: String, + val providerName: String, + val providerVersion: String, +) + /** * `Provider.getVersionStr()` needs API 28 and `Provider.getVersion()` is deprecated, so read the version * straight out of the provider's own property map, where it is registered under this key. @@ -65,6 +109,7 @@ private const val PROVIDER_VERSION_PROPERTY = "Provider.id version" private fun Provider.versionString(): String = getProperty(PROVIDER_VERSION_PROPERTY).orEmpty() data class SecurityProvidersViewState( - val appPaths: List = emptyList(), + val appPaths: List = emptyList(), + val cryptoServices: List = emptyList(), val providers: List? = null, ) diff --git a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt new file mode 100644 index 00000000000..a5f6fcbe961 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt @@ -0,0 +1,67 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.util.crypto + +import java.security.Provider +import java.util.concurrent.ConcurrentHashMap + +/** + * Records which security provider actually served each cryptographic call site in the app module. + * + * Mirrors kalium's `CryptoServiceRegistry` for the lookups the app makes directly, which kalium cannot + * see. A call site that has not run yet is absent rather than guessed at, so every entry the debug screen + * shows is something that actually happened. + */ +object AppCryptoServiceRegistry { + + private val records = ConcurrentHashMap() + + /** + * Notes that [usage] was served by [provider]. + * + * @param lookup the lookup performed, as written in the source. + */ + fun record(usage: AppCryptoUsage, lookup: String, algorithm: String, provider: Provider) { + records[usage] = AppCryptoServiceRecord( + lookup = lookup, + algorithm = algorithm, + providerName = provider.name, + providerVersion = provider.versionString(), + ) + } + + /** Every app call site observed so far, in [AppCryptoUsage] declaration order. */ + fun recorded(): List> = + AppCryptoUsage.entries.mapNotNull { usage -> records[usage]?.let { usage to it } } + + /** + * `Provider.getVersionStr()` needs API 28 and `Provider.getVersion()` is deprecated, so read the + * version out of the provider's own property map, where it is registered under this key. + */ + private fun Provider.versionString(): String = getProperty(PROVIDER_VERSION_PROPERTY).orEmpty() + + private const val PROVIDER_VERSION_PROPERTY = "Provider.id version" +} + +/** Which security provider served an app call site, as observed when it ran. */ +data class AppCryptoServiceRecord( + val lookup: String, + val algorithm: String, + val providerName: String, + val providerVersion: String, +) diff --git a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt new file mode 100644 index 00000000000..eafc4c52a7d --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt @@ -0,0 +1,36 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.util.crypto + +/** A cryptographic call site in the app module. Kalium's own are tracked by `CryptoServiceRegistry`. */ +enum class AppCryptoUsage { + /** `EncryptionManager.keyStore`. */ + DATASTORE_KEYSTORE, + + /** `EncryptionManager.createKey`. */ + DATASTORE_KEY_GENERATION, + + /** `EncryptionManager.cipher`. */ + DATASTORE_CIPHER, + + /** `OAuthUseCase`, PKCE code verifier. */ + OAUTH_PKCE_VERIFIER, + + /** `OAuthUseCase`, PKCE code challenge. */ + OAUTH_PKCE_CHALLENGE, +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b476f936541..bec4c15fb8e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1794,6 +1794,18 @@ In group conversations, the group admin can overwrite this setting. Databases dir No backup dir External files dir + Crypto Services + Asset encryption IV + Asset AES-256 key + Asset cipher + Database secret / random password + DataStore keystore + DataStore key generation + DataStore cipher + OAuth PKCE verifier + OAuth PKCE challenge + %1$s \u2014 %2$s %3$s + No crypto call site has run yet in this session %1$d entries diff --git a/kalium b/kalium index 65657c61a5a..4d2fb212794 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 65657c61a5a574a4f1c1e8746ee6f6ec3a501550 +Subproject commit 4d2fb212794525534ff18f273711665d6126a8d7 From 98f2255812dad8aa6850eee5afe2836b2c038890 Mon Sep 17 00:00:00 2001 From: ohassine Date: Thu, 13 Aug 2026 12:06:53 +0200 Subject: [PATCH 04/14] feat: remove security providers section --- .../SecurityProviderListItem.kt | 128 ------------------ .../SecurityProvidersScreen.kt | 5 - .../SecurityProvidersViewModel.kt | 18 +-- 3 files changed, 1 insertion(+), 150 deletions(-) delete mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt deleted file mode 100644 index 65937f4808d..00000000000 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProviderListItem.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Wire - * Copyright (C) 2026 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.android.ui.debug.securityproviders - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import com.wire.android.R -import com.wire.android.model.Clickable -import com.wire.android.ui.common.R as commonR -import com.wire.android.ui.common.colorsScheme -import com.wire.android.ui.common.dimensions -import com.wire.android.ui.common.rowitem.RowItem -import com.wire.android.ui.common.typography -import com.wire.android.ui.theme.WireTheme -import com.wire.android.util.ui.PreviewMultipleThemes - -@Composable -fun SecurityProviderListItem( - provider: SecurityProvider, - modifier: Modifier = Modifier, -) { - var expanded by remember { mutableStateOf(false) } - - Column(modifier = modifier.fillMaxWidth()) { - RowItem( - modifier = Modifier - .fillMaxWidth() - .defaultMinSize(minHeight = dimensions().spacing48x) - .padding(dimensions().spacing16x), - clickable = Clickable { expanded = !expanded } - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Text( - text = listOf(provider.name, provider.version).filter(String::isNotBlank).joinToString(" "), - style = typography().body02, - color = colorsScheme().onBackground, - ) - Text( - text = provider.info, - style = typography().label01, - color = colorsScheme().secondaryText, - ) - Text( - text = stringResource(R.string.debug_settings_security_providers_entry_count, provider.entries.size), - style = typography().label01, - color = colorsScheme().secondaryText, - ) - } - - Icon( - painter = painterResource( - if (expanded) commonR.drawable.ic_collapse else commonR.drawable.ic_expand_more - ), - contentDescription = null, - tint = colorsScheme().onSurfaceVariant, - ) - } - - AnimatedVisibility(expanded) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding( - start = dimensions().spacing16x, - end = dimensions().spacing16x, - bottom = dimensions().spacing16x, - ) - ) { - provider.entries.forEach { entry -> - Text( - text = "${entry.key}: ${entry.value}", - modifier = Modifier.fillMaxWidth(), - style = typography().label01, - color = colorsScheme().secondaryText, - ) - } - } - } - } -} - -@PreviewMultipleThemes -@Composable -fun PreviewSecurityProviderListItem() = WireTheme { - SecurityProviderListItem( - provider = SecurityProvider( - name = "AndroidKeyStore", - version = "1.0", - info = "Android KeyStore security provider", - entries = listOf( - KeyValueEntry("KeyStore.AndroidKeyStore", "android.security.keystore2.AndroidKeyStoreProvider"), - KeyValueEntry("Signature.SHA256withECDSA", "android.security.keystore2.AndroidKeyStoreSignatureSpi\$ECDSA"), - ) - ) - ) -} diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt index 2c561a7f68a..672f3a89aa8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt @@ -81,11 +81,6 @@ fun SecurityProvidersScreen( state.appPaths.forEach { entry -> SettingsItem(title = stringResource(entry.labelRes), text = entry.path) } - - SectionHeader(stringResource(R.string.debug_settings_security_providers)) - state.providers?.forEach { provider -> - SecurityProviderListItem(provider) - } } } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index b8bd59b0d39..7a3d5011ac3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -19,19 +19,15 @@ package com.wire.android.ui.debug.securityproviders import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.wire.android.util.dispatchers.DispatcherProvider import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.security.Provider -import java.security.Security class SecurityProvidersViewModel @Inject constructor( private val appPathsProvider: AppPathsProvider, - private val dispatcherProvider: DispatcherProvider, ) : ViewModel() { private val _state = MutableStateFlow(SecurityProvidersViewState()) @@ -39,19 +35,7 @@ class SecurityProvidersViewModel @Inject constructor( init { viewModelScope.launch { - val providers = withContext(dispatcherProvider.default()) { - Security.getProviders().map { provider -> - SecurityProvider( - name = provider.name, - version = provider.versionString(), - info = provider.info, - entries = provider.entries - .map { (key, value) -> KeyValueEntry(key.toString(), value.toString()) } - .sortedBy(KeyValueEntry::key) - ) - } - } - _state.update { current -> current.copy(appPaths = appPathsProvider(), providers = providers) } + _state.update { current -> current.copy(appPaths = appPathsProvider()) } } } } From 44cfbdb2af87601410a88f8a7f5c1c474343f533 Mon Sep 17 00:00:00 2001 From: ohassine Date: Thu, 13 Aug 2026 14:54:00 +0200 Subject: [PATCH 05/14] feat: update strings.xml --- .../main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt | 2 +- .../ui/debug/securityproviders/SecurityProvidersScreen.kt | 2 +- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt b/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt index 9346d3d7520..39478c47a2e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/DebugDataOptions.kt @@ -214,7 +214,7 @@ fun DebugDataOptionsContent( ) SettingsItem( - text = stringResource(R.string.debug_settings_security_providers), + text = stringResource(R.string.debug_settings_security_diagnostics), onRowPressed = Clickable( enabled = true, onClick = onShowSecurityProviders diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt index 672f3a89aa8..8a490c2520f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt @@ -56,7 +56,7 @@ fun SecurityProvidersScreen( elevation = scrollState.rememberTopBarElevationState().value, titleContent = { WireTopAppBarTitle( - title = stringResource(R.string.debug_settings_security_providers), + title = stringResource(R.string.debug_settings_security_diagnostics), style = typography().title01, maxLines = 2 ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b476f936541..4b97530408d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1785,7 +1785,7 @@ In group conversations, the group admin can overwrite this setting. Reset Feature Flags Conversation Crypto Stats - Security Providers + Security Diagnostics App Paths Assets path Cache path From c9618e404eea8a6f8b7835354e62fb7559c8488e Mon Sep 17 00:00:00 2001 From: ohassine Date: Fri, 14 Aug 2026 16:28:25 +0200 Subject: [PATCH 06/14] chore: adapt crypto services screen to kalium report API --- .../AppCryptoServicesProvider.kt | 8 ++++++-- .../securityproviders/CryptoServiceListItem.kt | 4 ++-- .../SecurityProvidersViewModel.kt | 18 +++--------------- app/src/main/res/values/strings.xml | 4 ---- kalium | 2 +- 5 files changed, 12 insertions(+), 24 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt index 409a3498f13..dd31a92de85 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt @@ -17,9 +17,11 @@ */ package com.wire.android.ui.debug.securityproviders +import android.content.Context import androidx.annotation.StringRes import com.wire.android.R import com.wire.android.datastore.EncryptionManager +import com.wire.android.di.ApplicationContext import com.wire.android.feature.e2ei.OAuthUseCase import com.wire.android.util.crypto.AppCryptoServiceRegistry import com.wire.android.util.crypto.AppCryptoUsage @@ -32,7 +34,9 @@ import dev.zacsweers.metro.Inject * directly, which kalium cannot see. Call sites that have not run yet are probed by performing the very * same lookup they perform, so every row is a provider the platform actually handed back. */ -class AppCryptoServicesProvider @Inject constructor() { +class AppCryptoServicesProvider @Inject constructor( + @ApplicationContext private val context: Context, +) { operator fun invoke(): List { // Neither path runs on app start, so resolve them the same way the call sites do. @@ -40,7 +44,7 @@ class AppCryptoServicesProvider @Inject constructor() { OAuthUseCase.probeCryptoServices() return AppCryptoServiceRegistry.recorded().map { (usage, record) -> CryptoServiceRow( - labelRes = usage.labelRes(), + label = context.getString(usage.labelRes()), lookup = record.lookup, algorithm = record.algorithm, providerName = record.providerName, diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt index 654d9a3aae3..275af716981 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/CryptoServiceListItem.kt @@ -46,7 +46,7 @@ fun CryptoServiceListItem( ) { Column(modifier = Modifier.fillMaxWidth()) { Text( - text = stringResource(row.labelRes), + text = row.label, style = typography().body02, color = colorsScheme().onBackground, ) @@ -74,7 +74,7 @@ fun CryptoServiceListItem( fun PreviewCryptoServiceListItem() = WireTheme { CryptoServiceListItem( row = CryptoServiceRow( - labelRes = R.string.debug_settings_crypto_asset_key, + label = "Asset AES-256 key", lookup = "KeyGenerator.getInstance(\"AES\")", algorithm = "AES", providerName = "AndroidOpenSSL", diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index 09b14f34afa..ede70dd0351 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -17,12 +17,9 @@ */ package com.wire.android.ui.debug.securityproviders -import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.wire.android.R import com.wire.android.util.dispatchers.DispatcherProvider -import com.wire.kalium.logic.feature.debug.CryptoUsage import com.wire.kalium.logic.feature.debug.CryptoServiceUsage import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase import com.wire.kalium.util.DebugKaliumApi @@ -75,25 +72,16 @@ class SecurityProvidersViewModel @Inject constructor( @OptIn(DebugKaliumApi::class) private fun CryptoServiceUsage.toRow() = CryptoServiceRow( - labelRes = usage.labelRes(), + label = name, lookup = lookup, algorithm = algorithm, providerName = providerName, providerVersion = providerVersion, ) -@OptIn(DebugKaliumApi::class) -@StringRes -private fun CryptoUsage.labelRes(): Int = when (this) { - CryptoUsage.ASSET_ENCRYPTION_IV -> R.string.debug_settings_crypto_asset_iv - CryptoUsage.ASSET_KEY -> R.string.debug_settings_crypto_asset_key - CryptoUsage.ASSET_CIPHER -> R.string.debug_settings_crypto_asset_cipher - CryptoUsage.DATABASE_SECRET -> R.string.debug_settings_crypto_database_secret -} - -/** One cryptographic call site, and the provider that served it on this device. */ +/** One cryptographic lookup, and the provider that serves it on this device. */ data class CryptoServiceRow( - @StringRes val labelRes: Int, + val label: String, val lookup: String, val algorithm: String, val providerName: String, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bec4c15fb8e..601327d3722 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1795,10 +1795,6 @@ In group conversations, the group admin can overwrite this setting. No backup dir External files dir Crypto Services - Asset encryption IV - Asset AES-256 key - Asset cipher - Database secret / random password DataStore keystore DataStore key generation DataStore cipher diff --git a/kalium b/kalium index 4d2fb212794..ef3a387da67 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 4d2fb212794525534ff18f273711665d6126a8d7 +Subproject commit ef3a387da67901cb2db517caba7b7973ed2f7fd7 From f1c344eada7648cbe19d25366ea4bb06818211a3 Mon Sep 17 00:00:00 2001 From: ohassine Date: Fri, 14 Aug 2026 18:16:41 +0200 Subject: [PATCH 07/14] chore: compose stablity --- app/stability/app-devDebug.stability | 27 +++++++++++++++++-- .../stability/meetings-debug.stability | 10 ++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/stability/app-devDebug.stability b/app/stability/app-devDebug.stability index 0b3c0eae82b..de3c93e4740 100644 --- a/app/stability/app-devDebug.stability +++ b/app/stability/app-devDebug.stability @@ -463,6 +463,12 @@ public fun com.ramcosta.composedestinations.generated.app.destinations.SearchCon restartable: true params: +@Composable +public fun com.ramcosta.composedestinations.generated.app.destinations.SecurityProvidersScreenDestination.Content(): kotlin.Unit + skippable: true + restartable: true + params: + @Composable public fun com.ramcosta.composedestinations.generated.app.destinations.SelfDevicesScreenDestination.Content(): kotlin.Unit skippable: true @@ -3548,7 +3554,7 @@ public fun com.wire.android.ui.debug.DangerOptions(exportObfuscatedCopyViewModel - modifier: STABLE (marked @Stable or @Immutable) @Composable -public fun com.wire.android.ui.debug.DebugDataOptions(appVersion: kotlin.String, buildVariant: kotlin.String, onCopyText: kotlin.Function1, onShowFeatureFlags: kotlin.Function0, onShowCryptoStats: kotlin.Function0, viewModel: com.wire.android.ui.debug.DebugDataOptionsViewModel): kotlin.Unit +public fun com.wire.android.ui.debug.DebugDataOptions(appVersion: kotlin.String, buildVariant: kotlin.String, onCopyText: kotlin.Function1, onShowFeatureFlags: kotlin.Function0, onShowCryptoStats: kotlin.Function0, onShowSecurityProviders: kotlin.Function0, viewModel: com.wire.android.ui.debug.DebugDataOptionsViewModel): kotlin.Unit skippable: false restartable: true params: @@ -3557,10 +3563,11 @@ public fun com.wire.android.ui.debug.DebugDataOptions(appVersion: kotlin.String, - onCopyText: STABLE (function type) - onShowFeatureFlags: STABLE (function type) - onShowCryptoStats: STABLE (function type) + - onShowSecurityProviders: STABLE (function type) - viewModel: RUNTIME (requires runtime check) @Composable -public fun com.wire.android.ui.debug.DebugDataOptionsContent(state: com.wire.android.ui.debug.DebugDataOptionsState, appVersion: kotlin.String, buildVariant: kotlin.String, onCopyText: kotlin.Function1, onDisableEventProcessingChange: kotlin.Function1, onRestartSlowSyncForRecovery: kotlin.Function0, onForceUpdateApiVersions: kotlin.Function0, enrollE2EICertificate: kotlin.Function0, e2eiCertificateExpirationInputState: androidx.compose.foundation.text.input.TextFieldState, handleE2EIEnrollmentResult: kotlin.Function1, dismissCertificateDialog: kotlin.Function0, checkCrlRevocationList: kotlin.Function0, forceCRLExpirationAfterOneMinute: kotlin.Boolean, onForceCRLExpirationAfterOneMinuteChange: kotlin.Function1, onResendFCMToken: kotlin.Function0, onShowFeatureFlags: kotlin.Function0, onShowCryptoStats: kotlin.Function0, onRepairFaultyRemovalKeys: kotlin.Function0, modifier: androidx.compose.ui.Modifier): kotlin.Unit +public fun com.wire.android.ui.debug.DebugDataOptionsContent(state: com.wire.android.ui.debug.DebugDataOptionsState, appVersion: kotlin.String, buildVariant: kotlin.String, onCopyText: kotlin.Function1, onDisableEventProcessingChange: kotlin.Function1, onRestartSlowSyncForRecovery: kotlin.Function0, onForceUpdateApiVersions: kotlin.Function0, enrollE2EICertificate: kotlin.Function0, e2eiCertificateExpirationInputState: androidx.compose.foundation.text.input.TextFieldState, handleE2EIEnrollmentResult: kotlin.Function1, dismissCertificateDialog: kotlin.Function0, checkCrlRevocationList: kotlin.Function0, forceCRLExpirationAfterOneMinute: kotlin.Boolean, onForceCRLExpirationAfterOneMinuteChange: kotlin.Function1, onResendFCMToken: kotlin.Function0, onShowFeatureFlags: kotlin.Function0, onShowCryptoStats: kotlin.Function0, onShowSecurityProviders: kotlin.Function0, onRepairFaultyRemovalKeys: kotlin.Function0, modifier: androidx.compose.ui.Modifier): kotlin.Unit skippable: true restartable: true params: @@ -3581,6 +3588,7 @@ public fun com.wire.android.ui.debug.DebugDataOptionsContent(state: com.wire.and - onResendFCMToken: STABLE (function type) - onShowFeatureFlags: STABLE (function type) - onShowCryptoStats: STABLE (function type) + - onShowSecurityProviders: STABLE (function type) - onRepairFaultyRemovalKeys: STABLE (function type) - modifier: STABLE (marked @Stable or @Immutable) @@ -3926,6 +3934,21 @@ public fun com.wire.android.ui.debug.rememberDebugContentState(logPath: kotlin.S params: - logPath: STABLE (String is immutable) +@Composable +public fun com.wire.android.ui.debug.securityProvidersViewModel(): com.wire.android.ui.debug.securityproviders.SecurityProvidersViewModel + skippable: true + restartable: true + params: + +@Composable +public fun com.wire.android.ui.debug.securityproviders.SecurityProvidersScreen(navigator: com.wire.android.navigation.Navigator, modifier: androidx.compose.ui.Modifier, viewModel: com.wire.android.ui.debug.securityproviders.SecurityProvidersViewModel): kotlin.Unit + skippable: false + restartable: true + params: + - navigator: STABLE (marked @Stable or @Immutable) + - modifier: STABLE (marked @Stable or @Immutable) + - viewModel: UNSTABLE (has mutable properties or unstable members) + @Composable public fun com.wire.android.ui.debug.userDebugViewModel(): com.wire.android.ui.debug.UserDebugViewModel skippable: true diff --git a/features/meetings/stability/meetings-debug.stability b/features/meetings/stability/meetings-debug.stability index 847664b150a..444b7bd8687 100644 --- a/features/meetings/stability/meetings-debug.stability +++ b/features/meetings/stability/meetings-debug.stability @@ -40,7 +40,14 @@ public fun com.wire.android.feature.meetings.ui.NewMeetingBottomSheet(sheetState - onScheduleClick: STABLE (function type) @Composable -public fun com.wire.android.feature.meetings.ui.create.NewMeetingContent(state: com.wire.android.feature.meetings.ui.create.NewMeetingState, titleState: androidx.compose.foundation.text.input.TextFieldState, type: com.wire.android.feature.meetings.ui.create.NewMeetingType, modifier: androidx.compose.ui.Modifier, onBackPressed: kotlin.Function0, onParticipantsClicked: kotlin.Function0, onCreateClicked: kotlin.Function0, onStartTimeChanged: kotlin.Function1<@[ParameterName(name = \, onEndTimeChanged: kotlin.Function1<@[ParameterName(name = \, onRepeatingIntervalChanged: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit +private fun com.wire.android.feature.meetings.ui.create.FailedToLoadEditMeetingDataError(navigateBack: kotlin.Function0): kotlin.Unit + skippable: true + restartable: true + params: + - navigateBack: STABLE (function type) + +@Composable +public fun com.wire.android.feature.meetings.ui.create.NewMeetingContent(state: com.wire.android.feature.meetings.ui.create.NewMeetingState, titleState: androidx.compose.foundation.text.input.TextFieldState, type: com.wire.android.feature.meetings.ui.create.NewMeetingType, modifier: androidx.compose.ui.Modifier, onBackPressed: kotlin.Function0, onParticipantsClicked: kotlin.Function0, onCreateClicked: kotlin.Function0, onUpdateClicked: kotlin.Function0, onStartTimeChanged: kotlin.Function1<@[ParameterName(name = \, onEndTimeChanged: kotlin.Function1<@[ParameterName(name = \, onRepeatingIntervalChanged: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit skippable: true restartable: true params: @@ -51,6 +58,7 @@ public fun com.wire.android.feature.meetings.ui.create.NewMeetingContent(state: - onBackPressed: STABLE (function type) - onParticipantsClicked: STABLE (function type) - onCreateClicked: STABLE (function type) + - onUpdateClicked: STABLE (function type) - onStartTimeChanged: STABLE (function type) - onEndTimeChanged: STABLE (function type) - onRepeatingIntervalChanged: STABLE (function type) From bbb1326c26cc78ae44a849861600b10068bcd3b3 Mon Sep 17 00:00:00 2001 From: ohassine Date: Fri, 14 Aug 2026 19:40:08 +0200 Subject: [PATCH 08/14] refactor: remove service recording --- .../android/datastore/EncryptionManager.kt | 62 +++++--------- .../wire/android/feature/e2ei/OAuthUseCase.kt | 45 +++++----- .../AppCryptoServicesProvider.kt | 64 -------------- .../SecurityProvidersScreen.kt | 2 +- .../SecurityProvidersViewModel.kt | 38 +++------ .../util/crypto/AppCryptoServiceRegistry.kt | 67 --------------- .../android/util/crypto/AppCryptoServices.kt | 84 +++++++++++++++++++ .../android/util/crypto/AppCryptoUsage.kt | 36 -------- app/src/main/res/values/strings.xml | 9 +- 9 files changed, 140 insertions(+), 267 deletions(-) delete mode 100644 app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt delete mode 100644 app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt create mode 100644 app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt delete mode 100644 app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt diff --git a/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt b/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt index 07956e683b1..b7be6b2e2a8 100644 --- a/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt +++ b/app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt @@ -20,8 +20,8 @@ package com.wire.android.datastore import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 -import com.wire.android.util.crypto.AppCryptoServiceRegistry -import com.wire.android.util.crypto.AppCryptoUsage +import com.wire.android.util.crypto.AppCryptoServiceInfo +import com.wire.android.util.crypto.appCryptoServiceInfo import java.io.UnsupportedEncodingException import java.nio.charset.Charset import java.security.InvalidKeyException @@ -42,44 +42,30 @@ object EncryptionManager { private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING" private const val ANDROID_KEY_STORE = "AndroidKeyStore" - private val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply { - load(null) - AppCryptoServiceRegistry.record( - usage = AppCryptoUsage.DATASTORE_KEYSTORE, - lookup = "KeyStore.getInstance(\"$ANDROID_KEY_STORE\")", - algorithm = type, - provider = provider - ) - } - private val cipher = Cipher.getInstance(TRANSFORMATION).also { - AppCryptoServiceRegistry.record( - usage = AppCryptoUsage.DATASTORE_CIPHER, - lookup = "Cipher.getInstance(\"$TRANSFORMATION\")", - algorithm = it.algorithm, - provider = it.provider - ) - } + private val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply { load(null) } + private val cipher = Cipher.getInstance(TRANSFORMATION) private val charset = Charset.defaultCharset() /** - * Resolves the providers backing this manager, for the security providers debug screen. + * Which providers serve DataStore crypto, for the security providers debug screen. * - * Touching [keyStore] and [cipher] initialises them, which records what served them. The key - * generator is only resolved, never initialised with a [KeyGenParameterSpec] or asked for a key, so - * nothing is written to the Android keystore. + * Lives here, next to the call sites, so it shares their algorithm constants: change a constant and + * this follows automatically instead of quietly reporting the old one. + * + * The key generator is only resolved, never initialised with a [KeyGenParameterSpec] or asked for a + * key, so nothing is written to the Android keystore. */ - fun probeCryptoServices() { - keyStore - cipher - KeyGenerator.getInstance(ALGORITHM).also { - AppCryptoServiceRegistry.record( - usage = AppCryptoUsage.DATASTORE_KEY_GENERATION, - lookup = "KeyGenerator.getInstance(\"$ALGORITHM\")", - algorithm = it.algorithm, - provider = it.provider - ) - } - } + fun cryptoServices(): List = listOfNotNull( + appCryptoServiceInfo("DataStore keystore", "KeyStore.getInstance(\"$ANDROID_KEY_STORE\")") { + KeyStore.getInstance(ANDROID_KEY_STORE).run { type to provider } + }, + appCryptoServiceInfo("DataStore key generation", "KeyGenerator.getInstance(\"$ALGORITHM\")") { + KeyGenerator.getInstance(ALGORITHM).run { algorithm to provider } + }, + appCryptoServiceInfo("DataStore cipher", "Cipher.getInstance(\"$TRANSFORMATION\")") { + Cipher.getInstance(TRANSFORMATION).run { algorithm to provider } + }, + ) private fun getKey(keyAlias: String): SecretKey { val existingKey = keyStore.getEntry(keyAlias, null) as? KeyStore.SecretKeyEntry @@ -88,12 +74,6 @@ object EncryptionManager { private fun createKey(keyAlias: String): SecretKey { return KeyGenerator.getInstance(ALGORITHM).apply { - AppCryptoServiceRegistry.record( - usage = AppCryptoUsage.DATASTORE_KEY_GENERATION, - lookup = "KeyGenerator.getInstance(\"$ALGORITHM\")", - algorithm = algorithm, - provider = provider - ) init( KeyGenParameterSpec.Builder( keyAlias, diff --git a/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt b/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt index e2c174aa13b..8d75853ce9b 100644 --- a/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt @@ -26,6 +26,8 @@ import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultRegistry import androidx.activity.result.contract.ActivityResultContracts import com.wire.android.appLogger +import com.wire.android.util.crypto.AppCryptoServiceInfo +import com.wire.android.util.crypto.appCryptoServiceInfo import com.wire.android.util.deeplink.DeepLinkProcessor import com.wire.android.util.findParameterValue import com.wire.android.util.removeQueryParams @@ -42,8 +44,6 @@ import net.openid.appauth.ResponseTypeValues import org.json.JSONObject import java.net.URI import java.security.MessageDigest -import com.wire.android.util.crypto.AppCryptoServiceRegistry -import com.wire.android.util.crypto.AppCryptoUsage import java.security.SecureRandom class OAuthUseCase( @@ -186,7 +186,7 @@ class OAuthUseCase( @Suppress("MagicNumber") private fun getCodeVerifier(): String { - val secureRandom = pkceRandom() + val secureRandom = SecureRandom() val bytes = ByteArray(64) secureRandom.nextBytes(bytes) return Base64.encodeToString(bytes, ENCODING) @@ -211,30 +211,23 @@ class OAuthUseCase( const val CLIENT_ID_QUERY_PARAM = "client_id" const val CODE_VERIFIER_CHALLENGE_METHOD = "S256" const val MESSAGE_DIGEST_ALGORITHM = "SHA-256" - val MESSAGE_DIGEST = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM).also { - AppCryptoServiceRegistry.record( - AppCryptoUsage.OAUTH_PKCE_CHALLENGE, - "MessageDigest.getInstance(\"$MESSAGE_DIGEST_ALGORITHM\")", - it.algorithm, - it.provider, - ) - } - - /** The randomness behind the PKCE code verifier, noted for the security providers debug screen. */ - fun pkceRandom(): SecureRandom = SecureRandom().also { - AppCryptoServiceRegistry.record( - usage = AppCryptoUsage.OAUTH_PKCE_VERIFIER, - lookup = "SecureRandom()", - algorithm = it.algorithm, - provider = it.provider - ) - } + val MESSAGE_DIGEST = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM) + + /** + * Which providers serve PKCE crypto, for the security providers debug screen. + * + * Lives here, next to the call sites, so it shares their algorithm constants: change a constant + * and this follows automatically instead of quietly reporting the old one. + */ + fun cryptoServices(): List = listOfNotNull( + appCryptoServiceInfo("OAuth PKCE verifier", "SecureRandom()") { + SecureRandom().run { algorithm to provider } + }, + appCryptoServiceInfo("OAuth PKCE challenge", "MessageDigest.getInstance(\"$MESSAGE_DIGEST_ALGORITHM\")") { + MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM).run { algorithm to provider } + }, + ) - /** Resolves the providers backing PKCE, for the security providers debug screen. */ - fun probeCryptoServices() { - pkceRandom() - MESSAGE_DIGEST - } const val ENCODING = Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP val URL_AUTH_REDIRECT: Uri = Uri.Builder().scheme(DeepLinkProcessor.DEEP_LINK_SCHEME) .authority(DeepLinkProcessor.E2EI_DEEPLINK_HOST) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt deleted file mode 100644 index dd31a92de85..00000000000 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppCryptoServicesProvider.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Wire - * Copyright (C) 2026 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.android.ui.debug.securityproviders - -import android.content.Context -import androidx.annotation.StringRes -import com.wire.android.R -import com.wire.android.datastore.EncryptionManager -import com.wire.android.di.ApplicationContext -import com.wire.android.feature.e2ei.OAuthUseCase -import com.wire.android.util.crypto.AppCryptoServiceRegistry -import com.wire.android.util.crypto.AppCryptoUsage -import dev.zacsweers.metro.Inject - -/** - * Reports which security provider served each cryptographic call site in the app module. - * - * Kalium's own call sites come from `GetCryptoServiceReportUseCase`; this covers the ones the app makes - * directly, which kalium cannot see. Call sites that have not run yet are probed by performing the very - * same lookup they perform, so every row is a provider the platform actually handed back. - */ -class AppCryptoServicesProvider @Inject constructor( - @ApplicationContext private val context: Context, -) { - - operator fun invoke(): List { - // Neither path runs on app start, so resolve them the same way the call sites do. - EncryptionManager.probeCryptoServices() - OAuthUseCase.probeCryptoServices() - return AppCryptoServiceRegistry.recorded().map { (usage, record) -> - CryptoServiceRow( - label = context.getString(usage.labelRes()), - lookup = record.lookup, - algorithm = record.algorithm, - providerName = record.providerName, - providerVersion = record.providerVersion, - ) - } - } - - @StringRes - private fun AppCryptoUsage.labelRes(): Int = when (this) { - AppCryptoUsage.DATASTORE_KEYSTORE -> R.string.debug_settings_crypto_datastore_keystore - AppCryptoUsage.DATASTORE_KEY_GENERATION -> R.string.debug_settings_crypto_datastore_key_generator - AppCryptoUsage.DATASTORE_CIPHER -> R.string.debug_settings_crypto_datastore_cipher - AppCryptoUsage.OAUTH_PKCE_VERIFIER -> R.string.debug_settings_crypto_oauth_verifier - AppCryptoUsage.OAUTH_PKCE_CHALLENGE -> R.string.debug_settings_crypto_oauth_challenge - } -} diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt index 8569621f587..281d3a6138e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt @@ -82,7 +82,7 @@ fun SecurityProvidersScreen( SettingsItem(title = stringResource(entry.labelRes), text = entry.value) } - SectionHeader(stringResource(R.string.debug_settings_crypto_services)) + SectionHeader(stringResource(R.string.debug_settings_entropy_sources)) if (state.cryptoServices.isEmpty()) { SettingsItem(text = stringResource(R.string.debug_settings_crypto_services_empty)) } diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index ede70dd0351..084b28026f1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -19,6 +19,8 @@ package com.wire.android.ui.debug.securityproviders import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.wire.android.util.crypto.AppCryptoServiceInfo +import com.wire.android.util.crypto.appCryptoServices import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.kalium.logic.feature.debug.CryptoServiceUsage import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase @@ -35,7 +37,6 @@ import java.security.Security @OptIn(DebugKaliumApi::class) class SecurityProvidersViewModel @Inject constructor( private val appPathsProvider: AppPathsProvider, - private val appCryptoServicesProvider: AppCryptoServicesProvider, private val getCryptoServiceReport: GetCryptoServiceReportUseCase, private val dispatcherProvider: DispatcherProvider, ) : ViewModel() { @@ -45,25 +46,13 @@ class SecurityProvidersViewModel @Inject constructor( init { viewModelScope.launch { - val providers = withContext(dispatcherProvider.default()) { - Security.getProviders().map { provider -> - SecurityProvider( - name = provider.name, - version = provider.versionString(), - info = provider.info, - entries = provider.entries - .map { (key, value) -> KeyValueEntry(key.toString(), value.toString()) } - .sortedBy(KeyValueEntry::key) - ) - } - } - val appCryptoServices = withContext(dispatcherProvider.io()) { appCryptoServicesProvider() } - val cryptoServices = getCryptoServiceReport().map(CryptoServiceUsage::toRow) + appCryptoServices + val appServices = withContext(dispatcherProvider.io()) { appCryptoServices() } + val cryptoServices = getCryptoServiceReport().map(CryptoServiceUsage::toRow) + + appServices.map(AppCryptoServiceInfo::toRow) _state.update { current -> current.copy( appPaths = appPathsProvider(), cryptoServices = cryptoServices, - providers = providers, ) } } @@ -79,6 +68,14 @@ private fun CryptoServiceUsage.toRow() = CryptoServiceRow( providerVersion = providerVersion, ) +private fun AppCryptoServiceInfo.toRow() = CryptoServiceRow( + label = name, + lookup = lookup, + algorithm = algorithm, + providerName = providerName, + providerVersion = providerVersion, +) + /** One cryptographic lookup, and the provider that serves it on this device. */ data class CryptoServiceRow( val label: String, @@ -88,16 +85,7 @@ data class CryptoServiceRow( val providerVersion: String, ) -/** - * `Provider.getVersionStr()` needs API 28 and `Provider.getVersion()` is deprecated, so read the version - * straight out of the provider's own property map, where it is registered under this key. - */ -private const val PROVIDER_VERSION_PROPERTY = "Provider.id version" - -private fun Provider.versionString(): String = getProperty(PROVIDER_VERSION_PROPERTY).orEmpty() - data class SecurityProvidersViewState( val appPaths: List = emptyList(), val cryptoServices: List = emptyList(), - val providers: List? = null, ) diff --git a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt deleted file mode 100644 index a5f6fcbe961..00000000000 --- a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceRegistry.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Wire - * Copyright (C) 2026 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.android.util.crypto - -import java.security.Provider -import java.util.concurrent.ConcurrentHashMap - -/** - * Records which security provider actually served each cryptographic call site in the app module. - * - * Mirrors kalium's `CryptoServiceRegistry` for the lookups the app makes directly, which kalium cannot - * see. A call site that has not run yet is absent rather than guessed at, so every entry the debug screen - * shows is something that actually happened. - */ -object AppCryptoServiceRegistry { - - private val records = ConcurrentHashMap() - - /** - * Notes that [usage] was served by [provider]. - * - * @param lookup the lookup performed, as written in the source. - */ - fun record(usage: AppCryptoUsage, lookup: String, algorithm: String, provider: Provider) { - records[usage] = AppCryptoServiceRecord( - lookup = lookup, - algorithm = algorithm, - providerName = provider.name, - providerVersion = provider.versionString(), - ) - } - - /** Every app call site observed so far, in [AppCryptoUsage] declaration order. */ - fun recorded(): List> = - AppCryptoUsage.entries.mapNotNull { usage -> records[usage]?.let { usage to it } } - - /** - * `Provider.getVersionStr()` needs API 28 and `Provider.getVersion()` is deprecated, so read the - * version out of the provider's own property map, where it is registered under this key. - */ - private fun Provider.versionString(): String = getProperty(PROVIDER_VERSION_PROPERTY).orEmpty() - - private const val PROVIDER_VERSION_PROPERTY = "Provider.id version" -} - -/** Which security provider served an app call site, as observed when it ran. */ -data class AppCryptoServiceRecord( - val lookup: String, - val algorithm: String, - val providerName: String, - val providerVersion: String, -) diff --git a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt new file mode 100644 index 00000000000..9be0884d050 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt @@ -0,0 +1,84 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.util.crypto + +import com.wire.android.datastore.EncryptionManager +import com.wire.android.feature.e2ei.OAuthUseCase +import java.security.Provider + +/** + * Which security provider serves each cryptographic lookup in the app module. + * + * Mirrors kalium's `cryptoServices()` for the lookups the app makes directly, which kalium cannot see. + * Which implementation backs an algorithm is decided at runtime by walking the installed security + * providers, so it varies per device, per OEM and per OS version. Each contributing function performs the + * same lookups its call sites perform, from the same file and with the same algorithm constants, and reads + * the provider off what comes back. + * + * Only for the security providers debug screen. Performs lookups and nothing else: no key is persisted and + * no crypto state is mutated. + * + * `SecureRandom` lookups can block while the platform gathers entropy, so call this off the main thread. + */ +fun appCryptoServices(): List = + EncryptionManager.cryptoServices() + OAuthUseCase.cryptoServices() + +/** + * Performs [resolve] and reads the algorithm and provider off the instance it returned, so what is reported + * is what the platform actually handed back. + * + * Null when the lookup fails: a debug screen must not bring down the caller over a missing algorithm. + * + * @param name what the lookup is for, e.g. `DataStore cipher`. + * @param lookup the lookup performed, as written in the source. Interpolate the same constants [resolve] + * uses, so this cannot describe a lookup the call sites do not make. + * @param resolve the JCA lookup, returning its result's `algorithm` and `provider`. + */ +fun appCryptoServiceInfo(name: String, lookup: String, resolve: () -> Pair): AppCryptoServiceInfo? = + runCatching(resolve).getOrNull()?.let { (algorithm, provider) -> + AppCryptoServiceInfo( + name = name, + lookup = lookup, + algorithm = algorithm, + providerName = provider.name, + providerVersion = provider.versionString(), + ) + } + +/** + * Which security provider serves one cryptographic lookup, read off the instance the platform returned. + * + * @param name what the lookup is for, e.g. `DataStore cipher`. + * @param lookup the lookup performed, as written in the source, e.g. `KeyGenerator.getInstance("AES")`. + * @param algorithm the algorithm the resolved instance reports, e.g. `AES/GCM/NoPadding`. + */ +data class AppCryptoServiceInfo( + val name: String, + val lookup: String, + val algorithm: String, + val providerName: String, + val providerVersion: String, +) + +/** + * `Provider.getVersionStr()` needs API 28 and `Provider.getVersion()` is deprecated, so read the version + * out of the provider's own property map, where it is registered under this key. + */ +private fun Provider.versionString(): String = getProperty(PROVIDER_VERSION_PROPERTY).orEmpty() + +private const val PROVIDER_VERSION_PROPERTY = "Provider.id version" diff --git a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt deleted file mode 100644 index eafc4c52a7d..00000000000 --- a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoUsage.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Wire - * Copyright (C) 2026 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.android.util.crypto - -/** A cryptographic call site in the app module. Kalium's own are tracked by `CryptoServiceRegistry`. */ -enum class AppCryptoUsage { - /** `EncryptionManager.keyStore`. */ - DATASTORE_KEYSTORE, - - /** `EncryptionManager.createKey`. */ - DATASTORE_KEY_GENERATION, - - /** `EncryptionManager.cipher`. */ - DATASTORE_CIPHER, - - /** `OAuthUseCase`, PKCE code verifier. */ - OAUTH_PKCE_VERIFIER, - - /** `OAuthUseCase`, PKCE code challenge. */ - OAUTH_PKCE_CHALLENGE, -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 601327d3722..f0f14c995b6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1794,14 +1794,9 @@ In group conversations, the group admin can overwrite this setting. Databases dir No backup dir External files dir - Crypto Services - DataStore keystore - DataStore key generation - DataStore cipher - OAuth PKCE verifier - OAuth PKCE challenge + Entropy sources %1$s \u2014 %2$s %3$s - No crypto call site has run yet in this session + No crypto service could be resolved %1$d entries From 066ffad57dd7dffcec23d46b63aaf336c08a7d5d Mon Sep 17 00:00:00 2001 From: ohassine Date: Mon, 17 Aug 2026 13:57:24 +0200 Subject: [PATCH 09/14] chore: cleanup --- .../ui/debug/securityproviders/SecurityProvidersViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index 86517ee0cc4..9095b5044ea 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import java.security.Provider +import kotlinx.coroutines.withContext @OptIn(DebugKaliumApi::class) class SecurityProvidersViewModel @Inject constructor( From b8b856c4b38a6bf37902f542fe71df26b2bda646 Mon Sep 17 00:00:00 2001 From: ohassine Date: Tue, 18 Aug 2026 12:15:57 +0200 Subject: [PATCH 10/14] chore: cleanup --- .../securityproviders/SecurityProvidersViewModel.kt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index d7d306a6b9d..9095b5044ea 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.security.Provider @OptIn(DebugKaliumApi::class) class SecurityProvidersViewModel @Inject constructor( @@ -58,14 +57,6 @@ class SecurityProvidersViewModel @Inject constructor( } } -/** - * `Provider.getVersionStr()` needs API 28 and `Provider.getVersion()` is deprecated, so read the version - * straight out of the provider's own property map, where it is registered under this key. - */ -private const val PROVIDER_VERSION_PROPERTY = "Provider.id version" - -private fun Provider.versionString(): String = getProperty(PROVIDER_VERSION_PROPERTY).orEmpty() - @OptIn(DebugKaliumApi::class) private fun CryptoServiceUsage.toRow() = CryptoServiceRow( label = name, From 4fbe2d776aeaf4394e0f009b74d95077c389722f Mon Sep 17 00:00:00 2001 From: ohassine Date: Tue, 18 Aug 2026 17:04:24 +0200 Subject: [PATCH 11/14] chore: kalium --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index b46950f005c..9ad6da76972 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit b46950f005cde4196e750b5df6ee12816ce2966d +Subproject commit 9ad6da7697238141389d501158e07bf43bb72f1b From b5f005255fed15fdd72ae46311a803400e3c6b33 Mon Sep 17 00:00:00 2001 From: ohassine Date: Tue, 18 Aug 2026 17:26:02 +0200 Subject: [PATCH 12/14] chore: detekt --- .../securityproviders/AppPathsProvider.kt | 1 - .../SecurityProvidersScreen.kt | 4 +- .../SecurityProvidersViewModel.kt | 45 +++++++++---------- .../util/crypto/AppCryptoServiceInfo.kt | 33 ++++++++++++++ .../android/util/crypto/AppCryptoServices.kt | 15 ------- 5 files changed, 55 insertions(+), 43 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceInfo.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt index 378eb15df89..be64cb098a9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt @@ -18,7 +18,6 @@ package com.wire.android.ui.debug.securityproviders import android.content.Context -import androidx.annotation.StringRes import com.wire.android.R import com.wire.android.di.ApplicationContext import com.wire.android.di.CurrentAccount diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt index eaefc4607c4..3a565e1e750 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersScreen.kt @@ -84,10 +84,10 @@ fun SecurityProvidersScreen( } SectionHeader(stringResource(R.string.debug_settings_entropy_sources)) - if (state.cryptoServices.isEmpty()) { + if (state.cryptoServices?.isEmpty() == true) { SettingsItem(text = stringResource(R.string.debug_settings_crypto_services_empty)) } - state.cryptoServices.forEach { row -> + state.cryptoServices?.forEach { row -> CryptoServiceListItem(row) } diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index c385c552919..9e9b669687e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -20,26 +20,22 @@ package com.wire.android.ui.debug.securityproviders import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wire.android.appLogger -import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase -import com.wire.kalium.logic.feature.user.SelfServerConfigUseCase import com.wire.android.util.crypto.AppCryptoServiceInfo import com.wire.android.util.crypto.appCryptoServices import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.kalium.logic.feature.debug.CryptoServiceUsage import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase +import com.wire.kalium.logic.feature.user.SelfServerConfigUseCase +import com.wire.kalium.network.NetworkStateObserver import com.wire.kalium.util.DebugKaliumApi import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import com.wire.kalium.logic.feature.user.SelfServerConfigUseCase -import com.wire.kalium.network.NetworkStateObserver @OptIn(DebugKaliumApi::class) class SecurityProvidersViewModel @Inject constructor( @@ -71,29 +67,11 @@ class SecurityProvidersViewModel @Inject constructor( } } - - @OptIn(DebugKaliumApi::class) - private fun CryptoServiceUsage.toRow() = CryptoServiceRow( - label = name, - lookup = lookup, - algorithm = algorithm, - providerName = providerName, - providerVersion = providerVersion, - ) - - private fun AppCryptoServiceInfo.toRow() = CryptoServiceRow( - label = name, - lookup = lookup, - algorithm = algorithm, - providerName = providerName, - providerVersion = providerVersion, - ) - private suspend fun observeNetworkDiagnostics() { val apiUrl = apiUrl() ?: return networkStateObserver.observeCurrentNetwork() .map { networkDiagnosticsProvider(apiUrl) } - .flowOn(dispatchers.io()) + .flowOn(dispatcherProvider.io()) .collect { diagnostics -> _state.update { current -> current.copy(network = diagnostics) } } } @@ -106,6 +84,23 @@ class SecurityProvidersViewModel @Inject constructor( } } +private fun AppCryptoServiceInfo.toRow() = CryptoServiceRow( + label = name, + lookup = lookup, + algorithm = algorithm, + providerName = providerName, + providerVersion = providerVersion, +) + +@OptIn(DebugKaliumApi::class) +private fun CryptoServiceUsage.toRow() = CryptoServiceRow( + label = name, + lookup = lookup, + algorithm = algorithm, + providerName = providerName, + providerVersion = providerVersion, +) + /** One cryptographic lookup, and the provider that serves it on this device. */ data class CryptoServiceRow( val label: String, diff --git a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceInfo.kt b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceInfo.kt new file mode 100644 index 00000000000..cbd24a35ead --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceInfo.kt @@ -0,0 +1,33 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.util.crypto + +/** + * Which security provider serves one cryptographic lookup, read off the instance the platform returned. + * + * @param name what the lookup is for, e.g. `DataStore cipher`. + * @param lookup the lookup performed, as written in the source, e.g. `KeyGenerator.getInstance("AES")`. + * @param algorithm the algorithm the resolved instance reports, e.g. `AES/GCM/NoPadding`. + */ +data class AppCryptoServiceInfo( + val name: String, + val lookup: String, + val algorithm: String, + val providerName: String, + val providerVersion: String, +) diff --git a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt index 9be0884d050..2600b2c2c69 100644 --- a/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt +++ b/app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServices.kt @@ -60,21 +60,6 @@ fun appCryptoServiceInfo(name: String, lookup: String, resolve: () -> Pair Date: Wed, 19 Aug 2026 17:26:35 +0200 Subject: [PATCH 13/14] chore: cleanup --- .../main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt | 1 - .../wire/android/ui/debug/securityproviders/AppPathsProvider.kt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt index 9f28abb296d..1c3d4c2c2f4 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/DebugModule.kt @@ -27,7 +27,6 @@ import com.wire.kalium.logic.feature.debug.DebugFeedConversationUseCase import com.wire.kalium.logic.feature.debug.DebugScope import com.wire.kalium.logic.feature.debug.DisableEventProcessingUseCase import com.wire.kalium.logic.feature.debug.GetConversationCryptoStatsUseCase -import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase import com.wire.kalium.logic.feature.debug.GetConversationEpochFromCCUseCase import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase import com.wire.kalium.logic.feature.debug.GetDebugE2EICertificateExpirationUseCase diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt index 739c75f439d..60104f7a114 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt @@ -23,6 +23,7 @@ import com.wire.android.di.ApplicationContext import com.wire.android.di.CurrentAccount import com.wire.kalium.logic.data.user.UserId import dev.zacsweers.metro.Inject +import java.io.File class AppPathsProvider @Inject constructor( @ApplicationContext private val context: Context, From 25d65adffa035a69fdb214c9b98d6c928fdf6f50 Mon Sep 17 00:00:00 2001 From: ohassine Date: Wed, 19 Aug 2026 17:28:39 +0200 Subject: [PATCH 14/14] chore: cleanup --- .../ui/debug/securityproviders/SecurityProvidersViewModel.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt index f34d9b0bca8..465aa6d7a5a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt @@ -23,9 +23,9 @@ import com.wire.android.appLogger import com.wire.android.util.crypto.AppCryptoServiceInfo import com.wire.android.util.crypto.appCryptoServices import com.wire.android.util.dispatchers.DispatcherProvider -import com.wire.kalium.logic.feature.debug.GetSqlCipherVersionUseCase import com.wire.kalium.logic.feature.debug.CryptoServiceUsage import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase +import com.wire.kalium.logic.feature.debug.GetSqlCipherVersionUseCase import com.wire.kalium.logic.feature.user.SelfServerConfigUseCase import com.wire.kalium.network.NetworkStateObserver import com.wire.kalium.util.DebugKaliumApi @@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.coroutines.withContext @OptIn(DebugKaliumApi::class) class SecurityProvidersViewModel @Inject constructor(