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:
		
				
					committed by
					
						 Dietmar Maurer
						Dietmar Maurer
					
				
			
			
				
	
			
			
			
						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 { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user