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