[PM-24258] Building a specific Fido2AttestationResponse to work with Binance (#5986)

This commit is contained in:
aj-rosado 2025-10-10 15:07:52 +01:00 committed by GitHub
parent 2d2b740ae1
commit a7bbb81b31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 63 additions and 16 deletions

View File

@ -323,6 +323,7 @@ class BitwardenCredentialManagerImpl(
createPublicKeyCredentialRequest = createPublicKeyCredentialRequest, createPublicKeyCredentialRequest = createPublicKeyCredentialRequest,
selectedCipherView = selectedCipherView, selectedCipherView = selectedCipherView,
clientData = clientData, clientData = clientData,
callingPackageName = callingAppInfo.packageName,
) )
} }
@ -347,6 +348,7 @@ class BitwardenCredentialManagerImpl(
createPublicKeyCredentialRequest = createPublicKeyCredentialRequest, createPublicKeyCredentialRequest = createPublicKeyCredentialRequest,
selectedCipherView = selectedCipherView, selectedCipherView = selectedCipherView,
clientData = clientData, clientData = clientData,
callingPackageName = callingAppInfo.packageName,
) )
} }
@ -356,6 +358,7 @@ class BitwardenCredentialManagerImpl(
createPublicKeyCredentialRequest: CreatePublicKeyCredentialRequest, createPublicKeyCredentialRequest: CreatePublicKeyCredentialRequest,
selectedCipherView: CipherView, selectedCipherView: CipherView,
clientData: ClientData, clientData: ClientData,
callingPackageName: String,
): Fido2RegisterCredentialResult = vaultSdkSource ): Fido2RegisterCredentialResult = vaultSdkSource
.registerFido2Credential( .registerFido2Credential(
request = RegisterFido2CredentialRequest( request = RegisterFido2CredentialRequest(
@ -370,7 +373,9 @@ class BitwardenCredentialManagerImpl(
), ),
fido2CredentialStore = this, fido2CredentialStore = this,
) )
.map { it.toAndroidAttestationResponse() } .map {
it.toAndroidAttestationResponse(callingPackageName = callingPackageName)
}
.mapCatching { json.encodeToString(it) } .mapCatching { json.encodeToString(it) }
.fold( .fold(
onSuccess = { Fido2RegisterCredentialResult.Success(it) }, onSuccess = { Fido2RegisterCredentialResult.Success(it) },

View File

@ -4,24 +4,33 @@ import android.util.Base64
import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse
import com.x8bit.bitwarden.data.credentials.model.Fido2AttestationResponse 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 * Converts the SDK attestation response to a [Fido2AttestationResponse] that can be serialized into
* the expected system JSON. * the expected system JSON.
*/ */
@Suppress("MaxLineLength") fun PublicKeyCredentialAuthenticatorAttestationResponse.toAndroidAttestationResponse(
fun PublicKeyCredentialAuthenticatorAttestationResponse.toAndroidAttestationResponse(): Fido2AttestationResponse = callingPackageName: String?,
Fido2AttestationResponse( ): Fido2AttestationResponse {
id = id, val registrationResponse = Fido2AttestationResponse.RegistrationResponse(
type = ty,
rawId = rawId.base64EncodeForFido2Response(),
response = Fido2AttestationResponse.RegistrationResponse(
clientDataJson = response.clientDataJson.base64EncodeForFido2Response(), clientDataJson = response.clientDataJson.base64EncodeForFido2Response(),
attestationObject = response.attestationObject.base64EncodeForFido2Response(), attestationObject = response.attestationObject.base64EncodeForFido2Response(),
transports = response.transports, 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, publicKeyAlgorithm = response.publicKeyAlgorithm,
publicKey = response.publicKey?.base64EncodeForFido2Response(), publicKey = response.publicKey?.base64EncodeForFido2Response(),
authenticatorData = response.authenticatorData.base64EncodeForFido2Response(), authenticatorData = response.authenticatorData.base64EncodeForFido2Response(),
), )
return Fido2AttestationResponse(
id = id,
type = ty,
rawId = rawId.base64EncodeForFido2Response(),
response = registrationResponse,
clientExtensionResults = clientExtensionResults clientExtensionResults = clientExtensionResults
.credProps .credProps
?.rk ?.rk
@ -34,6 +43,7 @@ fun PublicKeyCredentialAuthenticatorAttestationResponse.toAndroidAttestationResp
} ?: Fido2AttestationResponse.ClientExtensionResults(), } ?: Fido2AttestationResponse.ClientExtensionResults(),
authenticatorAttachment = authenticatorAttachment, authenticatorAttachment = authenticatorAttachment,
) )
}
/** /**
* Attestation response fields of type [ByteArray] must be base 64 encoded in a url safe format * Attestation response fields of type [ByteArray] must be base 64 encoded in a url safe format

View File

@ -33,7 +33,7 @@ class PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest {
@Test @Test
fun `authenticatorAttachment should be null when SDK value is null`() { fun `authenticatorAttachment should be null when SDK value is null`() {
val mockSdkResponse = createMockSdkAttestationResponse(number = 1) val mockSdkResponse = createMockSdkAttestationResponse(number = 1)
val result = mockSdkResponse.toAndroidAttestationResponse() val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "")
assertNull(result.authenticatorAttachment) assertNull(result.authenticatorAttachment)
} }
@ -43,14 +43,14 @@ class PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest {
number = 1, number = 1,
authenticatorAttachment = "mockAuthenticatorAttachment", authenticatorAttachment = "mockAuthenticatorAttachment",
) )
val result = mockSdkResponse.toAndroidAttestationResponse() val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "")
assertNotNull(result.authenticatorAttachment) assertNotNull(result.authenticatorAttachment)
} }
@Test @Test
fun `clientExtensionResults should be populated when SDK value is null`() { fun `clientExtensionResults should be populated when SDK value is null`() {
val mockSdkResponse = createMockSdkAttestationResponse(number = 1) val mockSdkResponse = createMockSdkAttestationResponse(number = 1)
val result = mockSdkResponse.toAndroidAttestationResponse() val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "")
assertNotNull(result.clientExtensionResults) assertNotNull(result.clientExtensionResults)
} }
@ -63,9 +63,41 @@ class PublicKeyCredentialAuthenticatorAttestationResponseExtensionsTest {
authenticatorDisplayName = null, authenticatorDisplayName = null,
), ),
) )
val result = mockSdkResponse.toAndroidAttestationResponse() val result = mockSdkResponse.toAndroidAttestationResponse(callingPackageName = "")
assert(result.clientExtensionResults.credentialProperties?.residentKey ?: false) 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( private fun createMockSdkAttestationResponse(