lix-installer/src/action/darwin/encrypt_apfs_volume.rs

187 lines
5.4 KiB
Rust
Raw Normal View History

2022-11-01 18:33:54 +00:00
use crate::{
action::{
darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionError, StatefulAction,
},
2022-11-01 18:33:54 +00:00
execute_command,
};
use rand::Rng;
2022-10-19 20:32:15 +00:00
use std::path::{Path, PathBuf};
2022-11-01 18:33:54 +00:00
use tokio::process::Command;
use tracing::{span, Span};
2022-10-18 18:03:19 +00:00
/**
Encrypt an APFS volume
*/
2022-10-18 18:03:19 +00:00
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct EncryptApfsVolume {
2022-10-19 20:32:15 +00:00
disk: PathBuf,
2022-11-01 18:33:54 +00:00
name: String,
2022-10-18 18:03:19 +00:00
}
impl EncryptApfsVolume {
#[tracing::instrument(level = "debug", skip_all)]
2022-10-19 20:32:15 +00:00
pub async fn plan(
disk: impl AsRef<Path>,
2022-11-01 18:33:54 +00:00
name: impl AsRef<str>,
) -> Result<StatefulAction<Self>, ActionError> {
2022-11-01 18:33:54 +00:00
let name = name.as_ref().to_owned();
2022-10-18 18:03:19 +00:00
Ok(Self {
2022-11-01 18:33:54 +00:00
name,
2022-10-19 20:32:15 +00:00
disk: disk.as_ref().to_path_buf(),
}
.into())
2022-10-18 18:03:19 +00:00
}
}
#[async_trait::async_trait]
2022-10-28 19:44:07 +00:00
#[typetag::serde(name = "encrypt_volume")]
impl Action for EncryptApfsVolume {
fn tracing_synopsis(&self) -> String {
format!(
"Encrypt volume `{}` on disk `{}`",
self.name,
self.disk.display()
)
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"encrypt_volume",
disk = tracing::field::display(self.disk.display()),
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
2022-10-18 18:03:19 +00:00
}
#[tracing::instrument(level = "debug", skip_all, fields(
2022-10-19 20:32:15 +00:00
disk = %self.disk.display(),
2022-10-18 18:03:19 +00:00
))]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { disk, name } = self;
2022-10-18 18:03:19 +00:00
2022-11-01 18:33:54 +00:00
// Generate a random password.
let password: String = {
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789)(*&^%$#@!~";
const PASSWORD_LEN: usize = 32;
let mut rng = rand::thread_rng();
(0..PASSWORD_LEN)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
};
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name))
.await
.map_err(ActionError::Command)?;
2022-11-01 22:31:31 +00:00
2022-11-01 18:33:54 +00:00
// Add the password to the user keychain so they can unlock it later.
2022-11-01 22:31:31 +00:00
execute_command(
Command::new("/usr/bin/security").process_group(0).args([
2022-11-01 18:33:54 +00:00
"add-generic-password",
"-a",
2022-11-01 22:31:31 +00:00
name.as_str(),
2022-11-01 18:33:54 +00:00
"-s",
name.as_str(),
"-l",
format!("{} encryption password", disk_str).as_str(),
"-D",
"Encrypted volume password",
"-j",
format!(
"Added automatically by the Nix installer for use by {NIX_VOLUME_MOUNTD_DEST}"
)
.as_str(),
"-w",
password.as_str(),
"-T",
"/System/Library/CoreServices/APFSUserAgent",
"-T",
"/System/Library/CoreServices/CSUserAgent",
"-T",
"/usr/bin/security",
"/Library/Keychains/System.keychain",
]),
)
.await
.map_err(ActionError::Command)?;
2022-11-01 18:33:54 +00:00
// Encrypt the mounted volume
execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([
2022-11-01 18:33:54 +00:00
"apfs",
"encryptVolume",
2022-11-01 22:31:31 +00:00
name.as_str(),
2022-11-01 18:33:54 +00:00
"-user",
"disk",
"-passphrase",
password.as_str(),
]))
.await
.map_err(ActionError::Command)?;
2022-10-18 18:03:19 +00:00
2022-11-01 22:31:31 +00:00
execute_command(
Command::new("/usr/sbin/diskutil")
.process_group(0)
2022-11-01 22:31:31 +00:00
.arg("unmount")
.arg("force")
.arg(&name),
)
.await
.map_err(ActionError::Command)?;
2022-11-01 22:31:31 +00:00
2022-10-18 18:03:19 +00:00
Ok(())
}
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!(
"Remove encryption keys for volume `{}`",
self.disk.display()
),
vec![],
)]
2022-10-18 18:03:19 +00:00
}
#[tracing::instrument(level = "debug", skip_all, fields(
2022-10-19 20:32:15 +00:00
disk = %self.disk.display(),
2022-10-18 18:03:19 +00:00
))]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self { disk, name } = self;
2022-11-01 22:31:31 +00:00
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
// TODO: This seems very rough and unsafe
execute_command(
Command::new("/usr/bin/security").process_group(0).args([
2022-11-01 22:31:31 +00:00
"delete-generic-password",
"-a",
name.as_str(),
"-s",
name.as_str(),
"-l",
format!("{} encryption password", disk_str).as_str(),
"-D",
"Encrypted volume password",
"-j",
format!(
"Added automatically by the Nix installer for use by {NIX_VOLUME_MOUNTD_DEST}"
)
.as_str(),
]),
)
.await
.map_err(ActionError::Command)?;
2022-10-18 18:03:19 +00:00
Ok(())
}
}