diff --git a/src/backup/crypt_config.rs b/src/backup/crypt_config.rs index 8a4fe0e3..7d27706a 100644 --- a/src/backup/crypt_config.rs +++ b/src/backup/crypt_config.rs @@ -17,6 +17,8 @@ use openssl::pkcs5::pbkdf2_hmac; use openssl::symm::{decrypt_aead, Cipher, Crypter, Mode}; use serde::{Deserialize, Serialize}; +use crate::tools::format::{as_fingerprint, bytes_as_fingerprint}; + use proxmox::api::api; // openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint") @@ -37,14 +39,18 @@ pub enum CryptMode { SignOnly, } +#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(transparent)] /// 32-byte fingerprint, usually calculated with SHA256. pub struct Fingerprint { + #[serde(with = "bytes_as_fingerprint")] bytes: [u8; 32], } +/// Display as short key ID impl Display for Fingerprint { 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, Error> { 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 mut buffer = vec![0u8; rsa.size() as usize]; diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs index 2c807723..e7b266ab 100644 --- a/src/backup/key_derivation.rs +++ b/src/backup/key_derivation.rs @@ -2,6 +2,8 @@ use anyhow::{bail, format_err, Context, Error}; use serde::{Deserialize, Serialize}; +use crate::backup::{CryptConfig, Fingerprint}; + use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; use proxmox::try_block; @@ -66,6 +68,9 @@ pub struct KeyConfig { pub modified: i64, #[serde(with = "proxmox::tools::serde::bytes_as_base64")] pub data: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub fingerprint: Option, } pub fn store_key_config( @@ -142,13 +147,14 @@ pub fn encrypt_key_with_passphrase( created, modified: created, data: enc_data, + fingerprint: None, }) } pub fn load_and_decrypt_key( path: &std::path::Path, passphrase: &dyn Fn() -> Result, Error>, -) -> Result<([u8;32], i64), Error> { +) -> Result<([u8;32], i64, Fingerprint), Error> { do_load_and_decrypt_key(path, passphrase) .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( path: &std::path::Path, passphrase: &dyn Fn() -> Result, Error>, -) -> Result<([u8;32], i64), Error> { +) -> Result<([u8;32], i64, Fingerprint), Error> { decrypt_key(&file_get_contents(&path)?, passphrase) } pub fn decrypt_key( mut keydata: &[u8], passphrase: &dyn Fn() -> Result, Error>, -) -> Result<([u8;32], i64), Error> { +) -> Result<([u8;32], i64, Fingerprint), Error> { let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?; let raw_data = key_config.data; @@ -203,5 +209,13 @@ pub fn decrypt_key( let mut result = [0u8; 32]; 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)) } diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 70aba77f..c63c7eb6 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1069,7 +1069,7 @@ async fn create_backup( let (crypt_config, rsa_encrypted_key) = match keydata { None => (None, None), 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)?; @@ -1380,7 +1380,7 @@ async fn restore(param: Value) -> Result { let crypt_config = match keydata { None => None, 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)?)) } }; @@ -1538,7 +1538,7 @@ async fn upload_log(param: Value) -> Result { let crypt_config = match keydata { None => None, 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)?; Some(Arc::new(crypt_config)) } diff --git a/src/bin/proxmox_backup_client/benchmark.rs b/src/bin/proxmox_backup_client/benchmark.rs index b0d769e8..e53e43ce 100644 --- a/src/bin/proxmox_backup_client/benchmark.rs +++ b/src/bin/proxmox_backup_client/benchmark.rs @@ -151,7 +151,7 @@ pub async fn benchmark( let crypt_config = match keyfile { None => None, 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)?; Some(Arc::new(crypt_config)) } diff --git a/src/bin/proxmox_backup_client/catalog.rs b/src/bin/proxmox_backup_client/catalog.rs index e4931e10..37ad842f 100644 --- a/src/bin/proxmox_backup_client/catalog.rs +++ b/src/bin/proxmox_backup_client/catalog.rs @@ -73,7 +73,7 @@ async fn dump_catalog(param: Value) -> Result { let crypt_config = match keydata { None => None, 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)?; Some(Arc::new(crypt_config)) } @@ -170,7 +170,7 @@ async fn catalog_shell(param: Value) -> Result<(), Error> { let crypt_config = match keydata { None => None, 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)?; Some(Arc::new(crypt_config)) } diff --git a/src/bin/proxmox_backup_client/key.rs b/src/bin/proxmox_backup_client/key.rs index 8b802b3e..193367e3 100644 --- a/src/bin/proxmox_backup_client/key.rs +++ b/src/bin/proxmox_backup_client/key.rs @@ -11,7 +11,11 @@ use proxmox::sys::linux::tty; use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; 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; @@ -120,7 +124,10 @@ fn create(kdf: Option, path: Option) -> Result<(), Error> { 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 { Kdf::None => { @@ -134,6 +141,7 @@ fn create(kdf: Option, path: Option) -> Result<(), Error> { created, modified: created, data: key, + fingerprint: Some(crypt_config.fingerprint()), }, )?; } @@ -145,7 +153,8 @@ fn create(kdf: Option, path: Option) -> Result<(), Error> { 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)?; } @@ -188,7 +197,7 @@ fn change_passphrase(kdf: Option, path: Option) -> Result<(), Error 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 { Kdf::None => { @@ -202,6 +211,7 @@ fn change_passphrase(kdf: Option, path: Option) -> Result<(), Error created, // keep original value modified, data: key.to_vec(), + fingerprint: Some(fingerprint), }, )?; } @@ -210,6 +220,7 @@ fn change_passphrase(kdf: Option, path: Option) -> Result<(), Error let mut new_key_config = encrypt_key_with_passphrase(&key, &password)?; new_key_config.created = created; // keep original value + new_key_config.fingerprint = Some(fingerprint); store_key_config(&path, true, new_key_config)?; } diff --git a/src/bin/proxmox_backup_client/mount.rs b/src/bin/proxmox_backup_client/mount.rs index 415fbf9d..6283961e 100644 --- a/src/bin/proxmox_backup_client/mount.rs +++ b/src/bin/proxmox_backup_client/mount.rs @@ -182,7 +182,7 @@ async fn mount_do(param: Value, pipe: Option) -> Result { let crypt_config = match keyfile { None => None, 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)?)) } }; diff --git a/src/tools/format.rs b/src/tools/format.rs index 8fe6aa82..c9f50053 100644 --- a/src/tools/format.rs +++ b/src/tools/format.rs @@ -102,6 +102,40 @@ impl From 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::>().join(":") +} + +pub mod bytes_as_fingerprint { + use serde::{Deserialize, Serializer, Deserializer}; + + pub fn serialize( + bytes: &[u8; 32], + serializer: S, + ) -> Result + 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] fn correct_byte_convert() { fn convert(b: usize) -> String {