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)]
pub struct AuthenticatorParameters {
pub struct Credentials {
/// FIDO credential ids, seperated by ',' generate using fido2luks credential
#[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
#[structopt(
@ -77,8 +84,9 @@ pub struct LuksParameters {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
/// Try to unlock the device using a specifc keyslot, ignore all other slots
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
slot_hint: Option<u32>,
slot: Option<u32>,
}
#[derive(Debug, StructOpt, Clone)]
@ -117,6 +125,7 @@ fn derive_secret(
credentials: &[HexEncoded],
salt: &[u8; 32],
timeout: u64,
pin: Option<&str>,
) -> Fido2LuksResult<[u8; 32]> {
let timeout = Duration::from_secs(timeout);
let start = SystemTime::now();
@ -147,11 +156,16 @@ fn derive_secret(
.collect::<Vec<_>>()[..],
salt,
timeout - start.elapsed().unwrap(),
pin,
)?[..],
salt,
]))
}
fn read_pin() -> Fido2LuksResult<String> {
util::read_password("Authenticator PIN", false)
}
#[derive(Debug, StructOpt)]
pub struct Args {
/// Request passwords via Stdin instead of using the password helper
@ -161,50 +175,6 @@ pub struct Args {
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)]
pub struct OtherSecret {
/// Use a keyfile instead of a password
@ -224,6 +194,8 @@ pub enum Command {
#[structopt(short = "b", long = "bin")]
binary: bool,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
@ -234,6 +206,8 @@ pub enum Command {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
@ -251,6 +225,8 @@ pub enum Command {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
@ -268,6 +244,8 @@ pub enum Command {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
@ -279,6 +257,8 @@ pub enum Command {
/// Generate a new FIDO credential
#[structopt(name = "credential")]
Credential {
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
/// Name to be displayed on the authenticator if it has a display
#[structopt(env = "FIDO2LUKS_CREDENTIAL_NAME")]
name: Option<String>,
@ -297,25 +277,44 @@ pub fn run_cli() -> Fido2LuksResult<()> {
let args = parse_cmdline();
let interactive = args.interactive;
match &args.command {
Command::Credential { name } => {
let cred = make_credential_id(name.as_ref().map(|n| n.as_ref()))?;
Command::Credential {
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));
Ok(())
}
Command::PrintSecret {
binary,
authenticator,
credentials,
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 {
util::read_password_hashed("Password", false)
} else {
secret.salt.obtain(&secret.password_helper)
}?;
let secret = derive_secret(
authenticator.credential_ids.0.as_slice(),
credentials.ids.0.as_slice(),
&salt,
authenticator.await_time,
pin,
)?;
if *binary {
stdout.write_all(&secret[..])?;
@ -327,11 +326,17 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Command::AddKey {
luks,
authenticator,
credentials,
secret,
exclusive,
existing_secret,
luks_mod,
} => {
let pin = if authenticator.pin {
Some(read_pin()?)
} else {
None
};
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify)
@ -347,9 +352,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
OtherSecret {
fido_device: true, ..
} => derive_secret(
&authenticator.credential_ids.0,
&credentials.ids.0,
&salt("Existing password", false)?,
authenticator.await_time,
pin.as_deref(),
)?[..]
.to_vec(),
_ => util::read_password("Existing password", false)?
@ -357,9 +363,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
.to_vec(),
};
let secret = derive_secret(
&authenticator.credential_ids.0,
&credentials.ids.0,
&salt("Password", false)?,
authenticator.await_time,
pin.as_deref(),
)?;
let added_slot = luks::add_key(
&luks.device,
@ -387,11 +394,17 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Command::ReplaceKey {
luks,
authenticator,
credentials,
secret,
add_password,
replacement,
luks_mod,
} => {
let pin = if authenticator.pin {
Some(read_pin()?)
} else {
None
};
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify)
@ -407,9 +420,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
OtherSecret {
fido_device: true, ..
} => derive_secret(
&authenticator.credential_ids.0,
&credentials.ids.0,
&salt("Replacement password", true)?,
authenticator.await_time,
pin.as_deref(),
)?[..]
.to_vec(),
_ => util::read_password("Replacement password", true)?
@ -417,9 +431,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
.to_vec(),
};
let existing_secret = derive_secret(
&authenticator.credential_ids.0,
&credentials.ids.0,
&salt("Password", false)?,
authenticator.await_time,
pin.as_deref(),
)?;
let slot = if *add_password {
luks::add_key(
@ -446,10 +461,18 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Command::Open {
luks,
authenticator,
credentials,
secret,
name,
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]> {
if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify)
@ -460,11 +483,12 @@ pub fn run_cli() -> Fido2LuksResult<()> {
let mut retries = *retries;
loop {
match derive_secret(
&authenticator.credential_ids.0,
&credentials.ids.0,
&salt("Password", false)?,
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) => {
match e {

View File

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