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> {
96 self.vault.create(password, data)
97 }
98
99 pub fn view(&self, password: &[u8], data: &[u8], fingerprint: &str) -> Result<String> {
118 self.vault.view(password, data, fingerprint)
119 }
120}
121
122pub trait Vault {
124 fn new(public: Option<PublicKey>, private: Option<PrivateKey>) -> Result<Self>
126 where
127 Self: Sized;
128
129 fn create(&self, password: SecretSlice<u8>, data: &mut [u8]) -> Result<String>;
131
132 fn view(&self, password: &[u8], data: &[u8], fingerprint: &str) -> Result<String>;
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::vault::{
140 Vault, crypto, parse, ssh::decrypt_private_key, ssh::ed25519::Ed25519Vault,
141 ssh::rsa::RsaVault,
142 };
143 use secrecy::{SecretSlice, SecretString};
144 use ssh_key::PublicKey;
145 use std::path::Path;
146
147 struct Test {
148 public_key: &'static str,
149 private_key: &'static str,
150 passphrase: &'static str,
151 }
152
153 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";
154
155 #[test]
156 fn test_rsa_vault() -> Result<()> {
157 let public_key_file = Path::new("test_data/id_rsa.pub");
158 let private_key_file = Path::new("test_data/id_rsa");
159 let public_key = PublicKey::read_openssh_file(&public_key_file)?;
160 let private_key = PrivateKey::read_openssh_file(&private_key_file)?;
161
162 let vault = RsaVault::new(Some(public_key), None)?;
163
164 let password: SecretSlice<u8> = crypto::gen_password()?;
165
166 let mut secret = String::from(SECRET).into_bytes();
167
168 assert!(secret.iter().all(|&byte| byte != 0));
170
171 let vault = vault.create(password, &mut secret)?;
172
173 assert!(secret.iter().all(|&byte| byte == 0));
175
176 let (_key_type, fingerprint, password, data) = parse(&vault)?;
177
178 let view = RsaVault::new(None, Some(private_key))?;
179
180 let vault = view.view(&password, &data, &fingerprint)?;
181
182 assert_eq!(vault, SECRET);
183 Ok(())
184 }
185
186 #[test]
187 fn test_ed25519_vault() -> Result<()> {
188 let public_key_file = Path::new("test_data/ed25519.pub");
189 let private_key_file = Path::new("test_data/ed25519");
190 let public_key = PublicKey::read_openssh_file(&public_key_file)?;
191 let private_key = PrivateKey::read_openssh_file(&private_key_file)?;
192
193 let vault = Ed25519Vault::new(Some(public_key), None)?;
194
195 let password: SecretSlice<u8> = crypto::gen_password()?;
196
197 let mut secret = String::from(SECRET).into_bytes();
198
199 assert!(secret.iter().all(|&byte| byte != 0));
201
202 let vault = vault.create(password, &mut secret)?;
203
204 assert!(secret.iter().all(|&byte| byte == 0));
206
207 let (_key_type, fingerprint, password, data) = parse(&vault)?;
208
209 let view = Ed25519Vault::new(None, Some(private_key))?;
210
211 let vault = view.view(&password, &data, &fingerprint)?;
212
213 assert_eq!(vault, SECRET);
214 Ok(())
215 }
216
217 #[test]
218 fn test_vault() -> Result<()> {
219 let tests = [
220 Test {
221 public_key: "test_data/id_rsa.pub",
222 private_key: "test_data/id_rsa",
223 passphrase: "",
224 },
225 Test {
226 public_key: "test_data/ed25519.pub",
227 private_key: "test_data/ed25519",
228 passphrase: "",
229 },
230 Test {
231 public_key: "test_data/id_rsa_password.pub",
232 private_key: "test_data/id_rsa_password",
233 passphrase: "85990de849bb89120ea3016b6b76f6d004857cb7",
235 },
236 Test {
237 public_key: "test_data/ed25519_password.pub",
238 private_key: "test_data/ed25519_password",
239 passphrase: "85990de849bb89120ea3016b6b76f6d004857cb7",
241 },
242 ];
243
244 for test in tests.iter() {
245 let public_key = test.public_key.to_string();
247 let public_key = find::public_key(Some(public_key))?;
248 let key_type = find::key_type(&public_key.algorithm())?;
249 let v = SshVault::new(&key_type, Some(public_key), None)?;
250 let password: SecretSlice<u8> = crypto::gen_password()?;
251
252 let mut secret = String::from(SECRET).into_bytes();
253
254 assert!(secret.iter().all(|&byte| byte != 0));
256
257 let vault = v.create(password, &mut secret)?;
258
259 assert!(secret.iter().all(|&byte| byte == 0));
261
262 let private_key = test.private_key.to_string();
264 let (key_type, fingerprint, password, data) = parse(&vault)?;
265 let mut private_key = find::private_key_type(Some(private_key), key_type)?;
266
267 if private_key.is_encrypted() {
268 private_key =
269 decrypt_private_key(&private_key, Some(SecretString::from(test.passphrase)))?;
270 }
271
272 let key_type = find::key_type(&private_key.algorithm())?;
273
274 let v = SshVault::new(&key_type, None, Some(private_key))?;
275
276 let vault = v.view(&password, &data, &fingerprint)?;
277
278 assert_eq!(vault, SECRET);
279 }
280 Ok(())
281 }
282}