diff --git a/src/tape/drive/encryption.rs b/src/tape/drive/encryption.rs new file mode 100644 index 00000000..7a6da8f3 --- /dev/null +++ b/src/tape/drive/encryption.rs @@ -0,0 +1,298 @@ +use std::os::unix::prelude::AsRawFd; + +use anyhow::{bail, format_err, Error}; +use endian_trait::Endian; + +use proxmox::tools::io::{ReadExt, WriteExt}; + +use crate::{ + tape::sgutils2::{ + SgRaw, + alloc_page_aligned_buffer, + }, +}; + +/// Test if drive supports hardware encryption +/// +/// We search for AES_CGM algorithm with 256bits key. +pub fn has_encryption( + file: &mut F, +) -> bool { + + let data = match sg_spin_data_encryption_caps(file) { + Ok(data) => data, + Err(_) => return false, + }; + match decode_spin_data_encryption_caps(&data) { + Ok(_) => true, + Err(_) => false, + } +} + +/// Set or clear encryption key +/// +/// We always use mixed mode, +pub fn set_encryption( + file: &mut F, + key: Option<[u8; 32]>, +) -> Result<(), Error> { + + let data = sg_spin_data_encryption_caps(file)?; + let algorithm_index = decode_spin_data_encryption_caps(&data)?; + + sg_spout_set_encryption(file, algorithm_index, key)?; + + let data = sg_spin_data_encryption_status(file)?; + let status = decode_spin_data_encryption_status(&data)?; + + match status.mode { + DataEncryptionMode::Off => { + if key.is_none() { + return Ok(()); + } + } + DataEncryptionMode::Mixed => { + if key.is_some() { + return Ok(()); + } + } + _ => {} + } + + bail!("got unexpected encryption mode {:?}", status.mode); +} + +#[derive(Endian)] +#[repr(C, packed)] +struct SspSetDataEncryptionPage { + page_code: u16, + page_len: u16, + scope_byte: u8, + control_byte_5: u8, + encryption_mode: u8, + decryption_mode: u8, + algorythm_index: u8, + key_format: u8, + reserved: [u8; 8], + key_len: u16, + key: [u8; 32], +} + +fn sg_spout_set_encryption( + file: &mut F, + algorythm_index: u8, + key: Option<[u8; 32]>, +) -> Result<(), Error> { + + let mut sg_raw = SgRaw::new(file, 0)?; + + let outbuf_len = std::mem::size_of::(); + let mut outbuf = alloc_page_aligned_buffer(outbuf_len)?; + let chok: u8 = 0; + + let page = SspSetDataEncryptionPage { + page_code: 0x10, + page_len: (outbuf_len - 4) as u16, + scope_byte: (0b10 << 5), // all IT nexus + control_byte_5: (chok << 2), + encryption_mode: if key.is_some() { 2 } else { 0 }, + decryption_mode: if key.is_some() { 3 } else { 0 }, // mixed mode + algorythm_index, + key_format: 0, + reserved: [0u8; 8], + key_len: 32, + key: match key { + Some(key) => key, + None => [0u8; 32], + } + }; + + let mut writer = &mut outbuf[..]; + unsafe { writer.write_be_value(page)? }; + + let mut cmd = Vec::new(); + cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT) + cmd.push(0x20); // Tape Data Encryption Page + cmd.push(0);cmd.push(0x10); // Set Data Encryption page + cmd.push(0); + cmd.push(0); + cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len + cmd.push(0); + cmd.push(0); + + sg_raw.do_out_command(&cmd, &outbuf) + .map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err)) +} + +// Warning: this blocks and fails if there is no media loaded +fn sg_spin_data_encryption_status(file: &mut F) -> Result, Error> { + + let allocation_len: u32 = 8192+4; + + let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; + + let mut cmd = Vec::new(); + cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN) + cmd.push(0x20); // Tape Data Encryption Page + cmd.push(0);cmd.push(0x20); // Data Encryption Status page + cmd.push(0); + cmd.push(0); + cmd.extend(&allocation_len.to_be_bytes()); + cmd.push(0); + cmd.push(0); + + sg_raw.do_command(&cmd) + .map_err(|err| format_err!("read data encryption status SPIN(20h[0020h]) failed - {}", err)) + .map(|v| v.to_vec()) +} + +// Warning: this blocks and fails if there is no media loaded +fn sg_spin_data_encryption_caps(file: &mut F) -> Result, Error> { + + let allocation_len: u32 = 8192+4; + + let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; + + let mut cmd = Vec::new(); + cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN) + cmd.push(0x20); // Tape Data Encryption Page + cmd.push(0);cmd.push(0x10); // Data Encryption Capabilities page + cmd.push(0); + cmd.push(0); + cmd.extend(&allocation_len.to_be_bytes()); + cmd.push(0); + cmd.push(0); + + sg_raw.do_command(&cmd) + .map_err(|err| format_err!("read data encryption caps SPIN(20h[0010h]) failed - {}", err)) + .map(|v| v.to_vec()) +} + +#[derive(Debug)] +enum DataEncryptionMode { + On, + Mixed, + RawRead, + Off, +} + +#[derive(Debug)] +struct DataEncryptionStatus { + mode: DataEncryptionMode, +} + +#[derive(Debug, Endian)] +#[repr(C, packed)] +struct SspDataEncryptionCapabilityPage { + page_code: u16, + page_len: u16, + extdecc_cfgp_byte: u8, + reserverd: [u8; 15], +} + +#[derive(Debug, Endian)] +#[repr(C, packed)] +struct SspDataEncryptionAlgorithmDescriptor { + algorythm_index: u8, + reserved1: u8, + descriptor_len: u16, + control_byte_4: u8, + control_byte_5: u8, + max_ucad_bytes: u16, + max_acad_bytes: u16, + key_size: u16, + control_byte_12: u8, + reserved2: u8, + msdk_count: u16, + reserved3: [u8; 4], + algorithm_code: u32, +} + +// Returns the algorythm_index for AES-CGM +fn decode_spin_data_encryption_caps(data: &[u8]) -> Result { + + proxmox::try_block!({ + let mut reader = &data[..]; + let page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? }; + + let extdecc = (page.extdecc_cfgp_byte & 0b00001100) >> 2; + if extdecc != 2 { + bail!("not external data encryption control capable"); + } + + let cfg_p = page.extdecc_cfgp_byte & 0b00000011; + if cfg_p != 1 { + bail!("not allow to change logical block encryption parameters"); + } + + let mut aes_cgm_index = None; + + loop { + if reader.is_empty() { break; }; + let desc: SspDataEncryptionAlgorithmDescriptor = + unsafe { reader.read_be_value()? }; + if desc.descriptor_len != 0x14 { + bail!("got wrong key descriptior len"); + } + if (desc.control_byte_4 & 0b00000011) != 2 { + continue; // cant encrypt in hardware + } + if ((desc.control_byte_4 & 0b00001100) >> 2) != 2 { + continue; // cant decrypt in hardware + } + if desc.algorithm_code == 0x00010014 && desc.key_size == 32 { + aes_cgm_index = Some(desc.algorythm_index); + break; + } + } + + match aes_cgm_index { + Some(index) => Ok(index), + None => bail!("drive dies not support AES-CGM encryption"), + } + }).map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err)) + +} + +#[derive(Debug, Endian)] +#[repr(C, packed)] +struct SspDataEncryptionStatusPage { + page_code: u16, + page_len: u16, + scope_byte: u8, + encryption_mode: u8, + decryption_mode: u8, + algorythm_index: u8, + key_instance_counter: u32, + control_byte: u8, + key_format: u8, + key_len: u16, + reserverd: [u8; 8], +} + +fn decode_spin_data_encryption_status(data: &[u8]) -> Result { + + proxmox::try_block!({ + let mut reader = &data[..]; + let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? }; + + if page.page_code != 0x20 { + bail!("invalid response"); + } + + let mode = match (page.encryption_mode, page.decryption_mode) { + (0, 0) => DataEncryptionMode::Off, + (2, 1) => DataEncryptionMode::RawRead, + (2, 2) => DataEncryptionMode::On, + (2, 3) => DataEncryptionMode::Mixed, + _ => bail!("unknown encryption mode"), + }; + + let status = DataEncryptionStatus { + mode, + }; + + Ok(status) + + }).map_err(|err| format_err!("decode data encryption status page failed - {}", err)) +} diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index d43274b0..5c710bde 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -7,6 +7,9 @@ pub use tape_alert_flags::*; mod volume_statistics; pub use volume_statistics::*; +mod encryption; +pub use encryption::*; + pub mod linux_tape; mod mam;