Compare commits

...

57 Commits
isalt ... 0.2.8

Author SHA1 Message Date
4507107fac update libcryptsetup-rs 2020-06-10 13:50:28 +02:00
a8482c50a2 handle tokens when replacing 2020-06-08 19:22:19 +02:00
09be5ef551 assemble secret in correct order 2020-06-08 18:08:24 +02:00
6f6c84ddba skip luks2 check until underlying issue is fixed 2020-06-07 14:14:51 +02:00
5a05cad695 more precise description 2020-06-06 23:39:23 +02:00
d8d24b40f5 Merge branch 'cli_reorg' 2020-06-06 23:37:22 +02:00
c1a82b9ae6 update libcryptsetup_rs to 0.4.0 2020-06-06 22:43:18 +02:00
a26b79bcd6 reduced redundant code 2020-05-05 23:53:50 +02:00
f774580c9c update to current api 2020-05-05 23:28:44 +02:00
69732a1ad6 restore order 2020-04-29 20:33:28 +02:00
b8ae9d91f0 rudimentary pin support 2020-04-29 19:56:18 +02:00
fcdd2a2d3d add option to specify keyslot 2020-04-29 18:55:25 +02:00
c3d6425e2d reorganised cli 2020-04-29 18:50:55 +02:00
0b19760175 hint slots 2020-04-28 19:09:53 +02:00
2ec8679c47 open token 2020-04-28 14:27:14 +02:00
65e1dead8b remove token 2020-04-27 22:07:00 +02:00
478fb5e036 store luks token 2020-04-27 19:26:21 +02:00
1547f5e199 get format 2020-04-27 18:12:06 +02:00
5c0364587e update ctap 2020-04-26 18:58:37 +02:00
9307503bdc applied clippy lints 2020-04-07 20:06:24 +02:00
b94f45d1ff patch secret_gen before obtaing first secret 2020-04-06 23:33:41 +02:00
c8fb636846 mention clang build dependency 2020-04-06 22:52:15 +02:00
49e2835f60 enable fido requests to be sent to multiple devices at once 2020-04-06 21:38:11 +02:00
d5c0d48f03 allow another fido device to be used as previous secret 2020-04-06 20:18:00 +02:00
ad2451f548 add timeout 2020-04-05 23:24:18 +02:00
bb7ee7c1ce request password only once if possible 2020-04-03 22:02:05 +02:00
0ba77963d2 update ctap_hmac 2020-04-02 17:22:15 +02:00
1658800553 request_multiple 2020-04-01 20:24:49 +02:00
a394b7d1d1 libcryptsetup-rs patch 2020-03-28 14:54:36 +01:00
c99d7f562d support luks2 2020-03-27 20:08:54 +01:00
c4f781e6e3 only process keyslots within a given range 2020-03-27 20:03:42 +01:00
f6de4a033e more detailed messages 2020-03-27 18:28:33 +01:00
f5880346b9 switch to libcryptsetup-rs 2020-03-27 18:09:38 +01:00
6089b254b4 switch to libcryptsetup-rs for luks2 support 2020-03-22 17:39:44 +01:00
03e34ec790 0.2.3
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is failing
2020-01-20 22:43:06 +01:00
a437106fcb use await-dev per default
All checks were successful
continuous-integration/drone/push Build is passing
2020-01-16 19:41:59 +01:00
7ed948d53b update & tidy readme
All checks were successful
continuous-integration/drone/push Build is passing
2020-01-15 16:46:15 +01:00
c4e08413c0 Added --await-dev flag
All checks were successful
continuous-integration/drone/push Build is passing
2020-01-13 23:23:45 +01:00
shimunn
7429706920 Merge pull request #7 from mmahut/patch-1
All checks were successful
continuous-integration/drone/push Build is passing
error.rs: typo
2020-01-13 21:54:09 +01:00
Marek Mahut
a5fd5fa9f6 error.rs: typo 2020-01-13 17:44:51 +01:00
659fafdfb4 update to 0.2.2
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is failing
2020-01-10 21:44:33 +01:00
7f2668eded allow for named credentials 2020-01-10 21:32:39 +01:00
shimunn
ae714cdef3 Merge pull request #6 from mmahut/fixid
All checks were successful
continuous-integration/drone/push Build is passing
match rp_id to fido2luks
2020-01-10 19:47:31 +01:00
shimunn
ae802e5e71 Merge pull request #5 from mmahut/env
use password helper in modified environments
2020-01-10 19:47:01 +01:00
Marek Mahut
a5f0444d24 match rp_id to fido2luks 2020-01-10 17:13:56 +01:00
Marek Mahut
a307d87d88 use password helper in modified environments 2020-01-10 16:52:22 +01:00
721dded6d2 WIP: 0.2.2
All checks were successful
continuous-integration/drone/push Build is passing
Warning: This release cointains changes to way credentials are generated,
which may cause your authenticator to reject the old credential.
2020-01-09 22:22:54 +01:00
e7049a281a Use fido2luks as rp_id instead if default hmac, consider making
All checks were successful
continuous-integration/drone/push Build is passing
credenials device specific
2020-01-02 15:35:32 +01:00
5d1c7beb4d added flag to retry open command
All checks were successful
continuous-integration/drone/push Build is passing
2019-10-12 22:46:54 +02:00
2bac911b32 assigned exit codes to error cases 2019-10-12 22:46:20 +02:00
9a8ea993b5 fmt
All checks were successful
continuous-integration/drone/push Build is passing
2019-10-12 13:40:24 +02:00
shimunn
7eb9dcc928 Merge pull request #2 from jannic/add-test-cases
Some checks failed
continuous-integration/drone/push Build is failing
Add test case for hash calculations
2019-10-12 13:24:33 +02:00
shimunn
509e300a8f Merge pull request #1 from jannic/port-to-ring
Use ring for sha256 calculation
2019-10-12 13:24:18 +02:00
Jan Niehusmann
42945956a6 Add test case for hash calculations
While replacing the implementation of sha256, I noticed that there
is no test case actually calling the hash calculations.

