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 read_optional_default_encryption_key() -> Result
<Option
<Vec
<u8>>, Error
> {
37 find_default_encryption_key()?
38 .map(file_get_contents
)
42 pub fn get_encryption_key_password() -> Result
<Vec
<u8>, Error
> {
43 // fixme: implement other input methods
45 use std
::env
::VarError
::*;
46 match std
::env
::var("PBS_ENCRYPTION_PASSWORD") {
47 Ok(p
) => return Ok(p
.as_bytes().to_vec()),
48 Err(NotUnicode(_
)) => bail
!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
54 // If we're on a TTY, query the user for a password
55 if tty
::stdin_isatty() {
56 return Ok(tty
::read_password("Encryption Key Password: ")?
);
59 bail
!("no password input mechanism available");
65 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
66 #[serde(rename_all = "kebab-case")]
67 /// Key derivation function for password protected encryption keys.
69 /// Do not encrypt the key.
72 /// Encrypt they key with a password using SCrypt.
76 impl Default
for Kdf
{
78 fn default() -> Self {
92 "Output file. Without this the key will become the new default encryption key.",
98 /// Create a new encryption key.
99 fn create(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
100 let path
= match path
{
101 Some(path
) => PathBuf
::from(path
),
102 None
=> place_default_encryption_key()?
,
105 let kdf
= kdf
.unwrap_or_default();
107 let key
= proxmox
::sys
::linux
::random_data(32)?
;
111 let created
= Local
.timestamp(Local
::now().timestamp(), 0);
125 // always read passphrase from tty
126 if !tty
::stdin_isatty() {
127 bail
!("unable to read passphrase - no tty");
130 let password
= tty
::read_and_verify_password("Encryption Key Password: ")?
;
132 let key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
134 store_key_config(&path
, false, key_config
)?
;
149 description
: "Key file. Without this the default key's password will be changed.",
155 /// Change the encryption key's password.
156 fn change_passphrase(kdf
: Option
<Kdf
>, path
: Option
<String
>) -> Result
<(), Error
> {
157 let path
= match path
{
158 Some(path
) => PathBuf
::from(path
),
159 None
=> find_default_encryption_key()?
160 .ok_or_else(|| format_err
!("no encryption file provided and no default file found"))?
,
163 let kdf
= kdf
.unwrap_or_default();
165 if !tty
::stdin_isatty() {
166 bail
!("unable to change passphrase - no tty");
169 let (key
, created
) = load_and_decrypt_key(&path
, &get_encryption_key_password
)?
;
173 let modified
= Local
.timestamp(Local
::now().timestamp(), 0);
180 created
, // keep original value
187 let password
= tty
::read_and_verify_password("New Password: ")?
;
189 let mut new_key_config
= encrypt_key_with_passphrase(&key
, &password
)?
;
190 new_key_config
.created
= created
; // keep original value
192 store_key_config(&path
, true, new_key_config
)?
;
203 description
: "Path to the PEM formatted RSA public key.",
208 /// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
209 /// key onto the backup server along with each backup.
210 fn import_master_pubkey(path
: String
) -> Result
<(), Error
> {
211 let pem_data
= file_get_contents(&path
)?
;
213 if let Err(err
) = openssl
::pkey
::PKey
::public_key_from_pem(&pem_data
) {
214 bail
!("Unable to decode PEM data - {}", err
);
217 let target_path
= place_master_pubkey()?
;
219 replace_file(&target_path
, &pem_data
, CreateOptions
::new())?
;
221 println
!("Imported public master key to {:?}", target_path
);
227 /// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
228 /// encryption key onto the backup server along with each backup.
229 fn create_master_key() -> Result
<(), Error
> {
230 // we need a TTY to query the new password
231 if !tty
::stdin_isatty() {
232 bail
!("unable to create master key - no tty");
235 let rsa
= openssl
::rsa
::Rsa
::generate(4096)?
;
236 let pkey
= openssl
::pkey
::PKey
::from_rsa(rsa
)?
;
238 let password
= String
::from_utf8(tty
::read_and_verify_password("Master Key Password: ")?
)?
;
240 let pub_key
: Vec
<u8> = pkey
.public_key_to_pem()?
;
241 let filename_pub
= "master-public.pem";
242 println
!("Writing public master key to {}", filename_pub
);
243 replace_file(filename_pub
, pub_key
.as_slice(), CreateOptions
::new())?
;
245 let cipher
= openssl
::symm
::Cipher
::aes_256_cbc();
246 let priv_key
: Vec
<u8> = pkey
.private_key_to_pem_pkcs8_passphrase(cipher
, password
.as_bytes())?
;
248 let filename_priv
= "master-private.pem";
249 println
!("Writing private master key to {}", filename_priv
);
250 replace_file(filename_priv
, priv_key
.as_slice(), CreateOptions
::new())?
;
255 pub fn cli() -> CliCommandMap
{
256 let key_create_cmd_def
= CliCommand
::new(&API_METHOD_CREATE
)
257 .arg_param(&["path"])
258 .completion_cb("path", tools
::complete_file_name
);
260 let key_change_passphrase_cmd_def
= CliCommand
::new(&API_METHOD_CHANGE_PASSPHRASE
)
261 .arg_param(&["path"])
262 .completion_cb("path", tools
::complete_file_name
);
264 let key_create_master_key_cmd_def
= CliCommand
::new(&API_METHOD_CREATE_MASTER_KEY
);
265 let key_import_master_pubkey_cmd_def
= CliCommand
::new(&API_METHOD_IMPORT_MASTER_PUBKEY
)
266 .arg_param(&["path"])
267 .completion_cb("path", tools
::complete_file_name
);
270 .insert("create", key_create_cmd_def
)
271 .insert("create-master-key", key_create_master_key_cmd_def
)
272 .insert("import-master-pubkey", key_import_master_pubkey_cmd_def
)
273 .insert("change-passphrase", key_change_passphrase_cmd_def
)