From feb1645f37fa49de94efc654ba07af37a8ab1d86 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Tue, 19 Jan 2021 06:19:18 +0100 Subject: [PATCH] tape: generate random encryptions keys and store key_config on media --- src/api2/config/tape_encryption_keys.rs | 58 ++++----- src/api2/tape/drive.rs | 65 ++++++++-- src/backup/crypt_config.rs | 11 +- src/backup/key_derivation.rs | 4 +- src/bin/proxmox_tape/encryption_key.rs | 86 ++++++++++++- src/config/tape_encryption_keys.rs | 164 ++++++++++++++++++++---- src/tape/drive/linux_tape.rs | 26 +++- src/tape/drive/mod.rs | 44 +++++-- src/tape/drive/virtual_tape.rs | 11 +- src/tape/pool_writer.rs | 20 ++- 10 files changed, 402 insertions(+), 87 deletions(-) diff --git a/src/api2/config/tape_encryption_keys.rs b/src/api2/config/tape_encryption_keys.rs index 09d1443b..31a4fdfa 100644 --- a/src/api2/config/tape_encryption_keys.rs +++ b/src/api2/config/tape_encryption_keys.rs @@ -15,9 +15,12 @@ use crate::{ config::{ tape_encryption_keys::{ TAPE_KEYS_LOCKFILE, - EncryptionKeyInfo, + generate_tape_encryption_key, load_keys, + load_key_configs, save_keys, + save_key_configs, + insert_key, }, }, api2::types::{ @@ -25,12 +28,13 @@ use crate::{ PROXMOX_CONFIG_DIGEST_SCHEMA, TapeKeyMetadata, }, - backup::Fingerprint, + backup::{ + Fingerprint, + }, tools::format::as_fingerprint, }; #[api( - protected: true, input: { properties: {}, }, @@ -47,17 +51,17 @@ pub fn list_keys( mut rpcenv: &mut dyn RpcEnvironment, ) -> Result, Error> { - let (key_map, digest) = load_keys()?; + let (key_map, digest) = load_key_configs()?; let mut list = Vec::new(); - - for (_fingerprint, item) in key_map { + + for (fingerprint, item) in key_map { list.push(TapeKeyMetadata { hint: item.hint, - fingerprint: as_fingerprint(item.fingerprint.bytes()), + fingerprint: as_fingerprint(fingerprint.bytes()), }); } - + rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); Ok(list) @@ -71,11 +75,15 @@ pub fn list_keys( min_length: 5, }, hint: { - description: "Password restore hint", + description: "Password restore hint.", min_length: 1, + max_length: 32, }, }, }, + returns: { + schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, + }, )] /// Create a new encryption key pub fn create_key( @@ -84,26 +92,11 @@ pub fn create_key( _rpcenv: &mut dyn RpcEnvironment ) -> Result { - let key = openssl::sha::sha256(password.as_bytes()); // fixme: better KDF ?? + let (key, key_config) = generate_tape_encryption_key(password.as_bytes())?; - let item = EncryptionKeyInfo::new(&key, hint); + let fingerprint = key_config.fingerprint.clone().unwrap(); - let _lock = open_file_locked( - TAPE_KEYS_LOCKFILE, - std::time::Duration::new(10, 0), - true, - )?; - - let (mut key_map, _) = load_keys()?; - - let fingerprint = item.fingerprint.clone(); - - if let Some(_) = key_map.get(&fingerprint) { - bail!("encryption key '{}' already exists.", fingerprint); - } - - key_map.insert(fingerprint.clone(), item); - save_keys(key_map)?; + insert_key(key, key_config, hint)?; Ok(fingerprint) } @@ -131,25 +124,28 @@ pub fn delete_key( digest: Option, _rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - + let _lock = open_file_locked( TAPE_KEYS_LOCKFILE, std::time::Duration::new(10, 0), true, )?; - let (mut key_map, expected_digest) = load_keys()?; + let (mut config_map, expected_digest) = load_key_configs()?; + let (mut key_map, _) = load_keys()?; if let Some(ref digest) = digest { let digest = proxmox::tools::hex_to_digest(digest)?; crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; } - match key_map.get(&fingerprint) { - Some(_) => { key_map.remove(&fingerprint); }, + match config_map.get(&fingerprint) { + Some(_) => { config_map.remove(&fingerprint); }, None => bail!("tape encryption key '{}' does not exist.", fingerprint), } + save_key_configs(config_map)?; + key_map.remove(&fingerprint); save_keys(key_map)?; Ok(()) diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 12c79dd6..5cf18fe5 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -24,6 +24,7 @@ use crate::{ self, drive::check_drive_exists, }, + backup::decrypt_key_config, api2::{ types::{ UPID_SCHEMA, @@ -408,7 +409,7 @@ fn write_media_label( worker.log(format!("Label media '{}' for pool '{}'", label.label_text, pool)); let set = MediaSetLabel::with_data(&pool, [0u8; 16].into(), 0, label.ctime, None); - drive.write_media_set_label(&set)?; + drive.write_media_set_label(&set, None)?; media_set_label = Some(set); } else { worker.log(format!("Label media '{}' (no pool assignment)", label.label_text)); @@ -427,7 +428,7 @@ fn write_media_label( drive.rewind()?; match drive.read_label() { - Ok(Some(info)) => { + Ok((Some(info), _)) => { if info.label.uuid != media_id.label.uuid { bail!("verify label failed - got wrong label uuid"); } @@ -447,7 +448,7 @@ fn write_media_label( } } }, - Ok(None) => bail!("verify label failed (got empty media)"), + Ok((None, _)) => bail!("verify label failed (got empty media)"), Err(err) => bail!("verify label failed - {}", err), }; @@ -457,6 +458,46 @@ fn write_media_label( } #[api( + protected: true, + input: { + properties: { + drive: { + schema: DRIVE_NAME_SCHEMA, + }, + password: { + description: "Encryption key password.", + }, + }, + }, +)] +/// Try to restore a tape encryption key +pub async fn restore_key( + drive: String, + password: String, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + + tokio::task::spawn_blocking(move || { + let mut drive = open_drive(&config, &drive)?; + + let (_media_id, key_config) = drive.read_label()?; + + if let Some(key_config) = key_config { + let hint = String::from("fixme: add hint"); + // fixme: howto show restore hint + let password_fn = || { Ok(password.as_bytes().to_vec()) }; + let (key, ..) = decrypt_key_config(&key_config, &password_fn)?; + config::tape_encryption_keys::insert_key(key, key_config, hint)?; + } else { + bail!("media does not contain any encryption key configuration"); + } + + Ok(()) + }).await? +} + + #[api( input: { properties: { drive: { @@ -472,7 +513,7 @@ fn write_media_label( type: MediaIdFlat, }, )] -/// Read media label +/// Read media label (optionally inventorize media) pub async fn read_label( drive: String, inventorize: Option, @@ -483,7 +524,7 @@ pub async fn read_label( tokio::task::spawn_blocking(move || { let mut drive = open_drive(&config, &drive)?; - let media_id = drive.read_label()?; + let (media_id, _key_config) = drive.read_label()?; let media_id = match media_id { Some(media_id) => { @@ -723,10 +764,10 @@ pub fn update_inventory( Err(err) => { worker.warn(format!("unable to read label form media '{}' - {}", label_text, err)); } - Ok(None) => { + Ok((None, _)) => { worker.log(format!("media '{}' is empty", label_text)); } - Ok(Some(media_id)) => { + Ok((Some(media_id), _key_config)) => { if label_text != media_id.label.label_text { worker.warn(format!("label text missmatch ({} != {})", label_text, media_id.label.label_text)); continue; @@ -970,14 +1011,20 @@ pub fn catalog_media( drive.rewind()?; let media_id = match drive.read_label()? { - Some(media_id) => { + (Some(media_id), key_config) => { worker.log(format!( "found media label: {}", serde_json::to_string_pretty(&serde_json::to_value(&media_id)?)? )); + if key_config.is_some() { + worker.log(format!( + "encryption key config: {}", + serde_json::to_string_pretty(&serde_json::to_value(&key_config)?)? + )); + } media_id }, - None => bail!("media is empty (no media label found)"), + (None, _) => bail!("media is empty (no media label found)"), }; let status_path = Path::new(TAPE_STATUS_DIR); diff --git a/src/backup/crypt_config.rs b/src/backup/crypt_config.rs index 9efc9cf8..53dc1e41 100644 --- a/src/backup/crypt_config.rs +++ b/src/backup/crypt_config.rs @@ -22,10 +22,13 @@ use crate::tools::format::{as_fingerprint, bytes_as_fingerprint}; use proxmox::api::api; // openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint") -const FINGERPRINT_INPUT: [u8; 32] = [ 110, 208, 239, 119, 71, 31, 255, 77, - 85, 199, 168, 254, 74, 157, 182, 33, - 97, 64, 127, 19, 76, 114, 93, 223, - 48, 153, 45, 37, 236, 69, 237, 38, ]; +/// This constant is used to compute fingerprints. +const FINGERPRINT_INPUT: [u8; 32] = [ + 110, 208, 239, 119, 71, 31, 255, 77, + 85, 199, 168, 254, 74, 157, 182, 33, + 97, 64, 127, 19, 76, 114, 93, 223, + 48, 153, 45, 37, 236, 69, 237, 38, +]; #[api(default: "encrypt")] #[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] diff --git a/src/backup/key_derivation.rs b/src/backup/key_derivation.rs index 36e378d8..b0647618 100644 --- a/src/backup/key_derivation.rs +++ b/src/backup/key_derivation.rs @@ -30,7 +30,7 @@ impl Default for Kdf { } } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Clone, Debug)] pub enum KeyDerivationConfig { Scrypt { n: u64, @@ -82,7 +82,7 @@ impl KeyDerivationConfig { } } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Clone, Debug)] pub struct KeyConfig { pub kdf: Option, #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")] diff --git a/src/bin/proxmox_tape/encryption_key.rs b/src/bin/proxmox_tape/encryption_key.rs index 78c2dce7..9f913648 100644 --- a/src/bin/proxmox_tape/encryption_key.rs +++ b/src/bin/proxmox_tape/encryption_key.rs @@ -1,4 +1,4 @@ -use anyhow::Error; +use anyhow::{bail, Error}; use serde_json::Value; use proxmox::{ @@ -8,11 +8,16 @@ use proxmox::{ RpcEnvironment, ApiHandler, }, + sys::linux::tty, }; use proxmox_backup::{ + config, api2::{ self, + types::{ + DRIVE_NAME_SCHEMA, + }, }, config::tape_encryption_keys::complete_key_fingerprint, }; @@ -23,7 +28,11 @@ pub fn encryption_key_commands() -> CommandLineInterface { .insert("list", CliCommand::new(&API_METHOD_LIST_KEYS)) .insert( "create", - CliCommand::new(&api2::config::tape_encryption_keys::API_METHOD_CREATE_KEY) + CliCommand::new(&API_METHOD_CREATE_KEY) + ) + .insert( + "restore", + CliCommand::new(&API_METHOD_RESTORE_KEY) ) .insert( "remove", @@ -36,6 +45,79 @@ pub fn encryption_key_commands() -> CommandLineInterface { cmd_def.into() } +#[api( + input: { + properties: { + drive: { + schema: DRIVE_NAME_SCHEMA, + optional: true, + }, + }, + }, +)] +/// Restore encryption key from tape (read password from stdin) +async fn restore_key( + mut param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + param["drive"] = crate::lookup_drive_name(¶m, &config)?.into(); + + if !tty::stdin_isatty() { + bail!("no password input mechanism available"); + } + + let password = tty::read_password("Tepe Encryption Key Password: ")?; + param["password"] = String::from_utf8(password)?.into(); + + let info = &api2::tape::drive::API_METHOD_RESTORE_KEY; + match info.handler { + ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, + _ => unreachable!(), + }; + + Ok(()) +} + +#[api( + input: { + properties: { + hint: { + description: "Password restore hint.", + type: String, + min_length: 1, + max_length: 32, + }, + }, + }, +)] +/// Create key (read password from stdin) +fn create_key( + mut param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + if !tty::stdin_isatty() { + bail!("no password input mechanism available"); + } + + let password = tty::read_and_verify_password("Tape Encryption Key Password: ")?; + + param["password"] = String::from_utf8(password)?.into(); + + let info = &api2::config::tape_encryption_keys::API_METHOD_CREATE_KEY; + let fingerprint = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + println!("{}", fingerprint); + + Ok(()) +} + + #[api( input: { properties: { diff --git a/src/config/tape_encryption_keys.rs b/src/config/tape_encryption_keys.rs index 854c330f..ff565349 100644 --- a/src/config/tape_encryption_keys.rs +++ b/src/config/tape_encryption_keys.rs @@ -1,18 +1,22 @@ -use std::collections::HashMap; +use std::collections::{HashSet, HashMap}; use anyhow::{bail, Error}; use serde::{Deserialize, Serialize}; -use openssl::sha::sha256; use proxmox::tools::fs::{ file_read_optional_string, replace_file, + open_file_locked, CreateOptions, }; use crate::{ backup::{ Fingerprint, + Kdf, + KeyConfig, + CryptConfig, + encrypt_key_with_passphrase, }, }; @@ -41,29 +45,53 @@ mod hex_key { } } -/// Store Hardware Encryption keys +/// Store Hardware Encryption keys (private part) #[derive(Deserialize, Serialize)] pub struct EncryptionKeyInfo { - pub hint: String, + pub fingerprint: Fingerprint, #[serde(with = "hex_key")] pub key: [u8; 32], - pub fingerprint: Fingerprint, +} + +/// Store Hardware Encryption keys (public part) +#[derive(Deserialize, Serialize)] +pub struct EncryptionKeyConfig { + pub hint: String, + pub key_config: KeyConfig, +} + +pub fn compute_tape_key_fingerprint(key: &[u8; 32]) -> Result { + let crypt_config = CryptConfig::new(key.clone())?; + Ok(crypt_config.fingerprint()) +} + +pub fn generate_tape_encryption_key(password: &[u8]) -> Result<([u8; 32], KeyConfig), Error> { + + let mut key = [0u8; 32]; + proxmox::sys::linux::fill_with_random_data(&mut key)?; + + let mut key_config = encrypt_key_with_passphrase(&key, password, Kdf::Scrypt)?; + key_config.fingerprint = Some(compute_tape_key_fingerprint(&key)?); + Ok((key, key_config)) } impl EncryptionKeyInfo { + pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self { + Self { fingerprint, key } + } +} - pub fn new(key: &[u8; 32], hint: String) -> Self { - Self { - hint, - key: key.clone(), - fingerprint: Fingerprint::new(sha256(key)), - } +impl EncryptionKeyConfig { + pub fn new(key_config: KeyConfig, hint: String) -> Self { + Self { hint, key_config } } } pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json"; +pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-key-config.json"; pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck"; +/// Load tape encryption keys (private part) pub fn load_keys() -> Result<(HashMap, [u8;32]), Error> { let content = file_read_optional_string(TAPE_KEYS_FILENAME)?; @@ -71,12 +99,12 @@ pub fn load_keys() -> Result<(HashMap, [u8;32]) let digest = openssl::sha::sha256(content.as_bytes()); - let list: Vec = serde_json::from_str(&content)?; + let key_list: Vec = serde_json::from_str(&content)?; let mut map = HashMap::new(); - - for item in list { - let expected_fingerprint = Fingerprint::new(sha256(&item.key)); + + for item in key_list { + let expected_fingerprint = compute_tape_key_fingerprint(&item.key)?; if item.fingerprint != expected_fingerprint { bail!( "inconsistent fingerprint ({} != {})", @@ -84,10 +112,42 @@ pub fn load_keys() -> Result<(HashMap, [u8;32]) expected_fingerprint, ); } - - map.insert(item.fingerprint.clone(), item); + + if map.insert(item.fingerprint.clone(), item).is_some() { + bail!("found duplicate fingerprint"); + } } - + + Ok((map, digest)) +} + +/// Load tape encryption key configurations (public part) +pub fn load_key_configs() -> Result<(HashMap, [u8;32]), Error> { + + let content = file_read_optional_string(TAPE_KEY_CONFIG_FILENAME)?; + let content = content.unwrap_or_else(|| String::from("[]")); + + let digest = openssl::sha::sha256(content.as_bytes()); + + let key_list: Vec = serde_json::from_str(&content)?; + + let mut map = HashMap::new(); + let mut hint_set = HashSet::new(); + + for item in key_list { + match item.key_config.fingerprint { + Some(ref fingerprint) => { + if !hint_set.insert(item.hint.clone()) { + bail!("found duplicate password hint '{}'", item.hint); + } + if map.insert(fingerprint.clone(), item).is_some() { + bail!("found duplicate fingerprint"); + } + } + None => bail!("missing fingerprint"), + } + } + Ok((map, digest)) } @@ -100,7 +160,7 @@ pub fn save_keys(map: HashMap) -> Result<(), Err } let raw = serde_json::to_string_pretty(&list)?; - + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); // set the correct owner/group/permissions while saving file // owner(rw) = root, group(r)= root @@ -110,17 +170,77 @@ pub fn save_keys(map: HashMap) -> Result<(), Err .group(nix::unistd::Gid::from_raw(0)); replace_file(TAPE_KEYS_FILENAME, raw.as_bytes(), options)?; - + Ok(()) } +pub fn save_key_configs(map: HashMap) -> Result<(), Error> { + + let mut list = Vec::new(); + + let mut hint_set = HashSet::new(); + + for (_fp, item) in map { + if !hint_set.insert(item.hint.clone()) { + bail!("found duplicate password hint '{}'", item.hint); + } + list.push(item); + } + + let raw = serde_json::to_string_pretty(&list)?; + + let backup_user = crate::backup::backup_user()?; + let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); + // set the correct owner/group/permissions while saving file + // owner(rw) = root, group(r)= backup + let options = CreateOptions::new() + .perm(mode) + .owner(nix::unistd::ROOT) + .group(backup_user.gid); + + replace_file(TAPE_KEY_CONFIG_FILENAME, raw.as_bytes(), options)?; + + Ok(()) +} + +pub fn insert_key(key: [u8;32], key_config: KeyConfig, hint: String) -> Result<(), Error> { + + let _lock = open_file_locked( + TAPE_KEYS_LOCKFILE, + std::time::Duration::new(10, 0), + true, + )?; + + let (mut key_map, _) = load_keys()?; + let (mut config_map, _) = load_key_configs()?; + + let fingerprint = match key_config.fingerprint.clone() { + Some(fingerprint) => fingerprint, + None => bail!("missing encryption key fingerprint - internal error"), + }; + + if let Some(_) = config_map.get(&fingerprint) { + bail!("encryption key '{}' already exists.", fingerprint); + } + + let item = EncryptionKeyInfo::new(key, fingerprint.clone()); + key_map.insert(fingerprint.clone(), item); + save_keys(key_map)?; + + let item = EncryptionKeyConfig::new(key_config, hint); + config_map.insert(fingerprint.clone(), item); + save_key_configs(config_map)?; + + Ok(()) + +} + // shell completion helper pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap) -> Vec { - let data = match load_keys() { + let data = match load_key_configs() { Ok((data, _digest)) => data, Err(_) => return Vec::new(), }; data.keys().map(|fp| crate::tools::format::as_fingerprint(fp.bytes())).collect() } - diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs index 85088495..a23524a7 100644 --- a/src/tape/drive/linux_tape.rs +++ b/src/tape/drive/linux_tape.rs @@ -10,7 +10,10 @@ use proxmox::sys::error::SysResult; use crate::{ config, - backup::Fingerprint, + backup::{ + Fingerprint, + KeyConfig, + }, tools::run_command, api2::types::{ TapeDensity, @@ -448,7 +451,11 @@ impl TapeDriver for LinuxTapeHandle { Ok(Box::new(handle)) } - fn write_media_set_label(&mut self, media_set_label: &MediaSetLabel) -> Result<(), Error> { + fn write_media_set_label( + &mut self, + media_set_label: &MediaSetLabel, + key_config: Option<&KeyConfig>, + ) -> Result<(), Error> { let file_number = self.current_file_number()?; if file_number != 1 { @@ -466,7 +473,20 @@ impl TapeDriver for LinuxTapeHandle { let mut handle = TapeWriterHandle { writer: BlockedWriter::new(&mut self.file), }; - let raw = serde_json::to_string_pretty(&serde_json::to_value(media_set_label)?)?; + + let mut value = serde_json::to_value(media_set_label)?; + if media_set_label.encryption_key_fingerprint.is_some() { + match key_config { + Some(key_config) => { + value["key-config"] = serde_json::to_value(key_config)?; + } + None => { + bail!("missing encryption key config"); + } + } + } + + let raw = serde_json::to_string_pretty(&value)?; let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32); handle.write_header(&header, raw.as_bytes())?; diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index ed272501..b651f742 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -20,12 +20,16 @@ pub use linux_list_drives::*; use anyhow::{bail, format_err, Error}; use ::serde::{Deserialize}; +use serde_json::Value; use proxmox::tools::io::ReadExt; use proxmox::api::section_config::SectionConfigData; use crate::{ - backup::Fingerprint, + backup::{ + Fingerprint, + KeyConfig, + }, api2::types::{ VirtualTapeDrive, LinuxTapeDrive, @@ -101,18 +105,26 @@ pub trait TapeDriver { } /// Write the media set label to tape - fn write_media_set_label(&mut self, media_set_label: &MediaSetLabel) -> Result<(), Error>; + /// + /// If the media-set is encrypted, we also store the encryption + /// key_config, so that it is possible to restore the key. + fn write_media_set_label( + &mut self, + media_set_label: &MediaSetLabel, + key_config: Option<&KeyConfig>, + ) -> Result<(), Error>; /// Read the media label /// - /// This tries to read both media labels (label and media_set_label). - fn read_label(&mut self) -> Result, Error> { + /// This tries to read both media labels (label and + /// media_set_label). Also returns the optional encryption key configuration. + fn read_label(&mut self) -> Result<(Option, Option), Error> { self.rewind()?; let label = { let mut reader = match self.read_next_file()? { - None => return Ok(None), // tape is empty + None => return Ok((None, None)), // tape is empty Some(reader) => reader, }; @@ -135,7 +147,7 @@ pub trait TapeDriver { // try to read MediaSet label let mut reader = match self.read_next_file()? { - None => return Ok(Some(media_id)), + None => return Ok((Some(media_id), None)), Some(reader) => reader, }; @@ -143,7 +155,17 @@ pub trait TapeDriver { header.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, 1, 64*1024)?; let data = reader.read_exact_allocated(header.size as usize)?; - let media_set_label: MediaSetLabel = serde_json::from_slice(&data) + let mut data: Value = serde_json::from_slice(&data) + .map_err(|err| format_err!("unable to parse media set label - {}", err))?; + + let key_config_value = data["key-config"].take(); + let key_config: Option = if !key_config_value.is_null() { + Some(serde_json::from_value(key_config_value)?) + } else { + None + }; + + let media_set_label: MediaSetLabel = serde_json::from_value(data) .map_err(|err| format_err!("unable to parse media set label - {}", err))?; // make sure we read the EOF marker @@ -153,7 +175,7 @@ pub trait TapeDriver { media_id.media_set_label = Some(media_set_label); - Ok(Some(media_id)) + Ok((Some(media_id), key_config)) } /// Eject media @@ -278,7 +300,7 @@ pub fn request_and_load_media( ), Error> { let check_label = |handle: &mut dyn TapeDriver, uuid: &proxmox::tools::Uuid| { - if let Ok(Some(media_id)) = handle.read_label() { + if let Ok((Some(media_id), _)) = handle.read_label() { worker.log(format!( "found media label {} ({})", media_id.label.label_text, @@ -348,7 +370,7 @@ pub fn request_and_load_media( }; match handle.read_label() { - Ok(Some(media_id)) => { + Ok((Some(media_id), _)) => { if media_id.label.uuid == label.uuid { worker.log(format!( "found media label {} ({})", @@ -367,7 +389,7 @@ pub fn request_and_load_media( } } } - Ok(None) => { + Ok((None, _)) => { if last_media_uuid.is_some() { worker.log(format!("found empty media without label (please label all tapes first)")); last_media_uuid = None; diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs index b600079b..06de1cd6 100644 --- a/src/tape/drive/virtual_tape.rs +++ b/src/tape/drive/virtual_tape.rs @@ -11,6 +11,7 @@ use proxmox::tools::{ }; use crate::{ + backup::KeyConfig, tape::{ TapeWrite, TapeRead, @@ -325,10 +326,18 @@ impl TapeDriver for VirtualTapeHandle { } } - fn write_media_set_label(&mut self, media_set_label: &MediaSetLabel) -> Result<(), Error> { + fn write_media_set_label( + &mut self, + media_set_label: &MediaSetLabel, + key_config: Option<&KeyConfig>, + ) -> Result<(), Error> { self.set_encryption(None)?; + if key_config.is_some() { + bail!("encryption is not implemented - internal error"); + } + let mut status = self.load_status()?; match status.current_tape { Some(VirtualTapeStatus { ref name, ref mut pos }) => { diff --git a/src/tape/pool_writer.rs b/src/tape/pool_writer.rs index f017497e..b04bfddb 100644 --- a/src/tape/pool_writer.rs +++ b/src/tape/pool_writer.rs @@ -30,6 +30,7 @@ use crate::{ media_changer, file_formats::MediaSetLabel, }, + config::tape_encryption_keys::load_key_configs, }; @@ -452,12 +453,27 @@ fn update_media_set_label( Some(ref set) => set, }; + let key_config = if let Some(ref fingerprint) = new_set.encryption_key_fingerprint { + let (config_map, _digest) = load_key_configs()?; + match config_map.get(fingerprint) { + Some(item) => { + // fixme: store item.hint??? should be in key-config instead + Some(item.key_config.clone()) + } + None => { + bail!("unable to find tape encryption key config '{}'", fingerprint); + } + } + } else { + None + }; + let status_path = Path::new(TAPE_STATUS_DIR); match old_set { None => { worker.log(format!("wrinting new media set label")); - drive.write_media_set_label(new_set)?; + drive.write_media_set_label(new_set, key_config.as_ref())?; media_catalog = MediaCatalog::overwrite(status_path, media_id, false)?; } Some(media_set_label) => { @@ -476,7 +492,7 @@ fn update_media_set_label( media_set_label.uuid.to_string(), media_set_label.seq_nr) ); - drive.write_media_set_label(new_set)?; + drive.write_media_set_label(new_set, key_config.as_ref())?; media_catalog = MediaCatalog::overwrite(status_path, media_id, false)?; } }