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
13pub 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
26pub 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
39pub 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
63pub 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 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 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}