Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 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 | //! `encryptedstore` is a program that (as the name indicates) provides encrypted storage |
| 18 | //! solution in a VM. This is based on dm-crypt & requires the (64 bytes') key & the backing device. |
| 19 | //! It uses dm_rust lib. |
| 20 | |
| 21 | use anyhow::{ensure, Context, Result}; |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 22 | use clap::arg; |
| 23 | use dm::{crypt::CipherType, util}; |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 24 | use log::info; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 25 | use std::ffi::CString; |
| 26 | use std::fs::{create_dir_all, OpenOptions}; |
| 27 | use std::io::{Error, Read, Write}; |
| 28 | use std::os::unix::ffi::OsStrExt; |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 29 | use std::os::unix::fs::FileTypeExt; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 30 | use std::path::{Path, PathBuf}; |
| 31 | use std::process::Command; |
| 32 | |
| 33 | const MK2FS_BIN: &str = "/system/bin/mke2fs"; |
| 34 | const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE"; |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 35 | |
| 36 | fn main() -> Result<()> { |
| 37 | android_logger::init_once( |
| 38 | android_logger::Config::default() |
| 39 | .with_tag("encryptedstore") |
| 40 | .with_min_level(log::Level::Info), |
| 41 | ); |
| 42 | info!("Starting encryptedstore binary"); |
| 43 | |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 44 | let matches = clap_command().get_matches(); |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 45 | |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 46 | let blkdevice = Path::new(matches.get_one::<String>("blkdevice").unwrap()); |
| 47 | let key = matches.get_one::<String>("key").unwrap(); |
| 48 | let mountpoint = Path::new(matches.get_one::<String>("mountpoint").unwrap()); |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 49 | encryptedstore_init(blkdevice, key, mountpoint).context(format!( |
| 50 | "Unable to initialize encryptedstore on {:?} & mount at {:?}", |
| 51 | blkdevice, mountpoint |
| 52 | ))?; |
| 53 | Ok(()) |
| 54 | } |
| 55 | |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 56 | fn clap_command() -> clap::Command { |
| 57 | clap::Command::new("encryptedstore").args(&[ |
| 58 | arg!(--blkdevice <FILE> "the block device backing the encrypted storage").required(true), |
| 59 | arg!(--key <KEY> "key (in hex) equivalent to 32 bytes)").required(true), |
| 60 | arg!(--mountpoint <MOUNTPOINT> "mount point for the storage").required(true), |
| 61 | ]) |
| 62 | } |
| 63 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 64 | fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> { |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 65 | ensure!( |
Andrew Walbran | 48294fb | 2023-01-16 12:01:53 +0000 | [diff] [blame] | 66 | std::fs::metadata(blkdevice) |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 67 | .context(format!("Failed to get metadata of {:?}", blkdevice))? |
| 68 | .file_type() |
| 69 | .is_block_device(), |
| 70 | "The path:{:?} is not of a block device", |
| 71 | blkdevice |
| 72 | ); |
| 73 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 74 | let needs_formatting = |
| 75 | needs_formatting(blkdevice).context("Unable to check if formatting is required")?; |
| 76 | let crypt_device = |
| 77 | enable_crypt(blkdevice, key, "cryptdev").context("Unable to map crypt device")?; |
| 78 | |
| 79 | // We might need to format it with filesystem if this is a "seen-for-the-first-time" device. |
| 80 | if needs_formatting { |
| 81 | info!("Freshly formatting the crypt device"); |
| 82 | format_ext4(&crypt_device)?; |
| 83 | } |
| 84 | mount(&crypt_device, mountpoint).context(format!("Unable to mount {:?}", crypt_device))?; |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 85 | Ok(()) |
| 86 | } |
| 87 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 88 | fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<PathBuf> { |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 89 | let dev_size = util::blkgetsize64(data_device)?; |
| 90 | let key = hex::decode(key).context("Unable to decode hex key")?; |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 91 | |
| 92 | // Create the dm-crypt spec |
| 93 | let target = dm::crypt::DmCryptTargetBuilder::default() |
| 94 | .data_device(data_device, dev_size) |
Shikha Panwar | 195f89c | 2022-11-23 16:20:34 +0000 | [diff] [blame] | 95 | .cipher(CipherType::AES256HCTR2) |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 96 | .key(&key) |
| 97 | .build() |
| 98 | .context("Couldn't build the DMCrypt target")?; |
| 99 | let dm = dm::DeviceMapper::new()?; |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 100 | dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device") |
| 101 | } |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 102 | |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 103 | // The disk contains UNFORMATTED_STORAGE_MAGIC to indicate we need to format the crypt device. |
| 104 | // This function looks for it, zeroing it, if present. |
| 105 | fn needs_formatting(data_device: &Path) -> Result<bool> { |
| 106 | let mut file = OpenOptions::new() |
| 107 | .read(true) |
| 108 | .write(true) |
| 109 | .open(data_device) |
| 110 | .with_context(|| format!("Failed to open {:?}", data_device))?; |
| 111 | |
| 112 | let mut buf = [0; UNFORMATTED_STORAGE_MAGIC.len()]; |
| 113 | file.read_exact(&mut buf)?; |
| 114 | |
| 115 | if buf == UNFORMATTED_STORAGE_MAGIC.as_bytes() { |
| 116 | buf.fill(0); |
| 117 | file.write_all(&buf)?; |
| 118 | return Ok(true); |
| 119 | } |
| 120 | Ok(false) |
| 121 | } |
| 122 | |
| 123 | fn format_ext4(device: &Path) -> Result<()> { |
| 124 | let mkfs_options = [ |
| 125 | "-j", // Create appropriate sized journal |
| 126 | "-O metadata_csum", // Metadata checksum for filesystem integrity |
| 127 | ]; |
| 128 | let mut cmd = Command::new(MK2FS_BIN); |
| 129 | let status = cmd |
| 130 | .args(mkfs_options) |
| 131 | .arg(device) |
| 132 | .status() |
| 133 | .context(format!("failed to execute {}", MK2FS_BIN))?; |
| 134 | ensure!(status.success(), "mkfs failed with {:?}", status); |
Shikha Panwar | 566c967 | 2022-11-15 14:39:58 +0000 | [diff] [blame] | 135 | Ok(()) |
| 136 | } |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 137 | |
| 138 | fn mount(source: &Path, mountpoint: &Path) -> Result<()> { |
| 139 | create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?; |
Shikha Panwar | d4ce1c0 | 2022-12-08 18:05:21 +0000 | [diff] [blame] | 140 | let mount_options = CString::new( |
| 141 | "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0", |
| 142 | ) |
| 143 | .unwrap(); |
Shikha Panwar | 9fd198f | 2022-11-18 17:43:43 +0000 | [diff] [blame] | 144 | let source = CString::new(source.as_os_str().as_bytes())?; |
| 145 | let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?; |
| 146 | let fstype = CString::new("ext4").unwrap(); |
| 147 | |
| 148 | let ret = unsafe { |
| 149 | libc::mount( |
| 150 | source.as_ptr(), |
| 151 | mountpoint.as_ptr(), |
| 152 | fstype.as_ptr(), |
| 153 | libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC, |
| 154 | mount_options.as_ptr() as *const std::ffi::c_void, |
| 155 | ) |
| 156 | }; |
| 157 | if ret < 0 { |
| 158 | Err(Error::last_os_error()).context("mount failed") |
| 159 | } else { |
| 160 | Ok(()) |
| 161 | } |
| 162 | } |
Andrew Walbran | aa1efc4 | 2022-08-10 13:33:57 +0000 | [diff] [blame] | 163 | |
| 164 | #[cfg(test)] |
| 165 | mod tests { |
| 166 | use super::*; |
| 167 | |
| 168 | #[test] |
| 169 | fn verify_command() { |
| 170 | // Check that the command parsing has been configured in a valid way. |
| 171 | clap_command().debug_assert(); |
| 172 | } |
| 173 | } |