1 use std
::path
::PathBuf
;
3 use std
::process
::{Stdio, Command}
;
5 use anyhow
::{bail, format_err, Error}
;
6 use serde
::{Deserialize, Serialize}
;
9 use proxmox
::api
::cli
::{CliCommand, CliCommandMap}
;
10 use proxmox
::sys
::linux
::tty
;
11 use proxmox
::tools
::fs
::{file_get_contents, replace_file, CreateOptions}
;
13 use proxmox_backup
::backup
::{
14 encrypt_key_with_passphrase
, load_and_decrypt_key
, store_key_config
, KeyConfig
,
16 use proxmox_backup
::tools
;
18 pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME
: &str = "encryption-key.json";
19 pub const MASTER_PUBKEY_FILE_NAME
: &str = "master-public.pem";
21 pub fn find_master_pubkey() -> Result
<Option
<PathBuf
>, Error
> {
22 super::find_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
25 pub fn place_master_pubkey() -> Result
<PathBuf
, Error
> {
26 super::place_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
29 pub fn find_default_encryption_key() -> Result
<Option
<PathBuf
>, Error
> {
30 super::find_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
33 pub fn place_default_encryption_key() -> Result
<PathBuf
, Error
> {
34 super::place_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
37 pub fn read_optional_default_encryption_key() -> Result
<Option
<Vec
<u8>>, Error
> {
38 find_default_encryption_key()?
39 .map(file_get_contents
)
43 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
44 // fixme: implement other input methods
46 use std
::env
::VarError
::*;
47 match std
::env
::var("PBS_ENCRYPTION_PASSWORD") {
48 Ok(p
) => return Ok(p
.as_bytes().to_vec()),
49 Err(NotUnicode(_
)) => bail
!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
55 // If we're on a TTY, query the user for a password
56 if tty
::stdin_isatty() {
57 return Ok(tty
::read_password("Encryption Key Password: ")?
);
60 bail
!("no password input mechanism available");
66 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
67 #[serde(rename_all = "kebab-case")]
68 /// Key derivation function for password protected encryption keys.
70 /// Do not encrypt the key.
73 /// Encrypt they key with a password using SCrypt.
77 impl Default
for Kdf
{
79 fn default() -> Self {
93 "Output file. Without this the key will become the new default encryption key.",
99 /// Create a new encryption key.
100 fn create(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
101 let path
= match path
{
102 Some(path
) => PathBuf
::from(path
),
104 let path
= place_default_encryption_key()?
;
105 println
!("creating default key at: {:?}", path
);
110 let kdf
= kdf
.unwrap_or_default();
112 let key
= proxmox
::sys
::linux
::random_data(32)?
;
116 let created
= proxmox
::tools
::time
::epoch_i64();
130 // always read passphrase from tty
131 if !tty
::stdin_isatty() {
132 bail
!("unable to read passphrase - no tty");
135 let password
= tty
::read_and_verify_password("Encryption Key Password: ")?
;
137 let key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
139 store_key_config(&path
, false, key_config
)?
;
154 description
: "Key file. Without this the default key's password will be changed.",
160 /// Change the encryption key's password.
161 fn change_passphrase(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
162 let path
= match path
{
163 Some(path
) => PathBuf
::from(path
),
165 let path
= find_default_encryption_key()?
167 format_err
!("no encryption file provided and no default file found")
169 println
!("updating default key at: {:?}", path
);
174 let kdf
= kdf
.unwrap_or_default();
176 if !tty
::stdin_isatty() {
177 bail
!("unable to change passphrase - no tty");
180 let (key
, created
) = load_and_decrypt_key(&path
, &get_encryption_key_password
)?
;
184 let modified
= proxmox
::tools
::time
::epoch_i64();
191 created
, // keep original value
198 let password
= tty
::read_and_verify_password("New Password: ")?
;
200 let mut new_key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
201 new_key_config
.created
= created
; // keep original value
203 store_key_config(&path
, true, new_key_config
)?
;
214 description
: "Path to the PEM formatted RSA public key.",
219 /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
220 /// key onto the backup server along with each backup.
221 fn import_master_pubkey(path
: String
) -> Result
<(), Error
> {
222 let pem_data
= file_get_contents(&path
)?
;
224 if let Err(err
) = openssl
::pkey
::PKey
::public_key_from_pem(&pem_data
) {
225 bail
!("Unable to decode PEM data - {}", err
);
228 let target_path
= place_master_pubkey()?
;
230 replace_file(&target_path
, &pem_data
, CreateOptions
::new())?
;
232 println
!("Imported public master key to {:?}", target_path
);
238 /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
239 /// encryption key onto the backup server along with each backup.
240 fn create_master_key() -> Result
<(), Error
> {
241 // we need a TTY to query the new password
242 if !tty
::stdin_isatty() {
243 bail
!("unable to create master key - no tty");
246 let rsa
= openssl
::rsa
::Rsa
::generate(4096)?
;
247 let pkey
= openssl
::pkey
::PKey
::from_rsa(rsa
)?
;
249 let password
= String
::from_utf8(tty
::read_and_verify_password("Master Key Password: ")?
)?
;
251 let pub_key
: Vec
<u8> = pkey
.public_key_to_pem()?
;
252 let filename_pub
= "master-public.pem";
253 println
!("Writing public master key to {}", filename_pub
);
254 replace_file(filename_pub
, pub_key
.as_slice(), CreateOptions
::new())?
;
256 let cipher
= openssl
::symm
::Cipher
::aes_256_cbc();
257 let priv_key
: Vec
<u8> = pkey
.private_key_to_pem_pkcs8_passphrase(cipher
, password
.as_bytes())?
;
259 let filename_priv
= "master-private.pem";
260 println
!("Writing private master key to {}", filename_priv
);
261 replace_file(filename_priv
, priv_key
.as_slice(), CreateOptions
::new())?
;
270 description
: "Key file. Without this the default key's will be used.",
274 description
: "Include the specified subject as titel text.",
280 /// Generate a printable, human readable text file containing the encryption key.
282 /// This also includes a scanable QR code for fast key restore.
283 fn paper_key(path
: Option
<String
>, subject
: Option
<String
>) -> Result
<(), Error
> {
284 let path
= match path
{
285 Some(path
) => PathBuf
::from(path
),
287 let path
= find_default_encryption_key()?
289 format_err
!("no encryption file provided and no default file found")
295 let data
= file_get_contents(&path
)?
;
296 let key_config
: KeyConfig
= serde_json
::from_slice(&data
)?
;
297 let key_text
= serde_json
::to_string_pretty(&key_config
)?
;
299 if let Some(subject
) = subject
{
300 println
!("Subject: {}\n", subject
);
303 println
!("-----BEGIN PROXMOX BACKUP KEY-----");
304 println
!("{}", key_text
);
305 println
!("-----END PROXMOX BACKUP KEY-----");
307 let mut child
= Command
::new("qrencode")
308 .args(&["-t", "utf8i", "-lm"])
309 .stdin(Stdio
::piped())
310 .stdout(Stdio
::piped())
314 let stdin
= child
.stdin
.as_mut().expect("Failed to open stdin");
315 stdin
.write_all(key_text
.as_bytes()).expect("Failed to write to stdin");
318 let output
= child
.wait_with_output().expect("Failed to read stdout");
320 println
!("{}", String
::from_utf8_lossy(&output
.stdout
));
326 pub fn cli() -> CliCommandMap
{
327 let key_create_cmd_def
= CliCommand
::new(&API_METHOD_CREATE
)
328 .arg_param(&["path"])
329 .completion_cb("path", tools
::complete_file_name
);
331 let key_change_passphrase_cmd_def
= CliCommand
::new(&API_METHOD_CHANGE_PASSPHRASE
)
332 .arg_param(&["path"])
333 .completion_cb("path", tools
::complete_file_name
);
335 let key_create_master_key_cmd_def
= CliCommand
::new(&API_METHOD_CREATE_MASTER_KEY
);
336 let key_import_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_MASTER_PUBKEY
)
337 .arg_param(&["path"])
338 .completion_cb("path", tools
::complete_file_name
);
340 let paper_key_cmd_def
= CliCommand
::new(&API_METHOD_PAPER_KEY
)
341 .arg_param(&["path"])
342 .completion_cb("path", tools
::complete_file_name
);
345 .insert("create", key_create_cmd_def
)
346 .insert("create-master-key", key_create_master_key_cmd_def
)
347 .insert("import-master-pubkey", key_import_master_pubkey_cmd_def
)
348 .insert("change-passphrase", key_change_passphrase_cmd_def
)
349 .insert("paper-key", paper_key_cmd_def
)