use xdg::BaseDirectories;
use pathpatterns::{MatchEntry, MatchType, PatternFlag};
-use proxmox::{sortable, identity};
use proxmox::tools::fs::{file_get_contents, file_get_json, replace_file, CreateOptions, image_size};
-use proxmox::sys::linux::tty;
use proxmox::api::{ApiHandler, ApiMethod, RpcEnvironment};
use proxmox::api::schema::*;
use proxmox::api::cli::*;
use proxmox_backup::pxar::catalog::*;
use proxmox_backup::backup::{
archive_type,
- encrypt_key_with_passphrase,
load_and_decrypt_key,
- store_key_config,
verify_chunk_size,
ArchiveType,
AsyncReadChunk,
FixedChunkStream,
FixedIndexReader,
IndexFile,
- KeyConfig,
MANIFEST_BLOB_NAME,
Shell,
};
let (crypt_config, rsa_encrypted_key) = match keyfile {
None => (None, None),
Some(path) => {
- let (key, created) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
+ let (key, created) = load_and_decrypt_key(&path, &key::get_encryption_key_password)?;
let crypt_config = CryptConfig::new(key)?;
let crypt_config = match keyfile {
None => None,
Some(path) => {
- let (key, _) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
+ let (key, _) = load_and_decrypt_key(&path, &key::get_encryption_key_password)?;
Some(Arc::new(CryptConfig::new(key)?))
}
};
let crypt_config = match keyfile {
None => None,
Some(path) => {
- let (key, _created) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
+ let (key, _created) = load_and_decrypt_key(&path, &key::get_encryption_key_password)?;
let crypt_config = CryptConfig::new(key)?;
Some(Arc::new(crypt_config))
}
result
}
-fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
-
- // fixme: implement other input methods
-
- use std::env::VarError::*;
- match std::env::var("PBS_ENCRYPTION_PASSWORD") {
- Ok(p) => return Ok(p.as_bytes().to_vec()),
- Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
- Err(NotPresent) => {
- // Try another method
- }
- }
-
- // If we're on a TTY, query the user for a password
- if tty::stdin_isatty() {
- return Ok(tty::read_password("Encryption Key Password: ")?);
- }
-
- bail!("no password input mechanism available");
-}
-
-fn key_create(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Value, Error> {
-
- let path = tools::required_string_param(¶m, "path")?;
- let path = PathBuf::from(path);
-
- let kdf = param["kdf"].as_str().unwrap_or("scrypt");
-
- let key = proxmox::sys::linux::random_data(32)?;
-
- if kdf == "scrypt" {
- // always read passphrase from tty
- if !tty::stdin_isatty() {
- bail!("unable to read passphrase - no tty");
- }
-
- let password = tty::read_and_verify_password("Encryption Key Password: ")?;
-
- let key_config = encrypt_key_with_passphrase(&key, &password)?;
-
- store_key_config(&path, false, key_config)?;
-
- Ok(Value::Null)
- } else if kdf == "none" {
- let created = Local.timestamp(Local::now().timestamp(), 0);
-
- store_key_config(&path, false, KeyConfig {
- kdf: None,
- created,
- modified: created,
- data: key,
- })?;
-
- Ok(Value::Null)
- } else {
- unreachable!();
- }
-}
-
fn master_pubkey_path() -> Result<PathBuf, Error> {
let base = BaseDirectories::with_prefix("proxmox-backup")?;
Ok(path)
}
-fn key_import_master_pubkey(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Value, Error> {
-
- let path = tools::required_string_param(¶m, "path")?;
- let path = PathBuf::from(path);
-
- let pem_data = file_get_contents(&path)?;
-
- if let Err(err) = openssl::pkey::PKey::public_key_from_pem(&pem_data) {
- bail!("Unable to decode PEM data - {}", err);
- }
-
- let target_path = master_pubkey_path()?;
-
- replace_file(&target_path, &pem_data, CreateOptions::new())?;
-
- println!("Imported public master key to {:?}", target_path);
-
- Ok(Value::Null)
-}
-
-fn key_create_master_key(
- _param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Value, Error> {
-
- // we need a TTY to query the new password
- if !tty::stdin_isatty() {
- bail!("unable to create master key - no tty");
- }
-
- let rsa = openssl::rsa::Rsa::generate(4096)?;
- let pkey = openssl::pkey::PKey::from_rsa(rsa)?;
-
-
- let password = String::from_utf8(tty::read_and_verify_password("Master Key Password: ")?)?;
-
- let pub_key: Vec<u8> = pkey.public_key_to_pem()?;
- let filename_pub = "master-public.pem";
- println!("Writing public master key to {}", filename_pub);
- replace_file(filename_pub, pub_key.as_slice(), CreateOptions::new())?;
-
- let cipher = openssl::symm::Cipher::aes_256_cbc();
- let priv_key: Vec<u8> = pkey.private_key_to_pem_pkcs8_passphrase(cipher, password.as_bytes())?;
-
- let filename_priv = "master-private.pem";
- println!("Writing private master key to {}", filename_priv);
- replace_file(filename_priv, priv_key.as_slice(), CreateOptions::new())?;
-
- Ok(Value::Null)
-}
-
-fn key_change_passphrase(
- param: Value,
- _info: &ApiMethod,
- _rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Value, Error> {
-
- 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 !tty::stdin_isatty() {
- bail!("unable to change passphrase - no tty");
- }
-
- let (key, created) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
-
- if kdf == "scrypt" {
-
- let password = tty::read_and_verify_password("New Password: ")?;
-
- let mut new_key_config = encrypt_key_with_passphrase(&key, &password)?;
- new_key_config.created = created; // keep original value
-
- store_key_config(&path, true, new_key_config)?;
-
- Ok(Value::Null)
- } else if kdf == "none" {
- let modified = Local.timestamp(Local::now().timestamp(), 0);
-
- store_key_config(&path, true, KeyConfig {
- kdf: None,
- created, // keep original value
- modified,
- data: key.to_vec(),
- })?;
-
- Ok(Value::Null)
- } else {
- unreachable!();
- }
-}
-
-fn key_mgmt_cli() -> CliCommandMap {
-
- const KDF_SCHEMA: Schema =
- StringSchema::new("Key derivation function. Choose 'none' to store the key unecrypted.")
- .format(&ApiStringFormat::Enum(&[
- EnumEntry::new("scrypt", "SCrypt"),
- EnumEntry::new("none", "Do not encrypt the key")]))
- .default("scrypt")
- .schema();
-
- #[sortable]
- const API_METHOD_KEY_CREATE: ApiMethod = ApiMethod::new(
- &ApiHandler::Sync(&key_create),
- &ObjectSchema::new(
- "Create a new encryption key.",
- &sorted!([
- ("path", false, &StringSchema::new("File system path.").schema()),
- ("kdf", true, &KDF_SCHEMA),
- ]),
- )
- );
-
- let key_create_cmd_def = CliCommand::new(&API_METHOD_KEY_CREATE)
- .arg_param(&["path"])
- .completion_cb("path", tools::complete_file_name);
-
- #[sortable]
- const API_METHOD_KEY_CHANGE_PASSPHRASE: ApiMethod = ApiMethod::new(
- &ApiHandler::Sync(&key_change_passphrase),
- &ObjectSchema::new(
- "Change the passphrase required to decrypt the key.",
- &sorted!([
- ("path", false, &StringSchema::new("File system path.").schema()),
- ("kdf", true, &KDF_SCHEMA),
- ]),
- )
- );
-
- let key_change_passphrase_cmd_def = CliCommand::new(&API_METHOD_KEY_CHANGE_PASSPHRASE)
- .arg_param(&["path"])
- .completion_cb("path", tools::complete_file_name);
-
- const API_METHOD_KEY_CREATE_MASTER_KEY: ApiMethod = ApiMethod::new(
- &ApiHandler::Sync(&key_create_master_key),
- &ObjectSchema::new("Create a new 4096 bit RSA master pub/priv key pair.", &[])
- );
-
- let key_create_master_key_cmd_def = CliCommand::new(&API_METHOD_KEY_CREATE_MASTER_KEY);
-
- #[sortable]
- const API_METHOD_KEY_IMPORT_MASTER_PUBKEY: ApiMethod = ApiMethod::new(
- &ApiHandler::Sync(&key_import_master_pubkey),
- &ObjectSchema::new(
- "Import a new RSA public key and use it as master key. The key is expected to be in '.pem' format.",
- &sorted!([ ("path", false, &StringSchema::new("File system path.").schema()) ]),
- )
- );
-
- let key_import_master_pubkey_cmd_def = CliCommand::new(&API_METHOD_KEY_IMPORT_MASTER_PUBKEY)
- .arg_param(&["path"])
- .completion_cb("path", tools::complete_file_name);
-
- CliCommandMap::new()
- .insert("create", key_create_cmd_def)
- .insert("create-master-key", key_create_master_key_cmd_def)
- .insert("import-master-pubkey", key_import_master_pubkey_cmd_def)
- .insert("change-passphrase", key_change_passphrase_cmd_def)
-}
-
-
use proxmox_backup::client::RemoteChunkReader;
/// This is a workaround until we have cleaned up the chunk/reader/... infrastructure for better
/// async use!
.insert("snapshots", snapshots_cmd_def)
.insert("files", files_cmd_def)
.insert("status", status_cmd_def)
- .insert("key", key_mgmt_cli())
+ .insert("key", key::cli())
.insert("mount", mount_cmd_def())
.insert("catalog", catalog_mgmt_cli())
.insert("task", task_mgmt_cli())
--- /dev/null
+use std::path::PathBuf;
+
+use anyhow::{bail, Error};
+use chrono::{Local, TimeZone};
+use serde::{Deserialize, Serialize};
+use xdg::BaseDirectories;
+
+use proxmox::api::api;
+use proxmox::api::cli::{CliCommand, CliCommandMap};
+use proxmox::sys::linux::tty;
+use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
+
+use proxmox_backup::backup::{
+ encrypt_key_with_passphrase, load_and_decrypt_key, store_key_config, KeyConfig,
+};
+use proxmox_backup::tools;
+
+pub fn master_pubkey_path() -> Result<PathBuf, Error> {
+ let base = BaseDirectories::with_prefix("proxmox-backup")?;
+
+ // usually $HOME/.config/proxmox-backup/master-public.pem
+ let path = base.place_config_file("master-public.pem")?;
+
+ Ok(path)
+}
+
+pub fn default_encryption_key_path() -> Result<PathBuf, Error> {
+ let base = BaseDirectories::with_prefix("proxmox-backup")?;
+
+ // usually $HOME/.config/proxmox-backup/encryption-key.json
+ let path = base.place_config_file("encryption-key.json")?;
+
+ Ok(path)
+}
+
+pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
+ // fixme: implement other input methods
+
+ use std::env::VarError::*;
+ match std::env::var("PBS_ENCRYPTION_PASSWORD") {
+ Ok(p) => return Ok(p.as_bytes().to_vec()),
+ Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
+ Err(NotPresent) => {
+ // Try another method
+ }
+ }
+
+ // If we're on a TTY, query the user for a password
+ if tty::stdin_isatty() {
+ return Ok(tty::read_password("Encryption Key Password: ")?);
+ }
+
+ bail!("no password input mechanism available");
+}
+
+#[api(
+ default: "scrypt",
+)]
+#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+/// Key derivation function for password protected encryption keys.
+pub enum Kdf {
+ /// Do not encrypt the key.
+ None,
+
+ /// Encrypt they key with a password using SCrypt.
+ Scrypt,
+}
+
+impl Default for Kdf {
+ #[inline]
+ fn default() -> Self {
+ Kdf::Scrypt
+ }
+}
+
+#[api(
+ input: {
+ properties: {
+ kdf: {
+ type: Kdf,
+ optional: true,
+ },
+ path: {
+ description:
+ "Output file. Without this the key will become the new default encryption key.",
+ optional: true,
+ }
+ },
+ },
+)]
+/// Create a new encryption key.
+fn create(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error> {
+ let path = match path {
+ Some(path) => PathBuf::from(path),
+ None => default_encryption_key_path()?,
+ };
+
+ let kdf = kdf.unwrap_or_default();
+
+ let key = proxmox::sys::linux::random_data(32)?;
+
+ match kdf {
+ Kdf::None => {
+ let created = Local.timestamp(Local::now().timestamp(), 0);
+
+ store_key_config(
+ &path,
+ false,
+ KeyConfig {
+ kdf: None,
+ created,
+ modified: created,
+ data: key,
+ },
+ )?;
+ }
+ Kdf::Scrypt => {
+ // always read passphrase from tty
+ if !tty::stdin_isatty() {
+ bail!("unable to read passphrase - no tty");
+ }
+
+ let password = tty::read_and_verify_password("Encryption Key Password: ")?;
+
+ let key_config = encrypt_key_with_passphrase(&key, &password)?;
+
+ store_key_config(&path, false, key_config)?;
+ }
+ }
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ kdf: {
+ type: Kdf,
+ optional: true,
+ },
+ path: {
+ description: "Key file. Without this the default key's password will be changed.",
+ optional: true,
+ }
+ },
+ },
+)]
+/// Change the encryption key's password.
+fn change_passphrase(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error> {
+ let path = match path {
+ Some(path) => PathBuf::from(path),
+ None => default_encryption_key_path()?,
+ };
+
+ let kdf = kdf.unwrap_or_default();
+
+ if !tty::stdin_isatty() {
+ bail!("unable to change passphrase - no tty");
+ }
+
+ let (key, created) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
+
+ match kdf {
+ Kdf::None => {
+ let modified = Local.timestamp(Local::now().timestamp(), 0);
+
+ store_key_config(
+ &path,
+ true,
+ KeyConfig {
+ kdf: None,
+ created, // keep original value
+ modified,
+ data: key.to_vec(),
+ },
+ )?;
+ }
+ Kdf::Scrypt => {
+ let password = tty::read_and_verify_password("New Password: ")?;
+
+ let mut new_key_config = encrypt_key_with_passphrase(&key, &password)?;
+ new_key_config.created = created; // keep original value
+
+ store_key_config(&path, true, new_key_config)?;
+ }
+ }
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ path: {
+ description: "Path to the PEM formatted RSA public key.",
+ },
+ },
+ },
+)]
+/// Import an RSA public key used to put an encrypted version of the symmetric backup encryption
+/// key onto the backup server along with each backup.
+fn import_master_pubkey(path: String) -> Result<(), Error> {
+ let pem_data = file_get_contents(&path)?;
+
+ if let Err(err) = openssl::pkey::PKey::public_key_from_pem(&pem_data) {
+ bail!("Unable to decode PEM data - {}", err);
+ }
+
+ let target_path = master_pubkey_path()?;
+
+ replace_file(&target_path, &pem_data, CreateOptions::new())?;
+
+ println!("Imported public master key to {:?}", target_path);
+
+ Ok(())
+}
+
+#[api]
+/// Create an RSA public/private key pair used to put an encrypted version of the symmetric backup
+/// encryption key onto the backup server along with each backup.
+fn create_master_key() -> Result<(), Error> {
+ // we need a TTY to query the new password
+ if !tty::stdin_isatty() {
+ bail!("unable to create master key - no tty");
+ }
+
+ let rsa = openssl::rsa::Rsa::generate(4096)?;
+ let pkey = openssl::pkey::PKey::from_rsa(rsa)?;
+
+ let password = String::from_utf8(tty::read_and_verify_password("Master Key Password: ")?)?;
+
+ let pub_key: Vec<u8> = pkey.public_key_to_pem()?;
+ let filename_pub = "master-public.pem";
+ println!("Writing public master key to {}", filename_pub);
+ replace_file(filename_pub, pub_key.as_slice(), CreateOptions::new())?;
+
+ let cipher = openssl::symm::Cipher::aes_256_cbc();
+ let priv_key: Vec<u8> = pkey.private_key_to_pem_pkcs8_passphrase(cipher, password.as_bytes())?;
+
+ let filename_priv = "master-private.pem";
+ println!("Writing private master key to {}", filename_priv);
+ replace_file(filename_priv, priv_key.as_slice(), CreateOptions::new())?;
+
+ Ok(())
+}
+
+pub fn cli() -> CliCommandMap {
+ let key_create_cmd_def = CliCommand::new(&API_METHOD_CREATE)
+ .arg_param(&["path"])
+ .completion_cb("path", tools::complete_file_name);
+
+ let key_change_passphrase_cmd_def = CliCommand::new(&API_METHOD_CHANGE_PASSPHRASE)
+ .arg_param(&["path"])
+ .completion_cb("path", tools::complete_file_name);
+
+ let key_create_master_key_cmd_def = CliCommand::new(&API_METHOD_CREATE_MASTER_KEY);
+ let key_import_master_pubkey_cmd_def = CliCommand::new(&API_METHOD_IMPORT_MASTER_PUBKEY)
+ .arg_param(&["path"])
+ .completion_cb("path", tools::complete_file_name);
+
+ CliCommandMap::new()
+ .insert("create", key_create_cmd_def)
+ .insert("create-master-key", key_create_master_key_cmd_def)
+ .insert("import-master-pubkey", key_import_master_pubkey_cmd_def)
+ .insert("change-passphrase", key_change_passphrase_cmd_def)
+}