From b3e885bcb13dd8c3c9f7082f23478d721e6d1a59 Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:07:30 +0200 Subject: [PATCH] [PM-12279] Update SDK reference and use Origin.Android on Fido2Credential (#3975) --- .../manager/Fido2CredentialManagerImpl.kt | 28 +++++- .../AuthenticateFido2CredentialRequest.kt | 6 +- .../model/RegisterFido2CredentialRequest.kt | 3 +- .../manager/Fido2CredentialManagerTest.kt | 91 +++++++++++++++++-- .../datasource/sdk/VaultSdkSourceTest.kt | 15 ++- gradle/libs.versions.toml | 2 +- 6 files changed, 128 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt index ace2baeb4..6c808920e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt @@ -2,6 +2,8 @@ package com.x8bit.bitwarden.data.autofill.fido2.manager import androidx.credentials.provider.CallingAppInfo import com.bitwarden.fido.ClientData +import com.bitwarden.fido.Origin +import com.bitwarden.fido.UnverifiedAssetLink import com.bitwarden.sdk.Fido2CredentialStore import com.bitwarden.vault.CipherView 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.util.toAndroidAttestationResponse 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.encodeToString import kotlinx.serialization.json.Json @@ -65,11 +68,23 @@ class Fido2CredentialManagerImpl( .packageName, ) } - val origin = fido2CredentialRequest + val assetLinkUrl = fido2CredentialRequest .origin ?: getOriginUrlFromAttestationOptionsOrNull(fido2CredentialRequest.requestJson) ?: 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 .registerFido2Credential( request = RegisterFido2CredentialRequest( @@ -157,7 +172,16 @@ class Fido2CredentialManagerImpl( .authenticateFido2Credential( request = AuthenticateFido2CredentialRequest( 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}}""", clientData = clientData, selectedCipherView = selectedCipherView, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AuthenticateFido2CredentialRequest.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AuthenticateFido2CredentialRequest.kt index ab7e908f2..4a7517ead 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AuthenticateFido2CredentialRequest.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AuthenticateFido2CredentialRequest.kt @@ -1,14 +1,14 @@ package com.x8bit.bitwarden.data.vault.datasource.sdk.model import com.bitwarden.fido.ClientData +import com.bitwarden.fido.Origin import com.bitwarden.vault.CipherView /** * Models a FIDO 2 authentication request to the Bitwarden SDK. * * @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 - * application fingerprint. + * @param origin Origin of the Relying Party WebAuthn Request. * @param requestJson Authentication request JSON received from the OS. * @param clientData Metadata containing either privileged application certificate hash or Android * package name of the Relying Party. @@ -18,7 +18,7 @@ import com.bitwarden.vault.CipherView */ data class AuthenticateFido2CredentialRequest( val userId: String, - val origin: String, + val origin: Origin, val requestJson: String, val clientData: ClientData, val selectedCipherView: CipherView, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/RegisterFido2CredentialRequest.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/RegisterFido2CredentialRequest.kt index 08d931c5d..22e8e55b1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/RegisterFido2CredentialRequest.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/RegisterFido2CredentialRequest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.vault.datasource.sdk.model import com.bitwarden.fido.ClientData +import com.bitwarden.fido.Origin import com.bitwarden.vault.CipherView /** @@ -18,7 +19,7 @@ import com.bitwarden.vault.CipherView */ data class RegisterFido2CredentialRequest( val userId: String, - val origin: String, + val origin: Origin, val requestJson: String, val clientData: ClientData, val selectedCipherView: CipherView, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt index 273186dd3..19f952668 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerTest.kt @@ -5,7 +5,9 @@ import android.content.pm.SigningInfo import android.util.Base64 import androidx.credentials.provider.CallingAppInfo import com.bitwarden.fido.ClientData +import com.bitwarden.fido.Origin import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse +import com.bitwarden.fido.UnverifiedAssetLink 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.service.DigitalAssetLinkService @@ -531,8 +533,58 @@ class Fido2CredentialManagerTest { selectedCipherView = createMockCipherView(number = 1), ) - assertTrue( - result is Fido2RegisterCredentialResult.Error, + assertEquals( + Fido2RegisterCredentialResult.Error, + result, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `registerFido2Credential should return Error when getSignatureFingerprintAsHexString is null`() = + runTest { + val mockSigningInfo = mockk { + 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 { + 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), ) - assertTrue( - result is Fido2RegisterCredentialResult.Error, + assertEquals( + Fido2RegisterCredentialResult.Error, + result, ) } @@ -709,7 +762,14 @@ class Fido2CredentialManagerTest { assertEquals( AuthenticateFido2CredentialRequest( 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}}""", clientData = ClientData.DefaultWithExtraData( androidPackageName = "android:apk-key-hash:$DEFAULT_APP_SIGNATURE", @@ -756,7 +816,14 @@ class Fido2CredentialManagerTest { } assertEquals( - "https://${mockAssertionOptions.relyingPartyId}", + Origin.Android( + UnverifiedAssetLink( + DEFAULT_PACKAGE_NAME, + DEFAULT_CERT_FINGERPRINT, + mockAssertionOptions.relyingPartyId!!, + "https://${mockAssertionOptions.relyingPartyId}", + ), + ), requestCaptureSlot.captured.origin, ) } @@ -942,9 +1009,17 @@ class Fido2CredentialManagerTest { } 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_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( relation = listOf( "delegate_permission/common.get_login_creds", @@ -1029,7 +1104,7 @@ private const val DEFAULT_FIDO2_AUTH_REQUEST_JSON = """ """ private fun createMockFido2AssertionRequest( - mockOrigin: String? = DEFAULT_ORIGIN, + mockOrigin: String? = "bitwarden.com", mockClientDataHash: ByteArray? = null, mockSigningInfo: SigningInfo, ) = mockk { diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt index 06aa5a98c..d4626c2c2 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt @@ -11,8 +11,10 @@ import com.bitwarden.crypto.TrustDeviceResponse import com.bitwarden.exporters.ExportFormat import com.bitwarden.fido.ClientData import com.bitwarden.fido.Fido2CredentialAutofillView +import com.bitwarden.fido.Origin import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAssertionResponse import com.bitwarden.fido.PublicKeyCredentialAuthenticatorAttestationResponse +import com.bitwarden.fido.UnverifiedAssetLink import com.bitwarden.sdk.BitwardenException import com.bitwarden.sdk.Client import com.bitwarden.sdk.ClientAuth @@ -1239,9 +1241,18 @@ class VaultSdkSourceTest { } 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( userId = "mockUserId", - origin = "www.bitwarden.com", + origin = DEFAULT_ORIGIN, requestJson = "requestJson", clientData = ClientData.DefaultWithCustomHash( DEFAULT_SIGNATURE.toByteArray(), @@ -1251,7 +1262,7 @@ private val DEFAULT_FIDO_2_REGISTER_CREDENTIAL_REQUEST = RegisterFido2Credential ) private val DEFAULT_FIDO_2_AUTH_REQUEST = AuthenticateFido2CredentialRequest( userId = "mockUserId", - origin = "www.bitwarden.com", + origin = DEFAULT_ORIGIN, requestJson = "requestJson", clientData = ClientData.DefaultWithCustomHash( DEFAULT_SIGNATURE.toByteArray(), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b0a787080..9f2fee706 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ androidxSplash = "1.1.0-rc01" androidXAppCompat = "1.7.0" androdixAutofill = "1.1.0" androidxWork = "2.9.1" -bitwardenSdk = "0.5.0-20240819.160739-177" +bitwardenSdk = "1.0.0-20240924.112512-21" crashlytics = "3.0.2" detekt = "1.23.7" firebaseBom = "33.3.0"