1 use std
::path
::PathBuf
;
2 use std
::convert
::TryFrom
;
4 use anyhow
::{bail, format_err, Error}
;
8 use proxmox
::api
::cli
::{
12 format_and_print_result_full
,
16 use proxmox
::api
::router
::ReturnType
;
17 use proxmox
::sys
::linux
::tty
;
18 use proxmox
::tools
::fs
::{file_get_contents, replace_file, CreateOptions}
;
32 rsa_decrypt_key_config
,
38 pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME
: &str = "encryption-key.json";
39 pub const DEFAULT_MASTER_PUBKEY_FILE_NAME
: &str = "master-public.pem";
41 pub fn find_default_master_pubkey() -> Result
<Option
<PathBuf
>, Error
> {
42 super::find_xdg_file(DEFAULT_MASTER_PUBKEY_FILE_NAME
, "default master public key file")
45 pub fn place_default_master_pubkey() -> Result
<PathBuf
, Error
> {
46 super::place_xdg_file(DEFAULT_MASTER_PUBKEY_FILE_NAME
, "default master public key file")
49 pub fn find_default_encryption_key() -> Result
<Option
<PathBuf
>, Error
> {
50 super::find_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
53 pub fn place_default_encryption_key() -> Result
<PathBuf
, Error
> {
54 super::place_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
57 pub fn read_optional_default_encryption_key() -> Result
<Option
<Vec
<u8>>, Error
> {
58 find_default_encryption_key()?
59 .map(file_get_contents
)
63 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
64 // fixme: implement other input methods
66 use std
::env
::VarError
::*;
67 match std
::env
::var("PBS_ENCRYPTION_PASSWORD") {
68 Ok(p
) => return Ok(p
.as_bytes().to_vec()),
69 Err(NotUnicode(_
)) => bail
!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
75 // If we're on a TTY, query the user for a password
76 if tty
::stdin_isatty() {
77 return Ok(tty
::read_password("Encryption Key Password: ")?
);
80 bail
!("no password input mechanism available");
92 "Output file. Without this the key will become the new default encryption key.",
96 schema
: PASSWORD_HINT_SCHEMA
,
102 /// Create a new encryption key.
105 path
: Option
<String
>,
107 ) -> Result
<(), Error
> {
108 let path
= match path
{
109 Some(path
) => PathBuf
::from(path
),
111 let path
= place_default_encryption_key()?
;
112 println
!("creating default key at: {:?}", path
);
117 let kdf
= kdf
.unwrap_or_default();
119 let mut key
= [0u8; 32];
120 proxmox
::sys
::linux
::fill_with_random_data(&mut key
)?
;
125 bail
!("password hint not allowed for Kdf::None");
128 let key_config
= KeyConfig
::without_password(key
)?
;
130 key_config
.store(path
, false)?
;
132 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
133 // always read passphrase from tty
134 if !tty
::stdin_isatty() {
135 bail
!("unable to read passphrase - no tty");
138 let password
= tty
::read_and_verify_password("Encryption Key Password: ")?
;
140 let mut key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
141 key_config
.hint
= hint
;
143 key_config
.store(&path
, false)?
;
154 description
: "(Private) master key to use.",
156 "encrypted-keyfile": {
157 description
: "RSA-encrypted keyfile to import.",
165 "Output file. Without this the key will become the new default encryption key.",
169 schema
: PASSWORD_HINT_SCHEMA
,
175 /// Import an encrypted backup of an encryption key using a (private) master key.
176 async
fn import_with_master_key(
177 master_keyfile
: String
,
178 encrypted_keyfile
: String
,
180 path
: Option
<String
>,
181 hint
: Option
<String
>,
182 ) -> Result
<(), Error
> {
183 let path
= match path
{
184 Some(path
) => PathBuf
::from(path
),
186 let path
= place_default_encryption_key()?
;
188 bail
!("Please remove default encryption key at {:?} before importing to default location (or choose a non-default one).", path
);
190 println
!("Importing key to default location at: {:?}", path
);
195 let encrypted_key
= file_get_contents(&encrypted_keyfile
)?
;
196 let master_key
= file_get_contents(&master_keyfile
)?
;
197 let password
= tty
::read_password("Master Key Password: ")?
;
200 openssl
::pkey
::PKey
::private_key_from_pem_passphrase(&master_key
, &password
)
201 .map_err(|err
| format_err
!("failed to read PEM-formatted private key - {}", err
))?
203 .map_err(|err
| format_err
!("not a valid private RSA key - {}", err
))?
;
205 let (key
, created
, _fingerprint
) =
206 rsa_decrypt_key_config(master_key
, &encrypted_key
, &get_encryption_key_password
)?
;
208 let kdf
= kdf
.unwrap_or_default();
212 bail
!("password hint not allowed for Kdf::None");
215 let mut key_config
= KeyConfig
::without_password(key
)?
;
216 key_config
.created
= created
; // keep original value
218 key_config
.store(path
, true)?
;
221 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
222 let password
= tty
::read_and_verify_password("New Password: ")?
;
224 let mut new_key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
225 new_key_config
.created
= created
; // keep original value
226 new_key_config
.hint
= hint
;
228 new_key_config
.store(path
, true)?
;
243 description
: "Key file. Without this the default key's password will be changed.",
247 schema
: PASSWORD_HINT_SCHEMA
,
253 /// Change the encryption key's password.
254 fn change_passphrase(
256 path
: Option
<String
>,
257 hint
: Option
<String
>,
258 ) -> Result
<(), Error
> {
259 let path
= match path
{
260 Some(path
) => PathBuf
::from(path
),
262 let path
= find_default_encryption_key()?
264 format_err
!("no encryption file provided and no default file found")
266 println
!("updating default key at: {:?}", path
);
271 let kdf
= kdf
.unwrap_or_default();
273 if !tty
::stdin_isatty() {
274 bail
!("unable to change passphrase - no tty");
277 let key_config
= KeyConfig
::load(&path
)?
;
278 let (key
, created
, _fingerprint
) = key_config
.decrypt(&get_encryption_key_password
)?
;
283 bail
!("password hint not allowed for Kdf::None");
286 let mut key_config
= KeyConfig
::without_password(key
)?
;
287 key_config
.created
= created
; // keep original value
289 key_config
.store(&path
, true)?
;
291 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
292 let password
= tty
::read_and_verify_password("New Password: ")?
;
294 let mut new_key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
295 new_key_config
.created
= created
; // keep original value
296 new_key_config
.hint
= hint
;
298 new_key_config
.store(&path
, true)?
;
309 description
: "Key file. Without this the default key's metadata will be shown.",
313 schema
: OUTPUT_FORMAT
,
319 /// Print the encryption key's metadata.
320 fn show_key(path
: Option
<String
>, param
: Value
) -> Result
<(), Error
> {
321 let path
= match path
{
322 Some(path
) => PathBuf
::from(path
),
323 None
=> find_default_encryption_key()?
324 .ok_or_else(|| format_err
!("no encryption file provided and no default file found"))?
,
327 let config
: KeyConfig
= serde_json
::from_slice(&file_get_contents(path
.clone())?
)?
;
329 let output_format
= get_output_format(¶m
);
331 let mut info
: KeyInfo
= (&config
).into();
332 info
.path
= Some(format
!("{:?}", path
));
334 let options
= proxmox
::api
::cli
::default_table_format_options()
335 .column(ColumnConfig
::new("path"))
336 .column(ColumnConfig
::new("kdf"))
337 .column(ColumnConfig
::new("created").renderer(tools
::format
::render_epoch
))
338 .column(ColumnConfig
::new("modified").renderer(tools
::format
::render_epoch
))
339 .column(ColumnConfig
::new("fingerprint"))
340 .column(ColumnConfig
::new("hint"));
342 let return_type
= ReturnType
::new(false, &KeyInfo
::API_SCHEMA
);
344 format_and_print_result_full(
345 &mut serde_json
::to_value(info
)?
,
358 description
: "Path to the PEM formatted RSA public key.",
363 /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
364 /// key onto the backup server along with each backup.
366 /// The imported key will be used as default master key for future invocations by the same local
368 fn import_master_pubkey(path
: String
) -> Result
<(), Error
> {
369 let pem_data
= file_get_contents(&path
)?
;
371 match openssl
::pkey
::PKey
::public_key_from_pem(&pem_data
) {
373 let info
= RsaPubKeyInfo
::try_from(key
.rsa()?
)?
;
374 println
!("Found following key at {:?}", path
);
375 println
!("Modulus: {}", info
.modulus
);
376 println
!("Exponent: {}", info
.exponent
);
377 println
!("Length: {}", info
.length
);
379 Err(err
) => bail
!("Unable to decode PEM data - {}", err
),
382 let target_path
= place_default_master_pubkey()?
;
384 replace_file(&target_path
, &pem_data
, CreateOptions
::new())?
;
386 println
!("Imported public master key to {:?}", target_path
);
392 /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
393 /// encryption key onto the backup server along with each backup.
394 fn create_master_key() -> Result
<(), Error
> {
395 // we need a TTY to query the new password
396 if !tty
::stdin_isatty() {
397 bail
!("unable to create master key - no tty");
401 println
!("Generating {}-bit RSA key..", bits
);
402 let rsa
= openssl
::rsa
::Rsa
::generate(bits
)?
;
403 let public
= openssl
::rsa
::Rsa
::from_public_components(
407 let info
= RsaPubKeyInfo
::try_from(public
)?
;
408 println
!("Modulus: {}", info
.modulus
);
409 println
!("Exponent: {}", info
.exponent
);
412 let pkey
= openssl
::pkey
::PKey
::from_rsa(rsa
)?
;
414 let password
= String
::from_utf8(tty
::read_and_verify_password("Master Key Password: ")?
)?
;
416 let pub_key
: Vec
<u8> = pkey
.public_key_to_pem()?
;
417 let filename_pub
= "master-public.pem";
418 println
!("Writing public master key to {}", filename_pub
);
419 replace_file(filename_pub
, pub_key
.as_slice(), CreateOptions
::new())?
;
421 let cipher
= openssl
::symm
::Cipher
::aes_256_cbc();
422 let priv_key
: Vec
<u8> = pkey
.private_key_to_pem_pkcs8_passphrase(cipher
, password
.as_bytes())?
;
424 let filename_priv
= "master-private.pem";
425 println
!("Writing private master key to {}", filename_priv
);
426 replace_file(filename_priv
, priv_key
.as_slice(), CreateOptions
::new())?
;
435 description
: "Path to the PEM formatted RSA public key. Default location will be used if not specified.",
439 schema
: OUTPUT_FORMAT
,
445 /// List information about master key
446 fn show_master_pubkey(path
: Option
<String
>, param
: Value
) -> Result
<(), Error
> {
447 let path
= match path
{
448 Some(path
) => PathBuf
::from(path
),
449 None
=> find_default_master_pubkey()?
450 .ok_or_else(|| format_err
!("No path specified and no default master key available."))?
,
453 let path
= path
.canonicalize()?
;
455 let output_format
= get_output_format(¶m
);
457 let pem_data
= file_get_contents(path
.clone())?
;
458 let rsa
= openssl
::rsa
::Rsa
::public_key_from_pem(&pem_data
)?
;
460 let mut info
= RsaPubKeyInfo
::try_from(rsa
)?
;
461 info
.path
= Some(path
.display().to_string());
463 let options
= proxmox
::api
::cli
::default_table_format_options()
464 .column(ColumnConfig
::new("path"))
465 .column(ColumnConfig
::new("modulus"))
466 .column(ColumnConfig
::new("exponent"))
467 .column(ColumnConfig
::new("length"));
469 let return_type
= ReturnType
::new(false, &RsaPubKeyInfo
::API_SCHEMA
);
471 format_and_print_result_full(
472 &mut serde_json
::to_value(info
)?
,
485 description
: "Key file. Without this the default key's will be used.",
489 description
: "Include the specified subject as titel text.",
493 type: PaperkeyFormat
,
499 /// Generate a printable, human readable text file containing the encryption key.
501 /// This also includes a scanable QR code for fast key restore.
503 path
: Option
<String
>,
504 subject
: Option
<String
>,
505 output_format
: Option
<PaperkeyFormat
>,
506 ) -> Result
<(), Error
> {
507 let path
= match path
{
508 Some(path
) => PathBuf
::from(path
),
509 None
=> find_default_encryption_key()?
510 .ok_or_else(|| format_err
!("no encryption file provided and no default file found"))?
,
513 let data
= file_get_contents(&path
)?
;
514 let data
= String
::from_utf8(data
)?
;
516 generate_paper_key(std
::io
::stdout(), &data
, subject
, output_format
)
519 pub fn cli() -> CliCommandMap
{
520 let key_create_cmd_def
= CliCommand
::new(&API_METHOD_CREATE
)
521 .arg_param(&["path"])
522 .completion_cb("path", tools
::complete_file_name
);
524 let key_import_with_master_key_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_WITH_MASTER_KEY
)
525 .arg_param(&["master-keyfile"])
526 .completion_cb("master-keyfile", tools
::complete_file_name
)
527 .arg_param(&["encrypted-keyfile"])
528 .completion_cb("encrypted-keyfile", tools
::complete_file_name
)
529 .arg_param(&["path"])
530 .completion_cb("path", tools
::complete_file_name
);
532 let key_change_passphrase_cmd_def
= CliCommand
::new(&API_METHOD_CHANGE_PASSPHRASE
)
533 .arg_param(&["path"])
534 .completion_cb("path", tools
::complete_file_name
);
536 let key_create_master_key_cmd_def
= CliCommand
::new(&API_METHOD_CREATE_MASTER_KEY
);
537 let key_import_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_MASTER_PUBKEY
)
538 .arg_param(&["path"])
539 .completion_cb("path", tools
::complete_file_name
);
540 let key_show_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_SHOW_MASTER_PUBKEY
)
541 .arg_param(&["path"])
542 .completion_cb("path", tools
::complete_file_name
);
544 let key_show_cmd_def
= CliCommand
::new(&API_METHOD_SHOW_KEY
)
545 .arg_param(&["path"])
546 .completion_cb("path", tools
::complete_file_name
);
548 let paper_key_cmd_def
= CliCommand
::new(&API_METHOD_PAPER_KEY
)
549 .arg_param(&["path"])
550 .completion_cb("path", tools
::complete_file_name
);
553 .insert("create", key_create_cmd_def
)
554 .insert("import-with-master-key", key_import_with_master_key_cmd_def
)
555 .insert("create-master-key", key_create_master_key_cmd_def
)
556 .insert("import-master-pubkey", key_import_master_pubkey_cmd_def
)
557 .insert("change-passphrase", key_change_passphrase_cmd_def
)
558 .insert("show", key_show_cmd_def
)
559 .insert("show-master-pubkey", key_show_master_pubkey_cmd_def
)
560 .insert("paperkey", paper_key_cmd_def
)