[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,
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) },

View File

@ -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

View File

@ -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(