]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-tape/src/sg_tape/encryption.rs
tape: improve function naming
[proxmox-backup.git] / pbs-tape / src / sg_tape / encryption.rs
CommitLineData
b22b6c22 1use std::io::Write;
b23adfd4 2use std::os::unix::prelude::AsRawFd;
90950c9c
DM
3
4use anyhow::{bail, format_err, Error};
5use endian_trait::Endian;
6
6ef1b649 7use proxmox_io::{ReadExt, WriteExt};
90950c9c 8
b23adfd4 9use crate::sgutils2::{alloc_page_aligned_buffer, SgRaw};
90950c9c
DM
10
11/// Test if drive supports hardware encryption
12///
572e6594 13/// We search for AES_GCM algorithm with 256bits key.
b23adfd4 14pub fn has_encryption<F: AsRawFd>(file: &mut F) -> bool {
90950c9c
DM
15 let data = match sg_spin_data_encryption_caps(file) {
16 Ok(data) => data,
17 Err(_) => return false,
18 };
ea368a06 19 decode_spin_data_encryption_caps(&data).is_ok()
90950c9c
DM
20}
21
22/// Set or clear encryption key
23///
24/// We always use mixed mode,
8cc8aa51 25pub fn drive_set_encryption<F: AsRawFd>(file: &mut F, key: Option<[u8; 32]>) -> Result<(), Error> {
979dccc7
DM
26 let data = match sg_spin_data_encryption_caps(file) {
27 Ok(data) => data,
0107fd32
DM
28 Err(_) if key.is_none() => {
29 // Assume device does not support HW encryption
30 // We can simply ignore the clear key request
979dccc7
DM
31 return Ok(());
32 }
33 Err(err) => return Err(err),
34 };
35
90950c9c
DM
36 let algorithm_index = decode_spin_data_encryption_caps(&data)?;
37
38 sg_spout_set_encryption(file, algorithm_index, key)?;
39
40 let data = sg_spin_data_encryption_status(file)?;
41 let status = decode_spin_data_encryption_status(&data)?;
42
43 match status.mode {
44 DataEncryptionMode::Off => {
45 if key.is_none() {
46 return Ok(());
47 }
48 }
49 DataEncryptionMode::Mixed => {
50 if key.is_some() {
51 return Ok(());
52 }
53 }
54 _ => {}
55 }
56
57 bail!("got unexpected encryption mode {:?}", status.mode);
58}
59
60#[derive(Endian)]
61#[repr(C, packed)]
62struct SspSetDataEncryptionPage {
63 page_code: u16,
64 page_len: u16,
65 scope_byte: u8,
66 control_byte_5: u8,
67 encryption_mode: u8,
68 decryption_mode: u8,
69 algorythm_index: u8,
70 key_format: u8,
71 reserved: [u8; 8],
72 key_len: u16,
b22b6c22 73 /* key follows */
90950c9c
DM
74}
75
e1db0670 76#[allow(clippy::vec_init_then_push)]
90950c9c
DM
77fn sg_spout_set_encryption<F: AsRawFd>(
78 file: &mut F,
79 algorythm_index: u8,
80 key: Option<[u8; 32]>,
81) -> Result<(), Error> {
90950c9c
DM
82 let mut sg_raw = SgRaw::new(file, 0)?;
83
b22b6c22
DM
84 let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>();
85 if let Some(ref key) = key {
86 outbuf_len += key.len();
87 }
88
90950c9c
DM
89 let mut outbuf = alloc_page_aligned_buffer(outbuf_len)?;
90 let chok: u8 = 0;
91
92 let page = SspSetDataEncryptionPage {
93 page_code: 0x10,
94 page_len: (outbuf_len - 4) as u16,
95 scope_byte: (0b10 << 5), // all IT nexus
96 control_byte_5: (chok << 2),
97 encryption_mode: if key.is_some() { 2 } else { 0 },
98 decryption_mode: if key.is_some() { 3 } else { 0 }, // mixed mode
99 algorythm_index,
100 key_format: 0,
101 reserved: [0u8; 8],
b23adfd4
TL
102 key_len: if let Some(ref key) = key {
103 key.len() as u16
104 } else {
105 0
106 },
90950c9c
DM
107 };
108
109 let mut writer = &mut outbuf[..];
110 unsafe { writer.write_be_value(page)? };
111
b22b6c22
DM
112 if let Some(ref key) = key {
113 writer.write_all(key)?;
114 }
115
90950c9c
DM
116 let mut cmd = Vec::new();
117 cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
118 cmd.push(0x20); // Tape Data Encryption Page
b23adfd4
TL
119 cmd.push(0);
120 cmd.push(0x10); // Set Data Encryption page
90950c9c
DM
121 cmd.push(0);
122 cmd.push(0);
16f6766a 123 cmd.extend((outbuf_len as u32).to_be_bytes()); // data out len
90950c9c
DM
124 cmd.push(0);
125 cmd.push(0);
126
b23adfd4
TL
127 sg_raw
128 .do_out_command(&cmd, &outbuf)
90950c9c
DM
129 .map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err))
130}
131
132// Warning: this blocks and fails if there is no media loaded
e1db0670 133#[allow(clippy::vec_init_then_push)]
90950c9c 134fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
b23adfd4 135 let allocation_len: u32 = 8192 + 4;
90950c9c
DM
136
137 let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
138
139 let mut cmd = Vec::new();
140 cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
141 cmd.push(0x20); // Tape Data Encryption Page
b23adfd4
TL
142 cmd.push(0);
143 cmd.push(0x20); // Data Encryption Status page
90950c9c
DM
144 cmd.push(0);
145 cmd.push(0);
16f6766a 146 cmd.extend(allocation_len.to_be_bytes());
90950c9c
DM
147 cmd.push(0);
148 cmd.push(0);
149
b23adfd4
TL
150 sg_raw
151 .do_command(&cmd)
152 .map_err(|err| {
153 format_err!(
154 "read data encryption status SPIN(20h[0020h]) failed - {}",
155 err
156 )
157 })
90950c9c
DM
158 .map(|v| v.to_vec())
159}
160
161// Warning: this blocks and fails if there is no media loaded
e1db0670 162#[allow(clippy::vec_init_then_push)]
90950c9c 163fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
b23adfd4 164 let allocation_len: u32 = 8192 + 4;
90950c9c
DM
165
166 let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
167
168 let mut cmd = Vec::new();
169 cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
170 cmd.push(0x20); // Tape Data Encryption Page
b23adfd4
TL
171 cmd.push(0);
172 cmd.push(0x10); // Data Encryption Capabilities page
90950c9c
DM
173 cmd.push(0);
174 cmd.push(0);
16f6766a 175 cmd.extend(allocation_len.to_be_bytes());
90950c9c
DM
176 cmd.push(0);
177 cmd.push(0);
178
b23adfd4
TL
179 sg_raw
180 .do_command(&cmd)
181 .map_err(|err| {
182 format_err!(
183 "read data encryption caps SPIN(20h[0010h]) failed - {}",
184 err
185 )
186 })
90950c9c
DM
187 .map(|v| v.to_vec())
188}
189
190#[derive(Debug)]
191enum DataEncryptionMode {
192 On,
193 Mixed,
194 RawRead,
195 Off,
196}
197
198#[derive(Debug)]
199struct DataEncryptionStatus {
200 mode: DataEncryptionMode,
201}
202
b22b6c22 203#[derive(Endian)]
90950c9c
DM
204#[repr(C, packed)]
205struct SspDataEncryptionCapabilityPage {
206 page_code: u16,
207 page_len: u16,
84737fb3 208 reserved: [u8; 16],
90950c9c
DM
209}
210
b22b6c22 211#[derive(Endian)]
90950c9c
DM
212#[repr(C, packed)]
213struct SspDataEncryptionAlgorithmDescriptor {
214 algorythm_index: u8,
215 reserved1: u8,
216 descriptor_len: u16,
217 control_byte_4: u8,
218 control_byte_5: u8,
219 max_ucad_bytes: u16,
220 max_acad_bytes: u16,
221 key_size: u16,
222 control_byte_12: u8,
223 reserved2: u8,
224 msdk_count: u16,
225 reserved3: [u8; 4],
226 algorithm_code: u32,
227}
228
572e6594 229// Returns the algorythm_index for AES-GCM
90950c9c 230fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
6ef1b649 231 proxmox_lang::try_block!({
dcf5a0f6 232 let mut reader = data;
84737fb3 233 let _page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? };
90950c9c 234
572e6594 235 let mut aes_gcm_index = None;
90950c9c
DM
236
237 loop {
b23adfd4
TL
238 if reader.is_empty() {
239 break;
240 };
241 let desc: SspDataEncryptionAlgorithmDescriptor = unsafe { reader.read_be_value()? };
90950c9c 242 if desc.descriptor_len != 0x14 {
d1d74c43 243 bail!("got wrong key descriptor len");
90950c9c
DM
244 }
245 if (desc.control_byte_4 & 0b00000011) != 2 {
d1d74c43 246 continue; // can't encrypt in hardware
90950c9c
DM
247 }
248 if ((desc.control_byte_4 & 0b00001100) >> 2) != 2 {
d1d74c43 249 continue; // can't decrypt in hardware
90950c9c
DM
250 }
251 if desc.algorithm_code == 0x00010014 && desc.key_size == 32 {
572e6594 252 aes_gcm_index = Some(desc.algorythm_index);
90950c9c
DM
253 break;
254 }
255 }
256
572e6594 257 match aes_gcm_index {
90950c9c 258 Some(index) => Ok(index),
572e6594 259 None => bail!("drive does not support AES-GCM encryption"),
90950c9c 260 }
b23adfd4
TL
261 })
262 .map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
90950c9c
DM
263}
264
b22b6c22 265#[derive(Endian)]
90950c9c
DM
266#[repr(C, packed)]
267struct SspDataEncryptionStatusPage {
268 page_code: u16,
269 page_len: u16,
270 scope_byte: u8,
271 encryption_mode: u8,
272 decryption_mode: u8,
273 algorythm_index: u8,
274 key_instance_counter: u32,
275 control_byte: u8,
276 key_format: u8,
277 key_len: u16,
d1d74c43 278 reserved: [u8; 8],
90950c9c
DM
279}
280
281fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> {
6ef1b649 282 proxmox_lang::try_block!({
dcf5a0f6 283 let mut reader = data;
90950c9c
DM
284 let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? };
285
286 if page.page_code != 0x20 {
287 bail!("invalid response");
288 }
289
290 let mode = match (page.encryption_mode, page.decryption_mode) {
291 (0, 0) => DataEncryptionMode::Off,
292 (2, 1) => DataEncryptionMode::RawRead,
293 (2, 2) => DataEncryptionMode::On,
294 (2, 3) => DataEncryptionMode::Mixed,
295 _ => bail!("unknown encryption mode"),
296 };
297
b23adfd4 298 let status = DataEncryptionStatus { mode };
90950c9c
DM
299
300 Ok(status)
b23adfd4
TL
301 })
302 .map_err(|err| format_err!("decode data encryption status page failed - {}", err))
90950c9c 303}