src/backup/key_derivation.rs: move kdf code into separate file
This commit is contained in:
parent
11515438cc
commit
826f309bf5
@ -130,6 +130,9 @@ pub static DYNAMIC_SIZED_CHUNK_INDEX_1_0: [u8; 8] = [28, 145, 78, 165, 25, 186,
|
|||||||
mod crypt_setup;
|
mod crypt_setup;
|
||||||
pub use crypt_setup::*;
|
pub use crypt_setup::*;
|
||||||
|
|
||||||
|
mod key_derivation;
|
||||||
|
pub use key_derivation::*;
|
||||||
|
|
||||||
mod data_chunk;
|
mod data_chunk;
|
||||||
pub use data_chunk::*;
|
pub use data_chunk::*;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
//! for a short introduction.
|
//! for a short introduction.
|
||||||
use failure::*;
|
use failure::*;
|
||||||
use proxmox::tools;
|
use proxmox::tools;
|
||||||
use openssl::pkcs5::{pbkdf2_hmac, scrypt};
|
use openssl::pkcs5::pbkdf2_hmac;
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode};
|
use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -26,13 +26,6 @@ pub struct CryptConfig {
|
|||||||
enc_key: [u8; 32],
|
enc_key: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SCryptConfig {
|
|
||||||
pub n: u64,
|
|
||||||
pub r: u64,
|
|
||||||
pub p: u64,
|
|
||||||
pub salt: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CryptConfig {
|
impl CryptConfig {
|
||||||
|
|
||||||
/// Create a new instance.
|
/// Create a new instance.
|
||||||
@ -53,22 +46,6 @@ impl CryptConfig {
|
|||||||
Ok(Self { id_key, enc_key, cipher: Cipher::aes_256_gcm() })
|
Ok(Self { id_key, enc_key, cipher: Cipher::aes_256_gcm() })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple key derivation function using scrypt
|
|
||||||
pub fn derive_key_from_password(password: &[u8], scrypt_config: &SCryptConfig) -> Result<[u8; 32], Error> {
|
|
||||||
|
|
||||||
let mut key = [0u8; 32];
|
|
||||||
|
|
||||||
// estimated scrypt memory usage is 128*r*n*p
|
|
||||||
|
|
||||||
scrypt(
|
|
||||||
password,
|
|
||||||
&scrypt_config.salt,
|
|
||||||
scrypt_config.n, scrypt_config.r, scrypt_config.p, 1025*1024*1024,
|
|
||||||
&mut key)?;
|
|
||||||
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute a chunk digest using a secret name space.
|
/// Compute a chunk digest using a secret name space.
|
||||||
///
|
///
|
||||||
/// Computes an SHA256 checksum over some secret data (derived
|
/// Computes an SHA256 checksum over some secret data (derived
|
||||||
|
180
src/backup/key_derivation.rs
Normal file
180
src/backup/key_derivation.rs
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
use failure::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use chrono::{Local, TimeZone, DateTime};
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
kdf: Option<KeyDerivationConfig>,
|
||||||
|
#[serde(with = "proxmox::tools::serde::date_time_as_rfc3339")]
|
||||||
|
created: DateTime<Local>,
|
||||||
|
#[serde(with = "proxmox::tools::serde::bytes_as_base64")]
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn store_key_with_passphrase(
|
||||||
|
path: &std::path::Path,
|
||||||
|
raw_key: &[u8],
|
||||||
|
passphrase: &[u8],
|
||||||
|
replace: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
|
let salt = proxmox::sys::linux::random_data(32)?;
|
||||||
|
|
||||||
|
let kdf = KeyDerivationConfig::Scrypt {
|
||||||
|
n: 65536,
|
||||||
|
r: 8,
|
||||||
|
p: 1,
|
||||||
|
salt,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = Local.timestamp(Local::now().timestamp(), 0);
|
||||||
|
|
||||||
|
|
||||||
|
let key_config = KeyConfig {
|
||||||
|
kdf: Some(kdf),
|
||||||
|
created,
|
||||||
|
data: enc_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
crate::tools::file_set_contents(&path, data.as_bytes(), Some(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 create file {:?} - {}", path, err))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_and_decrtypt_key(path: &std::path::Path, passphrase: fn() -> Result<Vec<u8>, Error>) -> Result<Vec<u8>, Error> {
|
||||||
|
|
||||||
|
let raw = crate::tools::file_get_contents(&path)?;
|
||||||
|
let data = String::from_utf8(raw)?;
|
||||||
|
|
||||||
|
let key_config: KeyConfig = serde_json::from_str(&data)?;
|
||||||
|
|
||||||
|
let raw_data = key_config.data;
|
||||||
|
|
||||||
|
if let Some(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();
|
||||||
|
|
||||||
|
let decr_data = openssl::symm::decrypt_aead(
|
||||||
|
cipher,
|
||||||
|
&derived_key,
|
||||||
|
Some(&iv),
|
||||||
|
b"", //??
|
||||||
|
&enc_data,
|
||||||
|
&tag,
|
||||||
|
).map_err(|err| format_err!("Unable to decrypt key - {}", err))?;
|
||||||
|
|
||||||
|
Ok(decr_data)
|
||||||
|
} else {
|
||||||
|
Ok(raw_data)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
#[macro_use]
|
//#[macro_use]
|
||||||
extern crate proxmox_backup;
|
extern crate proxmox_backup;
|
||||||
|
|
||||||
use failure::*;
|
use failure::*;
|
||||||
@ -782,13 +782,13 @@ fn complete_chunk_size(_arg: &str, _param: &HashMap<String, String>) -> Vec<Stri
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_encryption_key_password() -> Result<String, Error> {
|
fn get_encryption_key_password() -> Result<Vec<u8>, Error> {
|
||||||
|
|
||||||
// fixme: implement other input methods
|
// fixme: implement other input methods
|
||||||
|
|
||||||
use std::env::VarError::*;
|
use std::env::VarError::*;
|
||||||
match std::env::var("PBS_ENCRYPTION_PASSWORD") {
|
match std::env::var("PBS_ENCRYPTION_PASSWORD") {
|
||||||
Ok(p) => return Ok(p),
|
Ok(p) => return Ok(p.as_bytes().to_vec()),
|
||||||
Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
|
Err(NotUnicode(_)) => bail!("PBS_ENCRYPTION_PASSWORD contains bad characters"),
|
||||||
Err(NotPresent) => {
|
Err(NotPresent) => {
|
||||||
// Try another method
|
// Try another method
|
||||||
@ -797,74 +797,12 @@ fn get_encryption_key_password() -> Result<String, Error> {
|
|||||||
|
|
||||||
// If we're on a TTY, query the user for a password
|
// If we're on a TTY, query the user for a password
|
||||||
if crate::tools::tty::stdin_isatty() {
|
if crate::tools::tty::stdin_isatty() {
|
||||||
return Ok(String::from_utf8(crate::tools::tty::read_password("Encryption Key Password: ")?)?);
|
return Ok(crate::tools::tty::read_password("Encryption Key Password: ")?);
|
||||||
}
|
}
|
||||||
|
|
||||||
bail!("no password input mechanism available");
|
bail!("no password input mechanism available");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_store_with_password(path: &std::path::Path, raw_key: &[u8], password: &[u8], replace: bool) -> Result<(), Error> {
|
|
||||||
|
|
||||||
let salt = proxmox::sys::linux::random_data(32)?;
|
|
||||||
|
|
||||||
let scrypt_config = SCryptConfig { n: 65536, r: 8, p: 1, salt };
|
|
||||||
|
|
||||||
let derived_key = CryptConfig::derive_key_from_password(password, &scrypt_config)?;
|
|
||||||
|
|
||||||
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 created = Local.timestamp(Local::now().timestamp(), 0);
|
|
||||||
|
|
||||||
let result = json!({
|
|
||||||
"kdf": "scrypt",
|
|
||||||
"created": created.to_rfc3339(),
|
|
||||||
"n": scrypt_config.n,
|
|
||||||
"r": scrypt_config.r,
|
|
||||||
"p": scrypt_config.p,
|
|
||||||
"salt": proxmox::tools::digest_to_hex(&scrypt_config.salt),
|
|
||||||
"iv": proxmox::tools::digest_to_hex(&iv),
|
|
||||||
"tag": proxmox::tools::digest_to_hex(&tag),
|
|
||||||
"data": proxmox::tools::digest_to_hex(&encrypted_key),
|
|
||||||
});
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
let data = result.to_string();
|
|
||||||
|
|
||||||
try_block!({
|
|
||||||
if replace {
|
|
||||||
let mode = nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR;
|
|
||||||
crate::tools::file_set_contents(&path, data.as_bytes(), Some(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 create file {:?} - {}", path, err))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_create(
|
fn key_create(
|
||||||
param: Value,
|
param: Value,
|
||||||
_info: &ApiMethod,
|
_info: &ApiMethod,
|
||||||
@ -885,82 +823,11 @@ fn key_create(
|
|||||||
|
|
||||||
let key = proxmox::sys::linux::random_data(32)?;
|
let key = proxmox::sys::linux::random_data(32)?;
|
||||||
|
|
||||||
key_store_with_password(&path, &key, password.as_bytes(), false)?;
|
store_key_with_passphrase(&path, &key, &password, false)?;
|
||||||
|
|
||||||
Ok(Value::Null)
|
Ok(Value::Null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_load_and_decrtypt(path: &std::path::Path) -> Result<Vec<u8>, Error> {
|
|
||||||
|
|
||||||
let data = crate::tools::file_get_json(&path, None)?;
|
|
||||||
println!("DATA: {:?}", data);
|
|
||||||
|
|
||||||
match data["kdf"].as_str() {
|
|
||||||
Some(kdf) => {
|
|
||||||
if kdf != "scrypt" {
|
|
||||||
bail!("Unknown key derivation function '{}'", kdf);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => bail!("missing property 'kdf'."),
|
|
||||||
}
|
|
||||||
|
|
||||||
let salt = match data["salt"].as_str() {
|
|
||||||
Some(hex) => proxmox::tools::hex_to_bin(hex)?,
|
|
||||||
None => bail!("missing property 'salt'."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let n = match data["n"].as_u64() {
|
|
||||||
Some(n) => n,
|
|
||||||
None => bail!("missing property 'n'."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let r = match data["r"].as_u64() {
|
|
||||||
Some(r) => r,
|
|
||||||
None => bail!("missing property 'r'."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let p = match data["p"].as_u64() {
|
|
||||||
Some(p) => p,
|
|
||||||
None => bail!("missing property 'p'."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let scrypt_config = SCryptConfig { n, r, p, salt };
|
|
||||||
|
|
||||||
let password = get_encryption_key_password()?;
|
|
||||||
if password.len() < 5 {
|
|
||||||
bail!("Password is too short!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let derived_key = CryptConfig::derive_key_from_password(password.as_bytes(), &scrypt_config)?;
|
|
||||||
|
|
||||||
let iv = match data["iv"].as_str() {
|
|
||||||
Some(hex) => proxmox::tools::hex_to_bin(hex)?,
|
|
||||||
None => bail!("missing property 'iv'."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tag = match data["tag"].as_str() {
|
|
||||||
Some(hex) => proxmox::tools::hex_to_bin(hex)?,
|
|
||||||
None => bail!("missing property 'tag'."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = match data["data"].as_str() {
|
|
||||||
Some(hex) => proxmox::tools::hex_to_bin(hex)?,
|
|
||||||
None => bail!("missing property 'data'."),
|
|
||||||
};
|
|
||||||
|
|
||||||
let cipher = openssl::symm::Cipher::aes_256_gcm();
|
|
||||||
|
|
||||||
let decr_data = openssl::symm::decrypt_aead(
|
|
||||||
cipher,
|
|
||||||
&derived_key,
|
|
||||||
Some(&iv),
|
|
||||||
b"", //??
|
|
||||||
&data,
|
|
||||||
&tag,
|
|
||||||
).map_err(|err| format_err!("Unable to decrypt key - {}", err))?;
|
|
||||||
|
|
||||||
Ok(decr_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_change_passphrase(
|
fn key_change_passphrase(
|
||||||
param: Value,
|
param: Value,
|
||||||
@ -983,7 +850,7 @@ fn key_change_passphrase(
|
|||||||
bail!("unable to change passphrase - no tty");
|
bail!("unable to change passphrase - no tty");
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = key_load_and_decrtypt(&path)?;
|
let key = load_and_decrtypt_key(&path, get_encryption_key_password)?;
|
||||||
|
|
||||||
let new_pw = String::from_utf8(crate::tools::tty::read_password("New Password: ")?)?;
|
let new_pw = String::from_utf8(crate::tools::tty::read_password("New Password: ")?)?;
|
||||||
let verify_pw = String::from_utf8(crate::tools::tty::read_password("Verify Password: ")?)?;
|
let verify_pw = String::from_utf8(crate::tools::tty::read_password("Verify Password: ")?)?;
|
||||||
@ -996,7 +863,7 @@ fn key_change_passphrase(
|
|||||||
bail!("Password is too short!");
|
bail!("Password is too short!");
|
||||||
}
|
}
|
||||||
|
|
||||||
key_store_with_password(&path, &key, new_pw.as_bytes(), true)?;
|
store_key_with_passphrase(&path, &key, new_pw.as_bytes(), true)?;
|
||||||
|
|
||||||
Ok(Value::Null)
|
Ok(Value::Null)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user