2 use std
::os
::unix
::prelude
::AsRawFd
;
4 use anyhow
::{bail, format_err, Error}
;
5 use endian_trait
::Endian
;
7 use proxmox_io
::{ReadExt, WriteExt}
;
9 use crate::sgutils2
::{alloc_page_aligned_buffer, SgRaw}
;
11 /// Set or clear encryption key
13 /// We always use mixed mode,
14 pub fn drive_set_encryption
<F
: AsRawFd
>(file
: &mut F
, key
: Option
<[u8; 32]>) -> Result
<(), Error
> {
15 let data
= match sg_spin_data_encryption_caps(file
) {
17 Err(_
) if key
.is_none() => {
18 // Assume device does not support HW encryption
19 // We can simply ignore the clear key request
22 Err(err
) => return Err(err
),
25 let algorithm_index
= decode_spin_data_encryption_caps(&data
)?
;
27 sg_spout_set_encryption(file
, algorithm_index
, key
)?
;
29 let data
= sg_spin_data_encryption_status(file
)?
;
30 let status
= decode_spin_data_encryption_status(&data
)?
;
33 DataEncryptionMode
::Off
=> {
38 DataEncryptionMode
::Mixed
=> {
46 bail
!("got unexpected encryption mode {:?}", status
.mode
);
49 /// Returns if encryption is enabled on the drive
50 pub fn drive_get_encryption
<F
: AsRawFd
>(file
: &mut F
) -> Result
<bool
, Error
> {
51 let data
= match sg_spin_data_encryption_status(file
) {
54 // Assume device does not support HW encryption
58 let status
= decode_spin_data_encryption_status(&data
)?
;
60 // these three below have all encryption enabled, and only differ in how decryption is
62 DataEncryptionMode
::On
=> Ok(true),
63 DataEncryptionMode
::Mixed
=> Ok(true),
64 DataEncryptionMode
::RawRead
=> Ok(true),
65 // currently, the mode below is the only one that has encryption actually disabled
66 DataEncryptionMode
::Off
=> Ok(false),
72 struct SspSetDataEncryptionPage
{
86 #[allow(clippy::vec_init_then_push)]
87 fn sg_spout_set_encryption
<F
: AsRawFd
>(
90 key
: Option
<[u8; 32]>,
91 ) -> Result
<(), Error
> {
92 let mut sg_raw
= SgRaw
::new(file
, 0)?
;
94 let mut outbuf_len
= std
::mem
::size_of
::<SspSetDataEncryptionPage
>();
95 if let Some(ref key
) = key
{
96 outbuf_len
+= key
.len();
99 let mut outbuf
= alloc_page_aligned_buffer(outbuf_len
)?
;
102 let page
= SspSetDataEncryptionPage
{
104 page_len
: (outbuf_len
- 4) as u16,
105 scope_byte
: (0b10 << 5), // all IT nexus
106 control_byte_5
: (chok
<< 2),
107 encryption_mode
: if key
.is_some() { 2 }
else { 0 }
,
108 decryption_mode
: if key
.is_some() { 3 }
else { 0 }
, // mixed mode
112 key_len
: if let Some(ref key
) = key
{
119 let mut writer
= &mut outbuf
[..];
120 unsafe { writer.write_be_value(page)? }
;
122 if let Some(ref key
) = key
{
123 writer
.write_all(key
)?
;
126 let mut cmd
= Vec
::new();
127 cmd
.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
128 cmd
.push(0x20); // Tape Data Encryption Page
130 cmd
.push(0x10); // Set Data Encryption page
133 cmd
.extend((outbuf_len
as u32).to_be_bytes()); // data out len
138 .do_out_command(&cmd
, &outbuf
)
139 .map_err(|err
| format_err
!("set data encryption SPOUT(20h[0010h]) failed - {}", err
))
142 // Warning: this blocks and fails if there is no media loaded
143 #[allow(clippy::vec_init_then_push)]
144 fn sg_spin_data_encryption_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<Vec
<u8>, Error
> {
145 let allocation_len
: u32 = 8192 + 4;
147 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
149 let mut cmd
= Vec
::new();
150 cmd
.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
151 cmd
.push(0x20); // Tape Data Encryption Page
153 cmd
.push(0x20); // Data Encryption Status page
156 cmd
.extend(allocation_len
.to_be_bytes());
164 "read data encryption status SPIN(20h[0020h]) failed - {}",
171 // Warning: this blocks and fails if there is no media loaded
172 #[allow(clippy::vec_init_then_push)]
173 fn sg_spin_data_encryption_caps
<F
: AsRawFd
>(file
: &mut F
) -> Result
<Vec
<u8>, Error
> {
174 let allocation_len
: u32 = 8192 + 4;
176 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
178 let mut cmd
= Vec
::new();
179 cmd
.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
180 cmd
.push(0x20); // Tape Data Encryption Page
182 cmd
.push(0x10); // Data Encryption Capabilities page
185 cmd
.extend(allocation_len
.to_be_bytes());
193 "read data encryption caps SPIN(20h[0010h]) failed - {}",
200 #[derive(Debug, PartialEq, Eq)]
201 enum DataEncryptionMode
{
209 struct DataEncryptionStatus
{
210 mode
: DataEncryptionMode
,
215 struct SspDataEncryptionCapabilityPage
{
223 struct SspDataEncryptionAlgorithmDescriptor
{
239 // Returns the algorythm_index for AES-GCM
240 fn decode_spin_data_encryption_caps(data
: &[u8]) -> Result
<u8, Error
> {
241 proxmox_lang
::try_block
!({
242 let mut reader
= data
;
243 let _page
: SspDataEncryptionCapabilityPage
= unsafe { reader.read_be_value()? }
;
245 let mut aes_gcm_index
= None
;
248 if reader
.is_empty() {
251 let desc
: SspDataEncryptionAlgorithmDescriptor
= unsafe { reader.read_be_value()? }
;
252 if desc
.descriptor_len
!= 0x14 {
253 bail
!("got wrong key descriptor len");
255 if (desc
.control_byte_4
& 0b00000011) != 2 {
256 continue; // can't encrypt in hardware
258 if ((desc
.control_byte_4
& 0b00001100) >> 2) != 2 {
259 continue; // can't decrypt in hardware
261 if desc
.algorithm_code
== 0x00010014 && desc
.key_size
== 32 {
262 aes_gcm_index
= Some(desc
.algorythm_index
);
267 match aes_gcm_index
{
268 Some(index
) => Ok(index
),
269 None
=> bail
!("drive does not support AES-GCM encryption"),
272 .map_err(|err
: Error
| format_err
!("decode data encryption caps page failed - {}", err
))
277 struct SspDataEncryptionStatusPage
{
284 key_instance_counter
: u32,
291 fn decode_spin_data_encryption_status(data
: &[u8]) -> Result
<DataEncryptionStatus
, Error
> {
292 proxmox_lang
::try_block
!({
293 let mut reader
= data
;
294 let page
: SspDataEncryptionStatusPage
= unsafe { reader.read_be_value()? }
;
296 if page
.page_code
!= 0x20 {
297 bail
!("invalid response");
300 let mode
= match (page
.encryption_mode
, page
.decryption_mode
) {
301 (0, 0) => DataEncryptionMode
::Off
,
302 (2, 1) => DataEncryptionMode
::RawRead
,
303 (2, 2) => DataEncryptionMode
::On
,
304 (2, 3) => DataEncryptionMode
::Mixed
,
305 _
=> bail
!("unknown encryption mode"),
308 let status
= DataEncryptionStatus { mode }
;
312 .map_err(|err
| format_err
!("decode data encryption status page failed - {}", err
))