From e9510216efced0ba463997b60312d7c9b17a5e30 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 11 Oct 2020 18:33:59 +0200 Subject: [PATCH 01/31] 1 --- Cargo.lock | 147 +++++++++++++++++++++++++++----------------- Cargo.toml | 2 +- src/cli.rs | 103 ++++++++++++++++++++++++++++--- src/cli_args/mod.rs | 58 ++++++++--------- 4 files changed, 216 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b91b404..f3f2431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.50" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +checksum = "f813291114c186a042350e787af10c26534601062603d888be110f59f85ef8fa" dependencies = [ "addr2line", "cfg-if", @@ -86,7 +86,7 @@ dependencies = [ "lazycell", "log", "peeking_take_while", - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", "regex", "rustc-hash", @@ -100,6 +100,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bstr" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "byteorder" version = "1.3.4" @@ -118,9 +130,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" [[package]] name = "cexpr" @@ -244,6 +256,19 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "csv" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + [[package]] name = "csv-core" version = "0.1.10" @@ -255,14 +280,14 @@ dependencies = [ [[package]] name = "ctap_hmac" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5fec79b66e3a7bc6a7ace0f4c98f0748892b36d3c5c317fadfce0344fd185dc" +checksum = "33ccc28f298181e943187fa63805e652bc4806ad43708beebd22c230fbe0baa3" dependencies = [ "byteorder", "cbor-codec", "crossbeam", - "csv-core", + "csv", "derive_builder", "failure", "failure_derive", @@ -271,6 +296,8 @@ dependencies = [ "rand 0.6.5", "ring", "rust-crypto", + "serde", + "serde_derive", "untrusted", ] @@ -292,10 +319,10 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", "strsim 0.9.3", - "syn 1.0.40", + "syn 1.0.44", ] [[package]] @@ -306,7 +333,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", ] [[package]] @@ -317,9 +344,9 @@ checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" dependencies = [ "darling", "derive_builder_core", - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", ] [[package]] @@ -329,16 +356,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ "darling", - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", ] [[package]] name = "either" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "env_logger" @@ -369,15 +396,15 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", "synstructure", ] [[package]] name = "fido2luks" -version = "0.2.14" +version = "0.3.0" dependencies = [ "ctap_hmac", "failure", @@ -432,9 +459,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ "libc", ] @@ -480,9 +507,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.76" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" +checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" [[package]] name = "libcryptsetup-rs" @@ -544,20 +571,21 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memoffset" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" dependencies = [ "autocfg 1.0.1", ] [[package]] name = "miniz_oxide" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" dependencies = [ "adler", + "autocfg 1.0.1", ] [[package]] @@ -615,9 +643,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", "version_check", ] @@ -627,7 +655,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", "version_check", ] @@ -643,9 +671,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.20" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175c513d55719db99da20232b06cda8bab6b83ec2d04e3283edf0213c37c1a29" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid 0.2.1", ] @@ -671,7 +699,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", ] [[package]] @@ -824,6 +852,15 @@ dependencies = [ "thread_local", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + [[package]] name = "regex-syntax" version = "0.6.18" @@ -867,9 +904,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b" [[package]] name = "rustc-hash" @@ -912,26 +949,26 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" [[package]] name = "serde_derive" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" +checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" dependencies = [ - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", ] [[package]] name = "serde_json" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4" dependencies = [ "itoa", "ryu", @@ -958,9 +995,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "structopt" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5" +checksum = "a7a7159e7d0dbcab6f9c980d7971ef50f3ff5753081461eeda120d5974a4ee95" dependencies = [ "clap", "lazy_static", @@ -969,15 +1006,15 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f" +checksum = "8fc47de4dfba76248d1e9169ccff240eea2a4dc1e34e309b95b2393109b4b383" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", ] [[package]] @@ -993,11 +1030,11 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963f7d3cc59b59b9325165add223142bbf1df27655d07789f109896d353d8350" +checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" dependencies = [ - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", "unicode-xid 0.2.1", ] @@ -1008,9 +1045,9 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.20", + "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.40", + "syn 1.0.44", "unicode-xid 0.2.1", ] diff --git a/Cargo.toml b/Cargo.toml index ad486f0..f9b83e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fido2luks" -version = "0.2.14" +version = "0.3.0" authors = ["shimunn "] edition = "2018" diff --git a/src/cli.rs b/src/cli.rs index 629b5b9..06522fd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -20,6 +20,8 @@ use std::fs::File; use std::time::SystemTime; pub use cli_args::Args; +use std::iter::FromIterator; +use std::path::PathBuf; fn read_pin(ap: &AuthenticatorParameters) -> Fido2LuksResult { if let Some(src) = ap.pin_source.as_ref() { @@ -64,7 +66,38 @@ fn derive_secret( let (unsalted, cred) = perform_challenge(&credentials, salt, timeout - start.elapsed().unwrap(), pin)?; - Ok((sha256(&[salt, &unsalted[..]]), cred.clone())) + let binary = sha256(&[salt, &unsalted[..]]); + Ok((binary, cred.clone())) +} + +pub fn extend_creds_device( + creds: &[HexEncoded], + luks_dev: &mut LuksDevice, +) -> Fido2LuksResult> { + let mut additional = HashSet::from_iter(creds.iter().cloned()); + for token in luks_dev.tokens()? { + for cred in token?.1.credential { + let parsed = HexEncoded::from_str(cred.as_str())?; + additional.insert(parsed); + } + } + Ok(Vec::from_iter(additional.into_iter())) +} + +pub fn read_password_pin_prefixed( + reader: impl Fn() -> Fido2LuksResult, +) -> Fido2LuksResult<(Option, [u8; 32])> { + let read = reader()?; + let separator = ':'; + let mut parts = read.split(separator); + let pin = parts.next().filter(|p| p.len() > 0).map(|p| p.to_string()); + let password = match pin { + Some(ref pin) if read.len() > pin.len() => { + read.chars().skip(pin.len() + 1).collect::() + } + _ => String::new(), + }; + Ok((pin, util::sha256(&[password.as_bytes()]))) } pub fn parse_cmdline() -> Args { @@ -96,6 +129,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { authenticator, credentials, secret, + device, } => { let pin_string; let pin = if authenticator.pin { @@ -104,16 +138,42 @@ pub fn run_cli() -> Fido2LuksResult<()> { } else { None }; - let salt = if interactive || secret.password_helper == PasswordHelper::Stdin { - util::read_password_hashed("Password", false) + let (pin, salt) = match ( + &secret.password_helper, + authenticator.pin, + authenticator.pin_prefixed, + args.interactive, + ) { + (Some(phelper), true, true, false) => { + read_password_pin_prefixed(|| phelper.obtain())? + } + (Some(phelper), false, false, false) => (None, secret.salt.obtain_sha256(&phelper)), + + (phelper, pin, _, true) => ( + if pin { + pin_string = read_pin(authenticator)?; + Some(pin_string.as_ref()) + } else { + None + }, + match phelper { + None | Some(PasswordHelper::Stdin) => { + util::read_password_hashed("Password", false) + } + Some(phelper) => secret.salt.obtain_sha256(&phelper), + }?, + ), + }; + let credentials = if let Some(dev) = device { + extend_creds_device(credentials.ids.0.as_slice(), &mut LuksDevice::load(dev)?)? } else { - secret.salt.obtain_sha256(&secret.password_helper) - }?; + credentials.ids.0 + }; let (secret, _cred) = derive_secret( - credentials.ids.0.as_slice(), + credentials.as_slice(), &salt, authenticator.await_time, - pin, + pin.as_deref(), )?; if *binary { stdout.write_all(&secret[..])?; @@ -129,7 +189,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret, luks_mod, existing_secret: other_secret, - token, + auto_credential, .. } | Command::ReplaceKey { @@ -139,7 +199,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret, luks_mod, replacement: other_secret, - token, + remove_cred, .. } => { let pin = if authenticator.pin { @@ -463,3 +523,28 @@ pub fn run_cli() -> Fido2LuksResult<()> { } } } + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_read_password_pin_prefixed() { + assert_eq!( + read_password_pin_prefixed(|| OK("1234:test")), + Ok((Some("1234".to_string()), util::sha256(&["test".as_bytes()]))) + ); + assert_eq!( + read_password_pin_prefixed(|| OK(":test")), + Ok((None, util::sha256(&["test".as_bytes()]))) + ); + assert_eq!( + read_password_pin_prefixed(|| OK("1234::test")), + Ok(( + Some("1234".to_string()), + util::sha256(&[":test".as_bytes()]) + )) + ); + } +} diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index b02799f..fffadce 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -58,8 +58,13 @@ impl FromStr for CommaSeparated { #[derive(Debug, StructOpt)] pub struct Credentials { /// FIDO credential ids, separated by ',' generate using fido2luks credential - #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")] - pub ids: CommaSeparated, + #[structopt( + name = "credential-ids", + env = "FIDO2LUKS_CREDENTIAL_ID", + short = "c", + long = "creds" + )] + pub ids: Option>, } #[derive(Debug, StructOpt)] @@ -68,9 +73,9 @@ pub struct AuthenticatorParameters { #[structopt(short = "P", long = "pin")] pub pin: bool, - /// Location to read PIN from - #[structopt(long = "pin-source", env = "FIDO2LUKS_PIN_SOURCE")] - pub pin_source: Option, + /// Request PIN and password combined `pin:password` when using an password helper + #[structopt(long = "pin-prefixed")] + pub pin_prefixed: bool, /// Await for an authenticator to be connected, timeout after n seconds #[structopt( @@ -90,13 +95,17 @@ pub struct LuksParameters { /// Try to unlock the device using a specifc keyslot, ignore all other slots #[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")] pub slot: Option, + + /// Disable implicit use of LUKS2 tokens + #[structopt(long = "disable-token", env = "FIDO2LUKS_DISABLE_TOKEN")] + pub disable_token: bool, } #[derive(Debug, StructOpt, Clone)] pub struct LuksModParameters { /// Number of milliseconds required to derive the volume decryption key /// Defaults to 10ms when using an authenticator or the default by cryptsetup when using a password - #[structopt(long = "kdf-time", name = "kdf-time")] + #[structopt(long = "kdf-time", name = "kdf-time", env = "FIDO2LUKS_KDF_TIME")] pub kdf_time: Option, } @@ -119,9 +128,10 @@ pub struct SecretParameters { #[structopt( name = "password-helper", env = "FIDO2LUKS_PASSWORD_HELPER", + long = "password-helper", default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'" )] - pub password_helper: PasswordHelper, + pub password_helper: Option, } #[derive(Debug, StructOpt)] pub struct Args { @@ -138,7 +148,7 @@ pub struct OtherSecret { #[structopt(short = "d", long = "keyfile", conflicts_with = "fido_device")] pub keyfile: Option, /// Use another fido device instead of a password - /// Note: this requires for the credential fot the other device to be passed as argument as well + /// Note: this requires for the credential for the other device to be passed as argument as well #[structopt(short = "f", long = "fido-device", conflicts_with = "keyfile")] pub fido_device: bool, } @@ -147,8 +157,9 @@ pub struct OtherSecret { pub enum Command { #[structopt(name = "print-secret")] PrintSecret { + // version 0.3.0 will store use the lower case ascii encoded hex string making binary output unnecessary /// Prints the secret as binary instead of hex encoded - #[structopt(short = "b", long = "bin")] + #[structopt(hidden = true, short = "b", long = "bin")] binary: bool, #[structopt(flatten)] credentials: Credentials, @@ -156,6 +167,9 @@ pub enum Command { authenticator: AuthenticatorParameters, #[structopt(flatten)] secret: SecretParameters, + /// Load credentials from LUKS header + #[structopt(env = "FIDO2LUKS_DEVICE")] + device: Option, }, /// Adds a generated key to the specified LUKS device #[structopt(name = "add-key")] @@ -171,9 +185,9 @@ pub enum Command { /// Will wipe all other keys #[structopt(short = "e", long = "exclusive")] exclusive: bool, - /// Will add an token to your LUKS 2 header, including the credential id - #[structopt(short = "t", long = "token")] - token: bool, + /// Will generate an credential for only this device + #[structopt(short = "a", long = "auto-cred")] + auto_credential: bool, #[structopt(flatten)] existing_secret: OtherSecret, #[structopt(flatten)] @@ -193,9 +207,9 @@ pub enum Command { /// Add the password and keep the key #[structopt(short = "a", long = "add-password")] add_password: bool, - /// Will add an token to your LUKS 2 header, including the credential id - #[structopt(short = "t", long = "token")] - token: bool, + /// Remove the affected credential for device header + #[structopt(short = "r", long = "remove-cred")] + remove_cred: bool, #[structopt(flatten)] replacement: OtherSecret, #[structopt(flatten)] @@ -217,20 +231,6 @@ pub enum Command { #[structopt(short = "r", long = "max-retries", default_value = "0")] retries: i32, }, - /// Open the LUKS device using credentials embedded in the LUKS 2 header - #[structopt(name = "open-token")] - OpenToken { - #[structopt(flatten)] - luks: LuksParameters, - #[structopt(env = "FIDO2LUKS_MAPPER_NAME")] - name: String, - #[structopt(flatten)] - authenticator: AuthenticatorParameters, - #[structopt(flatten)] - secret: SecretParameters, - #[structopt(short = "r", long = "max-retries", default_value = "0")] - retries: i32, - }, /// Generate a new FIDO credential #[structopt(name = "credential")] Credential { -- 2.49.0 From bd29452980e003297a99d1033b227fd6e51673f2 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 11 Oct 2020 19:05:27 +0200 Subject: [PATCH 02/31] use prebuild image --- .drone.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 7e3a505..0536eed 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,21 +8,19 @@ steps: - rustup component add rustfmt - cargo fmt --all -- --check - name: test - image: ubuntu:focal + image: shimun/fido2luks@sha256:c1c9b0fb36f24555bc575dd6f643efd3a83f9db25a7ed88af3e22eacf44a89b8 environment: DEBIAN_FRONTEND: noninteractive commands: - - apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev - cargo test --locked - name: publish - image: ubuntu:focal + image: shimun/fido2luks@sha256:c1c9b0fb36f24555bc575dd6f643efd3a83f9db25a7ed88af3e22eacf44a89b8 environment: DEBIAN_FRONTEND: noninteractive CARGO_REGISTRY_TOKEN: from_secret: cargo_tkn commands: - grep -E 'version ?= ?"${DRONE_TAG}"' -i Cargo.toml || (printf "incorrect crate/tag version" && exit 1) - - apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev - cargo package --all-features - cargo publish --all-features when: -- 2.49.0 From 8954de35580ee97cb0647b7fbdd6993462106f70 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 11 Oct 2020 21:11:42 +0200 Subject: [PATCH 03/31] 2 --- .drone.yml | 4 +- src/cli.rs | 269 +++++++++++++++++++++++++++----------------- src/cli_args/mod.rs | 30 ++++- src/error.rs | 2 + src/luks.rs | 22 +++- 5 files changed, 216 insertions(+), 111 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0536eed..79f57a1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,13 +8,13 @@ steps: - rustup component add rustfmt - cargo fmt --all -- --check - name: test - image: shimun/fido2luks@sha256:c1c9b0fb36f24555bc575dd6f643efd3a83f9db25a7ed88af3e22eacf44a89b8 + image: shimun/fido2luks@sha256:6d0b4017bffbec5fac8f25d383d68671fcc9930efb02e97ce5ea81acf0060ece environment: DEBIAN_FRONTEND: noninteractive commands: - cargo test --locked - name: publish - image: shimun/fido2luks@sha256:c1c9b0fb36f24555bc575dd6f643efd3a83f9db25a7ed88af3e22eacf44a89b8 + image: shimun/fido2luks@sha256:6d0b4017bffbec5fac8f25d383d68671fcc9930efb02e97ce5ea81acf0060ece environment: DEBIAN_FRONTEND: noninteractive CARGO_REGISTRY_TOKEN: diff --git a/src/cli.rs b/src/cli.rs index 06522fd..451f9d5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -20,17 +20,13 @@ use std::fs::File; use std::time::SystemTime; pub use cli_args::Args; +use failure::ResultExt; +use std::collections::hash_map::RandomState; use std::iter::FromIterator; use std::path::PathBuf; -fn read_pin(ap: &AuthenticatorParameters) -> Fido2LuksResult { - if let Some(src) = ap.pin_source.as_ref() { - let mut pin = String::new(); - File::open(src)?.read_to_string(&mut pin)?; - Ok(pin.trim_end_matches("\n").to_string()) //remove trailing newline - } else { - util::read_password("Authenticator PIN", false) - } +fn read_pin() -> Fido2LuksResult { + util::read_password("Authenticator PIN", false) } fn derive_secret( @@ -74,10 +70,15 @@ pub fn extend_creds_device( creds: &[HexEncoded], luks_dev: &mut LuksDevice, ) -> Fido2LuksResult> { - let mut additional = HashSet::from_iter(creds.iter().cloned()); + let mut additional = HashSet::new(); + additional.extend(creds.iter().cloned()); for token in luks_dev.tokens()? { for cred in token?.1.credential { - let parsed = HexEncoded::from_str(cred.as_str())?; + let parsed = HexEncoded::from_str(cred.as_str()).map_err(|_e| { + Fido2LuksError::HexEncodingError { + string: cred.clone(), + } + })?; additional.insert(parsed); } } @@ -107,7 +108,6 @@ pub fn parse_cmdline() -> Args { pub fn run_cli() -> Fido2LuksResult<()> { let mut stdout = io::stdout(); let args = parse_cmdline(); - let interactive = args.interactive; match &args.command { Command::Credential { authenticator, @@ -115,7 +115,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { } => { let pin_string; let pin = if authenticator.pin { - pin_string = read_pin(authenticator)?; + pin_string = read_pin()?; Some(pin_string.as_ref()) } else { None @@ -131,13 +131,6 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret, device, } => { - let pin_string; - let pin = if authenticator.pin { - pin_string = read_pin(authenticator)?; - Some(pin_string.as_ref()) - } else { - None - }; let (pin, salt) = match ( &secret.password_helper, authenticator.pin, @@ -147,15 +140,12 @@ pub fn run_cli() -> Fido2LuksResult<()> { (Some(phelper), true, true, false) => { read_password_pin_prefixed(|| phelper.obtain())? } - (Some(phelper), false, false, false) => (None, secret.salt.obtain_sha256(&phelper)), + (Some(phelper), false, false, false) => { + (None, secret.salt.obtain_sha256(&phelper)?) + } - (phelper, pin, _, true) => ( - if pin { - pin_string = read_pin(authenticator)?; - Some(pin_string.as_ref()) - } else { - None - }, + (phelper, pin, _, _) => ( + if pin { Some(read_pin()?) } else { None }, match phelper { None | Some(PasswordHelper::Stdin) => { util::read_password_hashed("Password", false) @@ -165,9 +155,17 @@ pub fn run_cli() -> Fido2LuksResult<()> { ), }; let credentials = if let Some(dev) = device { - extend_creds_device(credentials.ids.0.as_slice(), &mut LuksDevice::load(dev)?)? + extend_creds_device( + credentials + .ids + .clone() + .map(|cs| cs.0) + .unwrap_or_default() + .as_slice(), + &mut LuksDevice::load(dev)?, + )? } else { - credentials.ids.0 + credentials.ids.clone().map(|cs| cs.0).unwrap_or_default() }; let (secret, _cred) = derive_secret( credentials.as_slice(), @@ -189,7 +187,6 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret, luks_mod, existing_secret: other_secret, - auto_credential, .. } | Command::ReplaceKey { @@ -199,21 +196,54 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret, luks_mod, replacement: other_secret, - remove_cred, .. } => { - let pin = if authenticator.pin { - Some(read_pin(authenticator)?) + let mut luks_dev = LuksDevice::load(&luks.device)?; + + let luks2 = luks_dev.is_luks2()?; + + let credentials = if !luks.disable_token && luks2 { + extend_creds_device( + credentials + .ids + .clone() + .map(|cs| cs.0) + .unwrap_or_default() + .as_slice(), + &mut luks_dev, + )? } else { - None + credentials.ids.clone().map(|cs| cs.0).unwrap_or_default() }; - let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { - if interactive || secret.password_helper == PasswordHelper::Stdin { - util::read_password_hashed(q, verify) - } else { - secret.salt.obtain_sha256(&secret.password_helper) - } + + let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option, [u8; 32])> { + Ok( + match ( + &secret.password_helper, + authenticator.pin, + authenticator.pin_prefixed, + args.interactive, + ) { + (Some(phelper), true, true, false) => { + read_password_pin_prefixed(|| phelper.obtain())? + } + (Some(phelper), false, false, false) => { + (None, secret.salt.obtain_sha256(&phelper)?) + } + + (phelper, pin, _, _) => ( + if pin { Some(read_pin()?) } else { None }, + match &secret.password_helper { + None | Some(PasswordHelper::Stdin) => { + util::read_password_hashed(q, verify) + } + Some(phelper) => secret.salt.obtain_sha256(&phelper), + }?, + ), + }, + ) }; + let other_secret = |salt_q: &str, verify: bool| -> Fido2LuksResult<(Vec, Option)> { @@ -224,38 +254,53 @@ pub fn run_cli() -> Fido2LuksResult<()> { } => Ok((util::read_keyfile(file)?, None)), OtherSecret { fido_device: true, .. - } => Ok(derive_secret( - &credentials.ids.0, - &salt(salt_q, verify)?, - authenticator.await_time, - pin.as_deref(), - ) - .map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?), + } => { + let (pin, salt) = inputs(salt_q, verify)?; + Ok(derive_secret( + &credentials, + &salt, + authenticator.await_time, + pin.as_deref(), + ) + .map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?) + } _ => Ok(( util::read_password(salt_q, verify)?.as_bytes().to_vec(), None, )), } }; - let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> { - derive_secret( - &credentials.ids.0, - &salt("Password", verify)?, - authenticator.await_time, - pin.as_deref(), - ) + let secret = |verify: bool, + credentials: &[HexEncoded]| + -> Fido2LuksResult<([u8; 32], FidoCredential)> { + let (pin, salt) = inputs("Password", verify)?; + derive_secret(credentials, &salt, authenticator.await_time, pin.as_deref()) }; - let mut luks_dev = LuksDevice::load(&luks.device)?; // Non overlap match &args.command { - Command::AddKey { exclusive, .. } => { + Command::AddKey { + exclusive, + auto_credential, + .. + } => { let (existing_secret, _) = other_secret("Current password", false)?; - let (new_secret, cred) = secret(true)?; + let (new_secret, cred) = if *auto_credential && luks2 { + let cred = make_credential_id( + Some(luks.device.display().to_string().as_str()), + None, + )?; //TODO: do ask for PIN + let creds = vec![HexEncoded(cred.id)]; + secret(true, &creds) + } else { + secret(true, &credentials) + }?; let added_slot = luks_dev.add_key( &new_secret, &existing_secret[..], luks_mod.kdf_time.or(Some(10)), - Some(&cred.id[..]).filter(|_| *token), + Some(&cred.id[..]) + .filter(|_| !luks.disable_token || *auto_credential) + .filter(|_| luks2), )?; if *exclusive { let destroyed = luks_dev.remove_keyslots(&[added_slot])?; @@ -274,23 +319,37 @@ pub fn run_cli() -> Fido2LuksResult<()> { } Ok(()) } - Command::ReplaceKey { add_password, .. } => { - let (existing_secret, _) = secret(false)?; + Command::ReplaceKey { + add_password, + remove_cred, + .. + } => { + let (existing_secret, _prev_cred) = secret(false, &credentials)?; let (replacement_secret, cred) = other_secret("Replacement password", true)?; let slot = if *add_password { luks_dev.add_key( &replacement_secret[..], &existing_secret, luks_mod.kdf_time, - cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]), + cred.as_ref() + .filter(|_| !luks.disable_token) + .filter(|_| luks2) + .map(|cred| &cred.id[..]), ) } else { - luks_dev.replace_key( + let slot = luks_dev.replace_key( &replacement_secret[..], &existing_secret, luks_mod.kdf_time, - cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]), - ) + cred.as_ref() + .filter(|_| !luks.disable_token) + .filter(|_| luks2) + .map(|cred| &cred.id[..]), + )?; + if *remove_cred && cred.is_none() { + luks_dev.remove_token_slot(slot)?; + } + Ok(slot) }?; println!( "Added to password to device {}, slot: {}", @@ -307,48 +366,56 @@ pub fn run_cli() -> Fido2LuksResult<()> { authenticator, secret, name, - retries, - .. - } - | Command::OpenToken { - luks, - authenticator, - secret, - name, + credentials, retries, } => { - let pin_string; - let pin = if authenticator.pin { - pin_string = read_pin(authenticator)?; - Some(pin_string.as_ref()) - } else { - None - }; - let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { - if interactive || secret.password_helper == PasswordHelper::Stdin { - util::read_password_hashed(q, verify) - } else { - secret.salt.obtain_sha256(&secret.password_helper) - } + let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option, [u8; 32])> { + Ok( + match ( + &secret.password_helper, + authenticator.pin, + authenticator.pin_prefixed, + args.interactive, + ) { + (Some(phelper), true, true, false) => { + read_password_pin_prefixed(|| phelper.obtain())? + } + (Some(phelper), false, false, false) => { + (None, secret.salt.obtain_sha256(&phelper)?) + } + + (phelper, pin, _, _) => ( + if pin { Some(read_pin()?) } else { None }, + match &phelper { + None | Some(PasswordHelper::Stdin) => { + util::read_password_hashed(q, verify) + } + Some(phelper) => secret.salt.obtain_sha256(&phelper), + }?, + ), + }, + ) }; // Cow shouldn't be necessary let secret = |credentials: Cow<'_, Vec>| { + let (pin, salt) = inputs("Password", false)?; derive_secret( credentials.as_ref(), - &salt("Password", false)?, + &salt, authenticator.await_time, - pin, + pin.as_deref(), ) }; let mut retries = *retries; let mut luks_dev = LuksDevice::load(&luks.device)?; loop { - let secret = match &args.command { - Command::Open { credentials, .. } => secret(Cow::Borrowed(&credentials.ids.0)) - .and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot)), - Command::OpenToken { .. } => luks_dev.activate_token( + let slot = if let Some(ref credentials) = credentials.ids { + secret(Cow::Borrowed(&credentials.0)) + .and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot)) + } else if luks_dev.is_luks2()? { + luks_dev.activate_token( &name, Box::new(|credentials: Vec| { let creds = credentials @@ -359,10 +426,11 @@ pub fn run_cli() -> Fido2LuksResult<()> { .map(|(secret, cred)| (secret, hex::encode(&cred.id))) }), luks.slot, - ), - _ => unreachable!(), + ) + } else { + return Err(Fido2LuksError::WrongSecret); // creds or luks2 }; - match secret { + match slot { Err(e) => { match e { Fido2LuksError::WrongSecret if retries > 0 => {} @@ -444,7 +512,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { } } let count = if tokens.is_empty() { - dev.add_token(&Fido2LuksToken::with_credentials(&credentials.ids.0, *slot))?; + dev.add_token(&Fido2LuksToken::with_credentials(&credentials.0, *slot))?; 1 } else { tokens.len() @@ -452,7 +520,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { for (id, mut token) in tokens { token .credential - .extend(credentials.ids.0.iter().map(|h| h.to_string())); + .extend(credentials.0.iter().map(|h| h.to_string())); dev.update_token(id, &token)?; } println!("Updated {} tokens", count); @@ -480,7 +548,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { token.credential = token .credential .into_iter() - .filter(|cred| !credentials.ids.0.iter().any(|h| &h.to_string() == cred)) + .filter(|cred| !credentials.0.iter().any(|h| &h.to_string() == cred)) .collect(); dev.update_token(id, &token)?; } @@ -512,11 +580,8 @@ pub fn run_cli() -> Fido2LuksResult<()> { Command::GenerateCompletions { shell, out_dir } => { Args::clap().gen_completions( env!("CARGO_PKG_NAME"), - match shell.as_ref() { - "bash" => Shell::Bash, - "fish" => Shell::Fish, - _ => unreachable!("structopt shouldn't allow us to reach this point"), - }, + Shell::from_str(shell.as_str()) + .expect("structopt shouldn't allow us to reach this point"), &out_dir, ); Ok(()) diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index fffadce..435e6ca 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -1,4 +1,5 @@ use std::fmt::{Display, Error, Formatter}; +use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::str::FromStr; use structopt::clap::AppSettings; @@ -31,6 +32,12 @@ impl FromStr for HexEncoded { } } +impl Hash for HexEncoded { + fn hash(&self, state: &mut H) { + self.0.hash(state) + } +} + #[derive(Debug, Eq, PartialEq, Clone)] pub struct CommaSeparated(pub Vec); @@ -128,8 +135,7 @@ pub struct SecretParameters { #[structopt( name = "password-helper", env = "FIDO2LUKS_PASSWORD_HELPER", - long = "password-helper", - default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'" + long = "password-helper" )] pub password_helper: Option, } @@ -269,8 +275,14 @@ pub enum TokenCommand { Add { #[structopt(env = "FIDO2LUKS_DEVICE")] device: PathBuf, - #[structopt(flatten)] - credentials: Credentials, + /// FIDO credential ids, separated by ',' generate using fido2luks credential + #[structopt( + name = "credential-ids", + env = "FIDO2LUKS_CREDENTIAL_ID", + short = "c", + long = "creds" + )] + credentials: CommaSeparated, /// Slot to which the credentials will be added #[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")] slot: u32, @@ -279,8 +291,14 @@ pub enum TokenCommand { Remove { #[structopt(env = "FIDO2LUKS_DEVICE")] device: PathBuf, - #[structopt(flatten)] - credentials: Credentials, + /// FIDO credential ids, separated by ',' generate using fido2luks credential + #[structopt( + name = "credential-ids", + env = "FIDO2LUKS_CREDENTIAL_ID", + short = "c", + long = "creds" + )] + credentials: CommaSeparated, /// Token from which the credentials will be removed #[structopt(long = "token")] token_id: Option, diff --git a/src/error.rs b/src/error.rs index 03d9741..f0f1f76 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,6 +29,8 @@ pub enum Fido2LuksError { WrongSecret, #[fail(display = "not an utf8 string")] StringEncodingError { cause: FromUtf8Error }, + #[fail(display = "not an hex string: {}", string)] + HexEncodingError { string: String }, } impl Fido2LuksError { diff --git a/src/luks.rs b/src/luks.rs index 6996eef..4a91e73 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -103,6 +103,10 @@ impl LuksDevice { Ok(()) } + pub fn add_credential(&mut self, slot: u32, credential: Vec) -> Fido2LuksResult<()> { + self.add_token(&Fido2LuksToken::with_credentials(&[credential], slot)) + } + pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> { self.require_luks2()?; self.device @@ -111,6 +115,20 @@ impl LuksDevice { Ok(()) } + pub fn remove_token_slot(&mut self, slot: u32) -> Fido2LuksResult<()> { + let mut remove = HashSet::new(); + for token in self.tokens()? { + let (id, token) = token?; + if token.keyslots.contains(&slot.to_string()) { + remove.insert(id); + } + } + for rm in remove { + self.remove_token(rm)?; + } + Ok(()) + } + pub fn update_token(&mut self, token: u32, data: &Fido2LuksToken) -> Fido2LuksResult<()> { self.require_luks2()?; self.device @@ -192,7 +210,9 @@ impl LuksDevice { old_secret, CryptActivateFlags::empty(), )?; - self.device.keyslot_handle().change_by_passphrase( + + // slot should stay the same but better be safe than sorry + let slot = self.device.keyslot_handle().change_by_passphrase( Some(slot), Some(slot), old_secret, -- 2.49.0 From 99a536f2d4ca6b7623fed33cd28d87f7cd0d308c Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 11 Oct 2020 22:03:07 +0200 Subject: [PATCH 04/31] 3 --- src/cli.rs | 40 +++++++++++++++++++++++++--------------- src/cli_args/config.rs | 2 +- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 451f9d5..1584be6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,21 +9,19 @@ use structopt::StructOpt; use ctap::{FidoCredential, FidoErrorKind}; -use std::io::{Read, Write}; +use std::io::Write; use std::str::FromStr; use std::thread; use std::time::Duration; use std::borrow::Cow; use std::collections::HashSet; -use std::fs::File; + use std::time::SystemTime; pub use cli_args::Args; -use failure::ResultExt; -use std::collections::hash_map::RandomState; + use std::iter::FromIterator; -use std::path::PathBuf; fn read_pin() -> Fido2LuksResult { util::read_password("Authenticator PIN", false) @@ -96,9 +94,13 @@ pub fn read_password_pin_prefixed( Some(ref pin) if read.len() > pin.len() => { read.chars().skip(pin.len() + 1).collect::() } - _ => String::new(), + Some(_) => String::new(), + _ => read + .chars() + .skip(read.chars().next().map(|c| c == separator).unwrap_or(false) as usize) + .collect::(), }; - Ok((pin, util::sha256(&[password.as_bytes()]))) + Ok((dbg!(pin), util::sha256(&[dbg!(password).as_bytes()]))) } pub fn parse_cmdline() -> Args { @@ -231,7 +233,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { (None, secret.salt.obtain_sha256(&phelper)?) } - (phelper, pin, _, _) => ( + (_phelper, pin, _, _) => ( if pin { Some(read_pin()?) } else { None }, match &secret.password_helper { None | Some(PasswordHelper::Stdin) => { @@ -597,19 +599,27 @@ mod test { #[test] fn test_read_password_pin_prefixed() { assert_eq!( - read_password_pin_prefixed(|| OK("1234:test")), - Ok((Some("1234".to_string()), util::sha256(&["test".as_bytes()]))) + read_password_pin_prefixed(|| Ok("1234:test".into())).unwrap(), + (Some("1234".to_string()), util::sha256(&["test".as_bytes()])) ); assert_eq!( - read_password_pin_prefixed(|| OK(":test")), - Ok((None, util::sha256(&["test".as_bytes()]))) + read_password_pin_prefixed(|| Ok(":test".into())).unwrap(), + (None, util::sha256(&["test".as_bytes()])) ); assert_eq!( - read_password_pin_prefixed(|| OK("1234::test")), - Ok(( + read_password_pin_prefixed(|| Ok("1234::test".into())).unwrap(), + ( Some("1234".to_string()), util::sha256(&[":test".as_bytes()]) - )) + ) + ); + assert_eq!( + read_password_pin_prefixed(|| Ok("1234".into())).unwrap(), + (Some("1234".to_string()), util::sha256(&["".as_bytes()])) + ); + assert_eq!( + read_password_pin_prefixed(|| Ok(":test".into())).unwrap(), + (None, util::sha256(&["test".as_bytes()])) ); } } diff --git a/src/cli_args/config.rs b/src/cli_args/config.rs index d2ef46c..cde093f 100644 --- a/src/cli_args/config.rs +++ b/src/cli_args/config.rs @@ -198,7 +198,7 @@ mod test { fn input_salt_obtain() { assert_eq!( SecretInput::String("abc".into()) - .obtain(&PasswordHelper::Stdin) + .obtain_sha256(&PasswordHelper::Stdin) .unwrap(), [ 186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97, -- 2.49.0 From 88b9677e7acabb6243299ce8be907178b80a2f08 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 11 Oct 2020 22:23:45 +0200 Subject: [PATCH 05/31] 4 --- src/cli.rs | 3 +++ src/error.rs | 2 ++ src/luks.rs | 4 ---- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 1584be6..bcffa7f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -33,6 +33,9 @@ fn derive_secret( timeout: u64, pin: Option<&str>, ) -> Fido2LuksResult<([u8; 32], FidoCredential)> { + if credentials.len() == 0 { + return Err(Fido2LuksError::InsufficientCredentials); + } let timeout = Duration::from_secs(timeout); let start = SystemTime::now(); diff --git a/src/error.rs b/src/error.rs index f0f1f76..dc29d3d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,6 +31,8 @@ pub enum Fido2LuksError { StringEncodingError { cause: FromUtf8Error }, #[fail(display = "not an hex string: {}", string)] HexEncodingError { string: String }, + #[fail(display = "couldn't obtain at least one credential")] + InsufficientCredentials, } impl Fido2LuksError { diff --git a/src/luks.rs b/src/luks.rs index 4a91e73..7b024ca 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -103,10 +103,6 @@ impl LuksDevice { Ok(()) } - pub fn add_credential(&mut self, slot: u32, credential: Vec) -> Fido2LuksResult<()> { - self.add_token(&Fido2LuksToken::with_credentials(&[credential], slot)) - } - pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> { self.require_luks2()?; self.device -- 2.49.0 From ae96d3ba5de548958f8e09bafb48937a1f4be8e9 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 11 Oct 2020 22:41:18 +0200 Subject: [PATCH 06/31] remove dbg --- src/cli.rs | 2 +- src/cli_args/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index bcffa7f..4bae469 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -103,7 +103,7 @@ pub fn read_password_pin_prefixed( .skip(read.chars().next().map(|c| c == separator).unwrap_or(false) as usize) .collect::(), }; - Ok((dbg!(pin), util::sha256(&[dbg!(password).as_bytes()]))) + Ok((pin, util::sha256(&[password.as_bytes()]))) } pub fn parse_cmdline() -> Args { diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index 435e6ca..42dae84 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -191,7 +191,7 @@ pub enum Command { /// Will wipe all other keys #[structopt(short = "e", long = "exclusive")] exclusive: bool, - /// Will generate an credential for only this device + /// Will generate an credential while adding a new key to this LUKS device if supported #[structopt(short = "a", long = "auto-cred")] auto_credential: bool, #[structopt(flatten)] @@ -213,7 +213,7 @@ pub enum Command { /// Add the password and keep the key #[structopt(short = "a", long = "add-password")] add_password: bool, - /// Remove the affected credential for device header + /// Remove the affected credential from LUKS header #[structopt(short = "r", long = "remove-cred")] remove_cred: bool, #[structopt(flatten)] -- 2.49.0 From 24a06b9085d75ee40609342fe0b05d0ad27c5a90 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 13 Oct 2020 19:18:35 +0200 Subject: [PATCH 07/31] doc --- src/cli.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 4bae469..1b5d208 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -87,9 +87,9 @@ pub fn extend_creds_device( } pub fn read_password_pin_prefixed( - reader: impl Fn() -> Fido2LuksResult, + prefixed: impl Fn() -> Fido2LuksResult, ) -> Fido2LuksResult<(Option, [u8; 32])> { - let read = reader()?; + let read = prefixed()?; let separator = ':'; let mut parts = read.split(separator); let pin = parts.next().filter(|p| p.len() > 0).map(|p| p.to_string()); @@ -601,14 +601,17 @@ mod test { #[test] fn test_read_password_pin_prefixed() { + // 1234:test -> PIN: 1234, password: test assert_eq!( read_password_pin_prefixed(|| Ok("1234:test".into())).unwrap(), (Some("1234".to_string()), util::sha256(&["test".as_bytes()])) ); + // :test -> PIN: None, password: test assert_eq!( read_password_pin_prefixed(|| Ok(":test".into())).unwrap(), (None, util::sha256(&["test".as_bytes()])) ); + // 1234::test -> PIN: 1234, password: :test assert_eq!( read_password_pin_prefixed(|| Ok("1234::test".into())).unwrap(), ( @@ -616,10 +619,12 @@ mod test { util::sha256(&[":test".as_bytes()]) ) ); + // 1234 -> PIN: 1234, password: empty assert_eq!( read_password_pin_prefixed(|| Ok("1234".into())).unwrap(), (Some("1234".to_string()), util::sha256(&["".as_bytes()])) ); + // 1234:test -> PIN: None, password: test assert_eq!( read_password_pin_prefixed(|| Ok(":test".into())).unwrap(), (None, util::sha256(&["test".as_bytes()])) -- 2.49.0 From 716a845e555f7aeb9d66c18e628c96d41dd0abf5 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 13 Oct 2020 19:23:02 +0200 Subject: [PATCH 08/31] open-token alias --- src/cli_args/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index 42dae84..7b99b0d 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -222,7 +222,7 @@ pub enum Command { luks_mod: LuksModParameters, }, /// Open the LUKS device - #[structopt(name = "open")] + #[structopt(name = "open", alias = "open-token")] Open { #[structopt(flatten)] luks: LuksParameters, -- 2.49.0 From e5c6ca9237015d7cde64604664de52c802376f51 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 13 Oct 2020 19:27:59 +0200 Subject: [PATCH 09/31] use claps variant list --- src/cli_args/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index 7b99b0d..bafd36a 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Error, Formatter}; use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::str::FromStr; -use structopt::clap::AppSettings; +use structopt::clap::{AppSettings, Shell}; use structopt::StructOpt; mod config; @@ -253,8 +253,8 @@ pub enum Command { /// Generate bash completion scripts #[structopt(name = "completions", setting = AppSettings::Hidden)] GenerateCompletions { - /// Shell to generate completions for: bash, fish - #[structopt(possible_values = &["bash", "fish"])] + /// Shell to generate completions for + #[structopt(possible_values = &Shell::variants()[..])] shell: String, out_dir: PathBuf, }, -- 2.49.0 From 4b09fcb6cb8dd6ce10a6184634cbe90b915331f3 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 13 Oct 2020 21:25:42 +0200 Subject: [PATCH 10/31] honour disable-token --- CHANGELOG.md | 0 src/cli.rs | 9 ++++++--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/src/cli.rs b/src/cli.rs index 1b5d208..5698574 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -33,7 +33,7 @@ fn derive_secret( timeout: u64, pin: Option<&str>, ) -> Fido2LuksResult<([u8; 32], FidoCredential)> { - if credentials.len() == 0 { + if credentials.is_empty() { return Err(Fido2LuksError::InsufficientCredentials); } let timeout = Duration::from_secs(timeout); @@ -419,7 +419,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { let slot = if let Some(ref credentials) = credentials.ids { secret(Cow::Borrowed(&credentials.0)) .and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot)) - } else if luks_dev.is_luks2()? { + } else if luks_dev.is_luks2()? && !luks.disable_token { luks_dev.activate_token( &name, Box::new(|credentials: Vec| { @@ -432,8 +432,11 @@ pub fn run_cli() -> Fido2LuksResult<()> { }), luks.slot, ) + } else if luks_dev.is_luks2()? && luks.disable_token { + // disable-token is mostly cosmetic in this instance + return Err(Fido2LuksError::InsufficientCredentials); } else { - return Err(Fido2LuksError::WrongSecret); // creds or luks2 + return Err(Fido2LuksError::WrongSecret); }; match slot { Err(e) => { -- 2.49.0 From ab23fe5ac9393f0e4f9e41a01f400330641e51ad Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 13 Oct 2020 21:29:06 +0200 Subject: [PATCH 11/31] remove env attr to keep --disable-token as flag --- src/cli_args/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index bafd36a..a682655 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -104,7 +104,10 @@ pub struct LuksParameters { pub slot: Option, /// Disable implicit use of LUKS2 tokens - #[structopt(long = "disable-token", env = "FIDO2LUKS_DISABLE_TOKEN")] + #[structopt( + long = "disable-token", + // env = "FIDO2LUKS_DISABLE_TOKEN" // unfortunately clap will convert flags into args if they have an env attribute + )] pub disable_token: bool, } -- 2.49.0 From a5c0840a59ff0a3c611a763cb434bb1ca996b90b Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 13 Oct 2020 23:00:44 +0200 Subject: [PATCH 12/31] update deps --- Cargo.lock | 56 ++++++++++++++++++++++++++++++------------------------ Cargo.toml | 6 +++--- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3f2431..ac154cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,9 +17,9 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" [[package]] name = "aho-corasick" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d" dependencies = [ "memchr", ] @@ -58,12 +58,12 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.52" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f813291114c186a042350e787af10c26534601062603d888be110f59f85ef8fa" +checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e" dependencies = [ "addr2line", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", @@ -78,7 +78,7 @@ checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36" dependencies = [ "bitflags", "cexpr", - "cfg-if", + "cfg-if 0.1.10", "clang-sys", "clap", "env_logger", @@ -149,6 +149,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clang-sys" version = "0.29.3" @@ -190,7 +196,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", @@ -226,7 +232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ "autocfg 1.0.1", - "cfg-if", + "cfg-if 0.1.10", "crossbeam-utils", "lazy_static", "maybe-uninit", @@ -240,7 +246,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "crossbeam-utils", "maybe-uninit", ] @@ -252,7 +258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.0.1", - "cfg-if", + "cfg-if 0.1.10", "lazy_static", ] @@ -554,7 +560,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -620,9 +626,9 @@ dependencies = [ [[package]] name = "object" -version = "0.20.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" +checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693" [[package]] name = "peeking_take_while" @@ -632,9 +638,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pkg-config" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "proc-macro-error" @@ -842,9 +848,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.9" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" dependencies = [ "aho-corasick", "memchr", @@ -863,9 +869,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" [[package]] name = "ring" @@ -966,9 +972,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ "itoa", "ryu", @@ -995,9 +1001,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "structopt" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a7159e7d0dbcab6f9c980d7971ef50f3ff5753081461eeda120d5974a4ee95" +checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8" dependencies = [ "clap", "lazy_static", @@ -1006,9 +1012,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc47de4dfba76248d1e9169ccff240eea2a4dc1e34e309b95b2393109b4b383" +checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8" dependencies = [ "heck", "proc-macro-error", diff --git a/Cargo.toml b/Cargo.toml index f9b83e8..97ba046 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,10 @@ ring = "0.13.5" failure = "0.1.5" rpassword = "4.0.1" structopt = "0.3.2" -libcryptsetup-rs = "0.4.1" +libcryptsetup-rs = "0.4.2" serde_json = "1.0.51" -serde_derive = "1.0.106" -serde = "1.0.106" +serde_derive = "1.0.116" +serde = "1.0.116" [build-dependencies] ctap_hmac = { version="0.4.2", features = ["request_multiple"] } -- 2.49.0 From a264f4c9ebbb6cd76ce9ae5f7ad14bb57a90834e Mon Sep 17 00:00:00 2001 From: shimun Date: Sat, 17 Oct 2020 17:58:28 +0200 Subject: [PATCH 13/31] rewrite helper --- src/cli.rs | 133 +++++++++++++++-------------------------- src/cli_args/config.rs | 26 +++++--- src/error.rs | 2 + 3 files changed, 68 insertions(+), 93 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 5698574..6607946 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,26 +2,19 @@ use crate::error::*; use crate::luks::{Fido2LuksToken, LuksDevice}; use crate::util::sha256; use crate::*; +pub use cli_args::Args; use cli_args::*; - -use structopt::clap::Shell; -use structopt::StructOpt; - use ctap::{FidoCredential, FidoErrorKind}; - +use std::borrow::Cow; +use std::collections::HashSet; use std::io::Write; +use std::iter::FromIterator; use std::str::FromStr; use std::thread; use std::time::Duration; - -use std::borrow::Cow; -use std::collections::HashSet; - use std::time::SystemTime; - -pub use cli_args::Args; - -use std::iter::FromIterator; +use structopt::clap::Shell; +use structopt::StructOpt; fn read_pin() -> Fido2LuksResult { util::read_password("Authenticator PIN", false) @@ -86,6 +79,43 @@ pub fn extend_creds_device( Ok(Vec::from_iter(additional.into_iter())) } +pub fn get_input( + secret: &SecretParameters, + authenticator: &AuthenticatorParameters, + interactive: bool, + q: &str, + verify: bool, +) -> Fido2LuksResult<(Option, [u8; 32])> { + let password_helper = secret + .password_helper + .as_ref() + .map(|helper| move || helper.obtain()); + let salt = &secret.salt; + Ok(if interactive { + ( + if authenticator.pin { + Some(util::read_password("PIN", false)?) + } else { + None + }, + salt.obtain_sha256(Some(|| util::read_password(q, verify)))?, + ) + } else { + match (authenticator.pin, authenticator.pin_prefixed) { + (true, false) => ( + Some(util::read_password("PIN", false)?), + salt.obtain_sha256(password_helper)?, + ), + (true, true) => read_password_pin_prefixed(|| { + salt.obtain(password_helper).and_then(|secret| { + String::from_utf8(secret).map_err(|e| Fido2LuksError::from(e)) + }) + })?, + (false, _) => (None, salt.obtain_sha256(password_helper)?), + } + }) +} + pub fn read_password_pin_prefixed( prefixed: impl Fn() -> Fido2LuksResult, ) -> Fido2LuksResult<(Option, [u8; 32])> { @@ -136,29 +166,8 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret, device, } => { - let (pin, salt) = match ( - &secret.password_helper, - authenticator.pin, - authenticator.pin_prefixed, - args.interactive, - ) { - (Some(phelper), true, true, false) => { - read_password_pin_prefixed(|| phelper.obtain())? - } - (Some(phelper), false, false, false) => { - (None, secret.salt.obtain_sha256(&phelper)?) - } - - (phelper, pin, _, _) => ( - if pin { Some(read_pin()?) } else { None }, - match phelper { - None | Some(PasswordHelper::Stdin) => { - util::read_password_hashed("Password", false) - } - Some(phelper) => secret.salt.obtain_sha256(&phelper), - }?, - ), - }; + let (pin, salt) = + get_input(&secret, &authenticator, args.interactive, "Password", false)?; let credentials = if let Some(dev) = device { extend_creds_device( credentials @@ -222,31 +231,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { }; let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option, [u8; 32])> { - Ok( - match ( - &secret.password_helper, - authenticator.pin, - authenticator.pin_prefixed, - args.interactive, - ) { - (Some(phelper), true, true, false) => { - read_password_pin_prefixed(|| phelper.obtain())? - } - (Some(phelper), false, false, false) => { - (None, secret.salt.obtain_sha256(&phelper)?) - } - - (_phelper, pin, _, _) => ( - if pin { Some(read_pin()?) } else { None }, - match &secret.password_helper { - None | Some(PasswordHelper::Stdin) => { - util::read_password_hashed(q, verify) - } - Some(phelper) => secret.salt.obtain_sha256(&phelper), - }?, - ), - }, - ) + get_input(&secret, &authenticator, args.interactive, q, verify) }; let other_secret = |salt_q: &str, @@ -375,31 +360,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { retries, } => { let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option, [u8; 32])> { - Ok( - match ( - &secret.password_helper, - authenticator.pin, - authenticator.pin_prefixed, - args.interactive, - ) { - (Some(phelper), true, true, false) => { - read_password_pin_prefixed(|| phelper.obtain())? - } - (Some(phelper), false, false, false) => { - (None, secret.salt.obtain_sha256(&phelper)?) - } - - (phelper, pin, _, _) => ( - if pin { Some(read_pin()?) } else { None }, - match &phelper { - None | Some(PasswordHelper::Stdin) => { - util::read_password_hashed(q, verify) - } - Some(phelper) => secret.salt.obtain_sha256(&phelper), - }?, - ), - }, - ) + get_input(&secret, &authenticator, args.interactive, q, verify) }; // Cow shouldn't be necessary diff --git a/src/cli_args/config.rs b/src/cli_args/config.rs index cde093f..00bdbbc 100644 --- a/src/cli_args/config.rs +++ b/src/cli_args/config.rs @@ -55,11 +55,17 @@ impl fmt::Display for SecretInput { } impl SecretInput { - pub fn obtain_string(&self, password_helper: &PasswordHelper) -> Fido2LuksResult { + pub fn obtain_string( + &self, + password_helper: Option Fido2LuksResult>, + ) -> Fido2LuksResult { Ok(String::from_utf8(self.obtain(password_helper)?)?) } - pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult> { + pub fn obtain( + &self, + password_helper: Option Fido2LuksResult>, + ) -> Fido2LuksResult> { let mut secret = Vec::new(); match self { SecretInput::File { path } => { @@ -67,16 +73,22 @@ impl SecretInput { let mut do_io = || File::open(path)?.read_to_end(&mut secret); do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?; } - SecretInput::AskPassword => { - secret.extend_from_slice(password_helper.obtain()?.as_bytes()) - } + SecretInput::AskPassword => secret.extend_from_slice( + password_helper.ok_or_else(|| Fido2LuksError::AskPassError { + cause: AskPassError::FailedHelper, + })?()? + .as_bytes(), + ), SecretInput::String(s) => secret.extend_from_slice(s.as_bytes()), } Ok(secret) } - pub fn obtain_sha256(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> { + pub fn obtain_sha256( + &self, + password_helper: Option Fido2LuksResult>, + ) -> Fido2LuksResult<[u8; 32]> { let mut digest = digest::Context::new(&digest::SHA256); match self { SecretInput::File { path } => { @@ -198,7 +210,7 @@ mod test { fn input_salt_obtain() { assert_eq!( SecretInput::String("abc".into()) - .obtain_sha256(&PasswordHelper::Stdin) + .obtain_sha256(None) .unwrap(), [ 186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97, diff --git a/src/error.rs b/src/error.rs index dc29d3d..f451f1c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -54,6 +54,8 @@ pub enum AskPassError { IO(io::Error), #[fail(display = "provided passwords don't match")] Mismatch, + #[fail(display = "failed to call password helper")] + FailedHelper, } #[derive(Debug, Fail)] -- 2.49.0 From 49a751274339260fb08d49ba53854bff79a984bd Mon Sep 17 00:00:00 2001 From: shimun Date: Sat, 17 Oct 2020 18:27:12 +0200 Subject: [PATCH 14/31] dry-run --- src/cli.rs | 7 +++++-- src/cli_args/config.rs | 2 +- src/cli_args/mod.rs | 3 +++ src/luks.rs | 11 +++++++++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 6607946..7a40941 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -358,6 +358,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { name, credentials, retries, + dry_run, } => { let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option, [u8; 32])> { get_input(&secret, &authenticator, args.interactive, q, verify) @@ -378,8 +379,9 @@ pub fn run_cli() -> Fido2LuksResult<()> { let mut luks_dev = LuksDevice::load(&luks.device)?; loop { let slot = if let Some(ref credentials) = credentials.ids { - secret(Cow::Borrowed(&credentials.0)) - .and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot)) + secret(Cow::Borrowed(&credentials.0)).and_then(|(secret, _cred)| { + luks_dev.activate(&name, &secret, luks.slot, *dry_run) + }) } else if luks_dev.is_luks2()? && !luks.disable_token { luks_dev.activate_token( &name, @@ -392,6 +394,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { .map(|(secret, cred)| (secret, hex::encode(&cred.id))) }), luks.slot, + *dry_run, ) } else if luks_dev.is_luks2()? && luks.disable_token { // disable-token is mostly cosmetic in this instance diff --git a/src/cli_args/config.rs b/src/cli_args/config.rs index 00bdbbc..bb1bb57 100644 --- a/src/cli_args/config.rs +++ b/src/cli_args/config.rs @@ -210,7 +210,7 @@ mod test { fn input_salt_obtain() { assert_eq!( SecretInput::String("abc".into()) - .obtain_sha256(None) + .obtain_sha256(Some(|| Ok("123456".to_string()))) .unwrap(), [ 186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97, diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index a682655..5b9acad 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -239,6 +239,9 @@ pub enum Command { secret: SecretParameters, #[structopt(short = "r", long = "max-retries", default_value = "0")] retries: i32, + /// Don't actually mount the LUKS image + #[structopt(long = "dry-run")] + dry_run: bool, }, /// Generate a new FIDO credential #[structopt(name = "credential")] diff --git a/src/luks.rs b/src/luks.rs index 7b024ca..fc40183 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -237,10 +237,16 @@ impl LuksDevice { name: &str, secret: &[u8], slot_hint: Option, + dry_run: bool, ) -> Fido2LuksResult { self.device .activate_handle() - .activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty()) + .activate_by_passphrase( + Some(name).filter(|_| !dry_run), + slot_hint, + secret, + CryptActivateFlags::empty(), + ) .map_err(LuksError::activate) } @@ -249,6 +255,7 @@ impl LuksDevice { name: &str, secret: impl Fn(Vec) -> Fido2LuksResult<([u8; 32], String)>, slot_hint: Option, + dry_run: bool, ) -> Fido2LuksResult { if !self.is_luks2()? { return Err(LuksError::Luks2Required.into()); @@ -292,7 +299,7 @@ impl LuksDevice { .chain(std::iter::once(None).take(slots.is_empty() as usize)), // Try all slots as last resort ); for slot in slots { - match self.activate(name, &secret, slot) { + match self.activate(name, &secret, slot, dry_run) { Err(Fido2LuksError::WrongSecret) => (), res => return res, } -- 2.49.0 From 8e98bf024eb5acf4a1cc2a0bf9e9ea7832efefce Mon Sep 17 00:00:00 2001 From: shimun Date: Sat, 17 Oct 2020 18:38:21 +0200 Subject: [PATCH 15/31] read_pin --- src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 7a40941..4890522 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -94,7 +94,7 @@ pub fn get_input( Ok(if interactive { ( if authenticator.pin { - Some(util::read_password("PIN", false)?) + Some(read_pin()?) } else { None }, @@ -103,7 +103,7 @@ pub fn get_input( } else { match (authenticator.pin, authenticator.pin_prefixed) { (true, false) => ( - Some(util::read_password("PIN", false)?), + Some(read_pin()?), salt.obtain_sha256(password_helper)?, ), (true, true) => read_password_pin_prefixed(|| { -- 2.49.0 From 2ed7f8141f91f616e867f538cb611275c958bd0a Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Oct 2020 15:39:30 +0200 Subject: [PATCH 16/31] verbose --- src/cli.rs | 83 ++++++++++++++++++++++++++++++++++-------- src/cli_args/config.rs | 2 +- src/cli_args/mod.rs | 2 + src/util.rs | 18 +++++---- 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 4890522..0f09b3d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,7 +17,7 @@ use structopt::clap::Shell; use structopt::StructOpt; fn read_pin() -> Fido2LuksResult { - util::read_password("Authenticator PIN", false) + util::read_password_tty("Authenticator PIN", false) } fn derive_secret( @@ -98,14 +98,11 @@ pub fn get_input( } else { None }, - salt.obtain_sha256(Some(|| util::read_password(q, verify)))?, + salt.obtain_sha256(Some(|| util::read_password_tty(q, verify)))?, ) } else { match (authenticator.pin, authenticator.pin_prefixed) { - (true, false) => ( - Some(read_pin()?), - salt.obtain_sha256(password_helper)?, - ), + (true, false) => (Some(read_pin()?), salt.obtain_sha256(password_helper)?), (true, true) => read_password_pin_prefixed(|| { salt.obtain(password_helper).and_then(|secret| { String::from_utf8(secret).map_err(|e| Fido2LuksError::from(e)) @@ -143,6 +140,11 @@ pub fn parse_cmdline() -> Args { pub fn run_cli() -> Fido2LuksResult<()> { let mut stdout = io::stdout(); let args = parse_cmdline(); + let log = |message: &dyn Fn() -> String| { + if args.verbose { + eprintln!("{}", message()); + } + }; match &args.command { Command::Credential { authenticator, @@ -168,7 +170,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { } => { let (pin, salt) = get_input(&secret, &authenticator, args.interactive, "Password", false)?; - let credentials = if let Some(dev) = device { + let credentials = if let Some(path) = device { + let mut dev = LuksDevice::load(path)?; + let luks2 = dev.is_luks2()?; + log(&|| format!("luks2 supported: {}", luks2)); extend_creds_device( credentials .ids @@ -176,17 +181,28 @@ pub fn run_cli() -> Fido2LuksResult<()> { .map(|cs| cs.0) .unwrap_or_default() .as_slice(), - &mut LuksDevice::load(dev)?, + &mut dev, )? } else { credentials.ids.clone().map(|cs| cs.0).unwrap_or_default() }; - let (secret, _cred) = derive_secret( + log(&|| { + format!( + "credentials: {}", + credentials + .iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + ) + }); + let (secret, cred) = derive_secret( credentials.as_slice(), &salt, authenticator.await_time, pin.as_deref(), )?; + log(&|| format!("credential used: {}", hex::encode(&cred.id))); if *binary { stdout.write_all(&secret[..])?; } else { @@ -216,6 +232,8 @@ pub fn run_cli() -> Fido2LuksResult<()> { let luks2 = luks_dev.is_luks2()?; + log(&|| format!("luks2 supported: {}", luks2)); + let credentials = if !luks.disable_token && luks2 { extend_creds_device( credentials @@ -229,7 +247,16 @@ pub fn run_cli() -> Fido2LuksResult<()> { } else { credentials.ids.clone().map(|cs| cs.0).unwrap_or_default() }; - + log(&|| { + format!( + "credentials: {}", + credentials + .iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + ) + }); let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option, [u8; 32])> { get_input(&secret, &authenticator, args.interactive, q, verify) }; @@ -255,7 +282,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { .map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?) } _ => Ok(( - util::read_password(salt_q, verify)?.as_bytes().to_vec(), + util::read_password_tty(salt_q, verify)?.as_bytes().to_vec(), None, )), } @@ -284,6 +311,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { } else { secret(true, &credentials) }?; + log(&|| format!("credential used: {}", hex::encode(&cred.id))); let added_slot = luks_dev.add_key( &new_secret, &existing_secret[..], @@ -341,6 +369,9 @@ pub fn run_cli() -> Fido2LuksResult<()> { } Ok(slot) }?; + if let Some(cred) = cred { + log(&|| format!("credential used: {}", hex::encode(&cred.id))); + } println!( "Added to password to device {}, slot: {}", luks.device.display(), @@ -377,21 +408,38 @@ pub fn run_cli() -> Fido2LuksResult<()> { let mut retries = *retries; let mut luks_dev = LuksDevice::load(&luks.device)?; + let luks2 = luks_dev.is_luks2()?; + log(&|| format!("luks2 supported: {}", luks2)); loop { let slot = if let Some(ref credentials) = credentials.ids { - secret(Cow::Borrowed(&credentials.0)).and_then(|(secret, _cred)| { + log(&|| { + format!( + "credentials: {}", + credentials + .0 + .iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + ) + }); + secret(Cow::Borrowed(&credentials.0)).and_then(|(secret, cred)| { + log(&|| format!("credential used: {}", hex::encode(&cred.id))); luks_dev.activate(&name, &secret, luks.slot, *dry_run) }) - } else if luks_dev.is_luks2()? && !luks.disable_token { + } else if luks2 && !luks.disable_token { luks_dev.activate_token( &name, Box::new(|credentials: Vec| { + log(&|| format!("credentials: {}", credentials.join(", "))); let creds = credentials .into_iter() .flat_map(|cred| HexEncoded::from_str(cred.as_ref()).ok()) .collect::>(); - secret(Cow::Owned(creds)) - .map(|(secret, cred)| (secret, hex::encode(&cred.id))) + secret(Cow::Owned(creds)).map(|(secret, cred)| { + log(&|| format!("credential used: {}", hex::encode(&cred.id))); + (secret, hex::encode(&cred.id)) + }) }), luks.slot, *dry_run, @@ -414,7 +462,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { retries -= 1; eprintln!("{}", e); } - res => break res.map(|_| ()), + Ok(slot) => { + log(&|| format!("keyslot: {}", slot)); + break Ok(()); + } } } } diff --git a/src/cli_args/config.rs b/src/cli_args/config.rs index bb1bb57..a223b83 100644 --- a/src/cli_args/config.rs +++ b/src/cli_args/config.rs @@ -163,7 +163,7 @@ impl PasswordHelper { use PasswordHelper::*; match self { Systemd => unimplemented!(), - Stdin => Ok(util::read_password("Password", true)?), + Stdin => Ok(util::read_password("Password", true, false)?), Script(password_helper) => { let password = Command::new("sh") .arg("-c") diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index 5b9acad..5c1c024 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -147,6 +147,8 @@ pub struct Args { /// Request passwords via Stdin instead of using the password helper #[structopt(short = "i", long = "interactive")] pub interactive: bool, + #[structopt(short = "v", long = "verbose")] + pub verbose: bool, #[structopt(subcommand)] pub command: Command, } diff --git a/src/util.rs b/src/util.rs index 3676480..5ca21ff 100644 --- a/src/util.rs +++ b/src/util.rs @@ -13,9 +13,17 @@ pub fn sha256(messages: &[&[u8]]) -> [u8; 32] { secret.as_mut().copy_from_slice(digest.finish().as_ref()); secret } - -pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult { - match rpassword::read_password_from_tty(Some(&[q, ": "].join("")))? { +pub fn read_password_tty(q: &str, verify: bool) -> Fido2LuksResult { + read_password(q, verify, true) +} +pub fn read_password(q: &str, verify: bool, tty: bool) -> Fido2LuksResult { + let res = if tty { + rpassword::read_password_from_tty(Some(&[q, ": "].join(""))) + } else { + print!("{}: ", q); + rpassword::read_password() + }?; + match res { ref pass if verify && &rpassword::read_password_from_tty(Some(&[q, "(again): "].join(" ")))? @@ -29,10 +37,6 @@ pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult { } } -pub fn read_password_hashed(q: &str, verify: bool) -> Fido2LuksResult<[u8; 32]> { - read_password(q, verify).map(|pass| sha256(&[pass.as_bytes()])) -} - pub fn read_keyfile>(path: P) -> Fido2LuksResult> { let mut file = File::open(path.into())?; let mut key = Vec::new(); -- 2.49.0 From 516b590739c2b5c499c970c34e6bb68bb385cdfe Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Oct 2020 15:46:56 +0200 Subject: [PATCH 17/31] describe dry-run --- src/cli_args/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index 5c1c024..d79088f 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -241,7 +241,7 @@ pub enum Command { secret: SecretParameters, #[structopt(short = "r", long = "max-retries", default_value = "0")] retries: i32, - /// Don't actually mount the LUKS image + /// Perform the whole procedure without mounting the LUKS volume on success #[structopt(long = "dry-run")] dry_run: bool, }, -- 2.49.0 From 81c2bbf692f456561c9e7e6e25116898957c21c0 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Oct 2020 15:47:30 +0200 Subject: [PATCH 18/31] changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..e19c064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +## 0.3.0 + +* LUKS2 Tokens are now supported by every subcommand +* `` has been converted into the flag `--creds` +credentials provided by `--creds` will be supplemented from the LUKS header unless this is disabled by `--disable-token` +* `fido2luks add-key` will take an `--auto-cred` flag which allows for credentials to be generated and stored without having to use `fido2luks credential` +`fido2luks replace-key` will allow for credentials to be removed using the `--remove-cred` flag respectively +* Removed `fido2luks open-token` subcommand + `fido2luks open` now fulfills both functions +* Added `fido2luks open --dry-run` flag, to perform the whole procedure apart from mounting the LUKS volume +* Added an `--verbose` flag to display additional information like credentials and keyslots used if desired \ No newline at end of file -- 2.49.0 From 6f9941a107449d38abcf63cef9d1c51f26beffeb Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Oct 2020 16:39:28 +0200 Subject: [PATCH 19/31] readable --- src/cli.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 0f09b3d..75fbc7c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,6 +9,7 @@ use std::borrow::Cow; use std::collections::HashSet; use std::io::Write; use std::iter::FromIterator; +use std::path::Path; use std::str::FromStr; use std::thread; use std::time::Duration; @@ -133,6 +134,20 @@ pub fn read_password_pin_prefixed( Ok((pin, util::sha256(&[password.as_bytes()]))) } +/// generate an more readable name from common paths +pub fn derive_credential_name(path: &Path) -> String { + match path.file_name() { + Some(name) + if path + .iter() + .any(|p| p == "by-label" || p == "by-partlabel" || p == "by-uuid") => + { + name.to_string_lossy().as_ref().to_string() + } + _ => path.display().to_string(), + } +} + pub fn parse_cmdline() -> Args { Args::from_args() } @@ -303,9 +318,22 @@ pub fn run_cli() -> Fido2LuksResult<()> { let (existing_secret, _) = other_secret("Current password", false)?; let (new_secret, cred) = if *auto_credential && luks2 { let cred = make_credential_id( - Some(luks.device.display().to_string().as_str()), - None, - )?; //TODO: do ask for PIN + Some(derive_credential_name(luks.device.as_path()).as_str()), + (if authenticator.pin { + //TODO: not ideal since it ignores pin-prefixed + Some(read_pin()?) + } else { + None + }) + .as_deref(), + )?; + log(&|| { + format!( + "generated credential: {}\ncredential username: {:?}", + hex::encode(&cred.id), + derive_credential_name(luks.device.as_path()) + ) + }); let creds = vec![HexEncoded(cred.id)]; secret(true, &creds) } else { -- 2.49.0 From be2639d9fecc69a2ecaa9ab778a8cf4e8085270d Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Oct 2020 16:47:36 +0200 Subject: [PATCH 20/31] tolerate generated bash-completion scripts --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 79f57a1..469fb73 100644 --- a/.drone.yml +++ b/.drone.yml @@ -21,7 +21,7 @@ steps: from_secret: cargo_tkn commands: - grep -E 'version ?= ?"${DRONE_TAG}"' -i Cargo.toml || (printf "incorrect crate/tag version" && exit 1) - - cargo package --all-features - - cargo publish --all-features + - cargo package --all-features --allow-dirty + - cargo publish --all-features --allow-dirty when: event: tag -- 2.49.0 From 8e2948fbb9f9d0952c2b00564620cad68e5be123 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Oct 2020 20:01:36 +0200 Subject: [PATCH 21/31] use CARGO_MANIFEST_DIR instead of PWD --- build.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index c520b10..c6bd9c0 100644 --- a/build.rs +++ b/build.rs @@ -19,6 +19,10 @@ use structopt::StructOpt; fn main() { // generate completion scripts, zsh does panic for some reason for shell in Shell::variants().iter().filter(|shell| **shell != "zsh") { - Args::clap().gen_completions(env!("CARGO_PKG_NAME"), Shell::from_str(shell).unwrap(), "."); + Args::clap().gen_completions( + env!("CARGO_PKG_NAME"), + Shell::from_str(shell).unwrap(), + env!("CARGO_MANIFEST_DIR"), + ); } } -- 2.49.0 From 06f97592c18309fce2b42e5762021a4f0425c19b Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Oct 2020 20:28:50 +0200 Subject: [PATCH 22/31] dectect enabled PIN --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- src/cli.rs | 13 ++++++++----- src/cli_args/mod.rs | 2 +- src/device.rs | 15 +++++++++++++-- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac154cf..782efa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "ctap_hmac" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ccc28f298181e943187fa63805e652bc4806ad43708beebd22c230fbe0baa3" +checksum = "c83f25a6970eb60fd32754c99ffb0b89ff464d70bf8dea147805497c7fb7eb5e" dependencies = [ "byteorder", "cbor-codec", diff --git a/Cargo.toml b/Cargo.toml index 97ba046..9546d26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ categories = ["command-line-utilities"] license = "MPL-2.0" [dependencies] -ctap_hmac = { version="0.4.2", features = ["request_multiple"] } +ctap_hmac = { version="0.4.4", features = ["request_multiple"] } hex = "0.3.2" ring = "0.13.5" failure = "0.1.5" @@ -26,7 +26,7 @@ serde_derive = "1.0.116" serde = "1.0.116" [build-dependencies] -ctap_hmac = { version="0.4.2", features = ["request_multiple"] } +ctap_hmac = { version="0.4.4", features = ["request_multiple"] } hex = "0.3.2" ring = "0.13.5" failure = "0.1.5" diff --git a/src/cli.rs b/src/cli.rs index 75fbc7c..11fd9c8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -94,7 +94,7 @@ pub fn get_input( let salt = &secret.salt; Ok(if interactive { ( - if authenticator.pin { + if authenticator.pin && may_require_pin()? { Some(read_pin()?) } else { None @@ -102,7 +102,10 @@ pub fn get_input( salt.obtain_sha256(Some(|| util::read_password_tty(q, verify)))?, ) } else { - match (authenticator.pin, authenticator.pin_prefixed) { + match ( + authenticator.pin && may_require_pin()?, + authenticator.pin_prefixed, + ) { (true, false) => (Some(read_pin()?), salt.obtain_sha256(password_helper)?), (true, true) => read_password_pin_prefixed(|| { salt.obtain(password_helper).and_then(|secret| { @@ -157,7 +160,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { let args = parse_cmdline(); let log = |message: &dyn Fn() -> String| { if args.verbose { - eprintln!("{}", message()); + eprintln!("{}", &*message()); } }; match &args.command { @@ -166,7 +169,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { name, } => { let pin_string; - let pin = if authenticator.pin { + let pin = if authenticator.pin && may_require_pin()? { pin_string = read_pin()?; Some(pin_string.as_ref()) } else { @@ -319,7 +322,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { let (new_secret, cred) = if *auto_credential && luks2 { let cred = make_credential_id( Some(derive_credential_name(luks.device.as_path()).as_str()), - (if authenticator.pin { + (if authenticator.pin && may_require_pin()? { //TODO: not ideal since it ignores pin-prefixed Some(read_pin()?) } else { diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index d79088f..ac5744f 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -76,7 +76,7 @@ pub struct Credentials { #[derive(Debug, StructOpt)] pub struct AuthenticatorParameters { - /// Request a PIN to unlock the authenticator + /// Request a PIN to unlock the authenticator if required #[structopt(short = "P", long = "pin")] pub pin: bool, diff --git a/src/device.rs b/src/device.rs index 99ac2a7..e735165 100644 --- a/src/device.rs +++ b/src/device.rs @@ -19,7 +19,7 @@ pub fn make_credential_id( } let request = request.build().unwrap(); let make_credential = |device: &mut FidoDevice| { - if let Some(pin) = pin { + if let Some(pin) = pin.filter(|_| device.needs_pin()) { device.unlock(pin)?; } device.make_hmac_credential(&request) @@ -44,7 +44,7 @@ pub fn perform_challenge<'a>( .build() .unwrap(); let get_assertion = |device: &mut FidoDevice| { - if let Some(pin) = pin { + if let Some(pin) = pin.filter(|_| device.needs_pin()) { device.unlock(pin)?; } device.get_hmac_assertion(&request, &util::sha256(&[&salt[..]]), None) @@ -58,6 +58,17 @@ pub fn perform_challenge<'a>( Ok((secret, credential)) } +pub fn may_require_pin() -> Fido2LuksResult { + for di in ctap::get_devices()? { + if let Ok(dev) = FidoDevice::new(&di) { + if dev.needs_pin() { + return Ok(true); + } + } + } + Ok(false) +} + pub fn get_devices() -> Fido2LuksResult> { let mut devices = Vec::with_capacity(2); for di in ctap::get_devices()? { -- 2.49.0 From d8aca91136185bef5c8e0b18a2bb66d408133fcc Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 27 Oct 2020 15:03:05 +0100 Subject: [PATCH 23/31] generate all completions by default --- src/cli.rs | 25 +++++++++++++++++++------ src/cli_args/mod.rs | 5 +++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 11fd9c8..e43ed11 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -632,12 +632,25 @@ pub fn run_cli() -> Fido2LuksResult<()> { } }, Command::GenerateCompletions { shell, out_dir } => { - Args::clap().gen_completions( - env!("CARGO_PKG_NAME"), - Shell::from_str(shell.as_str()) - .expect("structopt shouldn't allow us to reach this point"), - &out_dir, - ); + // zsh won't work atm https://github.com/clap-rs/clap/issues/1822 + if let Some(s) = shell { + if s.as_str() == "zsh" { + unimplemented!("zsh completions are broken atm: see https://github.com/clap-rs/clap/issues/1822") + } + } + for variant in Shell::variants().iter().filter(|v| *v != &"zsh") { + if let Some(s) = shell { + if *variant != s.as_str() { + break; + } + } + Args::clap().gen_completions( + env!("CARGO_PKG_NAME"), + Shell::from_str(variant) + .expect("structopt shouldn't allow us to reach this point"), + &out_dir, + ); + } Ok(()) } } diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index ac5744f..322e155 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -259,11 +259,12 @@ pub enum Command { Connected, Token(TokenCommand), /// Generate bash completion scripts + /// Example: fido2luks completions --shell bash /usr/share/bash-completion/completions #[structopt(name = "completions", setting = AppSettings::Hidden)] GenerateCompletions { /// Shell to generate completions for - #[structopt(possible_values = &Shell::variants()[..])] - shell: String, + #[structopt(short = "s", long = "shell",possible_values = &Shell::variants()[..])] + shell: Option, out_dir: PathBuf, }, } -- 2.49.0 From 543198a5fea7f894d3d54c9343f5a884a6c7c32f Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 27 Oct 2020 15:44:26 +0100 Subject: [PATCH 24/31] auto_credential -> generate_credential --- src/cli.rs | 6 +++--- src/cli_args/mod.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index e43ed11..00834cc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -315,11 +315,11 @@ pub fn run_cli() -> Fido2LuksResult<()> { match &args.command { Command::AddKey { exclusive, - auto_credential, + generate_credential, .. } => { let (existing_secret, _) = other_secret("Current password", false)?; - let (new_secret, cred) = if *auto_credential && luks2 { + let (new_secret, cred) = if *generate_credential && luks2 { let cred = make_credential_id( Some(derive_credential_name(luks.device.as_path()).as_str()), (if authenticator.pin && may_require_pin()? { @@ -348,7 +348,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { &existing_secret[..], luks_mod.kdf_time.or(Some(10)), Some(&cred.id[..]) - .filter(|_| !luks.disable_token || *auto_credential) + .filter(|_| !luks.disable_token || *generate_credential) .filter(|_| luks2), )?; if *exclusive { diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index 322e155..354066b 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -197,8 +197,8 @@ pub enum Command { #[structopt(short = "e", long = "exclusive")] exclusive: bool, /// Will generate an credential while adding a new key to this LUKS device if supported - #[structopt(short = "a", long = "auto-cred")] - auto_credential: bool, + #[structopt(short = "g", long = "gen-cred")] + generate_credential: bool, #[structopt(flatten)] existing_secret: OtherSecret, #[structopt(flatten)] -- 2.49.0 From f37ad8e78b935baf7dbe3cfab2a1214dcde4b1f2 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 27 Oct 2020 15:53:21 +0100 Subject: [PATCH 25/31] update readme --- README_NEW.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 README_NEW.md diff --git a/README_NEW.md b/README_NEW.md new file mode 100644 index 0000000..fea8b3e --- /dev/null +++ b/README_NEW.md @@ -0,0 +1,79 @@ +# fido2luks [![Crates.io Version](https://img.shields.io/crates/v/fido2luks.svg)](https://crates.io/crates/fido2luks) + +This will allow you to unlock your LUKS encrypted disk with an FIDO2 compatible key. + +Note: This has only been tested under Fedora 31, [Ubuntu 20.04](initramfs-tools/), [NixOS](https://nixos.org/nixos/manual/#sec-luks-file-systems-fido2) using a Solo Key, Trezor Model T + +## Installation + +### From Source + +Installing from source requires the following dependencies: + +Ubuntu: `cargo, libclang-dev, libcryptsetup-dev >= 2.2` + +Fedora: `cargo, clang-devel, cryptsetup-devel` + +To compile the fido2luks binary you can simply run `sudo cargo install --root /usr fido2luks` but since you may want to install the scripts included it this repo as well, +it's recommended to clone the repo and install from there. + +`` +git clone https://github.com/shimunn/fido2luks.git +cargo install --root /usr --path fido2luks +`` + +Continue with further instructions for [Ubuntu](initramfs-tools) or [Fedora](dracut) + +### From Package + +Ubuntu: see [releases](https://github.com/shimunn/fido2luks/releases) + +NixOS: https://nixos.org/nixos/manual/#sec-luks-file-systems-fido2 + +ArchLinux: +* [AUR](https://aur.archlinux.org/packages/fido2luks/) +* [Git](PKGBUILD) + +Fedora: coming soon + +## Credentials + +Depending on the version of cryptsetup and the age of your installation your LUKS header might be in the LUKS2 format already if that's the case fido2luks will be able to spare you from dealing with just another config file by simply storeing all the required information within your LUKS header. +If your header is still using the LUKS1 format you may convert it: + +``` +cryptsetup convert --type luks2 +``` + +if you want to keep using LUKS1 due to other software such as pam_mount not being compatible with LUKS2 at the moment, you will have to generate credentials by hand an add them to `/etc/fido2luks.conf` otherwise you can skip this step. +``` +fido2luks credential [optional name] +``` +the generated hexadecimal credential can then be added to `FIDO2LUKS_CREDENTIAL_ID=` in `/etc/fido2luks.conf` multiple credentials can be separated by comma. + +## Adding a Key + +If you had to generate a credential in the previous step you'll have to provide it to the following commands as a parameter or via an environment variable: + +``` +set -a +. /etc/fido2luks.conf +``` + +To then add the key you need to have your current password/keyfile ready: + +without having generated a credential in the previous step: `fido2luks -i add-key --gen-cred ` + +with a keyfile: `fido2luks -i add-key --keyfile ` + +if you've confirmed at a later stage that everything works as expected you may want to remove your keyfile/password by running the previous commands with the `--exclusive` flag which will remove all other keys from the device. + +## Replacing a Key + +with password: `fido2luks -i replace-key ` + +with keyfile: `fido2luks -i replace-key -d ` + +with another fido2 derived key: `fido2luks -i replace-key -f ` + + -- 2.49.0 From 39b90d27b7f5488f19ed28f81da2d72a81303e2e Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 27 Oct 2020 15:53:42 +0100 Subject: [PATCH 26/31] update keyscript --- initramfs-tools/keyscript.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/initramfs-tools/keyscript.sh b/initramfs-tools/keyscript.sh index d6c8a02..1c59617 100755 --- a/initramfs-tools/keyscript.sh +++ b/initramfs-tools/keyscript.sh @@ -7,8 +7,4 @@ if [ -z "$FIDO2LUKS_PASSWORD_HELPER" ]; then export FIDO2LUKS_PASSWORD_HELPER="plymouth ask-for-password --prompt '$MSG'" fi -if [ "$FIDO2LUKS_USE_TOKEN" -eq 1 ]; then - export FIDO2LUKS_CREDENTIAL_ID="$FIDO2LUKS_CREDENTIAL_ID,$(fido2luks token list --csv $CRYPTTAB_SOURCE)" -fi - -fido2luks print-secret --bin +fido2luks print-secret --bin "$CRYPTTAB_SOURCE" $([ "$FIDO2LUKS_USE_TOKEN" -eq 0 ] && printf "--disable-token") -- 2.49.0 From a75d1af01bd6f935446e809d56bdc4741d41bd0e Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 18 Apr 2021 16:33:07 +0200 Subject: [PATCH 27/31] added: hydra job --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index cc6df56..c9f1d05 100644 --- a/flake.nix +++ b/flake.nix @@ -46,6 +46,8 @@ ''; }; + hydraJobs = checks // packages; + # `nix develop` devShell = pkgs.mkShell { nativeBuildInputs = with pkgs; [ rustc cargo rustfmt nixpkgs-fmt ] ++ nativeBuildInputs; -- 2.49.0 From f53096dc5b42eff95c3e9d86f5cdfddb7a392e58 Mon Sep 17 00:00:00 2001 From: shimun Date: Fri, 16 Jul 2021 15:11:31 +0200 Subject: [PATCH 28/31] password helper: inherit stdin, stderr should make fido2luks much easier to use in boot scripts since it will allow for usage as follows: `fido2luks open-token /dev/disk/by-uuid/sda1 test 'bash -c "read -p Pass PW 1>&2; echo $PW"'` which will read the password from the current terminal --- src/cli_args/config.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli_args/config.rs b/src/cli_args/config.rs index cde093f..ed99975 100644 --- a/src/cli_args/config.rs +++ b/src/cli_args/config.rs @@ -7,6 +7,7 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; use std::process::Command; +use std::process::Stdio; use std::str::FromStr; #[derive(Debug, Clone, PartialEq)] @@ -156,6 +157,8 @@ impl PasswordHelper { let password = Command::new("sh") .arg("-c") .arg(&password_helper) + .stdin(Stdio::inherit()) + .stderr(Stdio::inherit()) .output() .map_err(|e| Fido2LuksError::AskPassError { cause: error::AskPassError::IO(e), -- 2.49.0 From 38b3a77b784147e5d83e28b7df5e992d0587ba20 Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 28 Jul 2021 19:27:42 +0200 Subject: [PATCH 29/31] added: Dockerimage for .deb build --- initramfs-tools/Dockerfile | 15 +++++++++++++++ initramfs-tools/build-deb.sh | 9 +++++++++ 2 files changed, 24 insertions(+) create mode 100644 initramfs-tools/Dockerfile create mode 100755 initramfs-tools/build-deb.sh diff --git a/initramfs-tools/Dockerfile b/initramfs-tools/Dockerfile new file mode 100644 index 0000000..62b6d63 --- /dev/null +++ b/initramfs-tools/Dockerfile @@ -0,0 +1,15 @@ +FROM rust:bullseye + +RUN cargo install -f cargo-deb --debug --version 1.30.0 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update && apt install -y cryptsetup pkg-config libclang-dev libcryptsetup-dev && mkdir -p /build/fido2luks + +WORKDIR /build/fido2luks + +ENV CARGO_TARGET_DIR=/build/fido2luks/target + +RUN cargo install fido2luks -f + +CMD bash -xc 'cp -rf /code/* /build/fido2luks && cargo-deb && cp target/debian/*.deb /out' diff --git a/initramfs-tools/build-deb.sh b/initramfs-tools/build-deb.sh new file mode 100755 index 0000000..6306327 --- /dev/null +++ b/initramfs-tools/build-deb.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -ex + +docker build . -t fido2luks-deb + +mkdir -p debs + +docker run -ti -v "$(pwd)/..:/code:ro" -v "$(pwd)/debs:/out" fido2luks-deb -- 2.49.0 From 0c9d001bd0b9dec684397be2c159dc0291504530 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 5 Sep 2021 16:11:04 +0200 Subject: [PATCH 30/31] flake.lock: Update Flake input changes: * Updated 'naersk': 'github:nmattia/naersk/6e149bfd726a8ebefa415f2d713ba6d942435abd' -> 'github:nmattia/naersk/df71f5e4babda41cd919a8684b72218e2e809fa9' * Updated 'nixpkgs': 'github:NixOS/nixpkgs/2118cf551b9944cfdb929b8ea03556f097dd0381' -> 'github:NixOS/nixpkgs/01ee7961039dabf15caca202c3416451e5290ff4' * Updated 'utils': 'github:numtide/flake-utils/3982c9903e93927c2164caa727cd3f6a0e6d14cc' -> 'github:numtide/flake-utils/997f7efcb746a9c140ce1f13c72263189225f482' --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index b2c2de9..c396698 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1612192764, - "narHash": "sha256-7EnLtZQWP6511G1ZPA7FmJlqAr3hWsAYb24tvTvJ/ec=", + "lastModified": 1629707199, + "narHash": "sha256-sGxlmfp5eXL5sAMNqHSb04Zq6gPl+JeltIZ226OYN0w=", "owner": "nmattia", "repo": "naersk", - "rev": "6e149bfd726a8ebefa415f2d713ba6d942435abd", + "rev": "df71f5e4babda41cd919a8684b72218e2e809fa9", "type": "github" }, "original": { @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1613208951, - "narHash": "sha256-pxDu+9KZBnZkMcKBqSAqw1oQwTIbxTeIwcMtygLsWio=", + "lastModified": 1630823084, + "narHash": "sha256-E6TZPt67MSONOdFc5YxTWZQUlS2+1aWllbAz4OQrl8A=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2118cf551b9944cfdb929b8ea03556f097dd0381", + "rev": "01ee7961039dabf15caca202c3416451e5290ff4", "type": "github" }, "original": { @@ -43,11 +43,11 @@ }, "utils": { "locked": { - "lastModified": 1610051610, - "narHash": "sha256-U9rPz/usA1/Aohhk7Cmc2gBrEEKRzcW4nwPWMPwja4Y=", + "lastModified": 1629481132, + "narHash": "sha256-JHgasjPR0/J1J3DRm4KxM4zTyAj4IOJY8vIl75v/kPI=", "owner": "numtide", "repo": "flake-utils", - "rev": "3982c9903e93927c2164caa727cd3f6a0e6d14cc", + "rev": "997f7efcb746a9c140ce1f13c72263189225f482", "type": "github" }, "original": { -- 2.49.0 From 4bac5142ccea45c183c2de303f96f72fc1d66cd7 Mon Sep 17 00:00:00 2001 From: shimun Date: Sat, 11 Dec 2021 11:43:25 +0100 Subject: [PATCH 31/31] chore: update flake.lock --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index c396698..29d2eda 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1629707199, - "narHash": "sha256-sGxlmfp5eXL5sAMNqHSb04Zq6gPl+JeltIZ226OYN0w=", + "lastModified": 1639051343, + "narHash": "sha256-62qARP+5Q0GmudcpuQHJP3/yXIgmUVoHR4orD/+FAC4=", "owner": "nmattia", "repo": "naersk", - "rev": "df71f5e4babda41cd919a8684b72218e2e809fa9", + "rev": "ebde51ec0eec82dc71eaca03bc24cf8eb44a3d74", "type": "github" }, "original": { @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1630823084, - "narHash": "sha256-E6TZPt67MSONOdFc5YxTWZQUlS2+1aWllbAz4OQrl8A=", + "lastModified": 1638109994, + "narHash": "sha256-OpA37PTiPMIqoRJbufbl5rOLII7HeeGcA0yl7FoyCIE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "01ee7961039dabf15caca202c3416451e5290ff4", + "rev": "a284564b7f75ac4db73607db02076e8da9d42c9d", "type": "github" }, "original": { @@ -43,11 +43,11 @@ }, "utils": { "locked": { - "lastModified": 1629481132, - "narHash": "sha256-JHgasjPR0/J1J3DRm4KxM4zTyAj4IOJY8vIl75v/kPI=", + "lastModified": 1638122382, + "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", "owner": "numtide", "repo": "flake-utils", - "rev": "997f7efcb746a9c140ce1f13c72263189225f482", + "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", "type": "github" }, "original": { -- 2.49.0