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