1pub mod crypto;
2pub mod dio;
3pub mod find;
4pub mod fingerprint;
5pub mod online;
6pub mod remote;
7pub mod ssh;
8
9pub mod parse;
10pub use self::parse::parse;
11
12use anyhow::Result;
13use secrecy::SecretSlice;
14use ssh_key::{PrivateKey, PublicKey};
15
16#[derive(Debug, PartialEq, Eq)]
18pub enum SshKeyType {
19 Ed25519,
21 Rsa,
23}
24
25pub struct SshVault {
31 vault: Box<dyn Vault>,
32}
33
34impl SshVault {
35 pub fn new(
64 key_type: &SshKeyType,
65 public: Option<PublicKey>,
66 private: Option<PrivateKey>,
67 ) -> Result<Self> {
68 let vault = match key_type {
69 SshKeyType::Ed25519 => {
70 Box::new(ssh::ed25519::Ed25519Vault::new(public, private)?) as Box<dyn Vault>
71 }
72 SshKeyType::Rsa => {
73 Box::new(ssh::rsa::RsaVault::new(public, private)?) as Box<dyn Vault>
74 }
75 };
76 Ok(Self { vault })
77 }
78
79 pub fn create(&self, password: SecretSlice<u8>, data: &mut [u8]) -> Result<String> {
100 self.vault.create(password, data)
101 }
102
103 pub fn view(&self, password: &[u8], data: &[u8], fingerprint: &str) -> Result<String> {
122 self.vault.view(password, data, fingerprint)
123 }
124}
125
126pub trait Vault {
128 fn new(public: Option<PublicKey>, private: Option<PrivateKey>) -> Result<Self>
134 where
135 Self: Sized;
136
137 fn create(&self, password: SecretSlice<u8>, data: &mut [u8]) -> Result<String>;
143
144 fn view(&self, password: &[u8], data: &[u8], fingerprint: &str) -> Result<String>;
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::vault::{
156 Vault, crypto, parse, ssh::decrypt_private_key, ssh::ed25519::Ed25519Vault,
157 ssh::rsa::RsaVault,
158 };
159 use secrecy::{SecretSlice, SecretString};
160 use ssh_key::PublicKey;
161 use std::path::Path;
162
163 struct Test {
164 public_key: &'static str,
165 private_key: &'static str,
166 passphrase: &'static str,
167 }
168
169 const SECRET: &str = "Take care of your thoughts, because they will become your words. Take care of your words, because they will become your actions. Take care of your actions, because they will become your habits. Take care of your habits, because they will become your destiny";
170
171 #[test]
172 fn test_rsa_vault() -> Result<()> {
173 let public_key_file = Path::new("test_data/id_rsa.pub");
174 let private_key_file = Path::new("test_data/id_rsa");
175 let public_key = PublicKey::read_openssh_file(public_key_file)?;
176 let private_key = PrivateKey::read_openssh_file(private_key_file)?;
177
178 let vault = RsaVault::new(Some(public_key), None)?;
179
180 let password: SecretSlice<u8> = crypto::gen_password()?;
181
182 let mut secret = String::from(SECRET).into_bytes();
183
184 assert!(secret.iter().all(|&byte| byte != 0));
186
187 let vault = vault.create(password, &mut secret)?;
188
189 assert!(secret.iter().all(|&byte| byte == 0));
191
192 let (_key_type, fingerprint, password, data) = parse(&vault)?;
193
194 let view = RsaVault::new(None, Some(private_key))?;
195
196 let vault = view.view(&password, &data, &fingerprint)?;
197
198 assert_eq!(vault, SECRET);
199 Ok(())
200 }
201
202 #[test]
203 fn test_ed25519_vault() -> Result<()> {
204 let public_key_file = Path::new("test_data/ed25519.pub");
205 let private_key_file = Path::new("test_data/ed25519");
206 let public_key = PublicKey::read_openssh_file(public_key_file)?;
207 let private_key = PrivateKey::read_openssh_file(private_key_file)?;
208
209 let vault = Ed25519Vault::new(Some(public_key), None)?;
210
211 let password: SecretSlice<u8> = crypto::gen_password()?;
212
213 let mut secret = String::from(SECRET).into_bytes();
214
215 assert!(secret.iter().all(|&byte| byte != 0));
217
218 let vault = vault.create(password, &mut secret)?;
219
220 assert!(secret.iter().all(|&byte| byte == 0));
222
223 let (_key_type, fingerprint, password, data) = parse(&vault)?;
224
225 let view = Ed25519Vault::new(None, Some(private_key))?;
226
227 let vault = view.view(&password, &data, &fingerprint)?;
228
229 assert_eq!(vault, SECRET);
230 Ok(())
231 }
232
233 #[test]
234 fn test_vault() -> Result<()> {
235 let tests = [
236 Test {
237 public_key: "test_data/id_rsa.pub",
238 private_key: "test_data/id_rsa",
239 passphrase: "",
240 },
241 Test {
242 public_key: "test_data/ed25519.pub",
243 private_key: "test_data/ed25519",
244 passphrase: "",
245 },
246 Test {
247 public_key: "test_data/id_rsa_password.pub",
248 private_key: "test_data/id_rsa_password",
249 passphrase: "85990de849bb89120ea3016b6b76f6d004857cb7",
251 },
252 Test {
253 public_key: "test_data/ed25519_password.pub",
254 private_key: "test_data/ed25519_password",
255 passphrase: "85990de849bb89120ea3016b6b76f6d004857cb7",
257 },
258 ];
259
260 for test in &tests {
261 let public_key = test.public_key.to_string();
263 let public_key = find::public_key(Some(public_key))?;
264 let key_type = find::key_type(&public_key.algorithm())?;
265 let v = SshVault::new(&key_type, Some(public_key), None)?;
266 let password: SecretSlice<u8> = crypto::gen_password()?;
267
268 let mut secret = String::from(SECRET).into_bytes();
269
270 assert!(secret.iter().all(|&byte| byte != 0));
272
273 let vault = v.create(password, &mut secret)?;
274
275 assert!(secret.iter().all(|&byte| byte == 0));
277
278 let private_key = test.private_key.to_string();
280 let (key_type, fingerprint, password, data) = parse(&vault)?;
281 let mut private_key = find::private_key_type(Some(private_key), key_type)?;
282
283 if private_key.is_encrypted() {
284 private_key =
285 decrypt_private_key(&private_key, Some(SecretString::from(test.passphrase)))?;
286 }
287
288 let key_type = find::key_type(&private_key.algorithm())?;
289
290 let v = SshVault::new(&key_type, None, Some(private_key))?;
291
292 let vault = v.view(&password, &data, &fingerprint)?;
293
294 assert_eq!(vault, SECRET);
295 }
296 Ok(())
297 }
298}