Skip to main content

ssh_vault/vault/
find.rs

1use crate::{
2    tools,
3    vault::{SshKeyType, remote},
4};
5use anyhow::{Context, Result, anyhow};
6use ssh_key::{Algorithm, PrivateKey, PublicKey};
7use std::{
8    fs::File,
9    io::Read,
10    path::{Path, PathBuf},
11};
12
13/// Find key type RSA or ED25519.
14///
15/// # Errors
16///
17/// Returns an error if the algorithm is unsupported.
18pub fn key_type(key: &Algorithm) -> Result<SshKeyType> {
19    match key {
20        Algorithm::Rsa { .. } => Ok(SshKeyType::Rsa),
21        Algorithm::Ed25519 => Ok(SshKeyType::Ed25519),
22        _ => Err(anyhow::anyhow!("Unsupported ssh key type")),
23    }
24}
25
26/// Find private key type RSA or ED25519 based on vault header.
27///
28/// # Errors
29///
30/// Returns an error if the key type is not supported.
31pub fn private_key_type(key: Option<String>, key_type: &str) -> Result<PrivateKey> {
32    match key_type {
33        "AES256" => private_key(key, &SshKeyType::Rsa),
34        "CHACHA20-POLY1305" => private_key(key, &SshKeyType::Ed25519),
35        _ => Err(anyhow!("Unsupported key type")),
36    }
37}
38
39/// Load a public key from disk.
40///
41/// # Errors
42///
43/// Returns an error if no key is found or the key cannot be parsed.
44pub fn public_key(key: Option<String>) -> Result<PublicKey> {
45    let key: PathBuf = if let Some(key) = key {
46        Path::new(&key).to_path_buf()
47    } else {
48        let home = tools::get_home()?;
49        let rsa_pub_key = home.join(".ssh").join("id_rsa.pub");
50        let ed25519_pub_key = home.join(".ssh").join("id_ed25519.pub");
51        if rsa_pub_key.exists() {
52            rsa_pub_key
53        } else if ed25519_pub_key.exists() {
54            ed25519_pub_key
55        } else {
56            return Err(anyhow::anyhow!("No key found"));
57        }
58    };
59
60    PublicKey::read_openssh_file(&key).context("Ensure you are passing a valid openssh public key")
61}
62
63/// Load a private key from disk or URL.
64///
65/// # Errors
66///
67/// Returns an error if the key is missing, cannot be read, or is in an
68/// unsupported format.
69pub fn private_key(key: Option<String>, ssh_type: &SshKeyType) -> Result<PrivateKey> {
70    let private_key = if let Some(key) = key {
71        if key.starts_with("http://") || key.starts_with("https://") {
72            remote::request(&key, true)?
73        } else {
74            let mut buffer = String::new();
75            File::open(&key)?.read_to_string(&mut buffer)?;
76            buffer
77        }
78    } else {
79        let home = tools::get_home()?;
80        let key_path = match ssh_type {
81            SshKeyType::Rsa => home.join(".ssh").join("id_rsa"),
82            SshKeyType::Ed25519 => home.join(".ssh").join("id_ed25519"),
83        };
84        if key_path.exists() {
85            let mut private_key = String::new();
86            File::open(key_path)?.read_to_string(&mut private_key)?;
87            private_key
88        } else {
89            return Err(anyhow!(
90                "No private key found in {}",
91                home.join(".ssh").display()
92            ));
93        }
94    };
95
96    let private_key = private_key.trim();
97
98    // check if it's a legacy rsa key
99    if private_key.starts_with("-----BEGIN RSA PRIVATE KEY-----") {
100        return Err(anyhow!(
101            "Legacy RSA key not supported, use ssh-keygen -p -f <key> to convert it to openssh format"
102        ));
103    }
104
105    // read openssh key and return it as a PrivateKey
106    PrivateKey::from_openssh(private_key)
107        .context("Ensure you are passing a valid openssh private key")
108}
109
110#[cfg(test)]
111#[allow(clippy::unwrap_used)]
112mod tests {
113    use super::*;
114    use crate::vault::SshKeyType;
115    use ssh_key::Algorithm;
116
117    #[test]
118    fn test_key_type() {
119        assert_eq!(
120            key_type(&Algorithm::Rsa { hash: None }).unwrap(),
121            SshKeyType::Rsa
122        );
123        assert_eq!(key_type(&Algorithm::Ed25519).unwrap(), SshKeyType::Ed25519);
124        assert!(key_type(&Algorithm::Dsa).is_err());
125    }
126
127    #[test]
128    fn test_private_key_type() {
129        assert!(private_key_type(Some("test_data/id_rsa".to_string()), "AES256").is_ok());
130        assert!(private_key_type(Some("test_data/id_rsa".to_string()), "RSA").is_err());
131        assert!(
132            private_key_type(Some("test_data/ed25519".to_string()), "CHACHA20-POLY1305",).is_ok()
133        );
134        assert!(private_key_type(Some("test_data/ed25519".to_string()), "AES256").is_ok());
135        assert_eq!(
136            private_key_type(Some("test_data/ed25519".to_string()), "AES256")
137                .unwrap()
138                .algorithm(),
139            Algorithm::Ed25519
140        );
141        assert_eq!(
142            private_key_type(Some("test_data/id_rsa".to_string()), "CHACHA20-POLY1305",)
143                .unwrap()
144                .algorithm(),
145            Algorithm::Rsa { hash: None }
146        );
147    }
148
149    #[test]
150    fn test_public_key() {
151        assert!(public_key(Some("test_data/id_rsa.pub".to_string())).is_ok());
152        assert!(public_key(Some("test_data/ed25519.pub".to_string())).is_ok());
153    }
154
155    #[test]
156    fn test_private_key() {
157        assert!(private_key(Some("test_data/id_rsa".to_string()), &SshKeyType::Rsa).is_ok());
158        assert!(private_key(Some("test_data/ed25519".to_string()), &SshKeyType::Ed25519).is_ok());
159    }
160}