]> git.sesse.net Git - bcachefs-tools-debian/blobdiff - src/key.rs
move Rust sources to top level, C sources into c_src
[bcachefs-tools-debian] / src / key.rs
diff --git a/src/key.rs b/src/key.rs
new file mode 100644 (file)
index 0000000..93daa26
--- /dev/null
@@ -0,0 +1,144 @@
+use log::{info};
+use bch_bindgen::bcachefs::bch_sb_handle;
+use clap::builder::PossibleValue;
+use crate::c_str;
+use anyhow::anyhow;
+
+#[derive(Clone, Debug)]
+pub enum KeyLocation {
+    None,
+    Fail,
+    Wait,
+    Ask,
+}
+
+impl std::str::FromStr for KeyLocation {
+    type Err = anyhow::Error;
+    fn from_str(s: &str) -> anyhow::Result<Self> {
+        match s {
+            ""|"none" => Ok(KeyLocation::None),
+            "fail"    => Ok(KeyLocation::Fail),
+            "wait"    => Ok(KeyLocation::Wait),
+            "ask"     => Ok(KeyLocation::Ask),
+            _         => Err(anyhow!("invalid password option")),
+        }
+    }
+}
+
+impl clap::ValueEnum for KeyLocation {
+    fn value_variants<'a>() -> &'a [Self] {
+        &[
+            KeyLocation::None,
+            KeyLocation::Fail,
+            KeyLocation::Wait,
+            KeyLocation::Ask,
+        ]
+    }
+
+    fn to_possible_value(&self) -> Option<PossibleValue> {
+        Some(match self {
+            Self::None => PossibleValue::new("none").alias(""),
+            Self::Fail => PossibleValue::new("fail"),
+            Self::Wait => PossibleValue::new("wait"),
+            Self::Ask => PossibleValue::new("ask"),
+        })
+    }
+}
+
+fn check_for_key(key_name: &std::ffi::CStr) -> anyhow::Result<bool> {
+    use bch_bindgen::keyutils::{self, keyctl_search};
+    let key_name = key_name.to_bytes_with_nul().as_ptr() as *const _;
+    let key_type = c_str!("user");
+
+    let key_id = unsafe { keyctl_search(keyutils::KEY_SPEC_USER_KEYRING, key_type, key_name, 0) };
+    if key_id > 0 {
+        info!("Key has became available");
+        Ok(true)
+    } else {
+        match errno::errno().0 {
+            libc::ENOKEY | libc::EKEYREVOKED => Ok(false),
+            _ => Err(crate::ErrnoError(errno::errno()).into()),
+        }
+    }
+}
+
+fn wait_for_key(uuid: &uuid::Uuid) -> anyhow::Result<()> {
+    let key_name = std::ffi::CString::new(format!("bcachefs:{}", uuid)).unwrap();
+    loop {
+        if check_for_key(&key_name)? {
+            break Ok(());
+        }
+
+        std::thread::sleep(std::time::Duration::from_secs(1));
+    }
+}
+
+const BCH_KEY_MAGIC: &str = "bch**key";
+fn ask_for_key(sb: &bch_sb_handle) -> anyhow::Result<()> {
+    use bch_bindgen::bcachefs::{self, bch2_chacha_encrypt_key, bch_encrypted_key, bch_key};
+    use byteorder::{LittleEndian, ReadBytesExt};
+    use std::os::raw::c_char;
+
+    let key_name = std::ffi::CString::new(format!("bcachefs:{}", sb.sb().uuid())).unwrap();
+    if check_for_key(&key_name)? {
+        return Ok(());
+    }
+
+    let bch_key_magic = BCH_KEY_MAGIC.as_bytes().read_u64::<LittleEndian>().unwrap();
+    let crypt = sb.sb().crypt().unwrap();
+    let pass = if atty::is(atty::Stream::Stdin) {
+        rpassword::read_password_from_tty(Some("Enter passphrase: "))?
+    } else {
+        let mut line = String::new();
+        std::io::stdin().read_line(&mut line)?;
+        line
+    };
+    let pass = std::ffi::CString::new(pass.trim_end())?; // bind to keep the CString alive
+    let mut output: bch_key = unsafe {
+        bcachefs::derive_passphrase(
+            crypt as *const _ as *mut _,
+            pass.as_c_str().to_bytes_with_nul().as_ptr() as *const _,
+        )
+    };
+
+    let mut key = crypt.key().clone();
+    let ret = unsafe {
+        bch2_chacha_encrypt_key(
+            &mut output as *mut _,
+            sb.sb().nonce(),
+            &mut key as *mut _ as *mut _,
+            std::mem::size_of::<bch_encrypted_key>() as usize,
+        )
+    };
+    if ret != 0 {
+        Err(anyhow!("chacha decryption failure"))
+    } else if key.magic != bch_key_magic {
+        Err(anyhow!("failed to verify the password"))
+    } else {
+        let key_type = c_str!("user");
+        let ret = unsafe {
+            bch_bindgen::keyutils::add_key(
+                key_type,
+                key_name.as_c_str().to_bytes_with_nul() as *const _ as *const c_char,
+                &output as *const _ as *const _,
+                std::mem::size_of::<bch_key>() as usize,
+                bch_bindgen::keyutils::KEY_SPEC_USER_KEYRING,
+            )
+        };
+        if ret == -1 {
+            Err(anyhow!("failed to add key to keyring: {}", errno::errno()))
+        } else {
+            Ok(())
+        }
+    }
+}
+
+pub fn prepare_key(sb: &bch_sb_handle, password: KeyLocation) -> anyhow::Result<()> {
+    info!("checking if key exists for filesystem {}", sb.sb().uuid());
+    match password {
+        KeyLocation::Fail => Err(anyhow!("no key available")),
+        KeyLocation::Wait => Ok(wait_for_key(&sb.sb().uuid())?),
+        KeyLocation::Ask => ask_for_key(sb),
+        _ => Err(anyhow!("no keyoption specified for locked filesystem")),
+    }
+}