Added two such test cases. Please note that I didn't verify that the
result is correct, but just took the value the existing implementation
returned. So those tests will only catch future regressions.
2019-10-11 22:15:21 +00:00
Jan Niehusmann
3cf5ccf2a0 Use ring for sha256 calculation
According to https://rustsec.org/advisories/RUSTSEC-2016-0005.html,
rust-crypto is unmaintained.

Crates depending on rust-crypto should be ported to other crates.

This port replaces rust-crypto with the sha2 implementation of ring,
as fido2luks already depends on it via ctap_hmac. Note that it uses
an old version of ring, so I used the same version, here.
2019-10-11 22:06:00 +00:00
79e9a37806 use cratesio deps only
All checks were successful
continuous-integration/drone/push Build is passing
2019-10-10 13:41:02 +02:00
d16118e695 Readme
All checks were successful
continuous-integration/drone/push Build is passing
2019-10-08 15:13:15 +02:00
11 changed files with 1610 additions and 548 deletions

995
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,29 @@
[package]
name = "fido2luks"
version = "0.2.0"
version = "0.2.8"
authors = ["shimunn <shimun@shimun.net>"]
edition = "2018"
[dependencies]
ctap = { git = "https://github.com/shimunn/ctap.git", branch = "hmac_ext" }
#cryptsetup-rs = "0.2.0"
cryptsetup-rs = { git = "https://github.com/shimunn/cryptsetup-rs.git", branch = "update_keyslot" }
libcryptsetup-sys = { git = "https://github.com/shimunn/cryptsetup-rs.git", branch = "update_keyslot" }
description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
documentation = "https://github.com/shimunn/fido2luks/blob/master/README.md"
homepage = "https://github.com/shimunn/fido2luks"
repository = "https://github.com/shimunn/fido2luks"
readme = "README.md"
keywords = ["luks", "fido2", "u2f"]
categories = ["command-line-utilities"]
license-file = "LICENSE"
[dependencies]
ctap_hmac = { version="0.4.2", features = ["request_multiple"] }
hex = "0.3.2"
rust-crypto = "0.2.36"
ring = "0.13.5"
failure = "0.1.5"
rpassword = "4.0.1"
structopt = "0.3.2"
libcryptsetup-rs = "0.4.1"
serde_json = "1.0.51"
serde_derive = "1.0.106"
serde = "1.0.106"
[profile.release]
lto = true

View File

@@ -1,15 +1,15 @@
# fido2luks
# fido2luks [![Crates.io Version](https://img.shields.io/crates/v/fido2luks.svg)](https://crates.io/crates/fido2luks)
This will allow you to unlock your luks encrypted disk with an fido2 compatible key
Note: This has only been tested under Fedora 30 using a Solo Key
Note: This has only been tested under Fedora 31 using a Solo Key, Trezor Model T
## Setup
### Prerequisites
```
dnf install cargo cryptsetup-devel -y
dnf install clang cargo cryptsetup-devel -y
```
### Device
@@ -17,18 +17,22 @@ dnf install cargo cryptsetup-devel -y
```
git clone https://github.com/shimunn/fido2luks.git && cd fido2luks
#Alternativly cargo build --release && sudo cp target/release/fido2luks /usr/bin/
CARGO_INSTALL_ROOT=/usr sudo -E cargo install -f --path .
# Alternativly cargo build --release && sudo cp target/release/fido2luks /usr/bin/
sudo -E cargo install -f --path . --root /usr
echo FIDO2LUKS_CREDENTIAL_ID=$(fido2luks credential) >> dracut/96luks-2fa/fido2luks.conf
# Copy template
cp dracut/96luks-2fa/fido2luks.conf /etc/
# Name is optional but useful if your authenticator has a display
echo FIDO2LUKS_CREDENTIAL_ID=$(fido2luks credential [NAME]) >> /etc/fido2luks.conf
# Load config into env
set -a
. dracut/96luks-2fa/fido2luks.conf
. /etc/fido2luks.conf
#Repeat for each luks volume
# Repeat for each luks volume
sudo -E fido2luks -i add-key /dev/disk/by-uuid/<DISK_UUID>
#Test(only works if the luks container isn't active)
# Test(only works if the luks container isn't active)
sudo -E fido2luks -i open /dev/disk/by-uuid/<DISK_UUID> luks-<DISK_UUID>
```
@@ -45,13 +49,13 @@ sudo make install
Add `rd.luks.2fa=<CREDENTIAL_ID>:<DISK_UUID>` to `GRUB_CMDLINE_LINUX` in /etc/default/grub
Note: This is only required for your root disk, systemd will try to unlock all other luks partions using the same key if you added it using `fido2luks add-key`
Note: This is only required for your root disk, systemd will try to unlock all other LUKS partions using the same key if you added it using `fido2luks add-key`
```
grub2-mkconfig > /boot/grub2/grub.cfg
```
I'd also recommend to copy the executable onto /boot so that it is accessible in case you have to access your disk from a live system
I'd also recommend to copy the executable onto /boot so that it is accessible in case you have to access your disk from a rescue system
```
mkdir /boot/fido2luks/
@@ -61,15 +65,33 @@ cp /etc/fido2luks.conf /boot/fido2luks/
## Test
Just reboot and see if it works, if thats the case you should remove your old less secure password from your luks header:
Just reboot and see if it works, if that's the case you should remove your old less secure password from your LUKS header:
```
#Recommend in case you lose your authenticator, store this backupfile somewhere safe
# Recommend in case you lose your authenticator, store this backupfile somewhere safe
cryptsetup luksHeaderBackup /dev/disk/by-uuid/<DISK_UUID> --header-backup-file luks_backup_<DISK_UUID>
#There is no turning back if you mess this up, make sure you made a backup
# There is no turning back if you mess this up, make sure you made a backup
fido2luks -i add-key --exclusive /dev/disk/by-uuid/<DISK_UUID>
```
## Addtional settings
### Password less
Remove your previous secret as described in the next section, in case you've already added one.
Open `/etc/fido2luks.conf` and replace `FIDO2LUKS_SALT=Ask` with `FIDO2LUKS_SALT=string:<YOUR_RANDOM_STRING>`
but be warned that this password will be included to into your initramfs.
Import the new config into env:
```
set -a
. /etc/fido2luks.conf
```
Then add the new secret to each device and update dracut afterwards `dracut -f`
## Removal
Remove `rd.luks.2fa` from `GRUB_CMDLINE_LINUX` in /etc/default/grub
@@ -79,5 +101,5 @@ set -a
. fido2luks.conf
sudo -E fido2luks -i replace-key /dev/disk/by-uuid/<DISK_UUID>
sudo rm -rf /usr/lib/dracut/modules.d/96luks-2fa /etc/dracut.conf.d/luks-2fa.conf
sudo rm -rf /usr/lib/dracut/modules.d/96luks-2fa /etc/dracut.conf.d/luks-2fa.conf /etc/fido2luks.conf
```

