149 lines
4.2 KiB
Rust
149 lines
4.2 KiB
Rust
|
//! 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)
|
||
|
}
|
||
|
}
|