Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
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
Expand All @@ -38,11 +40,33 @@
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 keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply { load(null) }

Check warning on line 45 in app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt#L45

Added line #L45 was not covered by tests
private val cipher = Cipher.getInstance(TRANSFORMATION)
private val charset = Charset.defaultCharset()

/**
* Which providers serve DataStore 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.
*
* 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 cryptoServices(): List<AppCryptoServiceInfo> = listOfNotNull(
appCryptoServiceInfo("DataStore keystore", "KeyStore.getInstance(\"$ANDROID_KEY_STORE\")") {
KeyStore.getInstance(ANDROID_KEY_STORE).run { type to provider }

Check warning on line 60 in app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt#L58-L60

Added lines #L58 - L60 were not covered by tests
},
appCryptoServiceInfo("DataStore key generation", "KeyGenerator.getInstance(\"$ALGORITHM\")") {
KeyGenerator.getInstance(ALGORITHM).run { algorithm to provider }

Check warning on line 63 in app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt#L62-L63

Added lines #L62 - L63 were not covered by tests
},
appCryptoServiceInfo("DataStore cipher", "Cipher.getInstance(\"$TRANSFORMATION\")") {
Cipher.getInstance(TRANSFORMATION).run { algorithm to provider }

Check warning on line 66 in app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/EncryptionManager.kt#L65-L66

Added lines #L65 - L66 were not covered by tests
},
)

private fun getKey(keyAlias: String): SecretKey {
val existingKey = keyStore.getEntry(keyAlias, null) as? KeyStore.SecretKeyEntry
return existingKey?.secretKey ?: createKey(keyAlias)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ 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.GetConversationEpochFromCCUseCase
import com.wire.kalium.logic.feature.debug.GetCryptoServiceReportUseCase
import com.wire.kalium.logic.feature.debug.GetDebugE2EICertificateExpirationUseCase
import com.wire.kalium.logic.feature.debug.GetFeatureConfigUseCase
import com.wire.kalium.logic.feature.debug.GetSqlCipherVersionUseCase
Expand Down Expand Up @@ -76,6 +77,10 @@ class DebugModule {
@Provides
fun provideGetSqlCipherVersionUseCase(debugScope: DebugScope): GetSqlCipherVersionUseCase = debugScope.getSqlCipherVersion

@Provides
fun provideGetCryptoServiceReportUseCase(debugScope: DebugScope): GetCryptoServiceReportUseCase =
debugScope.getCryptoServiceReport

@Provides
fun provideGetDebugE2EICertificateExpirationUseCase(debugScope: DebugScope): GetDebugE2EICertificateExpirationUseCase =
debugScope.getDebugE2EICertificateExpiration
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
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
Expand Down Expand Up @@ -210,6 +212,22 @@
const val CODE_VERIFIER_CHALLENGE_METHOD = "S256"
const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
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<AppCryptoServiceInfo> = listOfNotNull(
appCryptoServiceInfo("OAuth PKCE verifier", "SecureRandom()") {
SecureRandom().run { algorithm to provider }

Check warning on line 224 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L222-L224

Added lines #L222 - L224 were not covered by tests
},
appCryptoServiceInfo("OAuth PKCE challenge", "MessageDigest.getInstance(\"$MESSAGE_DIGEST_ALGORITHM\")") {
MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM).run { algorithm to provider }

Check warning on line 227 in app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/feature/e2ei/OAuthUseCase.kt#L226-L227

Added lines #L226 - L227 were not covered by tests
},
)

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,27 @@
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
import java.io.File

class AppPathsProvider(
private val context: Context,
private val currentAccount: UserId,
class AppPathsProvider @Inject constructor(
@ApplicationContext private val context: Context,
@CurrentAccount private val currentAccount: UserId,

Check warning on line 30 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt#L28-L30

Added lines #L28 - L30 were not covered by tests
) {
operator fun invoke(): List<AppPathEntry> = with(context) {
operator fun invoke(): List<LabelledValue> = with(context) {

Check warning on line 32 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt#L32

Added line #L32 was not covered by tests
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),

Check warning on line 38 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt#L35-L38

Added lines #L35 - L38 were not covered by tests
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),

Check warning on line 40 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/AppPathsProvider.kt#L40

Added line #L40 was not covered by tests
LabelledValue(R.string.debug_settings_app_path_external_files_dir, getExternalFilesDir(null)?.absolutePath.orEmpty()),
)
}

Expand Down Expand Up @@ -115,8 +117,3 @@

@Suppress("MagicNumber")
private fun ByteArray.toHexString(): String = joinToString(separator = "") { byte -> "%02x".format(byte.toInt() and 0xff) }

data class AppPathEntry(
@StringRes val labelRes: Int,
val path: String,
)
Original file line number Diff line number Diff line change
@@ -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 = row.label,
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(
label = "Asset AES-256 key",
lookup = "KeyGenerator.getInstance(\"AES\")",
algorithm = "AES",
providerName = "AndroidOpenSSL",
providerVersion = "1.0",
)
)
}
Original file line number Diff line number Diff line change
@@ -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,

Check warning on line 25 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/LabelledValue.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/LabelledValue.kt#L23-L25

Added lines #L23 - L25 were not covered by tests
)
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,15 @@ internal fun SecurityProvidersRouteScreen(
) {
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_entropy_sources))
if (state.cryptoServices?.isEmpty() == true) {
SettingsItem(text = stringResource(R.string.debug_settings_crypto_services_empty))
}
state.cryptoServices?.forEach { row ->
CryptoServiceListItem(row)
}

state.network?.let { network ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.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
Expand All @@ -37,6 +41,7 @@
@OptIn(DebugKaliumApi::class)
class SecurityProvidersViewModel @Inject constructor(
private val appPathsProvider: AppPathsProvider,
private val getCryptoServiceReport: GetCryptoServiceReportUseCase,

Check warning on line 44 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L44

Added line #L44 was not covered by tests
private val networkDiagnosticsProvider: NetworkDiagnosticsProvider,
private val networkStateObserver: NetworkStateObserver,
private val selfServerConfig: SelfServerConfigUseCase,
Expand All @@ -55,9 +60,13 @@
userDatabase = appPathsProvider.userDatabaseSecurityStatus(),
)
}
val appServices = withContext(dispatchers.io()) { appCryptoServices() }
val cryptoServices = getCryptoServiceReport().map(CryptoServiceUsage::toRow) +
appServices.map(AppCryptoServiceInfo::toRow)

Check warning on line 65 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L63-L65

Added lines #L63 - L65 were not covered by tests
_state.update { current ->
current.copy(
appPaths = appPathsProvider(),
cryptoServices = cryptoServices,

Check warning on line 69 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L69

Added line #L69 was not covered by tests
databaseSecurity = databaseSecurity
)
}
Expand All @@ -84,10 +93,36 @@
}
}

private fun AppCryptoServiceInfo.toRow() = CryptoServiceRow(
label = name,
lookup = lookup,
algorithm = algorithm,
providerName = providerName,
providerVersion = providerVersion,

Check warning on line 101 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L96-L101

Added lines #L96 - L101 were not covered by tests
)

@OptIn(DebugKaliumApi::class)
private fun CryptoServiceUsage.toRow() = CryptoServiceRow(
label = name,
lookup = lookup,
algorithm = algorithm,
providerName = providerName,
providerVersion = providerVersion,

Check warning on line 110 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L105-L110

Added lines #L105 - L110 were not covered by tests
)

/** One cryptographic lookup, and the provider that serves it on this device. */
data class CryptoServiceRow(
val label: String,
val lookup: String,
val algorithm: String,
val providerName: String,
val providerVersion: String,

Check warning on line 119 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L114-L119

Added lines #L114 - L119 were not covered by tests
)

data class SecurityProvidersViewState(
val appPaths: List<AppPathEntry> = emptyList(),
val appPaths: List<LabelledValue> = emptyList(),

Check warning on line 123 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L123

Added line #L123 was not covered by tests
val network: NetworkDiagnostics? = null,
val providers: List<SecurityProvider>? = null,
val cryptoServices: List<CryptoServiceRow>? = null,

Check warning on line 125 in app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/debug/securityproviders/SecurityProvidersViewModel.kt#L125

Added line #L125 was not covered by tests
val databaseSecurity: DatabaseSecurityInfo? = null,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -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,

Check warning on line 32 in app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceInfo.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/util/crypto/AppCryptoServiceInfo.kt#L27-L32

Added lines #L27 - L32 were not covered by tests
)
Loading
Loading