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 {