1 use std
::path
::PathBuf
;
3 use anyhow
::{bail, format_err, Error}
;
7 use proxmox
::api
::cli
::{
11 format_and_print_result_full
,
15 use proxmox
::api
::router
::ReturnType
;
16 use proxmox
::sys
::linux
::tty
;
17 use proxmox
::tools
::fs
::{file_get_contents, replace_file, CreateOptions}
;
30 rsa_decrypt_key_config
,
36 pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME
: &str = "encryption-key.json";
37 pub const MASTER_PUBKEY_FILE_NAME
: &str = "master-public.pem";
39 pub fn find_master_pubkey() -> Result
<Option
<PathBuf
>, Error
> {
40 super::find_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
43 pub fn place_master_pubkey() -> Result
<PathBuf
, Error
> {
44 super::place_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
47 pub fn find_default_encryption_key() -> Result
<Option
<PathBuf
>, Error
> {
48 super::find_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
51 pub fn place_default_encryption_key() -> Result
<PathBuf
, Error
> {
52 super::place_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
55 pub fn read_optional_default_encryption_key() -> Result
<Option
<Vec
<u8>>, Error
> {
56 find_default_encryption_key()?
57 .map(file_get_contents
)
61 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
62 // fixme: implement other input methods
64 use std
::env
::VarError
::*;
65 match std
::env
::var("PBS_ENCRYPTION_PASSWORD") {
66 Ok(p
) => return Ok(p
.as_bytes().to_vec()),
67 Err(NotUnicode(_
)) => bail
!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
73 // If we're on a TTY, query the user for a password
74 if tty
::stdin_isatty() {
75 return Ok(tty
::read_password("Encryption Key Password: ")?
);
78 bail
!("no password input mechanism available");
90 "Output file. Without this the key will become the new default encryption key.",
94 schema
: PASSWORD_HINT_SCHEMA
,
100 /// Create a new encryption key.
103 path
: Option
<String
>,
105 ) -> Result
<(), Error
> {
106 let path
= match path
{
107 Some(path
) => PathBuf
::from(path
),
109 let path
= place_default_encryption_key()?
;
110 println
!("creating default key at: {:?}", path
);
115 let kdf
= kdf
.unwrap_or_default();
117 let mut key
= [0u8; 32];
118 proxmox
::sys
::linux
::fill_with_random_data(&mut key
)?
;
123 bail
!("password hint not allowed for Kdf::None");
126 let key_config
= KeyConfig
::without_password(key
)?
;
128 key_config
.store(path
, false)?
;
130 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
131 // always read passphrase from tty
132 if !tty
::stdin_isatty() {
133 bail
!("unable to read passphrase - no tty");
136 let password
= tty
::read_and_verify_password("Encryption Key Password: ")?
;
138 let mut key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
139 key_config
.hint
= hint
;
141 key_config
.store(&path
, false)?
;
152 description
: "(Private) master key to use.",
154 "encrypted-keyfile": {
155 description
: "RSA-encrypted keyfile to import.",
163 "Output file. Without this the key will become the new default encryption key.",
167 schema
: PASSWORD_HINT_SCHEMA
,
173 /// Import an encrypted backup of an encryption key using a (private) master key.
174 async
fn import_with_master_key(
175 master_keyfile
: String
,
176 encrypted_keyfile
: String
,
178 path
: Option
<String
>,
179 hint
: Option
<String
>,
180 ) -> Result
<(), Error
> {
181 let path
= match path
{
182 Some(path
) => PathBuf
::from(path
),
184 let path
= place_default_encryption_key()?
;
186 bail
!("Please remove default encryption key at {:?} before importing to default location (or choose a non-default one).", path
);
188 println
!("Importing key to default location at: {:?}", path
);
193 let encrypted_key
= file_get_contents(&encrypted_keyfile
)?
;
194 let master_key
= file_get_contents(&master_keyfile
)?
;
195 let password
= tty
::read_password("Master Key Password: ")?
;
198 openssl
::pkey
::PKey
::private_key_from_pem_passphrase(&master_key
, &password
)
199 .map_err(|err
| format_err
!("failed to read PEM-formatted private key - {}", err
))?
201 .map_err(|err
| format_err
!("not a valid private RSA key - {}", err
))?
;
203 let (key
, created
, _fingerprint
) =
204 rsa_decrypt_key_config(master_key
, &encrypted_key
, &get_encryption_key_password
)?
;
206 let kdf
= kdf
.unwrap_or_default();
210 bail
!("password hint not allowed for Kdf::None");
213 let mut key_config
= KeyConfig
::without_password(key
)?
;
214 key_config
.created
= created
; // keep original value
216 key_config
.store(path
, true)?
;
219 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
220 let password
= tty
::read_and_verify_password("New Password: ")?
;
222 let mut new_key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
223 new_key_config
.created
= created
; // keep original value
224 new_key_config
.hint
= hint
;
226 new_key_config
.store(path
, true)?
;
241 description
: "Key file. Without this the default key's password will be changed.",
245 schema
: PASSWORD_HINT_SCHEMA
,
251 /// Change the encryption key's password.
252 fn change_passphrase(
254 path
: Option
<String
>,
255 hint
: Option
<String
>,
256 ) -> Result
<(), Error
> {
257 let path
= match path
{
258 Some(path
) => PathBuf
::from(path
),
260 let path
= find_default_encryption_key()?
262 format_err
!("no encryption file provided and no default file found")
264 println
!("updating default key at: {:?}", path
);
269 let kdf
= kdf
.unwrap_or_default();
271 if !tty
::stdin_isatty() {
272 bail
!("unable to change passphrase - no tty");
275 let key_config
= KeyConfig
::load(&path
)?
;
276 let (key
, created
, _fingerprint
) = key_config
.decrypt(&get_encryption_key_password
)?
;
281 bail
!("password hint not allowed for Kdf::None");
284 let mut key_config
= KeyConfig
::without_password(key
)?
;
285 key_config
.created
= created
; // keep original value
287 key_config
.store(&path
, true)?
;
289 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
290 let password
= tty
::read_and_verify_password("New Password: ")?
;
292 let mut new_key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
293 new_key_config
.created
= created
; // keep original value
294 new_key_config
.hint
= hint
;
296 new_key_config
.store(&path
, true)?
;
307 description
: "Key file. Without this the default key's metadata will be shown.",
311 schema
: OUTPUT_FORMAT
,
317 /// Print the encryption key's metadata.
319 path
: Option
<String
>,
321 ) -> Result
<(), Error
> {
322 let path
= match path
{
323 Some(path
) => PathBuf
::from(path
),
325 let path
= find_default_encryption_key()?
327 format_err
!("no encryption file provided and no default file found")
334 let config
: KeyConfig
= serde_json
::from_slice(&file_get_contents(path
.clone())?
)?
;
336 let output_format
= get_output_format(¶m
);
338 let mut info
: KeyInfo
= (&config
).into();
339 info
.path
= Some(format
!("{:?}", path
));
341 let options
= proxmox
::api
::cli
::default_table_format_options()
342 .column(ColumnConfig
::new("path"))
343 .column(ColumnConfig
::new("kdf"))
344 .column(ColumnConfig
::new("created").renderer(tools
::format
::render_epoch
))
345 .column(ColumnConfig
::new("modified").renderer(tools
::format
::render_epoch
))
346 .column(ColumnConfig
::new("fingerprint"))
347 .column(ColumnConfig
::new("hint"));
349 let return_type
= ReturnType
::new(false, &KeyInfo
::API_SCHEMA
);
351 format_and_print_result_full(
352 &mut serde_json
::to_value(info
)?
,
365 description
: "Path to the PEM formatted RSA public key.",
370 /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
371 /// key onto the backup server along with each backup.
372 fn import_master_pubkey(path
: String
) -> Result
<(), Error
> {
373 let pem_data
= file_get_contents(&path
)?
;
375 if let Err(err
) = openssl
::pkey
::PKey
::public_key_from_pem(&pem_data
) {
376 bail
!("Unable to decode PEM data - {}", err
);
379 let target_path
= place_master_pubkey()?
;
381 replace_file(&target_path
, &pem_data
, CreateOptions
::new())?
;
383 println
!("Imported public master key to {:?}", target_path
);
389 /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
390 /// encryption key onto the backup server along with each backup.
391 fn create_master_key() -> Result
<(), Error
> {
392 // we need a TTY to query the new password
393 if !tty
::stdin_isatty() {
394 bail
!("unable to create master key - no tty");
397 let rsa
= openssl
::rsa
::Rsa
::generate(4096)?
;
398 let pkey
= openssl
::pkey
::PKey
::from_rsa(rsa
)?
;
400 let password
= String
::from_utf8(tty
::read_and_verify_password("Master Key Password: ")?
)?
;
402 let pub_key
: Vec
<u8> = pkey
.public_key_to_pem()?
;
403 let filename_pub
= "master-public.pem";
404 println
!("Writing public master key to {}", filename_pub
);
405 replace_file(filename_pub
, pub_key
.as_slice(), CreateOptions
::new())?
;
407 let cipher
= openssl
::symm
::Cipher
::aes_256_cbc();
408 let priv_key
: Vec
<u8> = pkey
.private_key_to_pem_pkcs8_passphrase(cipher
, password
.as_bytes())?
;
410 let filename_priv
= "master-private.pem";
411 println
!("Writing private master key to {}", filename_priv
);
412 replace_file(filename_priv
, priv_key
.as_slice(), CreateOptions
::new())?
;
421 description
: "Key file. Without this the default key's will be used.",
425 description
: "Include the specified subject as titel text.",
429 type: PaperkeyFormat
,
435 /// Generate a printable, human readable text file containing the encryption key.
437 /// This also includes a scanable QR code for fast key restore.
439 path
: Option
<String
>,
440 subject
: Option
<String
>,
441 output_format
: Option
<PaperkeyFormat
>,
442 ) -> Result
<(), Error
> {
443 let path
= match path
{
444 Some(path
) => PathBuf
::from(path
),
446 let path
= find_default_encryption_key()?
448 format_err
!("no encryption file provided and no default file found")
454 let data
= file_get_contents(&path
)?
;
455 let data
= String
::from_utf8(data
)?
;
457 generate_paper_key(std
::io
::stdout(), &data
, subject
, output_format
)
460 pub fn cli() -> CliCommandMap
{
461 let key_create_cmd_def
= CliCommand
::new(&API_METHOD_CREATE
)
462 .arg_param(&["path"])
463 .completion_cb("path", tools
::complete_file_name
);
465 let key_import_with_master_key_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_WITH_MASTER_KEY
)
466 .arg_param(&["master-keyfile"])
467 .completion_cb("master-keyfile", tools
::complete_file_name
)
468 .arg_param(&["encrypted-keyfile"])
469 .completion_cb("encrypted-keyfile", tools
::complete_file_name
)
470 .arg_param(&["path"])
471 .completion_cb("path", tools
::complete_file_name
);
473 let key_change_passphrase_cmd_def
= CliCommand
::new(&API_METHOD_CHANGE_PASSPHRASE
)
474 .arg_param(&["path"])
475 .completion_cb("path", tools
::complete_file_name
);
477 let key_create_master_key_cmd_def
= CliCommand
::new(&API_METHOD_CREATE_MASTER_KEY
);
478 let key_import_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_MASTER_PUBKEY
)
479 .arg_param(&["path"])
480 .completion_cb("path", tools
::complete_file_name
);
482 let key_show_cmd_def
= CliCommand
::new(&API_METHOD_SHOW_KEY
)
483 .arg_param(&["path"])
484 .completion_cb("path", tools
::complete_file_name
);
486 let paper_key_cmd_def
= CliCommand
::new(&API_METHOD_PAPER_KEY
)
487 .arg_param(&["path"])
488 .completion_cb("path", tools
::complete_file_name
);
491 .insert("create", key_create_cmd_def
)
492 .insert("import-with-master-key", key_import_with_master_key_cmd_def
)
493 .insert("create-master-key", key_create_master_key_cmd_def
)
494 .insert("import-master-pubkey", key_import_master_pubkey_cmd_def
)
495 .insert("change-passphrase", key_change_passphrase_cmd_def
)
496 .insert("show", key_show_cmd_def
)
497 .insert("paperkey", paper_key_cmd_def
)