]>
Commit | Line | Data |
---|---|---|
42c0f784 | 1 | use std::convert::TryFrom; |
2924b37d | 2 | use std::path::PathBuf; |
9696f519 | 3 | |
05389a01 | 4 | use anyhow::{bail, format_err, Error}; |
dfb04575 | 5 | use serde_json::Value; |
9696f519 WB |
6 | |
7 | use proxmox::api::api; | |
dfb04575 | 8 | use proxmox::api::cli::{ |
2924b37d | 9 | format_and_print_result_full, get_output_format, CliCommand, CliCommandMap, ColumnConfig, |
dfb04575 FG |
10 | OUTPUT_FORMAT, |
11 | }; | |
b2362a12 | 12 | use proxmox::api::router::ReturnType; |
9696f519 WB |
13 | use proxmox::sys::linux::tty; |
14 | use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; | |
15 | ||
82a103c8 | 16 | use proxmox_backup::{ |
2924b37d FG |
17 | api2::types::{Kdf, KeyInfo, RsaPubKeyInfo, PASSWORD_HINT_SCHEMA}, |
18 | backup::{rsa_decrypt_key_config, KeyConfig}, | |
82a103c8 | 19 | tools, |
2924b37d | 20 | tools::paperkey::{generate_paper_key, PaperkeyFormat}, |
9696f519 | 21 | }; |
7137630d | 22 | |
2f26b866 FG |
23 | use crate::KeyWithSource; |
24 | ||
b65390eb | 25 | pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME: &str = "encryption-key.json"; |
05f17d1e | 26 | pub const DEFAULT_MASTER_PUBKEY_FILE_NAME: &str = "master-public.pem"; |
b65390eb | 27 | |
05f17d1e | 28 | pub fn find_default_master_pubkey() -> Result<Option<PathBuf>, Error> { |
2924b37d FG |
29 | super::find_xdg_file( |
30 | DEFAULT_MASTER_PUBKEY_FILE_NAME, | |
31 | "default master public key file", | |
32 | ) | |
05389a01 | 33 | } |
9696f519 | 34 | |
05f17d1e | 35 | pub fn place_default_master_pubkey() -> Result<PathBuf, Error> { |
2924b37d FG |
36 | super::place_xdg_file( |
37 | DEFAULT_MASTER_PUBKEY_FILE_NAME, | |
38 | "default master public key file", | |
39 | ) | |
9696f519 WB |
40 | } |
41 | ||
b65390eb | 42 | pub fn find_default_encryption_key() -> Result<Option<PathBuf>, Error> { |
2924b37d FG |
43 | super::find_xdg_file( |
44 | DEFAULT_ENCRYPTION_KEY_FILE_NAME, | |
45 | "default encryption key file", | |
46 | ) | |
b65390eb | 47 | } |
9696f519 | 48 | |
b65390eb | 49 | pub fn place_default_encryption_key() -> Result<PathBuf, Error> { |
2924b37d FG |
50 | super::place_xdg_file( |
51 | DEFAULT_ENCRYPTION_KEY_FILE_NAME, | |
52 | "default encryption key file", | |
53 | ) | |
9696f519 WB |
54 | } |
55 | ||
5bb057e5 | 56 | #[cfg(not(test))] |
2f26b866 | 57 | pub(crate) fn read_optional_default_encryption_key() -> Result<Option<KeyWithSource>, Error> { |
0351f23b | 58 | find_default_encryption_key()? |
2f26b866 | 59 | .map(|path| file_get_contents(path).map(KeyWithSource::from_default)) |
0351f23b WB |
60 | .transpose() |
61 | } | |
62 | ||
c0a87c12 | 63 | #[cfg(not(test))] |
2f26b866 | 64 | pub(crate) fn read_optional_default_master_pubkey() -> Result<Option<KeyWithSource>, Error> { |
c0a87c12 | 65 | find_default_master_pubkey()? |
2f26b866 | 66 | .map(|path| file_get_contents(path).map(KeyWithSource::from_default)) |
c0a87c12 FG |
67 | .transpose() |
68 | } | |
69 | ||
5bb057e5 FG |
70 | #[cfg(test)] |
71 | static mut TEST_DEFAULT_ENCRYPTION_KEY: Result<Option<Vec<u8>>, Error> = Ok(None); | |
72 | ||
73 | #[cfg(test)] | |
2f26b866 | 74 | pub(crate) fn read_optional_default_encryption_key() -> Result<Option<KeyWithSource>, Error> { |
5bb057e5 FG |
75 | // not safe when multiple concurrent test cases end up here! |
76 | unsafe { | |
77 | match &TEST_DEFAULT_ENCRYPTION_KEY { | |
2f26b866 FG |
78 | Ok(Some(key)) => Ok(Some(KeyWithSource::from_default(key.clone()))), |
79 | Ok(None) => Ok(None), | |
5bb057e5 FG |
80 | Err(_) => bail!("test error"), |
81 | } | |
82 | } | |
83 | } | |
84 | ||
85 | #[cfg(test)] | |
86 | // not safe when multiple concurrent test cases end up here! | |
2f26b866 | 87 | pub(crate) unsafe fn set_test_encryption_key(value: Result<Option<Vec<u8>>, Error>) { |
5bb057e5 FG |
88 | TEST_DEFAULT_ENCRYPTION_KEY = value; |
89 | } | |
90 | ||
c0a87c12 FG |
91 | #[cfg(test)] |
92 | static mut TEST_DEFAULT_MASTER_PUBKEY: Result<Option<Vec<u8>>, Error> = Ok(None); | |
93 | ||
94 | #[cfg(test)] | |
2f26b866 | 95 | pub(crate) fn read_optional_default_master_pubkey() -> Result<Option<KeyWithSource>, Error> { |
c0a87c12 FG |
96 | // not safe when multiple concurrent test cases end up here! |
97 | unsafe { | |
98 | match &TEST_DEFAULT_MASTER_PUBKEY { | |
2f26b866 FG |
99 | Ok(Some(key)) => Ok(Some(KeyWithSource::from_default(key.clone()))), |
100 | Ok(None) => Ok(None), | |
c0a87c12 FG |
101 | Err(_) => bail!("test error"), |
102 | } | |
103 | } | |
104 | } | |
105 | ||
106 | #[cfg(test)] | |
107 | // not safe when multiple concurrent test cases end up here! | |
2f26b866 | 108 | pub(crate) unsafe fn set_test_default_master_pubkey(value: Result<Option<Vec<u8>>, Error>) { |
c0a87c12 FG |
109 | TEST_DEFAULT_MASTER_PUBKEY = value; |
110 | } | |
111 | ||
9696f519 WB |
112 | pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> { |
113 | // fixme: implement other input methods | |
114 | ||
115 | use std::env::VarError::*; | |
116 | match std::env::var("PBS_ENCRYPTION_PASSWORD") { | |
117 | Ok(p) => return Ok(p.as_bytes().to_vec()), | |
118 | Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"), | |
119 | Err(NotPresent) => { | |
120 | // Try another method | |
121 | } | |
122 | } | |
123 | ||
124 | // If we're on a TTY, query the user for a password | |
125 | if tty::stdin_isatty() { | |
126 | return Ok(tty::read_password("Encryption Key Password: ")?); | |
127 | } | |
128 | ||
129 | bail!("no password input mechanism available"); | |
130 | } | |
131 | ||
9696f519 WB |
132 | #[api( |
133 | input: { | |
134 | properties: { | |
135 | kdf: { | |
136 | type: Kdf, | |
137 | optional: true, | |
138 | }, | |
139 | path: { | |
140 | description: | |
141 | "Output file. Without this the key will become the new default encryption key.", | |
142 | optional: true, | |
82a103c8 DM |
143 | }, |
144 | hint: { | |
145 | schema: PASSWORD_HINT_SCHEMA, | |
146 | optional: true, | |
147 | }, | |
9696f519 WB |
148 | }, |
149 | }, | |
150 | )] | |
151 | /// Create a new encryption key. | |
2924b37d | 152 | fn create(kdf: Option<Kdf>, path: Option<String>, hint: Option<String>) -> Result<(), Error> { |
9696f519 WB |
153 | let path = match path { |
154 | Some(path) => PathBuf::from(path), | |
0eaef8eb WB |
155 | None => { |
156 | let path = place_default_encryption_key()?; | |
157 | println!("creating default key at: {:?}", path); | |
158 | path | |
159 | } | |
9696f519 WB |
160 | }; |
161 | ||
162 | let kdf = kdf.unwrap_or_default(); | |
163 | ||
9a045790 DM |
164 | let mut key = [0u8; 32]; |
165 | proxmox::sys::linux::fill_with_random_data(&mut key)?; | |
9696f519 WB |
166 | |
167 | match kdf { | |
168 | Kdf::None => { | |
82a103c8 DM |
169 | if hint.is_some() { |
170 | bail!("password hint not allowed for Kdf::None"); | |
171 | } | |
172 | ||
1c86893d | 173 | let key_config = KeyConfig::without_password(key)?; |
9a045790 DM |
174 | |
175 | key_config.store(path, false)?; | |
9696f519 | 176 | } |
5e17dbf2 | 177 | Kdf::Scrypt | Kdf::PBKDF2 => { |
9696f519 WB |
178 | // always read passphrase from tty |
179 | if !tty::stdin_isatty() { | |
180 | bail!("unable to read passphrase - no tty"); | |
181 | } | |
182 | ||
183 | let password = tty::read_and_verify_password("Encryption Key Password: ")?; | |
184 | ||
9a045790 | 185 | let mut key_config = KeyConfig::with_key(&key, &password, kdf)?; |
82a103c8 | 186 | key_config.hint = hint; |
9696f519 | 187 | |
9a045790 | 188 | key_config.store(&path, false)?; |
9696f519 WB |
189 | } |
190 | } | |
191 | ||
192 | Ok(()) | |
193 | } | |
194 | ||
7137630d FG |
195 | #[api( |
196 | input: { | |
197 | properties: { | |
198 | "master-keyfile": { | |
199 | description: "(Private) master key to use.", | |
200 | }, | |
201 | "encrypted-keyfile": { | |
202 | description: "RSA-encrypted keyfile to import.", | |
203 | }, | |
204 | kdf: { | |
205 | type: Kdf, | |
206 | optional: true, | |
207 | }, | |
208 | "path": { | |
209 | description: | |
210 | "Output file. Without this the key will become the new default encryption key.", | |
211 | optional: true, | |
82a103c8 DM |
212 | }, |
213 | hint: { | |
214 | schema: PASSWORD_HINT_SCHEMA, | |
215 | optional: true, | |
216 | }, | |
7137630d FG |
217 | }, |
218 | }, | |
219 | )] | |
220 | /// Import an encrypted backup of an encryption key using a (private) master key. | |
221 | async fn import_with_master_key( | |
222 | master_keyfile: String, | |
223 | encrypted_keyfile: String, | |
224 | kdf: Option<Kdf>, | |
225 | path: Option<String>, | |
82a103c8 | 226 | hint: Option<String>, |
7137630d FG |
227 | ) -> Result<(), Error> { |
228 | let path = match path { | |
229 | Some(path) => PathBuf::from(path), | |
230 | None => { | |
231 | let path = place_default_encryption_key()?; | |
232 | if path.exists() { | |
233 | bail!("Please remove default encryption key at {:?} before importing to default location (or choose a non-default one).", path); | |
234 | } | |
235 | println!("Importing key to default location at: {:?}", path); | |
236 | path | |
237 | } | |
238 | }; | |
239 | ||
240 | let encrypted_key = file_get_contents(&encrypted_keyfile)?; | |
241 | let master_key = file_get_contents(&master_keyfile)?; | |
242 | let password = tty::read_password("Master Key Password: ")?; | |
243 | ||
2924b37d | 244 | let master_key = openssl::pkey::PKey::private_key_from_pem_passphrase(&master_key, &password) |
7137630d FG |
245 | .map_err(|err| format_err!("failed to read PEM-formatted private key - {}", err))? |
246 | .rsa() | |
247 | .map_err(|err| format_err!("not a valid private RSA key - {}", err))?; | |
248 | ||
1c86893d | 249 | let (key, created, _fingerprint) = |
7137630d FG |
250 | rsa_decrypt_key_config(master_key, &encrypted_key, &get_encryption_key_password)?; |
251 | ||
252 | let kdf = kdf.unwrap_or_default(); | |
253 | match kdf { | |
254 | Kdf::None => { | |
82a103c8 DM |
255 | if hint.is_some() { |
256 | bail!("password hint not allowed for Kdf::None"); | |
257 | } | |
258 | ||
1c86893d | 259 | let mut key_config = KeyConfig::without_password(key)?; |
9a045790 | 260 | key_config.created = created; // keep original value |
9a045790 DM |
261 | |
262 | key_config.store(path, true)?; | |
7137630d FG |
263 | } |
264 | Kdf::Scrypt | Kdf::PBKDF2 => { | |
265 | let password = tty::read_and_verify_password("New Password: ")?; | |
266 | ||
9a045790 | 267 | let mut new_key_config = KeyConfig::with_key(&key, &password, kdf)?; |
7137630d | 268 | new_key_config.created = created; // keep original value |
82a103c8 | 269 | new_key_config.hint = hint; |
7137630d | 270 | |
9a045790 | 271 | new_key_config.store(path, true)?; |
7137630d FG |
272 | } |
273 | } | |
274 | ||
275 | Ok(()) | |
276 | } | |
277 | ||
9696f519 WB |
278 | #[api( |
279 | input: { | |
280 | properties: { | |
281 | kdf: { | |
282 | type: Kdf, | |
283 | optional: true, | |
284 | }, | |
285 | path: { | |
286 | description: "Key file. Without this the default key's password will be changed.", | |
287 | optional: true, | |
82a103c8 DM |
288 | }, |
289 | hint: { | |
290 | schema: PASSWORD_HINT_SCHEMA, | |
291 | optional: true, | |
292 | }, | |
9696f519 WB |
293 | }, |
294 | }, | |
295 | )] | |
296 | /// Change the encryption key's password. | |
82a103c8 DM |
297 | fn change_passphrase( |
298 | kdf: Option<Kdf>, | |
299 | path: Option<String>, | |
300 | hint: Option<String>, | |
301 | ) -> Result<(), Error> { | |
9696f519 WB |
302 | let path = match path { |
303 | Some(path) => PathBuf::from(path), | |
0eaef8eb | 304 | None => { |
2924b37d FG |
305 | let path = find_default_encryption_key()?.ok_or_else(|| { |
306 | format_err!("no encryption file provided and no default file found") | |
307 | })?; | |
0eaef8eb WB |
308 | println!("updating default key at: {:?}", path); |
309 | path | |
310 | } | |
9696f519 WB |
311 | }; |
312 | ||
313 | let kdf = kdf.unwrap_or_default(); | |
314 | ||
315 | if !tty::stdin_isatty() { | |
316 | bail!("unable to change passphrase - no tty"); | |
317 | } | |
318 | ||
9a045790 | 319 | let key_config = KeyConfig::load(&path)?; |
1c86893d | 320 | let (key, created, _fingerprint) = key_config.decrypt(&get_encryption_key_password)?; |
9696f519 WB |
321 | |
322 | match kdf { | |
323 | Kdf::None => { | |
82a103c8 DM |
324 | if hint.is_some() { |
325 | bail!("password hint not allowed for Kdf::None"); | |
326 | } | |
327 | ||
1c86893d | 328 | let mut key_config = KeyConfig::without_password(key)?; |
2924b37d | 329 | key_config.created = created; // keep original value |
9a045790 DM |
330 | |
331 | key_config.store(&path, true)?; | |
9696f519 | 332 | } |
5e17dbf2 | 333 | Kdf::Scrypt | Kdf::PBKDF2 => { |
9696f519 WB |
334 | let password = tty::read_and_verify_password("New Password: ")?; |
335 | ||
9a045790 | 336 | let mut new_key_config = KeyConfig::with_key(&key, &password, kdf)?; |
9696f519 | 337 | new_key_config.created = created; // keep original value |
82a103c8 | 338 | new_key_config.hint = hint; |
9a045790 DM |
339 | |
340 | new_key_config.store(&path, true)?; | |
9696f519 WB |
341 | } |
342 | } | |
343 | ||
344 | Ok(()) | |
345 | } | |
346 | ||
dfb04575 FG |
347 | #[api( |
348 | input: { | |
349 | properties: { | |
350 | path: { | |
351 | description: "Key file. Without this the default key's metadata will be shown.", | |
352 | optional: true, | |
353 | }, | |
354 | "output-format": { | |
355 | schema: OUTPUT_FORMAT, | |
356 | optional: true, | |
357 | }, | |
358 | }, | |
359 | }, | |
360 | )] | |
361 | /// Print the encryption key's metadata. | |
4d104cd4 | 362 | fn show_key(path: Option<String>, param: Value) -> Result<(), Error> { |
dfb04575 FG |
363 | let path = match path { |
364 | Some(path) => PathBuf::from(path), | |
4d104cd4 FG |
365 | None => find_default_encryption_key()? |
366 | .ok_or_else(|| format_err!("no encryption file provided and no default file found"))?, | |
dfb04575 FG |
367 | }; |
368 | ||
dfb04575 FG |
369 | let config: KeyConfig = serde_json::from_slice(&file_get_contents(path.clone())?)?; |
370 | ||
5e17dbf2 DM |
371 | let output_format = get_output_format(¶m); |
372 | ||
69b8bc3b DM |
373 | let mut info: KeyInfo = (&config).into(); |
374 | info.path = Some(format!("{:?}", path)); | |
5e17dbf2 DM |
375 | |
376 | let options = proxmox::api::cli::default_table_format_options() | |
377 | .column(ColumnConfig::new("path")) | |
378 | .column(ColumnConfig::new("kdf")) | |
379 | .column(ColumnConfig::new("created").renderer(tools::format::render_epoch)) | |
380 | .column(ColumnConfig::new("modified").renderer(tools::format::render_epoch)) | |
82a103c8 DM |
381 | .column(ColumnConfig::new("fingerprint")) |
382 | .column(ColumnConfig::new("hint")); | |
5e17dbf2 | 383 | |
b2362a12 | 384 | let return_type = ReturnType::new(false, &KeyInfo::API_SCHEMA); |
5e17dbf2 | 385 | |
b2362a12 WB |
386 | format_and_print_result_full( |
387 | &mut serde_json::to_value(info)?, | |
388 | &return_type, | |
389 | &output_format, | |
390 | &options, | |
391 | ); | |
dfb04575 FG |
392 | |
393 | Ok(()) | |
394 | } | |
395 | ||
9696f519 WB |
396 | #[api( |
397 | input: { | |
398 | properties: { | |
399 | path: { | |
400 | description: "Path to the PEM formatted RSA public key.", | |
401 | }, | |
402 | }, | |
403 | }, | |
404 | )] | |
405 | /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption | |
406 | /// key onto the backup server along with each backup. | |
05f17d1e FG |
407 | /// |
408 | /// The imported key will be used as default master key for future invocations by the same local | |
409 | /// user. | |
9696f519 WB |
410 | fn import_master_pubkey(path: String) -> Result<(), Error> { |
411 | let pem_data = file_get_contents(&path)?; | |
412 | ||
42c0f784 FG |
413 | match openssl::pkey::PKey::public_key_from_pem(&pem_data) { |
414 | Ok(key) => { | |
415 | let info = RsaPubKeyInfo::try_from(key.rsa()?)?; | |
416 | println!("Found following key at {:?}", path); | |
417 | println!("Modulus: {}", info.modulus); | |
418 | println!("Exponent: {}", info.exponent); | |
419 | println!("Length: {}", info.length); | |
2924b37d | 420 | } |
42c0f784 FG |
421 | Err(err) => bail!("Unable to decode PEM data - {}", err), |
422 | }; | |
9696f519 | 423 | |
05f17d1e | 424 | let target_path = place_default_master_pubkey()?; |
9696f519 WB |
425 | |
426 | replace_file(&target_path, &pem_data, CreateOptions::new())?; | |
427 | ||
428 | println!("Imported public master key to {:?}", target_path); | |
429 | ||
430 | Ok(()) | |
431 | } | |
432 | ||
433 | #[api] | |
434 | /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup | |
435 | /// encryption key onto the backup server along with each backup. | |
436 | fn create_master_key() -> Result<(), Error> { | |
437 | // we need a TTY to query the new password | |
438 | if !tty::stdin_isatty() { | |
439 | bail!("unable to create master key - no tty"); | |
440 | } | |
441 | ||
42c0f784 FG |
442 | let bits = 4096; |
443 | println!("Generating {}-bit RSA key..", bits); | |
444 | let rsa = openssl::rsa::Rsa::generate(bits)?; | |
2924b37d FG |
445 | let public = |
446 | openssl::rsa::Rsa::from_public_components(rsa.n().to_owned()?, rsa.e().to_owned()?)?; | |
42c0f784 FG |
447 | let info = RsaPubKeyInfo::try_from(public)?; |
448 | println!("Modulus: {}", info.modulus); | |
449 | println!("Exponent: {}", info.exponent); | |
450 | println!(); | |
451 | ||
9696f519 WB |
452 | let pkey = openssl::pkey::PKey::from_rsa(rsa)?; |
453 | ||
454 | let password = String::from_utf8(tty::read_and_verify_password("Master Key Password: ")?)?; | |
455 | ||
456 | let pub_key: Vec<u8> = pkey.public_key_to_pem()?; | |
457 | let filename_pub = "master-public.pem"; | |
458 | println!("Writing public master key to {}", filename_pub); | |
459 | replace_file(filename_pub, pub_key.as_slice(), CreateOptions::new())?; | |
460 | ||
461 | let cipher = openssl::symm::Cipher::aes_256_cbc(); | |
2924b37d FG |
462 | let priv_key: Vec<u8> = |
463 | pkey.private_key_to_pem_pkcs8_passphrase(cipher, password.as_bytes())?; | |
9696f519 WB |
464 | |
465 | let filename_priv = "master-private.pem"; | |
466 | println!("Writing private master key to {}", filename_priv); | |
467 | replace_file(filename_priv, priv_key.as_slice(), CreateOptions::new())?; | |
468 | ||
469 | Ok(()) | |
470 | } | |
471 | ||
42c0f784 FG |
472 | #[api( |
473 | input: { | |
474 | properties: { | |
475 | path: { | |
476 | description: "Path to the PEM formatted RSA public key. Default location will be used if not specified.", | |
477 | optional: true, | |
478 | }, | |
479 | "output-format": { | |
480 | schema: OUTPUT_FORMAT, | |
481 | optional: true, | |
482 | }, | |
483 | }, | |
484 | }, | |
485 | )] | |
486 | /// List information about master key | |
487 | fn show_master_pubkey(path: Option<String>, param: Value) -> Result<(), Error> { | |
488 | let path = match path { | |
489 | Some(path) => PathBuf::from(path), | |
490 | None => find_default_master_pubkey()? | |
491 | .ok_or_else(|| format_err!("No path specified and no default master key available."))?, | |
492 | }; | |
493 | ||
494 | let path = path.canonicalize()?; | |
495 | ||
496 | let output_format = get_output_format(¶m); | |
497 | ||
498 | let pem_data = file_get_contents(path.clone())?; | |
499 | let rsa = openssl::rsa::Rsa::public_key_from_pem(&pem_data)?; | |
500 | ||
501 | let mut info = RsaPubKeyInfo::try_from(rsa)?; | |
502 | info.path = Some(path.display().to_string()); | |
503 | ||
504 | let options = proxmox::api::cli::default_table_format_options() | |
505 | .column(ColumnConfig::new("path")) | |
506 | .column(ColumnConfig::new("modulus")) | |
507 | .column(ColumnConfig::new("exponent")) | |
508 | .column(ColumnConfig::new("length")); | |
509 | ||
510 | let return_type = ReturnType::new(false, &RsaPubKeyInfo::API_SCHEMA); | |
511 | ||
512 | format_and_print_result_full( | |
513 | &mut serde_json::to_value(info)?, | |
514 | &return_type, | |
515 | &output_format, | |
516 | &options, | |
517 | ); | |
518 | ||
519 | Ok(()) | |
520 | } | |
521 | ||
82a0cd2a DM |
522 | #[api( |
523 | input: { | |
524 | properties: { | |
525 | path: { | |
526 | description: "Key file. Without this the default key's will be used.", | |
527 | optional: true, | |
528 | }, | |
529 | subject: { | |
d1d74c43 | 530 | description: "Include the specified subject as title text.", |
82a0cd2a DM |
531 | optional: true, |
532 | }, | |
ef1b4363 DM |
533 | "output-format": { |
534 | type: PaperkeyFormat, | |
ef1b4363 DM |
535 | optional: true, |
536 | }, | |
82a0cd2a DM |
537 | }, |
538 | }, | |
539 | )] | |
540 | /// Generate a printable, human readable text file containing the encryption key. | |
541 | /// | |
542 | /// This also includes a scanable QR code for fast key restore. | |
ef1b4363 DM |
543 | fn paper_key( |
544 | path: Option<String>, | |
545 | subject: Option<String>, | |
546 | output_format: Option<PaperkeyFormat>, | |
547 | ) -> Result<(), Error> { | |
82a0cd2a DM |
548 | let path = match path { |
549 | Some(path) => PathBuf::from(path), | |
4d104cd4 FG |
550 | None => find_default_encryption_key()? |
551 | .ok_or_else(|| format_err!("no encryption file provided and no default file found"))?, | |
82a0cd2a DM |
552 | }; |
553 | ||
554 | let data = file_get_contents(&path)?; | |
f1e29041 FG |
555 | let data = String::from_utf8(data)?; |
556 | ||
639a6782 | 557 | generate_paper_key(std::io::stdout(), &data, subject, output_format) |
ef1b4363 DM |
558 | } |
559 | ||
560 | pub fn cli() -> CliCommandMap { | |
561 | let key_create_cmd_def = CliCommand::new(&API_METHOD_CREATE) | |
562 | .arg_param(&["path"]) | |
563 | .completion_cb("path", tools::complete_file_name); | |
564 | ||
7137630d FG |
565 | let key_import_with_master_key_cmd_def = CliCommand::new(&API_METHOD_IMPORT_WITH_MASTER_KEY) |
566 | .arg_param(&["master-keyfile"]) | |
567 | .completion_cb("master-keyfile", tools::complete_file_name) | |
568 | .arg_param(&["encrypted-keyfile"]) | |
569 | .completion_cb("encrypted-keyfile", tools::complete_file_name) | |
570 | .arg_param(&["path"]) | |
571 | .completion_cb("path", tools::complete_file_name); | |
572 | ||
ef1b4363 DM |
573 | let key_change_passphrase_cmd_def = CliCommand::new(&API_METHOD_CHANGE_PASSPHRASE) |
574 | .arg_param(&["path"]) | |
575 | .completion_cb("path", tools::complete_file_name); | |
576 | ||
577 | let key_create_master_key_cmd_def = CliCommand::new(&API_METHOD_CREATE_MASTER_KEY); | |
578 | let key_import_master_pubkey_cmd_def = CliCommand::new(&API_METHOD_IMPORT_MASTER_PUBKEY) | |
579 | .arg_param(&["path"]) | |
580 | .completion_cb("path", tools::complete_file_name); | |
42c0f784 FG |
581 | let key_show_master_pubkey_cmd_def = CliCommand::new(&API_METHOD_SHOW_MASTER_PUBKEY) |
582 | .arg_param(&["path"]) | |
583 | .completion_cb("path", tools::complete_file_name); | |
ef1b4363 | 584 | |
dfb04575 FG |
585 | let key_show_cmd_def = CliCommand::new(&API_METHOD_SHOW_KEY) |
586 | .arg_param(&["path"]) | |
587 | .completion_cb("path", tools::complete_file_name); | |
588 | ||
ef1b4363 DM |
589 | let paper_key_cmd_def = CliCommand::new(&API_METHOD_PAPER_KEY) |
590 | .arg_param(&["path"]) | |
591 | .completion_cb("path", tools::complete_file_name); | |
592 | ||
593 | CliCommandMap::new() | |
594 | .insert("create", key_create_cmd_def) | |
7137630d | 595 | .insert("import-with-master-key", key_import_with_master_key_cmd_def) |
ef1b4363 DM |
596 | .insert("create-master-key", key_create_master_key_cmd_def) |
597 | .insert("import-master-pubkey", key_import_master_pubkey_cmd_def) | |
598 | .insert("change-passphrase", key_change_passphrase_cmd_def) | |
dfb04575 | 599 | .insert("show", key_show_cmd_def) |
42c0f784 | 600 | .insert("show-master-pubkey", key_show_master_pubkey_cmd_def) |
fdc00811 | 601 | .insert("paperkey", paper_key_cmd_def) |
ef1b4363 | 602 | } |