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