1 use std
::path
::PathBuf
;
3 use anyhow
::{bail, format_err, Error}
;
4 use serde
::{Deserialize, Serialize}
;
7 use proxmox
::api
::cli
::{CliCommand, CliCommandMap}
;
8 use proxmox
::sys
::linux
::tty
;
9 use proxmox
::tools
::fs
::{file_get_contents, replace_file, CreateOptions}
;
11 use proxmox_backup
::backup
::{
12 encrypt_key_with_passphrase
, load_and_decrypt_key
, store_key_config
, KeyConfig
,
14 use proxmox_backup
::tools
;
16 pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME
: &str = "encryption-key.json";
17 pub const MASTER_PUBKEY_FILE_NAME
: &str = "master-public.pem";
19 pub fn find_master_pubkey() -> Result
<Option
<PathBuf
>, Error
> {
20 super::find_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
23 pub fn place_master_pubkey() -> Result
<PathBuf
, Error
> {
24 super::place_xdg_file(MASTER_PUBKEY_FILE_NAME
, "main public key file")
27 pub fn find_default_encryption_key() -> Result
<Option
<PathBuf
>, Error
> {
28 super::find_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
31 pub fn place_default_encryption_key() -> Result
<PathBuf
, Error
> {
32 super::place_xdg_file(DEFAULT_ENCRYPTION_KEY_FILE_NAME
, "default encryption key file")
35 pub fn read_optional_default_encryption_key() -> Result
<Option
<Vec
<u8>>, Error
> {
36 find_default_encryption_key()?
37 .map(file_get_contents
)
41 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
42 // fixme: implement other input methods
44 use std
::env
::VarError
::*;
45 match std
::env
::var("PBS_ENCRYPTION_PASSWORD") {
46 Ok(p
) => return Ok(p
.as_bytes().to_vec()),
47 Err(NotUnicode(_
)) => bail
!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
53 // If we're on a TTY, query the user for a password
54 if tty
::stdin_isatty() {
55 return Ok(tty
::read_password("Encryption Key Password: ")?
);
58 bail
!("no password input mechanism available");
64 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
65 #[serde(rename_all = "kebab-case")]
66 /// Key derivation function for password protected encryption keys.
68 /// Do not encrypt the key.
71 /// Encrypt they key with a password using SCrypt.
75 impl Default
for Kdf
{
77 fn default() -> Self {
91 "Output file. Without this the key will become the new default encryption key.",
97 /// Create a new encryption key.
98 fn create(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
99 let path
= match path
{
100 Some(path
) => PathBuf
::from(path
),
102 let path
= place_default_encryption_key()?
;
103 println
!("creating default key at: {:?}", path
);
108 let kdf
= kdf
.unwrap_or_default();
110 let key
= proxmox
::sys
::linux
::random_data(32)?
;
114 let created
= proxmox
::tools
::time
::epoch_i64();
128 // always read passphrase from tty
129 if !tty
::stdin_isatty() {
130 bail
!("unable to read passphrase - no tty");
133 let password
= tty
::read_and_verify_password("Encryption Key Password: ")?
;
135 let key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
137 store_key_config(&path
, false, key_config
)?
;
152 description
: "Key file. Without this the default key's password will be changed.",
158 /// Change the encryption key's password.
159 fn change_passphrase(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
160 let path
= match path
{
161 Some(path
) => PathBuf
::from(path
),
163 let path
= find_default_encryption_key()?
165 format_err
!("no encryption file provided and no default file found")
167 println
!("updating default key at: {:?}", path
);
172 let kdf
= kdf
.unwrap_or_default();
174 if !tty
::stdin_isatty() {
175 bail
!("unable to change passphrase - no tty");
178 let (key
, created
) = load_and_decrypt_key(&path
, &get_encryption_key_password
)?
;
182 let modified
= proxmox
::tools
::time
::epoch_i64();
189 created
, // keep original value
196 let password
= tty
::read_and_verify_password("New Password: ")?
;
198 let mut new_key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
199 new_key_config
.created
= created
; // keep original value
201 store_key_config(&path
, true, new_key_config
)?
;
212 description
: "Path to the PEM formatted RSA public key.",
217 /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
218 /// key onto the backup server along with each backup.
219 fn import_master_pubkey(path
: String
) -> Result
<(), Error
> {
220 let pem_data
= file_get_contents(&path
)?
;
222 if let Err(err
) = openssl
::pkey
::PKey
::public_key_from_pem(&pem_data
) {
223 bail
!("Unable to decode PEM data - {}", err
);
226 let target_path
= place_master_pubkey()?
;
228 replace_file(&target_path
, &pem_data
, CreateOptions
::new())?
;
230 println
!("Imported public master key to {:?}", target_path
);
236 /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
237 /// encryption key onto the backup server along with each backup.
238 fn create_master_key() -> Result
<(), Error
> {
239 // we need a TTY to query the new password
240 if !tty
::stdin_isatty() {
241 bail
!("unable to create master key - no tty");
244 let rsa
= openssl
::rsa
::Rsa
::generate(4096)?
;
245 let pkey
= openssl
::pkey
::PKey
::from_rsa(rsa
)?
;
247 let password
= String
::from_utf8(tty
::read_and_verify_password("Master Key Password: ")?
)?
;
249 let pub_key
: Vec
<u8> = pkey
.public_key_to_pem()?
;
250 let filename_pub
= "master-public.pem";
251 println
!("Writing public master key to {}", filename_pub
);
252 replace_file(filename_pub
, pub_key
.as_slice(), CreateOptions
::new())?
;
254 let cipher
= openssl
::symm
::Cipher
::aes_256_cbc();
255 let priv_key
: Vec
<u8> = pkey
.private_key_to_pem_pkcs8_passphrase(cipher
, password
.as_bytes())?
;
257 let filename_priv
= "master-private.pem";
258 println
!("Writing private master key to {}", filename_priv
);
259 replace_file(filename_priv
, priv_key
.as_slice(), CreateOptions
::new())?
;
264 pub fn cli() -> CliCommandMap
{
265 let key_create_cmd_def
= CliCommand
::new(&API_METHOD_CREATE
)
266 .arg_param(&["path"])
267 .completion_cb("path", tools
::complete_file_name
);
269 let key_change_passphrase_cmd_def
= CliCommand
::new(&API_METHOD_CHANGE_PASSPHRASE
)
270 .arg_param(&["path"])
271 .completion_cb("path", tools
::complete_file_name
);
273 let key_create_master_key_cmd_def
= CliCommand
::new(&API_METHOD_CREATE_MASTER_KEY
);
274 let key_import_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_MASTER_PUBKEY
)
275 .arg_param(&["path"])
276 .completion_cb("path", tools
::complete_file_name
);
279 .insert("create", key_create_cmd_def
)
280 .insert("create-master-key", key_create_master_key_cmd_def
)
281 .insert("import-master-pubkey", key_import_master_pubkey_cmd_def
)
282 .insert("change-passphrase", key_change_passphrase_cmd_def
)