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 157079cbba..597a1a6417 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 @@ -323,6 +323,7 @@ class BitwardenCredentialManagerImpl( createPublicKeyCredentialRequest = createPublicKeyCredentialRequest, selectedCipherView = selectedCipherView, clientData = clientData, + callingPackageName = callingAppInfo.packageName, ) } @@ -347,6 +348,7 @@ class BitwardenCredentialManagerImpl( createPublicKeyCredentialRequest = createPublicKeyCredentialRequest, selectedCipherView = selectedCipherView, clientData = clientData, + callingPackageName = callingAppInfo.packageName, ) } @@ -356,6 +358,7 @@ class BitwardenCredentialManagerImpl( createPublicKeyCredentialRequest: CreatePublicKeyCredentialRequest, selectedCipherView: CipherView, clientData: ClientData, + callingPackageName: String, ): Fido2RegisterCredentialResult = vaultSdkSource .registerFido2Credential( request = RegisterFido2CredentialRequest( @@ -370,7 +373,9 @@ class BitwardenCredentialManagerImpl( ), fido2CredentialStore = this, ) - .map { it.toAndroidAttestationResponse() } + .map { + it.toAndroidAttestationResponse(callingPackageName = callingPackageName) + } .mapCatching { json.encodeToString(it) } .fold( onSuccess = { Fido2RegisterCredentialResult.Success(it) }, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt index edeaefbbda..f7c24592f2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensions.kt @@ -4,24 +4,33 @@ import android.util.Base64 import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse import com.x8bit.bitwarden.data.credentials.model.Fido2AttestationResponse +private const val BINANCE_PACKAGE_NAME = "com.binance.dev" + /** * Converts the SDK attestation response to a [Fido2AttestationResponse] that can be serialized into * the expected system JSON. */ -@Suppress("MaxLineLength") -fun PublicKeyCredentialAuthenticatorAttestationResponse.toAndroidAttestationResponse(): Fido2AttestationResponse = - Fido2AttestationResponse( +fun PublicKeyCredentialAuthenticatorAttestationResponse.toAndroidAttestationResponse( + callingPackageName: String?, +): Fido2AttestationResponse { + val registrationResponse = Fido2AttestationResponse.RegistrationResponse( + clientDataJson = response.clientDataJson.base64EncodeForFido2Response(), + attestationObject = response.attestationObject.base64EncodeForFido2Response(), + transports = response.transports.takeUnless { + // Setting transports as null, otherwise Binance labels the passkey broken + // PM-26734 remove this flow if not necessary anymore + callingPackageName == BINANCE_PACKAGE_NAME + }, + publicKeyAlgorithm = response.publicKeyAlgorithm, + publicKey = response.publicKey?.base64EncodeForFido2Response(), + authenticatorData = response.authenticatorData.base64EncodeForFido2Response(), + ) + + return Fido2AttestationResponse( id = id, type = ty, rawId = rawId.base64EncodeForFido2Response(), - response = Fido2AttestationResponse.RegistrationResponse( - clientDataJson = response.clientDataJson.base64EncodeForFido2Response(), - attestationObject = response.attestationObject.base64EncodeForFido2Response(), - transports = response.transports, - publicKeyAlgorithm = response.publicKeyAlgorithm, - publicKey = response.publicKey?.base64EncodeForFido2Response(), - authenticatorData = response.authenticatorData.base64EncodeForFido2Response(), - ), + response = registrationResponse, clientExtensionResults = clientExtensionResults .credProps ?.rk @@ -34,6 +43,7 @@ fun PublicKeyCredentialAuthenticatorAttestationResponse.toAndroidAttestationResp } ?: Fido2AttestationResponse.ClientExtensionResults(), authenticatorAttachment = authenticatorAttachment, ) +} /** * Attestation response fields of type [ByteArray] must be base 64 encoded in a url safe format diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest.kt index b092900c5b..82f4861fec 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/util/PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest.kt @@ -33,7 +33,7 @@ class PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest { @Test fun `authenticatorAttachment should be null when SDK value is null`() { val mockSdkResponse = createMockSdkAttestationResponse(number = 1) - val result = mockSdkResponse.toAndroidAttestationResponse() + val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "") assertNull(result.authenticatorAttachment) } @@ -43,14 +43,14 @@ class PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest { number = 1, authenticatorAttachment = "mockAuthenticatorAttachment", ) - val result = mockSdkResponse.toAndroidAttestationResponse() + val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "") assertNotNull(result.authenticatorAttachment) } @Test fun `clientExtensionResults should be populated when SDK value is null`() { val mockSdkResponse = createMockSdkAttestationResponse(number = 1) - val result = mockSdkResponse.toAndroidAttestationResponse() + val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "") assertNotNull(result.clientExtensionResults) } @@ -63,9 +63,41 @@ class PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest { authenticatorDisplayName = null, ), ) - val result = mockSdkResponse.toAndroidAttestationResponse() + val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "") assert(result.clientExtensionResults.credentialProperties?.residentKey ?: false) } + + @Suppress("MaxLineLength") + @Test + fun `toAndroidAttestationResponse should build specific response when package name is Binance`() { + val binancePackageName = "com.binance.dev" + val mockSdkResponse = createMockSdkAttestationResponse(number = 1) + + val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = binancePackageName) + + assertNull(result.response.transports) + assertNotNull(result.response.publicKey) + assertNotNull(result.response.publicKeyAlgorithm) + assertNotNull(result.response.authenticatorData) + assertNotNull(result.response.clientDataJson) + assertNotNull(result.response.attestationObject) + } + + @Suppress("MaxLineLength") + @Test + fun `toAndroidAttestationResponse should build full response for any package name other than Binance`() { + val otherPackageName = "com.any.app" + val mockSdkResponse = createMockSdkAttestationResponse(number = 1) + + val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = otherPackageName) + + assertNotNull(result.response.transports) + assertNotNull(result.response.publicKey) + assertNotNull(result.response.publicKeyAlgorithm) + assertNotNull(result.response.authenticatorData) + assertNotNull(result.response.clientDataJson) + assertNotNull(result.response.attestationObject) + } } private fun createMockSdkAttestationResponse(