diff --git a/src/backup/crypt_setup.rs b/src/backup/crypt_setup.rs index b2301dc6..0f22ec8b 100644 --- a/src/backup/crypt_setup.rs +++ b/src/backup/crypt_setup.rs @@ -26,6 +26,13 @@ pub struct CryptConfig { enc_key: [u8; 32], } +pub struct SCryptConfig { + pub n: u64, + pub r: u64, + pub p: u64, + pub salt: Vec, +} + impl CryptConfig { /// Create a new instance. @@ -47,34 +54,21 @@ impl CryptConfig { } /// A simple key derivation function using scrypt - fn derive_key_from_password(password: &[u8]) -> Result<[u8; 32], Error> { + pub fn derive_key_from_password(password: &[u8], scrypt_config: &SCryptConfig) -> Result<[u8; 32], Error> { let mut key = [0u8; 32]; - // estimated scrypt memory usage is N*2r*64 - let n = 65536; - let r = 8; - let p = 1; - - let salt = b""; // Salt?? + // estimated scrypt memory usage is 128*r*n*p scrypt( password, - salt, - n, r, p, 128*1024*1024, + &scrypt_config.salt, + scrypt_config.n, scrypt_config.r, scrypt_config.p, 1025*1024*1024, &mut key)?; Ok(key) } - /// Create a new instance, but derive key from password using scrypt. - pub fn with_password(password: &[u8]) -> Result { - - let enc_key = Self::derive_key_from_password(password)?; - - Self::new(enc_key) - } - /// Compute a chunk digest using a secret name space. /// /// Computes an SHA256 checksum over some secret data (derived diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index df30b970..da7b4a6c 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1,3 +1,4 @@ +#[macro_use] extern crate proxmox_backup; use failure::*; @@ -29,6 +30,13 @@ use tokio::sync::mpsc; lazy_static! { static ref BACKUPSPEC_REGEX: Regex = Regex::new(r"^([a-zA-Z0-9_-]+\.(?:pxar|img|conf)):(.+)$").unwrap(); + + static ref REPO_URL_SCHEMA: Arc = Arc::new( + StringSchema::new("Repository URL.") + .format(BACKUP_REPO_URL.clone()) + .max_length(256) + .into() + ); } @@ -774,14 +782,119 @@ fn complete_chunk_size(_arg: &str, _param: &HashMap) -> Vec Result { - let repo_url_schema: Arc = Arc::new( - StringSchema::new("Repository URL.") - .format(BACKUP_REPO_URL.clone()) - .max_length(256) - .into() - ); + // fixme: implement other input methods + + use std::env::VarError::*; + match std::env::var("PBS_ENCRYPTION_PASSWORD") { + Ok(p) => return Ok(p), + 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 crate::tools::tty::stdin_isatty() { + return Ok(String::from_utf8(crate::tools::tty::read_password("Encryption Key Password: ")?)?); + } + + bail!("no password input mechanism available"); +} + +fn key_create( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut dyn RpcEnvironment, +) -> Result { + + let repo_url = tools::required_string_param(¶m, "repository")?; + let repo: BackupRepository = repo_url.parse()?; + + let base = BaseDirectories::with_prefix("proxmox-backup")?; + + let repo = repo.to_string(); + + // usually $HOME/.config/proxmox-backup/xxx.enc_key + let path = base.place_config_file(&format!("{}.enc_key", repo))?; + + let password = get_encryption_key_password()?; + + let raw_key = proxmox::sys::linux::random_data(32)?; + + let salt = proxmox::sys::linux::random_data(32)?; + + let scrypt_config = SCryptConfig { n: 65536, r: 8, p: 1, salt }; + + let derived_key = CryptConfig::derive_key_from_password(password.as_bytes(), &scrypt_config)?; + + let cipher = openssl::symm::Cipher::aes_256_gcm(); + + let iv = proxmox::sys::linux::random_data(16)?; + let mut tag = [0u8; 16]; + + let encrypted_key = openssl::symm::encrypt_aead( + cipher, + &derived_key, + Some(&iv), + b"", + &raw_key, + &mut tag, + )?; + + let created = Local.timestamp(Local::now().timestamp(), 0); + + let result = json!({ + "kdf": "scrypt", + "created": created.to_rfc3339(), + "n": scrypt_config.n, + "r": scrypt_config.r, + "p": scrypt_config.p, + "salt": proxmox::tools::digest_to_hex(&scrypt_config.salt), + "iv": proxmox::tools::digest_to_hex(&iv), + "tag": proxmox::tools::digest_to_hex(&tag), + "data": proxmox::tools::digest_to_hex(&encrypted_key), + }); + + use std::io::Write; + + let data = result.to_string(); + + try_block!({ + let mut file = std::fs::OpenOptions::new() + .write(true) + .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(Value::Null) +} + +fn key_mgmt_cli() -> CliCommandMap { + + // 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("repository", REPO_URL_SCHEMA.clone()) + )) + .arg_param(vec!["repository"]) + .completion_cb("repository", complete_repository); + + let cmd_def = CliCommandMap::new() + .insert("create".to_owned(), key_create_cmd_def.into()); + + cmd_def +} + + +fn main() { let backup_source_schema: Arc = Arc::new( StringSchema::new("Backup source specification ([