bitwarden_vault/cipher/
ssh_key.rs1use bitwarden_api_api::models::CipherSshKeyModel;
2use bitwarden_core::{
3 key_management::{KeyIds, SymmetricKeyId},
4 require,
5};
6use bitwarden_crypto::{
7 CompositeEncryptable, CryptoError, Decryptable, EncString, KeyStoreContext,
8 PrimitiveEncryptable,
9};
10use serde::{Deserialize, Serialize};
11#[cfg(feature = "wasm")]
12use tsify::Tsify;
13
14use super::cipher::CipherKind;
15use crate::{cipher::cipher::CopyableCipherFields, Cipher, VaultParseError};
16
17#[derive(Serialize, Deserialize, Debug, Clone)]
18#[serde(rename_all = "camelCase", deny_unknown_fields)]
19#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
20#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
21pub struct SshKey {
22 pub private_key: EncString,
24 pub public_key: EncString,
26 pub fingerprint: EncString,
28}
29
30#[allow(missing_docs)]
31#[derive(Serialize, Deserialize, Debug, Clone)]
32#[serde(rename_all = "camelCase", deny_unknown_fields)]
33#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
34#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
35pub struct SshKeyView {
36 pub private_key: String,
38 pub public_key: String,
40 pub fingerprint: String,
42}
43
44impl CompositeEncryptable<KeyIds, SymmetricKeyId, SshKey> for SshKeyView {
45 fn encrypt_composite(
46 &self,
47 ctx: &mut KeyStoreContext<KeyIds>,
48 key: SymmetricKeyId,
49 ) -> Result<SshKey, CryptoError> {
50 Ok(SshKey {
51 private_key: self.private_key.encrypt(ctx, key)?,
52 public_key: self.public_key.encrypt(ctx, key)?,
53 fingerprint: self.fingerprint.encrypt(ctx, key)?,
54 })
55 }
56}
57
58impl Decryptable<KeyIds, SymmetricKeyId, SshKeyView> for SshKey {
59 fn decrypt(
60 &self,
61 ctx: &mut KeyStoreContext<KeyIds>,
62 key: SymmetricKeyId,
63 ) -> Result<SshKeyView, CryptoError> {
64 Ok(SshKeyView {
65 private_key: self.private_key.decrypt(ctx, key)?,
66 public_key: self.public_key.decrypt(ctx, key)?,
67 fingerprint: self.fingerprint.decrypt(ctx, key)?,
68 })
69 }
70}
71
72impl CipherKind for SshKey {
73 fn decrypt_subtitle(
74 &self,
75 ctx: &mut KeyStoreContext<KeyIds>,
76 key: SymmetricKeyId,
77 ) -> Result<String, CryptoError> {
78 self.fingerprint.decrypt(ctx, key)
79 }
80
81 fn get_copyable_fields(&self, _: Option<&Cipher>) -> Vec<CopyableCipherFields> {
82 [CopyableCipherFields::SshKey].into_iter().collect()
83 }
84}
85
86impl TryFrom<CipherSshKeyModel> for SshKey {
87 type Error = VaultParseError;
88
89 fn try_from(ssh_key: CipherSshKeyModel) -> Result<Self, Self::Error> {
90 Ok(Self {
91 private_key: require!(EncString::try_from_optional(ssh_key.private_key)?),
92 public_key: require!(EncString::try_from_optional(ssh_key.public_key)?),
93 fingerprint: require!(EncString::try_from_optional(ssh_key.key_fingerprint)?),
94 })
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use bitwarden_core::key_management::create_test_crypto_with_user_key;
101 use bitwarden_crypto::SymmetricCryptoKey;
102
103 use super::*;
104 use crate::cipher::cipher::CopyableCipherFields;
105
106 #[test]
107 fn test_subtitle_ssh_key() {
108 let key = SymmetricCryptoKey::try_from("hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe0+G8EwxvW3v1iywVmSl61iwzd17JW5C/ivzxSP2C9h7Tw==".to_string()).unwrap();
109 let key_store = create_test_crypto_with_user_key(key);
110 let key = SymmetricKeyId::User;
111 let mut ctx = key_store.context();
112
113 let original_subtitle = "SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0".to_string();
114 let fingerprint_encrypted = original_subtitle.to_owned().encrypt(&mut ctx, key).unwrap();
115 let private_key_encrypted = "".to_string().encrypt(&mut ctx, key).unwrap();
116 let public_key_encrypted = "".to_string().encrypt(&mut ctx, key).unwrap();
117
118 let ssh_key = SshKey {
119 private_key: private_key_encrypted,
120 public_key: public_key_encrypted,
121 fingerprint: fingerprint_encrypted,
122 };
123
124 assert_eq!(
125 ssh_key.decrypt_subtitle(&mut ctx, key).unwrap(),
126 original_subtitle
127 );
128 }
129
130 #[test]
131 fn test_get_copyable_fields_sshkey() {
132 let ssh_key = SshKey {
133 private_key: "2.tMIugb6zQOL+EuOizna1wQ==|W5dDLoNJtajN68yeOjrr6w==|qS4hwJB0B0gNLI0o+jxn+sKMBmvtVgJCRYNEXBZoGeE=".parse().unwrap(),
134 public_key: "2.tMIugb6zQOL+EuOizna1wQ==|W5dDLoNJtajN68yeOjrr6w==|qS4hwJB0B0gNLI0o+jxn+sKMBmvtVgJCRYNEXBZoGeE=".parse().unwrap(),
135 fingerprint: "2.tMIugb6zQOL+EuOizna1wQ==|W5dDLoNJtajN68yeOjrr6w==|qS4hwJB0B0gNLI0o+jxn+sKMBmvtVgJCRYNEXBZoGeE=".parse().unwrap(),
136 };
137
138 let copyable_fields = ssh_key.get_copyable_fields(None);
139 assert_eq!(copyable_fields, vec![CopyableCipherFields::SshKey]);
140 }
141}