]> git.proxmox.com Git - proxmox-backup.git/commitdiff
src/backup/crypt_setup.rs: crypto helpers
authorDietmar Maurer <dietmar@proxmox.com>
Sat, 8 Jun 2019 07:51:49 +0000 (09:51 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Sat, 8 Jun 2019 07:56:51 +0000 (09:56 +0200)
src/backup.rs
src/backup/crypt_setup.rs [new file with mode: 0644]

index e6aa8e2aea66f7c21315c25706f5865b3d0b4b18..2c5f105d74a27ba48c3e2d2b9ca5ea8a4aa958e3 100644 (file)
@@ -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 (file)
index 0000000..0f9db27
--- /dev/null
@@ -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<u8>,
+    // 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<Self, Error> {
+
+        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<Self, Error> {
+
+        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<u8>, 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<Vec<u8>, 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)
+    }
+}