BIT-2046 Define FIDO2 credential network models (#1137)

This commit is contained in:
Patrick Honkonen 2024-03-15 14:39:41 -04:00 committed by Álison Fernandes
parent 77a40aeb2f
commit 143a46165c
14 changed files with 235 additions and 6 deletions

View file

@ -91,5 +91,8 @@ object PlatformNetworkModule {
serializersModule = SerializersModule {
contextual(ZonedDateTimeSerializer())
}
// Respect model default property values.
coerceInputValues = true
}
}

View file

@ -6,6 +6,10 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
import java.time.ZonedDateTime
private const val DEFAULT_FIDO_2_KEY_TYPE = "public-key"
private const val DEFAULT_FIDO_2_KEY_ALGORITHM = "ECDSA"
private const val DEFAULT_FIDO_2_KEY_CURVE = "P-256"
/**
* Represents the response model for vault data fetched from the server.
*
@ -731,6 +735,9 @@ data class SyncResponseJson(
@SerialName("username")
val username: String?,
@SerialName("fido2Credentials")
val fido2Credentials: List<Fido2Credential>?,
) {
/**
* Represents a URI in the vault response.
@ -774,6 +781,66 @@ data class SyncResponseJson(
@SerialName("type")
val type: SecureNoteTypeJson,
)
/**
* Represents a FIDO2 credential object in the vault response.
*
* @property credentialId The unique identifier of the FIDO2 credential.
* @property keyType The type of public key of the FIDO2 credential.
* @property keyAlgorithm The public Key algorithm of the credential.
* @property keyValue The public key of the credential.
* @property rpId The relying party (RP) identity.
* @property rpName The optional name of the relying party (RP).
* @property userHandle The optional unique identifier used to identify an account.
* @property userName The conditional, formal name of the user associated to the credential.
* @property userDisplayName The optional display name of the user associated to the
* credential.
* @property counter The signature counter for the credential.
* @property discoverable Whether the FIDO2 credential is discoverable or non-discoverable.
* @property creationDate The creation date and time of the credential.
*/
@Serializable
data class Fido2Credential(
@SerialName("credentialId")
val credentialId: String,
@SerialName("keyType")
val keyType: String = DEFAULT_FIDO_2_KEY_TYPE,
@SerialName("keyAlgorithm")
val keyAlgorithm: String = DEFAULT_FIDO_2_KEY_ALGORITHM,
@SerialName("keyCurve")
val keyCurve: String = DEFAULT_FIDO_2_KEY_CURVE,
@SerialName("keyValue")
val keyValue: String,
@SerialName("rpId")
val rpId: String,
@SerialName("rpName")
val rpName: String?,
@SerialName("userHandle")
val userHandle: String?,
@SerialName("userName")
val userName: String?,
@SerialName("userDisplayName")
val userDisplayName: String?,
@SerialName("counter")
val counter: String,
@SerialName("discoverable")
val discoverable: String,
@SerialName("creationDate")
@Contextual
val creationDate: ZonedDateTime,
)
}
/**

View file

@ -8,6 +8,7 @@ import com.bitwarden.core.Cipher
import com.bitwarden.core.CipherRepromptType
import com.bitwarden.core.CipherType
import com.bitwarden.core.CipherView
import com.bitwarden.core.Fido2Credential
import com.bitwarden.core.Field
import com.bitwarden.core.FieldType
import com.bitwarden.core.Identity
@ -232,8 +233,28 @@ private fun Login.toEncryptedNetworkLogin(): SyncResponseJson.Cipher.Login =
// uri needs to be null to avoid duplicating the first url entry for a login item.
uri = null,
username = username,
fido2Credentials = fido2Credentials?.toNetworkFido2Credentials(),
)
private fun List<Fido2Credential>.toNetworkFido2Credentials() =
this.map { it.toNetworkFido2Credential() }
private fun Fido2Credential.toNetworkFido2Credential() = SyncResponseJson.Cipher.Fido2Credential(
credentialId = credentialId,
keyType = keyType,
keyAlgorithm = keyAlgorithm,
keyCurve = keyCurve,
keyValue = keyValue,
rpId = rpId,
rpName = rpName,
userHandle = userHandle,
userName = userName,
userDisplayName = userDisplayName,
counter = counter,
discoverable = discoverable,
creationDate = ZonedDateTime.ofInstant(creationDate, ZoneOffset.UTC),
)
/**
* Converts a list of Bitwarden SDK [PasswordHistory] objects to a corresponding
* list of [SyncResponseJson.Cipher.PasswordHistory] objects.
@ -325,8 +346,28 @@ fun SyncResponseJson.Cipher.Login.toSdkLogin(): Login =
uris = uris?.toSdkLoginUriList(),
totp = totp,
autofillOnPageLoad = shouldAutofillOnPageLoad,
fido2Credentials = fido2Credentials?.toSdkFido2Credentials(),
)
private fun List<SyncResponseJson.Cipher.Fido2Credential>.toSdkFido2Credentials() =
this.map { it.toSdkFido2Credential() }
private fun SyncResponseJson.Cipher.Fido2Credential.toSdkFido2Credential() = Fido2Credential(
credentialId = credentialId,
keyType = keyType,
keyAlgorithm = keyAlgorithm,
keyCurve = keyCurve,
keyValue = keyValue,
rpId = rpId,
rpName = rpName,
userHandle = userHandle,
userName = userName,
userDisplayName = userDisplayName,
counter = counter,
discoverable = discoverable,
creationDate = creationDate.toInstant(),
)
/**
* Transforms a [SyncResponseJson.Cipher.Identity] into the corresponding Bitwarden SDK [Identity].
*/

