[PM-12279] Update SDK reference and use Origin.Android on Fido2Credential (#3975)

This commit is contained in:
aj-rosado 2024-10-01 15:07:30 +02:00 committed by GitHub
parent 10bbab971f
commit b3e885bcb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 128 additions and 17 deletions

View file

@ -2,6 +2,8 @@ package com.x8bit.bitwarden.data.autofill.fido2.manager
import androidx.credentials.provider.CallingAppInfo import androidx.credentials.provider.CallingAppInfo
import com.bitwarden.fido.ClientData import com.bitwarden.fido.ClientData
import com.bitwarden.fido.Origin
import com.bitwarden.fido.UnverifiedAssetLink
import com.bitwarden.sdk.Fido2CredentialStore import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.vault.CipherView import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
@ -24,6 +26,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2Cred
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidAttestationResponse import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidAttestationResponse
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidFido2PublicKeyCredential import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidFido2PublicKeyCredential
import com.x8bit.bitwarden.ui.platform.base.util.toHostOrPathOrNull
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -65,11 +68,23 @@ class Fido2CredentialManagerImpl(
.packageName, .packageName,
) )
} }
val origin = fido2CredentialRequest val assetLinkUrl = fido2CredentialRequest
.origin .origin
?: getOriginUrlFromAttestationOptionsOrNull(fido2CredentialRequest.requestJson) ?: getOriginUrlFromAttestationOptionsOrNull(fido2CredentialRequest.requestJson)
?: return Fido2RegisterCredentialResult.Error ?: return Fido2RegisterCredentialResult.Error
val origin = Origin.Android(
UnverifiedAssetLink(
packageName = fido2CredentialRequest.packageName,
sha256CertFingerprint = fido2CredentialRequest
.callingAppInfo
.getSignatureFingerprintAsHexString()
?: return Fido2RegisterCredentialResult.Error,
host = assetLinkUrl.toHostOrPathOrNull()
?: return Fido2RegisterCredentialResult.Error,
assetLinkUrl = assetLinkUrl,
),
)
return vaultSdkSource return vaultSdkSource
.registerFido2Credential( .registerFido2Credential(
request = RegisterFido2CredentialRequest( request = RegisterFido2CredentialRequest(
@ -157,7 +172,16 @@ class Fido2CredentialManagerImpl(
.authenticateFido2Credential( .authenticateFido2Credential(
request = AuthenticateFido2CredentialRequest( request = AuthenticateFido2CredentialRequest(
userId = userId, userId = userId,
origin = origin, origin = Origin.Android(
UnverifiedAssetLink(
callingAppInfo.packageName,
callingAppInfo.getSignatureFingerprintAsHexString()
?: return Fido2CredentialAssertionResult.Error,
origin.toHostOrPathOrNull()
?: return Fido2CredentialAssertionResult.Error,
origin,
),
),
requestJson = """{"publicKey": ${request.requestJson}}""", requestJson = """{"publicKey": ${request.requestJson}}""",
clientData = clientData, clientData = clientData,
selectedCipherView = selectedCipherView, selectedCipherView = selectedCipherView,

View file

@ -1,14 +1,14 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk.model package com.x8bit.bitwarden.data.vault.datasource.sdk.model
import com.bitwarden.fido.ClientData import com.bitwarden.fido.ClientData
import com.bitwarden.fido.Origin
import com.bitwarden.vault.CipherView import com.bitwarden.vault.CipherView
/** /**
* Models a FIDO 2 authentication request to the Bitwarden SDK. * Models a FIDO 2 authentication request to the Bitwarden SDK.
* *
* @param userId User whom the credential is being authenticated for. * @param userId User whom the credential is being authenticated for.
* @param origin Origin of the Relying Party. This can either be a Relying Party's URL or their * @param origin Origin of the Relying Party WebAuthn Request.
* application fingerprint.
* @param requestJson Authentication request JSON received from the OS. * @param requestJson Authentication request JSON received from the OS.
* @param clientData Metadata containing either privileged application certificate hash or Android * @param clientData Metadata containing either privileged application certificate hash or Android
* package name of the Relying Party. * package name of the Relying Party.
@ -18,7 +18,7 @@ import com.bitwarden.vault.CipherView
*/ */
data class AuthenticateFido2CredentialRequest( data class AuthenticateFido2CredentialRequest(
val userId: String, val userId: String,
val origin: String, val origin: Origin,
val requestJson: String, val requestJson: String,
val clientData: ClientData, val clientData: ClientData,
val selectedCipherView: CipherView, val selectedCipherView: CipherView,

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk.model package com.x8bit.bitwarden.data.vault.datasource.sdk.model
import com.bitwarden.fido.ClientData import com.bitwarden.fido.ClientData
import com.bitwarden.fido.Origin
import com.bitwarden.vault.CipherView import com.bitwarden.vault.CipherView
/** /**
@ -18,7 +19,7 @@ import com.bitwarden.vault.CipherView
*/ */
data class RegisterFido2CredentialRequest( data class RegisterFido2CredentialRequest(
val userId: String, val userId: String,
val origin: String, val origin: Origin,
val requestJson: String, val requestJson: String,
val clientData: ClientData, val clientData: ClientData,
val selectedCipherView: CipherView, val selectedCipherView: CipherView,

View file

@ -5,7 +5,9 @@ import android.content.pm.SigningInfo
import android.util.Base64 import android.util.Base64
import androidx.credentials.provider.CallingAppInfo import androidx.credentials.provider.CallingAppInfo
import com.bitwarden.fido.ClientData import com.bitwarden.fido.ClientData
import com.bitwarden.fido.Origin
import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse
import com.bitwarden.fido.UnverifiedAssetLink
import com.bitwarden.sdk.Fido2CredentialStore import com.bitwarden.sdk.Fido2CredentialStore
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
@ -531,8 +533,58 @@ class Fido2CredentialManagerTest {
selectedCipherView = createMockCipherView(number = 1), selectedCipherView = createMockCipherView(number = 1),
) )
assertTrue( assertEquals(
result is Fido2RegisterCredentialResult.Error, Fido2RegisterCredentialResult.Error,
result,
)
}
@Suppress("MaxLineLength")
@Test
fun `registerFido2Credential should return Error when getSignatureFingerprintAsHexString is null`() =
runTest {
val mockSigningInfo = mockk<SigningInfo> {
every { hasMultipleSigners() } returns true
}
val mockFido2CredentialRequest = createMockFido2CredentialRequest(
number = 1,
signingInfo = mockSigningInfo,
)
val result = fido2CredentialManager.registerFido2Credential(
userId = "mockUserId",
fido2CredentialRequest = mockFido2CredentialRequest,
selectedCipherView = createMockCipherView(number = 1),
)
assertEquals(
Fido2RegisterCredentialResult.Error,
result,
)
}
@Test
fun `registerFido2Credential should return Error when toHostOrPathOrNull is null`() =
runTest {
val mockSigningInfo = mockk<SigningInfo> {
every { apkContentsSigners } returns arrayOf(Signature(DEFAULT_APP_SIGNATURE))
every { hasMultipleSigners() } returns false
}
val mockFido2CredentialRequest = createMockFido2CredentialRequest(
number = 1,
origin = "illegal empty spaces",
signingInfo = mockSigningInfo,
)
val result = fido2CredentialManager.registerFido2Credential(
userId = "mockUserId",
fido2CredentialRequest = mockFido2CredentialRequest,
selectedCipherView = createMockCipherView(number = 1),
)
assertEquals(
Fido2RegisterCredentialResult.Error,
result,
) )
} }
@ -573,8 +625,9 @@ class Fido2CredentialManagerTest {
selectedCipherView = createMockCipherView(number = 1), selectedCipherView = createMockCipherView(number = 1),
) )
assertTrue( assertEquals(
result is Fido2RegisterCredentialResult.Error, Fido2RegisterCredentialResult.Error,
result,
) )
} }
@ -709,7 +762,14 @@ class Fido2CredentialManagerTest {
assertEquals( assertEquals(
AuthenticateFido2CredentialRequest( AuthenticateFido2CredentialRequest(
userId = "activeUserId", userId = "activeUserId",
origin = mockRequest.origin!!, origin = Origin.Android(
UnverifiedAssetLink(
packageName = DEFAULT_PACKAGE_NAME,
sha256CertFingerprint = DEFAULT_CERT_FINGERPRINT,
host = DEFAULT_HOST,
assetLinkUrl = mockRequest.origin!!,
),
),
requestJson = """{"publicKey": ${mockRequest.requestJson}}""", requestJson = """{"publicKey": ${mockRequest.requestJson}}""",
clientData = ClientData.DefaultWithExtraData( clientData = ClientData.DefaultWithExtraData(
androidPackageName = "android:apk-key-hash:$DEFAULT_APP_SIGNATURE", androidPackageName = "android:apk-key-hash:$DEFAULT_APP_SIGNATURE",
@ -756,7 +816,14 @@ class Fido2CredentialManagerTest {
} }
assertEquals( assertEquals(
Origin.Android(
UnverifiedAssetLink(
DEFAULT_PACKAGE_NAME,
DEFAULT_CERT_FINGERPRINT,
mockAssertionOptions.relyingPartyId!!,
"https://${mockAssertionOptions.relyingPartyId}", "https://${mockAssertionOptions.relyingPartyId}",
),
),
requestCaptureSlot.captured.origin, requestCaptureSlot.captured.origin,
) )
} }
@ -942,9 +1009,17 @@ class Fido2CredentialManagerTest {
} }
private const val DEFAULT_PACKAGE_NAME = "com.x8bit.bitwarden" private const val DEFAULT_PACKAGE_NAME = "com.x8bit.bitwarden"
private const val DEFAULT_ORIGIN = "bitwarden.com"
private const val DEFAULT_APP_SIGNATURE = "0987654321ABCDEF" private const val DEFAULT_APP_SIGNATURE = "0987654321ABCDEF"
private const val DEFAULT_CERT_FINGERPRINT = "30:39:38:37:36:35:34:33:32:31:41:42:43:44:45:46" private const val DEFAULT_CERT_FINGERPRINT = "30:39:38:37:36:35:34:33:32:31:41:42:43:44:45:46"
private const val DEFAULT_HOST = "bitwarden.com"
private val DEFAULT_ORIGIN = Origin.Android(
UnverifiedAssetLink(
packageName = DEFAULT_PACKAGE_NAME,
sha256CertFingerprint = DEFAULT_CERT_FINGERPRINT,
host = DEFAULT_HOST,
assetLinkUrl = "bitwarden.com",
),
)
private val DEFAULT_STATEMENT = DigitalAssetLinkResponseJson( private val DEFAULT_STATEMENT = DigitalAssetLinkResponseJson(
relation = listOf( relation = listOf(
"delegate_permission/common.get_login_creds", "delegate_permission/common.get_login_creds",
@ -1029,7 +1104,7 @@ private const val DEFAULT_FIDO2_AUTH_REQUEST_JSON = """
""" """
private fun createMockFido2AssertionRequest( private fun createMockFido2AssertionRequest(
mockOrigin: String? = DEFAULT_ORIGIN, mockOrigin: String? = "bitwarden.com",
mockClientDataHash: ByteArray? = null, mockClientDataHash: ByteArray? = null,
mockSigningInfo: SigningInfo, mockSigningInfo: SigningInfo,
) = mockk<Fido2CredentialAssertionRequest> { ) = mockk<Fido2CredentialAssertionRequest> {

View file

@ -11,8 +11,10 @@ import com.bitwarden.crypto.TrustDeviceResponse
import com.bitwarden.exporters.ExportFormat import com.bitwarden.exporters.ExportFormat
import com.bitwarden.fido.ClientData import com.bitwarden.fido.ClientData
import com.bitwarden.fido.Fido2CredentialAutofillView import com.bitwarden.fido.Fido2CredentialAutofillView
import com.bitwarden.fido.Origin
import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse
import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse
import com.bitwarden.fido.UnverifiedAssetLink
import com.bitwarden.sdk.BitwardenException import com.bitwarden.sdk.BitwardenException
import com.bitwarden.sdk.Client import com.bitwarden.sdk.Client
import com.bitwarden.sdk.ClientAuth import com.bitwarden.sdk.ClientAuth
@ -1239,9 +1241,18 @@ class VaultSdkSourceTest {
} }
private const val DEFAULT_SIGNATURE = "0987654321ABCDEF" private const val DEFAULT_SIGNATURE = "0987654321ABCDEF"
private val DEFAULT_ORIGIN = Origin.Android(
UnverifiedAssetLink(
packageName = "com.x8bit.bitwarden",
sha256CertFingerprint = "30:39:38:37:36:35:34:33:32:31:41:42:43:44:45:46",
host = "bitwarden.com",
assetLinkUrl = "www.bitwarden.com",
),
)
private val DEFAULT_FIDO_2_REGISTER_CREDENTIAL_REQUEST = RegisterFido2CredentialRequest( private val DEFAULT_FIDO_2_REGISTER_CREDENTIAL_REQUEST = RegisterFido2CredentialRequest(
userId = "mockUserId", userId = "mockUserId",
origin = "www.bitwarden.com", origin = DEFAULT_ORIGIN,
requestJson = "requestJson", requestJson = "requestJson",
clientData = ClientData.DefaultWithCustomHash( clientData = ClientData.DefaultWithCustomHash(
DEFAULT_SIGNATURE.toByteArray(), DEFAULT_SIGNATURE.toByteArray(),
@ -1251,7 +1262,7 @@ private val DEFAULT_FIDO_2_REGISTER_CREDENTIAL_REQUEST = RegisterFido2Credential
) )
private val DEFAULT_FIDO_2_AUTH_REQUEST = AuthenticateFido2CredentialRequest( private val DEFAULT_FIDO_2_AUTH_REQUEST = AuthenticateFido2CredentialRequest(
userId = "mockUserId", userId = "mockUserId",
origin = "www.bitwarden.com", origin = DEFAULT_ORIGIN,
requestJson = "requestJson", requestJson = "requestJson",
clientData = ClientData.DefaultWithCustomHash( clientData = ClientData.DefaultWithCustomHash(
DEFAULT_SIGNATURE.toByteArray(), DEFAULT_SIGNATURE.toByteArray(),

View file

@ -24,7 +24,7 @@ androidxSplash = "1.1.0-rc01"
androidXAppCompat = "1.7.0" androidXAppCompat = "1.7.0"
androdixAutofill = "1.1.0" androdixAutofill = "1.1.0"
androidxWork = "2.9.1" androidxWork = "2.9.1"
bitwardenSdk = "0.5.0-20240819.160739-177" bitwardenSdk = "1.0.0-20240924.112512-21"
crashlytics = "3.0.2" crashlytics = "3.0.2"
detekt = "1.23.7" detekt = "1.23.7"
firebaseBom = "33.3.0" firebaseBom = "33.3.0"