From 6f26ae50ea1e52243df89ca6d903ec27133e4db0 Mon Sep 17 00:00:00 2001
From: David Perez <david@livefront.com>
Date: Tue, 29 Oct 2024 16:20:44 -0500
Subject: [PATCH 1/2] PM-14044: Update generator line breaks to account for
 padding on both sides (#4187)

---
 .../ui/platform/components/field/BitwardenTextField.kt          | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt
index a767f771b..e29e1fa50 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/field/BitwardenTextField.kt
@@ -72,7 +72,7 @@ fun BitwardenTextField(
     val formattedText = if (shouldAddCustomLineBreaks) {
         value.withLineBreaksAtWidth(
             // Adjust for built in padding
-            widthPx = widthPx - 16.dp.toPx(),
+            widthPx = widthPx - 32.dp.toPx(),
             monospacedTextStyle = textStyle,
         )
     } else {

From 78e7adfbc16395925c15c809498154681b82badc Mon Sep 17 00:00:00 2001
From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
Date: Tue, 29 Oct 2024 17:40:20 -0400
Subject: [PATCH 2/2] [PM-10405] Add SSH key cipher type (#4158)

---
 .../platform/util/CipherViewExtensions.kt     |   5 +-
 .../network/model/CipherJsonRequest.kt        |   3 +
 .../network/model/CipherTypeJson.kt           |   6 +
 .../network/model/SyncResponseJson.kt         |  22 ++
 .../util/VaultSdkCipherExtensions.kt          |  24 ++
 .../feature/search/SearchNavigation.kt        |   4 +
 .../feature/search/SearchViewModel.kt         |  12 +
 .../feature/search/model/SearchType.kt        |   5 +
 .../search/util/SearchTypeDataExtensions.kt   |   3 +
 .../search/util/SearchTypeExtensions.kt       |   1 +
 .../addedit/VaultAddEditItemContent.kt        |  20 +-
 .../feature/addedit/VaultAddEditNavigation.kt |   3 +
 .../feature/addedit/VaultAddEditScreen.kt     |   7 +
 .../addedit/VaultAddEditSshKeyItems.kt        | 127 ++++++++++
 .../feature/addedit/VaultAddEditViewModel.kt  | 166 +++++++++++++
 .../VaultAddEditSshKeyTypeHandlers.kt         |  64 +++++
 .../feature/addedit/model/CustomFieldType.kt  |   1 +
 .../addedit/util/CipherViewExtensions.kt      |   7 +
 .../addedit/util/VaultAddEditExtensions.kt    |   1 +
 .../ui/vault/feature/item/VaultItemScreen.kt  |  14 ++
 .../feature/item/VaultItemSshKeyContent.kt    | 127 ++++++++++
 .../vault/feature/item/VaultItemViewModel.kt  |  66 ++++++
 .../handlers/VaultSshKeyItemTypeHandlers.kt   |  32 +++
 .../feature/item/util/CipherViewExtensions.kt |  13 +
 .../itemlisting/VaultItemListingNavigation.kt |   4 +
 .../itemlisting/VaultItemListingViewModel.kt  |   8 +
 .../util/VaultItemListingDataExtensions.kt    |   7 +
 .../util/VaultItemListingStateExtensions.kt   |   2 +
 .../util/VaultItemListingTypeExtensions.kt    |   1 +
 .../ui/vault/feature/vault/VaultContent.kt    |  20 +-
 .../ui/vault/feature/vault/VaultScreen.kt     |   1 +
 .../ui/vault/feature/vault/VaultViewModel.kt  |  82 ++++++-
 .../feature/vault/handlers/VaultHandlers.kt   |   2 +
 .../vault/util/VaultAddItemStateExtensions.kt |  12 +
 .../feature/vault/util/VaultDataExtensions.kt |  46 +++-
 .../ui/vault/model/VaultItemCipherType.kt     |   5 +
 .../ui/vault/model/VaultItemListingType.kt    |   7 +-
 app/src/main/res/drawable/ic_ssh_key.xml      |  14 ++
 app/src/main/res/values/strings.xml           |   4 +
 .../data/util/CipherViewExtensionsTest.kt     |  23 ++
 .../datasource/disk/VaultDiskSourceTest.kt    |   7 +-
 .../network/model/CipherJsonRequestUtil.kt    |   1 +
 .../network/model/SyncResponseCipherUtil.kt   |  13 +
 .../network/service/CiphersServiceTest.kt     |  14 +-
 .../network/service/SyncServiceTest.kt        |   7 +-
 .../datasource/sdk/model/CipherViewUtil.kt    |  13 +
 .../sdk/model/VaultSdkCipherUtil.kt           |  12 +
 .../util/VaultSdkCipherExtensionsTest.kt      |  12 +
 .../feature/search/SearchScreenTest.kt        |  25 ++
 .../feature/search/SearchViewModelTest.kt     |   2 +
 .../util/SearchTypeDataExtensionsTest.kt      |  26 ++
 .../search/util/SearchTypeExtensionsTest.kt   |  14 ++
 .../feature/search/util/SearchUtil.kt         |  30 +++
 .../feature/addedit/VaultAddEditScreenTest.kt |  88 +++++++
 .../addedit/VaultAddEditViewModelTest.kt      | 205 ++++++++++++++++
 .../addedit/util/CipherViewExtensionsTest.kt  |  55 +++++
 .../util/VaultAddEditExtensionsTest.kt        |   2 +
 .../vault/feature/item/VaultItemScreenTest.kt | 113 +++++++++
 .../feature/item/VaultItemViewModelTest.kt    |  69 ++++++
 .../item/util/CipherViewExtensionsTest.kt     |  28 +++
 .../feature/item/util/VaultItemTestUtil.kt    |  18 ++
 .../itemlisting/VaultItemListingScreenTest.kt |   7 +
 .../VaultItemListingViewModelTest.kt          |   2 +
 .../VaultItemListingDataExtensionsTest.kt     | 133 ++++++++++-
 .../util/VaultItemListingDataUtil.kt          |  38 +++
 .../VaultItemListingStateExtensionsTest.kt    |  12 +
 .../VaultItemListingTypeExtensionsTest.kt     |  14 ++
 .../ui/vault/feature/vault/VaultScreenTest.kt |  68 ++++++
 .../vault/feature/vault/VaultViewModelTest.kt | 188 ++++++++++++++-
 .../util/VaultAddItemStateExtensionsTest.kt   |  65 +++++
 .../vault/util/VaultDataExtensionsTest.kt     | 224 ++++++++++++++++++
 .../vault/util/VaultStateExtensionsTest.kt    |   2 +
 gradle/libs.versions.toml                     |   2 +-
 73 files changed, 2447 insertions(+), 33 deletions(-)
 create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSshKeyItems.kt
 create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditSshKeyTypeHandlers.kt
 create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt
 create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultSshKeyItemTypeHandlers.kt
 create mode 100644 app/src/main/res/drawable/ic_ssh_key.xml

diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/CipherViewExtensions.kt
index 71abdba5b..9f451a7ab 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/CipherViewExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/CipherViewExtensions.kt
@@ -21,7 +21,6 @@ private const val CARD_DIGITS_DISPLAYED: Int = 4
 val CipherView.subtitle: String?
     get() = when (type) {
         CipherType.LOGIN -> this.login?.username.orEmpty()
-        CipherType.SECURE_NOTE -> null
         CipherType.CARD -> {
             this
                 .card
@@ -45,6 +44,10 @@ val CipherView.subtitle: String?
                     }
                 }
         }
+
+        CipherType.SECURE_NOTE,
+        CipherType.SSH_KEY,
+            -> null
     }
 
 /**
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequest.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequest.kt
index dc7adef69..dbf4266f4 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequest.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequest.kt
@@ -51,6 +51,9 @@ data class CipherJsonRequest(
     @SerialName("secureNote")
     val secureNote: SyncResponseJson.Cipher.SecureNote?,
 
+    @SerialName("sshKey")
+    val sshKey: SyncResponseJson.Cipher.SshKey?,
+
     @SerialName("folderId")
     val folderId: String?,
 
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherTypeJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherTypeJson.kt
index 63ddfaec8..1b43aeec7 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherTypeJson.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherTypeJson.kt
@@ -33,6 +33,12 @@ enum class CipherTypeJson {
      */
     @SerialName("4")
     IDENTITY,
+
+    /**
+     * A SSH key.
+     */
+    @SerialName("5")
+    SSH_KEY,
 }
 
 @Keep
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt
index 5ea91d252..8e47a2624 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt
@@ -472,6 +472,9 @@ data class SyncResponseJson(
         @SerialName("identity")
         val identity: Identity?,
 
+        @SerialName("sshKey")
+        val sshKey: SshKey?,
+
         @SerialName("collectionIds")
         val collectionIds: List<String>?,
 
@@ -718,6 +721,25 @@ data class SyncResponseJson(
             )
         }
 
+        /**
+         * Represents a SSH key in the vault response.
+         *
+         * @property publicKey The public key of the SSH key.
+         * @property privateKey The private key of the SSH key.
+         * @property keyFingerprint The key fingerprint of the SSH key.
+         */
+        @Serializable
+        data class SshKey(
+            @SerialName("publicKey")
+            val publicKey: String?,
+
+            @SerialName("privateKey")
+            val privateKey: String?,
+
+            @SerialName("keyFingerprint")
+            val keyFingerprint: String?,
+        )
+
         /**
          * Represents password history in the vault response.
          *
diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt
index 6c5e8f28d..c31cffaad 100644
--- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt
@@ -17,6 +17,7 @@ import com.bitwarden.vault.LoginUri
 import com.bitwarden.vault.PasswordHistory
 import com.bitwarden.vault.SecureNote
 import com.bitwarden.vault.SecureNoteType
+import com.bitwarden.vault.SshKey
 import com.bitwarden.vault.UriMatchType
 import com.x8bit.bitwarden.data.platform.util.SpecialCharWithPrecedenceComparator
 import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
@@ -55,6 +56,7 @@ fun Cipher.toEncryptedNetworkCipher(): CipherJsonRequest =
         isFavorite = favorite,
         card = card?.toEncryptedNetworkCard(),
         key = key,
+        sshKey = sshKey?.toEncryptedNetworkSshKey(),
     )
 
 /**
@@ -77,6 +79,7 @@ fun Cipher.toEncryptedNetworkCipherResponse(): SyncResponseJson.Cipher =
         isFavorite = favorite,
         card = card?.toEncryptedNetworkCard(),
         attachments = attachments?.toNetworkAttachmentList(),
+        sshKey = sshKey?.toEncryptedNetworkSshKey(),
         shouldOrganizationUseTotp = organizationUseTotp,
         shouldEdit = edit,
         revisionDate = ZonedDateTime.ofInstant(revisionDate, ZoneOffset.UTC),
@@ -102,6 +105,13 @@ private fun Card.toEncryptedNetworkCard(): SyncResponseJson.Cipher.Card =
         brand = brand,
     )
 
+private fun SshKey.toEncryptedNetworkSshKey(): SyncResponseJson.Cipher.SshKey =
+    SyncResponseJson.Cipher.SshKey(
+        publicKey = publicKey,
+        privateKey = privateKey,
+        keyFingerprint = fingerprint,
+    )
+
 /**
  * Converts a list of Bitwarden SDK [Field] objects to a corresponding
  * list of [SyncResponseJson.Cipher.Field] objects.
@@ -309,6 +319,7 @@ private fun CipherType.toNetworkCipherType(): CipherTypeJson =
         CipherType.SECURE_NOTE -> CipherTypeJson.SECURE_NOTE
         CipherType.CARD -> CipherTypeJson.CARD
         CipherType.IDENTITY -> CipherTypeJson.IDENTITY
+        CipherType.SSH_KEY -> CipherTypeJson.SSH_KEY
     }
 
 /**
@@ -334,6 +345,7 @@ fun SyncResponseJson.Cipher.toEncryptedSdkCipher(): Cipher =
         type = type.toSdkCipherType(),
         login = login?.toSdkLogin(),
         identity = identity?.toSdkIdentity(),
+        sshKey = sshKey?.toSdkSshKey(),
         card = card?.toSdkCard(),
         secureNote = secureNote?.toSdkSecureNote(),
         favorite = isFavorite,
@@ -432,6 +444,17 @@ fun SyncResponseJson.Cipher.SecureNote.toSdkSecureNote(): SecureNote =
         },
     )
 
+/**
+ * Transforms a [SyncResponseJson.Cipher.SshKey] into
+ * the corresponding Bitwarden SDK [SshKey].
+ */
+fun SyncResponseJson.Cipher.SshKey.toSdkSshKey(): SshKey =
+    SshKey(
+        publicKey = publicKey,
+        privateKey = privateKey,
+        fingerprint = keyFingerprint,
+    )
+
 /**
  * Transforms a list of [SyncResponseJson.Cipher.Login.Uri] into
  * a corresponding list of  Bitwarden SDK [LoginUri].
@@ -517,6 +540,7 @@ fun CipherTypeJson.toSdkCipherType(): CipherType =
         CipherTypeJson.SECURE_NOTE -> CipherType.SECURE_NOTE
         CipherTypeJson.CARD -> CipherType.CARD
         CipherTypeJson.IDENTITY -> CipherType.IDENTITY
+        CipherTypeJson.SSH_KEY -> CipherType.SSH_KEY
     }
 
 /**
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt
index 40f59b517..93bf3d9a0 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt
@@ -26,6 +26,7 @@ private const val SEARCH_TYPE_VAULT_TRASH: String = "search_type_vault_trash"
 private const val SEARCH_TYPE_VAULT_VERIFICATION_CODES: String =
     "search_type_vault_verification_codes"
 private const val SEARCH_TYPE_ID: String = "search_type_id"
+private const val SEARCH_TYPE_VAULT_SSH_KEYS: String = "search_type_vault_ssh_keys"
 
 private const val SEARCH_ROUTE_PREFIX: String = "search"
 private const val SEARCH_ROUTE: String = "$SEARCH_ROUTE_PREFIX/{$SEARCH_TYPE}/{$SEARCH_TYPE_ID}"
@@ -104,6 +105,7 @@ private fun determineSearchType(
         SEARCH_TYPE_VAULT_FOLDER -> SearchType.Vault.Folder(requireNotNull(id))
         SEARCH_TYPE_VAULT_TRASH -> SearchType.Vault.Trash
         SEARCH_TYPE_VAULT_VERIFICATION_CODES -> SearchType.Vault.VerificationCodes
+        SEARCH_TYPE_VAULT_SSH_KEYS -> SearchType.Vault.SshKeys
         else -> throw IllegalArgumentException("Invalid Search Type")
     }
 
@@ -122,6 +124,7 @@ private fun SearchType.toTypeString(): String =
         SearchType.Vault.SecureNotes -> SEARCH_TYPE_VAULT_SECURE_NOTES
         SearchType.Vault.Trash -> SEARCH_TYPE_VAULT_TRASH
         SearchType.Vault.VerificationCodes -> SEARCH_TYPE_VAULT_VERIFICATION_CODES
+        SearchType.Vault.SshKeys -> SEARCH_TYPE_VAULT_SSH_KEYS
     }
 
 private fun SearchType.toIdOrNull(): String? =
@@ -139,4 +142,5 @@ private fun SearchType.toIdOrNull(): String? =
         SearchType.Vault.SecureNotes -> null
         SearchType.Vault.Trash -> null
         SearchType.Vault.VerificationCodes -> null
+        SearchType.Vault.SshKeys -> null
     }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt
index 1f06d7871..f498b8372 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt
@@ -11,6 +11,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
 import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
 import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
 import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
+import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
 import com.x8bit.bitwarden.data.platform.manager.PolicyManager
 import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
 import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
@@ -876,6 +877,7 @@ sealed class SearchTypeData : Parcelable {
      * Indicates that we should be searching vault items.
      */
     @Parcelize
+    @OmitFromCoverage
     sealed class Vault : SearchTypeData() {
         /**
          * Indicates that we should be searching all vault items.
@@ -924,6 +926,16 @@ sealed class SearchTypeData : Parcelable {
                     .concat(R.string.secure_notes.asText())
         }
 
+        /**
+         * Indicates that we should be searching only ssh key ciphers.
+         */
+        data object SshKeys : Vault() {
+            override val title: Text
+                get() = R.string.search.asText()
+                    .concat(" ".asText())
+                    .concat(R.string.ssh_keys.asText())
+        }
+
         /**
          * Indicates that we should be searching only ciphers in the given collection.
          */
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt
index 3eb74d887..94331d15a 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt
@@ -58,6 +58,11 @@ sealed class SearchType : Parcelable {
          */
         data object SecureNotes : Vault()
 
+        /**
+         * Indicates that we should be searching only SSH key ciphers.
+         */
+        data object SshKeys : Vault()
+
         /**
          * Indicates that we should be searching only ciphers in the given collection.
          */
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt
index 861eb7c67..650dfd707 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt
@@ -62,6 +62,7 @@ fun SearchTypeData.updateWithAdditionalDataIfNecessary(
         SearchTypeData.Vault.SecureNotes -> this
         SearchTypeData.Vault.Trash -> this
         SearchTypeData.Vault.VerificationCodes -> this
+        SearchTypeData.Vault.SshKeys -> this
     }
 
 /**
@@ -114,6 +115,7 @@ private fun CipherView.filterBySearchType(
         is SearchTypeData.Vault.Identities -> type == CipherType.IDENTITY && deletedDate == null
         is SearchTypeData.Vault.Logins -> type == CipherType.LOGIN && deletedDate == null
         is SearchTypeData.Vault.SecureNotes -> type == CipherType.SECURE_NOTE && deletedDate == null
+        is SearchTypeData.Vault.SshKeys -> type == CipherType.SSH_KEY && deletedDate == null
         is SearchTypeData.Vault.VerificationCodes -> login?.totp != null && deletedDate == null
         is SearchTypeData.Vault.Trash -> deletedDate != null
     }
@@ -255,6 +257,7 @@ private val CipherType.iconRes: Int
         CipherType.SECURE_NOTE -> R.drawable.ic_note
         CipherType.CARD -> R.drawable.ic_payment_card
         CipherType.IDENTITY -> R.drawable.ic_id_card
+        CipherType.SSH_KEY -> R.drawable.ic_ssh_key
     }
 
 /**
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt
index 29b865ec9..c0475acb2 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt
@@ -21,4 +21,5 @@ fun SearchType.toSearchTypeData(): SearchTypeData =
         SearchType.Vault.SecureNotes -> SearchTypeData.Vault.SecureNotes
         SearchType.Vault.Trash -> SearchTypeData.Vault.Trash
         SearchType.Vault.VerificationCodes -> SearchTypeData.Vault.VerificationCodes
+        SearchType.Vault.SshKeys -> SearchTypeData.Vault.SshKeys
     }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt
index 979a021c8..fcd7fc16a 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt
@@ -21,21 +21,24 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCardTyp
 import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
 import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
 import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
+import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers
 import kotlinx.collections.immutable.toImmutableList
 
 /**
  * The top level content UI state for the [VaultAddEditScreen].
  */
 @Composable
-@Suppress("LongMethod")
+@Suppress("LongMethod", "CyclomaticComplexMethod")
 fun VaultAddEditContent(
     state: VaultAddEditState.ViewState.Content,
     isAddItemMode: Boolean,
+    typeOptions: List<VaultAddEditState.ItemTypeOption>,
     onTypeOptionClicked: (VaultAddEditState.ItemTypeOption) -> Unit,
     commonTypeHandlers: VaultAddEditCommonHandlers,
     loginItemTypeHandlers: VaultAddEditLoginTypeHandlers,
     identityItemTypeHandlers: VaultAddEditIdentityTypeHandlers,
     cardItemTypeHandlers: VaultAddEditCardTypeHandlers,
+    sshKeyItemTypeHandlers: VaultAddEditSshKeyTypeHandlers,
     modifier: Modifier = Modifier,
     permissionsManager: PermissionsManager,
 ) {
@@ -45,6 +48,7 @@ fun VaultAddEditContent(
                 is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> Unit
                 is VaultAddEditState.ViewState.Content.ItemType.Card -> Unit
                 is VaultAddEditState.ViewState.Content.ItemType.Identity -> Unit
+                is VaultAddEditState.ViewState.Content.ItemType.SshKey -> Unit
                 is VaultAddEditState.ViewState.Content.ItemType.Login -> {
                     loginItemTypeHandlers.onSetupTotpClick(isGranted)
                 }
@@ -77,6 +81,7 @@ fun VaultAddEditContent(
             item {
                 Spacer(modifier = Modifier.height(8.dp))
                 TypeOptionsItem(
+                    entries = typeOptions,
                     itemType = state.type,
                     onTypeOptionClicked = onTypeOptionClicked,
                     modifier = Modifier
@@ -131,6 +136,15 @@ fun VaultAddEditContent(
                     commonTypeHandlers = commonTypeHandlers,
                 )
             }
+
+            is VaultAddEditState.ViewState.Content.ItemType.SshKey -> {
+                vaultAddEditSshKeyItems(
+                    commonState = state.common,
+                    sshKeyState = state.type,
+                    commonTypeHandlers = commonTypeHandlers,
+                    sshKeyTypeHandlers = sshKeyItemTypeHandlers,
+                )
+            }
         }
 
         item {
@@ -141,12 +155,12 @@ fun VaultAddEditContent(
 
 @Composable
 private fun TypeOptionsItem(
+    entries: List<VaultAddEditState.ItemTypeOption>,
     itemType: VaultAddEditState.ViewState.Content.ItemType,
     onTypeOptionClicked: (VaultAddEditState.ItemTypeOption) -> Unit,
     modifier: Modifier = Modifier,
 ) {
-    val possibleMainStates = VaultAddEditState.ItemTypeOption.entries.toList()
-    val optionsWithStrings = possibleMainStates.associateWith { stringResource(id = it.labelRes) }
+    val optionsWithStrings = entries.associateWith { stringResource(id = it.labelRes) }
 
     BitwardenMultiSelectButton(
         label = stringResource(id = R.string.type),
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt
index 5a9ee185c..ea2ec0fa6 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditNavigation.kt
@@ -21,6 +21,7 @@ private const val LOGIN: String = "login"
 private const val CARD: String = "card"
 private const val IDENTITY: String = "identity"
 private const val SECURE_NOTE: String = "secure_note"
+private const val SSH_KEY: String = "ssh_key"
 private const val ADD_ITEM_TYPE: String = "vault_add_item_type"
 
 private const val ADD_EDIT_ITEM_PREFIX: String = "vault_add_edit_item"
@@ -127,6 +128,7 @@ private fun VaultItemCipherType.toTypeString(): String =
         VaultItemCipherType.CARD -> CARD
         VaultItemCipherType.IDENTITY -> IDENTITY
         VaultItemCipherType.SECURE_NOTE -> SECURE_NOTE
+        VaultItemCipherType.SSH_KEY -> SSH_KEY
     }
 
 private fun String.toVaultItemCipherType(): VaultItemCipherType =
@@ -135,6 +137,7 @@ private fun String.toVaultItemCipherType(): VaultItemCipherType =
         CARD -> VaultItemCipherType.CARD
         IDENTITY -> VaultItemCipherType.IDENTITY
         SECURE_NOTE -> VaultItemCipherType.SECURE_NOTE
+        SSH_KEY -> VaultItemCipherType.SSH_KEY
         else -> throw IllegalStateException(
             "Edit Item string arguments for VaultAddEditNavigation must match!",
         )
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt
index 092806bd2..2bf3d550c 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt
@@ -58,6 +58,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCardTyp
 import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
 import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
 import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
+import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers
 import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditUserVerificationHandlers
 
 /**
@@ -155,6 +156,10 @@ fun VaultAddEditScreen(
         VaultAddEditCardTypeHandlers.create(viewModel = viewModel)
     }
 
+    val sshKeyItemTypeHandlers = remember(viewModel) {
+        VaultAddEditSshKeyTypeHandlers.create(viewModel = viewModel)
+    }
+
     val confirmDeleteClickAction = remember(viewModel) {
         { viewModel.trySendAction(VaultAddEditAction.Common.ConfirmDeleteClick) }
     }
@@ -321,6 +326,7 @@ fun VaultAddEditScreen(
                 VaultAddEditContent(
                     state = viewState,
                     isAddItemMode = state.isAddItemMode,
+                    typeOptions = state.supportedItemTypes,
                     onTypeOptionClicked = remember(viewModel) {
                         { viewModel.trySendAction(VaultAddEditAction.Common.TypeOptionSelect(it)) }
                     },
@@ -329,6 +335,7 @@ fun VaultAddEditScreen(
                     permissionsManager = permissionsManager,
                     identityItemTypeHandlers = identityItemTypeHandlers,
                     cardItemTypeHandlers = cardItemTypeHandlers,
+                    sshKeyItemTypeHandlers = sshKeyItemTypeHandlers,
                     modifier = Modifier
                         .imePadding()
                         .padding(innerPadding)
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSshKeyItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSshKeyItems.kt
new file mode 100644
index 000000000..547ea390d
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSshKeyItems.kt
@@ -0,0 +1,127 @@
+package com.x8bit.bitwarden.ui.vault.feature.addedit
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
+import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
+import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
+import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
+import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
+import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers
+
+/**
+ * The UI for adding and editing a SSH key cipher.
+ */
+fun LazyListScope.vaultAddEditSshKeyItems(
+    commonState: VaultAddEditState.ViewState.Content.Common,
+    sshKeyState: VaultAddEditState.ViewState.Content.ItemType.SshKey,
+    commonTypeHandlers: VaultAddEditCommonHandlers,
+    sshKeyTypeHandlers: VaultAddEditSshKeyTypeHandlers,
+) {
+    item {
+        Spacer(modifier = Modifier.height(8.dp))
+        BitwardenTextField(
+            label = stringResource(id = R.string.name),
+            value = commonState.name,
+            onValueChange = commonTypeHandlers.onNameTextChange,
+            modifier = Modifier
+                .testTag("ItemNameEntry")
+                .fillMaxWidth()
+                .standardHorizontalMargin(),
+        )
+    }
+
+    item {
+        Spacer(modifier = Modifier.height(8.dp))
+        BitwardenTextField(
+            label = stringResource(id = R.string.public_key),
+            value = sshKeyState.publicKey,
+            onValueChange = sshKeyTypeHandlers.onPublicKeyTextChange,
+            modifier = Modifier
+                .testTag("PublicKeyEntry")
+                .fillMaxWidth()
+                .standardHorizontalMargin(),
+        )
+    }
+
+    item {
+        Spacer(modifier = Modifier.height(8.dp))
+        BitwardenPasswordField(
+            label = stringResource(id = R.string.private_key),
+            value = sshKeyState.privateKey,
+            onValueChange = sshKeyTypeHandlers.onPrivateKeyTextChange,
+            showPassword = sshKeyState.showPrivateKey,
+            showPasswordChange = { sshKeyTypeHandlers.onPrivateKeyVisibilityChange(it) },
+            showPasswordTestTag = "ViewPrivateKeyButton",
+            modifier = Modifier
+                .testTag("PrivateKeyEntry")
+                .fillMaxWidth()
+                .standardHorizontalMargin(),
+        )
+    }
+
+    item {
+        Spacer(modifier = Modifier.height(8.dp))
+        BitwardenTextField(
+            label = stringResource(id = R.string.fingerprint),
+            value = sshKeyState.fingerprint,
+            onValueChange = sshKeyTypeHandlers.onFingerprintTextChange,
+            modifier = Modifier
+                .testTag("FingerprintEntry")
+                .fillMaxWidth()
+                .standardHorizontalMargin(),
+        )
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun VaultAddEditSshKeyItems_preview() {
+    BitwardenTheme {
+        LazyColumn {
+            vaultAddEditSshKeyItems(
+                commonState = VaultAddEditState.ViewState.Content.Common(
+                    name = "SSH Key",
+                ),
+                sshKeyState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                    publicKey = "public key",
+                    privateKey = "private key",
+                    fingerprint = "fingerprint",
+                    showPublicKey = false,
+                    showPrivateKey = false,
+                    showFingerprint = false,
+                ),
+                commonTypeHandlers = VaultAddEditCommonHandlers(
+                    onNameTextChange = { },
+                    onFolderSelected = { },
+                    onToggleFavorite = { },
+                    onToggleMasterPasswordReprompt = { },
+                    onNotesTextChange = { },
+                    onOwnerSelected = { },
+                    onTooltipClick = { },
+                    onAddNewCustomFieldClick = { _, _ -> },
+                    onCustomFieldValueChange = { },
+                    onCustomFieldActionSelect = { _, _ -> },
+                    onCollectionSelect = { },
+                    onHiddenFieldVisibilityChange = { },
+                ),
+                sshKeyTypeHandlers = VaultAddEditSshKeyTypeHandlers(
+                    onPublicKeyTextChange = { },
+                    onPrivateKeyTextChange = { },
+                    onPrivateKeyVisibilityChange = { },
+                    onFingerprintTextChange = { },
+                ),
+            )
+        }
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt
index 1aa9daa52..3c2e0b6e3 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt
@@ -16,10 +16,12 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
 import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
 import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
 import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
+import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
 import com.x8bit.bitwarden.data.platform.manager.PolicyManager
 import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
 import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
 import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
+import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
 import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
 import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
 import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
@@ -101,6 +103,7 @@ class VaultAddEditViewModel @Inject constructor(
     private val resourceManager: ResourceManager,
     private val clock: Clock,
     private val organizationEventManager: OrganizationEventManager,
+    private val featureFlagManager: FeatureFlagManager,
 ) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
     // We load the state from the savedStateHandle for testing purposes.
     initialState = savedStateHandle[KEY_STATE]
@@ -162,6 +165,11 @@ class VaultAddEditViewModel @Inject constructor(
                 // Set special conditions for autofill and fido2 save
                 shouldShowCloseButton = autofillSaveItem == null && fido2AttestationOptions == null,
                 shouldExitOnSave = shouldExitOnSave,
+                supportedItemTypes = getSupportedItemTypeOptions(
+                    isSshKeyVaultItemSupported = featureFlagManager.getFeatureFlag(
+                        key = FlagKey.SshKeyCipherItems,
+                    ),
+                ),
             )
         },
 ) {
@@ -203,6 +211,11 @@ class VaultAddEditViewModel @Inject constructor(
             }
             .onEach(::sendAction)
             .launchIn(viewModelScope)
+
+        featureFlagManager.getFeatureFlagFlow(FlagKey.SshKeyCipherItems)
+            .map { VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive(it) }
+            .onEach(::sendAction)
+            .launchIn(viewModelScope)
     }
 
     override fun handleAction(action: VaultAddEditAction) {
@@ -211,6 +224,7 @@ class VaultAddEditViewModel @Inject constructor(
             is VaultAddEditAction.ItemType.LoginType -> handleAddLoginTypeAction(action)
             is VaultAddEditAction.ItemType.IdentityType -> handleIdentityTypeActions(action)
             is VaultAddEditAction.ItemType.CardType -> handleCardTypeActions(action)
+            is VaultAddEditAction.ItemType.SshKeyType -> handleSshKeyTypeActions(action)
             is VaultAddEditAction.Internal -> handleInternalActions(action)
         }
     }
@@ -320,6 +334,7 @@ class VaultAddEditViewModel @Inject constructor(
             VaultAddEditState.ItemTypeOption.CARD -> handleSwitchToAddCardItem()
             VaultAddEditState.ItemTypeOption.IDENTITY -> handleSwitchToAddIdentityItem()
             VaultAddEditState.ItemTypeOption.SECURE_NOTES -> handleSwitchToAddSecureNotesItem()
+            VaultAddEditState.ItemTypeOption.SSH_KEYS -> handleSwitchToSshKeyItem()
         }
     }
 
@@ -371,6 +386,18 @@ class VaultAddEditViewModel @Inject constructor(
         }
     }
 
+    private fun handleSwitchToSshKeyItem() {
+        updateContent { currentContent ->
+            currentContent.copy(
+                common = currentContent.clearNonSharedData(),
+                type = currentContent.previousItemTypeOrDefault(
+                    itemType = VaultAddEditState.ItemTypeOption.SSH_KEYS,
+                ),
+                previousItemTypes = currentContent.toUpdatedPreviousItemTypes(),
+            )
+        }
+    }
+
     @Suppress("LongMethod")
     private fun handleSaveClick() = onContent { content ->
         if (content.common.name.isBlank()) {
@@ -1363,6 +1390,54 @@ class VaultAddEditViewModel @Inject constructor(
 
     //endregion Card Type Handlers
 
+    //region SSH Key Type Handlers
+
+    private fun handleSshKeyTypeActions(action: VaultAddEditAction.ItemType.SshKeyType) {
+        when (action) {
+            is VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange -> {
+                handlePublicKeyTextChange(action)
+            }
+
+            is VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange -> {
+                handlePrivateKeyTextChange(action)
+            }
+
+            is VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange -> {
+                handlePrivateKeyVisibilityChange(action)
+            }
+
+            is VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange -> {
+                handleSshKeyFingerprintTextChange(action)
+            }
+        }
+    }
+
+    private fun handlePublicKeyTextChange(
+        action: VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange,
+    ) {
+        updateSshKeyContent { it.copy(publicKey = action.publicKey) }
+    }
+
+    private fun handlePrivateKeyTextChange(
+        action: VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange,
+    ) {
+        updateSshKeyContent { it.copy(privateKey = action.privateKey) }
+    }
+
+    private fun handlePrivateKeyVisibilityChange(
+        action: VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange,
+    ) {
+        updateSshKeyContent { it.copy(showPrivateKey = action.isVisible) }
+    }
+
+    private fun handleSshKeyFingerprintTextChange(
+        action: VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange,
+    ) {
+        updateSshKeyContent { it.copy(fingerprint = action.fingerprint) }
+    }
+
+    //endregion SSH Key Type Handlers
+
     //region Internal Type Handlers
 
     private fun handleInternalActions(action: VaultAddEditAction.Internal) {
@@ -1397,6 +1472,10 @@ class VaultAddEditViewModel @Inject constructor(
             is VaultAddEditAction.Internal.ValidateFido2PinResultReceive -> {
                 handleValidateFido2PinResultReceive(action)
             }
+
+            is VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive -> {
+                handleSshKeyCipherItemsFeatureFlagReceive(action)
+            }
         }
     }
 
@@ -1707,6 +1786,18 @@ class VaultAddEditViewModel @Inject constructor(
         getRequestAndRegisterCredential()
     }
 
+    private fun handleSshKeyCipherItemsFeatureFlagReceive(
+        action: VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive,
+    ) {
+        mutableStateFlow.update {
+            it.copy(
+                supportedItemTypes = getSupportedItemTypeOptions(
+                    isSshKeyVaultItemSupported = action.enabled,
+                ),
+            )
+        }
+    }
+
     //endregion Internal Type Handlers
 
     //region Utility Functions
@@ -1818,6 +1909,19 @@ class VaultAddEditViewModel @Inject constructor(
         }
     }
 
+    private inline fun updateSshKeyContent(
+        crossinline block: (VaultAddEditState.ViewState.Content.ItemType.SshKey) ->
+        VaultAddEditState.ViewState.Content.ItemType.SshKey,
+    ) {
+        updateContent { currentContent ->
+            (currentContent.type as? VaultAddEditState.ViewState.Content.ItemType.SshKey)?.let {
+                currentContent.copy(
+                    type = block(it),
+                )
+            }
+        }
+    }
+
     private fun VaultAddEditState.ViewState.Content.clearNonSharedData():
         VaultAddEditState.ViewState.Content.Common =
         common.copy(
@@ -1852,6 +1956,10 @@ class VaultAddEditViewModel @Inject constructor(
                 VaultAddEditState.ItemTypeOption.SECURE_NOTES -> {
                     VaultAddEditState.ViewState.Content.ItemType.SecureNotes
                 }
+
+                VaultAddEditState.ItemTypeOption.SSH_KEYS -> {
+                    VaultAddEditState.ViewState.Content.ItemType.SshKey()
+                }
             },
         )
 
@@ -1911,6 +2019,7 @@ data class VaultAddEditState(
     val viewState: ViewState,
     val dialog: DialogState?,
     val shouldShowCloseButton: Boolean = true,
+    val supportedItemTypes: List<ItemTypeOption>,
     // Internal
     val shouldExitOnSave: Boolean = false,
     val totpData: TotpData? = null,
@@ -1957,6 +2066,7 @@ data class VaultAddEditState(
         CARD(R.string.type_card),
         IDENTITY(R.string.type_identity),
         SECURE_NOTES(R.string.type_secure_note),
+        SSH_KEYS(R.string.type_ssh_key),
     }
 
     /**
@@ -2162,6 +2272,25 @@ data class VaultAddEditState(
                 data object SecureNotes : ItemType() {
                     override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SECURE_NOTES
                 }
+
+                /**
+                 * Represents the `SshKey` item type.
+                 *
+                 * @property publicKey The public key for the SSH key item.
+                 * @property privateKey The private key for the SSH key item.
+                 * @property fingerprint The fingerprint for the SSH key item.
+                 */
+                @Parcelize
+                data class SshKey(
+                    val publicKey: String = "",
+                    val privateKey: String = "",
+                    val fingerprint: String = "",
+                    val showPublicKey: Boolean = false,
+                    val showPrivateKey: Boolean = false,
+                    val showFingerprint: Boolean = false,
+                ) : ItemType() {
+                    override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SSH_KEYS
+                }
             }
 
             /**
@@ -2928,6 +3057,31 @@ sealed class VaultAddEditAction {
              */
             data class SecurityCodeVisibilityChange(val isVisible: Boolean) : CardType()
         }
+
+        /**
+         * Represents actions specific to the SSH Key type.
+         */
+        sealed class SshKeyType : ItemType() {
+            /**
+             * Fired when the public key text input is changed.
+             */
+            data class PublicKeyTextChange(val publicKey: String) : SshKeyType()
+
+            /**
+             * Fired when the private key text input is changed.
+             */
+            data class PrivateKeyTextChange(val privateKey: String) : SshKeyType()
+
+            /**
+             * Fired when the private key's visibility has changed.
+             */
+            data class PrivateKeyVisibilityChange(val isVisible: Boolean) : SshKeyType()
+
+            /**
+             * Fired when the fingerprint text input is changed.
+             */
+            data class FingerprintTextChange(val fingerprint: String) : SshKeyType()
+        }
     }
 
     /**
@@ -2952,6 +3106,13 @@ sealed class VaultAddEditAction {
             val generatorResult: GeneratorResult,
         ) : Internal()
 
+        /**
+         * Indicates that the the SSH key vault item feature flag state has been received.
+         */
+        data class SshKeyCipherItemsFeatureFlagReceive(
+            val enabled: Boolean,
+        ) : Internal()
+
         /**
          * Indicates that the vault item data has been received.
          */
@@ -3005,3 +3166,8 @@ sealed class VaultAddEditAction {
         ) : Internal()
     }
 }
+
+private fun getSupportedItemTypeOptions(
+    isSshKeyVaultItemSupported: Boolean,
+) = VaultAddEditState.ItemTypeOption.entries
+    .filter { isSshKeyVaultItemSupported || it != VaultAddEditState.ItemTypeOption.SSH_KEYS }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditSshKeyTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditSshKeyTypeHandlers.kt
new file mode 100644
index 000000000..3fb197d9c
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditSshKeyTypeHandlers.kt
@@ -0,0 +1,64 @@
+package com.x8bit.bitwarden.ui.vault.feature.addedit.handlers
+
+import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditAction
+import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
+
+/**
+ * Provides a set of handlers for interactions related to SSH key types within the vault add/edit
+ * screen.
+ *
+ * These handlers are used to update the ViewModel with user actions such as text changes and
+ * visibility changes for different SSH key fields (public key, private key, fingerprint).
+ *
+ * @property onPublicKeyTextChange Handler for changes in the public key text field.
+ * @property onPrivateKeyTextChange Handler for changes in the private key text field.
+ * @property onPrivateKeyVisibilityChange Handler for toggling the visibility of the private key.
+ * @property onFingerprintTextChange Handler for changes in the fingerprint text field.
+ */
+data class VaultAddEditSshKeyTypeHandlers(
+    val onPublicKeyTextChange: (String) -> Unit,
+    val onPrivateKeyTextChange: (String) -> Unit,
+    val onPrivateKeyVisibilityChange: (Boolean) -> Unit,
+    val onFingerprintTextChange: (String) -> Unit,
+) {
+    @Suppress("UndocumentedPublicClass")
+    companion object {
+        /**
+         * Creates an instance of [VaultAddEditSshKeyTypeHandlers] with handlers that dispatch
+         * actions to the provided ViewModel.
+         *
+         * @param viewModel The ViewModel to which actions will be dispatched.
+         */
+        fun create(viewModel: VaultAddEditViewModel): VaultAddEditSshKeyTypeHandlers =
+            VaultAddEditSshKeyTypeHandlers(
+                onPublicKeyTextChange = { newPublicKey ->
+                    viewModel.trySendAction(
+                        VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange(
+                            publicKey = newPublicKey,
+                        ),
+                    )
+                },
+                onPrivateKeyTextChange = { newPrivateKey ->
+                    viewModel.trySendAction(
+                        VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange(
+                            privateKey = newPrivateKey,
+                        ),
+                    )
+                },
+                onPrivateKeyVisibilityChange = {
+                    viewModel.trySendAction(
+                        VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange(
+                            isVisible = it,
+                        ),
+                    )
+                },
+                onFingerprintTextChange = { newFingerprint ->
+                    viewModel.trySendAction(
+                        VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange(
+                            fingerprint = newFingerprint,
+                        ),
+                    )
+                },
+            )
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt
index 70306669c..0163a1b7e 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt
@@ -66,4 +66,5 @@ private val VaultAddEditState.ViewState.Content.ItemType.defaultLinkedFieldTypeO
         is VaultAddEditState.ViewState.Content.ItemType.Identity -> VaultLinkedFieldType.TITLE
         is VaultAddEditState.ViewState.Content.ItemType.Login -> VaultLinkedFieldType.USERNAME
         is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> null
+        is VaultAddEditState.ViewState.Content.ItemType.SshKey -> null
     }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt
index 62af2170e..749e8e051 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensions.kt
@@ -35,6 +35,7 @@ private const val PASSKEY_CREATION_TIME_PATTERN: String = "hh:mm a"
 /**
  * Transforms [CipherView] into [VaultAddEditState.ViewState].
  */
+@Suppress("LongMethod")
 fun CipherView.toViewState(
     isClone: Boolean,
     isIndividualVaultDisabled: Boolean,
@@ -88,6 +89,12 @@ fun CipherView.toViewState(
                 zip = identity?.postalCode.orEmpty(),
                 country = identity?.country.orEmpty(),
             )
+
+            CipherType.SSH_KEY -> VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                publicKey = sshKey?.publicKey.orEmpty(),
+                privateKey = sshKey?.privateKey.orEmpty(),
+                fingerprint = sshKey?.fingerprint.orEmpty(),
+            )
         },
         common = VaultAddEditState.ViewState.Content.Common(
             originalCipher = this,
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensions.kt
index 807c43f22..7a637b6bd 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensions.kt
@@ -25,4 +25,5 @@ fun VaultItemCipherType.toItemType(): VaultAddEditState.ViewState.Content.ItemTy
         VaultItemCipherType.CARD -> VaultAddEditState.ViewState.Content.ItemType.Card()
         VaultItemCipherType.IDENTITY -> VaultAddEditState.ViewState.Content.ItemType.Identity()
         VaultItemCipherType.SECURE_NOTE -> VaultAddEditState.ViewState.Content.ItemType.SecureNotes
+        VaultItemCipherType.SSH_KEY -> VaultAddEditState.ViewState.Content.ItemType.SshKey()
     }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt
index 38965e384..63011234f 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt
@@ -45,6 +45,7 @@ import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
 import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCardItemTypeHandlers
 import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCommonItemTypeHandlers
 import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultLoginItemTypeHandlers
+import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultSshKeyItemTypeHandlers
 
 /**
  * Displays the vault item screen.
@@ -265,6 +266,9 @@ fun VaultItemScreen(
             vaultCardItemTypeHandlers = remember(viewModel) {
                 VaultCardItemTypeHandlers.create(viewModel = viewModel)
             },
+            vaultSshKeyItemTypeHandlers = remember(viewModel) {
+                VaultSshKeyItemTypeHandlers.create(viewModel = viewModel)
+            },
         )
     }
 }
@@ -342,6 +346,7 @@ private fun VaultItemContent(
     vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers,
     vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers,
     vaultCardItemTypeHandlers: VaultCardItemTypeHandlers,
+    vaultSshKeyItemTypeHandlers: VaultSshKeyItemTypeHandlers,
     modifier: Modifier = Modifier,
 ) {
     when (viewState) {
@@ -389,6 +394,15 @@ private fun VaultItemContent(
                         modifier = modifier,
                     )
                 }
+
+                is VaultItemState.ViewState.Content.ItemType.SshKey -> {
+                    VaultItemSshKeyContent(
+                        commonState = viewState.common,
+                        sshKeyItemState = viewState.type,
+                        vaultSshKeyItemTypeHandlers = vaultSshKeyItemTypeHandlers,
+                        modifier = modifier,
+                    )
+                }
             }
         }
 
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt
new file mode 100644
index 000000000..bac0d17c5
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt
@@ -0,0 +1,127 @@
+package com.x8bit.bitwarden.ui.vault.feature.item
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.x8bit.bitwarden.R
+import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
+import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
+import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
+import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultSshKeyItemTypeHandlers
+
+/**
+ * The top level content UI state for the [VaultItemScreen] when viewing a SSH key cipher.
+ */
+@Suppress("LongMethod")
+@Composable
+fun VaultItemSshKeyContent(
+    commonState: VaultItemState.ViewState.Content.Common,
+    sshKeyItemState: VaultItemState.ViewState.Content.ItemType.SshKey,
+    vaultSshKeyItemTypeHandlers: VaultSshKeyItemTypeHandlers,
+    modifier: Modifier = Modifier,
+) {
+    LazyColumn(modifier = modifier) {
+        item {
+            BitwardenListHeaderText(
+                label = stringResource(id = R.string.item_information),
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 16.dp),
+            )
+        }
+
+        item {
+            Spacer(modifier = Modifier.height(8.dp))
+            BitwardenTextField(
+                label = stringResource(id = R.string.name),
+                value = commonState.name,
+                onValueChange = { },
+                readOnly = true,
+                singleLine = false,
+                modifier = Modifier
+                    .testTag("SshKeyItemNameEntry")
+                    .fillMaxWidth()
+                    .padding(horizontal = 16.dp),
+            )
+        }
+
+        sshKeyItemState.publicKey?.let { publicKey ->
+            item {
+                Spacer(modifier = Modifier.height(8.dp))
+                BitwardenTextField(
+                    label = stringResource(id = R.string.public_key),
+                    value = publicKey,
+                    onValueChange = { },
+                    singleLine = false,
+                    readOnly = true,
+                    modifier = Modifier
+                        .testTag("SshKeyItemPublicKeyEntry")
+                        .fillMaxWidth()
+                        .padding(horizontal = 16.dp),
+                )
+            }
+        }
+
+        sshKeyItemState.privateKey?.let { privateKey ->
+            item {
+                Spacer(modifier = Modifier.height(8.dp))
+                BitwardenPasswordField(
+                    label = stringResource(id = R.string.private_key),
+                    value = privateKey,
+                    onValueChange = { },
+                    singleLine = false,
+                    readOnly = true,
+                    showPassword = sshKeyItemState.showPrivateKey,
+                    showPasswordTestTag = "ViewPrivateKeyButton",
+                    showPasswordChange = vaultSshKeyItemTypeHandlers.onShowPrivateKeyClick,
+                    modifier = Modifier
+                        .testTag("SshKeyItemPrivateKeyEntry")
+                        .fillMaxWidth()
+                        .padding(horizontal = 16.dp),
+                )
+            }
+        }
+
+        sshKeyItemState.fingerprint?.let { fingerprint ->
+            item {
+                Spacer(modifier = Modifier.height(8.dp))
+                BitwardenTextField(
+                    label = stringResource(id = R.string.fingerprint),
+                    value = fingerprint,
+                    onValueChange = { },
+                    singleLine = false,
+                    readOnly = true,
+                    modifier = Modifier
+                        .testTag("SshKeyItemFingerprintEntry")
+                        .fillMaxWidth()
+                        .padding(horizontal = 16.dp),
+                )
+            }
+        }
+
+        item {
+            Spacer(modifier = Modifier.height(24.dp))
+            VaultItemUpdateText(
+                header = "${stringResource(id = R.string.date_updated)}: ",
+                text = commonState.lastUpdated,
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(horizontal = 16.dp)
+                    .testTag("SshKeyItemLastUpdated"),
+            )
+        }
+
+        item {
+            Spacer(modifier = Modifier.height(88.dp))
+            Spacer(modifier = Modifier.navigationBarsPadding())
+        }
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
index df774ff66..72063980c 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
@@ -116,6 +116,7 @@ class VaultItemViewModel @Inject constructor(
         when (action) {
             is VaultItemAction.ItemType.Login -> handleLoginTypeActions(action)
             is VaultItemAction.ItemType.Card -> handleCardTypeActions(action)
+            is VaultItemAction.ItemType.SshKey -> handleSshKeyTypeActions(action)
             is VaultItemAction.Common -> handleCommonActions(action)
             is VaultItemAction.Internal -> handleInternalAction(action)
         }
@@ -753,6 +754,32 @@ class VaultItemViewModel @Inject constructor(
 
     //endregion Card Type Handlers
 
+    //region SSH Key Type Handlers
+
+    private fun handleSshKeyTypeActions(action: VaultItemAction.ItemType.SshKey) {
+        when (action) {
+            is VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked -> {
+                handlePrivateKeyVisibilityClicked(action)
+            }
+        }
+    }
+
+    private fun handlePrivateKeyVisibilityClicked(
+        action: VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked,
+    ) {
+        onSshKeyContent { content, sshKey ->
+            mutableStateFlow.update { currentState ->
+                currentState.copy(
+                    viewState = content.copy(
+                        type = sshKey.copy(showPrivateKey = action.isVisible),
+                    ),
+                )
+            }
+        }
+    }
+
+    //endregion SSH Key Type Handlers
+
     //region Internal Type Handlers
 
     private fun handleInternalAction(action: VaultItemAction.Internal) {
@@ -1057,6 +1084,21 @@ class VaultItemViewModel @Inject constructor(
                     }
             }
     }
+
+    private inline fun onSshKeyContent(
+        crossinline block: (
+            VaultItemState.ViewState.Content,
+            VaultItemState.ViewState.Content.ItemType.SshKey,
+        ) -> Unit,
+    ) {
+        state.viewState.asContentOrNull()
+            ?.let { content ->
+                (content.type as? VaultItemState.ViewState.Content.ItemType.SshKey)
+                    ?.let { sshKeyContent ->
+                        block(content, sshKeyContent)
+                    }
+            }
+    }
 }
 
 /**
@@ -1359,6 +1401,20 @@ data class VaultItemState(
                         val isVisible: Boolean,
                     ) : Parcelable
                 }
+
+                /**
+                 * Represents the data for displaying an `SSHKey` item type.
+                 *
+                 * @property name The name of the key.
+                 * @property privateKey The SSH private key.
+                 */
+                data class SshKey(
+                    val name: String?,
+                    val publicKey: String?,
+                    val privateKey: String?,
+                    val fingerprint: String?,
+                    val showPrivateKey: Boolean,
+                ) : ItemType()
             }
         }
 
@@ -1697,6 +1753,16 @@ sealed class VaultItemAction {
              */
             data class NumberVisibilityClick(val isVisible: Boolean) : Card()
         }
+
+        /**
+         * Represents actions specific to the SshKey type.
+         */
+        sealed class SshKey : ItemType() {
+            /**
+             * The user has clicked to display the private key.
+             */
+            data class PrivateKeyVisibilityClicked(val isVisible: Boolean) : SshKey()
+        }
     }
 
     /**
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultSshKeyItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultSshKeyItemTypeHandlers.kt
new file mode 100644
index 000000000..fc4e1411b
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultSshKeyItemTypeHandlers.kt
@@ -0,0 +1,32 @@
+package com.x8bit.bitwarden.ui.vault.feature.item.handlers
+
+import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemAction
+import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemViewModel
+
+/**
+ * A collection of handler functions for managing actions within the context of viewing SSH key
+ * items in a vault.
+ */
+data class VaultSshKeyItemTypeHandlers(
+    val onShowPrivateKeyClick: (isVisible: Boolean) -> Unit,
+) {
+
+    @Suppress("UndocumentedPublicClass")
+    companion object {
+
+        /**
+         * Creates the [VaultSshKeyItemTypeHandlers] using the [viewModel] to send desired actions.
+         */
+        @Suppress("LongMethod")
+        fun create(viewModel: VaultItemViewModel): VaultSshKeyItemTypeHandlers =
+            VaultSshKeyItemTypeHandlers(
+                onShowPrivateKeyClick = {
+                    viewModel.trySendAction(
+                        VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked(
+                            isVisible = it,
+                        ),
+                    )
+                },
+            )
+    }
+}
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt
index 108e1cb76..e4c46f592 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt
@@ -156,6 +156,19 @@ fun CipherView.toViewState(
                     address = identity?.identityAddress,
                 )
             }
+
+            CipherType.SSH_KEY -> {
+                val sshKeyValues = requireNotNull(sshKey)
+                VaultItemState.ViewState.Content.ItemType.SshKey(
+                    name = name,
+                    publicKey = sshKeyValues.publicKey,
+                    privateKey = sshKeyValues.privateKey,
+                    fingerprint = sshKeyValues.fingerprint,
+                    showPrivateKey = (previousState?.type as?
+                        VaultItemState.ViewState.Content.ItemType.SshKey)
+                        ?.showPrivateKey == true,
+                )
+            }
         },
     )
 
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt
index daf653fed..b076c532a 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt
@@ -18,6 +18,7 @@ private const val COLLECTION: String = "collection"
 private const val FOLDER: String = "folder"
 private const val IDENTITY: String = "identity"
 private const val LOGIN: String = "login"
+private const val SSH_KEY: String = "ssh_key"
 private const val SECURE_NOTE: String = "secure_note"
 private const val SEND_FILE: String = "send_file"
 private const val SEND_TEXT: String = "send_text"
@@ -234,6 +235,7 @@ private fun VaultItemListingType.toTypeString(): String {
         is VaultItemListingType.Trash -> TRASH
         is VaultItemListingType.SendFile -> SEND_FILE
         is VaultItemListingType.SendText -> SEND_TEXT
+        is VaultItemListingType.SshKey -> SSH_KEY
     }
 }
 
