ssh_vault/vault/ssh/
rsa.rs1use crate::vault::{
2 Vault, crypto::Crypto, crypto::aes256::Aes256Crypto, fingerprint::md5_fingerprint,
3};
4use anyhow::{Context, Result};
5use base64ct::{Base64, Encoding};
6use rsa::{BigUint, Oaep, RsaPrivateKey, RsaPublicKey, rand_core::OsRng, sha2::Sha256};
7use secrecy::{ExposeSecret, SecretSlice};
8use ssh_key::{PrivateKey, PublicKey, private::KeypairData, public::KeyData};
9use zeroize::Zeroize;
10
11#[derive(Debug)]
12pub struct RsaVault {
13 public_key: RsaPublicKey,
14 private_key: Option<RsaPrivateKey>,
15}
16
17impl Vault for RsaVault {
18 fn new(public: Option<PublicKey>, private: Option<PrivateKey>) -> Result<Self> {
19 match (public, private) {
20 (Some(public), None) => match public.key_data() {
21 KeyData::Rsa(key_data) => {
22 let public_key =
23 RsaPublicKey::try_from(key_data).context("Could not load key")?;
24
25 Ok(Self {
26 public_key,
27 private_key: None,
28 })
29 }
30 _ => Err(anyhow::anyhow!("Invalid key type for RsaVault")),
31 },
32
33 (None, Some(private)) => match private.key_data() {
34 KeypairData::Rsa(rsa_keypair) => {
35 if private.is_encrypted() {
36 return Err(anyhow::anyhow!("Private key is encrypted"));
37 }
38
39 let modulus = BigUint::from_bytes_be(rsa_keypair.public.n.as_ref());
51 let public_exponent = BigUint::from_bytes_be(rsa_keypair.public.e.as_ref());
52 let private_exponent = BigUint::from_bytes_be(rsa_keypair.private.d.as_ref());
53 let prime_p = BigUint::from_bytes_be(rsa_keypair.private.p.as_ref());
54 let prime_q = BigUint::from_bytes_be(rsa_keypair.private.q.as_ref());
55
56 let private_key = RsaPrivateKey::from_components(
71 modulus,
72 public_exponent,
73 private_exponent,
74 vec![prime_p, prime_q],
75 )?;
76
77 let public_key = private_key.to_public_key();
80
81 Ok(Self {
82 public_key,
83 private_key: Some(private_key),
84 })
85 }
86 _ => Err(anyhow::anyhow!("Invalid key type for RsaVault")),
87 },
88
89 (Some(_), Some(_)) => Err(anyhow::anyhow!(
90 "Only one of public and private key is required"
91 )),
92
93 _ => Err(anyhow::anyhow!("Missing public and private key")),
94 }
95 }
96
97 fn create(&self, password: SecretSlice<u8>, data: &mut [u8]) -> Result<String> {
98 let crypto = Aes256Crypto::new(password.clone());
99
100 let fingerprint = md5_fingerprint(&self.public_key)?;
101
102 let encrypted_data = crypto.encrypt(data, fingerprint.as_bytes())?;
103
104 data.zeroize();
106
107 let encrypted_password =
111 self.public_key
112 .encrypt(&mut OsRng, Oaep::new::<Sha256>(), password.expose_secret())?;
113
114 let payload = format!(
116 "{};{}",
117 Base64::encode_string(&encrypted_password),
118 Base64::encode_string(&encrypted_data)
119 )
120 .chars()
121 .collect::<Vec<_>>()
122 .chunks(64)
123 .map(|chunk| chunk.iter().collect::<String>())
124 .collect::<Vec<_>>()
125 .join("\n");
126
127 Ok(format!("SSH-VAULT;AES256;{fingerprint}\n{payload}"))
128 }
129
130 fn view(&self, password: &[u8], data: &[u8], fingerprint: &str) -> Result<String> {
131 let get_fingerprint = md5_fingerprint(&self.public_key)?;
132
133 if get_fingerprint != fingerprint {
134 return Err(anyhow::anyhow!("Fingerprint mismatch, use correct key"));
135 }
136
137 match &self.private_key {
138 Some(private_key) => {
139 let password: SecretSlice<u8> =
140 SecretSlice::new(private_key.decrypt(Oaep::new::<Sha256>(), password)?.into());
141
142 let crypto = Aes256Crypto::new(password);
143
144 let out = crypto.decrypt(data, fingerprint.as_bytes())?;
145 Ok(String::from_utf8(out)?)
146 }
147 None => Err(anyhow::anyhow!("Private key is required to view vault")),
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::vault::Vault;
156 use anyhow::Result;
157 use ssh_key::{PrivateKey, PublicKey};
158 use std::path::Path;
159
160 #[test]
161 fn test_rsa_vault_using_both_keys() -> Result<()> {
162 let public_key_file = Path::new("test_data/id_rsa.pub");
163 let private_key_file = Path::new("test_data/id_rsa");
164 let public_key = PublicKey::read_openssh_file(public_key_file)?;
165 let private_key = PrivateKey::read_openssh_file(private_key_file)?;
166 let vault = RsaVault::new(Some(public_key), Some(private_key));
167 assert!(vault.is_err());
168
169 let Err(err) = vault else {
170 unreachable!("expected error when both keys provided")
171 };
172
173 assert_eq!(
175 err.to_string(),
176 "Only one of public and private key is required"
177 );
178
179 Ok(())
180 }
181
182 #[test]
183 fn test_rsa_vault_using_public_key() -> Result<()> {
184 let public_key_file = Path::new("test_data/id_rsa.pub");
185 let public_key = PublicKey::read_openssh_file(public_key_file)?;
186 let vault = RsaVault::new(Some(public_key), None);
187 assert!(vault.is_ok());
188 Ok(())
189 }
190
191 #[test]
192 fn test_rsa_vault_using_private_key() -> Result<()> {
193 let private_key_file = Path::new("test_data/id_rsa");
194 let private_key = PrivateKey::read_openssh_file(private_key_file)?;
195 let vault = RsaVault::new(None, Some(private_key));
196 assert!(vault.is_ok());
197 Ok(())
198 }
199}