[pbs-devel] [PATCH proxmox-backup 2/7] key: add fingerprint to key config
Wolfgang Bumiller
w.bumiller at proxmox.com
Wed Nov 18 09:48:20 CET 2020
On Tue, Nov 17, 2020 at 06:57:20PM +0100, Fabian Grünbichler wrote:
> and set/generate it on
> - key creation
> - key passphrase change
> - key decryption if not already set (not persisted, but returned)
> - key encryption with master key
>
> Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
> ---
>
> Notes:
> an existing passwordless key can be 'updated' non-interactively with
>
> proxmox-backup-client key change-passphrase $path --kdf none
>
> something like this could go into the/some postinst to retro-actively give all
> the PVE generated storage keys a fingerprint field. this only affects the 'key
> show' subcommand (introduced later in this series) or manual checks of the key
> file, any operations actually using the key have access to its CryptConfig and
> thus the fingerprint() function anyway..
>
> src/backup/crypt_config.rs | 8 ++-
> src/backup/key_derivation.rs | 23 +++++++--
> src/bin/proxmox-backup-client.rs | 6 +--
> src/bin/proxmox_backup_client/benchmark.rs | 2 +-
> src/bin/proxmox_backup_client/catalog.rs | 4 +-
> src/bin/proxmox_backup_client/key.rs | 17 +++++--
> src/bin/proxmox_backup_client/mount.rs | 2 +-
> src/tools/format.rs | 58 ++++++++++++++++++++++
> 8 files changed, 105 insertions(+), 15 deletions(-)
>
> diff --git a/src/backup/crypt_config.rs b/src/backup/crypt_config.rs
> index 01ab0942..177923b3 100644
> --- a/src/backup/crypt_config.rs
> +++ b/src/backup/crypt_config.rs
> @@ -66,6 +68,10 @@ pub struct KeyConfig {
> pub modified: i64,
> #[serde(with = "proxmox::tools::serde::bytes_as_base64")]
> pub data: Vec<u8>,
> + #[serde(skip_serializing_if = "Option::is_none")]
> + #[serde(default)]
> + #[serde(with = "crate::tools::format::opt_sha256_as_fingerprint")]
> + pub fingerprint: Option<[u8; 32]>,
> }
>
> pub fn store_key_config(
(...)
> diff --git a/src/bin/proxmox_backup_client/key.rs b/src/bin/proxmox_backup_client/key.rs
> index 8b802b3e..915ee970 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;
>
> @@ -121,6 +125,9 @@ fn create(kdf: Option<Kdf>, path: Option<String>) -> Result<(), Error> {
> let kdf = kdf.unwrap_or_default();
>
> let key = proxmox::sys::linux::random_data(32)?;
This allocates, and calls `fill_with_random_data`. You could shorten
this to:
let mut key_array = [0u8; 32];
proxmox::sys::linux::random_data(&mut key_array)?;
> + use std::convert::TryInto;
> + let key_array: [u8; 32] = key.clone()[0..32].try_into().unwrap();
> + let crypt_config = CryptConfig::new(key_array)?;
>
> match kdf {
> Kdf::None => {
> diff --git a/src/tools/format.rs b/src/tools/format.rs
> index 8fe6aa82..c16559a7 100644
> --- a/src/tools/format.rs
> +++ b/src/tools/format.rs
> @@ -102,6 +102,64 @@ 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 opt_sha256_as_fingerprint {
> + use serde::{self, Serializer, Deserializer};
^ `self` not needed there
> +
> + pub fn serialize<S>(
> + csum: &Option<[u8; 32]>,
> + serializer: S,
> + ) -> Result<S::Ok, S::Error>
> + where
> + S: Serializer,
> + {
> + crate::tools::format::sha256_as_fingerprint::serialize(&csum.unwrap(), serializer)
This unwrap makes skipping `None` a hard requirement, so any user of this without
the `#[serde(skip_serializing_if = "Option::is_none")]` attribute will
panic.
Simply translate `None` to `serialize_none()`:
match csum {
Some(csum) => super::sha256_as_fingerprint::serialize(csum, serializer),
None => serializer.serialize_none(),
}
(Also note the possible use of `super::` instead of the full
`crate::tools::format::` prefix.
> + }
> +
> + pub fn deserialize<'de, D>(
> + deserializer: D,
> + ) -> Result<Option<[u8; 32]>, D::Error>
> + where
> + D: Deserializer<'de>,
> + {
> + crate::tools::format::sha256_as_fingerprint::deserialize(deserializer)
> + .map(|digest| Some(digest))
Similarly this fails if there the option type is not skipped and
serialized eg. as json `null`. You can go via `Option<String>` here, but
unfortunately lose the ability to reuse the existing code from below.
match Option::<String>::deserialize(deserializer)? {
Some(mut s) => {
<code from sha256_as_fingerprint::deserialize>,
}
None => Ok(None),
}
I've also played around a bit with making this more general. Not sure we
need/want this, but this did work:
Assuming a `fn hex_to_bin_exact(hex: &str, out: &mut [u8]) -> Result<(), Error>`
being in `proxmox::tools` (same as `hex_to_bin` but writing to `out`
expecting the string to have an exactly matching length):
---8<---
pub mod opt_bytes_as_fingerprint {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S, T>(csum: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
match csum {
Some(csum) => super::bytes_as_fingerprint::serialize(csum, serializer),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: Default + AsMut<[u8]>,
{
match Option::<String>::deserialize(deserializer)? {
Some(mut s) => {
s.retain(|c| c != ':');
let mut out = T::default();
proxmox::tools::hex_to_bin_exact(&s, out.as_mut())
.map_err(serde::de::Error::custom)?;
Ok(Some(out))
}
None => Ok(None),
}
}
}
pub mod bytes_as_fingerprint {
use serde::{Deserialize, Serializer, Deserializer};
pub fn serialize<S, T>(csum: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
let s = super::as_fingerprint(csum.as_ref());
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Default + AsMut<[u8]>,
{
let mut s = String::deserialize(deserializer)?;
s.retain(|c| c != ':');
let mut out = T::default();
proxmox::tools::hex_to_bin_exact(&s, out.as_mut())
.map_err(serde::de::Error::custom)?;
Ok(out)
}
}
More information about the pbs-devel
mailing list