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