1 use std
::path
::PathBuf
;
3 use anyhow
::{bail, format_err, Error}
;
4 use chrono
::{Local, TimeZone}
;
5 use serde
::{Deserialize, Serialize}
;
8 use proxmox
::api
::cli
::{CliCommand, CliCommandMap}
;
9 use proxmox
::sys
::linux
::tty
;
10 use proxmox
::tools
::fs
::{file_get_contents, replace_file, CreateOptions}
;
12 use proxmox_backup
::backup
::{
13 encrypt_key_with_passphrase
, load_and_decrypt_key
, store_key_config
, KeyConfig
,
15 use proxmox_backup
::tools
;
17 pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME
: &str = "encryption-key.json";
18 pub const MASTER_PUBKEY_FILE_NAME
: &str = "master-public.pem";
20 pub fn find_master_pubkey() -> Result
<Option
<PathBuf
>, Error
> {
21 super::find_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
24 pub fn place_master_pubkey() -> Result
<PathBuf
, Error
> {
25 super::place_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
28 pub fn find_default_encryption_key() -> Result
<Option
<PathBuf
>, Error
> {
29 super::find_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
32 pub fn place_default_encryption_key() -> Result
<PathBuf
, Error
> {
33 super::place_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
36 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
37 // fixme: implement other input methods
39 use std
::env
::VarError
::*;
40 match std
::env
::var("PBS_ENCRYPTION_PASSWORD") {
41 Ok(p
) => return Ok(p
.as_bytes().to_vec()),
42 Err(NotUnicode(_
)) => bail
!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
48 // If we're on a TTY, query the user for a password
49 if tty
::stdin_isatty() {
50 return Ok(tty
::read_password("Encryption Key Password: ")?
);
53 bail
!("no password input mechanism available");
59 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
60 #[serde(rename_all = "kebab-case")]
61 /// Key derivation function for password protected encryption keys.
63 /// Do not encrypt the key.
66 /// Encrypt they key with a password using SCrypt.
70 impl Default
for Kdf
{
72 fn default() -> Self {
86 "Output file. Without this the key will become the new default encryption key.",
92 /// Create a new encryption key.
93 fn create(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
94 let path
= match path
{
95 Some(path
) => PathBuf
::from(path
),
96 None
=> place_default_encryption_key()?
,
99 let kdf
= kdf
.unwrap_or_default();
101 let key
= proxmox
::sys
::linux
::random_data(32)?
;
105 let created
= Local
.timestamp(Local
::now().timestamp(), 0);
119 // always read passphrase from tty
120 if !tty
::stdin_isatty() {
121 bail
!("unable to read passphrase - no tty");
124 let password
= tty
::read_and_verify_password("Encryption Key Password: ")?
;
126 let key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
128 store_key_config(&path
, false, key_config
)?
;
143 description
: "Key file. Without this the default key's password will be changed.",
149 /// Change the encryption key's password.
150 fn change_passphrase(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
151 let path
= match path
{
152 Some(path
) => PathBuf
::from(path
),
153 None
=> find_default_encryption_key()?
154 .ok_or_else(|| format_err
!("no encryption file provided and no default file found"))?
,
157 let kdf
= kdf
.unwrap_or_default();
159 if !tty
::stdin_isatty() {
160 bail
!("unable to change passphrase - no tty");
163 let (key
, created
) = load_and_decrypt_key(&path
, &get_encryption_key_password
)?
;
167 let modified
= Local
.timestamp(Local
::now().timestamp(), 0);
174 created
, // keep original value
181 let password
= tty
::read_and_verify_password("New Password: ")?
;
183 let mut new_key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
184 new_key_config
.created
= created
; // keep original value
186 store_key_config(&path
, true, new_key_config
)?
;
197 description
: "Path to the PEM formatted RSA public key.",
202 /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
203 /// key onto the backup server along with each backup.
204 fn import_master_pubkey(path
: String
) -> Result
<(), Error
> {
205 let pem_data
= file_get_contents(&path
)?
;
207 if let Err(err
) = openssl
::pkey
::PKey
::public_key_from_pem(&pem_data
) {
208 bail
!("Unable to decode PEM data - {}", err
);
211 let target_path
= place_master_pubkey()?
;
213 replace_file(&target_path
, &pem_data
, CreateOptions
::new())?
;
215 println
!("Imported public master key to {:?}", target_path
);
221 /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
222 /// encryption key onto the backup server along with each backup.
223 fn create_master_key() -> Result
<(), Error
> {
224 // we need a TTY to query the new password
225 if !tty
::stdin_isatty() {
226 bail
!("unable to create master key - no tty");
229 let rsa
= openssl
::rsa
::Rsa
::generate(4096)?
;
230 let pkey
= openssl
::pkey
::PKey
::from_rsa(rsa
)?
;
232 let password
= String
::from_utf8(tty
::read_and_verify_password("Master Key Password: ")?
)?
;
234 let pub_key
: Vec
<u8> = pkey
.public_key_to_pem()?
;
235 let filename_pub
= "master-public.pem";
236 println
!("Writing public master key to {}", filename_pub
);
237 replace_file(filename_pub
, pub_key
.as_slice(), CreateOptions
::new())?
;
239 let cipher
= openssl
::symm
::Cipher
::aes_256_cbc();
240 let priv_key
: Vec
<u8> = pkey
.private_key_to_pem_pkcs8_passphrase(cipher
, password
.as_bytes())?
;
242 let filename_priv
= "master-private.pem";
243 println
!("Writing private master key to {}", filename_priv
);
244 replace_file(filename_priv
, priv_key
.as_slice(), CreateOptions
::new())?
;
249 pub fn cli() -> CliCommandMap
{
250 let key_create_cmd_def
= CliCommand
::new(&API_METHOD_CREATE
)
251 .arg_param(&["path"])
252 .completion_cb("path", tools
::complete_file_name
);
254 let key_change_passphrase_cmd_def
= CliCommand
::new(&API_METHOD_CHANGE_PASSPHRASE
)
255 .arg_param(&["path"])
256 .completion_cb("path", tools
::complete_file_name
);
258 let key_create_master_key_cmd_def
= CliCommand
::new(&API_METHOD_CREATE_MASTER_KEY
);
259 let key_import_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_MASTER_PUBKEY
)
260 .arg_param(&["path"])
261 .completion_cb("path", tools
::complete_file_name
);
264 .insert("create", key_create_cmd_def
)
265 .insert("create-master-key", key_create_master_key_cmd_def
)
266 .insert("import-master-pubkey", key_import_master_pubkey_cmd_def
)
267 .insert("change-passphrase", key_change_passphrase_cmd_def
)