ssh_vault/cli/actions/
create.rs1use crate::cli::actions::{Action, process_input};
2use crate::vault::{SshVault, crypto, dio, find, online, remote};
3use anyhow::{Result, anyhow};
4use secrecy::SecretSlice;
5use serde::{Deserialize, Serialize};
6use ssh_key::PublicKey;
7use std::io::{Read, Write};
8
9#[derive(Serialize, Deserialize)]
10pub struct JsonVault {
11 vault: String,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 private_key: Option<String>,
14}
15
16pub fn handle(action: Action) -> Result<()> {
18 match action {
19 Action::Create {
20 fingerprint,
21 key,
22 user,
23 vault,
24 json,
25 input,
26 } => {
27 let mut helper: Option<String> = None;
29
30 let ssh_key: PublicKey = if let Some(user) = user {
31 if user == "new" && (key.is_some() || fingerprint.is_some()) {
33 return Err(anyhow!("Options -k and -f not required when using -u new"));
34 }
35
36 let int_key: Option<u32> = key.as_ref().and_then(|s| s.parse::<u32>().ok());
37
38 let keys = remote::get_keys(&user)?;
40
41 let ssh_key = remote::get_user_key(&keys, int_key, &fingerprint)?;
43
44 if let Ok(key) = online::get_private_key_id(&ssh_key, &user)
46 && !key.is_empty()
47 {
48 helper = Some(key);
49 }
50
51 ssh_key
52 } else {
53 find::public_key(key)?
54 };
55
56 let key_type = find::key_type(&ssh_key.algorithm())?;
57
58 let v = SshVault::new(&key_type, Some(ssh_key), None)?;
59
60 let mut buffer = Vec::new();
61
62 let skip_editor = input.as_ref().is_some_and(|stdin| stdin == "-");
64
65 let (mut input, output) = dio::setup_io(input, vault)?;
67
68 if !output.is_empty()? {
69 return Err(anyhow!("Vault file already exists"));
70 }
71
72 if input.is_terminal() {
73 if skip_editor {
74 input.read_to_end(&mut buffer)?;
75 } else {
76 process_input(&mut buffer, None)?;
78 }
79 } else {
80 input.read_to_end(&mut buffer)?;
82 }
83
84 let password: SecretSlice<u8> = crypto::gen_password()?;
86
87 let vault = v.create(password, &mut buffer)?;
89
90 format(output, vault, json, helper)?;
92 }
93 _ => unreachable!(),
94 }
95 Ok(())
96}
97
98fn format<W: Write>(
99 mut output: W,
100 vault: String,
101 json: bool,
102 helper: Option<String>,
103) -> Result<()> {
104 if json {
106 let json_vault = JsonVault {
107 vault,
108 private_key: helper,
109 };
110
111 let json = serde_json::to_string(&json_vault)?;
112
113 output.write_all(json.as_bytes())?;
114 } else if let Some(helper) = helper {
115 let format = format!("echo \"{vault}\" | ssh-vault view -k {helper}");
116 output.write_all(format.as_bytes())?;
117 } else {
118 output.write_all(vault.as_bytes())?;
119 }
120
121 Ok(())
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_format() {
130 let mut output = Vec::new();
131 let vault = "vault".to_string();
132 let json = false;
133 let helper = None;
134
135 format(&mut output, vault, json, helper).unwrap();
136
137 assert_eq!(output, b"vault");
138 }
139
140 #[test]
141 fn test_format_helper() {
142 let mut output = Vec::new();
143 let vault = "vault".to_string();
144 let json = false;
145 let helper = Some("helper".to_string());
146
147 format(&mut output, vault, json, helper).unwrap();
148
149 assert_eq!(output, b"echo \"vault\" | ssh-vault view -k helper");
150 }
151
152 #[test]
153 fn test_format_json() {
154 let mut output = Vec::new();
155 let vault = "vault".to_string();
156 let json = true;
157 let helper = None;
158
159 format(&mut output, vault, json, helper).unwrap();
160
161 assert_eq!(output, b"{\"vault\":\"vault\"}");
162 }
163
164 #[test]
165 fn test_format_helper_json() {
166 let mut output = Vec::new();
167 let vault = "vault".to_string();
168 let json = true;
169 let helper = Some("helper".to_string());
170
171 format(&mut output, vault, json, helper).unwrap();
172
173 assert_eq!(output, b"{\"vault\":\"vault\",\"private_key\":\"helper\"}");
174 }
175}