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

196 lines
5.8 KiB
Rust
Raw Normal View History

2022-11-01 18:33:54 +00:00
use crate::{
action::{darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionState},
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;
2022-10-18 18:03:19 +00:00
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct EncryptVolume {
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
action_state: ActionState,
}
impl EncryptVolume {
#[tracing::instrument(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<Self, Box<dyn std::error::Error + Send + Sync>> {
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(),
2022-10-18 18:03:19 +00:00
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
2022-10-28 19:44:07 +00:00
#[typetag::serde(name = "encrypt_volume")]
impl Action for EncryptVolume {
2022-10-18 18:03:19 +00:00
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
2022-10-19 20:32:15 +00:00
format!("Encrypt volume `{}`", self.disk.display()),
vec![],
2022-10-18 18:03:19 +00:00
)]
}
}
#[tracing::instrument(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<(), Box<dyn std::error::Error + Send + Sync>> {
2022-10-19 20:32:15 +00:00
let Self {
2022-11-01 18:33:54 +00:00
disk,
name,
2022-10-19 20:32:15 +00:00
action_state,
} = self;
2022-10-18 18:03:19 +00:00
if *action_state == ActionState::Completed {
2022-10-19 20:32:15 +00:00
tracing::trace!("Already completed: Encrypting volume");
2022-10-18 18:03:19 +00:00
return Ok(());
}
2022-10-19 20:32:15 +00:00
tracing::debug!("Encrypting volume");
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 */
2022-11-01 22:31:31 +00:00
execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name)).await?;
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(
2022-11-01 18:33:54 +00:00
Command::new("/usr/bin/security").args([
"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?;
// Encrypt the mounted volume
execute_command(Command::new("/usr/sbin/diskutil").args([
"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?;
2022-10-18 18:03:19 +00:00
2022-11-01 22:31:31 +00:00
execute_command(
Command::new("/usr/sbin/diskutil")
.arg("unmount")
.arg("force")
.arg(&name),
)
.await?;
2022-10-19 20:32:15 +00:00
tracing::trace!("Encrypted volume");
2022-10-18 18:03:19 +00:00
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
2022-10-19 20:32:15 +00:00
vec![]
2022-10-18 18:03:19 +00:00
}
}
#[tracing::instrument(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<(), Box<dyn std::error::Error + Send + Sync>> {
2022-10-19 20:32:15 +00:00
let Self {
2022-11-01 22:31:31 +00:00
disk,
name,
2022-10-19 20:32:15 +00:00
action_state,
} = self;
2022-10-18 18:03:19 +00:00
if *action_state == ActionState::Uncompleted {
2022-11-01 22:31:31 +00:00
tracing::trace!("Already reverted: Unencrypted volume");
2022-10-18 18:03:19 +00:00
return Ok(());
}
2022-11-01 22:31:31 +00:00
tracing::debug!("Unencrypted volume");
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").args([
"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?;
2022-10-18 18:03:19 +00:00
2022-11-01 22:31:31 +00:00
tracing::trace!("Unencrypted volume");
2022-10-18 18:03:19 +00:00
*action_state = ActionState::Completed;
Ok(())
}
2022-11-08 18:18:05 +00:00
fn action_state(&self) -> ActionState {
self.action_state
}
2022-10-18 18:03:19 +00:00
}
#[derive(Debug, thiserror::Error)]
2022-10-18 18:03:19 +00:00
pub enum EncryptVolumeError {
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
2022-10-18 18:03:19 +00:00
}