key: add fingerprint to key config
and set/generate it on - key creation - key passphrase change - key decryption if not already set - key encryption with master key Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
parent
05cdc05347
commit
37e60ddcde
@ -17,6 +17,8 @@ use openssl::pkcs5::pbkdf2_hmac;
|
|||||||
use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode};
|
use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::tools::format::{as_fingerprint, bytes_as_fingerprint};
|
||||||
|
|
||||||
use proxmox::api::api;
|
use proxmox::api::api;
|
||||||
|
|
||||||
// openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint")
|
// openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint")
|
||||||
@ -37,14 +39,18 @@ pub enum CryptMode {
|
|||||||
SignOnly,
|
SignOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
/// 32-byte fingerprint, usually calculated with SHA256.
|
/// 32-byte fingerprint, usually calculated with SHA256.
|
||||||
pub struct Fingerprint {
|
pub struct Fingerprint {
|
||||||
|
#[serde(with = "bytes_as_fingerprint")]
|
||||||
bytes: [u8; 32],
|
bytes: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display as short key ID
|
||||||
impl Display for Fingerprint {
|
impl Display for Fingerprint {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?}", self.bytes)
|
write!(f, "{}", as_fingerprint(&self.bytes[0..8]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +249,13 @@ impl CryptConfig {
|
|||||||
) -> Result<Vec<u8>, Error> {
|
) -> Result<Vec<u8>, Error> {
|
||||||
|
|
||||||
let modified = proxmox::tools::time::epoch_i64();
|
let modified = proxmox::tools::time::epoch_i64();
|
||||||
let key_config = super::KeyConfig { kdf: None, created, modified, data: self.enc_key.to_vec() };
|
let key_config = super::KeyConfig {
|
||||||
|
kdf: None,
|
||||||
|
created,
|
||||||
|
modified,
|
||||||
|
data: self.enc_key.to_vec(),
|
||||||
|
fingerprint: Some(self.fingerprint()),
|
||||||
|
};
|
||||||
let data = serde_json::to_string(&key_config)?.as_bytes().to_vec();
|
let data = serde_json::to_string(&key_config)?.as_bytes().to_vec();
|
||||||
|
|
||||||
let mut buffer = vec![0u8; rsa.size() as usize];
|
let mut buffer = vec![0u8; rsa.size() as usize];
|
||||||
|
@ -2,6 +2,8 @@ use anyhow::{bail, format_err, Context, Error};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::backup::{CryptConfig, Fingerprint};
|
||||||
|
|
||||||
use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
|
use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
|
||||||
use proxmox::try_block;
|
use proxmox::try_block;
|
||||||
|
|
||||||
@ -66,6 +68,9 @@ pub struct KeyConfig {
|
|||||||
pub modified: i64,
|
pub modified: i64,
|
||||||
#[serde(with = "proxmox::tools::serde::bytes_as_base64")]
|
#[serde(with = "proxmox::tools::serde::bytes_as_base64")]
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub fingerprint: Option<Fingerprint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_key_config(
|
pub fn store_key_config(
|
||||||
@ -142,13 +147,14 @@ pub fn encrypt_key_with_passphrase(
|
|||||||
created,
|
created,
|
||||||
modified: created,
|
modified: created,
|
||||||
data: enc_data,
|
data: enc_data,
|
||||||
|
fingerprint: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_and_decrypt_key(
|
pub fn load_and_decrypt_key(
|
||||||
path: &std::path::Path,
|
path: &std::path::Path,
|
||||||
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
||||||
) -> Result<([u8;32], i64), Error> {
|
) -> Result<([u8;32], i64, Fingerprint), Error> {
|
||||||
do_load_and_decrypt_key(path, passphrase)
|
do_load_and_decrypt_key(path, passphrase)
|
||||||
.with_context(|| format!("failed to load decryption key from {:?}", path))
|
.with_context(|| format!("failed to load decryption key from {:?}", path))
|
||||||
}
|
}
|
||||||
@ -156,14 +162,14 @@ pub fn load_and_decrypt_key(
|
|||||||
fn do_load_and_decrypt_key(
|
fn do_load_and_decrypt_key(
|
||||||
path: &std::path::Path,
|
path: &std::path::Path,
|
||||||
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
||||||
) -> Result<([u8;32], i64), Error> {
|
) -> Result<([u8;32], i64, Fingerprint), Error> {
|
||||||
decrypt_key(&file_get_contents(&path)?, passphrase)
|
decrypt_key(&file_get_contents(&path)?, passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt_key(
|
pub fn decrypt_key(
|
||||||
mut keydata: &[u8],
|
mut keydata: &[u8],
|
||||||
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
passphrase: &dyn Fn() -> Result<Vec<u8>, Error>,
|
||||||
) -> Result<([u8;32], i64), Error> {
|
) -> Result<([u8;32], i64, Fingerprint), Error> {
|
||||||
let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?;
|
let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?;
|
||||||
|
|
||||||
let raw_data = key_config.data;
|
let raw_data = key_config.data;
|
||||||
@ -203,5 +209,13 @@ pub fn decrypt_key(
|
|||||||
let mut result = [0u8; 32];
|
let mut result = [0u8; 32];
|
||||||
result.copy_from_slice(&key);
|
result.copy_from_slice(&key);
|
||||||
|
|
||||||
Ok((result, created))
|
let fingerprint = match key_config.fingerprint {
|
||||||
|
Some(fingerprint) => fingerprint,
|
||||||
|
None => {
|
||||||
|
let crypt_config = CryptConfig::new(result.clone())?;
|
||||||
|
crypt_config.fingerprint()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((result, created, fingerprint))
|
||||||
}
|
}
|
||||||
|
@ -1069,7 +1069,7 @@ async fn create_backup(
|
|||||||
let (crypt_config, rsa_encrypted_key) = match keydata {
|
let (crypt_config, rsa_encrypted_key) = match keydata {
|
||||||
None => (None, None),
|
None => (None, None),
|
||||||
Some(key) => {
|
Some(key) => {
|
||||||
let (key, created) = decrypt_key(&key, &key::get_encryption_key_password)?;
|
let (key, created, _fingerprint) = decrypt_key(&key, &key::get_encryption_key_password)?;
|
||||||
|
|
||||||
let crypt_config = CryptConfig::new(key)?;
|
let crypt_config = CryptConfig::new(key)?;
|
||||||
|
|
||||||
@ -1380,7 +1380,7 @@ async fn restore(param: Value) -> Result<Value, Error> {
|
|||||||
let crypt_config = match keydata {
|
let crypt_config = match keydata {
|
||||||
None => None,
|
None => None,
|
||||||
Some(key) => {
|
Some(key) => {
|
||||||
let (key, _) = decrypt_key(&key, &key::get_encryption_key_password)?;
|
let (key, _, _) = decrypt_key(&key, &key::get_encryption_key_password)?;
|
||||||
Some(Arc::new(CryptConfig::new(key)?))
|
Some(Arc::new(CryptConfig::new(key)?))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1538,7 +1538,7 @@ async fn upload_log(param: Value) -> Result<Value, Error> {
|
|||||||
let crypt_config = match keydata {
|
let crypt_config = match keydata {
|
||||||
None => None,
|
None => None,
|
||||||
Some(key) => {
|
Some(key) => {
|
||||||
let (key, _created) = decrypt_key(&key, &key::get_encryption_key_password)?;
|
let (key, _created, _) = decrypt_key(&key, &key::get_encryption_key_password)?;
|
||||||
let crypt_config = CryptConfig::new(key)?;
|
let crypt_config = CryptConfig::new(key)?;
|
||||||
Some(Arc::new(crypt_config))
|
Some(Arc::new(crypt_config))
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ pub async fn benchmark(
|
|||||||
let crypt_config = match keyfile {
|
let crypt_config = match keyfile {
|
||||||
None => None,
|
None => None,
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
let (key, _) = load_and_decrypt_key(&path, &crate::key::get_encryption_key_password)?;
|
let (key, _, _) = load_and_decrypt_key(&path, &crate::key::get_encryption_key_password)?;
|
||||||
let crypt_config = CryptConfig::new(key)?;
|
let crypt_config = CryptConfig::new(key)?;
|
||||||
Some(Arc::new(crypt_config))
|
Some(Arc::new(crypt_config))
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> {
|
|||||||
let crypt_config = match keydata {
|
let crypt_config = match keydata {
|
||||||
None => None,
|
None => None,
|
||||||
Some(key) => {
|
Some(key) => {
|
||||||
let (key, _created) = decrypt_key(&key, &get_encryption_key_password)?;
|
let (key, _created, _fingerprint) = decrypt_key(&key, &get_encryption_key_password)?;
|
||||||
let crypt_config = CryptConfig::new(key)?;
|
let crypt_config = CryptConfig::new(key)?;
|
||||||
Some(Arc::new(crypt_config))
|
Some(Arc::new(crypt_config))
|
||||||
}
|
}
|
||||||
@ -170,7 +170,7 @@ async fn catalog_shell(param: Value) -> Result<(), Error> {
|
|||||||
let crypt_config = match keydata {
|
let crypt_config = match keydata {
|
||||||
None => None,
|
None => None,
|
||||||
Some(key) => {
|
Some(key) => {
|
||||||
let (key, _created) = decrypt_key(&key, &get_encryption_key_password)?;
|
let (key, _created, _fingerprint) = decrypt_key(&key, &get_encryption_key_password)?;
|
||||||
let crypt_config = CryptConfig::new(key)?;
|
let crypt_config = CryptConfig::new(key)?;
|
||||||
Some(Arc::new(crypt_config))
|
Some(Arc::new(crypt_config))
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,11 @@ use proxmox::sys::linux::tty;
|
|||||||
use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
|
use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions};
|
||||||
|
|
||||||
use proxmox_backup::backup::{
|
use proxmox_backup::backup::{
|
||||||
encrypt_key_with_passphrase, load_and_decrypt_key, store_key_config, KeyConfig,
|
encrypt_key_with_passphrase,
|
||||||
|
load_and_decrypt_key,
|
||||||
|
store_key_config,
|
||||||
|
CryptConfig,
|
||||||
|
KeyConfig,
|
||||||
};
|
};
|
||||||
use proxmox_backup::tools;
|
use proxmox_backup::tools;
|
||||||
|
|
||||||
@ -120,7 +124,10 @@ fn create(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error> {
|
|||||||
|
|
||||||
let kdf = kdf.unwrap_or_default();
|
let kdf = kdf.unwrap_or_default();
|
||||||
|
|
||||||
let key = proxmox::sys::linux::random_data(32)?;
|
let mut key_array = [0u8; 32];
|
||||||
|
proxmox::sys::linux::fill_with_random_data(&mut key_array)?;
|
||||||
|
let crypt_config = CryptConfig::new(key_array.clone())?;
|
||||||
|
let key = key_array.to_vec();
|
||||||
|
|
||||||
match kdf {
|
match kdf {
|
||||||
Kdf::None => {
|
Kdf::None => {
|
||||||
@ -134,6 +141,7 @@ fn create(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error> {
|
|||||||
created,
|
created,
|
||||||
modified: created,
|
modified: created,
|
||||||
data: key,
|
data: key,
|
||||||
|
fingerprint: Some(crypt_config.fingerprint()),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@ -145,7 +153,8 @@ fn create(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error> {
|
|||||||
|
|
||||||
let password = tty::read_and_verify_password("Encryption Key Password: ")?;
|
let password = tty::read_and_verify_password("Encryption Key Password: ")?;
|
||||||
|
|
||||||
let key_config = encrypt_key_with_passphrase(&key, &password)?;
|
let mut key_config = encrypt_key_with_passphrase(&key, &password)?;
|
||||||
|
key_config.fingerprint = Some(crypt_config.fingerprint());
|
||||||
|
|
||||||
store_key_config(&path, false, key_config)?;
|
store_key_config(&path, false, key_config)?;
|
||||||
}
|
}
|
||||||
@ -188,7 +197,7 @@ fn change_passphrase(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error
|
|||||||
bail!("unable to change passphrase - no tty");
|
bail!("unable to change passphrase - no tty");
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key, created) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
|
let (key, created, fingerprint) = load_and_decrypt_key(&path, &get_encryption_key_password)?;
|
||||||
|
|
||||||
match kdf {
|
match kdf {
|
||||||
Kdf::None => {
|
Kdf::None => {
|
||||||
@ -202,6 +211,7 @@ fn change_passphrase(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error
|
|||||||
created, // keep original value
|
created, // keep original value
|
||||||
modified,
|
modified,
|
||||||
data: key.to_vec(),
|
data: key.to_vec(),
|
||||||
|
fingerprint: Some(fingerprint),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@ -210,6 +220,7 @@ fn change_passphrase(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error
|
|||||||
|
|
||||||
let mut new_key_config = encrypt_key_with_passphrase(&key, &password)?;
|
let mut new_key_config = encrypt_key_with_passphrase(&key, &password)?;
|
||||||
new_key_config.created = created; // keep original value
|
new_key_config.created = created; // keep original value
|
||||||
|
new_key_config.fingerprint = Some(fingerprint);
|
||||||
|
|
||||||
store_key_config(&path, true, new_key_config)?;
|
store_key_config(&path, true, new_key_config)?;
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> {
|
|||||||
let crypt_config = match keyfile {
|
let crypt_config = match keyfile {
|
||||||
None => None,
|
None => None,
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
let (key, _) = load_and_decrypt_key(&path, &crate::key::get_encryption_key_password)?;
|
let (key, _, _) = load_and_decrypt_key(&path, &crate::key::get_encryption_key_password)?;
|
||||||
Some(Arc::new(CryptConfig::new(key)?))
|
Some(Arc::new(CryptConfig::new(key)?))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -102,6 +102,40 @@ impl From<u64> for HumanByte {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_fingerprint(bytes: &[u8]) -> String {
|
||||||
|
proxmox::tools::digest_to_hex(bytes)
|
||||||
|
.as_bytes()
|
||||||
|
.chunks(2)
|
||||||
|
.map(|v| std::str::from_utf8(v).unwrap())
|
||||||
|
.collect::<Vec<&str>>().join(":")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod bytes_as_fingerprint {
|
||||||
|
use serde::{Deserialize, Serializer, Deserializer};
|
||||||
|
|
||||||
|
pub fn serialize<S>(
|
||||||
|
bytes: &[u8; 32],
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let s = crate::tools::format::as_fingerprint(bytes);
|
||||||
|
serializer.serialize_str(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<[u8; 32], D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let mut s = String::deserialize(deserializer)?;
|
||||||
|
s.retain(|c| c != ':');
|
||||||
|
proxmox::tools::hex_to_digest(&s).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn correct_byte_convert() {
|
fn correct_byte_convert() {
|
||||||
fn convert(b: usize) -> String {
|
fn convert(b: usize) -> String {
|
||||||
|
Loading…
Reference in New Issue
Block a user