From 33b555b9d6a868520361ad297782e8fcddba59ce Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 31 Oct 2025 15:02:28 -0400 Subject: [PATCH 01/10] [PM-24971] Sanitize passkey attestation options from AliExpress This commit introduces a sanitizer for `PasskeyAttestationOptions` to address an issue with the AliExpress Android app. The AliExpress app incorrectly appends a newline character to the `user.id` field when creating a passkey. This malformed request causes the passkey creation to fail. To work around this, a `PasskeyAttestationOptionsSanitizer` is introduced. This sanitizer specifically checks if the request originates from the AliExpress relying party ID (`m.aliexpress.com`) and if the `user.id` ends with a newline. If both conditions are met, it trims the `user.id` before the request is further processed, allowing the passkey registration to succeed. Specific changes include: - Added `PasskeyAttestationOptionsSanitizer` interface and its implementation, `PasskeyAttestationOptionSanitizerImpl`, which contains the specific fix for AliExpress. - Injected and utilized the new sanitizer within `BitwardenCredentialManagerImpl` to clean the `PasskeyAttestationOptions` before they are used to register a FIDO2 credential. - Updated dependency injection in `CredentialProviderModule` to provide the sanitizer. - Added unit tests for the new sanitizer logic. --- .../di/CredentialProviderModule.kt | 13 ++- .../manager/BitwardenCredentialManagerImpl.kt | 65 +++++++---- .../PasskeyAttestationOptionSanitizerImpl.kt | 26 +++++ .../PasskeyAttestationOptionsSanitizer.kt | 16 +++ .../manager/BitwardenCredentialManagerTest.kt | 30 ++++- .../PasskeyAttestationOptionSanitizerTest.kt | 109 ++++++++++++++++++ 6 files changed, 226 insertions(+), 33 deletions(-) create mode 100644 app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerImpl.kt create mode 100644 app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionsSanitizer.kt create mode 100644 app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerTest.kt diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt index b6c131d6768..69142e05635 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt @@ -22,6 +22,8 @@ import com.x8bit.bitwarden.data.credentials.processor.CredentialProviderProcesso import com.x8bit.bitwarden.data.credentials.processor.CredentialProviderProcessorImpl import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepository import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepositoryImpl +import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionSanitizerImpl +import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionsSanitizer import com.x8bit.bitwarden.data.platform.manager.AssetManager import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager @@ -75,15 +77,17 @@ object CredentialProviderModule { dispatcherManager: DispatcherManager, credentialEntryBuilder: CredentialEntryBuilder, cipherMatchingManager: CipherMatchingManager, + passkeyAttestationOptionsSanitizer: PasskeyAttestationOptionsSanitizer, ): BitwardenCredentialManager = BitwardenCredentialManagerImpl( vaultSdkSource = vaultSdkSource, fido2CredentialStore = fido2CredentialStore, + credentialEntryBuilder = credentialEntryBuilder, json = json, vaultRepository = vaultRepository, - dispatcherManager = dispatcherManager, - credentialEntryBuilder = credentialEntryBuilder, cipherMatchingManager = cipherMatchingManager, + passkeyAttestationOptionsSanitizer = passkeyAttestationOptionsSanitizer, + dispatcherManager = dispatcherManager, ) @Provides @@ -139,4 +143,9 @@ object CredentialProviderModule { CredentialManagerPendingIntentManagerImpl( context = context, ) + + @Provides + @Singleton + fun providePasskeyAttestationOptionsSanitizer(): PasskeyAttestationOptionsSanitizer = + PasskeyAttestationOptionSanitizerImpl } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt index 597a1a64173..04c1bba1ee7 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt @@ -33,6 +33,7 @@ import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest import com.x8bit.bitwarden.data.credentials.model.PasskeyAssertionOptions import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions import com.x8bit.bitwarden.data.credentials.model.UserVerificationRequirement +import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionsSanitizer import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager import com.x8bit.bitwarden.data.platform.util.getAppOrigin import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint @@ -60,6 +61,7 @@ class BitwardenCredentialManagerImpl( private val json: Json, private val vaultRepository: VaultRepository, private val cipherMatchingManager: CipherMatchingManager, + private val passkeyAttestationOptionsSanitizer: PasskeyAttestationOptionsSanitizer, dispatcherManager: DispatcherManager, ) : BitwardenCredentialManager, Fido2CredentialStore by fido2CredentialStore { @@ -359,31 +361,44 @@ class BitwardenCredentialManagerImpl( selectedCipherView: CipherView, clientData: ClientData, callingPackageName: String, - ): Fido2RegisterCredentialResult = vaultSdkSource - .registerFido2Credential( - request = RegisterFido2CredentialRequest( - userId = userId, - origin = sdkOrigin, - requestJson = """{"publicKey": ${createPublicKeyCredentialRequest.requestJson}}""", - clientData = clientData, - selectedCipherView = selectedCipherView, - // User verification is handled prior to engaging the SDK. We always respond - // `true` so that the SDK does not fail if the relying party requests UV. - isUserVerificationSupported = true, - ), - fido2CredentialStore = this, - ) - .map { - it.toAndroidAttestationResponse(callingPackageName = callingPackageName) - } - .mapCatching { json.encodeToString(it) } - .fold( - onSuccess = { Fido2RegisterCredentialResult.Success(it) }, - onFailure = { - Timber.e(it, "Failed to register FIDO2 credential.") - Fido2RegisterCredentialResult.Error.InternalError - }, - ) + ): Fido2RegisterCredentialResult { + + val requestJson = + getPasskeyAttestationOptionsOrNull(createPublicKeyCredentialRequest.requestJson) + ?.also { passkeyAttestationOptionsSanitizer.sanitize(it) } + ?.runCatching { json.encodeToString(this) } + ?.fold( + onSuccess = { it }, + onFailure = { null }, + ) + ?: return Fido2RegisterCredentialResult.Error.InternalError + + return vaultSdkSource + .registerFido2Credential( + request = RegisterFido2CredentialRequest( + userId = userId, + origin = sdkOrigin, + requestJson = """{"publicKey": $requestJson}""", + clientData = clientData, + selectedCipherView = selectedCipherView, + // User verification is handled prior to engaging the SDK. We always respond + // `true` so that the SDK does not fail if the relying party requests UV. + isUserVerificationSupported = true, + ), + fido2CredentialStore = this, + ) + .map { + it.toAndroidAttestationResponse(callingPackageName = callingPackageName) + } + .mapCatching { json.encodeToString(it) } + .fold( + onSuccess = { Fido2RegisterCredentialResult.Success(it) }, + onFailure = { + Timber.e(it, "Failed to register FIDO2 credential.") + Fido2RegisterCredentialResult.Error.InternalError + }, + ) + } private fun List.toPasswordCredentialEntries( userId: String, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerImpl.kt new file mode 100644 index 00000000000..b51e1932197 --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerImpl.kt @@ -0,0 +1,26 @@ +package com.x8bit.bitwarden.data.credentials.santizer + +import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions + +/** + * Default implementation of [PasskeyAttestationOptionsSanitizer]. + */ +object PasskeyAttestationOptionSanitizerImpl : PasskeyAttestationOptionsSanitizer { + override fun sanitize(options: PasskeyAttestationOptions): PasskeyAttestationOptions { + // The AliExpress Android app (com.alibaba.aliexpresshd) incorrectly appends a newline + // to the user.id field when creating a passkey. This causes the operation to fail + // downstream. As a workaround, we detect this specific scenario, trim the newline, and + // re-serialize the JSON request. + return if (options.relyingParty.id.contains(ALIEXPRESS_RP_ID) && + options.user.id.endsWith("\n") + ) { + options.copy( + user = options.user.copy(id = options.user.id.trim()), + ) + } else { + options + } + } +} + +private const val ALIEXPRESS_RP_ID = "m.aliexpress.com" diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionsSanitizer.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionsSanitizer.kt new file mode 100644 index 00000000000..343f7cbeeb6 --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionsSanitizer.kt @@ -0,0 +1,16 @@ +package com.x8bit.bitwarden.data.credentials.santizer + +import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions + +/** + * Interface for sanitizing [PasskeyAttestationOptions]. + */ +interface PasskeyAttestationOptionsSanitizer { + + /** + * Sanitizes the given [options] in preparation for processing. + * + * @param options The [PasskeyAttestationOptions] to sanitize. + */ + fun sanitize(options: PasskeyAttestationOptions): PasskeyAttestationOptions +} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt index 388bf030b4e..de53c8e1ed7 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt @@ -35,6 +35,7 @@ import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest import com.x8bit.bitwarden.data.credentials.model.PasskeyAssertionOptions import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions import com.x8bit.bitwarden.data.credentials.model.UserVerificationRequirement +import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionsSanitizer import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource @@ -81,19 +82,23 @@ class BitwardenCredentialManagerTest { private val mutableDecryptCipherListResultStateFlow = MutableStateFlow>(DataState.Loading) + private val mockPasskeyAttestationOptions = createMockPasskeyAttestationOptions( + number = 1, + relyingPartyId = DEFAULT_HOST, + ) private val json = mockk { every { decodeFromStringOrNull(any()) - } returns createMockPasskeyAttestationOptions( - number = 1, - relyingPartyId = DEFAULT_HOST, - ) + } returns mockPasskeyAttestationOptions every { decodeFromStringOrNull(any()) } returns createMockPasskeyAssertionOptions(number = 1) every { decodeFromStringOrNull(DEFAULT_FIDO2_AUTH_REQUEST_JSON) } returns createMockPasskeyAssertionOptions(number = 1) + every { + encodeToString(mockPasskeyAttestationOptions) + } returns DEFAULT_FIDO2_CREATE_REQUEST_JSON } private val mockSigningInfo = mockk { every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE)) @@ -126,6 +131,9 @@ class BitwardenCredentialManagerTest { } private val mockCredentialEntryBuilder = mockk() private val mockCipherMatchingManager = mockk() + private val mockPasskeyAttestationOptionsSanitizer = mockk { + every { sanitize(any()) } returns mockPasskeyAttestationOptions + } @BeforeEach fun setUp() { @@ -139,11 +147,12 @@ class BitwardenCredentialManagerTest { bitwardenCredentialManager = BitwardenCredentialManagerImpl( vaultSdkSource = mockVaultSdkSource, fido2CredentialStore = mockFido2CredentialStore, + credentialEntryBuilder = mockCredentialEntryBuilder, json = json, - dispatcherManager = FakeDispatcherManager(), vaultRepository = mockVaultRepository, - credentialEntryBuilder = mockCredentialEntryBuilder, cipherMatchingManager = mockCipherMatchingManager, + passkeyAttestationOptionsSanitizer = mockPasskeyAttestationOptionsSanitizer, + dispatcherManager = FakeDispatcherManager(), ) } @@ -192,6 +201,9 @@ class BitwardenCredentialManagerTest { val selectedCipherView = createMockCipherView(number = 1) val slot = slot() + every { + json.encodeToString(any()) + } returns mockCreatePublicKeyCredentialRequest.requestJson every { mockCallingAppInfo.getAppSigningSignatureFingerprint() } returns DEFAULT_APP_SIGNATURE.toByteArray() @@ -242,6 +254,9 @@ class BitwardenCredentialManagerTest { val slot = slot() every { json.encodeToString(any(), any()) } returns "" + every { + json.encodeToString(any()) + } returns DEFAULT_FIDO2_CREATE_REQUEST_JSON every { mockCallingAppInfo.isOriginPopulated() } returns false every { mockCreatePublicKeyCredentialRequest.origin } returns null coEvery { @@ -286,6 +301,9 @@ class BitwardenCredentialManagerTest { every { Base64.encodeToString(any(), any()) } returns DEFAULT_APP_SIGNATURE every { json.encodeToString(any(), any()) } returns "" + every { + json.encodeToString(any()) + } returns mockCreatePublicKeyCredentialRequest.requestJson every { mockCreatePublicKeyCredentialRequest.origin } returns DEFAULT_WEB_ORIGIN.v1 val requestCaptureSlot = slot() coEvery { diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerTest.kt new file mode 100644 index 00000000000..fa9ba9e3351 --- /dev/null +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerTest.kt @@ -0,0 +1,109 @@ +package com.x8bit.bitwarden.data.credentials.santizer + +import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotSame +import org.junit.jupiter.api.Assertions.assertSame +import org.junit.jupiter.api.Test + +class PasskeyAttestationOptionSanitizerTest { + + private val sanitizer: PasskeyAttestationOptionsSanitizer = + PasskeyAttestationOptionSanitizerImpl + + @Test + fun `Sanitization on matching RP ID and user ID with newline`() { + val options = createOptions(rpId = "m.aliexpress.com", userId = "user123\n") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertEquals("user123", sanitizedOptions.user.id) + assertNotSame(options, sanitizedOptions) + } + + @Test + fun `No sanitization on non matching RP ID`() { + val options = createOptions(rpId = "some.other.rp", userId = "user123") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertSame(options, sanitizedOptions) + } + + @Test + fun `No sanitization when user ID does not end with newline`() { + val options = createOptions(rpId = "m.aliexpress.com", userId = "user123") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertSame(options, sanitizedOptions) + } + + @Test + fun `No sanitization on matching RP ID and newline in middle of user ID`() { + val options = createOptions(rpId = "m.aliexpress.com", userId = "user123") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertSame(options, sanitizedOptions) + } + + @Test + fun `Sanitization with multiple trailing newlines`() { + val options = createOptions(rpId = "m.aliexpress.com", userId = "user123\n") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertEquals("user123", sanitizedOptions.user.id) + assertNotSame(options, sanitizedOptions) + } + + @Test + fun `Sanitization with preceding trailing spaces and a newline`() { + val options = createOptions(rpId = "m.aliexpress.com", userId = "user123\n") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertEquals("user123", sanitizedOptions.user.id) + assertNotSame(options, sanitizedOptions) + } + + @Test + fun `No sanitization with an empty user ID`() { + val options = createOptions(rpId = "m.aliexpress.com", userId = "") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertSame(options, sanitizedOptions) + } + + @Test + fun `Sanitization with user ID containing only a newline`() { + val options = createOptions(rpId = "m.aliexpress.com", userId = "\n") + + val sanitizedOptions = sanitizer.sanitize(options) + + assertEquals("", sanitizedOptions.user.id) + assertNotSame(options, sanitizedOptions) + } + + private fun createOptions( + rpId: String, + userId: String, + ): PasskeyAttestationOptions { + return PasskeyAttestationOptions( + relyingParty = PasskeyAttestationOptions.PublicKeyCredentialRpEntity( + id = rpId, + name = "RP", + ), + user = PasskeyAttestationOptions.PublicKeyCredentialUserEntity( + id = userId, + name = "User", + displayName = "User", + ), + challenge = "challenge", + pubKeyCredParams = emptyList(), + authenticatorSelection = PasskeyAttestationOptions.AuthenticatorSelectionCriteria(), + ) + } +} From b3a5b2cfa4a63e991ab9f863dce98686179e2ca0 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 31 Oct 2025 15:15:03 -0400 Subject: [PATCH 02/10] Fix typo in `sanitizer` package name This commit corrects a "santizer" to "sanitizer" typo in the package name within the `com.x8bit.bitwarden.data.credentials` path. The primary change involves renaming the directory `santizer` to `sanitizer`. All corresponding import statements and package declarations that referenced the misspelled path have been updated accordingly across multiple files. --- .../bitwarden/data/credentials/di/CredentialProviderModule.kt | 4 ++-- .../credentials/manager/BitwardenCredentialManagerImpl.kt | 2 +- .../PasskeyAttestationOptionSanitizerImpl.kt | 2 +- .../PasskeyAttestationOptionsSanitizer.kt | 2 +- .../credentials/manager/BitwardenCredentialManagerTest.kt | 2 +- .../PasskeyAttestationOptionSanitizerTest.kt | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/{santizer => sanitizer}/PasskeyAttestationOptionSanitizerImpl.kt (94%) rename app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/{santizer => sanitizer}/PasskeyAttestationOptionsSanitizer.kt (88%) rename app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/{santizer => sanitizer}/PasskeyAttestationOptionSanitizerTest.kt (98%) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt index 69142e05635..f0c93f6a5df 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt @@ -22,8 +22,8 @@ import com.x8bit.bitwarden.data.credentials.processor.CredentialProviderProcesso import com.x8bit.bitwarden.data.credentials.processor.CredentialProviderProcessorImpl import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepository import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepositoryImpl -import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionSanitizerImpl -import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionsSanitizer +import com.x8bit.bitwarden.data.credentials.sanitizer.PasskeyAttestationOptionSanitizerImpl +import com.x8bit.bitwarden.data.credentials.sanitizer.PasskeyAttestationOptionsSanitizer import com.x8bit.bitwarden.data.platform.manager.AssetManager import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt index 04c1bba1ee7..9c7e8cde98e 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt @@ -33,7 +33,7 @@ import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest import com.x8bit.bitwarden.data.credentials.model.PasskeyAssertionOptions import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions import com.x8bit.bitwarden.data.credentials.model.UserVerificationRequirement -import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionsSanitizer +import com.x8bit.bitwarden.data.credentials.sanitizer.PasskeyAttestationOptionsSanitizer import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager import com.x8bit.bitwarden.data.platform.util.getAppOrigin import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerImpl.kt similarity index 94% rename from app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerImpl.kt rename to app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerImpl.kt index b51e1932197..9654bf01dec 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerImpl.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.data.credentials.santizer +package com.x8bit.bitwarden.data.credentials.sanitizer import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionsSanitizer.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt similarity index 88% rename from app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionsSanitizer.kt rename to app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt index 343f7cbeeb6..72615a46eb2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionsSanitizer.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.data.credentials.santizer +package com.x8bit.bitwarden.data.credentials.sanitizer import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt index de53c8e1ed7..31c46e162a1 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt @@ -35,7 +35,7 @@ import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest import com.x8bit.bitwarden.data.credentials.model.PasskeyAssertionOptions import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions import com.x8bit.bitwarden.data.credentials.model.UserVerificationRequirement -import com.x8bit.bitwarden.data.credentials.santizer.PasskeyAttestationOptionsSanitizer +import com.x8bit.bitwarden.data.credentials.sanitizer.PasskeyAttestationOptionsSanitizer import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerTest.kt similarity index 98% rename from app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerTest.kt rename to app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerTest.kt index fa9ba9e3351..f32fd835fdd 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/santizer/PasskeyAttestationOptionSanitizerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerTest.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.data.credentials.santizer +package com.x8bit.bitwarden.data.credentials.sanitizer import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions import org.junit.jupiter.api.Assertions.assertEquals From 6544a463207cf41675c9007ce31b86923460a33a Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 31 Oct 2025 15:22:09 -0400 Subject: [PATCH 03/10] Use `apply` instead of `also` for object sanitization --- .../data/credentials/manager/BitwardenCredentialManagerImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt index 9c7e8cde98e..c7229a0bb08 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt @@ -365,7 +365,7 @@ class BitwardenCredentialManagerImpl( val requestJson = getPasskeyAttestationOptionsOrNull(createPublicKeyCredentialRequest.requestJson) - ?.also { passkeyAttestationOptionsSanitizer.sanitize(it) } + ?.apply { passkeyAttestationOptionsSanitizer.sanitize(this) } ?.runCatching { json.encodeToString(this) } ?.fold( onSuccess = { it }, From 1f03aad5d0f0eeab1b9b4333f553a2da2ca954f9 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 31 Oct 2025 15:28:26 -0400 Subject: [PATCH 04/10] Rename PasskeyAttestationOptionSanitizerImpl to PasskeyAttestationOptionsSanitizerImpl This commit renames `PasskeyAttestationOptionSanitizerImpl` to `PasskeyAttestationOptionsSanitizerImpl` to align with the plural form used in the `PasskeyAttestationOptionsSanitizer` interface it implements. --- .../bitwarden/data/credentials/di/CredentialProviderModule.kt | 4 ++-- ...tizerImpl.kt => PasskeyAttestationOptionsSanitizerImpl.kt} | 2 +- .../sanitizer/PasskeyAttestationOptionSanitizerTest.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/{PasskeyAttestationOptionSanitizerImpl.kt => PasskeyAttestationOptionsSanitizerImpl.kt} (91%) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt index f0c93f6a5df..5b77bcf7888 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt @@ -22,8 +22,8 @@ import com.x8bit.bitwarden.data.credentials.processor.CredentialProviderProcesso import com.x8bit.bitwarden.data.credentials.processor.CredentialProviderProcessorImpl import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepository import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepositoryImpl -import com.x8bit.bitwarden.data.credentials.sanitizer.PasskeyAttestationOptionSanitizerImpl import com.x8bit.bitwarden.data.credentials.sanitizer.PasskeyAttestationOptionsSanitizer +import com.x8bit.bitwarden.data.credentials.sanitizer.PasskeyAttestationOptionsSanitizerImpl import com.x8bit.bitwarden.data.platform.manager.AssetManager import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager @@ -147,5 +147,5 @@ object CredentialProviderModule { @Provides @Singleton fun providePasskeyAttestationOptionsSanitizer(): PasskeyAttestationOptionsSanitizer = - PasskeyAttestationOptionSanitizerImpl + PasskeyAttestationOptionsSanitizerImpl } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt similarity index 91% rename from app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerImpl.kt rename to app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt index 9654bf01dec..51200936c24 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt @@ -5,7 +5,7 @@ import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions /** * Default implementation of [PasskeyAttestationOptionsSanitizer]. */ -object PasskeyAttestationOptionSanitizerImpl : PasskeyAttestationOptionsSanitizer { +object PasskeyAttestationOptionsSanitizerImpl : PasskeyAttestationOptionsSanitizer { override fun sanitize(options: PasskeyAttestationOptions): PasskeyAttestationOptions { // The AliExpress Android app (com.alibaba.aliexpresshd) incorrectly appends a newline // to the user.id field when creating a passkey. This causes the operation to fail diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerTest.kt index f32fd835fdd..6d02d9022b8 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionSanitizerTest.kt @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test class PasskeyAttestationOptionSanitizerTest { private val sanitizer: PasskeyAttestationOptionsSanitizer = - PasskeyAttestationOptionSanitizerImpl + PasskeyAttestationOptionsSanitizerImpl @Test fun `Sanitization on matching RP ID and user ID with newline`() { From 52a0b550626f1689cd7ab24e56b32a7b6b631aaf Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 31 Oct 2025 15:28:44 -0400 Subject: [PATCH 05/10] Correct AliExpress Relying Party ID check for passkey creation This commit refines the workaround for a bug in the AliExpress passkey creation process. The previous implementation incorrectly used `contains` to check the Relying Party (RP) ID, which could lead to false positives if another RP ID happened to contain the AliExpress ID as a substring. This has been corrected to use a strict equality check (`==`) to ensure the workaround is applied only to AliExpress. This change makes the fix more precise and robust. --- .../sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt index 51200936c24..f0ecd9008a0 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt @@ -11,7 +11,7 @@ object PasskeyAttestationOptionsSanitizerImpl : PasskeyAttestationOptionsSanitiz // to the user.id field when creating a passkey. This causes the operation to fail // downstream. As a workaround, we detect this specific scenario, trim the newline, and // re-serialize the JSON request. - return if (options.relyingParty.id.contains(ALIEXPRESS_RP_ID) && + return if (options.relyingParty.id == ALIEXPRESS_RP_ID && options.user.id.endsWith("\n") ) { options.copy( From 50cfa5720e34c0c9c27454f8e23d5ace9cec4897 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 31 Oct 2025 15:29:44 -0400 Subject: [PATCH 06/10] Refactor PasskeyAttestationOptionsSanitizer comments for clarity This commit improves the KDoc comments for the `PasskeyAttestationOptionsSanitizer` interface and its `sanitize` method. The goal is to provide a more detailed and clearer explanation of their purpose, particularly in the context of handling passkey options received from a server. The updated documentation clarifies that sanitization involves ensuring the options conform to expected formats, such as Base64 URL decoding fields and setting defaults, before they are used by the client. It also specifies that the `sanitize` method returns a new, sanitized instance. --- .../sanitizer/PasskeyAttestationOptionsSanitizer.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt index 72615a46eb2..18c5fbd5f08 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt @@ -3,14 +3,20 @@ package com.x8bit.bitwarden.data.credentials.sanitizer import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions /** - * Interface for sanitizing [PasskeyAttestationOptions]. + * Defines a contract for sanitizing [PasskeyAttestationOptions] received from a server. + * + * Sanitization ensures that the options conform to expected formats and values before being + * used by the client to create a passkey credential. This can involve tasks like Base64 URL + * decoding certain fields and setting appropriate default values. */ interface PasskeyAttestationOptionsSanitizer { /** - * Sanitizes the given [options] in preparation for processing. + * Sanitizes the given [PasskeyAttestationOptions] in preparation for use in the + * passkey creation process. * * @param options The [PasskeyAttestationOptions] to sanitize. + * @return A new, sanitized instance of [PasskeyAttestationOptions]. */ fun sanitize(options: PasskeyAttestationOptions): PasskeyAttestationOptions } From dd92be6c8ebc60ed7ad6ccc6737e99cd5598ead6 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Fri, 31 Oct 2025 15:31:31 -0400 Subject: [PATCH 07/10] Trim trailing newlines from passkey user id This commit updates the `PasskeyAttestationOptionsSanitizer` to only trim trailing newlines from the user id. Previously, it would trim both leading and trailing whitespace, which could incorrectly alter user ids that legitimately start with whitespace characters. The `trim()` function has been replaced with `trimEnd()` to correct this behavior. --- .../sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt index f0ecd9008a0..09225bffe37 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt @@ -15,7 +15,7 @@ object PasskeyAttestationOptionsSanitizerImpl : PasskeyAttestationOptionsSanitiz options.user.id.endsWith("\n") ) { options.copy( - user = options.user.copy(id = options.user.id.trim()), + user = options.user.copy(id = options.user.id.trimEnd()), ) } else { options From 4c5023da842e0965e28c46f1b5348982817af35b Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Mon, 3 Nov 2025 09:37:35 -0500 Subject: [PATCH 08/10] Address Claude review comments Corrected `sanitize` implementation Added test coverage for `sanitize` implementation. Updated documentation to better reflect usage. Only trim newlines from end of options. --- .../manager/BitwardenCredentialManagerImpl.kt | 2 +- .../PasskeyAttestationOptionsSanitizer.kt | 8 ++-- .../PasskeyAttestationOptionsSanitizerImpl.kt | 2 +- .../manager/BitwardenCredentialManagerTest.kt | 47 +++++++++++++++++++ 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt index c7229a0bb08..464efc5747c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt @@ -365,7 +365,7 @@ class BitwardenCredentialManagerImpl( val requestJson = getPasskeyAttestationOptionsOrNull(createPublicKeyCredentialRequest.requestJson) - ?.apply { passkeyAttestationOptionsSanitizer.sanitize(this) } + ?.let { passkeyAttestationOptionsSanitizer.sanitize(options = it) } ?.runCatching { json.encodeToString(this) } ?.fold( onSuccess = { it }, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt index 18c5fbd5f08..984ceb62833 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizer.kt @@ -3,11 +3,11 @@ package com.x8bit.bitwarden.data.credentials.sanitizer import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions /** - * Defines a contract for sanitizing [PasskeyAttestationOptions] received from a server. + * Defines a contract for sanitizing [PasskeyAttestationOptions] received from applications. * - * Sanitization ensures that the options conform to expected formats and values before being - * used by the client to create a passkey credential. This can involve tasks like Base64 URL - * decoding certain fields and setting appropriate default values. + * Sanitization applies workarounds for known issues with specific applications' + * passkey implementations, ensuring the options are in the correct format before + * being used to create a passkey credential. */ interface PasskeyAttestationOptionsSanitizer { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt index 09225bffe37..a608965f47d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/sanitizer/PasskeyAttestationOptionsSanitizerImpl.kt @@ -15,7 +15,7 @@ object PasskeyAttestationOptionsSanitizerImpl : PasskeyAttestationOptionsSanitiz options.user.id.endsWith("\n") ) { options.copy( - user = options.user.copy(id = options.user.id.trimEnd()), + user = options.user.copy(id = options.user.id.trimEnd('\n')), ) } else { options diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt index 31c46e162a1..7bfcf39029e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerTest.kt @@ -434,6 +434,53 @@ class BitwardenCredentialManagerTest { ) } + @Test + fun `registerFido2Credential should sanitize attestation options before registration`() = + runTest { + val originalOptions = createMockPasskeyAttestationOptions(number = 1) + val sanitizedOptions = originalOptions.copy( + user = originalOptions.user.copy(id = "sanitized-user-id"), + ) + + every { mockCallingAppInfo.signingInfo } returns mockSigningInfo + every { Base64.encodeToString(any(), any()) } returns DEFAULT_APP_SIGNATURE + every { + json.encodeToString(any(), any()) + } returns "" + every { + json.decodeFromStringOrNull(any()) + } returns originalOptions + every { + mockPasskeyAttestationOptionsSanitizer.sanitize(originalOptions) + } returns sanitizedOptions + every { + json.encodeToString(sanitizedOptions) + } returns "sanitized-json" + every { mockCreatePublicKeyCredentialRequest.origin } returns DEFAULT_WEB_ORIGIN.v1 + val mockRegistrationResponse = createMockPublicKeyAttestationResponse(number = 1) + val requestCaptureSlot = slot() + coEvery { + mockVaultSdkSource.registerFido2Credential( + request = capture(requestCaptureSlot), + fido2CredentialStore = any(), + ) + } returns mockRegistrationResponse.asSuccess() + + bitwardenCredentialManager.registerFido2Credential( + userId = "mockUserId", + createPublicKeyCredentialRequest = mockCreatePublicKeyCredentialRequest, + selectedCipherView = createMockCipherView(number = 1), + callingAppInfo = mockCallingAppInfo, + ) + + verify { mockPasskeyAttestationOptionsSanitizer.sanitize(originalOptions) } + verify { json.encodeToString(sanitizedOptions) } + assertEquals( + """{"publicKey": sanitized-json}""", + requestCaptureSlot.captured.requestJson, + ) + } + @Test fun `registerFido2Credential should return Error when origin is null`() = runTest { every { Base64.encodeToString(any(), any()) } returns DEFAULT_APP_SIGNATURE From 7fa03d6a7f83ab55e273acd384c4413a20359782 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Mon, 3 Nov 2025 11:54:09 -0500 Subject: [PATCH 09/10] Remove redundant blank line --- .../data/credentials/manager/BitwardenCredentialManagerImpl.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt index 464efc5747c..2017c5645bf 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt @@ -362,7 +362,6 @@ class BitwardenCredentialManagerImpl( clientData: ClientData, callingPackageName: String, ): Fido2RegisterCredentialResult { - val requestJson = getPasskeyAttestationOptionsOrNull(createPublicKeyCredentialRequest.requestJson) ?.let { passkeyAttestationOptionsSanitizer.sanitize(options = it) } From df37505c478f1cc6489bde145119edda88c39b09 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Mon, 3 Nov 2025 12:43:43 -0500 Subject: [PATCH 10/10] Log passkey attestation option sanitization failures --- .../credentials/manager/BitwardenCredentialManagerImpl.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt index 2017c5645bf..6172ff6926a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt @@ -368,7 +368,10 @@ class BitwardenCredentialManagerImpl( ?.runCatching { json.encodeToString(this) } ?.fold( onSuccess = { it }, - onFailure = { null }, + onFailure = { + Timber.e(it, "Failed to sanitize passkey attestation options.") + null + }, ) ?: return Fido2RegisterCredentialResult.Error.InternalError