summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/mount.rs113
-rw-r--r--src/key.rs111
2 files changed, 124 insertions, 100 deletions
diff --git a/src/commands/mount.rs b/src/commands/mount.rs
index 5782b3ab..18b4aae5 100644
--- a/src/commands/mount.rs
+++ b/src/commands/mount.rs
@@ -215,28 +215,20 @@ fn get_uuid_for_dev_node(
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
- /// Path to passphrase/key file
+ /// Path to passphrase file
///
- /// Precedes key_location/unlock_policy: if the filesystem can be decrypted
- /// by the specified passphrase file; it is decrypted. (i.e. Regardless
- /// if "fail" is specified for key_location/unlock_policy.)
+ /// This can be used to optionally specify a file to read the passphrase
+ /// from. An explictly specified key_location/unlock_policy overrides this
+ /// argument.
#[arg(short = 'f', long)]
passphrase_file: Option<PathBuf>,
- /// Password policy to use in case of encrypted filesystem.
- ///
- /// Possible values are:
- /// "fail" - don't ask for password, fail if filesystem is encrypted;
- /// "wait" - wait for password to become available before mounting;
- /// "ask" - prompt the user for password;
- #[arg(
- short = 'k',
- long = "key_location",
- value_enum,
- default_value_t,
- verbatim_doc_comment
- )]
- unlock_policy: UnlockPolicy,
+ /// Passphrase policy to use in case of an encrypted filesystem. If not
+ /// specified, the password will be searched for in the keyring. If not
+ /// found, the password will be prompted or read from stdin, depending on
+ /// whether the stdin is connected to a terminal or not.
+ #[arg(short = 'k', long = "key_location", value_enum)]
+ unlock_policy: Option<UnlockPolicy>,
/// Device, or UUID=\<UUID\>
dev: String,
@@ -305,68 +297,67 @@ fn devs_str_sbs_from_device(
}
}
-fn cmd_mount_inner(opt: Cli) -> Result<()> {
+/// If a user explicitly specifies `unlock_policy` or `passphrase_file` then use
+/// that without falling back to other mechanisms. If these options are not
+/// used, then search for the key or ask for it.
+fn handle_unlock(cli: &Cli, sb: &bch_sb_handle) -> Result<KeyHandle> {
+ if let Some(policy) = cli.unlock_policy.as_ref() {
+ return policy.apply(sb);
+ }
+
+ if let Some(path) = cli.passphrase_file.as_deref() {
+ return Passphrase::new_from_file(path).and_then(|p| KeyHandle::new(sb, &p));
+ }
+
+ KeyHandle::new_from_search(&sb.sb().uuid())
+ .or_else(|_| Passphrase::new().and_then(|p| KeyHandle::new(sb, &p)))
+}
+
+fn cmd_mount_inner(cli: &Cli) -> Result<()> {
// Grab the udev information once
let udev_info = udev_bcachefs_info()?;
- let (devices, sbs) = if let Some(uuid) = opt.dev.strip_prefix("UUID=") {
- devs_str_sbs_from_uuid(&udev_info, uuid)?
- } else if let Some(uuid) = opt.dev.strip_prefix("OLD_BLKID_UUID=") {
+ let (devices, sbs) = if let Some(("UUID" | "OLD_BLKID_UUID", uuid)) = cli.dev.split_once('=') {
devs_str_sbs_from_uuid(&udev_info, uuid)?
+ } else if cli.dev.contains(':') {
+ // If the device string contains ":" we will assume the user knows the
+ // entire list. If they supply a single device it could be either the FS
+ // only has 1 device or it's only 1 of a number of devices which are
+ // part of the FS. This appears to be the case when we get called during
+ // fstab mount processing and the fstab specifies a UUID.
+
+ let sbs = cli
+ .dev
+ .split(':')
+ .map(read_super_silent)
+ .collect::<Result<Vec<_>>>()?;
+
+ (cli.dev.clone(), sbs)
} else {
- // If the device string contains ":" we will assume the user knows the entire list.
- // If they supply a single device it could be either the FS only has 1 device or it's
- // only 1 of a number of devices which are part of the FS. This appears to be the case
- // when we get called during fstab mount processing and the fstab specifies a UUID.
- if opt.dev.contains(':') {
- let sbs = opt
- .dev
- .split(':')
- .map(read_super_silent)
- .collect::<Result<Vec<_>>>()?;
-
- (opt.dev, sbs)
- } else {
- devs_str_sbs_from_device(&udev_info, Path::new(&opt.dev))?
- }
+ devs_str_sbs_from_device(&udev_info, Path::new(&cli.dev))?
};
ensure!(!sbs.is_empty(), "No device(s) to mount specified");
let first_sb = sbs[0];
- let uuid = first_sb.sb().uuid();
-
if unsafe { bcachefs::bch2_sb_is_encrypted(first_sb.sb) } {
- let _key_handle: KeyHandle = KeyHandle::new_from_search(&uuid).or_else(|_| {
- opt.passphrase_file
- .and_then(|path| match Passphrase::new_from_file(&first_sb, path) {
- Ok(p) => Some(KeyHandle::new(&first_sb, &p)),
- Err(e) => {
- error!(
- "Failed to read passphrase from file, falling back to prompt: {}",
- e
- );
- None
- }
- })
- .unwrap_or_else(|| opt.unlock_policy.apply(&first_sb))
- })?;
+ handle_unlock(cli, &first_sb)?;
}
- if let Some(mountpoint) = opt.mountpoint {
+ if let Some(mountpoint) = cli.mountpoint.as_deref() {
info!(
"mounting with params: device: {}, target: {}, options: {}",
devices,
mountpoint.to_string_lossy(),
- &opt.options
+ &cli.options
);
- let (data, mountflags) = parse_mount_options(&opt.options);
+ let (data, mountflags) = parse_mount_options(&cli.options);
mount_inner(devices, mountpoint, "bcachefs", mountflags, data)
} else {
info!(
"would mount with params: device: {}, options: {}",
- devices, &opt.options
+ devices, &cli.options
);
Ok(())
@@ -381,17 +372,17 @@ pub fn mount(mut argv: Vec<String>, symlink_cmd: Option<&str>) -> i32 {
argv.remove(0);
}
- let opt = Cli::parse_from(argv);
+ let cli = Cli::parse_from(argv);
// @TODO : more granular log levels via mount option
- log::set_max_level(match opt.verbose {
+ log::set_max_level(match cli.verbose {
0 => LevelFilter::Warn,
1 => LevelFilter::Trace,
2_u8..=u8::MAX => todo!(),
});
- colored::control::set_override(opt.colorize);
- if let Err(e) = cmd_mount_inner(opt) {
+ colored::control::set_override(cli.colorize);
+ if let Err(e) = cmd_mount_inner(&cli) {
error!("Fatal error: {}", e);
1
} else {
diff --git a/src/key.rs b/src/key.rs
index 0e061ee5..0a2d08da 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -11,7 +11,7 @@ use std::{
use anyhow::{anyhow, ensure, Result};
use bch_bindgen::{
bcachefs::{self, bch_key, bch_sb_handle},
- c::{bch2_chacha_encrypt_key, bch_sb_field_crypt},
+ c::{bch2_chacha_encrypt_key, bch_encrypted_key, bch_sb_field_crypt},
keyutils::{self, keyctl_search},
};
use byteorder::{LittleEndian, ReadBytesExt};
@@ -25,9 +25,15 @@ const BCH_KEY_MAGIC: &str = "bch**key";
#[derive(Clone, Debug, clap::ValueEnum, strum::Display)]
pub enum UnlockPolicy {
+ /// Don't ask for passphrase, if the key cannot be found in the keyring just
+ /// fail
Fail,
+ /// Wait for passphrase to become available before mounting
Wait,
+ /// Interactively prompt the user for a passphrase
Ask,
+ /// Try to read the passphrase from `stdin` without prompting
+ Stdin,
}
impl UnlockPolicy {
@@ -37,9 +43,10 @@ impl UnlockPolicy {
info!("Using filesystem unlock policy '{self}' on {uuid}");
match self {
- Self::Fail => Err(anyhow!("no passphrase available")),
+ Self::Fail => KeyHandle::new_from_search(&uuid),
Self::Wait => Ok(KeyHandle::wait_for_unlock(&uuid)?),
Self::Ask => Passphrase::new_from_prompt().and_then(|p| KeyHandle::new(sb, &p)),
+ Self::Stdin => Passphrase::new_from_stdin().and_then(|p| KeyHandle::new(sb, &p)),
}
}
}
@@ -63,44 +70,24 @@ impl KeyHandle {
}
pub fn new(sb: &bch_sb_handle, passphrase: &Passphrase) -> Result<Self> {
- let bch_key_magic = BCH_KEY_MAGIC.as_bytes().read_u64::<LittleEndian>().unwrap();
-
- let crypt = sb.sb().crypt().unwrap();
- let crypt_ptr = (crypt as *const bch_sb_field_crypt).cast_mut();
-
- let mut output: bch_key =
- unsafe { bcachefs::derive_passphrase(crypt_ptr, passphrase.get().as_ptr()) };
-
- let mut key = *crypt.key();
-
- let ret = unsafe {
- bch2_chacha_encrypt_key(
- ptr::addr_of_mut!(output),
- sb.sb().nonce(),
- ptr::addr_of_mut!(key).cast(),
- mem::size_of_val(&key),
- )
- };
-
- ensure!(ret == 0, "chacha decryption failure");
- ensure!(key.magic == bch_key_magic, "failed to verify passphrase");
-
let key_name = Self::format_key_name(&sb.sb().uuid());
let key_name = CStr::as_ptr(&key_name);
let key_type = c_str!("user");
+ let (passphrase_key, _sb_key) = passphrase.check(sb)?;
+
let key_id = unsafe {
keyutils::add_key(
key_type,
key_name,
- ptr::addr_of!(output).cast(),
- mem::size_of_val(&output),
+ ptr::addr_of!(passphrase_key).cast(),
+ mem::size_of_val(&passphrase_key),
keyutils::KEY_SPEC_USER_KEYRING,
)
};
if key_id > 0 {
- info!("Found key in keyring");
+ info!("Added key to keyring");
Ok(KeyHandle {
_uuid: sb.sb().uuid(),
_id: c_long::from(key_id),
@@ -154,26 +141,36 @@ impl Passphrase {
&self.0
}
- // blocks indefinitely if no input is available on stdin
- fn new_from_prompt() -> Result<Self> {
- let passphrase = if stdin().is_terminal() {
- Zeroizing::new(rpassword::prompt_password("Enter passphrase: ")?)
+ pub fn new() -> Result<Self> {
+ if stdin().is_terminal() {
+ Self::new_from_prompt()
} else {
- info!("Trying to read passphrase from stdin...");
- let mut line = Zeroizing::new(String::new());
- stdin().read_line(&mut line)?;
- line
- };
+ Self::new_from_stdin()
+ }
+ }
+
+ // blocks indefinitely if no input is available on stdin
+ pub fn new_from_prompt() -> Result<Self> {
+ let passphrase = Zeroizing::new(rpassword::prompt_password("Enter passphrase: ")?);
Ok(Self(CString::new(passphrase.trim_end_matches('\n'))?))
}
- pub fn new_from_file(sb: &bch_sb_handle, passphrase_file: impl AsRef<Path>) -> Result<Self> {
+ // blocks indefinitely if no input is available on stdin
+ pub fn new_from_stdin() -> Result<Self> {
+ info!("Trying to read passphrase from stdin...");
+
+ let mut line = Zeroizing::new(String::new());
+ stdin().read_line(&mut line)?;
+
+ Ok(Self(CString::new(line.trim_end_matches('\n'))?))
+ }
+
+ pub fn new_from_file(passphrase_file: impl AsRef<Path>) -> Result<Self> {
let passphrase_file = passphrase_file.as_ref();
info!(
- "Attempting to unlock key for filesystem {} with passphrase from file {}",
- sb.sb().uuid(),
+ "Attempting to unlock key with passphrase from file {}",
passphrase_file.display()
);
@@ -181,4 +178,40 @@ impl Passphrase {
Ok(Self(CString::new(passphrase.trim_end_matches('\n'))?))
}
+
+ fn derive(&self, crypt: &bch_sb_field_crypt) -> bch_key {
+ let crypt_ptr = (crypt as *const bch_sb_field_crypt).cast_mut();
+
+ unsafe { bcachefs::derive_passphrase(crypt_ptr, self.get().as_ptr()) }
+ }
+
+ pub fn check(&self, sb: &bch_sb_handle) -> Result<(bch_key, bch_encrypted_key)> {
+ let bch_key_magic = BCH_KEY_MAGIC.as_bytes().read_u64::<LittleEndian>().unwrap();
+
+ let crypt = sb
+ .sb()
+ .crypt()
+ .ok_or_else(|| anyhow!("filesystem is not encrypted"))?;
+ let mut sb_key = *crypt.key();
+
+ ensure!(
+ sb_key.magic != bch_key_magic,
+ "filesystem does not have encryption key"
+ );
+
+ let mut passphrase_key: bch_key = self.derive(crypt);
+
+ let ret = unsafe {
+ bch2_chacha_encrypt_key(
+ ptr::addr_of_mut!(passphrase_key),
+ sb.sb().nonce(),
+ ptr::addr_of_mut!(sb_key).cast(),
+ mem::size_of_val(&sb_key),
+ )
+ };
+ ensure!(ret == 0, "error encrypting key");
+ ensure!(sb_key.magic == bch_key_magic, "incorrect passphrase");
+
+ Ok((passphrase_key, sb_key))
+ }
}