diff --git a/src/backup.rs b/src/backup.rs index e6aa8e2a..2c5f105d 100644 --- a/src/backup.rs +++ b/src/backup.rs @@ -107,6 +107,9 @@ macro_rules! PROXMOX_BACKUP_PROTOCOL_ID_V1 { () => { "proxmox-backup-protocol-v1" } } +mod crypt_setup; +pub use crypt_setup::*; + mod chunk_stream; pub use chunk_stream::*; diff --git a/src/backup/crypt_setup.rs b/src/backup/crypt_setup.rs new file mode 100644 index 00000000..0f9db276 --- /dev/null +++ b/src/backup/crypt_setup.rs @@ -0,0 +1,148 @@ +//! Wrappers for OpenSSL crypto functions +//! +//! We use this to encrypt and decryprt data chunks. Cipher is +//! AES_256_GCM, which is fast and provides authenticated encryption. +//! +//! See the Wikipedia Artikel for [Authenticated +//! encryption](https://en.wikipedia.org/wiki/Authenticated_encryption) +//! for a short introduction. +use failure::*; +use proxmox::tools; +use openssl::pkcs5::{pbkdf2_hmac, scrypt}; +use openssl::hash::MessageDigest; +use openssl::symm::{encrypt_aead, decrypt_aead, Cipher}; + +/// Store data required for authenticated enryption +pub struct CryptData { + /// A 16 byte IV + pub iv: [u8; 16], + /// A 16 byte message authentication code (MAC) + pub mac: [u8; 16], +} + +/// Encryption Configuration with secret key +/// +/// This structure stores the secret key and provides helpers for +/// authenticated encryption. +pub struct CryptConfig { + // the Cipher + cipher: Cipher, + // A secrect key use to provide the chunk digest name space. + id_key: Vec, + // The private key used by the cipher. + enc_key: [u8; 32], +} + +impl CryptConfig { + + /// Create a new instance. + /// + /// We compute a derived 32 byte key using pbkdf2_hmac. This second + /// key is used in compute_digest. + pub fn new(enc_key: [u8; 32]) -> Result { + + let mut id_key = tools::vec::undefined(32); + + pbkdf2_hmac( + &enc_key, + b"_id_key", + 10, + MessageDigest::sha256(), + &mut id_key)?; + + Ok(Self { id_key, enc_key, cipher: Cipher::aes_256_gcm() }) + } + + /// A simple key derivation function using scrypt + fn derive_key_from_password(password: &[u8]) -> 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?? + + scrypt( + password, + salt, + n, r, p, 128*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 + /// from the secret key) and the provided data. This ensures that + /// chunk digest values do not clash with values computed for + /// other sectret keys. + pub fn compute_digest(&self, data: &[u8]) -> [u8; 32] { + let mut hasher = openssl::sha::Sha256::new(); + hasher.update(&self.id_key); + hasher.update(data); + let digest = hasher.finish(); + digest + } + + /// Encrypt data using a random 16 byte IV. + /// + /// Return the encrypted data, IV and MAC. + pub fn encrypt(&self, data: &[u8]) -> Result<(Vec, CryptData), Error> { + + let mac = [0u8; 16]; + let mut iv = [0u8; 16]; + + proxmox::sys::linux::fill_with_random_data(&mut iv)?; + + let mut crypt_data = CryptData { mac: mac, iv: iv }; + + let enc_data = encrypt_aead( + self.cipher, + &self.enc_key, + Some(&iv), + b"", // no additional data + &data, + &mut crypt_data.mac)?; + + Ok((enc_data, crypt_data)) + } + + /// Decrypt data, verify authentication. + /// + /// You need to pass the IV and MAC from the entryption step in ``crypt_data``. + pub fn decrypt(&self, data: &[u8], crypt_data: &CryptData) -> Result, Error> { + + let decrypt_result = decrypt_aead( + self.cipher, + &self.enc_key, + Some(&crypt_data.iv), + b"", // no additional data + data, + &crypt_data.mac); + + let raw_data = match decrypt_result { + Ok(data) => data, + Err(err) => { + // for unknown reason, openssl does not return useful errors (just empty array) + if err.errors().len() == 0 { + bail!("unable to decyrpt chunk data"); + } + bail!("unable to decyrpt data - {}", err); + } + }; + + Ok(raw_data) + } +}