@@ -248,6 +250,7 @@ private fun VaultItemListingType.toIdOrNull(): String? =
         is VaultItemListingType.Trash -> null
         is VaultItemListingType.SendFile -> null
         is VaultItemListingType.SendText -> null
+        is VaultItemListingType.SshKey -> null
     }
 
 private fun determineVaultItemListingType(
@@ -259,6 +262,7 @@ private fun determineVaultItemListingType(
         CARD -> VaultItemListingType.Card
         IDENTITY -> VaultItemListingType.Identity
         SECURE_NOTE -> VaultItemListingType.SecureNote
+        SSH_KEY -> VaultItemListingType.SshKey
         TRASH -> VaultItemListingType.Trash
         FOLDER -> VaultItemListingType.Folder(folderId = id)
         COLLECTION -> VaultItemListingType.Collection(collectionId = requireNotNull(id))
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt
index d0db9cb6e..e1e8084ef 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt
@@ -2058,6 +2058,14 @@ data class VaultItemListingState(
                 override val hasFab: Boolean get() = true
             }
 
+            /**
+             * A SSH key item listing.
+             */
+            data object SshKey : Vault() {
+                override val titleText: Text get() = R.string.ssh_keys.asText()
+                override val hasFab: Boolean get() = true
+            }
+
             /**
              * A Secure Trash item listing.
              */
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt
index 148dc3cf2..dfc1f4fcf 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt
@@ -70,6 +70,10 @@ fun CipherView.determineListingPredicate(
             type == CipherType.SECURE_NOTE && deletedDate == null
         }
 
+        is VaultItemListingState.ItemListingType.Vault.SshKey -> {
+            type == CipherType.SSH_KEY && deletedDate == null
+        }
+
         is VaultItemListingState.ItemListingType.Vault.Trash -> {
             deletedDate != null
         }
@@ -272,6 +276,7 @@ fun VaultItemListingState.ItemListingType.updateWithAdditionalDataIfNecessary(
         is VaultItemListingState.ItemListingType.Vault.Trash -> this
         is VaultItemListingState.ItemListingType.Send.SendFile -> this
         is VaultItemListingState.ItemListingType.Send.SendText -> this
+        is VaultItemListingState.ItemListingType.Vault.SshKey -> this
     }
 
 @Suppress("LongParameterList")
@@ -374,6 +379,7 @@ private fun CipherView.toIconTestTag(): String =
         CipherType.SECURE_NOTE -> "SecureNoteCipherIcon"
         CipherType.CARD -> "CardCipherIcon"
         CipherType.IDENTITY -> "IdentityCipherIcon"
+        CipherType.SSH_KEY -> "SshKeyCipherIcon"
     }
 
 private fun CipherView.toIconData(
@@ -431,4 +437,5 @@ private val CipherType.iconRes: Int
         CipherType.SECURE_NOTE -> R.drawable.ic_note
         CipherType.CARD -> R.drawable.ic_payment_card
         CipherType.IDENTITY -> R.drawable.ic_id_card
+        CipherType.SSH_KEY -> R.drawable.ic_ssh_key
     }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensions.kt
index 3548da9f3..87814f044 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensions.kt
@@ -19,6 +19,7 @@ fun VaultItemListingState.ItemListingType.toSearchType(): SearchType =
         is VaultItemListingState.ItemListingType.Vault.Identity -> SearchType.Vault.Identities
         is VaultItemListingState.ItemListingType.Vault.Login -> SearchType.Vault.Logins
         is VaultItemListingState.ItemListingType.Vault.SecureNote -> SearchType.Vault.SecureNotes
+        is VaultItemListingState.ItemListingType.Vault.SshKey -> SearchType.Vault.SshKeys
         is VaultItemListingState.ItemListingType.Vault.Trash -> SearchType.Vault.Trash
         is VaultItemListingState.ItemListingType.Vault.Collection -> {
             SearchType.Vault.Collection(collectionId = collectionId)
@@ -36,6 +37,7 @@ fun VaultItemListingState.ItemListingType.Vault.toVaultItemCipherType(): VaultIt
         is VaultItemListingState.ItemListingType.Vault.Card -> VaultItemCipherType.CARD
         is VaultItemListingState.ItemListingType.Vault.Identity -> VaultItemCipherType.IDENTITY
         is VaultItemListingState.ItemListingType.Vault.SecureNote -> VaultItemCipherType.SECURE_NOTE
+        is VaultItemListingState.ItemListingType.Vault.SshKey -> VaultItemCipherType.SSH_KEY
         is VaultItemListingState.ItemListingType.Vault.Login -> VaultItemCipherType.LOGIN
         is VaultItemListingState.ItemListingType.Vault.Collection -> VaultItemCipherType.LOGIN
         is VaultItemListingState.ItemListingType.Vault.Trash,
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt
index 86a99c31b..dd052de8e 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt
@@ -16,6 +16,7 @@ fun VaultItemListingType.toItemListingType(): VaultItemListingState.ItemListingT
         is VaultItemListingType.Identity -> VaultItemListingState.ItemListingType.Vault.Identity
         is VaultItemListingType.Login -> VaultItemListingState.ItemListingType.Vault.Login
         is VaultItemListingType.SecureNote -> VaultItemListingState.ItemListingType.Vault.SecureNote
+        is VaultItemListingType.SshKey -> VaultItemListingState.ItemListingType.Vault.SshKey
         is VaultItemListingType.Trash -> VaultItemListingState.ItemListingType.Vault.Trash
         is VaultItemListingType.Collection -> {
             VaultItemListingState.ItemListingType.Vault.Collection(collectionId = collectionId)
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt
index 5e11a4c53..d40429e9b 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultContent.kt
@@ -31,6 +31,7 @@ fun VaultContent(
     state: VaultState.ViewState.Content,
     vaultHandlers: VaultHandlers,
     onOverflowOptionClick: (action: ListingItemOverflowAction.VaultAction) -> Unit,
+    showSshKeys: Boolean,
     modifier: Modifier = Modifier,
 ) {
     LazyColumn(
@@ -122,7 +123,7 @@ fun VaultContent(
         item {
             BitwardenListHeaderText(
                 label = stringResource(id = R.string.types),
-                supportingLabel = "4",
+                supportingLabel = state.itemTypesCount.toString(),
                 modifier = Modifier
                     .fillMaxWidth()
                     .padding(horizontal = 16.dp),
@@ -193,6 +194,23 @@ fun VaultContent(
             )
         }
 
+        if (showSshKeys) {
+            item {
+                BitwardenGroupItem(
+                    startIcon = rememberVectorPainter(id = R.drawable.ic_ssh_key),
+                    startIconTestTag = "SshKeyCipherIcon",
+                    label = stringResource(id = R.string.type_ssh_key),
+                    supportingLabel = state.sshKeyItemsCount.toString(),
+                    onClick = vaultHandlers.sshKeyGroupClick,
+                    showDivider = false,
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .testTag("SshKeyFilter")
+                        .padding(horizontal = 16.dp),
+                )
+            }
+        }
+
         if (state.folderItems.isNotEmpty()) {
             item {
                 BitwardenHorizontalDivider(
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt
index 9d165acc8..0bbded82d 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt
@@ -321,6 +321,7 @@ private fun VaultScreenScaffold(
                 when (val viewState = state.viewState) {
                     is VaultState.ViewState.Content -> VaultContent(
                         state = viewState,
+                        showSshKeys = state.showSshKeys,
                         vaultHandlers = vaultHandlers,
                         onOverflowOptionClick = { masterPasswordRepromptAction = it },
                         modifier = innerModifier,
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt
index 526898b32..37a321337 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt
@@ -89,6 +89,7 @@ class VaultViewModel @Inject constructor(
                 .any(),
         )
         val appBarTitle = vaultFilterData.toAppBarTitle()
+        val showSshKeys = featureFlagManager.getFeatureFlag(FlagKey.SshKeyCipherItems)
         VaultState(
             appBarTitle = appBarTitle,
             initials = activeAccountSummary.initials,
@@ -104,6 +105,7 @@ class VaultViewModel @Inject constructor(
             hideNotificationsDialog = isBuildVersionBelow(Build.VERSION_CODES.TIRAMISU) || isFdroid,
             isRefreshing = false,
             showImportActionCard = false,
+            showSshKeys = showSshKeys,
         )
     },
 ) {
@@ -131,9 +133,16 @@ class VaultViewModel @Inject constructor(
             .onEach(::sendAction)
             .launchIn(viewModelScope)
 
-        vaultRepository
-            .vaultDataStateFlow
-            .onEach { sendAction(VaultAction.Internal.VaultDataReceive(vaultData = it)) }
+        combine(
+            vaultRepository.vaultDataStateFlow,
+            featureFlagManager.getFeatureFlagFlow(FlagKey.SshKeyCipherItems),
+        ) { vaultData, sshKeyCipherItemsEnabled ->
+            VaultAction.Internal.VaultDataReceive(
+                vaultData = vaultData,
+                showSshKeys = sshKeyCipherItemsEnabled,
+            )
+        }
+            .onEach(::sendAction)
             .launchIn(viewModelScope)
 
         authRepository
@@ -177,6 +186,7 @@ class VaultViewModel @Inject constructor(
             is VaultAction.ExitConfirmationClick -> handleExitConfirmationClick()
             is VaultAction.VaultFilterTypeSelect -> handleVaultFilterTypeSelect(action)
             is VaultAction.SecureNoteGroupClick -> handleSecureNoteClick()
+            is VaultAction.SshKeyGroupClick -> handleSshKeyClick()
             is VaultAction.TrashClick -> handleTrashClick()
             is VaultAction.VaultItemClick -> handleVaultItemClick(action)
             is VaultAction.TryAgainClick -> handleTryAgainClick()
@@ -211,7 +221,10 @@ class VaultViewModel @Inject constructor(
             it.copy(isIconLoadingDisabled = action.isIconLoadingDisabled)
         }
 
-        updateViewState(vaultRepository.vaultDataStateFlow.value)
+        updateViewState(
+            vaultData = vaultRepository.vaultDataStateFlow.value,
+            showSshKeys = state.showSshKeys,
+        )
     }
 
     //region VaultAction Handlers
@@ -311,7 +324,10 @@ class VaultViewModel @Inject constructor(
         }
 
         // Re-process the current vault data with the new filter
-        updateViewState(vaultData = vaultRepository.vaultDataStateFlow.value)
+        updateViewState(
+            vaultData = vaultRepository.vaultDataStateFlow.value,
+            showSshKeys = state.showSshKeys,
+        )
     }
 
     private fun handleTrashClick() {
@@ -322,6 +338,10 @@ class VaultViewModel @Inject constructor(
         sendEvent(VaultEvent.NavigateToItemListing(VaultItemListingType.SecureNote))
     }
 
+    private fun handleSshKeyClick() {
+        sendEvent(VaultEvent.NavigateToItemListing(VaultItemListingType.SshKey))
+    }
+
     private fun handleVaultItemClick(action: VaultAction.VaultItemClick) {
         sendEvent(VaultEvent.NavigateToVaultItem(action.vaultItem.id))
     }
@@ -517,6 +537,7 @@ class VaultViewModel @Inject constructor(
         val appBarTitle = vaultFilterData.toAppBarTitle()
         val shouldShowImportActionCard = action.importLoginsFlowEnabled &&
             firstTimeState.showImportLoginsCard
+
         mutableStateFlow.update {
             val accountSummaries = userState.toAccountSummaries()
             val activeAccountSummary = userState.toActiveAccountSummary()
@@ -537,13 +558,20 @@ class VaultViewModel @Inject constructor(
         // navigating.
         if (state.isSwitchingAccounts) return
 
-        updateViewState(vaultData = action.vaultData)
+        updateViewState(
+            vaultData = action.vaultData,
+            showSshKeys = action.showSshKeys,
+        )
     }
 
-    private fun updateViewState(vaultData: DataState<VaultData>) {
+    private fun updateViewState(vaultData: DataState<VaultData>, showSshKeys: Boolean) {
         when (vaultData) {
             is DataState.Error -> vaultErrorReceive(vaultData = vaultData)
-            is DataState.Loaded -> vaultLoadedReceive(vaultData = vaultData)
+            is DataState.Loaded -> vaultLoadedReceive(
+                vaultData = vaultData,
+                showSshKeys = showSshKeys,
+            )
+
             is DataState.Loading -> vaultLoadingReceive()
             is DataState.NoNetwork -> vaultNoNetworkReceive(vaultData = vaultData)
             is DataState.Pending -> vaultPendingReceive(vaultData = vaultData)
@@ -564,7 +592,7 @@ class VaultViewModel @Inject constructor(
         )
     }
 
-    private fun vaultLoadedReceive(vaultData: DataState.Loaded<VaultData>) {
+    private fun vaultLoadedReceive(vaultData: DataState.Loaded<VaultData>, showSshKeys: Boolean) {
         if (state.dialog == VaultState.DialogState.Syncing) {
             sendEvent(
                 VaultEvent.ShowToast(
@@ -580,9 +608,11 @@ class VaultViewModel @Inject constructor(
                     isPremium = state.isPremium,
                     hasMasterPassword = state.hasMasterPassword,
                     vaultFilterType = vaultFilterTypeOrDefault,
+                    showSshKeys = showSshKeys,
                 ),
                 dialog = null,
                 isRefreshing = false,
+                showSshKeys = showSshKeys,
             )
         }
     }
@@ -614,6 +644,7 @@ class VaultViewModel @Inject constructor(
                     isPremium = state.isPremium,
                     hasMasterPassword = state.hasMasterPassword,
                     vaultFilterType = vaultFilterTypeOrDefault,
+                    showSshKeys = state.showSshKeys,
                 ),
             )
         }
@@ -685,6 +716,7 @@ data class VaultState(
     val hideNotificationsDialog: Boolean,
     val isRefreshing: Boolean,
     val showImportActionCard: Boolean,
+    val showSshKeys: Boolean,
 ) : Parcelable {
 
     /**
@@ -767,11 +799,13 @@ data class VaultState(
          */
         @Parcelize
         data class Content(
+            val itemTypesCount: Int,
             val totpItemsCount: Int,
             val loginItemsCount: Int,
             val cardItemsCount: Int,
             val identityItemsCount: Int,
             val secureNoteItemsCount: Int,
+            val sshKeyItemsCount: Int,
             val favoriteItems: List<VaultItem>,
             val folderItems: List<FolderItem>,
             val noFolderItems: List<VaultItem>,
@@ -944,6 +978,29 @@ data class VaultState(
             ) : VaultItem() {
                 override val supportingLabel: Text? get() = null
             }
+
+            /**
+             * Represents a SSH key item within the vault, designed to store SSH keys.
+             *
+             * @property publicKey The public key associated with this SSH key item.
+             * @property privateKey The private key associated with this SSH key item.
+             * @property fingerprint The fingerprint associated with this SSH key item.
+             */
+            @Parcelize
+            data class SshKey(
+                override val id: String,
+                override val name: Text,
+                override val startIcon: IconData = IconData.Local(R.drawable.ic_ssh_key),
+                override val startIconTestTag: String = "SshKeyCipherIcon",
+                override val extraIconList: List<IconRes> = emptyList(),
+                override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
+                override val shouldShowMasterPasswordReprompt: Boolean,
+                val publicKey: Text?,
+                val privateKey: Text?,
+                val fingerprint: Text?,
+            ) : VaultItem() {
+                override val supportingLabel: Text? get() = null
+            }
         }
     }
 
@@ -1154,6 +1211,11 @@ sealed class VaultAction {
      */
     data object SecureNoteGroupClick : VaultAction()
 
+    /**
+     * User clicked the SSH key types button.
+     */
+    data object SshKeyGroupClick : VaultAction()
+
     /**
      * User clicked the trash button.
      */
@@ -1232,6 +1294,7 @@ sealed class VaultAction {
          */
         data class VaultDataReceive(
             val vaultData: DataState<VaultData>,
+            val showSshKeys: Boolean,
         ) : Internal()
 
         /**
@@ -1272,6 +1335,7 @@ private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
                     hasMasterPassword = hasMasterPassword,
                     vaultFilterType = vaultFilterType,
                     isIconLoadingDisabled = isIconLoadingDisabled,
+                    showSshKeys = it.showSshKeys,
                 ),
                 dialog = VaultState.DialogState.Error(
                     title = errorTitle,
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt
index 3e1106d62..0c257640c 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/handlers/VaultHandlers.kt
@@ -29,6 +29,7 @@ data class VaultHandlers(
     val cardGroupClick: () -> Unit,
     val identityGroupClick: () -> Unit,
     val secureNoteGroupClick: () -> Unit,
+    val sshKeyGroupClick: () -> Unit,
     val trashClick: () -> Unit,
     val tryAgainClick: () -> Unit,
     val dialogDismiss: () -> Unit,
@@ -77,6 +78,7 @@ data class VaultHandlers(
                 secureNoteGroupClick = {
                     viewModel.trySendAction(VaultAction.SecureNoteGroupClick)
                 },
+                sshKeyGroupClick = { viewModel.trySendAction(VaultAction.SshKeyGroupClick) },
                 trashClick = { viewModel.trySendAction(VaultAction.TrashClick) },
                 tryAgainClick = { viewModel.trySendAction(VaultAction.TryAgainClick) },
                 dialogDismiss = { viewModel.trySendAction(VaultAction.DialogDismiss) },
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt
index 4c4d64297..78b4ea852 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt
@@ -15,6 +15,7 @@ import com.bitwarden.vault.LoginView
 import com.bitwarden.vault.PasswordHistoryView
 import com.bitwarden.vault.SecureNoteType
 import com.bitwarden.vault.SecureNoteView
+import com.bitwarden.vault.SshKeyView
 import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
 import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
 import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
@@ -49,6 +50,7 @@ fun VaultAddEditState.ViewState.Content.toCipherView(): CipherView =
         secureNote = type.toSecureNotesView(),
         login = type.toLoginView(common = common),
         card = type.toCardView(),
+        sshKey = type.toSshKeyView(),
 
         // Fields we always grab from the UI
         name = common.name,
@@ -66,6 +68,16 @@ private fun VaultAddEditState.ViewState.Content.ItemType.toCipherType(): CipherT
         is VaultAddEditState.ViewState.Content.ItemType.Identity -> CipherType.IDENTITY
         is VaultAddEditState.ViewState.Content.ItemType.Login -> CipherType.LOGIN
         is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> CipherType.SECURE_NOTE
+        is VaultAddEditState.ViewState.Content.ItemType.SshKey -> CipherType.SSH_KEY
+    }
+
+private fun VaultAddEditState.ViewState.Content.ItemType.toSshKeyView(): SshKeyView? =
+    (this as? VaultAddEditState.ViewState.Content.ItemType.SshKey)?.let {
+        SshKeyView(
+            publicKey = it.publicKey.orNullIfBlank(),
+            privateKey = it.privateKey.orNullIfBlank(),
+            fingerprint = it.fingerprint.orNullIfBlank(),
+        )
     }
 
 private fun VaultAddEditState.ViewState.Content.ItemType.toCardView(): CardView? =
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt
index 6b087bd18..b4acd8860 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt
@@ -32,13 +32,14 @@ private const val NO_FOLDER_ITEM_THRESHOLD: Int = 100
 /**
  * Transforms [VaultData] into [VaultState.ViewState] using the given [vaultFilterType].
  */
-@Suppress("LongMethod")
+@Suppress("LongMethod", "LongParameterList")
 fun VaultData.toViewState(
     isPremium: Boolean,
     hasMasterPassword: Boolean,
     isIconLoadingDisabled: Boolean,
     baseIconUrl: String,
     vaultFilterType: VaultFilterType,
+    showSshKeys: Boolean,
 ): VaultState.ViewState {
 
     val filteredCipherViewListWithDeletedItems =
@@ -46,6 +47,7 @@ fun VaultData.toViewState(
 
     val filteredCipherViewList = filteredCipherViewListWithDeletedItems
         .filter { it.deletedDate == null }
+        .filterSshKeysIfNecessary(showSshKeys)
 
     val filteredFolderViewList = folderViewList
         .toFilteredList(
@@ -61,11 +63,19 @@ fun VaultData.toViewState(
     val noFolderItems = filteredCipherViewList
         .filter { it.folderId.isNullOrBlank() }
 
+    val itemTypesCount: Int = if (showSshKeys) {
+        CipherType.entries
+    } else {
+        CipherType.entries.filterNot { it == CipherType.SSH_KEY }
+    }
+        .size
+
     return if (filteredCipherViewListWithDeletedItems.isEmpty()) {
         VaultState.ViewState.NoItems
     } else {
         val totpItems = filteredCipherViewList.filter { it.login?.totp != null }
         VaultState.ViewState.Content(
+            itemTypesCount = itemTypesCount,
             totpItemsCount = if (isPremium) {
                 totpItems.count()
             } else {
@@ -76,6 +86,7 @@ fun VaultData.toViewState(
             identityItemsCount = filteredCipherViewList.count { it.type == CipherType.IDENTITY },
             secureNoteItemsCount = filteredCipherViewList
                 .count { it.type == CipherType.SECURE_NOTE },
+            sshKeyItemsCount = filteredCipherViewList.count { it.type == CipherType.SSH_KEY },
             favoriteItems = filteredCipherViewList
                 .filter { it.favorite }
                 .mapNotNull {
@@ -259,6 +270,26 @@ private fun CipherView.toVaultItemOrNull(
             extraIconList = toLabelIcons(),
             shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
         )
+
+        CipherType.SSH_KEY -> VaultState.ViewState.VaultItem.SshKey(
+            id = id,
+            name = name.asText(),
+            publicKey = sshKey
+                ?.publicKey
+                ?.asText(),
+            privateKey = sshKey
+                ?.privateKey
+                ?.asText(),
+            fingerprint = sshKey
+                ?.fingerprint
+                ?.asText(),
+            overflowOptions = toOverflowActions(
+                hasMasterPassword = hasMasterPassword,
+                isPremiumUser = isPremiumUser,
+            ),
+            extraIconList = toLabelIcons(),
+            shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
+        )
     }
 }
 
@@ -321,3 +352,16 @@ fun List<CollectionView>.toFilteredList(
                 }
             }
         }
+
+/**
+ * Filters out all [CipherView]s that are of type [CipherType.SSH_KEY] if [showSshKeys] is false.
+ *
+ * @param showSshKeys Whether to show SSH keys in the vault.
+ */
+@JvmName("filterSshKeysIfNecessary")
+fun List<CipherView>.filterSshKeysIfNecessary(showSshKeys: Boolean): List<CipherView> =
+    if (showSshKeys) {
+        this
+    } else {
+        filter { it.type != CipherType.SSH_KEY }
+    }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemCipherType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemCipherType.kt
index a44c95207..6571a4601 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemCipherType.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemCipherType.kt
@@ -24,4 +24,9 @@ enum class VaultItemCipherType {
      * A secure note cipher.
      */
     SECURE_NOTE,
+
+    /**
+     * A SSH key cipher.
+     */
+    SSH_KEY,
 }
diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt
index dc8b56760..2a49826be 100644
--- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt
@@ -21,10 +21,15 @@ sealed class VaultItemListingType {
     data object SecureNote : VaultItemListingType()
 
     /**
-     * A Card  listing.
+     * A Card listing.
      */
     data object Card : VaultItemListingType()
 
+    /**
+     * A SSH key listing.
+     */
+    data object SshKey : VaultItemListingType()
+
     /**
      * A Trash listing.
      */
diff --git a/app/src/main/res/drawable/ic_ssh_key.xml b/app/src/main/res/drawable/ic_ssh_key.xml
new file mode 100644
index 000000000..227926e9b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_ssh_key.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M14.838,11.797L15.439,11.916C16.826,12.191 18.316,11.79 19.387,10.719C21.096,9.01 21.096,6.24 19.387,4.531C17.679,2.823 14.909,2.823 13.2,4.531C12.128,5.603 11.728,7.092 12.003,8.48L12.136,9.153L4.375,16.574L3.601,19.67H6.024L6.649,17.17H8.524L9.149,14.67H11.533L14.838,11.797ZM12,15.92H10.125L9.5,18.42H7.625L7,20.92H2L3.25,15.92L10.776,8.723C10.424,6.943 10.937,5.027 12.316,3.648C14.513,1.451 18.075,1.451 20.271,3.648C22.468,5.844 22.468,9.406 20.271,11.602C18.892,12.981 16.975,13.495 15.196,13.142L12,15.92Z"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M16.736,6.299C16.492,6.543 16.492,6.939 16.736,7.183C16.98,7.427 17.376,7.427 17.62,7.183C17.864,6.939 17.864,6.543 17.62,6.299C17.376,6.055 16.98,6.055 16.736,6.299ZM18.504,5.415C19.236,6.148 19.236,7.335 18.504,8.067C17.771,8.799 16.584,8.799 15.852,8.067C15.12,7.335 15.12,6.148 15.852,5.415C16.584,4.683 17.771,4.683 18.504,5.415Z"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 963b2c569..4c1c14a0f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1075,4 +1075,8 @@ Do you want to switch to this account?</string>
   <string name="no_logins_were_imported">No logins were imported</string>
   <string name="logins_imported">Logins imported</string>
   <string name="remember_to_delete_your_imported_password_file_from_your_computer">Remember to delete your imported password file from your computer</string>
+  <string name="type_ssh_key">SSH key</string>
+  <string name="public_key">Public key</string>
+  <string name="private_key">Private key</string>
+  <string name="ssh_keys">SSH keys</string>
 </resources>
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/util/CipherViewExtensionsTest.kt
index 66474e3ec..8db5de845 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/util/CipherViewExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/util/CipherViewExtensionsTest.kt
@@ -310,6 +310,18 @@ class CipherViewExtensionsTest {
         assertEquals(expected, actual)
     }
 
+    @Test
+    fun `subtitle should return null when type is IDENTITY and identity is null`() {
+        val cipherView: CipherView = mockk {
+            every { identity } returns null
+            every { type } returns CipherType.IDENTITY
+        }
+
+        val actual = cipherView.subtitle
+
+        assertNull(actual)
+    }
+
     @Suppress("MaxLineLength")
     @Test
     fun `subtitle should return null when type is IDENTITY and first and last name are null`() {
@@ -329,4 +341,15 @@ class CipherViewExtensionsTest {
         // Verify
         assertNull(actual)
     }
+
+    @Test
+    fun `subtitle should return null when type is SSH_KEY`() {
+        val cipherView: CipherView = mockk {
+            every { type } returns CipherType.SSH_KEY
+        }
+
+        val actual = cipherView.subtitle
+
+        assertNull(actual)
+    }
 }
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt
index 7613fe173..085611f39 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt
@@ -407,7 +407,12 @@ private const val CIPHER_JSON = """
     "cardholderName": "mockCardholderName-1",
     "brand": "mockBrand-1"
   },
-  "key": "mockKey-1"
+  "key": "mockKey-1",
+  "sshKey": {
+    "publicKey": "mockPublicKey-1",
+    "privateKey": "mockPrivateKey-1",
+    "keyFingerprint": "mockKeyFingerprint-1"
+  }
 }
 """
 
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequestUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequestUtil.kt
index fbcaef7cf..dcff50d18 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequestUtil.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/CipherJsonRequestUtil.kt
@@ -21,6 +21,7 @@ fun createMockCipherJsonRequest(number: Int, hasNullUri: Boolean = false): Ciphe
         type = CipherTypeJson.LOGIN,
         login = createMockLogin(number = number, hasNullUri = hasNullUri),
         card = createMockCard(number = number),
+        sshKey = createMockSshKey(number = number),
         fields = listOf(createMockField(number = number)),
         identity = createMockIdentity(number = number),
         isFavorite = false,
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt
index f6be71879..1ac03e59e 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt
@@ -38,6 +38,7 @@ fun createMockCipher(
         card = createMockCard(number = number),
         fields = listOf(createMockField(number = number)),
         identity = createMockIdentity(number = number),
+        sshKey = createMockSshKey(number = number),
         isFavorite = false,
         passwordHistory = listOf(createMockPasswordHistory(number = number)),
         reprompt = CipherRepromptTypeJson.NONE,
@@ -148,6 +149,18 @@ fun createMockLogin(
         fido2Credentials = fido2Credentials,
     )
 
+/**
+ * Create a mock [SyncResponseJson.Cipher.SshKey] with a given [number].
+ */
+fun createMockSshKey(number: Int) = SyncResponseJson.Cipher.SshKey(
+    publicKey = "mockPublicKey-$number",
+    privateKey = "mockPrivateKey-$number",
+    keyFingerprint = "mockKeyFingerprint-$number",
+)
+
+/**
+ * Create a mock [SyncResponseJson.Cipher.Fido2Credential] with a given [number].
+ */
 fun createMockFido2Credential(number: Int) = SyncResponseJson.Cipher.Fido2Credential(
     credentialId = "mockCredentialId-$number",
     keyType = "mockKeyType-$number",
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt
index e8134e016..50c6b07a4 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/CiphersServiceTest.kt
@@ -465,7 +465,12 @@ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """
       "cardholderName": "mockCardholderName-1",
       "brand": "mockBrand-1"
     },
-    "key": "mockKey-1"
+    "key": "mockKey-1",
+    "sshKey": {
+      "publicKey": "mockPublicKey-1",
+      "privateKey": "mockPrivateKey-1",
+      "keyFingerprint": "mockKeyFingerprint-1"
+    }
   }
 }
 """
@@ -576,7 +581,12 @@ private const val CREATE_RESTORE_UPDATE_CIPHER_SUCCESS_JSON = """
     "cardholderName": "mockCardholderName-1",
     "brand": "mockBrand-1"
   },
-  "key": "mockKey-1"
+  "key": "mockKey-1",
+  "sshKey": {
+    "publicKey": "mockPublicKey-1",
+    "privateKey": "mockPrivateKey-1",
+    "keyFingerprint": "mockKeyFingerprint-1"
+  }
 }
 """
 
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt
index f2b4a9e4f..8ca29fa96 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt
@@ -302,7 +302,12 @@ private const val SYNC_SUCCESS_JSON = """
         "cardholderName": "mockCardholderName-1",
         "brand": "mockBrand-1"
       },
-      "key": "mockKey-1"
+      "key": "mockKey-1",
+      "sshKey": {
+        "publicKey": "mockPublicKey-1",
+        "privateKey": "mockPrivateKey-1",
+        "keyFingerprint": "mockKeyFingerprint-1"
+      }
     }
   ],
   "domains": {
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt
index 936e98fcd..3498970ac 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt
@@ -15,6 +15,7 @@ import com.bitwarden.vault.LoginView
 import com.bitwarden.vault.PasswordHistoryView
 import com.bitwarden.vault.SecureNoteType
 import com.bitwarden.vault.SecureNoteView
+import com.bitwarden.vault.SshKeyView
 import com.bitwarden.vault.UriMatchType
 import java.time.Clock
 import java.time.Instant
@@ -47,6 +48,7 @@ fun createMockCipherView(
     folderId: String? = "mockId-$number",
     clock: Clock = FIXED_CLOCK,
     fido2Credentials: List<Fido2Credential>? = null,
+    sshKey: SshKeyView? = createMockSshKeyView(number = number),
 ): CipherView =
     CipherView(
         id = "mockId-$number",
@@ -77,6 +79,7 @@ fun createMockCipherView(
         identity = createMockIdentityView(number = number).takeIf {
             cipherType == CipherType.IDENTITY
         },
+        sshKey = sshKey.takeIf { cipherType == CipherType.SSH_KEY },
         favorite = false,
         passwordHistory = listOf(createMockPasswordHistoryView(number = number, clock)),
         reprompt = repromptType,
@@ -223,6 +226,16 @@ fun createMockIdentityView(number: Int): IdentityView =
         username = "mockUsername-$number",
     )
 
+/**
+ * Create a mock [SshKeyView] with a given [number].
+ */
+fun createMockSshKeyView(number: Int): SshKeyView =
+    SshKeyView(
+        publicKey = "mockPublicKey-$number",
+        privateKey = "mockPrivateKey-$number",
+        fingerprint = "mockKeyFingerprint-$number",
+    )
+
 /**
  * Create a mock [PasswordHistoryView] with a given [number].
  */
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt
index ed3ce0cce..2920e071f 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt
@@ -13,6 +13,7 @@ import com.bitwarden.vault.LoginUri
 import com.bitwarden.vault.PasswordHistory
 import com.bitwarden.vault.SecureNote
 import com.bitwarden.vault.SecureNoteType
+import com.bitwarden.vault.SshKey
 import com.bitwarden.vault.UriMatchType
 import java.time.Clock
 import java.time.Instant
@@ -49,6 +50,7 @@ fun createMockSdkCipher(number: Int, clock: Clock = FIXED_CLOCK): Cipher =
         card = createMockSdkCard(number = number),
         fields = listOf(createMockSdkField(number = number)),
         identity = createMockSdkIdentity(number = number),
+        sshKey = createMockSdkSshKey(number = number),
         favorite = false,
         passwordHistory = listOf(createMockSdkPasswordHistory(number = number, clock = clock)),
         reprompt = CipherRepromptType.NONE,
@@ -101,6 +103,16 @@ fun createMockSdkIdentity(number: Int): Identity =
         username = "mockUsername-$number",
     )
 
+/**
+ * Create a mock [SshKey] with a given [number].
+ */
+fun createMockSdkSshKey(number: Int): SshKey =
+    SshKey(
+        publicKey = "mockPublicKey-$number",
+        privateKey = "mockPrivateKey-$number",
+        fingerprint = "mockKeyFingerprint-$number",
+    )
+
 /**
  * Create a mock [Field] with a given [number].
  */
diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt
index d8c902995..702afa8cc 100644
--- a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt
@@ -18,6 +18,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockIdentit
 import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockLogin
 import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPasswordHistory
 import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSecureNote
+import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSshKey
 import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockUri
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkAttachment
@@ -28,6 +29,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkIdentity
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkLogin
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkPasswordHistory
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSecureNote
+import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSshKey
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkUri
 import org.junit.Assert.assertEquals
 import org.junit.Test
@@ -152,6 +154,16 @@ class VaultSdkCipherExtensionsTest {
         )
     }
 
+    @Test
+    fun `toSdkSshKey should convert a SyncResponseJson Cipher SshKey to a SshKey`() {
+        val syncSshKey = createMockSshKey(number = 1)
+        val sdkSshKey = syncSshKey.toSdkSshKey()
+        assertEquals(
+            createMockSdkSshKey(number = 1),
+            sdkSshKey,
+        )
+    }
+
     @Test
     fun `toSdkLoginUriList should convert list of LoginUri to List of Sdk LoginUri`() {
         val syncLoginUris = listOf(
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt
index 91fbc0164..734af84e6 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt
@@ -485,6 +485,31 @@ class SearchScreenTest : BaseComposeTest() {
         }
         composeTestRule.onNodeWithText(text = "Search Verification codes").assertIsDisplayed()
 
+        mutableStateFlow.update {
+            it.copy(searchType = SearchTypeData.Vault.SshKeys)
+        }
+        composeTestRule.onNodeWithText(text = "Search SSH keys").assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(searchType = SearchTypeData.Vault.Logins)
+        }
+        composeTestRule.onNodeWithText(text = "Search Logins").assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(searchType = SearchTypeData.Vault.NoFolder)
+        }
+        composeTestRule.onNodeWithText(text = "Search No Folder").assertIsDisplayed()
+
+        mutableStateFlow.update {
+            it.copy(
+                searchType = SearchTypeData.Vault.Collection(
+                    collectionId = "mockId",
+                    collectionName = "mockName",
+                ),
+            )
+        }
+        composeTestRule.onNodeWithText(text = "Search mockName").assertIsDisplayed()
+
         mutableStateFlow.update {
             it.copy(
                 searchType = SearchTypeData.Vault.Folder(
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt
index 9936ac3e8..dd528c56f 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt
@@ -1444,6 +1444,7 @@ class SearchViewModelTest : BaseViewModelTest() {
                     SearchTypeData.Sends.Texts -> "search_type_sends_text"
                     SearchTypeData.Vault.All -> "search_type_vault_all"
                     SearchTypeData.Vault.Cards -> "search_type_vault_cards"
+                    SearchTypeData.Vault.SshKeys -> "search_type_vault_ssh_keys"
                     is SearchTypeData.Vault.Collection -> "search_type_vault_collection"
                     is SearchTypeData.Vault.Folder -> "search_type_vault_folder"
                     SearchTypeData.Vault.Identities -> "search_type_vault_identities"
@@ -1463,6 +1464,7 @@ class SearchViewModelTest : BaseViewModelTest() {
                     SearchTypeData.Sends.Texts -> null
                     SearchTypeData.Vault.All -> null
                     SearchTypeData.Vault.Cards -> null
+                    SearchTypeData.Vault.SshKeys -> null
                     is SearchTypeData.Vault.Collection -> searchType.collectionId
                     is SearchTypeData.Vault.Folder -> searchType.folderId
                     SearchTypeData.Vault.Identities -> null
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensionsTest.kt
index 7898c1698..ccecb1bcb 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensionsTest.kt
@@ -203,6 +203,32 @@ class SearchTypeDataExtensionsTest {
         )
     }
 
+    @Suppress("MaxLineLength")
+    @Test
+    fun `updateWithAdditionalDataIfNecessary should return the searchTypeData unchanged for Vault SshKeys`() {
+        val searchTypeData = SearchTypeData.Vault.SshKeys
+        assertEquals(
+            searchTypeData,
+            searchTypeData.updateWithAdditionalDataIfNecessary(
+                folderList = listOf(),
+                collectionList = emptyList(),
+            ),
+        )
+    }
+
+    @Suppress("MaxLineLength")
+    @Test
+    fun `updateWithAdditionalDataIfNecessary should return the searchTypeData unchanged for Vault VerificationCodes`() {
+        val searchTypeData = SearchTypeData.Vault.VerificationCodes
+        assertEquals(
+            searchTypeData,
+            searchTypeData.updateWithAdditionalDataIfNecessary(
+                folderList = listOf(),
+                collectionList = emptyList(),
+            ),
+        )
+    }
+
     @Suppress("MaxLineLength")
     @Test
     fun `updateWithAdditionalDataIfNecessary should return the searchTypeData unchanged for Vault Trash`() {
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensionsTest.kt
index 7077fe0ee..d7757078b 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensionsTest.kt
@@ -80,4 +80,18 @@ class SearchTypeExtensionsTest {
     fun `toSearchTypeData should return Vault Trash then SearchType is Vault Trash`() {
         assertEquals(SearchTypeData.Vault.Trash, SearchType.Vault.Trash.toSearchTypeData())
     }
+
+    @Suppress("MaxLineLength")
+    @Test
+    fun `toSearchTypeData should return Vault VerificationCodes then SearchType is Vault VerificationCodes`() {
+        assertEquals(
+            SearchTypeData.Vault.VerificationCodes,
+            SearchType.Vault.VerificationCodes.toSearchTypeData(),
+        )
+    }
+
+    @Test
+    fun `toSearchTypeData should return Vault SshKeys then SearchType is Vault SshKeys`() {
+        assertEquals(SearchTypeData.Vault.SshKeys, SearchType.Vault.SshKeys.toSearchTypeData())
+    }
 }
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt
index 80736f1bf..a3e23fe2b 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt
@@ -186,6 +186,36 @@ fun createMockDisplayItemForCipher(
                 isTotp = false,
             )
         }
+
+        CipherType.SSH_KEY -> {
+            SearchState.DisplayItem(
+                id = "mockId-$number",
+                title = "mockName-$number",
+                titleTestTag = "CipherNameLabel",
+                subtitle = "mockPublicKey-$number",
+                subtitleTestTag = "CipherSubTitleLabel",
+                iconData = IconData.Local(R.drawable.ic_ssh_key),
+                extraIconList = listOf(
+                    IconRes(
+                        iconRes = R.drawable.ic_collections,
+                        contentDescription = R.string.collections.asText(),
+                        testTag = "CipherInCollectionIcon",
+                    ),
+                ),
+                overflowOptions = listOf(
+                    ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"),
+                    ListingItemOverflowAction.VaultAction.EditClick(
+                        cipherId = "mockId-$number",
+                        requiresPasswordReprompt = true,
+                    ),
+                ),
+                overflowTestTag = "CipherOptionsButton",
+                totpCode = null,
+                autofillSelectionOptions = emptyList(),
+                shouldDisplayMasterPasswordReprompt = false,
+                isTotp = false,
+            )
+        }
     }
 
 /**
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt
index db34dd628..1956f980a 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt
@@ -3322,6 +3322,77 @@ class VaultAddEditScreenTest : BaseComposeTest() {
         }
     }
 
+    @Test
+    fun `in ItemType_SshKeys changing the public key should trigger PublicKeyTextChange`() {
+        mutableStateFlow.value = DEFAULT_STATE_SSH_KEYS
+
+        composeTestRule
+            .onNodeWithTextAfterScroll("Public key")
+            .performTextInput("TestPublicKey")
+
+        verify {
+            viewModel.trySendAction(
+                VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange(
+                    publicKey = "TestPublicKey",
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `in ItemType_SshKeys changing the private key should trigger PrivateKeyTextChange`() {
+        mutableStateFlow.value = DEFAULT_STATE_SSH_KEYS
+
+        composeTestRule
+            .onNodeWithTextAfterScroll("Private key")
+            .performTextInput("TestPrivateKey")
+
+        verify {
+            viewModel.trySendAction(
+                VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange(
+                    privateKey = "TestPrivateKey",
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `in ItemType_SshKeys changing the fingerprint should trigger FingerprintTextChange`() {
+        mutableStateFlow.value = DEFAULT_STATE_SSH_KEYS
+
+        composeTestRule
+            .onNodeWithTextAfterScroll("Fingerprint")
+            .performTextInput("TestFingerprint")
+
+        verify {
+            viewModel.trySendAction(
+                VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange(
+                    fingerprint = "TestFingerprint",
+                ),
+            )
+        }
+    }
+
+    @Suppress("MaxLineLength")
+    @Test
+    fun `in ItemType_SshKeys changing the private key visibility should trigger PrivateKeyVisibilityChange`() {
+        mutableStateFlow.value = DEFAULT_STATE_SSH_KEYS
+
+        composeTestRule
+            .onNodeWithTextAfterScroll(text = "Private key")
+            .assertExists()
+        composeTestRule
+            .onNodeWithContentDescriptionAfterScroll(label = "Show")
+            .assertExists()
+            .performClick()
+
+        verify(exactly = 1) {
+            viewModel.trySendAction(
+                VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange(true),
+            )
+        }
+    }
+
     //region Helper functions
 
     private fun updateLoginType(
@@ -3444,6 +3515,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
             ),
             dialog = VaultAddEditState.DialogState.Generic(message = "test".asText()),
             vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
         )
 
         private val DEFAULT_STATE_LOGIN = VaultAddEditState(
@@ -3454,6 +3526,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
                 isIndividualVaultDisabled = false,
             ),
             dialog = null,
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
         )
 
         private val DEFAULT_STATE_IDENTITY = VaultAddEditState(
@@ -3464,6 +3537,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
                 isIndividualVaultDisabled = false,
             ),
             dialog = null,
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
         )
 
         private val DEFAULT_STATE_CARD = VaultAddEditState(
@@ -3474,6 +3548,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
                 isIndividualVaultDisabled = false,
             ),
             dialog = null,
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
         )
 
         @Suppress("MaxLineLength")
@@ -3495,6 +3570,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
             ),
             dialog = null,
             vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.SECURE_NOTE),
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
         )
 
         private val DEFAULT_STATE_SECURE_NOTES = VaultAddEditState(
@@ -3505,6 +3581,18 @@ class VaultAddEditScreenTest : BaseComposeTest() {
                 isIndividualVaultDisabled = false,
             ),
             dialog = null,
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
+        )
+
+        private val DEFAULT_STATE_SSH_KEYS = VaultAddEditState(
+            vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.SSH_KEY),
+            viewState = VaultAddEditState.ViewState.Content(
+                common = VaultAddEditState.ViewState.Content.Common(),
+                type = VaultAddEditState.ViewState.Content.ItemType.SshKey(),
+                isIndividualVaultDisabled = false,
+            ),
+            dialog = null,
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
         )
 
         private val ALTERED_COLLECTIONS = listOf(
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt
index 5169d97d9..4bd49bbd4 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt
@@ -25,12 +25,14 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRe
 import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
 import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
 import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
+import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
 import com.x8bit.bitwarden.data.platform.manager.PolicyManager
 import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
 import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
 import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
 import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
 import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
+import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
 import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
 import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
 import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
@@ -152,6 +154,15 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
     private val organizationEventManager = mockk<OrganizationEventManager> {
         every { trackEvent(event = any()) } just runs
     }
+    private val mutableSshVaultItemsFeatureFlagFlow = MutableStateFlow<Boolean>(true)
+    private val featureFlagManager = mockk<FeatureFlagManager> {
+        every {
+            getFeatureFlagFlow(key = FlagKey.SshKeyCipherItems)
+        } returns mutableSshVaultItemsFeatureFlagFlow
+        every {
+            getFeatureFlag(key = FlagKey.SshKeyCipherItems)
+        } returns mutableSshVaultItemsFeatureFlagFlow.value
+    }
 
     @BeforeEach
     fun setup() {
@@ -250,6 +261,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
                     type = VaultAddEditState.ViewState.Content.ItemType.Login(),
                 ),
                 dialog = null,
+                supportedItemTypes = VaultAddEditState.ItemTypeOption.entries,
             ),
             viewModel.stateFlow.value,
         )
@@ -365,6 +377,56 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
         }
     }
 
+    @Test
+    fun `initial add state should be correct when SSH key feature flag is enabled`() {
+        mutableSshVaultItemsFeatureFlagFlow.value = true
+        val vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN)
+        val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType)
+        val viewModel = createAddVaultItemViewModel(
+            savedStateHandle = createSavedStateHandleWithState(
+                state = initState,
+                vaultAddEditType = vaultAddEditType,
+            ),
+        )
+        assertEquals(
+            initState,
+            viewModel.stateFlow.value,
+        )
+    }
+
+    @Test
+    fun `initial add state should be correct when SSH key feature flag is disabled`() {
+        mutableSshVaultItemsFeatureFlagFlow.value = false
+        every {
+            featureFlagManager.getFeatureFlag(key = FlagKey.SshKeyCipherItems)
+        } returns false
+        val vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN)
+        val expectedState = VaultAddEditState(
+            vaultAddEditType = vaultAddEditType,
+            viewState = VaultAddEditState.ViewState.Content(
+                common = VaultAddEditState.ViewState.Content.Common(),
+                isIndividualVaultDisabled = false,
+                type = VaultAddEditState.ViewState.Content.ItemType.Login(),
+            ),
+            dialog = null,
+            totpData = null,
+            shouldShowCloseButton = true,
+            shouldExitOnSave = false,
+            supportedItemTypes = VaultAddEditState.ItemTypeOption.entries
+                .filter { it != VaultAddEditState.ItemTypeOption.SSH_KEYS },
+        )
+        val viewModel = createAddVaultItemViewModel(
+            savedStateHandle = createSavedStateHandleWithState(
+                state = null,
+                vaultAddEditType = vaultAddEditType,
+            ),
+        )
+        assertEquals(
+            expectedState,
+            viewModel.stateFlow.value,
+        )
+    }
+
     @Test
     fun `initial edit state should be correct`() = runTest {
         val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
@@ -1792,6 +1854,36 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
         )
     }
 
+    @Test
+    fun `TypeOptionSelect SSH_KEYS should switch to SshKeysItem`() = runTest {
+        mutableVaultDataFlow.value = DataState.Loaded(
+            createVaultData(cipherView = createMockCipherView(1)),
+        )
+        val viewModel = createAddVaultItemViewModel()
+        val action = VaultAddEditAction.Common.TypeOptionSelect(
+            VaultAddEditState.ItemTypeOption.SSH_KEYS,
+        )
+
+        viewModel.trySendAction(action)
+
+        val expectedState = loginInitialState.copy(
+            viewState = VaultAddEditState.ViewState.Content(
+                common = createCommonContentViewState(),
+                isIndividualVaultDisabled = false,
+                type = VaultAddEditState.ViewState.Content.ItemType.SshKey(),
+                previousItemTypes = mapOf(
+                    VaultAddEditState.ItemTypeOption.LOGIN
+                        to VaultAddEditState.ViewState.Content.ItemType.Login(),
+                ),
+            ),
+        )
+
+        assertEquals(
+            expectedState,
+            viewModel.stateFlow.value,
+        )
+    }
+
     @Nested
     inner class VaultAddEditLoginTypeItemActions {
         private lateinit var viewModel: VaultAddEditViewModel
@@ -2610,6 +2702,90 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
         }
     }
 
+    @Nested
+    inner class VaultAddEditSshKeyTypeItemActions {
+        private lateinit var viewModel: VaultAddEditViewModel
+        private lateinit var vaultAddItemInitialState: VaultAddEditState
+        private lateinit var sshKeyInitialSavedStateHandle: SavedStateHandle
+
+        @BeforeEach
+        fun setup() {
+            mutableVaultDataFlow.value = DataState.Loaded(
+                createVaultData(cipherView = createMockCipherView(1)),
+            )
+            vaultAddItemInitialState = createVaultAddItemState(
+                typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(),
+            )
+            sshKeyInitialSavedStateHandle = createSavedStateHandleWithState(
+                state = vaultAddItemInitialState,
+                vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.SSH_KEY),
+            )
+            viewModel = createAddVaultItemViewModel(
+                savedStateHandle = sshKeyInitialSavedStateHandle,
+            )
+        }
+
+        @Test
+        fun `PublicKeyTextChange should update public key`() = runTest {
+            val action = VaultAddEditAction.ItemType.SshKeyType.PublicKeyTextChange(
+                publicKey = "newPublicKey",
+            )
+            val expectedState = createVaultAddItemState(
+                typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                    publicKey = "newPublicKey",
+                ),
+            )
+            viewModel.trySendAction(action)
+
+            assertEquals(expectedState, viewModel.stateFlow.value)
+        }
+
+        @Test
+        fun `PrivateKeyTextChange should update private key`() = runTest {
+            val action = VaultAddEditAction.ItemType.SshKeyType.PrivateKeyTextChange(
+                privateKey = "newPrivateKey",
+            )
+            val expectedState = createVaultAddItemState(
+                typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                    privateKey = "newPrivateKey",
+                ),
+            )
+            viewModel.trySendAction(action)
+
+            assertEquals(expectedState, viewModel.stateFlow.value)
+        }
+
+        @Test
+        fun `PrivateKeyVisibilityChange should update private key visibility`() = runTest {
+            val action = VaultAddEditAction.ItemType.SshKeyType.PrivateKeyVisibilityChange(
+                isVisible = true,
+            )
+            val expectedState = createVaultAddItemState(
+                typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                    showPrivateKey = true,
+                ),
+            )
+            viewModel.trySendAction(action)
+
+            assertEquals(expectedState, viewModel.stateFlow.value)
+        }
+
+        @Test
+        fun `FingerprintTextChange should update fingerprint`() = runTest {
+            val action = VaultAddEditAction.ItemType.SshKeyType.FingerprintTextChange(
+                fingerprint = "newFingerprint",
+            )
+            val expectedState = createVaultAddItemState(
+                typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                    fingerprint = "newFingerprint",
+                ),
+            )
+            viewModel.trySendAction(action)
+
+            assertEquals(expectedState, viewModel.stateFlow.value)
+        }
+    }
+
     @Test
     fun `NumberVisibilityChange should log an event when in edit mode and password is visible`() =
         runTest {
@@ -2686,6 +2862,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
                 resourceManager = resourceManager,
                 clock = fixedClock,
                 organizationEventManager = organizationEventManager,
+                featureFlagManager = featureFlagManager,
             )
         }
 
@@ -3781,6 +3958,30 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
                     )
                 }
             }
+
+        @Suppress("MaxLineLength")
+        @Test
+        fun `SshKeyCipherItemsFeatureFlagReceive should update supportedItemTypes`() = runTest {
+            // Verify SSH keys is supported when feature flag is enabled.
+            viewModel.trySendAction(
+                VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive(enabled = true),
+            )
+            assertEquals(
+                VaultAddEditState.ItemTypeOption.entries,
+                viewModel.stateFlow.value.supportedItemTypes,
+            )
+
+            // Verify SSH keys is not supported when feature flag is disabled.
+            viewModel.trySendAction(
+                VaultAddEditAction.Internal.SshKeyCipherItemsFeatureFlagReceive(enabled = false),
+            )
+            assertEquals(
+                VaultAddEditState.ItemTypeOption.entries.filterNot {
+                    it == VaultAddEditState.ItemTypeOption.SSH_KEYS
+                },
+                viewModel.stateFlow.value.supportedItemTypes,
+            )
+        }
     }
 
     //region Helper functions
@@ -3794,6 +3995,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
         typeContentViewState: VaultAddEditState.ViewState.Content.ItemType = createLoginTypeContentViewState(),
         dialogState: VaultAddEditState.DialogState? = null,
         totpData: TotpData? = null,
+        supportedItemTypes: List<VaultAddEditState.ItemTypeOption> = VaultAddEditState.ItemTypeOption.entries,
     ): VaultAddEditState =
         VaultAddEditState(
             vaultAddEditType = vaultAddEditType,
@@ -3805,6 +4007,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
             dialog = dialogState,
             shouldExitOnSave = shouldExitOnSave,
             totpData = totpData,
+            supportedItemTypes = supportedItemTypes,
         )
 
     @Suppress("LongParameterList")
@@ -3879,6 +4082,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
                 VaultItemCipherType.CARD -> "card"
                 VaultItemCipherType.IDENTITY -> "identity"
                 VaultItemCipherType.SECURE_NOTE -> "secure_note"
+                VaultItemCipherType.SSH_KEY -> "ssh_key"
                 null -> null
             },
         )
@@ -3906,6 +4110,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
             resourceManager = bitwardenResourceManager,
             clock = clock,
             organizationEventManager = organizationEventManager,
+            featureFlagManager = featureFlagManager,
         )
 
     private fun createVaultData(
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt
index 58a6cf633..02c551f71 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/CipherViewExtensionsTest.kt
@@ -12,6 +12,7 @@ import com.bitwarden.vault.LoginView
 import com.bitwarden.vault.PasswordHistoryView
 import com.bitwarden.vault.SecureNoteType
 import com.bitwarden.vault.SecureNoteView
+import com.bitwarden.vault.SshKeyView
 import com.x8bit.bitwarden.R
 import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
 import com.x8bit.bitwarden.data.auth.repository.model.Organization
@@ -313,6 +314,50 @@ class CipherViewExtensionsTest {
         )
     }
 
+    @Test
+    fun `toViewState should create SSH Key ViewState`() {
+        val cipherView = DEFAULT_SSH_KEY_CIPHER_VIEW
+
+        val result = cipherView.toViewState(
+            isClone = false,
+            isIndividualVaultDisabled = false,
+            totpData = null,
+            resourceManager = resourceManager,
+            clock = FIXED_CLOCK,
+        )
+
+        assertEquals(
+            VaultAddEditState.ViewState.Content(
+                common = VaultAddEditState.ViewState.Content.Common(
+                    originalCipher = cipherView,
+                    name = "cipher",
+                    favorite = false,
+                    masterPasswordReprompt = true,
+                    notes = "Lots of notes",
+                    customFieldData = listOf(
+                        VaultAddEditState.Custom.BooleanField(TEST_ID, "TestBoolean", false),
+                        VaultAddEditState.Custom.TextField(TEST_ID, "TestText", "TestText"),
+                        VaultAddEditState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"),
+                        VaultAddEditState.Custom.LinkedField(
+                            TEST_ID,
+                            "TestLinked",
+                            VaultLinkedFieldType.USERNAME,
+                        ),
+                    ),
+                    availableFolders = emptyList(),
+                    availableOwners = emptyList(),
+                ),
+                isIndividualVaultDisabled = false,
+                type = VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                    publicKey = "PublicKey",
+                    privateKey = "PrivateKey",
+                    fingerprint = "Fingerprint",
+                ),
+            ),
+            result,
+        )
+    }
+
     @Test
     fun `toViewState with isClone true should append clone text to the cipher name`() {
         val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW
@@ -578,6 +623,7 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
     creationDate = FIXED_CLOCK.instant(),
     deletedDate = null,
     revisionDate = FIXED_CLOCK.instant(),
+    sshKey = null,
 )
 
 private val DEFAULT_CARD_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.copy(
@@ -660,4 +706,13 @@ private val DEFAULT_SECURE_NOTES_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_V
     secureNote = SecureNoteView(type = SecureNoteType.GENERIC),
 )
 
+private val DEFAULT_SSH_KEY_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.copy(
+    type = CipherType.SSH_KEY,
+    sshKey = SshKeyView(
+        publicKey = "PublicKey",
+        privateKey = "PrivateKey",
+        fingerprint = "Fingerprint",
+    ),
+)
+
 private const val TEST_ID = "testID"
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensionsTest.kt
index aa4d49997..483d37337 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/VaultAddEditExtensionsTest.kt
@@ -31,6 +31,7 @@ class VaultAddEditExtensionsTest {
             VaultItemCipherType.CARD,
             VaultItemCipherType.SECURE_NOTE,
             VaultItemCipherType.IDENTITY,
+            VaultItemCipherType.SSH_KEY,
         )
 
         val result = vaultItemCipherTypeList.map { it.toItemType() }
@@ -41,6 +42,7 @@ class VaultAddEditExtensionsTest {
                 VaultAddEditState.ViewState.Content.ItemType.Card(),
                 VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
                 VaultAddEditState.ViewState.Content.ItemType.Identity(),
+                VaultAddEditState.ViewState.Content.ItemType.SshKey(),
             ),
             result,
         )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt
index 803811bd2..22d67c1d4 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt
@@ -1867,6 +1867,8 @@ class VaultItemScreenTest : BaseComposeTest() {
     }
     //endregion identity
 
+    //region card
+
     @Test
     fun `in card state, cardholderName should be displayed according to state`() {
         val cardholderName = "the cardholder name"
@@ -2139,6 +2141,79 @@ class VaultItemScreenTest : BaseComposeTest() {
             viewModel.trySendAction(VaultItemAction.ItemType.Card.CopySecurityCodeClick)
         }
     }
+
+    //endregion card
+
+    //region ssh key
+
+    @Test
+    fun `in ssh key state, public key should be displayed according to state`() {
+        val publicKey = "the public key"
+        mutableStateFlow.update { it.copy(viewState = DEFAULT_SSH_KEY_VIEW_STATE) }
+        composeTestRule.onNodeWithTextAfterScroll(publicKey).assertIsDisplayed()
+
+        mutableStateFlow.update { currentState ->
+            updateSshKeyType(currentState) { copy(publicKey = null) }
+        }
+
+        composeTestRule.assertScrollableNodeDoesNotExist(publicKey)
+    }
+
+    @Test
+    fun `in ssh key state, private key should be displayed according to state`() {
+        val privateKey = "the private key"
+        mutableStateFlow.update {
+            it.copy(
+                viewState = DEFAULT_SSH_KEY_VIEW_STATE
+                    .copy(
+                        type = DEFAULT_SSH_KEY.copy(showPrivateKey = true),
+                    ),
+            )
+        }
+        composeTestRule
+            .onNodeWithText(privateKey)
+            .assertIsDisplayed()
+
+        mutableStateFlow.update { currentState ->
+            updateSshKeyType(currentState) { copy(privateKey = null) }
+        }
+
+        composeTestRule.assertScrollableNodeDoesNotExist(privateKey)
+    }
+
+    @Test
+    fun `in ssh key state, on show private key click should send ShowPrivateKeyClick`() {
+        mutableStateFlow.update { it.copy(viewState = DEFAULT_SSH_KEY_VIEW_STATE) }
+
+        composeTestRule
+            .onNodeWithTextAfterScroll("Private key")
+            .onChildren()
+            .filterToOne(hasContentDescription("Show"))
+            .performClick()
+
+        verify(exactly = 1) {
+            viewModel.trySendAction(
+                VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked(
+                    isVisible = true,
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `in ssh key state, fingerprint should be displayed according to state`() {
+        val fingerprint = "the fingerprint"
+        mutableStateFlow.update { it.copy(viewState = DEFAULT_SSH_KEY_VIEW_STATE) }
+        composeTestRule.onNodeWithTextAfterScroll(fingerprint).assertIsDisplayed()
+
+        mutableStateFlow.update { currentState ->
+            updateSshKeyType(currentState) { copy(fingerprint = null) }
+        }
+
+        composeTestRule.assertScrollableNodeDoesNotExist(fingerprint)
+    }
+
+    //endregion ssh key
 }
 
 //region Helper functions
@@ -2212,6 +2287,29 @@ private fun updateCardType(
     return currentState.copy(viewState = updatedType)
 }
 
+private fun updateSshKeyType(
+    currentState: VaultItemState,
+    transform: VaultItemState.ViewState.Content.ItemType.SshKey.() ->
+    VaultItemState.ViewState.Content.ItemType.SshKey,
+): VaultItemState {
+    val updatedType = when (val viewState = currentState.viewState) {
+        is VaultItemState.ViewState.Content -> {
+            when (val type = viewState.type) {
+                is VaultItemState.ViewState.Content.ItemType.SshKey -> {
+                    viewState.copy(
+                        type = type.transform(),
+                    )
+                }
+
+                else -> viewState
+            }
+        }
+
+        else -> viewState
+    }
+    return currentState.copy(viewState = updatedType)
+}
+
 private fun updateCommonContent(
     currentState: VaultItemState,
     transform: VaultItemState.ViewState.Content.Common.()
@@ -2333,6 +2431,15 @@ private val DEFAULT_CARD: VaultItemState.ViewState.Content.ItemType.Card =
         ),
     )
 
+private val DEFAULT_SSH_KEY: VaultItemState.ViewState.Content.ItemType.SshKey =
+    VaultItemState.ViewState.Content.ItemType.SshKey(
+        name = "the ssh key name",
+        publicKey = "the public key",
+        privateKey = "the private key",
+        fingerprint = "the fingerprint",
+        showPrivateKey = false,
+    )
+
 private val EMPTY_COMMON: VaultItemState.ViewState.Content.Common =
     VaultItemState.ViewState.Content.Common(
         name = "cipher",
@@ -2433,6 +2540,12 @@ private val DEFAULT_SECURE_NOTE_VIEW_STATE: VaultItemState.ViewState.Content =
         type = VaultItemState.ViewState.Content.ItemType.SecureNote,
     )
 
+private val DEFAULT_SSH_KEY_VIEW_STATE: VaultItemState.ViewState.Content =
+    VaultItemState.ViewState.Content(
+        common = DEFAULT_COMMON,
+        type = DEFAULT_SSH_KEY,
+    )
+
 private val EMPTY_VIEW_STATES = listOf(
     EMPTY_LOGIN_VIEW_STATE,
     EMPTY_IDENTITY_VIEW_STATE,
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
index 160e78575..be2bc9512 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
@@ -2336,6 +2336,60 @@ class VaultItemViewModelTest : BaseViewModelTest() {
         }
     }
 
+    @Nested
+    inner class SshKeyActions {
+        private lateinit var viewModel: VaultItemViewModel
+
+        @BeforeEach
+        fun setup() {
+            viewModel = createViewModel(
+                state = DEFAULT_STATE.copy(
+                    viewState = SSH_KEY_VIEW_STATE,
+                ),
+            )
+        }
+
+        @Suppress("MaxLineLength")
+        @Test
+        fun `on PrivateKeyVisibilityClick should show password dialog when re-prompt is required`() =
+            runTest {
+                val sshKeyViewState = createViewState(
+                    common = DEFAULT_COMMON.copy(requiresReprompt = false),
+                )
+                val sshKeyState = DEFAULT_STATE.copy(viewState = SSH_KEY_VIEW_STATE)
+                val mockCipherView = mockk<CipherView> {
+                    every {
+                        toViewState(
+                            previousState = null,
+                            isPremiumUser = true,
+                            hasMasterPassword = true,
+                            totpCodeItemData = null,
+                        )
+                    } returns SSH_KEY_VIEW_STATE
+                }
+                mutableVaultItemFlow.value = DataState.Loaded(data = mockCipherView)
+                mutableAuthCodeItemFlow.value = DataState.Loaded(data = null)
+
+                assertEquals(sshKeyState, viewModel.stateFlow.value)
+                viewModel.trySendAction(
+                    VaultItemAction.ItemType.SshKey.PrivateKeyVisibilityClicked(
+                        isVisible = true,
+                    ),
+                )
+                assertEquals(
+                    sshKeyState.copy(
+                        viewState = sshKeyViewState.copy(
+                            common = DEFAULT_COMMON,
+                            type = DEFAULT_SSH_KEY_TYPE.copy(
+                                showPrivateKey = true,
+                            ),
+                        ),
+                    ),
+                    viewModel.stateFlow.value,
+                )
+            }
+    }
+
     @Nested
     inner class VaultItemFlow {
         @BeforeEach
@@ -2628,6 +2682,15 @@ class VaultItemViewModelTest : BaseViewModelTest() {
                 ),
             )
 
+        private val DEFAULT_SSH_KEY_TYPE: VaultItemState.ViewState.Content.ItemType.SshKey =
+            VaultItemState.ViewState.Content.ItemType.SshKey(
+                name = "mockName",
+                publicKey = "mockPublicKey",
+                privateKey = "mockPrivateKey",
+                fingerprint = "mockFingerprint",
+                showPrivateKey = false,
+            )
+
         private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
             VaultItemState.ViewState.Content.Common(
                 name = "login cipher",
@@ -2684,5 +2747,11 @@ class VaultItemViewModelTest : BaseViewModelTest() {
                 common = DEFAULT_COMMON,
                 type = DEFAULT_CARD_TYPE,
             )
+
+        private val SSH_KEY_VIEW_STATE: VaultItemState.ViewState.Content =
+            VaultItemState.ViewState.Content(
+                common = DEFAULT_COMMON,
+                type = DEFAULT_SSH_KEY_TYPE,
+            )
     }
 }
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt
index 3ceefb197..b2ab66549 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt
@@ -374,4 +374,32 @@ class CipherViewExtensionsTest {
 
         assertEquals(expectedState, viewState)
     }
+
+    @Test
+    fun `toViewState should transform full CipherView into ViewState SSH Key Content`() {
+        val cipherView = createCipherView(type = CipherType.SSH_KEY, isEmpty = false)
+        val viewState = cipherView.toViewState(
+            previousState = null,
+            isPremiumUser = true,
+            hasMasterPassword = true,
+            totpCodeItemData = null,
+            clock = fixedClock,
+        )
+        assertEquals(
+            VaultItemState.ViewState.Content(
+                common = createCommonContent(isEmpty = false, isPremiumUser = true).copy(
+                    currentCipher = cipherView.copy(
+                        name = "mockName",
+                        sshKey = cipherView.sshKey?.copy(
+                            publicKey = "publicKey",
+                            privateKey = "privateKey",
+                            fingerprint = "fingerprint",
+                        ),
+                    ),
+                ),
+                type = createSshKeyContent(isEmpty = false),
+            ),
+            viewState,
+        )
+    }
 }
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt
index e8dbc22e1..c07c29879 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/VaultItemTestUtil.kt
@@ -10,6 +10,7 @@ import com.bitwarden.vault.IdentityView
 import com.bitwarden.vault.LoginUriView
 import com.bitwarden.vault.LoginView
 import com.bitwarden.vault.PasswordHistoryView
+import com.bitwarden.vault.SshKeyView
 import com.x8bit.bitwarden.R
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
 import com.x8bit.bitwarden.ui.platform.base.util.asText
@@ -72,6 +73,13 @@ fun createIdentityView(isEmpty: Boolean): IdentityView =
         licenseNumber = "licenseNumber".takeUnless { isEmpty },
     )
 
+fun createSshKeyView(isEmpty: Boolean): SshKeyView =
+    SshKeyView(
+        privateKey = "privateKey".takeUnless { isEmpty },
+        publicKey = "publicKey".takeUnless { isEmpty },
+        fingerprint = "fingerprint".takeUnless { isEmpty },
+    )
+
 fun createCipherView(type: CipherType, isEmpty: Boolean): CipherView =
     CipherView(
         id = null,
@@ -146,6 +154,7 @@ fun createCipherView(type: CipherType, isEmpty: Boolean): CipherView =
         creationDate = Instant.ofEpochSecond(1_000L),
         deletedDate = null,
         revisionDate = Instant.ofEpochSecond(1_000L),
+        sshKey = createSshKeyView(isEmpty = isEmpty),
     )
 
 fun createCommonContent(
@@ -259,3 +268,12 @@ fun createIdentityContent(
         phone = "phone".takeUnless { isEmpty },
         address = address.takeUnless { isEmpty },
     )
+
+fun createSshKeyContent(isEmpty: Boolean): VaultItemState.ViewState.Content.ItemType.SshKey =
+    VaultItemState.ViewState.Content.ItemType.SshKey(
+        name = "mockName".takeUnless { isEmpty },
+        privateKey = "privateKey".takeUnless { isEmpty },
+        publicKey = "publicKey".takeUnless { isEmpty },
+        fingerprint = "fingerprint".takeUnless { isEmpty },
+        showPrivateKey = false,
+    )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt
index 1222a9f27..f84921b60 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt
@@ -1199,6 +1199,13 @@ class VaultItemListingScreenTest : BaseComposeTest() {
             .onNodeWithText(text = "Identities")
             .assertIsDisplayed()
 
+        mutableStateFlow.update {
+            it.copy(itemListingType = VaultItemListingState.ItemListingType.Vault.SshKey)
+        }
+        composeTestRule
+            .onNodeWithText(text = "SSH keys")
+            .assertIsDisplayed()
+
         mutableStateFlow.update {
             it.copy(itemListingType = VaultItemListingState.ItemListingType.Vault.Trash)
         }
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt
index 7eeb19f82..591a0f1e4 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt
@@ -3971,6 +3971,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
                 is VaultItemListingType.Trash -> "trash"
                 is VaultItemListingType.SendFile -> "send_file"
                 is VaultItemListingType.SendText -> "send_text"
+                is VaultItemListingType.SshKey -> "ssh_key"
             },
         )
         set(
@@ -3985,6 +3986,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
                 is VaultItemListingType.Trash -> null
                 is VaultItemListingType.SendFile -> null
                 is VaultItemListingType.SendText -> null
+                is VaultItemListingType.SshKey -> null
             },
         )
     }
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt
index 891e74680..db50ecce2 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt
@@ -254,6 +254,66 @@ class VaultItemListingDataExtensionsTest {
             }
     }
 
+    @Test
+    @Suppress("MaxLineLength")
+    fun `determineListingPredicate should return the correct predicate for a trash SshKey cipherView`() {
+        val cipherView = createMockCipherView(
+            number = 1,
+            isDeleted = true,
+            cipherType = CipherType.SSH_KEY,
+        )
+
+        mapOf(
+            VaultItemListingState.ItemListingType.Vault.Login to false,
+            VaultItemListingState.ItemListingType.Vault.Card to false,
+            VaultItemListingState.ItemListingType.Vault.SecureNote to false,
+            VaultItemListingState.ItemListingType.Vault.Identity to false,
+            VaultItemListingState.ItemListingType.Vault.Trash to true,
+            VaultItemListingState.ItemListingType.Vault.Folder(folderId = "mockId-1") to false,
+            VaultItemListingState.ItemListingType.Vault.Collection(collectionId = "mockId-1") to false,
+            VaultItemListingState.ItemListingType.Vault.SshKey to false,
+        )
+            .forEach { (type, expected) ->
+                val result = cipherView.determineListingPredicate(
+                    itemListingType = type,
+                )
+                assertEquals(
+                    expected,
+                    result,
+                )
+            }
+    }
+
+    @Test
+    @Suppress("MaxLineLength")
+    fun `determineListingPredicate should return the correct predicate for a non trash SshKey cipherView`() {
+        val cipherView = createMockCipherView(
+            number = 1,
+            isDeleted = false,
+            cipherType = CipherType.SSH_KEY,
+        )
+
+        mapOf(
+            VaultItemListingState.ItemListingType.Vault.Login to false,
+            VaultItemListingState.ItemListingType.Vault.Card to false,
+            VaultItemListingState.ItemListingType.Vault.SecureNote to false,
+            VaultItemListingState.ItemListingType.Vault.Identity to false,
+            VaultItemListingState.ItemListingType.Vault.Trash to false,
+            VaultItemListingState.ItemListingType.Vault.SshKey to true,
+            VaultItemListingState.ItemListingType.Vault.Folder(folderId = "mockId-1") to true,
+            VaultItemListingState.ItemListingType.Vault.Collection(collectionId = "mockId-1") to true,
+        )
+            .forEach { (type, expected) ->
+                val result = cipherView.determineListingPredicate(
+                    itemListingType = type,
+                )
+                assertEquals(
+                    expected,
+                    result,
+                )
+            }
+    }
+
     @Test
     @Suppress("MaxLineLength")
     fun `determineListingPredicate should return the correct predicate for item not in a folder`() {
@@ -873,15 +933,76 @@ class VaultItemListingDataExtensionsTest {
             createMockCollectionView(number = 3),
         )
 
-        val result = VaultItemListingState.ItemListingType.Vault.Login
-            .updateWithAdditionalDataIfNecessary(
-                folderList = folderViewList,
-                collectionList = collectionViewList,
-            )
+        assertEquals(
+            VaultItemListingState.ItemListingType.Vault.Identity,
+            VaultItemListingState.ItemListingType.Vault.Identity
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
+        )
 
         assertEquals(
             VaultItemListingState.ItemListingType.Vault.Login,
-            result,
+            VaultItemListingState.ItemListingType.Vault.Login
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
+        )
+
+        assertEquals(
+            VaultItemListingState.ItemListingType.Vault.SecureNote,
+            VaultItemListingState.ItemListingType.Vault.SecureNote
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
+        )
+
+        assertEquals(
+            VaultItemListingState.ItemListingType.Vault.Trash,
+            VaultItemListingState.ItemListingType.Vault.Trash
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
+        )
+
+        assertEquals(
+            VaultItemListingState.ItemListingType.Vault.SshKey,
+            VaultItemListingState.ItemListingType.Vault.SshKey
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
+        )
+
+        assertEquals(
+            VaultItemListingState.ItemListingType.Vault.Card,
+            VaultItemListingState.ItemListingType.Vault.Card
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
+        )
+
+        assertEquals(
+            VaultItemListingState.ItemListingType.Send.SendFile,
+            VaultItemListingState.ItemListingType.Send.SendFile
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
+        )
+
+        assertEquals(
+            VaultItemListingState.ItemListingType.Send.SendText,
+            VaultItemListingState.ItemListingType.Send.SendText
+                .updateWithAdditionalDataIfNecessary(
+                    folderList = folderViewList,
+                    collectionList = collectionViewList,
+                ),
         )
     }
 
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt
index 61fa4bb8d..454b20800 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt
@@ -202,6 +202,44 @@ fun createMockDisplayItemForCipher(
                 isTotp = false,
             )
         }
+
+        CipherType.SSH_KEY -> {
+            VaultItemListingState.DisplayItem(
+                id = "mockId-$number",
+                title = "mockName-$number",
+                titleTestTag = "CipherNameLabel",
+                secondSubtitle = null,
+                secondSubtitleTestTag = secondSubtitleTestTag,
+                subtitle = subtitle,
+                subtitleTestTag = "CipherSubTitleLabel",
+                iconData = IconData.Local(R.drawable.ic_ssh_key),
+                extraIconList = listOf(
+                    IconRes(
+                        iconRes = R.drawable.ic_collections,
+                        contentDescription = R.string.collections.asText(),
+                        testTag = "CipherInCollectionIcon",
+                    ),
+                    IconRes(
+                        iconRes = R.drawable.ic_paperclip,
+                        contentDescription = R.string.attachments.asText(),
+                        testTag = "CipherWithAttachmentsIcon",
+                    ),
+                ),
+                overflowOptions = listOf(
+                    ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"),
+                    ListingItemOverflowAction.VaultAction.EditClick(
+                        cipherId = "mockId-$number",
+                        requiresPasswordReprompt = requiresPasswordReprompt,
+                    ),
+                ),
+                optionsTestTag = "CipherOptionsButton",
+                isAutofill = false,
+                isFido2Creation = false,
+                shouldShowMasterPasswordReprompt = false,
+                iconTestTag = "SshKeyCipherIcon",
+                isTotp = false,
+            )
+        }
     }
 
 /**
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensionsTest.kt
index 0485bf6ee..211d6dc58 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingStateExtensionsTest.kt
@@ -111,6 +111,16 @@ class VaultItemListingStateExtensionsTest {
         assertEquals(expected, result)
     }
 
+    @Test
+    fun `toSearchType should return SshKey when item type is SshKey`() {
+        val expected = SearchType.Vault.SshKeys
+        val itemType = VaultItemListingState.ItemListingType.Vault.SshKey
+
+        val result = itemType.toSearchType()
+
+        assertEquals(expected, result)
+    }
+
     @Test
     fun `toVaultItemCipherType should return the correct response`() {
         val itemListingTypes = listOf(
@@ -119,6 +129,7 @@ class VaultItemListingStateExtensionsTest {
             VaultItemListingState.ItemListingType.Vault.SecureNote,
             VaultItemListingState.ItemListingType.Vault.Login,
             VaultItemListingState.ItemListingType.Vault.Collection(collectionId = "mockId"),
+            VaultItemListingState.ItemListingType.Vault.SshKey,
         )
 
         val result = itemListingTypes.map { it.toVaultItemCipherType() }
@@ -130,6 +141,7 @@ class VaultItemListingStateExtensionsTest {
                 VaultItemCipherType.SECURE_NOTE,
                 VaultItemCipherType.LOGIN,
                 VaultItemCipherType.LOGIN,
+                VaultItemCipherType.SSH_KEY,
             ),
             result,
         )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt
index c29d7fe74..571c51dc8 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt
@@ -14,6 +14,13 @@ class VaultItemListingTypeExtensionsTest {
             VaultItemListingType.Folder(folderId = "mock"),
             VaultItemListingType.Trash,
             VaultItemListingType.Collection(collectionId = "collectionId"),
+            VaultItemListingType.SshKey,
+            VaultItemListingType.SendFile,
+            VaultItemListingType.SendText,
+            VaultItemListingType.Card,
+            VaultItemListingType.Identity,
+            VaultItemListingType.Login,
+            VaultItemListingType.SecureNote,
         )
 
         val result = itemListingTypeList.map { it.toItemListingType() }
@@ -25,6 +32,13 @@ class VaultItemListingTypeExtensionsTest {
                 VaultItemListingState.ItemListingType.Vault.Collection(
                     collectionId = "collectionId",
                 ),
+                VaultItemListingState.ItemListingType.Vault.SshKey,
+                VaultItemListingState.ItemListingType.Send.SendFile,
+                VaultItemListingState.ItemListingType.Send.SendText,
+                VaultItemListingState.ItemListingType.Vault.Card,
+                VaultItemListingState.ItemListingType.Vault.Identity,
+                VaultItemListingState.ItemListingType.Vault.Login,
+                VaultItemListingState.ItemListingType.Vault.SecureNote,
             ),
             result,
         )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt
index 157d01688..203f9a8fe 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt
@@ -9,6 +9,8 @@ import androidx.compose.ui.test.hasClickAction
 import androidx.compose.ui.test.hasScrollToNodeAction
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.isDialog
+import androidx.compose.ui.test.isDisplayed
+import androidx.compose.ui.test.isNotDisplayed
 import androidx.compose.ui.test.isPopup
 import androidx.compose.ui.test.onAllNodesWithText
 import androidx.compose.ui.test.onNodeWithContentDescription
@@ -704,6 +706,13 @@ class VaultScreenTest : BaseComposeTest() {
         assertEquals(VaultItemListingType.SecureNote, onNavigateToVaultItemListingType)
     }
 
+    @Test
+    @Suppress("MaxLineLength")
+    fun `NavigateToItemListing event for SshKey type should call onNavigateToVaultItemListingType with SshKey type`() {
+        mutableEventFlow.tryEmit(VaultEvent.NavigateToItemListing(VaultItemListingType.SshKey))
+        assertEquals(VaultItemListingType.SshKey, onNavigateToVaultItemListingType)
+    }
+
     @Test
     @Suppress("MaxLineLength")
     fun `NavigateToItemListing event for Trash type should call onNavigateToVaultItemListingType with Trash type`() {
@@ -1207,6 +1216,62 @@ class VaultScreenTest : BaseComposeTest() {
         mutableEventFlow.tryEmit(VaultEvent.ShowSnackbar(data))
         composeTestRule.onNodeWithText("message").assertIsDisplayed()
     }
+
+    @Test
+    fun `SSH key group header should display correctly based on state`() {
+        val count = 1
+        // Verify SSH key group is displayed when showSshKeys is true
+        mutableStateFlow.update {
+            it.copy(
+                showSshKeys = true,
+                viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
+                    sshKeyItemsCount = count,
+                ),
+            )
+        }
+        composeTestRule
+            .onNodeWithText("SSH key")
+            .assertTextEquals("SSH key", count.toString())
+            .assertIsDisplayed()
+
+        // Verify SSH key group is hidden when showSshKeys is false
+        mutableStateFlow.update { it.copy(showSshKeys = false) }
+        composeTestRule
+            .onNodeWithText("SSH key")
+            .assertIsNotDisplayed()
+    }
+
+    @Test
+    fun `SSH key vault items should display correctly based on state`() {
+        // Verify SSH key vault items are displayed when showSshKeys is true
+        mutableStateFlow.update {
+            it.copy(
+                showSshKeys = true,
+                viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
+                    noFolderItems = listOf(
+                        VaultState.ViewState.VaultItem.SshKey(
+                            id = "mockId",
+                            name = "mockSshKey".asText(),
+                            publicKey = "mockPublicKey".asText(),
+                            privateKey = "mockPrivateKey".asText(),
+                            fingerprint = "mockFingerprint".asText(),
+                            overflowOptions = emptyList(),
+                            shouldShowMasterPasswordReprompt = false,
+                        ),
+                    ),
+                ),
+            )
+        }
+        composeTestRule
+            .onNodeWithTextAfterScroll("mockSshKey")
+            .isDisplayed()
+
+        // Verify SSH key vault items are hidden when showSshKeys is false
+        mutableStateFlow.update { it.copy(showSshKeys = false) }
+        composeTestRule
+            .onNodeWithText("mockSshKey")
+            .isNotDisplayed()
+    }
 }
 
 private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(
@@ -1262,6 +1327,7 @@ private val DEFAULT_STATE: VaultState = VaultState(
     hideNotificationsDialog = true,
     isRefreshing = false,
     showImportActionCard = false,
+    showSshKeys = false,
 )
 
 private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultState.ViewState.Content(
@@ -1275,4 +1341,6 @@ private val DEFAULT_CONTENT_VIEW_STATE: VaultState.ViewState.Content = VaultStat
     collectionItems = emptyList(),
     trashItemsCount = 0,
     totpItemsCount = 0,
+    itemTypesCount = 4,
+    sshKeyItemsCount = 0,
 )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt
index 509b3bba5..d3071498d 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt
@@ -1,6 +1,7 @@
 package com.x8bit.bitwarden.ui.vault.feature.vault
 
 import app.cash.turbine.test
+import com.bitwarden.vault.CipherType
 import com.x8bit.bitwarden.R
 import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
 import com.x8bit.bitwarden.data.auth.repository.AuthRepository
@@ -124,10 +125,17 @@ class VaultViewModelTest : BaseViewModelTest() {
     }
 
     private val mutableImportLoginsFeatureFlow = MutableStateFlow(true)
+    private val mutableSshKeyVaultItemsEnabledFlow = MutableStateFlow(false)
     private val featureFlagManager: FeatureFlagManager = mockk {
         every {
             getFeatureFlagFlow(FlagKey.ImportLoginsFlow)
         } returns mutableImportLoginsFeatureFlow
+        every {
+            getFeatureFlagFlow(FlagKey.SshKeyCipherItems)
+        } returns mutableSshKeyVaultItemsEnabledFlow
+        every {
+            getFeatureFlag(FlagKey.SshKeyCipherItems)
+        } returns mutableSshKeyVaultItemsEnabledFlow.value
     }
 
     @Test
@@ -526,6 +534,7 @@ class VaultViewModelTest : BaseViewModelTest() {
                 isIconLoadingDisabled = viewModel.stateFlow.value.isIconLoadingDisabled,
                 baseIconUrl = viewModel.stateFlow.value.baseIconUrl,
                 hasMasterPassword = true,
+                showSshKeys = false,
             ),
         )
             .copy(
@@ -550,6 +559,7 @@ class VaultViewModelTest : BaseViewModelTest() {
                     isIconLoadingDisabled = viewModel.stateFlow.value.isIconLoadingDisabled,
                     baseIconUrl = viewModel.stateFlow.value.baseIconUrl,
                     hasMasterPassword = true,
+                    showSshKeys = false,
                 ),
             ),
             viewModel.stateFlow.value,
@@ -559,12 +569,31 @@ class VaultViewModelTest : BaseViewModelTest() {
 
     @Test
     fun `vaultDataStateFlow Loaded with items should update state to Content`() = runTest {
+        mutableSshKeyVaultItemsEnabledFlow.value = true
         mutableVaultDataStateFlow.tryEmit(
             value = DataState.Loaded(
                 data = VaultData(
-                    cipherViewList = listOf(createMockCipherView(number = 1)),
-                    collectionViewList = listOf(createMockCollectionView(number = 1)),
-                    folderViewList = listOf(createMockFolderView(number = 1)),
+                    cipherViewList = listOf(
+                        createMockCipherView(number = 1, cipherType = CipherType.LOGIN),
+                        createMockCipherView(number = 2, cipherType = CipherType.CARD),
+                        createMockCipherView(number = 3, cipherType = CipherType.IDENTITY),
+                        createMockCipherView(number = 4, cipherType = CipherType.SECURE_NOTE),
+                        createMockCipherView(number = 5, cipherType = CipherType.SSH_KEY),
+                    ),
+                    collectionViewList = listOf(
+                        createMockCollectionView(number = 1),
+                        createMockCollectionView(number = 2),
+                        createMockCollectionView(number = 3),
+                        createMockCollectionView(number = 4),
+                        createMockCollectionView(number = 5),
+                    ),
+                    folderViewList = listOf(
+                        createMockFolderView(number = 1),
+                        createMockFolderView(number = 2),
+                        createMockFolderView(number = 3),
+                        createMockFolderView(number = 4),
+                        createMockFolderView(number = 5),
+                    ),
                     sendViewList = listOf(createMockSendView(number = 1)),
                 ),
             ),
@@ -576,9 +605,9 @@ class VaultViewModelTest : BaseViewModelTest() {
             createMockVaultState(
                 viewState = VaultState.ViewState.Content(
                     loginItemsCount = 1,
-                    cardItemsCount = 0,
-                    identityItemsCount = 0,
-                    secureNoteItemsCount = 0,
+                    cardItemsCount = 1,
+                    identityItemsCount = 1,
+                    secureNoteItemsCount = 1,
                     favoriteItems = listOf(),
                     folderItems = listOf(
                         VaultState.ViewState.FolderItem(
@@ -586,6 +615,26 @@ class VaultViewModelTest : BaseViewModelTest() {
                             name = "mockName-1".asText(),
                             itemCount = 1,
                         ),
+                        VaultState.ViewState.FolderItem(
+                            id = "mockId-2",
+                            name = "mockName-2".asText(),
+                            itemCount = 1,
+                        ),
+                        VaultState.ViewState.FolderItem(
+                            id = "mockId-3",
+                            name = "mockName-3".asText(),
+                            itemCount = 1,
+                        ),
+                        VaultState.ViewState.FolderItem(
+                            id = "mockId-4",
+                            name = "mockName-4".asText(),
+                            itemCount = 1,
+                        ),
+                        VaultState.ViewState.FolderItem(
+                            id = "mockId-5",
+                            name = "mockName-5".asText(),
+                            itemCount = 1,
+                        ),
                     ),
                     collectionItems = listOf(
                         VaultState.ViewState.CollectionItem(
@@ -593,11 +642,34 @@ class VaultViewModelTest : BaseViewModelTest() {
                             name = "mockName-1",
                             itemCount = 1,
                         ),
+                        VaultState.ViewState.CollectionItem(
+                            id = "mockId-2",
+                            name = "mockName-2",
+                            itemCount = 1,
+                        ),
+                        VaultState.ViewState.CollectionItem(
+                            id = "mockId-3",
+                            name = "mockName-3",
+                            itemCount = 1,
+                        ),
+                        VaultState.ViewState.CollectionItem(
+                            id = "mockId-4",
+                            name = "mockName-4",
+                            itemCount = 1,
+                        ),
+                        VaultState.ViewState.CollectionItem(
+                            id = "mockId-5",
+                            name = "mockName-5",
+                            itemCount = 1,
+                        ),
                     ),
                     noFolderItems = listOf(),
                     trashItemsCount = 0,
                     totpItemsCount = 1,
+                    itemTypesCount = CipherType.entries.size,
+                    sshKeyItemsCount = 1,
                 ),
+                showSshKeys = true,
             ),
             viewModel.stateFlow.value,
         )
@@ -619,6 +691,8 @@ class VaultViewModelTest : BaseViewModelTest() {
                     noFolderItems = listOf(),
                     trashItemsCount = 0,
                     totpItemsCount = 1,
+                    itemTypesCount = 4,
+                    sshKeyItemsCount = 0,
                 ),
             )
             val viewModel = createViewModel()
@@ -731,6 +805,8 @@ class VaultViewModelTest : BaseViewModelTest() {
                     noFolderItems = listOf(),
                     trashItemsCount = 0,
                     totpItemsCount = 1,
+                    itemTypesCount = 4,
+                    sshKeyItemsCount = 0,
                 ),
             ),
             viewModel.stateFlow.value,
@@ -829,6 +905,8 @@ class VaultViewModelTest : BaseViewModelTest() {
                         noFolderItems = listOf(),
                         trashItemsCount = 0,
                         totpItemsCount = 1,
+                        itemTypesCount = 4,
+                        sshKeyItemsCount = 0,
                     ),
                     dialog = VaultState.DialogState.Error(
                         title = R.string.an_error_has_occurred.asText(),
@@ -927,6 +1005,8 @@ class VaultViewModelTest : BaseViewModelTest() {
                         noFolderItems = listOf(),
                         trashItemsCount = 0,
                         totpItemsCount = 1,
+                        itemTypesCount = 4,
+                        sshKeyItemsCount = 0,
                     ),
                     dialog = VaultState.DialogState.Error(
                         title = R.string.internet_connection_required_title.asText(),
@@ -999,6 +1079,88 @@ class VaultViewModelTest : BaseViewModelTest() {
         )
     }
 
+    @Test
+    fun `vaultDataStateFlow Loaded should exclude SSH key vault items when showSshKeys is false`() =
+        runTest {
+            mutableVaultDataStateFlow.tryEmit(
+                value = DataState.Loaded(
+                    data = VaultData(
+                        cipherViewList = listOf(
+                            createMockCipherView(number = 1),
+                            createMockCipherView(number = 1, cipherType = CipherType.SSH_KEY),
+                        ),
+                        collectionViewList = listOf(),
+                        folderViewList = listOf(),
+                        sendViewList = listOf(),
+                    ),
+                ),
+            )
+
+            val viewModel = createViewModel()
+
+            assertEquals(
+                createMockVaultState(
+                    viewState = VaultState.ViewState.Content(
+                        loginItemsCount = 1,
+                        cardItemsCount = 0,
+                        identityItemsCount = 0,
+                        secureNoteItemsCount = 0,
+                        favoriteItems = listOf(),
+                        folderItems = listOf(),
+                        collectionItems = listOf(),
+                        noFolderItems = listOf(),
+                        trashItemsCount = 0,
+                        totpItemsCount = 1,
+                        itemTypesCount = CipherType.entries.size - 1,
+                        sshKeyItemsCount = 0,
+                    ),
+                ),
+                viewModel.stateFlow.value,
+            )
+        }
+
+    @Test
+    fun `vaultDataStateFlow Loaded should include SSH key vault items when showSshKeys is true`() =
+        runTest {
+            mutableSshKeyVaultItemsEnabledFlow.value = true
+            mutableVaultDataStateFlow.tryEmit(
+                value = DataState.Loaded(
+                    data = VaultData(
+                        cipherViewList = listOf(
+                            createMockCipherView(number = 1),
+                            createMockCipherView(number = 1, cipherType = CipherType.SSH_KEY),
+                        ),
+                        collectionViewList = listOf(),
+                        folderViewList = listOf(),
+                        sendViewList = listOf(),
+                    ),
+                ),
+            )
+
+            val viewModel = createViewModel()
+
+            assertEquals(
+                createMockVaultState(
+                    viewState = VaultState.ViewState.Content(
+                        loginItemsCount = 1,
+                        cardItemsCount = 0,
+                        identityItemsCount = 0,
+                        secureNoteItemsCount = 0,
+                        favoriteItems = listOf(),
+                        folderItems = listOf(),
+                        collectionItems = listOf(),
+                        noFolderItems = listOf(),
+                        trashItemsCount = 0,
+                        totpItemsCount = 1,
+                        itemTypesCount = CipherType.entries.size,
+                        sshKeyItemsCount = 1,
+                    ),
+                    showSshKeys = true,
+                ),
+                viewModel.stateFlow.value,
+            )
+        }
+
     @Test
     fun `VerificationCodesClick should emit NavigateToVerificationCodeScreen`() = runTest {
         val viewModel = createViewModel()
@@ -1112,6 +1274,18 @@ class VaultViewModelTest : BaseViewModelTest() {
             }
         }
 
+    @Test
+    fun `SshKeyGroupClick should emit NavigateToItemListing event with SshKey type`() = runTest {
+        val viewModel = createViewModel()
+        viewModel.eventFlow.test {
+            viewModel.trySendAction(VaultAction.SshKeyGroupClick)
+            assertEquals(
+                VaultEvent.NavigateToItemListing(VaultItemListingType.SshKey),
+                awaitItem(),
+            )
+        }
+    }
+
     @Test
     fun `TrashClick should emit NavigateToItemListing event with Trash type`() = runTest {
         val viewModel = createViewModel()
@@ -1720,6 +1894,7 @@ private val DEFAULT_USER_STATE = UserState(
 private fun createMockVaultState(
     viewState: VaultState.ViewState,
     dialog: VaultState.DialogState? = null,
+    showSshKeys: Boolean = false,
 ): VaultState =
     VaultState(
         appBarTitle = R.string.my_vault.asText(),
@@ -1758,4 +1933,5 @@ private fun createMockVaultState(
         hideNotificationsDialog = true,
         showImportActionCard = true,
         isRefreshing = false,
+        showSshKeys = showSshKeys,
     )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt
index 43c4d4be4..6430fc086 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt
@@ -12,6 +12,7 @@ import com.bitwarden.vault.LoginView
 import com.bitwarden.vault.PasswordHistoryView
 import com.bitwarden.vault.SecureNoteType
 import com.bitwarden.vault.SecureNoteView
+import com.bitwarden.vault.SshKeyView
 import com.bitwarden.vault.UriMatchType
 import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkFido2CredentialList
 import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
@@ -112,6 +113,7 @@ class VaultAddItemStateExtensionsTest {
                 creationDate = Instant.MIN,
                 deletedDate = null,
                 revisionDate = Instant.MIN,
+                sshKey = null,
             ),
             result,
         )
@@ -295,6 +297,7 @@ class VaultAddItemStateExtensionsTest {
                 creationDate = Instant.MIN,
                 deletedDate = null,
                 revisionDate = Instant.MIN,
+                sshKey = null,
             ),
             result,
         )
@@ -426,6 +429,7 @@ class VaultAddItemStateExtensionsTest {
                 creationDate = Instant.MIN,
                 deletedDate = null,
                 revisionDate = Instant.MIN,
+                sshKey = null,
             ),
             result,
         )
@@ -611,6 +615,7 @@ class VaultAddItemStateExtensionsTest {
                 creationDate = Instant.MIN,
                 deletedDate = null,
                 revisionDate = Instant.MIN,
+                sshKey = null,
             ),
             result,
         )
@@ -703,6 +708,65 @@ class VaultAddItemStateExtensionsTest {
         )
     }
 
+    @Test
+    fun `toCipherView should transform SSH Key ItemType to CipherView`() {
+        mockkStatic(Instant::class)
+        every { Instant.now() } returns Instant.MIN
+        val viewState = VaultAddEditState.ViewState.Content(
+            common = VaultAddEditState.ViewState.Content.Common(
+                name = "mockName-1",
+                selectedFolderId = "mockId-1",
+                favorite = false,
+                masterPasswordReprompt = false,
+                notes = "mockNotes-1",
+                selectedOwnerId = "mockOwnerId-1",
+            ),
+            isIndividualVaultDisabled = false,
+            type = VaultAddEditState.ViewState.Content.ItemType.SshKey(
+                publicKey = "mockPublicKey-1",
+                privateKey = "mockPrivateKey-1",
+                fingerprint = "mockFingerprint-1",
+            ),
+        )
+
+        val result = viewState.toCipherView()
+
+        assertEquals(
+            CipherView(
+                id = null,
+                organizationId = "mockOwnerId-1",
+                folderId = "mockId-1",
+                collectionIds = emptyList(),
+                key = null,
+                name = "mockName-1",
+                notes = "mockNotes-1",
+                type = CipherType.SSH_KEY,
+                login = null,
+                identity = null,
+                card = null,
+                secureNote = null,
+                favorite = false,
+                reprompt = CipherRepromptType.NONE,
+                organizationUseTotp = false,
+                edit = true,
+                viewPassword = true,
+                localData = null,
+                attachments = null,
+                fields = emptyList(),
+                passwordHistory = null,
+                creationDate = Instant.MIN,
+                deletedDate = null,
+                revisionDate = Instant.MIN,
+                sshKey = SshKeyView(
+                    publicKey = "mockPublicKey-1",
+                    privateKey = "mockPrivateKey-1",
+                    fingerprint = "mockFingerprint-1",
+                ),
+            ),
+            result,
+        )
+    }
+
     @Suppress("MaxLineLength")
     @Test
     fun `toLoginView should transform Login ItemType to LoginView deleting fido2Credentials with original cipher`() {
@@ -911,6 +975,7 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
     creationDate = Instant.MIN,
     deletedDate = null,
     revisionDate = Instant.MIN,
+    sshKey = null,
 )
 
 private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.copy(
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt
index 5ea4c6e64..17e257de1 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt
@@ -1,6 +1,7 @@
 package com.x8bit.bitwarden.ui.vault.feature.vault.util
 
 import android.net.Uri
+import com.bitwarden.vault.CipherRepromptType
 import com.bitwarden.vault.CipherType
 import com.bitwarden.vault.FolderView
 import com.bitwarden.vault.LoginUriView
@@ -12,9 +13,12 @@ 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.createMockSendView
+import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSshKeyView
 import com.x8bit.bitwarden.data.vault.repository.model.VaultData
 import com.x8bit.bitwarden.ui.platform.base.util.asText
 import com.x8bit.bitwarden.ui.platform.components.model.IconData
+import com.x8bit.bitwarden.ui.platform.components.model.IconRes
+import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
 import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
 import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
 import io.mockk.every
@@ -57,6 +61,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -94,6 +99,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -118,6 +125,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.MyVault,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -138,6 +146,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -171,6 +181,7 @@ class VaultDataExtensionsTest {
                 organizationName = "Mock Organization 1",
             ),
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -197,6 +208,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -217,6 +230,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -240,6 +254,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -264,6 +279,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -278,6 +294,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -299,6 +317,7 @@ class VaultDataExtensionsTest {
             isIconLoadingDisabled = false,
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -313,6 +332,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 0,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -334,6 +355,7 @@ class VaultDataExtensionsTest {
             isIconLoadingDisabled = false,
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -348,6 +370,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -371,6 +395,7 @@ class VaultDataExtensionsTest {
             isIconLoadingDisabled = false,
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -385,6 +410,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -592,6 +619,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -606,6 +634,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 2,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -629,6 +659,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -643,6 +674,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 2,
                 totpItemsCount = 0,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -669,6 +702,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -689,6 +723,8 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 100,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
             ),
             actual,
         )
@@ -722,6 +758,7 @@ class VaultDataExtensionsTest {
             baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
             vaultFilterType = VaultFilterType.AllVaults,
             hasMasterPassword = true,
+            showSshKeys = false,
         )
 
         assertEquals(
@@ -764,8 +801,195 @@ class VaultDataExtensionsTest {
                 noFolderItems = listOf(),
                 trashItemsCount = 0,
                 totpItemsCount = 1,
+                itemTypesCount = 4,
+                sshKeyItemsCount = 0,
+            ),
+            actual,
+        )
+    }
+
+    @Test
+    fun `toViewState should exclude SSH keys if showSshKeys is false`() {
+        val vaultData = VaultData(
+            cipherViewList = listOf(
+                createMockCipherView(number = 1),
+                createMockCipherView(number = 2, cipherType = CipherType.SSH_KEY),
+            ),
+            collectionViewList = listOf(),
+            folderViewList = listOf(),
+            sendViewList = listOf(),
+            fido2CredentialAutofillViewList = null,
+        )
+
+        val actual = vaultData.toViewState(
+            isPremium = true,
+            isIconLoadingDisabled = false,
+            baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
+            vaultFilterType = VaultFilterType.AllVaults,
+            hasMasterPassword = true,
+            showSshKeys = false,
+        )
+
+        assertEquals(
+            VaultState.ViewState.Content(
+                loginItemsCount = 1,
+                cardItemsCount = 0,
+                identityItemsCount = 0,
+                secureNoteItemsCount = 0,
+                // Verify SSH key vault items are not counted when showSshKeys is false.
+                sshKeyItemsCount = 0,
+                favoriteItems = listOf(),
+                collectionItems = listOf(),
+                folderItems = listOf(),
+                noFolderItems = listOf(),
+                trashItemsCount = 0,
+                totpItemsCount = 1,
+                // Verify item types count excludes CipherType.SSH_KEY when showSshKeys is false.
+                itemTypesCount = 4,
+            ),
+            actual,
+        )
+    }
+
+    @Test
+    fun `toViewState should include SSH key vault items and type count if showSshKeys is true`() {
+        val vaultData = VaultData(
+            cipherViewList = listOf(
+                createMockCipherView(number = 1),
+                createMockCipherView(number = 2, cipherType = CipherType.SSH_KEY),
+            ),
+            collectionViewList = listOf(),
+            folderViewList = listOf(),
+            sendViewList = listOf(),
+            fido2CredentialAutofillViewList = null,
+        )
+
+        val actual = vaultData.toViewState(
+            isPremium = true,
+            isIconLoadingDisabled = false,
+            baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
+            vaultFilterType = VaultFilterType.AllVaults,
+            hasMasterPassword = true,
+            showSshKeys = true,
+        )
+
+        assertEquals(
+            VaultState.ViewState.Content(
+                loginItemsCount = 1,
+                cardItemsCount = 0,
+                identityItemsCount = 0,
+                secureNoteItemsCount = 0,
+                // Verify SSH key vault items are counted
+                sshKeyItemsCount = 1,
+                favoriteItems = listOf(),
+                collectionItems = listOf(),
+                folderItems = listOf(),
+                noFolderItems = listOf(),
+                trashItemsCount = 0,
+                totpItemsCount = 1,
+                // Verify item types count includes all CipherTypes when showSshKeys is true.
+                itemTypesCount = CipherType.entries.size,
+            ),
+            actual,
+        )
+    }
+
+    @Test
+    fun `toViewState should transform SSH key vault items into correct vault item`() {
+        val vaultData = VaultData(
+            cipherViewList = listOf(
+                createMockCipherView(number = 1, cipherType = CipherType.SSH_KEY, folderId = null),
+                createMockCipherView(
+                    number = 2,
+                    cipherType = CipherType.SSH_KEY,
+                    repromptType = CipherRepromptType.PASSWORD,
+                    folderId = null,
+                    sshKey = createMockSshKeyView(number = 1)
+                        .copy(
+                            publicKey = null,
+                            privateKey = null,
+                            fingerprint = null,
+                        ),
+                ),
+                createMockCipherView(
+                    number = 3,
+                    cipherType = CipherType.SSH_KEY,
+                    folderId = null,
+                    sshKey = null,
+                ),
+            ),
+            collectionViewList = listOf(),
+            folderViewList = listOf(),
+            sendViewList = listOf(),
+        )
+        val actual = vaultData.toViewState(
+            isPremium = true,
+            isIconLoadingDisabled = false,
+            baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
+            vaultFilterType = VaultFilterType.AllVaults,
+            hasMasterPassword = true,
+            showSshKeys = true,
+        )
+
+        assertEquals(
+            VaultState.ViewState.Content(
+                loginItemsCount = 0,
+                cardItemsCount = 0,
+                identityItemsCount = 0,
+                secureNoteItemsCount = 0,
+                sshKeyItemsCount = 3,
+                favoriteItems = listOf(),
+                collectionItems = listOf(),
+                folderItems = listOf(),
+                noFolderItems = listOf(
+                    createMockSshKeyVaultItem(number = 1),
+                    createMockSshKeyVaultItem(number = 2)
+                        .copy(
+                            publicKey = null,
+                            privateKey = null,
+                            fingerprint = null,
+                            shouldShowMasterPasswordReprompt = true,
+                        ),
+                    createMockSshKeyVaultItem(number = 3)
+                        .copy(
+                            publicKey = null,
+                            privateKey = null,
+                            fingerprint = null,
+                        ),
+                ),
+                trashItemsCount = 0,
+                totpItemsCount = 0,
+                itemTypesCount = CipherType.entries.size,
             ),
             actual,
         )
     }
 }
+
+private fun createMockSshKeyVaultItem(number: Int): VaultState.ViewState.VaultItem.SshKey =
+    VaultState.ViewState.VaultItem.SshKey(
+        id = "mockId-$number",
+        name = "mockName-$number".asText(),
+        publicKey = "mockPublicKey-$number".asText(),
+        privateKey = "mockPrivateKey-$number".asText(),
+        fingerprint = "mockKeyFingerprint-$number".asText(),
+        overflowOptions = listOf(
+            ListingItemOverflowAction.VaultAction.ViewClick("mockId-$number"),
+            ListingItemOverflowAction.VaultAction.EditClick("mockId-$number", true),
+        ),
+        startIcon = IconData.Local(iconRes = R.drawable.ic_ssh_key),
+        startIconTestTag = "SshKeyCipherIcon",
+        extraIconList = listOf(
+            IconRes(
+                iconRes = R.drawable.ic_collections,
+                contentDescription = R.string.collections.asText(),
+                testTag = "CipherInCollectionIcon",
+            ),
+            IconRes(
+                iconRes = R.drawable.ic_paperclip,
+                contentDescription = R.string.attachments.asText(),
+                testTag = "CipherWithAttachmentsIcon",
+            ),
+        ),
+        shouldShowMasterPasswordReprompt = false,
+    )
diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt
index 6d275b9eb..47ab815fb 100644
--- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt
+++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt
@@ -83,5 +83,7 @@ class VaultStateExtensionsTest {
             noFolderItems = listOf(),
             trashItemsCount = 0,
             totpItemsCount = 1,
+            itemTypesCount = 4,
+            sshKeyItemsCount = 0,
         )
 }
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ca783f41e..2049224d1 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 = "1.0.0-20240924.112512-21"
+bitwardenSdk = "1.0.0-20241021.160919-71"
 crashlytics = "3.0.2"
 detekt = "1.23.7"
 firebaseBom = "33.5.1"