diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs index e667bbd2..2dfaafde 100644 --- a/src/backup/key_derivation.rs +++ b/src/backup/key_derivation.rs @@ -57,13 +57,44 @@ impl KeyDerivationConfig { #[derive(Deserialize, Serialize, Debug)] pub struct KeyConfig { - kdf: Option, + pub kdf: Option, #[serde(with = "proxmox::tools::serde::date_time_as_rfc3339")] - created: DateTime, + pub created: DateTime, #[serde(with = "proxmox::tools::serde::bytes_as_base64")] - data: Vec, + pub data: Vec, } +pub fn store_key_config( + path: &std::path::Path, + replace: bool, + key_config: KeyConfig, +) -> Result<(), Error> { + + let data = serde_json::to_string(&key_config)?; + + use std::io::Write; + + try_block!({ + if replace { + let mode = nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR; + crate::tools::file_set_contents(&path, data.as_bytes(), Some(mode))?; + } else { + use std::os::unix::fs::OpenOptionsExt; + + let mut file = std::fs::OpenOptions::new() + .write(true) + .mode(0o0600) + .create_new(true) + .open(&path)?; + + file.write_all(data.as_bytes())?; + } + + Ok(()) + }).map_err(|err: Error| format_err!("Unable to create file {:?} - {}", path, err))?; + + Ok(()) +} pub fn store_key_with_passphrase( path: &std::path::Path, @@ -104,37 +135,11 @@ pub fn store_key_with_passphrase( let created = Local.timestamp(Local::now().timestamp(), 0); - - let key_config = KeyConfig { + store_key_config(path, replace, KeyConfig { kdf: Some(kdf), created, data: enc_data, - }; - - let data = serde_json::to_string(&key_config)?; - - use std::io::Write; - - try_block!({ - if replace { - let mode = nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR; - crate::tools::file_set_contents(&path, data.as_bytes(), Some(mode))?; - } else { - use std::os::unix::fs::OpenOptionsExt; - - let mut file = std::fs::OpenOptions::new() - .write(true) - .mode(0o0600) - .create_new(true) - .open(&path)?; - - file.write_all(data.as_bytes())?; - } - - Ok(()) - }).map_err(|err: Error| format_err!("Unable to create file {:?} - {}", path, err))?; - - Ok(()) + }) } pub fn load_and_decrtypt_key(path: &std::path::Path, passphrase: fn() -> Result, Error>) -> Result, Error> { diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index b0a7e70d..8cd2bf5f 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -812,18 +812,34 @@ fn key_create( let path = tools::required_string_param(¶m, "path")?; let path = PathBuf::from(path); - // always read from tty - if !crate::tools::tty::stdin_isatty() { - bail!("unable to read passphrase - no tty"); - } - - let password = crate::tools::tty::read_password("Encryption Key Password: ")?; + let kdf = param["kdf"].as_str().unwrap_or("scrypt"); let key = proxmox::sys::linux::random_data(32)?; - store_key_with_passphrase(&path, &key, &password, false)?; + if kdf == "scrypt" { + // always read passphrase from tty + if !crate::tools::tty::stdin_isatty() { + bail!("unable to read passphrase - no tty"); + } - Ok(Value::Null) + let password = crate::tools::tty::read_password("Encryption Key Password: ")?; + + store_key_with_passphrase(&path, &key, &password, false)?; + + Ok(Value::Null) + } else if kdf == "none" { + let created = Local.timestamp(Local::now().timestamp(), 0); + + store_key_config(&path, false, KeyConfig { + kdf: None, + created, + data: key, + })?; + + Ok(Value::Null) + } else { + unreachable!(); + } } @@ -836,6 +852,8 @@ fn key_change_passphrase( let path = tools::required_string_param(¶m, "path")?; let path = PathBuf::from(path); + let kdf = param["kdf"].as_str().unwrap_or("scrypt"); + // we need a TTY to query the new password if !crate::tools::tty::stdin_isatty() { bail!("unable to change passphrase - no tty"); @@ -843,30 +861,54 @@ fn key_change_passphrase( let key = load_and_decrtypt_key(&path, get_encryption_key_password)?; - let new_pw = String::from_utf8(crate::tools::tty::read_password("New Password: ")?)?; - let verify_pw = String::from_utf8(crate::tools::tty::read_password("Verify Password: ")?)?; + if kdf == "scrypt" { - if new_pw != verify_pw { - bail!("Password verification fail!"); + let new_pw = String::from_utf8(crate::tools::tty::read_password("New Password: ")?)?; + let verify_pw = String::from_utf8(crate::tools::tty::read_password("Verify Password: ")?)?; + + if new_pw != verify_pw { + bail!("Password verification fail!"); + } + + if new_pw.len() < 5 { + bail!("Password is too short!"); + } + + store_key_with_passphrase(&path, &key, new_pw.as_bytes(), true)?; + + Ok(Value::Null) + } else if kdf == "none" { + // fixme: keep original creation time, add modified timestamp ?? + let created = Local.timestamp(Local::now().timestamp(), 0); + + store_key_config(&path, true, KeyConfig { + kdf: None, + created, + data: key, + })?; + + Ok(Value::Null) + } else { + unreachable!(); } - - if new_pw.len() < 5 { - bail!("Password is too short!"); - } - - store_key_with_passphrase(&path, &key, new_pw.as_bytes(), true)?; - - Ok(Value::Null) } fn key_mgmt_cli() -> CliCommandMap { + let kdf_schema: Arc = Arc::new( + StringSchema::new("Key derivation function. Choose 'none' to store the key unecrypted.") + .format(Arc::new(ApiStringFormat::Enum(&["scrypt", "none"]))) + .default("scrypt") + .into() + ); + // fixme: change-passphrase, import, export, list let key_create_cmd_def = CliCommand::new( ApiMethod::new( key_create, ObjectSchema::new("Create a new encryption key.") .required("path", StringSchema::new("File system path.")) + .optional("kdf", kdf_schema.clone()) )) .arg_param(vec!["path"]) .completion_cb("path", tools::complete_file_name); @@ -876,6 +918,7 @@ fn key_mgmt_cli() -> CliCommandMap { key_change_passphrase, ObjectSchema::new("Change the passphrase required to decrypt the key.") .required("path", StringSchema::new("File system path.")) + .optional("kdf", kdf_schema.clone()) )) .arg_param(vec!["path"]) .completion_cb("path", tools::complete_file_name);