1 use std
::convert
::TryFrom
;
2 use std
::path
::PathBuf
;
4 use anyhow
::{bail, format_err, Error}
;
7 use proxmox_sys
::linux
::tty
;
8 use proxmox_sys
::fs
::{file_get_contents, replace_file, CreateOptions}
;
9 use proxmox_router
::cli
::{
10 complete_file_name
, format_and_print_result_full
, get_output_format
,
11 CliCommand
, CliCommandMap
, ColumnConfig
,
14 use proxmox_schema
::{api, ApiType, ReturnType}
;
16 use pbs_api_types
::{RsaPubKeyInfo, PASSWORD_HINT_SCHEMA, Kdf, KeyInfo}
;
17 use pbs_config
::key_config
::{KeyConfig, rsa_decrypt_key_config}
;
18 use pbs_datastore
::paperkey
::{generate_paper_key, PaperkeyFormat}
;
19 use pbs_client
::tools
::key_source
::{
20 find_default_encryption_key
, find_default_master_pubkey
, get_encryption_key_password
,
21 place_default_encryption_key
, place_default_master_pubkey
,
33 "Output file. Without this the key will become the new default encryption key.",
37 schema
: PASSWORD_HINT_SCHEMA
,
43 /// Create a new encryption key.
44 fn create(kdf
: Option
<Kdf
>, path
: Option
<String
>, hint
: Option
<String
>) -> Result
<(), Error
> {
45 let path
= match path
{
46 Some(path
) => PathBuf
::from(path
),
48 let path
= place_default_encryption_key()?
;
49 println
!("creating default key at: {:?}", path
);
54 let kdf
= kdf
.unwrap_or_default();
56 let mut key
= [0u8; 32];
57 proxmox_sys
::linux
::fill_with_random_data(&mut key
)?
;
62 bail
!("password hint not allowed for Kdf::None");
65 let key_config
= KeyConfig
::without_password(key
)?
;
67 key_config
.store(path
, false)?
;
69 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
70 // always read passphrase from tty
71 if !tty
::stdin_isatty() {
72 bail
!("unable to read passphrase - no tty");
75 let password
= tty
::read_and_verify_password("Encryption Key Password: ")?
;
77 let mut key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
78 key_config
.hint
= hint
;
80 key_config
.store(&path
, false)?
;
91 description
: "(Private) master key to use.",
93 "encrypted-keyfile": {
94 description
: "RSA-encrypted keyfile to import.",
102 "Output file. Without this the key will become the new default encryption key.",
106 schema
: PASSWORD_HINT_SCHEMA
,
112 /// Import an encrypted backup of an encryption key using a (private) master key.
113 async
fn import_with_master_key(
114 master_keyfile
: String
,
115 encrypted_keyfile
: String
,
117 path
: Option
<String
>,
118 hint
: Option
<String
>,
119 ) -> Result
<(), Error
> {
120 let path
= match path
{
121 Some(path
) => PathBuf
::from(path
),
123 let path
= place_default_encryption_key()?
;
125 bail
!("Please remove default encryption key at {:?} before importing to default location (or choose a non-default one).", path
);
127 println
!("Importing key to default location at: {:?}", path
);
132 let encrypted_key
= file_get_contents(&encrypted_keyfile
)?
;
133 let master_key
= file_get_contents(&master_keyfile
)?
;
134 let password
= tty
::read_password("Master Key Password: ")?
;
136 let master_key
= openssl
::pkey
::PKey
::private_key_from_pem_passphrase(&master_key
, &password
)
137 .map_err(|err
| format_err
!("failed to read PEM-formatted private key - {}", err
))?
139 .map_err(|err
| format_err
!("not a valid private RSA key - {}", err
))?
;
141 let (key
, created
, _fingerprint
) =
142 rsa_decrypt_key_config(master_key
, &encrypted_key
, &get_encryption_key_password
)?
;
144 let kdf
= kdf
.unwrap_or_default();
148 bail
!("password hint not allowed for Kdf::None");
151 let mut key_config
= KeyConfig
::without_password(key
)?
;
152 key_config
.created
= created
; // keep original value
154 key_config
.store(path
, true)?
;
156 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
157 let password
= tty
::read_and_verify_password("New Password: ")?
;
159 let mut new_key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
160 new_key_config
.created
= created
; // keep original value
161 new_key_config
.hint
= hint
;
163 new_key_config
.store(path
, true)?
;
178 description
: "Key file. Without this the default key's password will be changed.",
182 schema
: PASSWORD_HINT_SCHEMA
,
188 /// Change the encryption key's password.
189 fn change_passphrase(
191 path
: Option
<String
>,
192 hint
: Option
<String
>,
193 ) -> Result
<(), Error
> {
194 let path
= match path
{
195 Some(path
) => PathBuf
::from(path
),
197 let path
= find_default_encryption_key()?
.ok_or_else(|| {
198 format_err
!("no encryption file provided and no default file found")
200 println
!("updating default key at: {:?}", path
);
205 let kdf
= kdf
.unwrap_or_default();
207 if !tty
::stdin_isatty() {
208 bail
!("unable to change passphrase - no tty");
211 let key_config
= KeyConfig
::load(&path
)?
;
212 let (key
, created
, _fingerprint
) = key_config
.decrypt(&get_encryption_key_password
)?
;
217 bail
!("password hint not allowed for Kdf::None");
220 let mut key_config
= KeyConfig
::without_password(key
)?
;
221 key_config
.created
= created
; // keep original value
223 key_config
.store(&path
, true)?
;
225 Kdf
::Scrypt
| Kdf
::PBKDF2
=> {
226 let password
= tty
::read_and_verify_password("New Password: ")?
;
228 let mut new_key_config
= KeyConfig
::with_key(&key
, &password
, kdf
)?
;
229 new_key_config
.created
= created
; // keep original value
230 new_key_config
.hint
= hint
;
232 new_key_config
.store(&path
, true)?
;
243 description
: "Key file. Without this the default key's metadata will be shown.",
247 schema
: OUTPUT_FORMAT
,
253 /// Print the encryption key's metadata.
254 fn show_key(path
: Option
<String
>, param
: Value
) -> Result
<(), Error
> {
255 let path
= match path
{
256 Some(path
) => PathBuf
::from(path
),
257 None
=> find_default_encryption_key()?
258 .ok_or_else(|| format_err
!("no encryption file provided and no default file found"))?
,
261 let config
: KeyConfig
= serde_json
::from_slice(&file_get_contents(path
.clone())?
)?
;
263 let output_format
= get_output_format(¶m
);
265 let mut info
: KeyInfo
= (&config
).into();
266 info
.path
= Some(format
!("{:?}", path
));
268 let options
= proxmox_router
::cli
::default_table_format_options()
269 .column(ColumnConfig
::new("path"))
270 .column(ColumnConfig
::new("kdf"))
271 .column(ColumnConfig
::new("created").renderer(pbs_tools
::format
::render_epoch
))
272 .column(ColumnConfig
::new("modified").renderer(pbs_tools
::format
::render_epoch
))
273 .column(ColumnConfig
::new("fingerprint"))
274 .column(ColumnConfig
::new("hint"));
276 let return_type
= ReturnType
::new(false, &KeyInfo
::API_SCHEMA
);
278 format_and_print_result_full(
279 &mut serde_json
::to_value(info
)?
,
292 description
: "Path to the PEM formatted RSA public key.",
297 /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
298 /// key onto the backup server along with each backup.
300 /// The imported key will be used as default master key for future invocations by the same local
302 fn import_master_pubkey(path
: String
) -> Result
<(), Error
> {
303 let pem_data
= file_get_contents(&path
)?
;
305 match openssl
::pkey
::PKey
::public_key_from_pem(&pem_data
) {
307 let info
= RsaPubKeyInfo
::try_from(key
.rsa()?
)?
;
308 println
!("Found following key at {:?}", path
);
309 println
!("Modulus: {}", info
.modulus
);
310 println
!("Exponent: {}", info
.exponent
);
311 println
!("Length: {}", info
.length
);
313 Err(err
) => bail
!("Unable to decode PEM data - {}", err
),
316 let target_path
= place_default_master_pubkey()?
;
318 replace_file(&target_path
, &pem_data
, CreateOptions
::new(), true)?
;
320 println
!("Imported public master key to {:?}", target_path
);
326 /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
327 /// encryption key onto the backup server along with each backup.
328 fn create_master_key() -> Result
<(), Error
> {
329 // we need a TTY to query the new password
330 if !tty
::stdin_isatty() {
331 bail
!("unable to create master key - no tty");
335 println
!("Generating {}-bit RSA key..", bits
);
336 let rsa
= openssl
::rsa
::Rsa
::generate(bits
)?
;
338 openssl
::rsa
::Rsa
::from_public_components(rsa
.n().to_owned()?
, rsa
.e().to_owned()?
)?
;
339 let info
= RsaPubKeyInfo
::try_from(public
)?
;
340 println
!("Modulus: {}", info
.modulus
);
341 println
!("Exponent: {}", info
.exponent
);
344 let pkey
= openssl
::pkey
::PKey
::from_rsa(rsa
)?
;
346 let password
= String
::from_utf8(tty
::read_and_verify_password("Master Key Password: ")?
)?
;
348 let pub_key
: Vec
<u8> = pkey
.public_key_to_pem()?
;
349 let filename_pub
= "master-public.pem";
350 println
!("Writing public master key to {}", filename_pub
);
351 replace_file(filename_pub
, pub_key
.as_slice(), CreateOptions
::new(), true)?
;
353 let cipher
= openssl
::symm
::Cipher
::aes_256_cbc();
354 let priv_key
: Vec
<u8> =
355 pkey
.private_key_to_pem_pkcs8_passphrase(cipher
, password
.as_bytes())?
;
357 let filename_priv
= "master-private.pem";
358 println
!("Writing private master key to {}", filename_priv
);
359 replace_file(filename_priv
, priv_key
.as_slice(), CreateOptions
::new(), true)?
;
368 description
: "Path to the PEM formatted RSA public key. Default location will be used if not specified.",
372 schema
: OUTPUT_FORMAT
,
378 /// List information about master key
379 fn show_master_pubkey(path
: Option
<String
>, param
: Value
) -> Result
<(), Error
> {
380 let path
= match path
{
381 Some(path
) => PathBuf
::from(path
),
382 None
=> find_default_master_pubkey()?
383 .ok_or_else(|| format_err
!("No path specified and no default master key available."))?
,
386 let path
= path
.canonicalize()?
;
388 let output_format
= get_output_format(¶m
);
390 let pem_data
= file_get_contents(path
.clone())?
;
391 let rsa
= openssl
::rsa
::Rsa
::public_key_from_pem(&pem_data
)?
;
393 let mut info
= RsaPubKeyInfo
::try_from(rsa
)?
;
394 info
.path
= Some(path
.display().to_string());
396 let options
= proxmox_router
::cli
::default_table_format_options()
397 .column(ColumnConfig
::new("path"))
398 .column(ColumnConfig
::new("modulus"))
399 .column(ColumnConfig
::new("exponent"))
400 .column(ColumnConfig
::new("length"));
402 let return_type
= ReturnType
::new(false, &RsaPubKeyInfo
::API_SCHEMA
);
404 format_and_print_result_full(
405 &mut serde_json
::to_value(info
)?
,
418 description
: "Key file. Without this the default key's will be used.",
422 description
: "Include the specified subject as title text.",
426 type: PaperkeyFormat
,
432 /// Generate a printable, human readable text file containing the encryption key.
434 /// This also includes a scanable QR code for fast key restore.
436 path
: Option
<String
>,
437 subject
: Option
<String
>,
438 output_format
: Option
<PaperkeyFormat
>,
439 ) -> Result
<(), Error
> {
440 let path
= match path
{
441 Some(path
) => PathBuf
::from(path
),
442 None
=> find_default_encryption_key()?
443 .ok_or_else(|| format_err
!("no encryption file provided and no default file found"))?
,
446 let data
= file_get_contents(&path
)?
;
447 let data
= String
::from_utf8(data
)?
;
449 generate_paper_key(std
::io
::stdout(), &data
, subject
, output_format
)
452 pub fn cli() -> CliCommandMap
{
453 let key_create_cmd_def
= CliCommand
::new(&API_METHOD_CREATE
)
454 .arg_param(&["path"])
455 .completion_cb("path", complete_file_name
);
457 let key_import_with_master_key_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_WITH_MASTER_KEY
)
458 .arg_param(&["master-keyfile"])
459 .completion_cb("master-keyfile", complete_file_name
)
460 .arg_param(&["encrypted-keyfile"])
461 .completion_cb("encrypted-keyfile", complete_file_name
)
462 .arg_param(&["path"])
463 .completion_cb("path", complete_file_name
);
465 let key_change_passphrase_cmd_def
= CliCommand
::new(&API_METHOD_CHANGE_PASSPHRASE
)
466 .arg_param(&["path"])
467 .completion_cb("path", complete_file_name
);
469 let key_create_master_key_cmd_def
= CliCommand
::new(&API_METHOD_CREATE_MASTER_KEY
);
470 let key_import_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_MASTER_PUBKEY
)
471 .arg_param(&["path"])
472 .completion_cb("path", complete_file_name
);
473 let key_show_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_SHOW_MASTER_PUBKEY
)
474 .arg_param(&["path"])
475 .completion_cb("path", complete_file_name
);
477 let key_show_cmd_def
= CliCommand
::new(&API_METHOD_SHOW_KEY
)
478 .arg_param(&["path"])
479 .completion_cb("path", complete_file_name
);
481 let paper_key_cmd_def
= CliCommand
::new(&API_METHOD_PAPER_KEY
)
482 .arg_param(&["path"])
483 .completion_cb("path", complete_file_name
);
486 .insert("create", key_create_cmd_def
)
487 .insert("import-with-master-key", key_import_with_master_key_cmd_def
)
488 .insert("create-master-key", key_create_master_key_cmd_def
)
489 .insert("import-master-pubkey", key_import_master_pubkey_cmd_def
)
490 .insert("change-passphrase", key_change_passphrase_cmd_def
)
491 .insert("show", key_show_cmd_def
)
492 .insert("show-master-pubkey", key_show_master_pubkey_cmd_def
)
493 .insert("paperkey", paper_key_cmd_def
)