tape: add scsi commands to control drive hardware encryption
This commit is contained in:
parent
0c5b9e7820
commit
90950c9c20
298
src/tape/drive/encryption.rs
Normal file
298
src/tape/drive/encryption.rs
Normal file
@ -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<F: AsRawFd>(
|
||||||
|
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<F: AsRawFd>(
|
||||||
|
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<F: AsRawFd>(
|
||||||
|
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::<SspSetDataEncryptionPage>();
|
||||||
|
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<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, 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<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, 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<u8, Error> {
|
||||||
|
|
||||||
|
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<DataEncryptionStatus, Error> {
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
@ -7,6 +7,9 @@ pub use tape_alert_flags::*;
|
|||||||
mod volume_statistics;
|
mod volume_statistics;
|
||||||
pub use volume_statistics::*;
|
pub use volume_statistics::*;
|
||||||
|
|
||||||
|
mod encryption;
|
||||||
|
pub use encryption::*;
|
||||||
|
|
||||||
pub mod linux_tape;
|
pub mod linux_tape;
|
||||||
|
|
||||||
mod mam;
|
mod mam;
|
||||||
|
Loading…
Reference in New Issue
Block a user