uni_vault/uni_vault.rs
1//! Universal Vault implementation for secure data storage
2//!
3//! This module provides the core functionality for storing and managing sensitive data
4//! in both encrypted and unencrypted formats. It will support multiple encryption methods
5//! including password-based encryption and hardware wallet (Trezor) based encryption.
6
7use crate::{
8 encryptor::{
9 Encryptor,
10 SecretType,
11 },
12 re_export::std_anyhow::*,
13 common::traits::JsonSerializable
14};
15
16/// # Example
17///
18/// ```
19/// use uni_vault::{
20/// uni_vault::{
21/// UniVault,
22/// UniVaultBuilder,
23/// },
24/// encryptor::{
25/// asymmetric::rage_trezor::{
26/// RageTrezorEncryptorBuilder,
27/// RageTrezorEncryptor,
28/// },
29/// Encryptor,
30/// HasUserIdentifier,
31/// SecretType,
32/// },
33/// re_export::std_anyhow::*,
34/// common::traits::JsonSerializable,
35/// common::test_helpers::{
36/// TREZOR_ENCRYPT_COMMENT,
37/// print_json_with_line,
38/// debug_with_line
39/// },
40/// };
41///
42/// let password_for_test = 12345678.to_string();
43/// let password_as_bytes = password_for_test.as_bytes();
44/// let mut recipients_with_my_backup_key = HashSet::new();
45/// recipients_with_my_backup_key.insert("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILD0y5DXSqOmx/Tf0pKP23+JrCVSUrezlPDf/vcFFt3s".to_string());
46///
47/// let mut rage_trezor_encryptor: RageTrezorEncryptor = RageTrezorEncryptorBuilder::default()
48/// .platform_id("uni.vault.onion")
49/// .username("test_account")
50/// .should_create_new_recipient(true)
51/// .recipients(recipients_with_my_backup_key)
52/// .secret_type(SecretType::Password)
53/// .bip32_path("m/44h/60h/11h/0/12")
54/// .comment(TREZOR_ENCRYPT_COMMENT)
55/// .build()
56/// .unwrap();
57///
58/// let mut uni_vault: UniVault<RageTrezorEncryptor> = UniVaultBuilder::default()
59/// .encryptor(rage_trezor_encryptor)
60/// .data_type(SecretType::Password)
61/// .data(password_as_bytes)
62/// .build().unwrap();
63/// print_json_with_line!(&uni_vault);
64///
65/// uni_vault.encrypt().unwrap();
66/// print_json_with_line!(&uni_vault);
67///
68/// uni_vault.decrypt().unwrap();
69/// print_json_with_line!(&uni_vault);
70///
71/// assert_eq!(password_as_bytes, uni_vault.data);
72/// ```
73
74#[derive(Debug, Clone, Serialize, Deserialize, Builder, Zeroize, ZeroizeOnDrop)]
75pub struct UniVault<T: Encryptor + Serialize> {
76 /// The encryption mechanism used by this vault.
77 #[zeroize(skip)]
78 pub encryptor: T,
79
80 /// Type of data stored in the vault, which influences how the data is handled.
81 #[zeroize(skip)]
82 #[builder(setter(into))]
83 #[builder(default = "SecretType::Text")]
84 pub data_type: SecretType,
85
86 /// Flag indicating whether the data in the vault is currently encrypted.
87 #[zeroize(skip)]
88 #[builder(setter(into))]
89 #[builder(default = "false")]
90 pub encrypted: bool,
91
92 /// The actual data content of the vault, which could be plaintext or encrypted bytes.
93 #[builder(setter(into))]
94 #[builder(default = "Vec::new()")]
95 pub data: Vec<u8>,
96}
97
98impl<T: Encryptor + Serialize + for<'de> Deserialize<'de>> JsonSerializable for UniVault<T> {}
99
100impl<T: Encryptor + Serialize + for<'de> Deserialize<'de>> UniVault<T> {
101 /// Encrypts the vault contents.
102 ///
103 /// # Returns
104 ///
105 /// Returns `Ok(())` if encryption is successful, or an `Error` if:
106 /// - The data is already encrypted.
107 /// - Encryption fails.
108 pub fn encrypt(&mut self) -> anyhow::Result<()> {
109 if self.encrypted {
110 return Err(Error::msg("Do not encrypt already encrypted Bytes again"));
111 }
112
113 let encrypted_bytes = self.encryptor.encrypt(&self.data)?;
114 self.data.zeroize();
115 self.data = encrypted_bytes;
116 self.encrypted = true;
117
118 Ok(())
119 }
120
121 /// Decrypts the vault contents.
122 ///
123 /// # Returns
124 ///
125 /// Returns `Ok(())` if decryption is successful, or an `Error` if:
126 /// - The data is not encrypted.
127 /// - Decryption fails.
128 pub fn decrypt(&mut self) -> anyhow::Result<()> {
129 if !self.encrypted {
130 return Err(Error::msg("Operation only supported on encrypted bytes"));
131 }
132 self.data = self.encryptor.decrypt(&self.data)?;
133 self.encrypted = false;
134 Ok(())
135 }
136
137 /// Decrypts and decodes a JSON string representation of a vault.
138 ///
139 /// This method parses a JSON string into a vault instance and then decrypts it.
140 ///
141 /// # Arguments
142 ///
143 /// * `json_str` - The JSON string representation of the vault.
144 ///
145 /// # Returns
146 ///
147 /// Returns `Ok(Self)` if decryption and decoding are successful, or an `Error` if:
148 /// - JSON parsing fails.
149 /// - Decryption fails.
150 pub fn decrypt_and_decode_json_string(
151 json_str: &str,
152 ) -> anyhow::Result<Self> {
153 let mut uni_vault = UniVault::from_json(json_str)?;
154
155 uni_vault.decrypt()?;
156
157 Ok(uni_vault)
158 }
159
160 /// Converts the vault's data to a UTF-8 String.
161 ///
162 /// This method will decrypt the data if it's encrypted before attempting to convert it to a String.
163 ///
164 /// # Returns
165 ///
166 /// Returns `Ok(String)` if conversion is successful, or an `Error` if:
167 /// - The data isn't valid UTF-8.
168 /// - Decryption fails (if the data is encrypted).
169 pub fn data_to_string(&mut self) -> anyhow::Result<String> {
170 if self.encrypted {
171 self.decrypt()?;
172 }
173
174 let data_str = String::from_utf8(self.data.clone())
175 .map_err(Error::msg)?;
176
177 Ok(data_str)
178 }
179}