View File

@@ -9,7 +9,7 @@ MOUNT=$(command -v mount)
UMOUNT=$(command -v umount)
TIMEOUT=120
CON_MSG="Please connect your authenicator"
CON_MSG="Please connect your authenticator"
generate_service () {
local credential_id=$1 target_uuid=$2 timeout=$3 sd_dir=${4:-$NORMAL_DIR}
@@ -19,6 +19,10 @@ generate_service () {
local crypto_target_service="systemd-cryptsetup@luks\x2d${sd_target_uuid}.service"
local sd_service="${sd_dir}/luks-2fa@luks\x2d${sd_target_uuid}.service"
local fido2luks_args="--bin"
if [ ! -z "$timeout" ]; then
fido2luks_args="$fido2luks_args --await-dev ${timeout}"
fi
{
printf -- "[Unit]"
printf -- "\nDescription=%s" "2fa for luks"
@@ -27,18 +31,15 @@ generate_service () {
printf -- "\nBefore=%s umount.target luks-2fa.target" "$crypto_target_service"
printf -- "\nConflicts=umount.target"
printf -- "\nDefaultDependencies=no"
printf -- "\nJobTimeoutSec=%s" "$timeout"
[ ! -z "$timeout" ] && printf -- "\nJobTimeoutSec=%s" "$timeout"
printf -- "\n\n[Service]"
printf -- "\nType=oneshot"
printf -- "\nRemainAfterExit=yes"
printf -- "\nEnvironmentFile=%s" "/etc/fido2luks.conf"
printf -- "\nEnvironment=FIDO2LUKS_CREDENTIAL_ID='%s'" "$credential_id"
[ ! -z "$credential_id" ] && printf -- "\nEnvironment=FIDO2LUKS_CREDENTIAL_ID='%s'" "$credential_id"
printf -- "\nKeyringMode=%s" "shared"
printf -- "\nExecStartPre=-/usr/bin/plymouth display-message --text \"${CON_MSG}\""
printf -- "\nExecStartPre=-/bin/bash -c \"while ! ${FIDO2LUKS} connected; do /usr/bin/sleep 1; done\""
printf -- "\nExecStartPre=-/usr/bin/plymouth hide-message --text \"${CON_MSG}\""
printf -- "\nExecStart=/bin/bash -c \"${FIDO2LUKS} print-secret --bin | ${CRYPTSETUP} attach 'luks-%s' '/dev/disk/by-uuid/%s' '/dev/stdin'\"" "$target_uuid" "$target_uuid"
printf -- "\nExecStart=/bin/bash -c \"${FIDO2LUKS} print-secret $fido2luks_args | ${CRYPTSETUP} attach 'luks-%s' '/dev/disk/by-uuid/%s' '/dev/stdin'\"" "$target_uuid" "$target_uuid"
printf -- "\nExecStop=${CRYPTSETUP} detach 'luks-%s'" "$target_uuid"
} > "$sd_service"

View File

@@ -1,97 +1,109 @@
use crate::error::*;
use crate::luks;
use crate::*;
use cryptsetup_rs as luks;
use cryptsetup_rs::api::{CryptDeviceHandle, CryptDeviceOpenBuilder, Luks1Params};
use cryptsetup_rs::{CryptDevice, Luks1CryptDevice};
use libcryptsetup_sys::crypt_keyslot_info;
use structopt::StructOpt;
use ctap::{FidoCredential, FidoErrorKind};
use failure::_core::fmt::{Display, Error, Formatter};
use failure::_core::str::FromStr;
use failure::_core::time::Duration;
use std::io::Write;
use std::process::exit;
use std::thread;
pub fn add_key_to_luks(
device: PathBuf,
secret: &[u8; 32],
old_secret: Box<dyn Fn() -> Fido2LuksResult<Vec<u8>>>,
exclusive: bool,
) -> Fido2LuksResult<u8> {
fn offer_format(
_dev: CryptDeviceOpenBuilder,
) -> Fido2LuksResult<CryptDeviceHandle<Luks1Params>> {
unimplemented!()
use crate::util::sha256;
use std::borrow::Cow;
use std::time::SystemTime;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct HexEncoded(pub Vec<u8>);
impl Display for HexEncoded {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.write_str(&hex::encode(&self.0))
}
let dev =
|| -> luks::device::Result<CryptDeviceOpenBuilder> { luks::open(&device.canonicalize()?) };
}
let prev_key = old_secret()?;
impl FromStr for HexEncoded {
type Err = hex::FromHexError;
let mut handle = match dev()?.luks1() {
Ok(handle) => handle,
Err(luks::device::Error::BlkidError(_)) => offer_format(dev()?)?,
Err(luks::device::Error::CryptsetupError(errno)) => {
//if i32::from(errno) == 555
dbg!(errno);
offer_format(dev()?)?
} //TODO: find correct errorno and offer to format as luks
err => err?,
};
handle.set_iteration_time(50);
let slot = handle.add_keyslot(secret, Some(prev_key.as_slice()), None)?;
if exclusive {
for old_slot in 0..8u8 {
if old_slot != slot
&& (handle.keyslot_status(old_slot.into()) == crypt_keyslot_info::CRYPT_SLOT_ACTIVE
|| handle.keyslot_status(old_slot.into())
== crypt_keyslot_info::CRYPT_SLOT_ACTIVE_LAST)
{
handle.destroy_keyslot(old_slot)?;
}
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(HexEncoded(hex::decode(s)?))
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct CommaSeparated<T: FromStr + Display>(pub Vec<T>);
impl<T: Display + FromStr> Display for CommaSeparated<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
for i in &self.0 {
f.write_str(&i.to_string())?;
f.write_str(",")?;
}
Ok(())
}
Ok(slot)
}
pub fn add_password_to_luks(
device: PathBuf,
secret: &[u8; 32],
new_secret: Box<dyn Fn() -> Fido2LuksResult<Vec<u8>>>,
add_password: bool,
) -> Fido2LuksResult<u8> {
let dev = luks::open(&device.canonicalize()?)?;
let mut handle = dev.luks1()?;
let prev_slot = if add_password {
Some(handle.add_keyslot(&secret[..], Some(&secret[..]), None)?)
} else {
None
};
let slot = handle.update_keyslot(&new_secret()?[..], &secret[..], prev_slot)?;
Ok(slot)
}
pub fn authenticator_connected() -> Fido2LuksResult<bool> {
Ok(!device::get_devices()?.is_empty())
impl<T: Display + FromStr> FromStr for CommaSeparated<T> {
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(CommaSeparated(
s.split(',')
.map(|part| <T as FromStr>::from_str(part))
.collect::<Result<Vec<_>, _>>()?,
))
}
}
#[derive(Debug, StructOpt)]
pub struct Args {
/// Request passwords via Stdin instead of using the password helper
#[structopt(short = "i", long = "interactive")]
pub interactive: bool,
#[structopt(subcommand)]
pub command: Command,
pub struct Credentials {
/// FIDO credential ids, seperated by ',' generate using fido2luks credential
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
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(
long = "await-dev",
name = "await-dev",
env = "FIDO2LUKS_DEVICE_AWAIT",
default_value = "15"
)]
pub await_time: u64,
}
#[derive(Debug, StructOpt)]
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: Option<u32>,
}
#[derive(Debug, StructOpt, Clone)]
pub struct SecretGeneration {
/// FIDO credential id, generate using fido2luks credential
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
pub credential_id: String,
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")]
kdf_time: Option<u64>,
}
#[derive(Debug, StructOpt)]
pub struct SecretParameters {
/// Salt for secret generation, defaults to 'ask'
///
/// Options:{n}
/// - ask : Promt user using password helper{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(
@@ -105,27 +117,69 @@ pub struct SecretGeneration {
#[structopt(
name = "password-helper",
env = "FIDO2LUKS_PASSWORD_HELPER",
default_value = "/usr/bin/systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
)]
pub password_helper: PasswordHelper,
}
impl SecretGeneration {
pub fn patch(&self, args: &Args) -> Self {
let mut me = self.clone();
if args.interactive {
me.password_helper = PasswordHelper::Stdin;
fn derive_secret(
credentials: &[HexEncoded],
salt: &[u8; 32],
timeout: u64,
pin: Option<&str>,
) -> Fido2LuksResult<([u8; 32], FidoCredential)> {
let timeout = Duration::from_secs(timeout);
let start = SystemTime::now();
while let Ok(el) = start.elapsed() {
if el > timeout {
return Err(error::Fido2LuksError::NoAuthenticatorError);
}
me
if get_devices()
.map(|devices| !devices.is_empty())
.unwrap_or(false)
{
break;
}
thread::sleep(Duration::from_millis(500));
}
pub fn obtain_secret(&self) -> Fido2LuksResult<[u8; 32]> {
let salt = self.salt.obtain(&self.password_helper)?;
Ok(assemble_secret(
&perform_challenge(&self.credential_id, &salt)?,
&salt,
))
}
let credentials = credentials
.iter()
.map(|hex| FidoCredential {
id: hex.0.clone(),
public_key: None,
})
.collect::<Vec<_>>();
let credentials = credentials.iter().collect::<Vec<_>>();
let (unsalted, cred) =
perform_challenge(&credentials, salt, timeout - start.elapsed().unwrap(), pin)?;
Ok((sha256(&[salt, &unsalted[..]]), cred.clone()))
}
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
#[structopt(short = "i", long = "interactive")]
pub interactive: bool,
#[structopt(subcommand)]
pub command: Command,
}
#[derive(Debug, StructOpt, Clone)]
pub struct OtherSecret {
/// Use a keyfile instead of a password
#[structopt(short = "d", long = "keyfile", conflicts_with = "fido_device")]
keyfile: Option<PathBuf>,
/// 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
#[structopt(short = "f", long = "fido-device", conflicts_with = "keyfile")]
fido_device: bool,
}
#[derive(Debug, StructOpt)]
@@ -136,50 +190,95 @@ pub enum Command {
#[structopt(short = "b", long = "bin")]
binary: bool,
#[structopt(flatten)]
secret_gen: SecretGeneration,
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
},
/// Adds a generated key to the specified LUKS device
#[structopt(name = "add-key")]
AddKey {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
/// Will wipe all other keys
#[structopt(short = "e", long = "exclusive")]
exclusive: bool,
/// Use a keyfile instead of a password
#[structopt(short = "d", long = "keyfile")]
keyfile: Option<PathBuf>,
/// Will add an token to your LUKS 2 header, including the credential id
#[structopt(short = "t", long = "token")]
token: bool,
#[structopt(flatten)]
secret_gen: SecretGeneration,
existing_secret: OtherSecret,
#[structopt(flatten)]
luks_mod: LuksModParameters,
},
/// Replace a previously added key with a password
#[structopt(name = "replace-key")]
ReplaceKey {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
/// Add the password and keep the key
#[structopt(short = "a", long = "add-password")]
add_password: bool,
/// Use a keyfile instead of a password
#[structopt(short = "d", long = "keyfile")]
keyfile: Option<PathBuf>,
/// Will add an token to your LUKS 2 header, including the credential id
#[structopt(short = "t", long = "token")]
token: bool,
#[structopt(flatten)]
secret_gen: SecretGeneration,
replacement: OtherSecret,
#[structopt(flatten)]
luks_mod: LuksModParameters,
},
/// Open the LUKS device
#[structopt(name = "open")]
Open {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
name: String,
#[structopt(flatten)]
secret_gen: SecretGeneration,
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
#[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,
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>,
},
/// Check if an authenticator is connected
#[structopt(name = "connected")]
Connected,
@@ -192,83 +291,249 @@ pub fn parse_cmdline() -> Args {
pub fn run_cli() -> Fido2LuksResult<()> {
let mut stdout = io::stdout();
let args = parse_cmdline();
let interactive = args.interactive;
match &args.command {
Command::Credential => {
let cred = make_credential_id()?;
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,
ref secret_gen,
authenticator,
credentials,
secret,
} => {
let secret = secret_gen.patch(&args).obtain_secret()?;
if *binary {
stdout.write(&secret[..])?;
let pin_string;
let pin = if authenticator.pin {
pin_string = read_pin()?;
Some(pin_string.as_ref())
} else {
stdout.write(hex::encode(&secret[..]).as_bytes())?;
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, _cred) = derive_secret(
credentials.ids.0.as_slice(),
&salt,
authenticator.await_time,
pin,
)?;
if *binary {
stdout.write_all(&secret[..])?;
} else {
stdout.write_all(hex::encode(&secret[..]).as_bytes())?;
}
Ok(stdout.flush()?)
}
Command::AddKey {
device,
exclusive,
keyfile,
ref secret_gen,
} => {
let secret = secret_gen.patch(&args).obtain_secret()?;
let slot = add_key_to_luks(
device.clone(),
&secret,
if let Some(keyfile) = keyfile.clone() {
Box::new(move || util::read_keyfile(keyfile.clone()))
} else {
Box::new(|| {
util::read_password("Old password", true).map(|p| p.as_bytes().to_vec())
})
},
*exclusive,
)?;
println!(
"Added to key to device {}, slot: {}",
device.display(),
slot
);
Ok(())
luks,
authenticator,
credentials,
secret,
luks_mod,
existing_secret: other_secret,
token,
..
}
Command::ReplaceKey {
device,
add_password,
keyfile,
ref secret_gen,
| Command::ReplaceKey {
luks,
authenticator,
credentials,
secret,
luks_mod,
replacement: other_secret,
token,
..
} => {
let secret = secret_gen.patch(&args).obtain_secret()?;
let slot = add_password_to_luks(
device.clone(),
&secret,
if let Some(keyfile) = keyfile.clone() {
Box::new(move || util::read_keyfile(keyfile.clone()))
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)
} else {
Box::new(|| {
util::read_password("Password to add", true).map(|p| p.as_bytes().to_vec())
})
},
*add_password,
)?;
println!(
"Added to password to device {}, slot: {}",
device.display(),
slot
);
Ok(())
secret.salt.obtain(&secret.password_helper)
}
};
let other_secret = |salt_q: &str, verify: bool| -> Fido2LuksResult<(Vec<u8>, Option<FidoCredential>)> {
match other_secret {
OtherSecret {
keyfile: Some(file),
..
} => Ok((util::read_keyfile(file)?, None)),
OtherSecret {
fido_device: true, ..
} => Ok(derive_secret(
&credentials.ids.0,
&salt(salt_q, verify)?,
authenticator.await_time,
pin.as_deref(),
)
.map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?),
_ => Ok((util::read_password(salt_q, verify)?.as_bytes().to_vec(), None)),
}
};
let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> {
derive_secret(
&credentials.ids.0,
&salt("Password", verify)?,
authenticator.await_time,
pin.as_deref(),
)
};
// Non overlap
match &args.command {
Command::AddKey {
exclusive, ..
} => {
let (existing_secret, _) = other_secret("Current password", false)?;
let (new_secret, cred) = secret(true)?;
let added_slot = luks::add_key(
&luks.device,
&new_secret,
&existing_secret[..],
luks_mod.kdf_time.or(Some(10)),
Some(&cred.id[..]).filter(|_| *token),
)?;
if *exclusive {
let destroyed = luks::remove_keyslots(&luks.device, &[added_slot])?;
println!(
"Added to key to device {}, slot: {}\nRemoved {} old keys",
luks.device.display(),
added_slot,
destroyed
);
} else {
println!(
"Added to key to device {}, slot: {}",
luks.device.display(),
added_slot
);
}
Ok(())
}
Command::ReplaceKey { add_password, .. } => {
let (existing_secret, _) = secret(false)?;
let (replacement_secret, cred) = other_secret("Replacement password", true)?;
let slot = if *add_password {
luks::add_key(
&luks.device,
&replacement_secret[..],
&existing_secret,
luks_mod.kdf_time,
cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]),
)
} else {
luks::replace_key(
&luks.device,
&replacement_secret[..],
&existing_secret,
luks_mod.kdf_time,
cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]),
)
}?;
println!(
"Added to password to device {}, slot: {}",
luks.device.display(),
slot
);
Ok(())
}
_ => unreachable!(),
}
}
Command::Open {
device,
luks,
authenticator,
secret,
name,
ref secret_gen,
retries,
..
}
| Command::OpenToken {
luks,
authenticator,
secret,
name,
retries,
} => {
let secret = secret_gen.patch(&args).obtain_secret()?;
open_container(&device, &name, &secret)
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)
} else {
secret.salt.obtain(&secret.password_helper)
}
};
// Cow shouldn't be necessary
let secret = |credentials: Cow<'_, Vec<HexEncoded>>| {
derive_secret(
credentials.as_ref(),
&salt("Password", false)?,
authenticator.await_time,
pin,
)
};
let mut retries = *retries;
loop {
let secret = match &args.command {
Command::Open { credentials, .. } => secret(Cow::Borrowed(&credentials.ids.0))
.and_then(|(secret, _cred)| {
luks::open_container(&luks.device, &name, &secret, luks.slot)
}),
Command::OpenToken { .. } => luks::open_container_token(
&luks.device,
&name,
Box::new(|credentials: Vec<String>| {
let creds = credentials
.into_iter()
.flat_map(|cred| HexEncoded::from_str(cred.as_ref()).ok())
.collect::<Vec<_>>();
secret(Cow::Owned(creds))
.map(|(secret, cred)| (secret, hex::encode(&cred.id)))
}),
),
_ => unreachable!(),
};
match secret {
Err(e) => {
match e {
Fido2LuksError::WrongSecret if retries > 0 => {}
Fido2LuksError::AuthenticatorError { ref cause }
if cause.kind() == FidoErrorKind::Timeout && retries > 0 => {}
e => return Err(e),
}
retries -= 1;
eprintln!("{}", e);
}
res => break res,
}
}
}
Command::Connected => match get_devices() {
Ok(ref devs) if !devs.is_empty() => {

View File

@@ -1,7 +1,6 @@
use crate::error::*;
use crate::*;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use ring::digest;
use std::fmt;
use std::fs::File;
@@ -25,7 +24,7 @@ impl Default for InputSalt {
impl From<&str> for InputSalt {
fn from(s: &str) -> Self {
let mut parts = s.split(":").into_iter();
let mut parts = s.split(':');
match parts.next() {
Some("ask") | Some("Ask") => InputSalt::AskPassword,
Some("file") => InputSalt::File {
@@ -57,7 +56,7 @@ impl fmt::Display for InputSalt {
impl InputSalt {
pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> {
let mut digest = Sha256::new();
let mut digest = digest::Context::new(&digest::SHA256);
match self {
InputSalt::File { path } => {
let mut do_io = || {
@@ -65,7 +64,7 @@ impl InputSalt {
let mut buf = [0u8; 512];
loop {
let red = reader.read(&mut buf)?;
digest.input(&buf[0..red]);
digest.update(&buf[0..red]);
if red == 0 {
break;
}
@@ -75,19 +74,20 @@ impl InputSalt {
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
}
InputSalt::AskPassword => {
digest.input(password_helper.obtain()?.as_bytes());
digest.update(password_helper.obtain()?.as_bytes());
}
InputSalt::String(s) => digest.input(s.as_bytes()),
InputSalt::String(s) => digest.update(s.as_bytes()),
}
let mut salt = [0u8; 32];
digest.result(&mut salt);
salt.as_mut().copy_from_slice(digest.finish().as_ref());
Ok(salt)
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PasswordHelper {
Script(String),
#[allow(dead_code)]
Systemd,
Stdin,
}
@@ -95,7 +95,7 @@ pub enum PasswordHelper {
impl Default for PasswordHelper {
fn default() -> Self {
PasswordHelper::Script(
"/usr/bin/systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
"/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
.into(),
)
}
@@ -135,7 +135,7 @@ impl PasswordHelper {
Systemd => unimplemented!(),
Stdin => Ok(util::read_password("Password", true)?),
Script(password_helper) => {
let mut helper_parts = password_helper.split(" ");
let mut helper_parts = password_helper.split(' ');
let password = Command::new((&mut helper_parts).next().unwrap())
.args(helper_parts)
@@ -170,4 +170,17 @@ mod test {
assert_eq!("ask".parse::<InputSalt>().unwrap(), InputSalt::AskPassword);
assert_eq!("lol".parse::<InputSalt>().unwrap(), InputSalt::default());
}
#[test]
fn input_salt_obtain() {
assert_eq!(
InputSalt::String("abc".into())
.obtain(&PasswordHelper::Stdin)
.unwrap(),
[
186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97,
163, 150, 23, 122, 156, 180, 16, 255, 97, 242, 0, 21, 173
]
)
}
}

View File

@@ -1,51 +1,61 @@
use crate::error::*;
use ctap;
use ctap::extensions::hmac::{FidoHmacCredential, HmacExtension};
use ctap::{FidoDevice, FidoError, FidoErrorKind};
use crate::util;
use ctap::{
self, extensions::hmac::HmacExtension, request_multiple_devices, FidoAssertionRequestBuilder,
FidoCredential, FidoCredentialRequestBuilder, FidoDevice, FidoError, FidoErrorKind,
};
use std::time::Duration;
pub fn make_credential_id() -> Fido2LuksResult<FidoHmacCredential> {
let mut errs = Vec::new();
match get_devices()? {
ref devs if devs.is_empty() => Err(Fido2LuksError::NoAuthenticatorError)?,
devs => {
for mut dev in devs.into_iter() {
match dev.make_hmac_credential() {
Ok(cred) => {
return Ok(cred);
}
Err(e) => {
errs.push(e);
}
}
}
}
const RP_ID: &str = "fido2luks";
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);
}
Err(errs.pop().ok_or(Fido2LuksError::NoAuthenticatorError)?)?
let request = request.build().unwrap();
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()
.map(|device| (device, &make_credential)),
None,
)?)
}
pub fn perform_challenge(credential_id: &str, salt: &[u8; 32]) -> Fido2LuksResult<[u8; 32]> {
let cred = FidoHmacCredential {
id: hex::decode(credential_id).unwrap(),
rp_id: "hmac".to_string(),
};
let mut errs = Vec::new();
match get_devices()? {
ref devs if devs.is_empty() => Err(Fido2LuksError::NoAuthenticatorError)?,
devs => {
for mut dev in devs.into_iter() {
match dev.hmac_challange(&cred, &salt[..]) {
Ok(secret) => {
return Ok(secret);
}
Err(e) => {
errs.push(e);
}
}
}
pub fn perform_challenge<'a>(
credentials: &'a [&'a FidoCredential],
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 {
device.unlock(pin)?;
}
}
Err(errs.pop().ok_or(Fido2LuksError::NoAuthenticatorError)?)?
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 get_devices() -> Fido2LuksResult<Vec<FidoDevice>> {
@@ -54,7 +64,7 @@ pub fn get_devices() -> Fido2LuksResult<Vec<FidoDevice>> {
match FidoDevice::new(&di) {
Err(e) => match e.kind() {
FidoErrorKind::ParseCtap | FidoErrorKind::DeviceUnsupported => (),
err => Err(FidoError::from(err))?,
err => return Err(FidoError::from(err).into()),
},
Ok(dev) => devices.push(dev),
}

View File

@@ -11,11 +11,15 @@ pub enum Fido2LuksError {
KeyfileError { cause: io::Error },
#[fail(display = "authenticator error: {}", cause)]
AuthenticatorError { cause: ctap::FidoError },
#[fail(display = "no authenticator found, please ensure you device is plugged in")]
#[fail(display = "no authenticator found, please ensure your device is plugged in")]
NoAuthenticatorError,
#[fail(display = "luks err")]
LuksError { cause: cryptsetup_rs::device::Error },
#[fail(display = "no authenticator found, please ensure you device is plugged in")]
#[fail(display = " {}", cause)]
CryptsetupError {
cause: libcryptsetup_rs::LibcryptErr,
},
#[fail(display = "{}", cause)]
LuksError { cause: LuksError },
#[fail(display = "{}", cause)]
IoError { cause: io::Error },
#[fail(display = "supplied secret isn't valid for this device")]
WrongSecret,
@@ -23,6 +27,19 @@ pub enum Fido2LuksError {
StringEncodingError { cause: FromUtf8Error },
}
impl Fido2LuksError {
pub fn exit_code(&self) -> i32 {
use Fido2LuksError::*;
match self {
AskPassError { .. } | KeyfileError { .. } => 2,
AuthenticatorError { .. } => 3,
NoAuthenticatorError => 4,
WrongSecret => 5,
_ => 1,
}
}
}
#[derive(Debug, Fail)]
pub enum AskPassError {
#[fail(display = "unable to retrieve password: {}", _0)]
@@ -31,6 +48,35 @@ pub enum AskPassError {
Mismatch,
}
#[derive(Debug, Fail)]
pub enum LuksError {
#[fail(display = "This feature requires to the LUKS device to be formatted as LUKS 2")]
Luks2Required,
#[fail(display = "Invalid token: {}", _0)]
InvalidToken(String),
#[fail(display = "No token found")]
NoToken,
#[fail(display = "The device already exists")]
DeviceExists,
}
impl LuksError {
pub fn activate(e: LibcryptErr) -> Fido2LuksError {
match e {
LibcryptErr::IOError(ref io) => match io.raw_os_error() {
Some(1) if io.kind() == ErrorKind::PermissionDenied => Fido2LuksError::WrongSecret,
Some(17) => Fido2LuksError::LuksError {
cause: LuksError::DeviceExists,
},
_ => return Fido2LuksError::CryptsetupError { cause: e },
},
_ => Fido2LuksError::CryptsetupError { cause: e },
}
}
}
use libcryptsetup_rs::LibcryptErr;
use std::io::ErrorKind;
use std::string::FromUtf8Error;
use Fido2LuksError::*;
@@ -40,17 +86,16 @@ impl From<FidoError> for Fido2LuksError {
}
}
impl From<cryptsetup_rs::device::Error> for Fido2LuksError {
fn from(e: cryptsetup_rs::device::Error) -> Self {
impl From<LibcryptErr> for Fido2LuksError {
fn from(e: LibcryptErr) -> Self {
match e {
cryptsetup_rs::device::Error::CryptsetupError(error_no) if error_no.0 == 1i32 => {
LibcryptErr::IOError(e) if e.raw_os_error().iter().any(|code| code == &1i32) => {
WrongSecret
}
e => LuksError { cause: e },
_ => CryptsetupError { cause: e },
}
}
}
impl From<io::Error> for Fido2LuksError {
fn from(e: io::Error) -> Self {
IoError { cause: e }

241
src/luks.rs Normal file
View File

@@ -0,0 +1,241 @@
use crate::error::*;
use libcryptsetup_rs::{
CryptActivateFlags, CryptDevice, CryptInit, CryptTokenInfo, EncryptionFormat, KeyslotInfo,
TokenInput,
};
use std::collections::{HashMap, HashSet};
use std::path::Path;
fn load_device_handle<P: AsRef<Path>>(path: P) -> Fido2LuksResult<CryptDevice> {
let mut device = CryptInit::init(path.as_ref())?;
device.context_handle().load::<()>(None, None)?;
Ok(device)
}
fn check_luks2(device: &mut CryptDevice) -> Fido2LuksResult<()> {
match device.format_handle().get_type()? {
EncryptionFormat::Luks2 => Ok(()),
_ => Err(Fido2LuksError::LuksError {
cause: LuksError::Luks2Required,
}),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Fido2LuksToken {
#[serde(rename = "type")]
type_: String,
credential: Vec<String>,
keyslots: Vec<String>,
}
impl Fido2LuksToken {
fn new(credential_id: impl AsRef<[u8]>, slot: u32) -> Self {
Self {
type_: "fido2luks\0".into(), // Doubles as c style string
credential: vec![hex::encode(credential_id)],
keyslots: vec![slot.to_string()],
}
}
}
pub fn open_container<P: AsRef<Path>>(
path: P,
name: &str,
secret: &[u8],
slot_hint: Option<u32>,
) -> Fido2LuksResult<()> {
let mut device = load_device_handle(path)?;
device
.activate_handle()
.activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty())
.map(|_slot| ())
.map_err(|_e| Fido2LuksError::WrongSecret)
}
pub fn open_container_token<P: AsRef<Path>>(
path: P,
name: &str,
secret: impl Fn(Vec<String>) -> Fido2LuksResult<([u8; 32], String)>,
) -> Fido2LuksResult<()> {
let mut device = load_device_handle(path)?;
check_luks2(&mut device)?;
let mut creds = HashMap::new();
for i in 0..256 {
let status = device.token_handle().status(i)?;
match status {
CryptTokenInfo::Inactive => break,
CryptTokenInfo::Internal(s)
| CryptTokenInfo::InternalUnknown(s)
| CryptTokenInfo::ExternalUnknown(s)
| CryptTokenInfo::External(s)
if &s != "fido2luks" =>
{
continue
}
_ => (),
};
let json = device.token_handle().json_get(i)?;
let info: Fido2LuksToken =
serde_json::from_value(json.clone()).map_err(|_| Fido2LuksError::LuksError {
cause: LuksError::InvalidToken(json.to_string()),
})?;
let slots = || {
info.keyslots
.iter()
.filter_map(|slot| slot.parse::<u32>().ok())
};
for cred in info.credential.iter().cloned() {
creds
.entry(cred)
.or_insert_with(|| slots().collect::<HashSet<u32>>())
.extend(slots());
}
}
if creds.is_empty() {
return Err(Fido2LuksError::LuksError {
cause: LuksError::NoToken,
});
}
let (secret, credential) = secret(creds.keys().cloned().collect())?;
let slots = creds.get(&credential).unwrap();
let slots = slots
.iter()
.cloned()
.map(Option::Some)
.chain(std::iter::once(None).take(slots.is_empty() as usize));
for slot in slots {
match device
.activate_handle()
.activate_by_passphrase(Some(name), slot, &secret, CryptActivateFlags::empty())
.map(|_slot| ())
.map_err(LuksError::activate)
{
Err(Fido2LuksError::WrongSecret) => (),
res => return res,
}
}
Err(Fido2LuksError::WrongSecret)
}
pub fn add_key<P: AsRef<Path>>(
path: P,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
credential_id: Option<&[u8]>,
) -> Fido2LuksResult<u32> {
let mut device = load_device_handle(path)?;
if let Some(millis) = iteration_time {
device.settings_handle().set_iteration_time(millis)
}
let slot = device
.keyslot_handle()
.add_by_passphrase(None, old_secret, secret)?;
if let Some(id) = credential_id {
/* if let e @ Err(_) = check_luks2(&mut device) {
//rollback
device.keyslot_handle(Some(slot)).destroy()?;
return e.map(|_| 0u32);
}*/
device.token_handle().json_set(TokenInput::AddToken(
&serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap(),
))?;
}
Ok(slot)
}
fn find_token(
device: &mut CryptDevice,
slot: u32,
) -> Fido2LuksResult<Option<(u32, Fido2LuksToken)>> {
for i in 0..256 {
let status = device.token_handle().status(i)?;
match status {
CryptTokenInfo::Inactive => break,
CryptTokenInfo::Internal(s)
| CryptTokenInfo::InternalUnknown(s)
| CryptTokenInfo::ExternalUnknown(s)
| CryptTokenInfo::External(s)
if &s != "fido2luks" =>
{
continue
}
_ => (),
};
let json = device.token_handle().json_get(i)?;
let info: Fido2LuksToken =
serde_json::from_value(json.clone()).map_err(|_| Fido2LuksError::LuksError {
cause: LuksError::InvalidToken(json.to_string()),
})?;
if info.keyslots.contains(&slot.to_string()) {
return Ok(Some((i, info)));
}
}
Ok(None)
}
pub fn remove_keyslots<P: AsRef<Path>>(path: P, exclude: &[u32]) -> Fido2LuksResult<u32> {
let mut device = load_device_handle(path)?;
let mut destroyed = 0;
let mut tokens = Vec::new();
for slot in 0..256 {
match device.keyslot_handle().status(slot)? {
KeyslotInfo::Inactive => continue,
KeyslotInfo::Active | KeyslotInfo::ActiveLast if !exclude.contains(&slot) => {
if let Ok(_) = check_luks2(&mut device) {
if let Some((token, _)) = find_token(&mut device, slot)? {
tokens.push(token);
}
}
device.keyslot_handle().destroy(slot)?;
destroyed += 1;
}
KeyslotInfo::ActiveLast => break,
_ => (),
}
if device.keyslot_handle().status(slot)? == KeyslotInfo::ActiveLast {
break;
}
}
// Ensure indices stay valid
tokens.sort();
for token in tokens.iter().rev() {
device
.token_handle()
.json_set(TokenInput::RemoveToken(*token))?;
}
Ok(destroyed)
}
pub fn replace_key<P: AsRef<Path>>(
path: P,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
credential_id: Option<&[u8]>,
) -> Fido2LuksResult<u32> {
let mut device = load_device_handle(path)?;
// Set iteration time not sure wether this applies to luks2 as well
if let Some(millis) = iteration_time {
device.settings_handle().set_iteration_time(millis)
}
let slot = device
.keyslot_handle()
.change_by_passphrase(None, None, old_secret, secret)? as u32;
if let Some(id) = credential_id {
if check_luks2(&mut device).is_ok() {
let token = find_token(&mut device, slot)?.map(|(t, _)| t);
if let Some(token) = token {
device.token_handle().json_set(TokenInput::ReplaceToken(
token,
&serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap(),
))?;
}
}
}
Ok(slot)
}

View File

@@ -1,38 +1,32 @@
#[macro_use]
extern crate failure;
extern crate ctap_hmac as ctap;
#[macro_use]
extern crate serde_derive;
use crate::cli::*;
use crate::config::*;
use crate::device::*;
use crate::error::*;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use cryptsetup_rs as luks;
use cryptsetup_rs::Luks1CryptDevice;
use std::io::{self};
use std::io;
use std::path::PathBuf;
use std::process::exit;
mod cli;
mod config;
mod device;
mod error;
mod luks;
mod util;
fn open_container(device: &PathBuf, name: &str, secret: &[u8; 32]) -> Fido2LuksResult<()> {
let mut handle = luks::open(device.canonicalize()?)?.luks1()?;
let _slot = handle.activate(name, &secret[..])?;
Ok(())
}
fn assemble_secret(hmac_result: &[u8], salt: &[u8]) -> [u8; 32] {
let mut digest = Sha256::new();
digest.input(salt);
digest.input(hmac_result);
let mut secret = [0u8; 32];
digest.result(&mut secret);
secret
}
fn main() -> Fido2LuksResult<()> {
run_cli()
match run_cli() {
Err(e) => {
#[cfg(debug_assertions)]
eprintln!("{:?}", e);
#[cfg(not(debug_assertions))]
eprintln!("{}", e);
exit(e.exit_code())
}
_ => exit(0),
}
}

View File

@@ -1,8 +1,19 @@
use crate::error::*;
use ring::digest;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
pub fn sha256(messages: &[&[u8]]) -> [u8; 32] {
let mut digest = digest::Context::new(&digest::SHA256);
for m in messages.iter() {
digest.update(m);
}
let mut secret = [0u8; 32];
secret.as_mut().copy_from_slice(digest.finish().as_ref());
secret
}
pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult<String> {
match rpassword::read_password_from_tty(Some(&[q, ": "].join("")))? {
ref pass
@@ -12,12 +23,16 @@ pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult<String> {
{
Err(Fido2LuksError::AskPassError {
cause: AskPassError::Mismatch,
})?
})
}
pass => Ok(pass),
}
}
pub fn read_password_hashed(q: &str, verify: bool) -> Fido2LuksResult<[u8; 32]> {
read_password(q, verify).map(|pass| sha256(&[pass.as_bytes()]))
}
pub fn read_keyfile<P: Into<PathBuf>>(path: P) -> Fido2LuksResult<Vec<u8>> {
let mut file = File::open(path.into())?;
let mut key = Vec::new();