rudimentary pin support

This commit is contained in:
shimun 2020-04-29 19:56:18 +02:00
parent fcdd2a2d3d
commit b8ae9d91f0
Signed by: shimun
GPG Key ID: E81D8382DC2F971B
2 changed files with 94 additions and 58 deletions

View File

@ -57,10 +57,17 @@ impl<T: Display + FromStr> FromStr for CommaSeparated<T> {
} }
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
pub struct AuthenticatorParameters { pub struct Credentials {
/// FIDO credential ids, seperated by ',' generate using fido2luks credential /// FIDO credential ids, seperated by ',' generate using fido2luks credential
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")] #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
pub credential_ids: CommaSeparated<HexEncoded>, pub ids: CommaSeparated<HexEncoded>,
}
#[derive(Debug, StructOpt)]
pub struct AuthenticatorParameters {
/// Request a PIN to unlock the authenticator
#[structopt(short = "P", long = "pin")]
pub pin: bool,
/// Await for an authenticator to be connected, timeout after n seconds /// Await for an authenticator to be connected, timeout after n seconds
#[structopt( #[structopt(
@ -77,8 +84,9 @@ pub struct LuksParameters {
#[structopt(env = "FIDO2LUKS_DEVICE")] #[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf, device: PathBuf,
/// Try to unlock the device using a specifc keyslot, ignore all other slots
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")] #[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
slot_hint: Option<u32>, slot: Option<u32>,
} }
#[derive(Debug, StructOpt, Clone)] #[derive(Debug, StructOpt, Clone)]
@ -117,6 +125,7 @@ fn derive_secret(
credentials: &[HexEncoded], credentials: &[HexEncoded],
salt: &[u8; 32], salt: &[u8; 32],
timeout: u64, timeout: u64,
pin: Option<&str>,
) -> Fido2LuksResult<[u8; 32]> { ) -> Fido2LuksResult<[u8; 32]> {
let timeout = Duration::from_secs(timeout); let timeout = Duration::from_secs(timeout);
let start = SystemTime::now(); let start = SystemTime::now();
@ -147,11 +156,16 @@ fn derive_secret(
.collect::<Vec<_>>()[..], .collect::<Vec<_>>()[..],
salt, salt,
timeout - start.elapsed().unwrap(), timeout - start.elapsed().unwrap(),
pin,
)?[..], )?[..],
salt, salt,
])) ]))
} }
fn read_pin() -> Fido2LuksResult<String> {
util::read_password("Authenticator PIN", false)
}
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
pub struct Args { pub struct Args {
/// Request passwords via Stdin instead of using the password helper /// Request passwords via Stdin instead of using the password helper
@ -161,50 +175,6 @@ pub struct Args {
pub command: Command, pub command: Command,
} }
#[derive(Debug, StructOpt, Clone)]
pub struct SecretGeneration {
/// FIDO credential ids, seperated by ',' generate using fido2luks credential
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
pub credential_ids: CommaSeparated<HexEncoded>,
/// Salt for secret generation, defaults to 'ask'
///
/// Options:{n}
/// - ask : Prompt user using password helper{n}
/// - file:<PATH> : Will read <FILE>{n}
/// - string:<STRING> : Will use <STRING>, which will be handled like a password provided to the 'ask' option{n}
#[structopt(
name = "salt",
long = "salt",
env = "FIDO2LUKS_SALT",
default_value = "ask"
)]
pub salt: InputSalt,
/// Script used to obtain passwords, overridden by --interactive flag
#[structopt(
name = "password-helper",
env = "FIDO2LUKS_PASSWORD_HELPER",
default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
)]
pub password_helper: PasswordHelper,
/// Await for an authenticator to be connected, timeout after n seconds
#[structopt(
long = "await-dev",
name = "await-dev",
env = "FIDO2LUKS_DEVICE_AWAIT",
default_value = "15"
)]
pub await_authenticator: u64,
/// Request the password twice to ensure it being correct
#[structopt(
long = "verify-password",
env = "FIDO2LUKS_VERIFY_PASSWORD",
hidden = true
)]
pub verify_password: Option<bool>,
}
#[derive(Debug, StructOpt, Clone)] #[derive(Debug, StructOpt, Clone)]
pub struct OtherSecret { pub struct OtherSecret {
/// Use a keyfile instead of a password /// Use a keyfile instead of a password
@ -224,6 +194,8 @@ pub enum Command {
#[structopt(short = "b", long = "bin")] #[structopt(short = "b", long = "bin")]
binary: bool, binary: bool,
#[structopt(flatten)] #[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters, authenticator: AuthenticatorParameters,
#[structopt(flatten)] #[structopt(flatten)]
secret: SecretParameters, secret: SecretParameters,
@ -234,6 +206,8 @@ pub enum Command {
#[structopt(flatten)] #[structopt(flatten)]
luks: LuksParameters, luks: LuksParameters,
#[structopt(flatten)] #[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters, authenticator: AuthenticatorParameters,
#[structopt(flatten)] #[structopt(flatten)]
secret: SecretParameters, secret: SecretParameters,
@ -251,6 +225,8 @@ pub enum Command {
#[structopt(flatten)] #[structopt(flatten)]
luks: LuksParameters, luks: LuksParameters,
#[structopt(flatten)] #[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters, authenticator: AuthenticatorParameters,
#[structopt(flatten)] #[structopt(flatten)]
secret: SecretParameters, secret: SecretParameters,
@ -268,6 +244,8 @@ pub enum Command {
#[structopt(flatten)] #[structopt(flatten)]
luks: LuksParameters, luks: LuksParameters,
#[structopt(flatten)] #[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters, authenticator: AuthenticatorParameters,
#[structopt(flatten)] #[structopt(flatten)]
secret: SecretParameters, secret: SecretParameters,
@ -279,6 +257,8 @@ pub enum Command {
/// Generate a new FIDO credential /// Generate a new FIDO credential
#[structopt(name = "credential")] #[structopt(name = "credential")]
Credential { Credential {
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
/// Name to be displayed on the authenticator if it has a display /// Name to be displayed on the authenticator if it has a display
#[structopt(env = "FIDO2LUKS_CREDENTIAL_NAME")] #[structopt(env = "FIDO2LUKS_CREDENTIAL_NAME")]
name: Option<String>, name: Option<String>,
@ -297,25 +277,44 @@ pub fn run_cli() -> Fido2LuksResult<()> {
let args = parse_cmdline(); let args = parse_cmdline();
let interactive = args.interactive; let interactive = args.interactive;
match &args.command { match &args.command {
Command::Credential { name } => { Command::Credential {
let cred = make_credential_id(name.as_ref().map(|n| n.as_ref()))?; authenticator,
name,
} => {
let pin_string;
let pin = if authenticator.pin {
pin_string = read_pin()?;
Some(pin_string.as_ref())
} else {
None
};
let cred = make_credential_id(name.as_ref().map(|n| n.as_ref()), pin)?;
println!("{}", hex::encode(&cred.id)); println!("{}", hex::encode(&cred.id));
Ok(()) Ok(())
} }
Command::PrintSecret { Command::PrintSecret {
binary, binary,
authenticator, authenticator,
credentials,
secret, secret,
} => { } => {
let pin_string;
let pin = if authenticator.pin {
pin_string = read_pin()?;
Some(pin_string.as_ref())
} else {
None
};
let salt = if interactive || secret.password_helper == PasswordHelper::Stdin { let salt = if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed("Password", false) util::read_password_hashed("Password", false)
} else { } else {
secret.salt.obtain(&secret.password_helper) secret.salt.obtain(&secret.password_helper)
}?; }?;
let secret = derive_secret( let secret = derive_secret(
authenticator.credential_ids.0.as_slice(), credentials.ids.0.as_slice(),
&salt, &salt,
authenticator.await_time, authenticator.await_time,
pin,
)?; )?;
if *binary { if *binary {
stdout.write_all(&secret[..])?; stdout.write_all(&secret[..])?;
@ -327,11 +326,17 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Command::AddKey { Command::AddKey {
luks, luks,
authenticator, authenticator,
credentials,
secret, secret,
exclusive, exclusive,
existing_secret, existing_secret,
luks_mod, luks_mod,
} => { } => {
let pin = if authenticator.pin {
Some(read_pin()?)
} else {
None
};
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
if interactive || secret.password_helper == PasswordHelper::Stdin { if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify) util::read_password_hashed(q, verify)
@ -347,9 +352,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
OtherSecret { OtherSecret {
fido_device: true, .. fido_device: true, ..
} => derive_secret( } => derive_secret(
&authenticator.credential_ids.0, &credentials.ids.0,
&salt("Existing password", false)?, &salt("Existing password", false)?,
authenticator.await_time, authenticator.await_time,
pin.as_deref(),
)?[..] )?[..]
.to_vec(), .to_vec(),
_ => util::read_password("Existing password", false)? _ => util::read_password("Existing password", false)?
@ -357,9 +363,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
.to_vec(), .to_vec(),
}; };
let secret = derive_secret( let secret = derive_secret(
&authenticator.credential_ids.0, &credentials.ids.0,
&salt("Password", false)?, &salt("Password", false)?,
authenticator.await_time, authenticator.await_time,
pin.as_deref(),
)?; )?;
let added_slot = luks::add_key( let added_slot = luks::add_key(
&luks.device, &luks.device,
@ -387,11 +394,17 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Command::ReplaceKey { Command::ReplaceKey {
luks, luks,
authenticator, authenticator,
credentials,
secret, secret,
add_password, add_password,
replacement, replacement,
luks_mod, luks_mod,
} => { } => {
let pin = if authenticator.pin {
Some(read_pin()?)
} else {
None
};
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
if interactive || secret.password_helper == PasswordHelper::Stdin { if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify) util::read_password_hashed(q, verify)
@ -407,9 +420,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
OtherSecret { OtherSecret {
fido_device: true, .. fido_device: true, ..
} => derive_secret( } => derive_secret(
&authenticator.credential_ids.0, &credentials.ids.0,
&salt("Replacement password", true)?, &salt("Replacement password", true)?,
authenticator.await_time, authenticator.await_time,
pin.as_deref(),
)?[..] )?[..]
.to_vec(), .to_vec(),
_ => util::read_password("Replacement password", true)? _ => util::read_password("Replacement password", true)?
@ -417,9 +431,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
.to_vec(), .to_vec(),
}; };
let existing_secret = derive_secret( let existing_secret = derive_secret(
&authenticator.credential_ids.0, &credentials.ids.0,
&salt("Password", false)?, &salt("Password", false)?,
authenticator.await_time, authenticator.await_time,
pin.as_deref(),
)?; )?;
let slot = if *add_password { let slot = if *add_password {
luks::add_key( luks::add_key(
@ -446,10 +461,18 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Command::Open { Command::Open {
luks, luks,
authenticator, authenticator,
credentials,
secret, secret,
name, name,
retries, retries,
} => { } => {
let pin_string;
let pin = if authenticator.pin {
pin_string = read_pin()?;
Some(pin_string.as_ref())
} else {
None
};
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
if interactive || secret.password_helper == PasswordHelper::Stdin { if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify) util::read_password_hashed(q, verify)
@ -460,11 +483,12 @@ pub fn run_cli() -> Fido2LuksResult<()> {
let mut retries = *retries; let mut retries = *retries;
loop { loop {
match derive_secret( match derive_secret(
&authenticator.credential_ids.0, &credentials.ids.0,
&salt("Password", false)?, &salt("Password", false)?,
authenticator.await_time, authenticator.await_time,
pin,
) )
.and_then(|secret| luks::open_container(&luks.device, &name, &secret, luks.slot_hint)) .and_then(|secret| luks::open_container(&luks.device, &name, &secret, luks.slot))
{ {
Err(e) => { Err(e) => {
match e { match e {

View File

@ -9,13 +9,21 @@ use std::time::Duration;
const RP_ID: &str = "fido2luks"; const RP_ID: &str = "fido2luks";
pub fn make_credential_id(name: Option<&str>) -> Fido2LuksResult<FidoCredential> { pub fn make_credential_id(
name: Option<&str>,
pin: Option<&str>,
) -> Fido2LuksResult<FidoCredential> {
let mut request = FidoCredentialRequestBuilder::default().rp_id(RP_ID); let mut request = FidoCredentialRequestBuilder::default().rp_id(RP_ID);
if let Some(user_name) = name { if let Some(user_name) = name {
request = request.user_name(user_name); request = request.user_name(user_name);
} }
let request = request.build().unwrap(); let request = request.build().unwrap();
let make_credential = |device: &mut FidoDevice| device.make_hmac_credential(&request); let make_credential = |device: &mut FidoDevice| {
if let Some(pin) = pin {
device.unlock(pin)?;
}
device.make_hmac_credential(&request)
};
Ok(request_multiple_devices( Ok(request_multiple_devices(
get_devices()? get_devices()?
.iter_mut() .iter_mut()
@ -28,6 +36,7 @@ pub fn perform_challenge(
credentials: &[&FidoCredential], credentials: &[&FidoCredential],
salt: &[u8; 32], salt: &[u8; 32],
timeout: Duration, timeout: Duration,
pin: Option<&str>,
) -> Fido2LuksResult<[u8; 32]> { ) -> Fido2LuksResult<[u8; 32]> {
let request = FidoAssertionRequestBuilder::default() let request = FidoAssertionRequestBuilder::default()
.rp_id(RP_ID) .rp_id(RP_ID)
@ -35,6 +44,9 @@ pub fn perform_challenge(
.build() .build()
.unwrap(); .unwrap();
let get_assertion = |device: &mut FidoDevice| { let get_assertion = |device: &mut FidoDevice| {
if let Some(pin) = pin {
device.unlock(pin)?;
}
device.get_hmac_assertion(&request, &util::sha256(&[&salt[..]]), None) device.get_hmac_assertion(&request, &util::sha256(&[&salt[..]]), None)
}; };
let (_, (secret, _)) = request_multiple_devices( let (_, (secret, _)) = request_multiple_devices(