client: track key source, print when used

to avoid confusing messages about using encryption keys when restoring
plaintext backups, or about loading master keys when they are not
actually used for the current operation.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
Fabian Grünbichler 2021-02-05 16:35:35 +01:00 committed by Dietmar Maurer
parent 9432838914
commit 2f26b8668a
4 changed files with 155 additions and 69 deletions

View File

@ -606,12 +606,56 @@ fn spawn_catalog_upload(
Ok(CatalogUploadResult { catalog_writer, result: catalog_result_rx })
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum KeySource {
DefaultKey,
Fd,
Path(String),
}
fn format_key_source(source: &KeySource, key_type: &str) -> String {
match source {
KeySource::DefaultKey => format!("Using default {} key..", key_type),
KeySource::Fd => format!("Using {} key from file descriptor..", key_type),
KeySource::Path(path) => format!("Using {} key from '{}'..", key_type, path),
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct KeyWithSource {
pub source: KeySource,
pub key: Vec<u8>,
}
impl KeyWithSource {
pub fn from_fd(key: Vec<u8>) -> Self {
Self {
source: KeySource::Fd,
key,
}
}
pub fn from_default(key: Vec<u8>) -> Self {
Self {
source: KeySource::DefaultKey,
key,
}
}
pub fn from_path(path: String, key: Vec<u8>) -> Self {
Self {
source: KeySource::Path(path),
key,
}
}
}
#[derive(Debug, Eq, PartialEq)]
struct CryptoParams {
mode: CryptMode,
enc_key: Option<Vec<u8>>,
enc_key: Option<KeyWithSource>,
// FIXME switch to openssl::rsa::rsa<openssl::pkey::Public> once that is Eq?
master_pubkey: Option<Vec<u8>>,
master_pubkey: Option<KeyWithSource>,
}
fn crypto_parameters(param: &Value) -> Result<CryptoParams, Error> {
@ -656,52 +700,47 @@ fn crypto_parameters(param: &Value) -> Result<CryptoParams, Error> {
None => None,
};
let keydata = match (keyfile, key_fd) {
let key = match (keyfile, key_fd) {
(None, None) => None,
(Some(_), Some(_)) => bail!("--keyfile and --keyfd are mutually exclusive"),
(Some(keyfile), None) => {
eprintln!("Using encryption key file: {}", keyfile);
Some(file_get_contents(keyfile)?)
},
(Some(keyfile), None) => Some(KeyWithSource::from_path(
keyfile.clone(),
file_get_contents(keyfile)?,
)),
(None, Some(fd)) => {
let input = unsafe { std::fs::File::from_raw_fd(fd) };
let mut data = Vec::new();
let _len: usize = { input }.read_to_end(&mut data)
.map_err(|err| {
format_err!("error reading encryption key from fd {}: {}", fd, err)
})?;
eprintln!("Using encryption key from file descriptor");
Some(data)
let _len: usize = { input }.read_to_end(&mut data).map_err(|err| {
format_err!("error reading encryption key from fd {}: {}", fd, err)
})?;
Some(KeyWithSource::from_fd(data))
}
};
let master_pubkey_data = match (master_pubkey_file, master_pubkey_fd) {
let master_pubkey = match (master_pubkey_file, master_pubkey_fd) {
(None, None) => None,
(Some(_), Some(_)) => bail!("--keyfile and --keyfd are mutually exclusive"),
(Some(keyfile), None) => {
eprintln!("Using master key from file: {}", keyfile);
Some(file_get_contents(keyfile)?)
},
(Some(keyfile), None) => Some(KeyWithSource::from_path(
keyfile.clone(),
file_get_contents(keyfile)?,
)),
(None, Some(fd)) => {
let input = unsafe { std::fs::File::from_raw_fd(fd) };
let mut data = Vec::new();
let _len: usize = { input }.read_to_end(&mut data)
.map_err(|err| {
format_err!("error reading master key from fd {}: {}", fd, err)
})?;
eprintln!("Using master key from file descriptor");
Some(data)
let _len: usize = { input }
.read_to_end(&mut data)
.map_err(|err| format_err!("error reading master key from fd {}: {}", fd, err))?;
Some(KeyWithSource::from_fd(data))
}
};
let res = match mode {
// no crypt mode, enable encryption if keys are available
None => match (keydata, master_pubkey_data) {
None => match (key, master_pubkey) {
// only default keys if available
(None, None) => match key::read_optional_default_encryption_key()? {
None => CryptoParams { mode: CryptMode::None, enc_key: None, master_pubkey: None },
enc_key => {
eprintln!("Encrypting with default encryption key!");
let master_pubkey = key::read_optional_default_master_pubkey()?;
CryptoParams {
mode: CryptMode::Encrypt,
@ -715,7 +754,6 @@ fn crypto_parameters(param: &Value) -> Result<CryptoParams, Error> {
(None, master_pubkey) => match key::read_optional_default_encryption_key()? {
None => bail!("--master-pubkey-file/--master-pubkey-fd specified, but no key available"),
enc_key => {
eprintln!("Encrypting with default encryption key!");
CryptoParams {
mode: CryptMode::Encrypt,
enc_key,
@ -732,7 +770,7 @@ fn crypto_parameters(param: &Value) -> Result<CryptoParams, Error> {
},
// explicitly disabled encryption
Some(CryptMode::None) => match (keydata, master_pubkey_data) {
Some(CryptMode::None) => match (key, master_pubkey) {
// no keys => OK, no encryption
(None, None) => CryptoParams { mode: CryptMode::None, enc_key: None, master_pubkey: None },
@ -744,7 +782,7 @@ fn crypto_parameters(param: &Value) -> Result<CryptoParams, Error> {
},
// explicitly enabled encryption
Some(mode) => match (keydata, master_pubkey_data) {
Some(mode) => match (key, master_pubkey) {
// no key, maybe master key
(None, master_pubkey) => match key::read_optional_default_encryption_key()? {
None => bail!("--crypt-mode without --keyfile and no default key file available"),
@ -782,11 +820,15 @@ fn crypto_parameters(param: &Value) -> Result<CryptoParams, Error> {
// WARNING: there must only be one test for crypto_parameters as the default key handling is not
// safe w.r.t. concurrency
fn test_crypto_parameters_handling() -> Result<(), Error> {
let some_key = Some(vec![1;1]);
let default_key = Some(vec![2;1]);
let some_key = vec![1;1];
let default_key = vec![2;1];
let some_master_key = Some(vec![3;1]);
let default_master_key = Some(vec![4;1]);
let some_master_key = vec![3;1];
let default_master_key = vec![4;1];
let keypath = "./tests/keyfile.test";
let master_keypath = "./tests/masterkeyfile.test";
let invalid_keypath = "./tests/invalid_keyfile.test";
let no_key_res = CryptoParams {
enc_key: None,
@ -794,42 +836,54 @@ fn test_crypto_parameters_handling() -> Result<(), Error> {
mode: CryptMode::None,
};
let some_key_res = CryptoParams {
enc_key: some_key.clone(),
enc_key: Some(KeyWithSource::from_path(
keypath.to_string(),
some_key.clone(),
)),
master_pubkey: None,
mode: CryptMode::Encrypt,
};
let some_key_some_master_res = CryptoParams {
enc_key: some_key.clone(),
master_pubkey: some_master_key.clone(),
enc_key: Some(KeyWithSource::from_path(
keypath.to_string(),
some_key.clone(),
)),
master_pubkey: Some(KeyWithSource::from_path(
master_keypath.to_string(),
some_master_key.clone(),
)),
mode: CryptMode::Encrypt,
};
let some_key_default_master_res = CryptoParams {
enc_key: some_key.clone(),
master_pubkey: default_master_key.clone(),
enc_key: Some(KeyWithSource::from_path(
keypath.to_string(),
some_key.clone(),
)),
master_pubkey: Some(KeyWithSource::from_default(default_master_key.clone())),
mode: CryptMode::Encrypt,
};
let some_key_sign_res = CryptoParams {
enc_key: some_key.clone(),
enc_key: Some(KeyWithSource::from_path(
keypath.to_string(),
some_key.clone(),
)),
master_pubkey: None,
mode: CryptMode::SignOnly,
};
let default_key_res = CryptoParams {
enc_key: default_key.clone(),
enc_key: Some(KeyWithSource::from_default(default_key.clone())),
master_pubkey: None,
mode: CryptMode::Encrypt,
};
let default_key_sign_res = CryptoParams {
enc_key: default_key.clone(),
enc_key: Some(KeyWithSource::from_default(default_key.clone())),
master_pubkey: None,
mode: CryptMode::SignOnly,
};
let keypath = "./tests/keyfile.test";
replace_file(&keypath, some_key.as_ref().unwrap(), CreateOptions::default())?;
let master_keypath = "./tests/masterkeyfile.test";
replace_file(&master_keypath, some_master_key.as_ref().unwrap(), CreateOptions::default())?;
let invalid_keypath = "./tests/invalid_keyfile.test";
replace_file(&keypath, &some_key, CreateOptions::default())?;
replace_file(&master_keypath, &some_master_key, CreateOptions::default())?;
// no params, no default key == no key
let res = crypto_parameters(&json!({}));
@ -863,7 +917,7 @@ fn test_crypto_parameters_handling() -> Result<(), Error> {
assert!(crypto_parameters(&json!({"keyfile": invalid_keypath, "crypt-mode": "encrypt"})).is_err());
// now set a default key
unsafe { key::set_test_encryption_key(Ok(default_key.clone())); }
unsafe { key::set_test_encryption_key(Ok(Some(default_key.clone()))); }
// and repeat
@ -938,7 +992,7 @@ fn test_crypto_parameters_handling() -> Result<(), Error> {
// now remove default key again
unsafe { key::set_test_encryption_key(Ok(None)); }
// set a default master key
unsafe { key::set_test_default_master_pubkey(Ok(default_master_key.clone())); }
unsafe { key::set_test_default_master_pubkey(Ok(Some(default_master_key.clone()))); }
// and use an explicit master key
assert!(crypto_parameters(&json!({"master-pubkey-file": master_keypath})).is_err());
@ -1206,15 +1260,23 @@ async fn create_backup(
let (crypt_config, rsa_encrypted_key) = match crypto.enc_key {
None => (None, None),
Some(key) => {
let (key, created, fingerprint) = decrypt_key(&key, &key::get_encryption_key_password)?;
Some(key_with_source) => {
println!(
"{}",
format_key_source(&key_with_source.source, "encryption")
);
let (key, created, fingerprint) =
decrypt_key(&key_with_source.key, &key::get_encryption_key_password)?;
println!("Encryption key fingerprint: {}", fingerprint);
let crypt_config = CryptConfig::new(key)?;
match crypto.master_pubkey {
Some(pem_data) => {
let rsa = openssl::rsa::Rsa::public_key_from_pem(&pem_data)?;
Some(pem_with_source) => {
println!("{}", format_key_source(&pem_with_source.source, "master"));
let rsa = openssl::rsa::Rsa::public_key_from_pem(&pem_with_source.key)?;
let mut key_config = KeyConfig::without_password(key)?;
key_config.created = created; // keep original value
@ -1222,7 +1284,7 @@ async fn create_backup(
let enc_key = rsa_encrypt_key_config(rsa, &key_config)?;
(Some(Arc::new(crypt_config)), Some(enc_key))
}
},
_ => (Some(Arc::new(crypt_config)), None),
}
}
@ -1574,9 +1636,12 @@ async fn restore(param: Value) -> Result<Value, Error> {
let crypt_config = match crypto.enc_key {
None => None,
Some(key) => {
let (key, _, fingerprint) = decrypt_key(&key, &key::get_encryption_key_password)?;
eprintln!("Encryption key fingerprint: '{}'", fingerprint);
Some(ref key) => {
let (key, _, _) =
decrypt_key(&key.key, &key::get_encryption_key_password).map_err(|err| {
eprintln!("{}", format_key_source(&key.source, "encryption"));
err
})?;
Some(Arc::new(CryptConfig::new(key)?))
}
};
@ -1598,6 +1663,14 @@ async fn restore(param: Value) -> Result<Value, Error> {
if archive_name == ENCRYPTED_KEY_BLOB_NAME && crypt_config.is_none() {
eprintln!("Restoring encrypted key blob without original key - skipping manifest fingerprint check!")
} else {
if manifest.signature.is_some() {
if let Some(key) = &crypto.enc_key {
eprintln!("{}", format_key_source(&key.source, "encryption"));
}
if let Some(config) = &crypt_config {
eprintln!("Fingerprint: {}", config.fingerprint());
}
}
manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
}

View File

@ -15,6 +15,7 @@ use crate::{
REPO_URL_SCHEMA,
KEYFD_SCHEMA,
extract_repository_from_value,
format_key_source,
record_repository,
key::get_encryption_key_password,
decrypt_key,
@ -73,7 +74,11 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> {
let crypt_config = match crypto.enc_key {
None => None,
Some(key) => {
let (key, _created, _fingerprint) = decrypt_key(&key, &get_encryption_key_password)?;
let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password)
.map_err(|err| {
eprintln!("{}", format_key_source(&key.source, "encryption"));
err
})?;
let crypt_config = CryptConfig::new(key)?;
Some(Arc::new(crypt_config))
}
@ -171,7 +176,11 @@ async fn catalog_shell(param: Value) -> Result<(), Error> {
let crypt_config = match crypto.enc_key {
None => None,
Some(key) => {
let (key, _created, _fingerprint) = decrypt_key(&key, &get_encryption_key_password)?;
let (key, _created, _fingerprint) = decrypt_key(&key.key, &get_encryption_key_password)
.map_err(|err| {
eprintln!("{}", format_key_source(&key.source, "encryption"));
err
})?;
let crypt_config = CryptConfig::new(key)?;
Some(Arc::new(crypt_config))
}

View File

@ -20,6 +20,8 @@ use proxmox_backup::{
tools::paperkey::{generate_paper_key, PaperkeyFormat},
};
use crate::KeyWithSource;
pub const DEFAULT_ENCRYPTION_KEY_FILE_NAME: &str = "encryption-key.json";
pub const DEFAULT_MASTER_PUBKEY_FILE_NAME: &str = "master-public.pem";
@ -52,16 +54,16 @@ pub fn place_default_encryption_key() -> Result<PathBuf, Error> {
}
#[cfg(not(test))]
pub fn read_optional_default_encryption_key() -> Result<Option<Vec<u8>>, Error> {
pub(crate) fn read_optional_default_encryption_key() -> Result<Option<KeyWithSource>, Error> {
find_default_encryption_key()?
.map(file_get_contents)
.map(|path| file_get_contents(path).map(KeyWithSource::from_default))
.transpose()
}
#[cfg(not(test))]
pub fn read_optional_default_master_pubkey() -> Result<Option<Vec<u8>>, Error> {
pub(crate) fn read_optional_default_master_pubkey() -> Result<Option<KeyWithSource>, Error> {
find_default_master_pubkey()?
.map(file_get_contents)
.map(|path| file_get_contents(path).map(KeyWithSource::from_default))
.transpose()
}
@ -69,11 +71,12 @@ pub fn read_optional_default_master_pubkey() -> Result<Option<Vec<u8>>, Error> {
static mut TEST_DEFAULT_ENCRYPTION_KEY: Result<Option<Vec<u8>>, Error> = Ok(None);
#[cfg(test)]
pub fn read_optional_default_encryption_key() -> Result<Option<Vec<u8>>, Error> {
pub(crate) fn read_optional_default_encryption_key() -> Result<Option<KeyWithSource>, Error> {
// not safe when multiple concurrent test cases end up here!
unsafe {
match &TEST_DEFAULT_ENCRYPTION_KEY {
Ok(key) => Ok(key.clone()),
Ok(Some(key)) => Ok(Some(KeyWithSource::from_default(key.clone()))),
Ok(None) => Ok(None),
Err(_) => bail!("test error"),
}
}
@ -81,7 +84,7 @@ pub fn read_optional_default_encryption_key() -> Result<Option<Vec<u8>>, Error>
#[cfg(test)]
// not safe when multiple concurrent test cases end up here!
pub unsafe fn set_test_encryption_key(value: Result<Option<Vec<u8>>, Error>) {
pub(crate) unsafe fn set_test_encryption_key(value: Result<Option<Vec<u8>>, Error>) {
TEST_DEFAULT_ENCRYPTION_KEY = value;
}
@ -89,11 +92,12 @@ pub unsafe fn set_test_encryption_key(value: Result<Option<Vec<u8>>, Error>) {
static mut TEST_DEFAULT_MASTER_PUBKEY: Result<Option<Vec<u8>>, Error> = Ok(None);
#[cfg(test)]
pub fn read_optional_default_master_pubkey() -> Result<Option<Vec<u8>>, Error> {
pub(crate) fn read_optional_default_master_pubkey() -> Result<Option<KeyWithSource>, Error> {
// not safe when multiple concurrent test cases end up here!
unsafe {
match &TEST_DEFAULT_MASTER_PUBKEY {
Ok(key) => Ok(key.clone()),
Ok(Some(key)) => Ok(Some(KeyWithSource::from_default(key.clone()))),
Ok(None) => Ok(None),
Err(_) => bail!("test error"),
}
}
@ -101,7 +105,7 @@ pub fn read_optional_default_master_pubkey() -> Result<Option<Vec<u8>>, Error> {
#[cfg(test)]
// not safe when multiple concurrent test cases end up here!
pub unsafe fn set_test_default_master_pubkey(value: Result<Option<Vec<u8>>, Error>) {
pub(crate) unsafe fn set_test_default_master_pubkey(value: Result<Option<Vec<u8>>, Error>) {
TEST_DEFAULT_MASTER_PUBKEY = value;
}

View File

@ -239,7 +239,7 @@ async fn upload_log(param: Value) -> Result<Value, Error> {
let crypt_config = match crypto.enc_key {
None => None,
Some(key) => {
let (key, _created, _) = decrypt_key(&key, &crate::key::get_encryption_key_password)?;
let (key, _created, _) = decrypt_key(&key.key, &crate::key::get_encryption_key_password)?;
let crypt_config = CryptConfig::new(key)?;
Some(Arc::new(crypt_config))
}