1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /// `crypt` module implements the "crypt" target in the device mapper framework. Specifically,
18 /// it provides `DmCryptTargetBuilder` struct which is used to construct a `DmCryptTarget`
19 /// struct which is then given to `DeviceMapper` to create a mapper device.
20 use crate::DmTargetSpec;
21 
22 use anyhow::{ensure, Context, Result};
23 use std::io::Write;
24 use std::mem::size_of;
25 use std::path::Path;
26 use zerocopy::AsBytes;
27 
28 const SECTOR_SIZE: u64 = 512;
29 
30 // The UAPI for the crypt target is at:
31 // Documentation/admin-guide/device-mapper/dm-crypt.rst
32 
33 /// Supported ciphers
34 #[derive(Clone, Copy, Debug)]
35 pub enum CipherType {
36     /// AES256 with HCTR2 mode. HCTR2 is a tweakable super-pseudorandom permutation
37     /// length-preserving encryption mode. It is the preferred mode in absence of other
38     /// dedicated integrity primitives (such as for encryptedstore in pVM) since it is less
39     /// malleable than other modes.
40     AES256HCTR2,
41     /// AES with XTS mode. This has slight performance benefits over HCTR2. In particular, XTS is
42     /// supported by inline encryption hardware. Note that (status quo) `encryptedstore` in VMs
43     /// is the only user of this module & inline encryption is not supported by guest kernel.
44     AES256XTS,
45 }
46 impl CipherType {
get_kernel_crypto_name(&self) -> &str47     fn get_kernel_crypto_name(&self) -> &str {
48         match *self {
49             // We use "plain64" as the IV/nonce generation algorithm -
50             // which basically is the sector number.
51             CipherType::AES256HCTR2 => "aes-hctr2-plain64",
52             CipherType::AES256XTS => "aes-xts-plain64",
53         }
54     }
55 
get_required_key_size(&self) -> usize56     fn get_required_key_size(&self) -> usize {
57         match *self {
58             // AES-256-HCTR2 takes a 32-byte key
59             CipherType::AES256HCTR2 => 32,
60             // XTS requires key of twice the length of the underlying block cipher
61             // i.e., 64B for AES256
62             CipherType::AES256XTS => 64,
63         }
64     }
65 
validata_key_size(&self, key_size: usize) -> bool66     fn validata_key_size(&self, key_size: usize) -> bool {
67         key_size == self.get_required_key_size()
68     }
69 }
70 
71 pub struct DmCryptTarget(Box<[u8]>);
72 
73 impl DmCryptTarget {
74     /// Flatten into slice
as_slice(&self) -> &[u8]75     pub fn as_slice(&self) -> &[u8] {
76         self.0.as_ref()
77     }
78 }
79 
80 pub struct DmCryptTargetBuilder<'a> {
81     cipher: CipherType,
82     key: Option<&'a [u8]>,
83     iv_offset: u64,
84     device_path: Option<&'a Path>,
85     offset: u64,
86     device_size: u64,
87     opt_params: Vec<&'a str>,
88 }
89 
90 impl<'a> Default for DmCryptTargetBuilder<'a> {
default() -> Self91     fn default() -> Self {
92         DmCryptTargetBuilder {
93             cipher: CipherType::AES256HCTR2,
94             key: None,
95             iv_offset: 0,
96             device_path: None,
97             offset: 0,
98             device_size: 0,
99             opt_params: Vec::new(),
100         }
101     }
102 }
103 
104 impl<'a> DmCryptTargetBuilder<'a> {
105     /// Sets the device that will be used as the data device (i.e. providing actual data).
data_device(&mut self, p: &'a Path, size: u64) -> &mut Self106     pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self {
107         self.device_path = Some(p);
108         self.device_size = size;
109         self
110     }
111 
112     /// Sets the encryption cipher.
cipher(&mut self, cipher: CipherType) -> &mut Self113     pub fn cipher(&mut self, cipher: CipherType) -> &mut Self {
114         self.cipher = cipher;
115         self
116     }
117 
118     /// Sets the key used for encryption. Input is byte array.
key(&mut self, key: &'a [u8]) -> &mut Self119     pub fn key(&mut self, key: &'a [u8]) -> &mut Self {
120         self.key = Some(key);
121         self
122     }
123 
124     /// The IV offset is a sector count that is added to the sector number before creating the IV.
iv_offset(&mut self, iv_offset: u64) -> &mut Self125     pub fn iv_offset(&mut self, iv_offset: u64) -> &mut Self {
126         self.iv_offset = iv_offset;
127         self
128     }
129 
130     /// Starting sector within the device where the encrypted data begins
offset(&mut self, offset: u64) -> &mut Self131     pub fn offset(&mut self, offset: u64) -> &mut Self {
132         self.offset = offset;
133         self
134     }
135 
136     /// Add additional optional parameter
opt_param(&mut self, param: &'a str) -> &mut Self137     pub fn opt_param(&mut self, param: &'a str) -> &mut Self {
138         self.opt_params.push(param);
139         self
140     }
141 
142     /// Constructs a `DmCryptTarget`.
build(&self) -> Result<DmCryptTarget>143     pub fn build(&self) -> Result<DmCryptTarget> {
144         // The `DmCryptTarget` struct actually is a flattened data consisting of a header and
145         // body. The format of the header is `dm_target_spec` as defined in
146         // include/uapi/linux/dm-ioctl.h.
147         let device_path = self
148             .device_path
149             .context("data device is not set")?
150             .to_str()
151             .context("data device path is not encoded in utf8")?;
152 
153         ensure!(self.key.is_some(), "key is not set");
154         // Unwrap is safe because we already made sure key.is_some()
155         ensure!(
156             self.cipher.validata_key_size(self.key.unwrap().len()),
157             format!("Invalid key size for cipher:{}", self.cipher.get_kernel_crypto_name())
158         );
159         let key = hex::encode(self.key.unwrap());
160 
161         // Step2: serialize the information according to the spec, which is ...
162         // DmTargetSpec{...}
163         // <cipher> <key> <iv_offset> <device path> \
164         // <offset> [<#opt_params> <opt_params>]
165         let mut body = String::new();
166         use std::fmt::Write;
167         write!(&mut body, "{} ", self.cipher.get_kernel_crypto_name())?;
168         write!(&mut body, "{} ", key)?;
169         write!(&mut body, "{} ", self.iv_offset)?;
170         write!(&mut body, "{} ", device_path)?;
171         write!(&mut body, "{} ", self.offset)?;
172         write!(&mut body, "{} {} ", self.opt_params.len(), self.opt_params.join(" "))?;
173         write!(&mut body, "\0")?; // null terminator
174 
175         let size = size_of::<DmTargetSpec>() + body.len();
176         let aligned_size = (size + 7) & !7; // align to 8 byte boundaries
177         let padding = aligned_size - size;
178 
179         let mut header = DmTargetSpec::new("crypt")?;
180         header.sector_start = 0;
181         header.length = self.device_size / SECTOR_SIZE; // number of 512-byte sectors
182         header.next = aligned_size as u32;
183 
184         let mut buf = Vec::with_capacity(aligned_size);
185         buf.write_all(header.as_bytes())?;
186         buf.write_all(body.as_bytes())?;
187         buf.write_all(vec![0; padding].as_slice())?;
188 
189         Ok(DmCryptTarget(buf.into_boxed_slice()))
190     }
191 }
192