From 9a045790edb1e53552a75f7eca294490aa1eb581 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Tue, 19 Jan 2021 17:55:27 +0100 Subject: [PATCH] cleanup KeyConfig --- src/api2/tape/drive.rs | 3 +- src/backup/key_derivation.rs | 315 ++++++++++++++------------- src/bin/proxmox_backup_client/key.rs | 85 +++----- src/config/tape_encryption_keys.rs | 7 +- 4 files changed, 198 insertions(+), 212 deletions(-) diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 2bef55d9..5eb8b1cf 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -24,7 +24,6 @@ use crate::{ self, drive::check_drive_exists, }, - backup::decrypt_key_config, api2::{ types::{ UPID_SCHEMA, @@ -485,7 +484,7 @@ pub async fn restore_key( if let Some(key_config) = key_config { let password_fn = || { Ok(password.as_bytes().to_vec()) }; - let key = match decrypt_key_config(&key_config, &password_fn) { + let key = match key_config.decrypt(&password_fn) { Ok((key, ..)) => key, Err(_) => { match key_config.hint { diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs index 97723457..34fc4a11 100644 --- a/src/backup/key_derivation.rs +++ b/src/backup/key_derivation.rs @@ -3,6 +3,8 @@ use anyhow::{bail, format_err, Context, Error}; use serde::{Deserialize, Serialize}; use crate::backup::{CryptConfig, Fingerprint}; +use std::io::Write; +use std::path::Path; use proxmox::api::api; use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; @@ -99,94 +101,179 @@ pub struct KeyConfig { pub hint: Option, } -pub fn store_key_config( - path: &std::path::Path, - replace: bool, - key_config: KeyConfig, -) -> Result<(), Error> { +impl KeyConfig { - let data = serde_json::to_string(&key_config)?; + pub fn new(passphrase: &[u8], kdf: Kdf) -> Result<([u8;32], Self), Error> { + let mut key = [0u8; 32]; + proxmox::sys::linux::fill_with_random_data(&mut key)?; + let key_config = Self::with_key(&key, passphrase, kdf)?; + Ok((key, key_config)) + } - use std::io::Write; - - try_block!({ - if replace { - let mode = nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR; - replace_file(&path, data.as_bytes(), CreateOptions::new().perm(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())?; + pub fn without_password(raw_key: [u8; 32]) -> Self { + let created = proxmox::tools::time::epoch_i64(); + Self { + kdf: None, + created, + modified: created, + data: raw_key.to_vec(), + fingerprint: None, + hint: None, } + } + + pub fn with_key( + raw_key: &[u8], + passphrase: &[u8], + kdf: Kdf, + ) -> Result { + + if raw_key.len() != 32 { + bail!("got strange key length ({} != 32)", raw_key.len()) + } + + let salt = proxmox::sys::linux::random_data(32)?; + + let kdf = match kdf { + Kdf::Scrypt => KeyDerivationConfig::Scrypt { + n: 65536, + r: 8, + p: 1, + salt, + }, + Kdf::PBKDF2 => KeyDerivationConfig::PBKDF2 { + iter: 65535, + salt, + }, + Kdf::None => { + bail!("No key derivation function specified"); + } + }; + + 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 = proxmox::tools::time::epoch_i64(); + + Ok(Self { + kdf: Some(kdf), + created, + modified: created, + data: enc_data, + fingerprint: None, + hint: None, + }) + } + + /// Loads a KeyConfig from path + pub fn load>(path: P) -> Result { + let keydata = file_get_contents(path)?; + let key_config: KeyConfig = serde_json::from_reader(&keydata[..])?; + Ok(key_config) + } + + pub fn decrypt( + &self, + passphrase: &dyn Fn() -> Result, Error>, + ) -> Result<([u8;32], i64, Fingerprint), Error> { + + let raw_data = &self.data; + + let key = if let Some(ref kdf) = self.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(); + + openssl::symm::decrypt_aead( + cipher, + &derived_key, + Some(&iv), + b"", + &enc_data, + &tag, + ).map_err(|err| format_err!("Unable to decrypt key (wrong password?) - {}", err))? + + } else { + raw_data.clone() + }; + + let mut result = [0u8; 32]; + result.copy_from_slice(&key); + + let crypt_config = CryptConfig::new(result.clone())?; + let fingerprint = crypt_config.fingerprint(); + if let Some(ref stored_fingerprint) = self.fingerprint { + if &fingerprint != stored_fingerprint { + bail!( + "KeyConfig contains wrong fingerprint {}, contained key has fingerprint {}", + stored_fingerprint, fingerprint + ); + } + } + + Ok((result, self.created, fingerprint)) + } + + pub fn store>(&self, path: P, replace: bool) -> Result<(), Error> { + + let path: &Path = path.as_ref(); + + let data = serde_json::to_string(self)?; + + try_block!({ + if replace { + let mode = nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR; + replace_file(path, data.as_bytes(), CreateOptions::new().perm(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 store key file {:?} - {}", path, err))?; Ok(()) - }).map_err(|err: Error| format_err!("Unable to create file {:?} - {}", path, err))?; - - Ok(()) + } } -pub fn encrypt_key_with_passphrase( - raw_key: &[u8], - passphrase: &[u8], - kdf: Kdf, -) -> Result { - - let salt = proxmox::sys::linux::random_data(32)?; - - let kdf = match kdf { - Kdf::Scrypt => KeyDerivationConfig::Scrypt { - n: 65536, - r: 8, - p: 1, - salt, - }, - Kdf::PBKDF2 => KeyDerivationConfig::PBKDF2 { - iter: 65535, - salt, - }, - Kdf::None => { - bail!("No key derivation function specified"); - } - }; - - 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 = proxmox::tools::time::epoch_i64(); - - Ok(KeyConfig { - kdf: Some(kdf), - created, - modified: created, - data: enc_data, - fingerprint: None, - hint: None, - }) -} pub fn load_and_decrypt_key( path: &std::path::Path, @@ -196,76 +283,12 @@ pub fn load_and_decrypt_key( .with_context(|| format!("failed to load decryption key from {:?}", path)) } -/// Loads a KeyConfig from path -pub fn load_key_config( - path: &std::path::Path, -) -> Result { - let keydata = file_get_contents(&path)?; - let key_config: KeyConfig = serde_json::from_reader(&keydata[..])?; - Ok(key_config) -} - -pub fn decrypt_key_config( - key_config: &KeyConfig, - passphrase: &dyn Fn() -> Result, Error>, -) -> Result<([u8;32], i64, Fingerprint), Error> { - - let raw_data = &key_config.data; - - let key = if let Some(ref 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(); - - openssl::symm::decrypt_aead( - cipher, - &derived_key, - Some(&iv), - b"", //?? - &enc_data, - &tag, - ).map_err(|err| format_err!("Unable to decrypt key (wrong password?) - {}", err))? - - } else { - raw_data.clone() - }; - - let mut result = [0u8; 32]; - result.copy_from_slice(&key); - - let crypt_config = CryptConfig::new(result.clone())?; - let fingerprint = crypt_config.fingerprint(); - if let Some(ref stored_fingerprint) = key_config.fingerprint { - if &fingerprint != stored_fingerprint { - bail!( - "KeyConfig contains wrong fingerprint {}, contained key has fingerprint {}", - stored_fingerprint, fingerprint - ); - } - } - - Ok((result, key_config.created, fingerprint)) -} - pub fn decrypt_key( mut keydata: &[u8], passphrase: &dyn Fn() -> Result, Error>, ) -> Result<([u8;32], i64, Fingerprint), Error> { let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?; - decrypt_key_config(&key_config, passphrase) + key_config.decrypt(passphrase) } pub fn rsa_encrypt_key_config( diff --git a/src/bin/proxmox_backup_client/key.rs b/src/bin/proxmox_backup_client/key.rs index 8618a6ec..8dfedea0 100644 --- a/src/bin/proxmox_backup_client/key.rs +++ b/src/bin/proxmox_backup_client/key.rs @@ -24,11 +24,7 @@ use proxmox_backup::{ PASSWORD_HINT_SCHEMA, }, backup::{ - encrypt_key_with_passphrase, - load_key_config, - decrypt_key_config, rsa_decrypt_key_config, - store_key_config, CryptConfig, Kdf, KeyConfig, @@ -129,31 +125,20 @@ fn create( let kdf = kdf.unwrap_or_default(); - let mut key_array = [0u8; 32]; - proxmox::sys::linux::fill_with_random_data(&mut key_array)?; - let crypt_config = CryptConfig::new(key_array.clone())?; - let key = key_array.to_vec(); + let mut key = [0u8; 32]; + proxmox::sys::linux::fill_with_random_data(&mut key)?; + let crypt_config = CryptConfig::new(key.clone())?; match kdf { Kdf::None => { - let created = proxmox::tools::time::epoch_i64(); - if hint.is_some() { bail!("password hint not allowed for Kdf::None"); } - store_key_config( - &path, - false, - KeyConfig { - kdf: None, - created, - modified: created, - data: key, - fingerprint: Some(crypt_config.fingerprint()), - hint: None, - }, - )?; + let mut key_config = KeyConfig::without_password(key); + key_config.fingerprint = Some(crypt_config.fingerprint()); + + key_config.store(path, false)?; } Kdf::Scrypt | Kdf::PBKDF2 => { // always read passphrase from tty @@ -163,11 +148,11 @@ fn create( let password = tty::read_and_verify_password("Encryption Key Password: ")?; - let mut key_config = encrypt_key_with_passphrase(&key, &password, kdf)?; + let mut key_config = KeyConfig::with_key(&key, &password, kdf)?; key_config.fingerprint = Some(crypt_config.fingerprint()); key_config.hint = hint; - store_key_config(&path, false, key_config)?; + key_config.store(&path, false)?; } } @@ -235,34 +220,26 @@ async fn import_with_master_key( let kdf = kdf.unwrap_or_default(); match kdf { Kdf::None => { - let modified = proxmox::tools::time::epoch_i64(); - if hint.is_some() { bail!("password hint not allowed for Kdf::None"); } - store_key_config( - &path, - true, - KeyConfig { - kdf: None, - created, // keep original value - modified, - data: key.to_vec(), - fingerprint: Some(fingerprint), - hint: None, - }, - )?; + let mut key_config = KeyConfig::without_password(key); + key_config.created = created; // keep original value + key_config.fingerprint = Some(fingerprint); + + key_config.store(path, true)?; + } Kdf::Scrypt | Kdf::PBKDF2 => { let password = tty::read_and_verify_password("New Password: ")?; - let mut new_key_config = encrypt_key_with_passphrase(&key, &password, kdf)?; + let mut new_key_config = KeyConfig::with_key(&key, &password, kdf)?; new_key_config.created = created; // keep original value new_key_config.fingerprint = Some(fingerprint); new_key_config.hint = hint; - store_key_config(&path, true, new_key_config)?; + new_key_config.store(path, true)?; } } @@ -311,38 +288,30 @@ fn change_passphrase( bail!("unable to change passphrase - no tty"); } - let key_config = load_key_config(&path)?; - let (key, created, fingerprint) = decrypt_key_config(&key_config, &get_encryption_key_password)?; + let key_config = KeyConfig::load(&path)?; + let (key, created, fingerprint) = key_config.decrypt(&get_encryption_key_password)?; match kdf { Kdf::None => { - let modified = proxmox::tools::time::epoch_i64(); - if hint.is_some() { bail!("password hint not allowed for Kdf::None"); } - store_key_config( - &path, - true, - KeyConfig { - kdf: None, - created, // keep original value - modified, - data: key.to_vec(), - fingerprint: Some(fingerprint), - hint: None, - }, - )?; + let mut key_config = KeyConfig::without_password(key); + key_config.created = created; // keep original value + key_config.fingerprint = Some(fingerprint); + + key_config.store(&path, true)?; } Kdf::Scrypt | Kdf::PBKDF2 => { let password = tty::read_and_verify_password("New Password: ")?; - let mut new_key_config = encrypt_key_with_passphrase(&key, &password, kdf)?; + let mut new_key_config = KeyConfig::with_key(&key, &password, kdf)?; new_key_config.created = created; // keep original value new_key_config.fingerprint = Some(fingerprint); new_key_config.hint = hint; - store_key_config(&path, true, new_key_config)?; + + new_key_config.store(&path, true)?; } } diff --git a/src/config/tape_encryption_keys.rs b/src/config/tape_encryption_keys.rs index 13f6961c..42906fc2 100644 --- a/src/config/tape_encryption_keys.rs +++ b/src/config/tape_encryption_keys.rs @@ -16,7 +16,6 @@ use crate::{ Kdf, KeyConfig, CryptConfig, - encrypt_key_with_passphrase, }, }; @@ -59,11 +58,7 @@ pub fn compute_tape_key_fingerprint(key: &[u8; 32]) -> Result Result<([u8; 32], KeyConfig), Error> { - - let mut key = [0u8; 32]; - proxmox::sys::linux::fill_with_random_data(&mut key)?; - - let mut key_config = encrypt_key_with_passphrase(&key, password, Kdf::Scrypt)?; + let (key, mut key_config) = KeyConfig::new(password, Kdf::Scrypt)?; key_config.fingerprint = Some(compute_tape_key_fingerprint(&key)?); Ok((key, key_config)) }