View file

@ -147,6 +147,7 @@ private fun VaultAddEditState.ViewState.Content.ItemType.toLoginView(
uris = it.uriList.toLoginUriView(),
totp = it.totp,
autofillOnPageLoad = common.originalCipher?.login?.autofillOnPageLoad,
fido2Credentials = common.originalCipher?.login?.fido2Credentials,
)
}

View file

@ -337,7 +337,24 @@ private const val CIPHER_JSON = """
"passwordRevisionDate": "2023-10-27T12:00:00.000Z",
"autofillOnPageLoad": false,
"uri": "mockUri-1",
"username": "mockUsername-1"
"username": "mockUsername-1",
"fido2Credentials": [
{
"credentialId": "mockCredentialId-1",
"keyType": "mockKeyType-1",
"keyAlgorithm": "mockKeyAlgorithm-1",
"keyCurve": "mockKeyCurve-1",
"keyValue": "mockKeyValue-1",
"rpId": "mockRpId-1",
"rpName": "mockRpName-1",
"userHandle": "mockUserHandle-1",
"userName": "mockUserName-1",
"userDisplayName": "mockUserDisplayName-1",
"counter": "mockCounter-1",
"discoverable": "mockDiscoverable-1",
"creationDate": "2024-03-12T20:20:16.456Z"
}
]
},
"creationDate": "2023-10-27T12:00:00.000Z",
"secureNote": {

View file

@ -120,11 +120,28 @@ fun createMockLogin(number: Int, hasNullUri: Boolean = false): SyncResponseJson.
password = "mockPassword-$number",
passwordRevisionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
shouldAutofillOnPageLoad = false,
uri = if (hasNullUri) { null } else "mockUri-$number",
uri = if (hasNullUri) null else "mockUri-$number",
uris = listOf(createMockUri(number = number)),
totp = "mockTotp-$number",
fido2Credentials = listOf(createMockFido2Credential(number)),
)
fun createMockFido2Credential(number: Int) = SyncResponseJson.Cipher.Fido2Credential(
credentialId = "mockCredentialId-$number",
keyType = "mockKeyType-$number",
keyAlgorithm = "mockKeyAlgorithm-$number",
keyCurve = "mockKeyCurve-$number",
keyValue = "mockKeyValue-$number",
rpId = "mockRpId-$number",
rpName = "mockRpName-$number",
userHandle = "mockUserHandle-$number",
userName = "mockUserName-$number",
userDisplayName = "mockUserDisplayName-$number",
counter = "mockCounter-$number",
discoverable = "mockDiscoverable-$number",
creationDate = ZonedDateTime.parse("2024-03-12T20:20:16.456Z"),
)
/**
* Create a mock [SyncResponseJson.Cipher.Login.Uri] with a given [number].
*/

View file

@ -314,7 +314,24 @@ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """
"passwordRevisionDate": "2023-10-27T12:00:00.00Z",
"autofillOnPageLoad": false,
"uri": "mockUri-1",
"username": "mockUsername-1"
"username": "mockUsername-1",
"fido2Credentials": [
{
"credentialId": "mockCredentialId-1",
"keyType": "mockKeyType-1",
"keyAlgorithm": "mockKeyAlgorithm-1",
"keyCurve": "mockKeyCurve-1",
"keyValue": "mockKeyValue-1",
"rpId": "mockRpId-1",
"rpName": "mockRpName-1",
"userHandle": "mockUserHandle-1",
"userName": "mockUserName-1",
"userDisplayName": "mockUserDisplayName-1",
"counter": "mockCounter-1",
"discoverable": "mockDiscoverable-1",
"creationDate": "2024-03-12T20:20:16.456Z"
}
]
},
"creationDate": "2023-10-27T12:00:00.00Z",
"secureNote": {
@ -407,7 +424,24 @@ private const val CREATE_UPDATE_CIPHER_SUCCESS_JSON = """
"passwordRevisionDate": "2023-10-27T12:00:00.00Z",
"autofillOnPageLoad": false,
"uri": "mockUri-1",
"username": "mockUsername-1"
"username": "mockUsername-1",
"fido2Credentials": [
{
"credentialId": "mockCredentialId-1",
"keyType": "mockKeyType-1",
"keyAlgorithm": "mockKeyAlgorithm-1",
"keyCurve": "mockKeyCurve-1",
"keyValue": "mockKeyValue-1",
"rpId": "mockRpId-1",
"rpName": "mockRpName-1",
"userHandle": "mockUserHandle-1",
"userName": "mockUserName-1",
"userDisplayName": "mockUserDisplayName-1",
"counter": "mockCounter-1",
"discoverable": "mockDiscoverable-1",
"creationDate": "2024-03-12T20:20:16.456Z"
}
]
},
"creationDate": "2023-10-27T12:00:00.00Z",
"secureNote": {

View file

@ -230,7 +230,24 @@ private const val SYNC_SUCCESS_JSON = """
"passwordRevisionDate": "2023-10-27T12:00:00.00Z",
"autofillOnPageLoad": false,
"uri": "mockUri-1",
"username": "mockUsername-1"
"username": "mockUsername-1",
"fido2Credentials": [
{
"credentialId": "mockCredentialId-1",
"keyType": "mockKeyType-1",
"keyAlgorithm": "mockKeyAlgorithm-1",
"keyCurve": "mockKeyCurve-1",
"keyValue": "mockKeyValue-1",
"rpId": "mockRpId-1",
"rpName": "mockRpName-1",
"userHandle": "mockUserHandle-1",
"userName": "mockUserName-1",
"userDisplayName": "mockUserDisplayName-1",
"counter": "mockCounter-1",
"discoverable": "mockDiscoverable-1",
"creationDate": "2024-03-12T20:20:16.456Z"
}
]
},
"creationDate": "2023-10-27T12:00:00.00Z",
"secureNote": {

View file

@ -5,6 +5,7 @@ import com.bitwarden.core.CardView
import com.bitwarden.core.CipherRepromptType
import com.bitwarden.core.CipherType
import com.bitwarden.core.CipherView
import com.bitwarden.core.Fido2Credential
import com.bitwarden.core.FieldType
import com.bitwarden.core.FieldView
import com.bitwarden.core.IdentityView
@ -89,6 +90,29 @@ fun createMockLoginView(
autofillOnPageLoad = false,
uris = listOf(createMockUriView(number = number)),
totp = totp,
fido2Credentials = createMockSdkFido2CredentialList(number),
)
fun createMockSdkFido2CredentialList(number: Int) =
listOf(createMockSdkFido2CredentialView(number))
fun createMockSdkFido2CredentialView(number: Int) =
Fido2Credential(
credentialId = "mockCredentialId-$number",
keyType = "mockKeyType-$number",
keyAlgorithm = "mockKeyAlgorithm-$number",
keyCurve = "mockKeyCurve-$number",
keyValue = "mockKeyValue-$number",
rpId = "mockRpId-$number",
userHandle = "mockUserHandle-$number",
userName = "mockUserName-$number",
counter = "mockCounter-$number",
rpName = "mockRpName-$number",
userDisplayName = "mockUserDisplayName-$number",
discoverable = "mockDiscoverable-$number",
creationDate = ZonedDateTime
.parse("2024-03-12T20:20:16.456Z")
.toInstant(),
)
/**

View file

@ -147,6 +147,7 @@ fun createMockSdkLogin(number: Int): Login =
autofillOnPageLoad = false,
uris = listOf(createMockSdkUri(number = number)),
totp = "mockTotp-$number",
fido2Credentials = createMockSdkFido2CredentialList(number),
)
/**

View file

@ -20,6 +20,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
@ -529,6 +530,7 @@ private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.cop
),
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
autofillOnPageLoad = false,
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
),
)

View file

@ -10,6 +10,7 @@ import com.bitwarden.core.IdentityView
import com.bitwarden.core.LoginUriView
import com.bitwarden.core.LoginView
import com.bitwarden.core.PasswordHistoryView
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
import com.x8bit.bitwarden.ui.vault.feature.item.model.TotpCodeItemData
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
@ -42,6 +43,7 @@ fun createLoginView(isEmpty: Boolean): LoginView =
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
.takeUnless { isEmpty },
autofillOnPageLoad = false,
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
)
@Suppress("CyclomaticComplexMethod")

View file

@ -84,6 +84,7 @@ class VaultAddItemStateExtensionsTest {
),
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
autofillOnPageLoad = null,
fido2Credentials = null,
),
identity = null,
card = null,
@ -162,6 +163,7 @@ class VaultAddItemStateExtensionsTest {
),
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
autofillOnPageLoad = false,
fido2Credentials = null,
),
favorite = true,
reprompt = CipherRepromptType.NONE,
@ -741,6 +743,7 @@ private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.cop
),
totp = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example",
autofillOnPageLoad = false,
fido2Credentials = null,
),
)

View file

@ -23,7 +23,7 @@ androidxSplash = "1.1.0-alpha02"
androidXAppCompat = "1.6.1"
androdixAutofill = "1.1.0"
androidxWork = "2.9.0"
bitwardenSdk = "0.4.0-20240306.185653-166"
bitwardenSdk = "0.4.0-20240314.115913-173"
crashlytics = "2.9.9"
detekt = "1.23.5"
firebaseBom = "32.7.3"