From d5b043840f732653c01971bd67f74974ccf473f0 Mon Sep 17 00:00:00 2001 From: shimun Date: Sat, 26 Mar 2022 13:28:29 +0100 Subject: [PATCH] chore: migrate to ctap-hid-fido2 --- Cargo.lock | 164 ++++++++++++++++++++++---------------------------- Cargo.toml | 6 +- build.rs | 1 - src/cli.rs | 74 ++++++++++++----------- src/device.rs | 137 ++++++++++++++++++++++------------------- src/error.rs | 16 ++--- src/main.rs | 1 - 7 files changed, 200 insertions(+), 199 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c56ec51..139303a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -54,7 +66,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.9", + "time", ] [[package]] @@ -147,6 +159,22 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bumpalo" version = "3.9.1" @@ -180,6 +208,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clang-sys" version = "1.3.1" @@ -207,20 +244,30 @@ dependencies = [ ] [[package]] -name = "ctap-hid-fido2" -version = "2.2.2" +name = "cpufeatures" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad55f610dc4c229ee03c8ca8c79f201b57d5b75797d15c0fa415f8c48d9834bf" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ + "libc", +] + +[[package]] +name = "ctap-hid-fido2" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d39f08d7b0949b5c82b2d3cc5d721eb47c8d42f889da8ee722b6b42db7a408" +dependencies = [ + "aes", "anyhow", "base64", + "block-modes", "byteorder", "hex 0.4.3", "hidapi", "num", "pad", "ring", - "rust-crypto", "serde", "serde_cbor", "strum", @@ -304,6 +351,7 @@ dependencies = [ name = "fido2luks" version = "0.3.0-alpha" dependencies = [ + "anyhow", "ctap-hid-fido2", "failure", "hex 0.3.2", @@ -317,16 +365,14 @@ dependencies = [ ] [[package]] -name = "fuchsia-cprng" -version = "0.1.1" +name = "generic-array" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] [[package]] name = "getrandom" @@ -630,6 +676,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "pad" version = "0.1.6" @@ -702,53 +754,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "regex" version = "1.5.5" @@ -791,19 +796,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rust-crypto" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -dependencies = [ - "gcc", - "libc", - "rand 0.3.23", - "rustc-serialize", - "time 0.1.43", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -816,12 +808,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-serialize" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" - [[package]] name = "rusticata-macros" version = "4.1.0" @@ -1027,16 +1013,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "time" version = "0.3.9" @@ -1055,6 +1031,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "ucd-trie" version = "0.1.3" @@ -1233,5 +1215,5 @@ dependencies = [ "oid-registry", "rusticata-macros", "thiserror", - "time 0.3.9", + "time", ] diff --git a/Cargo.toml b/Cargo.toml index 917d66b..764e12e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ categories = ["command-line-utilities"] license = "MPL-2.0" [dependencies] -ctap-hid-fido2 = "2.2.2" +ctap-hid-fido2 = "2.2.3" hex = "0.3.2" ring = "0.16.5" failure = "0.1.5" @@ -24,14 +24,16 @@ libcryptsetup-rs = "0.4.2" serde_json = "1.0.51" serde_derive = "1.0.116" serde = "1.0.116" +anyhow = "1.0.56" [build-dependencies] -ctap-hid-fido2 = "2.2.2" +ctap-hid-fido2 = "2.2.3" hex = "0.3.2" ring = "0.16.5" failure = "0.1.5" rpassword = "4.0.1" libcryptsetup-rs = "0.4.1" +anyhow = "1.0.56" structopt = "0.3.2" [profile.release] diff --git a/build.rs b/build.rs index c6bd9c0..47ea4bc 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,6 @@ #![allow(warnings)] #[macro_use] extern crate failure; -extern crate ctap_hmac as ctap; #[path = "src/cli_args/mod.rs"] mod cli_args; diff --git a/src/cli.rs b/src/cli.rs index 1f5a2b5..c245f6e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,7 +4,7 @@ use crate::util::sha256; use crate::*; pub use cli_args::Args; use cli_args::*; -use ctap::{FidoCredential, FidoErrorKind}; +use ctap_hid_fido2::public_key_credential_descriptor::PublicKeyCredentialDescriptor; use std::borrow::Cow; use std::collections::HashSet; use std::io::Write; @@ -26,31 +26,31 @@ fn derive_secret( salt: &[u8; 32], timeout: u64, pin: Option<&str>, -) -> Fido2LuksResult<([u8; 32], FidoCredential)> { +) -> Fido2LuksResult<([u8; 32], PublicKeyCredentialDescriptor)> { if credentials.is_empty() { return Err(Fido2LuksError::InsufficientCredentials); } let timeout = Duration::from_secs(timeout); let start = SystemTime::now(); - while let Ok(el) = start.elapsed() { - if el > timeout { - return Err(error::Fido2LuksError::NoAuthenticatorError); - } - if get_devices() - .map(|devices| !devices.is_empty()) - .unwrap_or(false) - { - break; - } - thread::sleep(Duration::from_millis(500)); - } + //while let Ok(el) = start.elapsed() { + // if el > timeout { + // return Err(error::Fido2LuksError::NoAuthenticatorError); + // } + // if get_devices() + // .map(|devices| !devices.is_empty()) + // .unwrap_or(false) + // { + // break; + // } + // thread::sleep(Duration::from_millis(500)); + //} let credentials = credentials .iter() - .map(|hex| FidoCredential { + .map(|hex| PublicKeyCredentialDescriptor { id: hex.0.clone(), - public_key: None, + ctype: Default::default(), }) .collect::>(); let credentials = credentials.iter().collect::>(); @@ -289,7 +289,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { let other_secret = |salt_q: &str, verify: bool| - -> Fido2LuksResult<(Vec, Option)> { + -> Fido2LuksResult<( + Vec, + Option, + )> { match other_secret { OtherSecret { keyfile: Some(file), @@ -314,14 +317,15 @@ pub fn run_cli() -> Fido2LuksResult<()> { )), } }; - let secret = |q: &str, - verify: bool, - credentials: &[HexEncoded]| - -> Fido2LuksResult<([u8; 32], FidoCredential)> { - let (pin, salt) = inputs(q, verify)?; - prompt_interaction(interactive); - derive_secret(credentials, &salt, authenticator.await_time, pin.as_deref()) - }; + let secret = + |q: &str, + verify: bool, + credentials: &[HexEncoded]| + -> Fido2LuksResult<([u8; 32], PublicKeyCredentialDescriptor)> { + let (pin, salt) = inputs(q, verify)?; + prompt_interaction(interactive); + derive_secret(credentials, &salt, authenticator.await_time, pin.as_deref()) + }; // Non overlap match &args.command { Command::AddKey { @@ -501,9 +505,8 @@ pub fn run_cli() -> Fido2LuksResult<()> { Err(e) => { match e { Fido2LuksError::WrongSecret if retries > 0 => {} - Fido2LuksError::AuthenticatorError { ref cause } - if cause.kind() == FidoErrorKind::Timeout && retries > 0 => {} - + //Fido2LuksError::AuthenticatorError { ref cause } + // if cause.kind() == FidoErrorKind::Timeout && retries > 0 => {} e => return Err(e), }; retries -= 1; @@ -516,13 +519,14 @@ pub fn run_cli() -> Fido2LuksResult<()> { } } } - Command::Connected => match get_devices() { - Ok(ref devs) if !devs.is_empty() => { - println!("Found {} devices", devs.len()); - Ok(()) - } - _ => exit(1), - }, + Command::Connected => unimplemented!("Not supported by current backend"), + //Command::Connected => match get_devices() { + // Ok(ref devs) if !devs.is_empty() => { + // println!("Found {} devices", devs.len()); + // Ok(()) + // } + // _ => exit(1), + //}, Command::Token(cmd) => match cmd { TokenCommand::List { device, diff --git a/src/device.rs b/src/device.rs index e735165..119942f 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,84 +1,99 @@ use crate::error::*; use crate::util; -use ctap::{ - self, extensions::hmac::HmacExtension, request_multiple_devices, FidoAssertionRequestBuilder, - FidoCredential, FidoCredentialRequestBuilder, FidoDevice, FidoError, FidoErrorKind, -}; +use ctap_hid_fido2; +use ctap_hid_fido2::get_assertion_params; +use ctap_hid_fido2::get_assertion_with_args; +use ctap_hid_fido2::get_info; +use ctap_hid_fido2::make_credential_params; +use ctap_hid_fido2::public_key_credential_descriptor::PublicKeyCredentialDescriptor; +use ctap_hid_fido2::public_key_credential_user_entity::PublicKeyCredentialUserEntity; +use ctap_hid_fido2::GetAssertionArgsBuilder; +use ctap_hid_fido2::LibCfg; +use ctap_hid_fido2::MakeCredentialArgsBuilder; use std::time::Duration; const RP_ID: &str = "fido2luks"; +fn lib_cfg() -> LibCfg { + let mut cfg = LibCfg::init(); + cfg.enable_log = false; + cfg +} + pub fn make_credential_id( name: Option<&str>, pin: Option<&str>, -) -> Fido2LuksResult { - let mut request = FidoCredentialRequestBuilder::default().rp_id(RP_ID); - if let Some(user_name) = name { - request = request.user_name(user_name); +) -> Fido2LuksResult { + let mut req = MakeCredentialArgsBuilder::new(RP_ID, &[]) + .extensions(&[make_credential_params::Extension::HmacSecret(Some(true))]); + if let Some(pin) = pin { + req = req.pin(pin); + } else { + req = req.without_pin_and_uv(); } - let request = request.build().unwrap(); - let make_credential = |device: &mut FidoDevice| { - if let Some(pin) = pin.filter(|_| device.needs_pin()) { - device.unlock(pin)?; - } - device.make_hmac_credential(&request) - }; - Ok(request_multiple_devices( - get_devices()? - .iter_mut() - .map(|device| (device, &make_credential)), - None, - )?) + if let Some(_) = name { + req = req.rkparam(&PublicKeyCredentialUserEntity::new( + Some(b"00"), + name.clone(), + name, + )); + } + let resp = ctap_hid_fido2::make_credential_with_args(&lib_cfg(), &req.build())?; + Ok(resp.credential_descriptor) } pub fn perform_challenge<'a>( - credentials: &'a [&'a FidoCredential], + credentials: &'a [&'a PublicKeyCredentialDescriptor], salt: &[u8; 32], timeout: Duration, pin: Option<&str>, -) -> Fido2LuksResult<([u8; 32], &'a FidoCredential)> { - let request = FidoAssertionRequestBuilder::default() - .rp_id(RP_ID) - .credentials(credentials) - .build() - .unwrap(); - let get_assertion = |device: &mut FidoDevice| { - if let Some(pin) = pin.filter(|_| device.needs_pin()) { - device.unlock(pin)?; - } - device.get_hmac_assertion(&request, &util::sha256(&[&salt[..]]), None) - }; - let (credential, (secret, _)) = request_multiple_devices( - get_devices()? - .iter_mut() - .map(|device| (device, &get_assertion)), - Some(timeout), - )?; - 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); +) -> Fido2LuksResult<([u8; 32], &'a PublicKeyCredentialDescriptor)> { + let mut req = GetAssertionArgsBuilder::new(RP_ID, &[]).extensions(&[ + get_assertion_params::Extension::HmacSecret(Some(util::sha256(&[&salt[..]]))), + ]); + if let Some(pin) = pin { + req = req.pin(pin); + } else { + req = req.without_pin_and_uv(); + } + let resp = get_assertion_with_args(&lib_cfg(), &req.build())?; + fn dbg_hex<'a>(name: &str, vec: &'a Vec) -> &'a Vec { + dbg!((name, hex::encode(&vec))); + vec + } + let cred_used2 = credentials.iter().copied().find(|cred| { + resp.iter() + .any(|att| dbg_hex("att", &att.credential_id) == dbg_hex("cred", &cred.id)) + }); + for att in resp { + for ext in att.extensions.iter() { + match ext { + get_assertion_params::Extension::HmacSecret(Some(secret)) => { + dbg!(cred_used2); + //TODO: eliminate unwrap + let cred_used = credentials + .iter() + .copied() + .find(|cred| { + dbg_hex("att", &att.credential_id) == dbg_hex("cred", &cred.id) + }) + .unwrap(); + return Ok((secret.clone(), cred_used)); + } + _ => continue, } } } - Ok(false) + //TODO: create fitting error + Err(Fido2LuksError::WrongSecret) } -pub fn get_devices() -> Fido2LuksResult> { - let mut devices = Vec::with_capacity(2); - for di in ctap::get_devices()? { - match FidoDevice::new(&di) { - Err(e) => match e.kind() { - FidoErrorKind::ParseCtap | FidoErrorKind::DeviceUnsupported => (), - err => return Err(FidoError::from(err).into()), - }, - Ok(dev) => devices.push(dev), - } - } - Ok(devices) +pub fn may_require_pin() -> Fido2LuksResult { + let info = get_info(&lib_cfg())?; + let needs_pin = info + .options + .iter() + .any(|(name, val)| &name[..] == "clientPin" && *val); + Ok(needs_pin) } diff --git a/src/error.rs b/src/error.rs index f451f1c..995c495 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use ctap::FidoError; +use anyhow; use libcryptsetup_rs::LibcryptErr; use std::io; use std::io::ErrorKind; @@ -14,7 +14,7 @@ pub enum Fido2LuksError { #[fail(display = "unable to read keyfile: {}", cause)] KeyfileError { cause: io::Error }, #[fail(display = "authenticator error: {}", cause)] - AuthenticatorError { cause: ctap::FidoError }, + AuthenticatorError { cause: anyhow::Error }, #[fail(display = "no authenticator found, please ensure your device is plugged in")] NoAuthenticatorError, #[fail(display = " {}", cause)] @@ -35,6 +35,12 @@ pub enum Fido2LuksError { InsufficientCredentials, } +impl From for Fido2LuksError { + fn from(cause: anyhow::Error) -> Self { + Fido2LuksError::AuthenticatorError { cause } + } +} + impl Fido2LuksError { pub fn exit_code(&self) -> i32 { use Fido2LuksError::*; @@ -91,12 +97,6 @@ impl From for Fido2LuksError { } } -impl From for Fido2LuksError { - fn from(e: FidoError) -> Self { - AuthenticatorError { cause: e } - } -} - impl From for Fido2LuksError { fn from(e: LibcryptErr) -> Self { match e { diff --git a/src/main.rs b/src/main.rs index cc5f42e..92d8d09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ #[macro_use] extern crate failure; -extern crate ctap_hmac as ctap; #[macro_use] extern crate serde_derive; use crate::cli::*;