diff --git a/src/backup.rs b/src/backup.rs index 5707294a..e936ab3d 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -130,6 +130,9 @@ pub static DYNAMIC_SIZED_CHUNK_INDEX_1_0: [u8; 8] = [28, 145, 78, 165, 25, 186, mod crypt_setup; pub use crypt_setup::*; +mod key_derivation; +pub use key_derivation::*; + mod data_chunk; pub use data_chunk::*; diff --git a/src/backup/crypt_setup.rs b/src/backup/crypt_setup.rs index 0f22ec8b..cbc83f07 100644 --- a/src/backup/crypt_setup.rs +++ b/src/backup/crypt_setup.rs @@ -8,7 +8,7 @@ //! for a short introduction. use failure::*; use proxmox::tools; -use openssl::pkcs5::{pbkdf2_hmac, scrypt}; +use openssl::pkcs5::pbkdf2_hmac; use openssl::hash::MessageDigest; use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode}; use std::io::Write; @@ -26,13 +26,6 @@ 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. @@ -53,22 +46,6 @@ impl CryptConfig { Ok(Self { id_key, enc_key, cipher: Cipher::aes_256_gcm() }) } - /// A simple key derivation function using scrypt - 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 128*r*n*p - - scrypt( - password, - &scrypt_config.salt, - scrypt_config.n, scrypt_config.r, scrypt_config.p, 1025*1024*1024, - &mut key)?; - - Ok(key) - } - /// Compute a chunk digest using a secret name space. /// /// Computes an SHA256 checksum over some secret data (derived diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs new file mode 100644 index 00000000..e667bbd2 --- /dev/null +++ b/src/backup/key_derivation.rs @@ -0,0 +1,180 @@ +use failure::*; + +use serde::{Deserialize, Serialize}; +use chrono::{Local, TimeZone, DateTime}; + +#[derive(Deserialize, Serialize, Debug)] +pub enum KeyDerivationConfig { + Scrypt { + n: u64, + r: u64, + p: u64, + #[serde(with = "proxmox::tools::serde::bytes_as_base64")] + salt: Vec, + }, + PBKDF2 { + iter: usize, + #[serde(with = "proxmox::tools::serde::bytes_as_base64")] + salt: Vec, + }, +} + +impl KeyDerivationConfig { + + /// Derive a key from provided passphrase + pub fn derive_key(&self, passphrase: &[u8]) -> Result<[u8; 32], Error> { + + let mut key = [0u8; 32]; + + match self { + KeyDerivationConfig::Scrypt { n, r, p, salt } => { + // estimated scrypt memory usage is 128*r*n*p + openssl::pkcs5::scrypt( + passphrase, + &salt, + *n, *r, *p, + 1025*1024*1024, + &mut key, + )?; + + Ok(key) + } + KeyDerivationConfig::PBKDF2 { iter, salt } => { + + openssl::pkcs5::pbkdf2_hmac( + passphrase, + &salt, + *iter, + openssl::hash::MessageDigest::sha256(), + &mut key, + )?; + + Ok(key) + } + } + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct KeyConfig { + kdf: Option, + #[serde(with = "proxmox::tools::serde::date_time_as_rfc3339")] + created: DateTime, + #[serde(with = "proxmox::tools::serde::bytes_as_base64")] + data: Vec, + } + + +pub fn store_key_with_passphrase( + path: &std::path::Path, + raw_key: &[u8], + passphrase: &[u8], + replace: bool, +) -> Result<(), Error> { + + let salt = proxmox::sys::linux::random_data(32)?; + + let kdf = KeyDerivationConfig::Scrypt { + n: 65536, + r: 8, + p: 1, + salt, + }; + + let derived_key = kdf.derive_key(passphrase)?; + + 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 mut enc_data = vec![]; + enc_data.extend_from_slice(&iv); + enc_data.extend_from_slice(&tag); + enc_data.extend_from_slice(&encrypted_key); + + let created = Local.timestamp(Local::now().timestamp(), 0); + + + let key_config = 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> { + + let raw = crate::tools::file_get_contents(&path)?; + let data = String::from_utf8(raw)?; + + let key_config: KeyConfig = serde_json::from_str(&data)?; + + let raw_data = key_config.data; + + if let Some(kdf) = key_config.kdf { + + let passphrase = passphrase()?; + if passphrase.len() < 5 { + bail!("Passphrase is too short!"); + } + + let derived_key = kdf.derive_key(&passphrase)?; + + if raw_data.len() < 32 { + bail!("Unable to encode key - short data"); + } + let iv = &raw_data[0..16]; + let tag = &raw_data[16..32]; + let enc_data = &raw_data[32..]; + + let cipher = openssl::symm::Cipher::aes_256_gcm(); + + let decr_data = openssl::symm::decrypt_aead( + cipher, + &derived_key, + Some(&iv), + b"", //?? + &enc_data, + &tag, + ).map_err(|err| format_err!("Unable to decrypt key - {}", err))?; + + Ok(decr_data) + } else { + Ok(raw_data) + } +} diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 1556affa..64fb4ab4 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1,4 +1,4 @@ -#[macro_use] +//#[macro_use] extern crate proxmox_backup; use failure::*; @@ -782,13 +782,13 @@ fn complete_chunk_size(_arg: &str, _param: &HashMap) -> Vec Result { +fn get_encryption_key_password() -> Result, Error> { // fixme: implement other input methods use std::env::VarError::*; match std::env::var("PBS_ENCRYPTION_PASSWORD") { - Ok(p) => return Ok(p), + Ok(p) => return Ok(p.as_bytes().to_vec()), Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"), Err(NotPresent) => { // Try another method @@ -797,74 +797,12 @@ fn get_encryption_key_password() -> Result { // 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: ")?)?); + return Ok(crate::tools::tty::read_password("Encryption Key Password: ")?); } bail!("no password input mechanism available"); } -fn key_store_with_password(path: &std::path::Path, raw_key: &[u8], password: &[u8], replace: bool) -> Result<(), Error> { - - 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, &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!({ - 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(()) -} - fn key_create( param: Value, _info: &ApiMethod, @@ -885,82 +823,11 @@ fn key_create( let key = proxmox::sys::linux::random_data(32)?; - key_store_with_password(&path, &key, password.as_bytes(), false)?; + store_key_with_passphrase(&path, &key, &password, false)?; Ok(Value::Null) } -fn key_load_and_decrtypt(path: &std::path::Path) -> Result, Error> { - - let data = crate::tools::file_get_json(&path, None)?; - println!("DATA: {:?}", data); - - match data["kdf"].as_str() { - Some(kdf) => { - if kdf != "scrypt" { - bail!("Unknown key derivation function '{}'", kdf); - } - }, - None => bail!("missing property 'kdf'."), - } - - let salt = match data["salt"].as_str() { - Some(hex) => proxmox::tools::hex_to_bin(hex)?, - None => bail!("missing property 'salt'."), - }; - - let n = match data["n"].as_u64() { - Some(n) => n, - None => bail!("missing property 'n'."), - }; - - let r = match data["r"].as_u64() { - Some(r) => r, - None => bail!("missing property 'r'."), - }; - - let p = match data["p"].as_u64() { - Some(p) => p, - None => bail!("missing property 'p'."), - }; - - let scrypt_config = SCryptConfig { n, r, p, salt }; - - let password = get_encryption_key_password()?; - if password.len() < 5 { - bail!("Password is too short!"); - } - - let derived_key = CryptConfig::derive_key_from_password(password.as_bytes(), &scrypt_config)?; - - let iv = match data["iv"].as_str() { - Some(hex) => proxmox::tools::hex_to_bin(hex)?, - None => bail!("missing property 'iv'."), - }; - - let tag = match data["tag"].as_str() { - Some(hex) => proxmox::tools::hex_to_bin(hex)?, - None => bail!("missing property 'tag'."), - }; - - let data = match data["data"].as_str() { - Some(hex) => proxmox::tools::hex_to_bin(hex)?, - None => bail!("missing property 'data'."), - }; - - let cipher = openssl::symm::Cipher::aes_256_gcm(); - - let decr_data = openssl::symm::decrypt_aead( - cipher, - &derived_key, - Some(&iv), - b"", //?? - &data, - &tag, - ).map_err(|err| format_err!("Unable to decrypt key - {}", err))?; - - Ok(decr_data) -} fn key_change_passphrase( param: Value, @@ -983,7 +850,7 @@ fn key_change_passphrase( bail!("unable to change passphrase - no tty"); } - let key = key_load_and_decrtypt(&path)?; + 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: ")?)?; @@ -996,7 +863,7 @@ fn key_change_passphrase( bail!("Password is too short!"); } - key_store_with_password(&path, &key, new_pw.as_bytes(), true)?; + store_key_with_passphrase(&path, &key, new_pw.as_bytes(), true)?; Ok(Value::Null) }