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