rudimentary pin support
This commit is contained in:
parent
fcdd2a2d3d
commit
b8ae9d91f0
136
src/cli.rs
136
src/cli.rs
@ -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 {
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user