2020-07-08 08:42:05 +00:00
|
|
|
use anyhow::{bail, format_err, Context, Error};
|
2019-06-18 09:17:22 +00:00
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
2020-11-20 16:38:32 +00:00
|
|
|
use crate::backup::{CryptConfig, Fingerprint};
|
|
|
|
|
2020-11-24 06:19:15 +00:00
|
|
|
use proxmox::api::api;
|
2020-01-21 11:28:01 +00:00
|
|
|
use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
|
|
|
|
use proxmox::try_block;
|
2019-08-03 11:05:38 +00:00
|
|
|
|
2020-11-24 06:19:15 +00:00
|
|
|
#[api(default: "scrypt")]
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
|
|
|
#[serde(rename_all = "lowercase")]
|
|
|
|
/// Key derivation function for password protected encryption keys.
|
|
|
|
pub enum Kdf {
|
|
|
|
/// Do not encrypt the key.
|
|
|
|
None,
|
|
|
|
|
|
|
|
/// Encrypt they key with a password using SCrypt.
|
|
|
|
Scrypt,
|
|
|
|
|
|
|
|
/// Encrtypt the Key with a password using PBKDF2
|
|
|
|
PBKDF2,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Kdf {
|
|
|
|
#[inline]
|
|
|
|
fn default() -> Self {
|
|
|
|
Kdf::Scrypt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-18 09:17:22 +00:00
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
|
|
pub enum KeyDerivationConfig {
|
|
|
|
Scrypt {
|
|
|
|
n: u64,
|
|
|
|
r: u64,
|
|
|
|
p: u64,
|
|
|
|
#[serde(with = "proxmox::tools::serde::bytes_as_base64")]
|
|
|
|
salt: Vec<u8>,
|
|
|
|
},
|
|
|
|
PBKDF2 {
|
|
|
|
iter: usize,
|
|
|
|
#[serde(with = "proxmox::tools::serde::bytes_as_base64")]
|
|
|
|
salt: Vec<u8>,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2019-06-18 16:21:01 +00:00
|
|
|
pub kdf: Option<KeyDerivationConfig>,
|
2020-09-12 13:10:47 +00:00
|
|
|
#[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
|
|
|
|
pub created: i64,
|
|
|
|
#[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
|
|
|
|
pub modified: i64,
|
2019-06-18 09:17:22 +00:00
|
|
|
#[serde(with = "proxmox::tools::serde::bytes_as_base64")]
|
2019-06-18 16:21:01 +00:00
|
|
|
pub data: Vec<u8>,
|
2020-11-20 16:38:32 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
#[serde(default)]
|
|
|
|
pub fingerprint: Option<Fingerprint>,
|
2019-06-18 09:17:22 +00:00
|
|
|
}
|
|
|
|
|
2019-06-18 16:21:01 +00:00
|
|
|
pub fn store_key_config(
|
|
|
|
path: &std::path::Path,
|
|
|
|
replace: bool,
|
|
|
|
key_config: KeyConfig,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
|
|
|
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;
|
2019-12-18 10:05:30 +00:00
|
|
|
replace_file(&path, data.as_bytes(), CreateOptions::new().perm(mode))?;
|
2019-06-18 16:21:01 +00:00
|
|
|
} 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(())
|
|
|
|
}
|
2019-06-18 09:17:22 +00:00
|
|
|
|
2019-06-20 05:58:33 +00:00
|
|
|
pub fn encrypt_key_with_passphrase(
|
2019-06-18 09:17:22 +00:00
|
|
|
raw_key: &[u8],
|
|
|
|
passphrase: &[u8],
|
2020-11-24 06:19:15 +00:00
|
|
|
kdf: Kdf,
|
2019-06-20 05:58:33 +00:00
|
|
|
) -> Result<KeyConfig, Error> {
|
2019-06-18 09:17:22 +00:00
|
|
|
|
|
|
|
let salt = proxmox::sys::linux::random_data(32)?;
|
|
|
|
|
2020-11-24 06:19:15 +00:00
|
|
|
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");
|
|
|
|
}
|
2019-06-18 09:17:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2020-09-12 13:10:47 +00:00
|
|
|
let created = proxmox::tools::time::epoch_i64();
|
2019-06-18 09:17:22 +00:00
|
|
|
|
2019-06-20 05:58:33 +00:00
|
|
|
Ok(KeyConfig {
|
2019-06-18 09:17:22 +00:00
|
|
|
kdf: Some(kdf),
|
|
|
|
created,
|
2019-06-20 05:58:33 +00:00
|
|
|
modified: created,
|
2019-06-18 09:17:22 +00:00
|
|
|
data: enc_data,
|
2020-11-20 16:38:32 +00:00
|
|
|
fingerprint: None,
|
2019-06-18 16:21:01 +00:00
|
|
|
})
|
2019-06-18 09:17:22 +00:00
|
|
|
}
|
|
|
|
|
2020-07-08 08:42:05 +00:00
|
|
|
pub fn load_and_decrypt_key(
|
|
|
|
path: &std::path::Path,
|
|
|
|
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
2020-11-20 16:38:32 +00:00
|
|
|
) -> Result<([u8;32], i64, Fingerprint), Error> {
|
2020-07-08 11:52:17 +00:00
|
|
|
decrypt_key(&file_get_contents(&path)?, passphrase)
|
2020-11-24 07:03:00 +00:00
|
|
|
.with_context(|| format!("failed to load decryption key from {:?}", path))
|
2020-07-08 11:52:17 +00:00
|
|
|
}
|
2019-06-18 09:17:22 +00:00
|
|
|
|
2020-07-08 11:52:17 +00:00
|
|
|
pub fn decrypt_key(
|
|
|
|
mut keydata: &[u8],
|
|
|
|
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
2020-11-20 16:38:32 +00:00
|
|
|
) -> Result<([u8;32], i64, Fingerprint), Error> {
|
2020-07-08 11:52:17 +00:00
|
|
|
let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?;
|
2019-06-18 09:17:22 +00:00
|
|
|
|
|
|
|
let raw_data = key_config.data;
|
2019-06-20 05:58:33 +00:00
|
|
|
let created = key_config.created;
|
2019-06-18 09:17:22 +00:00
|
|
|
|
2019-06-19 15:16:41 +00:00
|
|
|
let key = if let Some(kdf) = key_config.kdf {
|
2019-06-18 09:17:22 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
2019-10-25 16:44:51 +00:00
|
|
|
openssl::symm::decrypt_aead(
|
2019-06-18 09:17:22 +00:00
|
|
|
cipher,
|
|
|
|
&derived_key,
|
|
|
|
Some(&iv),
|
|
|
|
b"", //??
|
|
|
|
&enc_data,
|
|
|
|
&tag,
|
2019-10-25 16:44:51 +00:00
|
|
|
).map_err(|err| format_err!("Unable to decrypt key - {}", err))?
|
2019-06-18 09:17:22 +00:00
|
|
|
|
|
|
|
} else {
|
2019-06-19 15:16:41 +00:00
|
|
|
raw_data
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut result = [0u8; 32];
|
|
|
|
result.copy_from_slice(&key);
|
|
|
|
|
2020-11-20 16:38:32 +00:00
|
|
|
let fingerprint = match key_config.fingerprint {
|
|
|
|
Some(fingerprint) => fingerprint,
|
|
|
|
None => {
|
|
|
|
let crypt_config = CryptConfig::new(result.clone())?;
|
|
|
|
crypt_config.fingerprint()
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((result, created, fingerprint))
|
2019-06-18 09:17:22 +00:00
|
|
|
}
|