]>
Commit | Line | Data |
---|---|---|
b22b6c22 | 1 | use std::io::Write; |
b23adfd4 | 2 | use std::os::unix::prelude::AsRawFd; |
90950c9c DM |
3 | |
4 | use anyhow::{bail, format_err, Error}; | |
5 | use endian_trait::Endian; | |
6 | ||
6ef1b649 | 7 | use proxmox_io::{ReadExt, WriteExt}; |
90950c9c | 8 | |
b23adfd4 | 9 | use 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 | 14 | pub 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 | 25 | pub 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)] | |
62 | struct 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 |
77 | fn 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 | 134 | fn 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 | 163 | fn 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)] | |
191 | enum DataEncryptionMode { | |
192 | On, | |
193 | Mixed, | |
194 | RawRead, | |
195 | Off, | |
196 | } | |
197 | ||
198 | #[derive(Debug)] | |
199 | struct DataEncryptionStatus { | |
200 | mode: DataEncryptionMode, | |
201 | } | |
202 | ||
b22b6c22 | 203 | #[derive(Endian)] |
90950c9c DM |
204 | #[repr(C, packed)] |
205 | struct 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)] |
213 | struct 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 | 230 | fn 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)] |
267 | struct 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 | ||
281 | fn 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 | } |