Compare commits
108 Commits
Author | SHA1 | Date | |
---|---|---|---|
210da1ce0f | |||
4509cacd6d | |||
![]() |
67136f2405 | ||
b2e4950db5 | |||
5496c4e61b | |||
51fa26b7d5 | |||
![]() |
a3696962e8 | ||
![]() |
7e6b33ae7f | ||
b3495c45f3 | |||
![]() |
17ca487b85 | ||
b0404f2fc1 | |||
![]() |
de21e3ef8d | ||
![]() |
8a7b3addbb | ||
e7e44cd61b | |||
0ec859f4a6 | |||
55bae4161e | |||
086c1a0594 | |||
c2e38eb06f | |||
03ef5721e0 | |||
008e644024 | |||
e1f762ddc9 | |||
![]() |
2266754a95 | ||
8811cff6d1 | |||
99787b614c | |||
ee28f87148 | |||
196356fe3b | |||
3ff7e698bd | |||
04d0d60fb3 | |||
e64f777c54 | |||
8465949b44 | |||
![]() |
06bed03e7b | ||
![]() |
36f82e7c3a | ||
cd90564f60 | |||
0f6d79a7e4 | |||
4136b1bfad | |||
81016a1a42 | |||
840868468b | |||
97880e4f41 | |||
e798ba5c70 | |||
298e05fed7 | |||
a498e1416f | |||
92e413de50 | |||
023399bb14 | |||
a53a430c23 | |||
5f107cd337 | |||
ddfd24a098 | |||
743edf668a | |||
4507107fac | |||
a8482c50a2 | |||
09be5ef551 | |||
6f6c84ddba | |||
5a05cad695 | |||
d8d24b40f5 | |||
c1a82b9ae6 | |||
a26b79bcd6 | |||
f774580c9c | |||
69732a1ad6 | |||
b8ae9d91f0 | |||
fcdd2a2d3d | |||
c3d6425e2d | |||
0b19760175 | |||
2ec8679c47 | |||
65e1dead8b | |||
478fb5e036 | |||
1547f5e199 | |||
5c0364587e | |||
9307503bdc | |||
b94f45d1ff | |||
c8fb636846 | |||
49e2835f60 | |||
d5c0d48f03 | |||
ad2451f548 | |||
bb7ee7c1ce | |||
0ba77963d2 | |||
1658800553 | |||
a394b7d1d1 | |||
c99d7f562d | |||
c4f781e6e3 | |||
f6de4a033e | |||
f5880346b9 | |||
6089b254b4 | |||
03e34ec790 | |||
a437106fcb | |||
7ed948d53b | |||
c4e08413c0 | |||
![]() |
7429706920 | ||
![]() |
a5fd5fa9f6 | ||
659fafdfb4 | |||
7f2668eded | |||
![]() |
ae714cdef3 | ||
![]() |
ae802e5e71 | ||
![]() |
a5f0444d24 | ||
![]() |
a307d87d88 | ||
721dded6d2 | |||
e7049a281a | |||
5d1c7beb4d | |||
2bac911b32 | |||
9a8ea993b5 | |||
![]() |
7eb9dcc928 | ||
![]() |
509e300a8f | ||
![]() |
42945956a6 | ||
![]() |
3cf5ccf2a0 | ||
79e9a37806 | |||
d16118e695 | |||
6e53449ff6 | |||
fbcfdea96b | |||
99e408cc8d | |||
8fc9e0dcce |
41
.drone.yml
41
.drone.yml
@ -3,32 +3,27 @@ name: default
|
||||
|
||||
steps:
|
||||
- name: fmt
|
||||
image: rust:1.37.0
|
||||
image: rust:1.43.0
|
||||
commands:
|
||||
- rustup component add rustfmt
|
||||
- cargo fmt --all -- --check
|
||||
- rustup component add rustfmt
|
||||
- cargo fmt --all -- --check
|
||||
- name: test
|
||||
image: rust:1.37.0
|
||||
image: ubuntu:focal
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
commands:
|
||||
- apt update && apt install -y libcryptsetup-dev libkeyutils-dev
|
||||
- cargo test
|
||||
|
||||
- name: build
|
||||
image: rust:1.37.0
|
||||
- apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev
|
||||
- cargo test --locked
|
||||
- name: publish
|
||||
image: ubuntu:focal
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
CARGO_REGISTRY_TOKEN:
|
||||
from_secret: cargo_tkn
|
||||
commands:
|
||||
- apt update && apt install -y libcryptsetup-dev libkeyutils-dev
|
||||
- cargo install -f --path . --root .
|
||||
- grep -E 'version ?= ?"${DRONE_TAG}"' -i Cargo.toml || (printf "incorrect crate/tag version" && exit 1)
|
||||
- apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev
|
||||
- cargo package --all-features
|
||||
- cargo publish --all-features
|
||||
when:
|
||||
event: tag
|
||||
- name: publish
|
||||
image: plugins/github-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: github_release
|
||||
files:
|
||||
- bin/fido2luks
|
||||
checksum:
|
||||
- md5
|
||||
- sha256
|
||||
when:
|
||||
event: tag
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -2,3 +2,9 @@
|
||||
**/*.rs.bk
|
||||
.idea/
|
||||
*.iml
|
||||
fido2luks.bash
|
||||
fido2luks.elv
|
||||
fido2luks.fish
|
||||
fido2luks.zsh
|
||||
result
|
||||
result-*
|
||||
|
1095
Cargo.lock
generated
1095
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
52
Cargo.toml
52
Cargo.toml
@ -1,28 +1,38 @@
|
||||
[package]
|
||||
name = "fido2luks"
|
||||
version = "0.2.1"
|
||||
version = "0.2.20"
|
||||
authors = ["shimunn <shimun@shimun.net>"]
|
||||
edition = "2018"
|
||||
|
||||
description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
|
||||
license = "GPL v3"
|
||||
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 = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
ctap = "0.1.0"
|
||||
cryptsetup-rs = "0.2.0"
|
||||
libcryptsetup-sys = "0.1.1"
|
||||
|
||||
ctap_hmac = { version="0.4.5", 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"
|
||||
|
||||
[patch.crates-io]
|
||||
#Until https://github.com/solidninja/cryptsetup-rs/pull/2 merges
|
||||
cryptsetup-rs = { path = "./patch/cryptsetup-rs" }
|
||||
libcryptsetup-sys = { path = "./patch/cryptsetup-rs/libcryptsetup-sys" }
|
||||
ctap = { path = "./patch/ctap" }
|
||||
[build-dependencies]
|
||||
ctap_hmac = { version="0.4.5", features = ["request_multiple"] }
|
||||
hex = "0.3.2"
|
||||
ring = "0.13.5"
|
||||
failure = "0.1.5"
|
||||
rpassword = "4.0.1"
|
||||
libcryptsetup-rs = "0.4.1"
|
||||
structopt = "0.3.2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
@ -31,8 +41,16 @@ panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = false
|
||||
|
||||
[package.metadata.rpm]
|
||||
buildflags = ["--release"]
|
||||
|
||||
[package.metadata.rpm.targets]
|
||||
fido2luks = { path = "/usr/bin/fido2luks" }
|
||||
[package.metadata.deb]
|
||||
depends = "$auto, cryptsetup"
|
||||
build-depends = "libclang-dev, libcryptsetup-dev"
|
||||
extended-description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
|
||||
assets = [
|
||||
["target/release/fido2luks", "usr/bin/", "755"],
|
||||
["fido2luks.bash", "usr/share/bash-completion/completions/fido2luks", "644"],
|
||||
["pam_mount/fido2luksmounthelper.sh", "usr/bin/", "755"],
|
||||
["initramfs-tools/keyscript.sh", "/lib/cryptsetup/scripts/fido2luks", "755" ],
|
||||
["initramfs-tools/hook/fido2luks.sh", "etc/initramfs-tools/hooks/", "755" ],
|
||||
["initramfs-tools/fido2luks.conf", "etc/", "644"],
|
||||
]
|
||||
conf-files = ["/etc/fido2luks.conf"]
|
||||
|
37
PKGBUILD
Normal file
37
PKGBUILD
Normal file
@ -0,0 +1,37 @@
|
||||
# Maintainer: shimunn <shimun@shimun.net>
|
||||
|
||||
pkgname=fido2luks-git
|
||||
pkgver=0.2.16.7e6b33a
|
||||
pkgrel=1
|
||||
makedepends=('rust' 'cargo' 'cryptsetup' 'clang' 'git')
|
||||
depends=('cryptsetup')
|
||||
arch=('i686' 'x86_64' 'armv6h' 'armv7h')
|
||||
pkgdesc="Decrypt your LUKS partition using a FIDO2 compatible authenticator"
|
||||
url="https://github.com/shimunn/fido2luks"
|
||||
license=('MPL-2.0')
|
||||
source=('git+https://github.com/shimunn/fido2luks')
|
||||
sha512sums=('SKIP')
|
||||
|
||||
pkgver() {
|
||||
cd fido2luks
|
||||
|
||||
# Use tag version if possible otherwise concat project version and git ref
|
||||
git describe --exact-match --tags HEAD 2>/dev/null ||
|
||||
echo "$(cargo pkgid | cut -d'#' -f2).$(git describe --always)"
|
||||
}
|
||||
|
||||
build() {
|
||||
cd fido2luks
|
||||
cargo build --release --locked --all-features --target-dir=target
|
||||
}
|
||||
|
||||
package() {
|
||||
cd fido2luks
|
||||
|
||||
install -Dm 755 target/release/fido2luks -t "${pkgdir}/usr/bin"
|
||||
install -Dm 755 pam_mount/fido2luksmounthelper.sh -t "${pkgdir}/usr/bin"
|
||||
install -Dm 644 initcpio/hooks/fido2luks -t "${pkgdir}/usr/lib/initcpio/hooks"
|
||||
install -Dm 644 initcpio/install/fido2luks -t "${pkgdir}/usr/lib/initcpio/install"
|
||||
install -Dm 644 fido2luks.bash "${pkgdir}/usr/share/bash-completion/completions/fido2luks"
|
||||
install -Dm 644 fido2luks.fish -t "${pkgdir}/usr/share/fish/vendor_completions.d"
|
||||
}
|
131
README.md
131
README.md
@ -1,15 +1,15 @@
|
||||
# fido2luks
|
||||
# fido2luks [](https://crates.io/crates/fido2luks)
|
||||
|
||||
This will allow you to unlock your luks encrypted disk with an fido2 compatable key
|
||||
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, [Ubuntu 20.04](initramfs-tools/), [NixOS](https://nixos.org/nixos/manual/#sec-luks-file-systems-fido2) using a Solo Key, Trezor Model T, YubiKey(fw >= [5.2.3](https://support.yubico.com/hc/en-us/articles/360016649319-YubiKey-5-2-3-Enhancements-to-FIDO-2-Support))
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```
|
||||
dnf install cargo cryptsetup-devel -y
|
||||
dnf install clang cargo cryptsetup-devel -y
|
||||
```
|
||||
|
||||
### Device
|
||||
@ -17,18 +17,24 @@ 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) >> 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
|
||||
. fido2luks.conf
|
||||
. /etc/fido2luks.conf
|
||||
|
||||
#Repeat for each luks volume
|
||||
# Repeat for each luks volume
|
||||
# You can also use the `--token` flag when using LUKS2 which will then store the credential in the LUKS header,
|
||||
# enabling you to use `fido2luks open-token` without passing a credential as parameter
|
||||
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>
|
||||
|
||||
```
|
||||
@ -43,21 +49,112 @@ sudo make install
|
||||
|
||||
### Grub
|
||||
|
||||
Add `rd.luks.2fa=<CREDENTIAL_ID>:<DISK_UUID>` to `GRUB_CMDLINE_LINUX`
|
||||
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 addkey`
|
||||
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
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
Just reboot and see if it works, if thats the case you should remove your old less secure password from your luks header:
|
||||
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
|
||||
|
||||
```
|
||||
#Recommend in case you lose your authenticator, store this backupfile somewhere safe
|
||||
mkdir /boot/fido2luks/
|
||||
cp /usr/bin/fido2luks /boot/fido2luks/
|
||||
cp /etc/fido2luks.conf /boot/fido2luks/
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
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
|
||||
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
|
||||
# You can also pass `--token` if you're using LUKS2 which will then store the credential in the LUKS header,
|
||||
# which will enable you to use `fido2luks open-token` without passing a credential as parameter
|
||||
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`
|
||||
|
||||
### Multiple keys
|
||||
|
||||
Additional/backup keys are supported, Multiple fido2luks credentials can be added to your /etc/fido2luks.conf file. Credential tokens are comma separated.
|
||||
```
|
||||
FIDO2LUKS_CREDENTIAL_ID=<CREDENTIAL1>,<CREDENTIAL2>,<CREDENTIAL3>
|
||||
```
|
||||
|
||||
## Removal
|
||||
|
||||
Remove `rd.luks.2fa` from `GRUB_CMDLINE_LINUX` in /etc/default/grub
|
||||
|
||||
```
|
||||
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 /etc/fido2luks.conf
|
||||
```
|
||||
|
||||
## Theory of operation
|
||||
|
||||
fido2luks builds on two basic building blocks, LUKS as an abstraction over linux disk encryption and and the FIDO2 extension [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#sctn-hmac-secret-extension).
|
||||
The `hmac-secret` extension allows for an secret to be dervied on the FIDO2 device from two inputs, the user supplied salt/password/keyfile and another secret contained within the FID2 device. The output of the `hmac-secret` function will then be used to decrypt the LUKS header which in turn is used to decrypt the disk.
|
||||
```
|
||||
|
||||
+-------------------------------------------------------------------------------+
|
||||
| |
|
||||
| +-----------------------------------------+ |
|
||||
| | FIDO2 device | |
|
||||
| | | |
|
||||
| | | |
|
||||
+-------+--------+ +------+ | +---------------+ | | +------------------------+
|
||||
| Salt/Password +-> |sha256+------------------------> | | | v | LUKS header |
|
||||
+----------------+ +------+ | | | | | | +---------------+
|
||||
| | | | +--------+ +------------------------+--------> |Disk master key|
|
||||
| | sha256_hmac +---------> | sha256 +-------> | Keyslot 1 | +---------------+
|
||||
+----------------+ | +----------+ | | | +--------+ +------------------------+
|
||||
| FIDO credential+---------------> |Credential| +----> | | | | Keyslot 2 |
|
||||
+----------------+ | |secret | | | | +------------------------+
|
||||
| +----------+ +---------------+ |
|
||||
| |
|
||||
| |
|
||||
+-----------------------------------------+
|
||||
|
||||
```
|
||||
Since all these components build upon each other losing or damaging just one of them will render the disk undecryptable, it's threfore of paramount importance to backup the LUKS header and ideally set an backup password
|
||||
or utilise more than one FIDO2 device. Each additional credential and password combination will require it's own LUKS keyslot since the credential secret is randomly generated for each new credential and will thus result
|
||||
in a completly different secret.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under
|
||||
|
||||
* Mozilla Public License 2.0, ([LICENSE-MPL](LICENSE-MPL) or https://www.mozilla.org/en-US/MPL/2.0/)
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the MPL 2.0
|
||||
license, shall be licensed as above, without any additional terms or
|
||||
conditions.
|
||||
|
||||
|
24
build.rs
Normal file
24
build.rs
Normal file
@ -0,0 +1,24 @@
|
||||
#![allow(warnings)]
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
extern crate ctap_hmac as ctap;
|
||||
|
||||
#[path = "src/cli_args/mod.rs"]
|
||||
mod cli_args;
|
||||
#[path = "src/error.rs"]
|
||||
mod error;
|
||||
#[path = "src/util.rs"]
|
||||
mod util;
|
||||
|
||||
use cli_args::Args;
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
use structopt::clap::Shell;
|
||||
use structopt::StructOpt;
|
||||
|
||||
fn main() {
|
||||
// generate completion scripts, zsh does panic for some reason
|
||||
for shell in Shell::variants().iter().filter(|shell| **shell != "zsh") {
|
||||
Args::clap().gen_completions(env!("CARGO_PKG_NAME"), Shell::from_str(shell).unwrap(), ".");
|
||||
}
|
||||
}
|
3
dracut/96luks-2fa/fido2luks.conf
Normal file
3
dracut/96luks-2fa/fido2luks.conf
Normal file
@ -0,0 +1,3 @@
|
||||
FIDO2LUKS_SALT=Ask
|
||||
FIDO2LUKS_PASSWORD_HELPER=/usr/bin/systemd-ask-password Please enter second factor for LUKS disk encryption
|
||||
|
@ -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,19 +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 -- "\nEnvironment=FIDO2LUKS_CREDENTIAL_ID='%s'" "$credential_id"
|
||||
printf -- "\nEnvironment=FIDO2LUKS_SALT='%s'" "Ask"
|
||||
printf -- "\nEnvironment=FIDO2LUKS_PASSWORD_HELPER='%s'" "/usr/bin/systemd-ask-password Disk 2fa password"
|
||||
printf -- "\nEnvironmentFile=%s" "/etc/fido2luks.conf"
|
||||
[ ! -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 -- "\nExecStartPre=-/usr/bin/plymouth display-message --text \"${CON_MSG}\""
|
||||
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"
|
||||
|
||||
|
@ -18,6 +18,7 @@ depends () {
|
||||
install () {
|
||||
inst "$moddir/luks-2fa-generator.sh" "/etc/systemd/system-generators/luks-2fa-generator.sh"
|
||||
inst_simple "/usr/bin/fido2luks" "/usr/bin/fido2luks"
|
||||
inst_simple "/etc/fido2luks.conf" "/etc/fido2luks.conf"
|
||||
inst "$systemdutildir/systemd-cryptsetup"
|
||||
mkdir -p "$initdir/luks-2fa"
|
||||
|
||||
|
@ -15,6 +15,7 @@ help:
|
||||
install:
|
||||
cp ${MODULE_CONF_D}/${MODULE_CONF} ${DRACUT_CONF_D}/
|
||||
cp -r ${MODULE_DIR} ${DRACUT_MODULES_D}/
|
||||
cp ${MODULE_DIR}/fido2luks.conf /etc/fido2luks.conf
|
||||
dracut -fv
|
||||
clean:
|
||||
rm ${DRACUT_CONF_D}/${MODULE_CONF}
|
||||
|
62
flake.lock
generated
Normal file
62
flake.lock
generated
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"nodes": {
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1639947939,
|
||||
"narHash": "sha256-pGsM8haJadVP80GFq4xhnSpNitYNQpaXk4cnA796Cso=",
|
||||
"owner": "nmattia",
|
||||
"repo": "naersk",
|
||||
"rev": "2fc8ce9d3c025d59fee349c1f80be9785049d653",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nmattia",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1638109994,
|
||||
"narHash": "sha256-OpA37PTiPMIqoRJbufbl5rOLII7HeeGcA0yl7FoyCIE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a284564b7f75ac4db73607db02076e8da9d42c9d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"locked": {
|
||||
"lastModified": 1638122382,
|
||||
"narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "74f7e4319258e287b0f9cb95426c9853b282730b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
61
flake.nix
Normal file
61
flake.nix
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator";
|
||||
|
||||
inputs = {
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
naersk = {
|
||||
url = "github:nmattia/naersk";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, utils, naersk }:
|
||||
let
|
||||
root = ./.;
|
||||
pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
|
||||
forPkgs = pkgs:
|
||||
let
|
||||
naersk-lib = naersk.lib."${pkgs.system}";
|
||||
buildInputs = with pkgs; [ cryptsetup ];
|
||||
LIBCLANG_PATH = "${pkgs.clang.cc.lib}/lib";
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkgconfig
|
||||
clang
|
||||
];
|
||||
in
|
||||
rec {
|
||||
# `nix build`
|
||||
packages.${pname} = naersk-lib.buildPackage {
|
||||
inherit pname root buildInputs nativeBuildInputs LIBCLANG_PATH;
|
||||
};
|
||||
defaultPackage = packages.${pname};
|
||||
|
||||
# `nix run`
|
||||
apps.${pname} = utils.lib.mkApp {
|
||||
drv = packages.${pname};
|
||||
};
|
||||
defaultApp = apps.${pname};
|
||||
|
||||
# `nix flake check`
|
||||
checks = {
|
||||
fmt = with pkgs; runCommandLocal "${pname}-fmt" { buildInputs = [ cargo rustfmt nixpkgs-fmt ]; } ''
|
||||
cd ${root}
|
||||
cargo fmt -- --check
|
||||
nixpkgs-fmt --check *.nix
|
||||
touch $out
|
||||
'';
|
||||
};
|
||||
|
||||
# `nix develop`
|
||||
devShell = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [ rustc cargo rustfmt nixpkgs-fmt ] ++ nativeBuildInputs;
|
||||
inherit buildInputs LIBCLANG_PATH;
|
||||
};
|
||||
};
|
||||
forSystem = system: forPkgs nixpkgs.legacyPackages."${system}";
|
||||
in
|
||||
(utils.lib.eachSystem [ "aarch64-linux" "i686-linux" "x86_64-linux" ] forSystem) // {
|
||||
overlay = final: prev: (forPkgs final).packages;
|
||||
};
|
||||
|
||||
}
|
18
initcpio/Makefile
Normal file
18
initcpio/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
.PHONY: install remove
|
||||
|
||||
install:
|
||||
install -Dm644 hooks/fido2luks -t /usr/lib/initcpio/hooks
|
||||
install -Dm644 install/fido2luks -t /usr/lib/initcpio/install
|
||||
ifdef preset
|
||||
mkinitcpio -p $(preset)
|
||||
else
|
||||
mkinitcpio -P
|
||||
endif
|
||||
|
||||
remove:
|
||||
rm /usr/lib/initcpio/{hooks,install}/fido2luks
|
||||
ifdef preset
|
||||
mkinitcpio -p $(preset)
|
||||
else
|
||||
mkinitcpio -P
|
||||
endif
|
52
initcpio/README.md
Normal file
52
initcpio/README.md
Normal file
@ -0,0 +1,52 @@
|
||||
## fido2luks hook for mkinitcpio (ArchLinux and derivatives)
|
||||
|
||||
> ⚠️ Before proceeding, it is very advised to [backup your existing LUKS2 header](https://wiki.archlinux.org/title/dm-crypt/Device_encryption#Backup_using_cryptsetup) to external storage
|
||||
|
||||
### Setup
|
||||
|
||||
1. Connect your FIDO2 authenticator
|
||||
2. Generate credential id
|
||||
|
||||
```shell
|
||||
fido2luks credential
|
||||
```
|
||||
|
||||
3. Generate salt (random string)
|
||||
|
||||
```shell
|
||||
pwgen 48 1
|
||||
```
|
||||
|
||||
4. Add key to your LUKS2 device
|
||||
|
||||
```shell
|
||||
fido2luks add-key -Pt --salt <salt> <block_device> <credential_id>
|
||||
```
|
||||
|
||||
`-P` - request PIN to unlock the authenticator
|
||||
`-t` - add token (including credential id) to the LUKS2 header
|
||||
`-e` - wipe all other keys
|
||||
|
||||
For the full list of options see `fido2luks add-key --help`
|
||||
|
||||
5. Edit [/etc/fido2luks.conf](/initcpio/fido2luks.conf)
|
||||
|
||||
Keyslot (`FIDO2LUKS_DEVICE_SLOT`) can be obtained from the output of
|
||||
|
||||
```shell
|
||||
cryptsetup luksDump <block_device>
|
||||
```
|
||||
|
||||
6. Add fido2luks hook to /etc/mkinitcpio.conf
|
||||
|
||||
Before or instead of `encrypt` hook, for example:
|
||||
|
||||
```shell
|
||||
HOOKS=(base udev autodetect modconf keyboard block fido2luks filesystems fsck)
|
||||
```
|
||||
|
||||
7. Recreate initial ramdisk
|
||||
|
||||
```shell
|
||||
mkinitcpio -p <preset>
|
||||
```
|
18
initcpio/fido2luks.conf
Normal file
18
initcpio/fido2luks.conf
Normal file
@ -0,0 +1,18 @@
|
||||
# Set credential *ONLY IF* it's not embedded in the LUKS2 header
|
||||
FIDO2LUKS_CREDENTIAL_ID=
|
||||
|
||||
# Encrypted device and its name under /dev/mapper
|
||||
# Can be overridden by `cryptdevice` kernel parameter
|
||||
FIDO2LUKS_DEVICE=
|
||||
FIDO2LUKS_MAPPER_NAME=
|
||||
|
||||
FIDO2LUKS_SALT=string:<salt>
|
||||
|
||||
# Use specific keyslot (ignore all other slots)
|
||||
FIDO2LUKS_DEVICE_SLOT=
|
||||
|
||||
# Await for an authenticator to be connected (in seconds)
|
||||
FIDO2LUKS_DEVICE_AWAIT=
|
||||
|
||||
# Set to 1 if PIN is required to unlock the authenticator
|
||||
FIDO2LUKS_ASK_PIN=
|
55
initcpio/hooks/fido2luks
Normal file
55
initcpio/hooks/fido2luks
Normal file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/ash
|
||||
|
||||
run_hook() {
|
||||
modprobe -a -q dm-crypt >/dev/null 2>&1
|
||||
. /etc/fido2luks.conf
|
||||
|
||||
if [ -z "$cryptdevice" ]; then
|
||||
device="$FIDO2LUKS_DEVICE"
|
||||
dmname="$FIDO2LUKS_MAPPER_NAME"
|
||||
else
|
||||
IFS=: read cryptdev dmname _cryptoptions <<EOF
|
||||
$cryptdevice
|
||||
EOF
|
||||
if ! device=$(resolve_device "${cryptdev}" ${rootdelay}); then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
options="--salt $FIDO2LUKS_SALT"
|
||||
|
||||
if [ "$FIDO2LUKS_ASK_PIN" == 1 ]; then
|
||||
options="$options --pin"
|
||||
fi
|
||||
|
||||
if [ -n "$FIDO2LUKS_DEVICE_SLOT" ]; then
|
||||
options="$options --slot $FIDO2LUKS_DEVICE_SLOT"
|
||||
fi
|
||||
|
||||
if [ -n "$FIDO2LUKS_DEVICE_AWAIT" ]; then
|
||||
options="$options --await-dev $FIDO2LUKS_DEVICE_AWAIT"
|
||||
fi
|
||||
|
||||
# HACK: /dev/tty is hardcoded in rpassword, but not accessible from the ramdisk
|
||||
# Temporary link it to /dev/tty1
|
||||
mv /dev/tty /dev/tty.back
|
||||
ln -s /dev/tty1 /dev/tty
|
||||
|
||||
printf "\nAuthentication is required to access the $dmname volume at $device\n"
|
||||
|
||||
if [ -z "$FIDO2LUKS_CREDENTIAL_ID" ]; then
|
||||
fido2luks open-token $device $dmname $options
|
||||
else
|
||||
fido2luks open $device $dmname $FIDO2LUKS_CREDENTIAL_ID $options
|
||||
fi
|
||||
exit_code=$?
|
||||
|
||||
# Restore /dev/tty
|
||||
mv /dev/tty.back /dev/tty
|
||||
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
printf '\n'
|
||||
read -s -p 'Press Enter to continue'
|
||||
printf '\n'
|
||||
fi
|
||||
}
|
31
initcpio/install/fido2luks
Normal file
31
initcpio/install/fido2luks
Normal file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
build() {
|
||||
local mod
|
||||
|
||||
add_module dm-crypt
|
||||
add_module dm-integrity
|
||||
if [[ $CRYPTO_MODULES ]]; then
|
||||
for mod in $CRYPTO_MODULES; do
|
||||
add_module "$mod"
|
||||
done
|
||||
else
|
||||
add_all_modules /crypto/
|
||||
fi
|
||||
|
||||
add_binary fido2luks
|
||||
add_binary dmsetup
|
||||
add_file /usr/lib/udev/rules.d/10-dm.rules
|
||||
add_file /usr/lib/udev/rules.d/13-dm-disk.rules
|
||||
add_file /usr/lib/udev/rules.d/95-dm-notify.rules
|
||||
add_file /usr/lib/initcpio/udev/11-dm-initramfs.rules /usr/lib/udev/rules.d/11-dm-initramfs.rules
|
||||
add_file /etc/fido2luks.conf /etc/fido2luks.conf
|
||||
|
||||
add_runscript
|
||||
}
|
||||
|
||||
help() {
|
||||
cat <<HELPEOF
|
||||
This hook allows to decrypt LUKS2 partition using FIDO2 compatible authenticator
|
||||
HELPEOF
|
||||
}
|
11
initramfs-tools/Makefile
Normal file
11
initramfs-tools/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
.PHONY: install
|
||||
install:
|
||||
chmod +x hook/fido2luks.sh keyscript.sh
|
||||
cp -f hook/fido2luks.sh /etc/initramfs-tools/hooks/
|
||||
mkdir -p /usr/share/fido2luks
|
||||
cp -f keyscript.sh /lib/cryptsetup/scripts/fido2luks
|
||||
update-initramfs -u
|
||||
remove:
|
||||
sh -c "grep 'keyscript=fido2luks' -i /etc/crypttab && ( echo 'ERROR: your system is still setup to use fido2luks during boot' && exit 1) || exit 0"
|
||||
rm /etc/initramfs-tools/hooks/fido2luks.sh /lib/cryptsetup/scripts/fido2luks
|
||||
update-initramfs -u
|
34
initramfs-tools/README.md
Normal file
34
initramfs-tools/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
## Initramfs-tools based systems(Ubuntu and derivatives)
|
||||
|
||||
For easiest installation [download and install the precompiled deb from releases.](https://github.com/shimunn/fido2luks/releases). However it is possible to build from source via the instructions on the main readme.
|
||||
|
||||
```
|
||||
sudo -s
|
||||
|
||||
# Insert FIDO key.
|
||||
fido2luks credential
|
||||
# Tap FIDO key
|
||||
# Copy returned string <CREDENTIAL>
|
||||
|
||||
nano /etc/fido2luks.conf
|
||||
# Insert <CREDENTIAL>
|
||||
# FIDO2LUKS_CREDENTIAL_ID=<CREDENTIAL>
|
||||
|
||||
set -a
|
||||
. /etc/fido2luks.conf
|
||||
fido2luks -i add-key /dev/<LUKS PARTITION>
|
||||
# Current password: <Any current LUKS password>
|
||||
# Password: <Password used as FIDO challange>
|
||||
# Tap FIDO key
|
||||
|
||||
nano /etc/crypttab
|
||||
# Append to end ",discard,initramfs,keyscript=fido2luks"
|
||||
# E.g. sda6_crypt UUID=XXXXXXXXXX none luks,discard,initramfs,keyscript=fido2luks
|
||||
|
||||
update-initramfs -u
|
||||
|
||||
|
||||
```
|
||||
|
||||
[Recording showing part of the setup](https://shimun.net/fido2luks/setup.svg)
|
||||
|
5
initramfs-tools/fido2luks.conf
Normal file
5
initramfs-tools/fido2luks.conf
Normal file
@ -0,0 +1,5 @@
|
||||
FIDO2LUKS_SALT=Ask
|
||||
#FIDO2LUKS_PASSWORD_HELPER="/usr/bin/plymouth ask-for-password --prompt 'FIDO2 password salt'"
|
||||
FIDO2LUKS_CREDENTIAL_ID=
|
||||
FIDO2LUKS_USE_TOKEN=0
|
||||
FIDO2LUKS_PASSWORD_FALLBACK=1
|
14
initramfs-tools/hook/fido2luks.sh
Executable file
14
initramfs-tools/hook/fido2luks.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
case "$1" in
|
||||
prereqs)
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
. /usr/share/initramfs-tools/hook-functions
|
||||
copy_file config /etc/fido2luks.conf /etc/fido2luks.conf
|
||||
copy_exec /usr/bin/fido2luks
|
||||
exit 0
|
30
initramfs-tools/keyscript.sh
Executable file
30
initramfs-tools/keyscript.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
set -a
|
||||
. /etc/fido2luks.conf
|
||||
|
||||
# Set Defaults
|
||||
if [ -z "$FIDO2LUKS_USE_TOKEN" ]; then
|
||||
FIDO2LUKS_USE_TOKEN=0
|
||||
fi
|
||||
|
||||
if [ -z "$FIDO2LUKS_PASSWORD_FALLBACK" ]; then
|
||||
FIDO2LUKS_PASSWORD_FALLBACK=1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [ -z "$FIDO2LUKS_PASSWORD_HELPER" ]; then
|
||||
MSG="FIDO2 password salt for $CRYPTTAB_NAME"
|
||||
export FIDO2LUKS_PASSWORD_HELPER="plymouth ask-for-password --prompt '$MSG'"
|
||||
fi
|
||||
|
||||
if [ "$FIDO2LUKS_USE_TOKEN" -eq 1 ]; then
|
||||
export FIDO2LUKS_CREDENTIAL_ID="$FIDO2LUKS_CREDENTIAL_ID,$(fido2luks token list --csv $CRYPTTAB_SOURCE)"
|
||||
fi
|
||||
|
||||
fido2luks print-secret --bin
|
||||
|
||||
# Fall back to passphrase-based unlock if fido2luks fails
|
||||
if [ "$?" -gt 0 ] && [ "$FIDO2LUKS_PASSWORD_FALLBACK" -eq 1 ]; then
|
||||
plymouth ask-for-password --prompt "Password for $CRYPTTAB_SOURCE"
|
||||
fi
|
332
initramfs-tools/setup.svg
Normal file
332
initramfs-tools/setup.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 550 KiB |
220
pam_mount/fido2luksmounthelper.sh
Executable file
220
pam_mount/fido2luksmounthelper.sh
Executable file
@ -0,0 +1,220 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This is a rather minimal example Argbash potential
|
||||
# Example taken from http://argbash.readthedocs.io/en/stable/example.html
|
||||
#
|
||||
# ARG_POSITIONAL_SINGLE([operation],[Operation to perform (mount|umount)],[])
|
||||
# ARG_OPTIONAL_SINGLE([credentials-type],[c],[Type of the credentials to use (external|embedded)])
|
||||
# ARG_OPTIONAL_SINGLE([device],[d],[Name of the device to create])
|
||||
# ARG_OPTIONAL_SINGLE([mount-point],[m],[Path of the mount point to use])
|
||||
# ARG_OPTIONAL_BOOLEAN([ask-pin],[a],[Ask for a pin],[off])
|
||||
# ARG_OPTIONAL_SINGLE([salt],[s],[Salt to use],[""])
|
||||
# ARG_HELP([Unlocks/Locks a LUKS volume and mount/unmount it in the given mount point.])
|
||||
# ARGBASH_GO()
|
||||
# needed because of Argbash --> m4_ignore([
|
||||
### START OF CODE GENERATED BY Argbash v2.9.0 one line above ###
|
||||
# Argbash is a bash code generator used to get arguments parsing right.
|
||||
# Argbash is FREE SOFTWARE, see https://argbash.io for more info
|
||||
# Generated online by https://argbash.io/generate
|
||||
|
||||
|
||||
die()
|
||||
{
|
||||
local _ret="${2:-1}"
|
||||
test "${_PRINT_HELP:-no}" = yes && print_help >&2
|
||||
echo "$1" >&2
|
||||
exit "${_ret}"
|
||||
}
|
||||
|
||||
|
||||
begins_with_short_option()
|
||||
{
|
||||
local first_option all_short_options='cdmash'
|
||||
first_option="${1:0:1}"
|
||||
test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
|
||||
}
|
||||
|
||||
# THE DEFAULTS INITIALIZATION - POSITIONALS
|
||||
_positionals=()
|
||||
# THE DEFAULTS INITIALIZATION - OPTIONALS
|
||||
_arg_credentials_type=
|
||||
_arg_device=
|
||||
_arg_mount_point=
|
||||
_arg_ask_pin="off"
|
||||
_arg_salt=""
|
||||
|
||||
|
||||
print_help()
|
||||
{
|
||||
printf '%s\n' "Unlocks/Locks a LUKS volume and mount/unmount it in the given mount point."
|
||||
printf 'Usage: %s [-c|--credentials-type <arg>] [-d|--device <arg>] [-m|--mount-point <arg>] [-a|--(no-)ask-pin] [-s|--salt <arg>] [-h|--help] <operation>\n' "$0"
|
||||
printf '\t%s\n' "<operation>: Operation to perform (mount|umount)"
|
||||
printf '\t%s\n' "-c, --credentials-type: Type of the credentials to use (external|embedded) (no default)"
|
||||
printf '\t%s\n' "-d, --device: Name of the device to create (no default)"
|
||||
printf '\t%s\n' "-m, --mount-point: Path of the mount point to use (no default)"
|
||||
printf '\t%s\n' "-a, --ask-pin, --no-ask-pin: Ask for a pin (off by default)"
|
||||
printf '\t%s\n' "-s, --salt: Salt to use (default: '""')"
|
||||
printf '\t%s\n' "-h, --help: Prints help"
|
||||
}
|
||||
|
||||
|
||||
parse_commandline()
|
||||
{
|
||||
_positionals_count=0
|
||||
while test $# -gt 0
|
||||
do
|
||||
_key="$1"
|
||||
case "$_key" in
|
||||
-c|--credentials-type)
|
||||
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
|
||||
_arg_credentials_type="$2"
|
||||
shift
|
||||
;;
|
||||
--credentials-type=*)
|
||||
_arg_credentials_type="${_key##--credentials-type=}"
|
||||
;;
|
||||
-c*)
|
||||
_arg_credentials_type="${_key##-c}"
|
||||
;;
|
||||
-d|--device)
|
||||
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
|
||||
_arg_device="$2"
|
||||
shift
|
||||
;;
|
||||
--device=*)
|
||||
_arg_device="${_key##--device=}"
|
||||
;;
|
||||
-d*)
|
||||
_arg_device="${_key##-d}"
|
||||
;;
|
||||
-m|--mount-point)
|
||||
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
|
||||
_arg_mount_point="$2"
|
||||
shift
|
||||
;;
|
||||
--mount-point=*)
|
||||
_arg_mount_point="${_key##--mount-point=}"
|
||||
;;
|
||||
-m*)
|
||||
_arg_mount_point="${_key##-m}"
|
||||
;;
|
||||
-a|--no-ask-pin|--ask-pin)
|
||||
_arg_ask_pin="on"
|
||||
test "${1:0:5}" = "--no-" && _arg_ask_pin="off"
|
||||
;;
|
||||
-a*)
|
||||
_arg_ask_pin="on"
|
||||
_next="${_key##-a}"
|
||||
if test -n "$_next" -a "$_next" != "$_key"
|
||||
then
|
||||
{ begins_with_short_option "$_next" && shift && set -- "-a" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
|
||||
fi
|
||||
;;
|
||||
-s|--salt)
|
||||
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
|
||||
_arg_salt="$2"
|
||||
shift
|
||||
;;
|
||||
--salt=*)
|
||||
_arg_salt="${_key##--salt=}"
|
||||
;;
|
||||
-s*)
|
||||
_arg_salt="${_key##-s}"
|
||||
;;
|
||||
-h|--help)
|
||||
print_help
|
||||
exit 0
|
||||
;;
|
||||
-h*)
|
||||
print_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
_last_positional="$1"
|
||||
_positionals+=("$_last_positional")
|
||||
_positionals_count=$((_positionals_count + 1))
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
handle_passed_args_count()
|
||||
{
|
||||
local _required_args_string="'operation'"
|
||||
test "${_positionals_count}" -ge 1 || _PRINT_HELP=yes die "FATAL ERROR: Not enough positional arguments - we require exactly 1 (namely: $_required_args_string), but got only ${_positionals_count}." 1
|
||||
test "${_positionals_count}" -le 1 || _PRINT_HELP=yes die "FATAL ERROR: There were spurious positional arguments --- we expect exactly 1 (namely: $_required_args_string), but got ${_positionals_count} (the last one was: '${_last_positional}')." 1
|
||||
}
|
||||
|
||||
|
||||
assign_positional_args()
|
||||
{
|
||||
local _positional_name _shift_for=$1
|
||||
_positional_names="_arg_operation "
|
||||
|
||||
shift "$_shift_for"
|
||||
for _positional_name in ${_positional_names}
|
||||
do
|
||||
test $# -gt 0 || break
|
||||
eval "$_positional_name=\${1}" || die "Error during argument parsing, possibly an Argbash bug." 1
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
parse_commandline "$@"
|
||||
handle_passed_args_count
|
||||
assign_positional_args 1 "${_positionals[@]}"
|
||||
|
||||
# OTHER STUFF GENERATED BY Argbash
|
||||
|
||||
### END OF CODE GENERATED BY Argbash (sortof) ### ])
|
||||
# [ <-- needed because of Argbash
|
||||
|
||||
if [ -z ${_arg_mount_point} ]; then
|
||||
die "Missing '--mount-point' argument"
|
||||
fi
|
||||
|
||||
if [ -z ${_arg_device} ]; then
|
||||
die "Missing '--device' argument"
|
||||
fi
|
||||
|
||||
ASK_PIN=${_arg_ask_pin}
|
||||
OPERATION=${_arg_operation}
|
||||
DEVICE=${_arg_device}
|
||||
DEVICE_NAME=$(sed "s|/|_|g" <<< ${DEVICE})
|
||||
MOUNT_POINT=${_arg_mount_point}
|
||||
CREDENTIALS_TYPE=${_arg_credentials_type}
|
||||
SALT=${_arg_salt}
|
||||
CONF_FILE_PATH="/etc/fido2luksmounthelper.conf"
|
||||
|
||||
if [ "${OPERATION}" == "mount" ]; then
|
||||
if [ "${CREDENTIALS_TYPE}" == "external" ]; then
|
||||
if [ -f ${CONF_FILE_PATH} ]; then
|
||||
if [ "${ASK_PIN}" == "on" ]; then
|
||||
read PASSWORD
|
||||
fi
|
||||
CREDENTIALS=$(<${CONF_FILE_PATH})
|
||||
else
|
||||
die "The configuration file '${CONF_FILE_PATH}' is missing. Please create it or use embedded credentials."
|
||||
fi
|
||||
printf ${PASSWORD} | fido2luks open --salt string:${SALT} --pin --pin-source /dev/stdin ${DEVICE} ${DEVICE_NAME} ${CREDENTIALS}
|
||||
elif [ "${CREDENTIALS_TYPE}" == "embedded" ]; then
|
||||
if [ "${ASK_PIN}" == "on" ]; then
|
||||
read PASSWORD
|
||||
fi
|
||||
printf ${PASSWORD} | fido2luks open-token --salt string:${SALT} --pin --pin-source /dev/stdin ${DEVICE} ${DEVICE_NAME}
|
||||
else
|
||||
die "Given credential-type '${CREDENTIALS_TYPE}' is invalid. It must be 'external' or 'embedded'"
|
||||
fi
|
||||
mount /dev/mapper/${DEVICE_NAME} ${MOUNT_POINT}
|
||||
elif [ "${OPERATION}" == "umount" ]; then
|
||||
umount ${MOUNT_POINT}
|
||||
cryptsetup luksClose ${DEVICE_NAME}
|
||||
else
|
||||
die "Given operation '${OPERATION}' is invalid. It must be 'mount' or 'unmount'"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
# ] <-- needed because of Argbash
|
4
patch/cryptsetup-rs/.gitignore
vendored
4
patch/cryptsetup-rs/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
target/
|
||||
Cargo.lock
|
||||
*.iml
|
||||
.idea/
|
@ -1,22 +0,0 @@
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
sudo: required
|
||||
dist: trusty
|
||||
os:
|
||||
- linux
|
||||
script:
|
||||
- cargo build --all-targets --verbose
|
||||
- sudo /bin/sh -c "PATH=/home/travis/.cargo/bin:$PATH LD_LIBRARY_PATH=/home/travis/.cargo/lib cargo test --verbose"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcryptsetup-dev
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
@ -1,32 +0,0 @@
|
||||
[package]
|
||||
authors = ["Vladimir Lushnikov <vladimir@solidninja.is>"]
|
||||
description = "Rust wrapper around the libcryptsetup library, allowing manipulation of LUKS devices in Linux"
|
||||
homepage = "https://github.com/solidninja/cryptsetup-rs"
|
||||
license = "LGPL-3.0"
|
||||
name = "cryptsetup-rs"
|
||||
version = "0.2.0"
|
||||
|
||||
[dependencies]
|
||||
errno = "0.2.3"
|
||||
libc = "0.2.42"
|
||||
log = "0.4.2"
|
||||
|
||||
[dependencies.blkid-rs]
|
||||
path = "blkid-rs"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies.libcryptsetup-sys]
|
||||
path = "libcryptsetup-sys"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies.uuid]
|
||||
features = ["v4"]
|
||||
version = "0.6.5"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5.10"
|
||||
expectest = "0.10.0"
|
||||
tempdir = "0.3.7"
|
||||
|
||||
[lib]
|
||||
name = "cryptsetup_rs"
|
@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
@ -1,29 +0,0 @@
|
||||
[](https://travis-ci.org/solidninja/cryptsetup-rs)
|
||||
[](https://crates.io/crates/cryptsetup-rs)
|
||||
[](https://docs.rs/crate/cryptsetup-rs/)
|
||||
|
||||
# cryptsetup-rs - Rust bindings to `libcryptsetup` on Linux
|
||||
|
||||
A safe binding to `libcryptsetup` that allows working with encrypted disks on Linux.
|
||||
|
||||
Features:
|
||||
* High-level API for open/format/other operations
|
||||
|
||||
|
||||
Documentation for the bindings can be found on [docs.rs](https://docs.rs/crate/cryptsetup-rs/).
|
||||
|
||||
The example [`luks_dump.rs`](examples/luks_dump.rs) shows how a command like `cryptsetup luksDump` can
|
||||
be implemented.
|
||||
|
||||
## TODO
|
||||
|
||||
* Secure string for passing keys
|
||||
* High-level API for non-LUKS1 disks (truecrypt, verity)
|
||||
* LUKS2 and cryptsetup2 support
|
||||
|
||||
## Contributing
|
||||
|
||||
`cryptsetup-rs` is the work of its contributors and is a free software project licensed under the
|
||||
LGPLv3 or later.
|
||||
|
||||
If you would like to contribute, please follow the [C4](https://rfc.zeromq.org/spec:42/C4/) process.
|
2
patch/cryptsetup-rs/blkid-rs/.gitignore
vendored
2
patch/cryptsetup-rs/blkid-rs/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
target/
|
||||
Cargo.lock
|
@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "blkid-rs"
|
||||
version = "0.1.1"
|
||||
authors = ["Vladimir Lushnikov <vladimir@solidninja.is>"]
|
||||
description = "Rust implementation of blkid for LUKS volumes"
|
||||
license = "LGPL-3.0"
|
||||
homepage = "https://github.com/solidninja/libcryptset-rs"
|
||||
|
||||
[lib]
|
||||
name = "blkid_rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.1"
|
||||
uuid = "0.6"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.4"
|
||||
tempdir = "0.3"
|
@ -1,286 +0,0 @@
|
||||
// The following code has been ported from libcryptsetup
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate uuid;
|
||||
|
||||
use std::convert;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::mem;
|
||||
use std::str;
|
||||
|
||||
pub trait LuksHeader {
|
||||
fn version(&self) -> u16;
|
||||
fn cipher_name(&self) -> Result<&str, Error>;
|
||||
fn cipher_mode(&self) -> Result<&str, Error>;
|
||||
fn hash_spec(&self) -> Result<&str, Error>;
|
||||
fn payload_offset(&self) -> u32;
|
||||
fn key_bytes(&self) -> u32;
|
||||
fn mk_digest(&self) -> &[u8];
|
||||
fn mk_digest_salt(&self) -> &[u8];
|
||||
fn mk_digest_iterations(&self) -> u32;
|
||||
fn uuid(&self) -> Result<uuid::Uuid, Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidMagic,
|
||||
InvalidStringEncoding(str::Utf8Error),
|
||||
InvalidVersion,
|
||||
InvalidUuid(uuid::ParseError),
|
||||
ReadError(io::Error),
|
||||
ReadIncorrectHeaderSize,
|
||||
HeaderProcessingError,
|
||||
}
|
||||
|
||||
pub struct BlockDevice;
|
||||
|
||||
impl BlockDevice {
|
||||
pub fn read_luks_header<R: Read>(reader: R) -> Result<raw::luks_phdr, Error> {
|
||||
let luks_phdr_size = mem::size_of::<raw::luks_phdr>();
|
||||
let mut buf = Vec::<u8>::with_capacity(luks_phdr_size);
|
||||
let read_size = try!(reader.take(luks_phdr_size as u64).read_to_end(&mut buf));
|
||||
if read_size != luks_phdr_size {
|
||||
Err(Error::ReadIncorrectHeaderSize)
|
||||
} else {
|
||||
raw::luks_phdr::from_buf(&buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl convert::From<str::Utf8Error> for Error {
|
||||
fn from(error: str::Utf8Error) -> Error {
|
||||
Error::InvalidStringEncoding(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl convert::From<uuid::ParseError> for Error {
|
||||
fn from(error: uuid::ParseError) -> Error {
|
||||
Error::InvalidUuid(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl convert::From<io::Error> for Error {
|
||||
fn from(error: io::Error) -> Error {
|
||||
Error::ReadError(error)
|
||||
}
|
||||
}
|
||||
/* FIXME
|
||||
impl convert::From<byteorder::Error> for Error {
|
||||
fn from(error: byteorder::Error) -> Error {
|
||||
match error {
|
||||
byteorder::Error::UnexpectedEOF => Error::HeaderProcessingError,
|
||||
byteorder::Error::Io(io_error) => Error::ReadError(io_error),
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub mod raw {
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::convert::From;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::mem;
|
||||
use std::str;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use uuid;
|
||||
|
||||
const LUKS_VERSION_SUPPORTED: u16 = 1;
|
||||
|
||||
const LUKS_MAGIC_L: usize = 6;
|
||||
const LUKS_CIPHERNAME_L: usize = 32;
|
||||
const LUKS_CIPHERMODE_L: usize = 32;
|
||||
const LUKS_HASHSPEC_L: usize = 32;
|
||||
const LUKS_DIGESTSIZE: usize = 20;
|
||||
const LUKS_SALTSIZE: usize = 32;
|
||||
const UUID_STRING_L: usize = 40;
|
||||
|
||||
const LUKS_MAGIC: &'static [u8; LUKS_MAGIC_L] = b"LUKS\xba\xbe";
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct luks_phdr {
|
||||
pub magic: [u8; LUKS_MAGIC_L],
|
||||
pub version: u16,
|
||||
pub cipherName: [u8; LUKS_CIPHERNAME_L],
|
||||
pub cipherMode: [u8; LUKS_CIPHERMODE_L],
|
||||
pub hashSpec: [u8; LUKS_HASHSPEC_L],
|
||||
pub payloadOffset: u32,
|
||||
pub keyBytes: u32,
|
||||
pub mkDigest: [u8; LUKS_DIGESTSIZE],
|
||||
pub mkDigestSalt: [u8; LUKS_SALTSIZE],
|
||||
pub mkDigestIterations: u32,
|
||||
pub uuid: [u8; UUID_STRING_L],
|
||||
}
|
||||
|
||||
impl luks_phdr {
|
||||
pub fn from_buf(buf: &[u8]) -> Result<luks_phdr, super::Error> {
|
||||
// FIXME - this is not particularly pretty
|
||||
|
||||
if buf.len() != mem::size_of::<luks_phdr>() {
|
||||
return Err(super::Error::ReadIncorrectHeaderSize);
|
||||
}
|
||||
let mut cursor = Cursor::new(buf);
|
||||
let mut magic_buf = [0u8; LUKS_MAGIC_L];
|
||||
let magic_len = try!(cursor.read(&mut magic_buf));
|
||||
|
||||
if magic_len != LUKS_MAGIC_L || magic_buf != &LUKS_MAGIC[..] {
|
||||
return Err(super::Error::InvalidMagic);
|
||||
}
|
||||
|
||||
let version = try!(cursor.read_u16::<BigEndian>());
|
||||
if version != LUKS_VERSION_SUPPORTED {
|
||||
return Err(super::Error::InvalidVersion);
|
||||
}
|
||||
|
||||
let mut cipher_name_buf = [0u8; LUKS_CIPHERNAME_L];
|
||||
let cipher_name_len = try!(cursor.read(&mut cipher_name_buf));
|
||||
if cipher_name_len != LUKS_CIPHERNAME_L {
|
||||
return Err(super::Error::HeaderProcessingError);
|
||||
}
|
||||
|
||||
let mut cipher_mode_buf = [0u8; LUKS_CIPHERMODE_L];
|
||||
let cipher_mode_len = try!(cursor.read(&mut cipher_mode_buf));
|
||||
if cipher_mode_len != LUKS_CIPHERMODE_L {
|
||||
return Err(super::Error::HeaderProcessingError);
|
||||
}
|
||||
|
||||
let mut hash_spec_buf = [0u8; LUKS_HASHSPEC_L];
|
||||
let hash_spec_len = try!(cursor.read(&mut hash_spec_buf));
|
||||
if hash_spec_len != LUKS_HASHSPEC_L {
|
||||
return Err(super::Error::HeaderProcessingError);
|
||||
}
|
||||
|
||||
let payload_offset = try!(cursor.read_u32::<BigEndian>());
|
||||
let key_bytes = try!(cursor.read_u32::<BigEndian>());
|
||||
|
||||
let mut mk_digest_buf = [0u8; LUKS_DIGESTSIZE];
|
||||
let mk_digest_len = try!(cursor.read(&mut mk_digest_buf));
|
||||
if mk_digest_len != LUKS_DIGESTSIZE {
|
||||
return Err(super::Error::HeaderProcessingError);
|
||||
}
|
||||
|
||||
let mut mk_digest_salt_buf = [0u8; LUKS_SALTSIZE];
|
||||
let mk_digest_salt_len = try!(cursor.read(&mut mk_digest_salt_buf));
|
||||
if mk_digest_salt_len != LUKS_SALTSIZE {
|
||||
return Err(super::Error::HeaderProcessingError);
|
||||
}
|
||||
|
||||
let mk_digest_iterations = try!(cursor.read_u32::<BigEndian>());
|
||||
|
||||
let mut uuid_buf = [0u8; UUID_STRING_L];
|
||||
let uuid_len = try!(cursor.read(&mut uuid_buf));
|
||||
if uuid_len != UUID_STRING_L {
|
||||
return Err(super::Error::HeaderProcessingError);
|
||||
}
|
||||
|
||||
let res = luks_phdr {
|
||||
magic: magic_buf,
|
||||
version: version,
|
||||
cipherName: cipher_name_buf,
|
||||
cipherMode: cipher_mode_buf,
|
||||
hashSpec: hash_spec_buf,
|
||||
payloadOffset: payload_offset,
|
||||
keyBytes: key_bytes,
|
||||
mkDigest: mk_digest_buf,
|
||||
mkDigestSalt: mk_digest_salt_buf,
|
||||
mkDigestIterations: mk_digest_iterations,
|
||||
uuid: uuid_buf,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn u8_buf_to_str(buf: &[u8]) -> Result<&str, super::Error> {
|
||||
if let Some(pos) = buf.iter().position(|&c| c == 0) {
|
||||
str::from_utf8(&buf[0..pos]).map_err(From::from)
|
||||
} else {
|
||||
str::from_utf8(buf).map_err(From::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::LuksHeader for luks_phdr {
|
||||
fn version(&self) -> u16 {
|
||||
self.version
|
||||
}
|
||||
|
||||
fn cipher_name(&self) -> Result<&str, super::Error> {
|
||||
u8_buf_to_str(&self.cipherName)
|
||||
}
|
||||
|
||||
fn cipher_mode(&self) -> Result<&str, super::Error> {
|
||||
u8_buf_to_str(&self.cipherMode)
|
||||
}
|
||||
|
||||
fn hash_spec(&self) -> Result<&str, super::Error> {
|
||||
u8_buf_to_str(&self.hashSpec)
|
||||
}
|
||||
|
||||
fn payload_offset(&self) -> u32 {
|
||||
self.payloadOffset
|
||||
}
|
||||
|
||||
fn key_bytes(&self) -> u32 {
|
||||
self.keyBytes
|
||||
}
|
||||
|
||||
fn mk_digest(&self) -> &[u8] {
|
||||
&self.mkDigest
|
||||
}
|
||||
|
||||
fn mk_digest_salt(&self) -> &[u8] {
|
||||
&self.mkDigestSalt
|
||||
}
|
||||
|
||||
fn mk_digest_iterations(&self) -> u32 {
|
||||
self.mkDigestIterations
|
||||
}
|
||||
|
||||
fn uuid(&self) -> Result<uuid::Uuid, super::Error> {
|
||||
let uuid_str = try!(u8_buf_to_str(&self.uuid));
|
||||
uuid::Uuid::parse_str(uuid_str).map_err(From::from)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Cursor;
|
||||
use uuid;
|
||||
|
||||
#[test]
|
||||
fn test_luks_header_from_byte_buffer() {
|
||||
let header = b"LUKS\xba\xbe\x00\x01aes\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ecb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00sha256\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00 \xcf^\xb4\xc00q\xbe\xd5\xe6\x90\xc8G\xb3\x00\xbe\xba\xd052qp\x92\x0c\x9c\xa9\x07R\\y_D\x08b\xf1\xe6\x8f\x0c\xa95\xad\xdb\x15+\xa5\xd7\xa7\xbf^\x96B\x90z\x00\x00\x03\xe8a1b49d2d-8a7e-4b04-ab2a-89f3408fd198\x00\x00\x00\x00";
|
||||
let mut cursor: Cursor<&[u8]> = Cursor::new(header);
|
||||
let luks_header = BlockDevice::read_luks_header(&mut cursor).unwrap();
|
||||
|
||||
assert_eq!(luks_header.version(), 1);
|
||||
assert_eq!(luks_header.cipher_name().unwrap(), "aes");
|
||||
assert_eq!(luks_header.cipher_mode().unwrap(), "ecb");
|
||||
assert_eq!(luks_header.hash_spec().unwrap(), "sha256");
|
||||
assert_eq!(luks_header.payload_offset(), 4096);
|
||||
assert_eq!(luks_header.key_bytes(), 32);
|
||||
assert_eq!(
|
||||
luks_header.mk_digest(),
|
||||
&[
|
||||
207, 94, 180, 192, 48, 113, 190, 213, 230, 144, 200, 71, 179, 0, 190, 186, 208, 53,
|
||||
50, 113
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
luks_header.mk_digest_salt(),
|
||||
&[
|
||||
112, 146, 12, 156, 169, 7, 82, 92, 121, 95, 68, 8, 98, 241, 230, 143, 12, 169, 53,
|
||||
173, 219, 21, 43, 165, 215, 167, 191, 94, 150, 66, 144, 122
|
||||
]
|
||||
);
|
||||
assert_eq!(luks_header.mk_digest_iterations(), 1000);
|
||||
assert_eq!(
|
||||
luks_header.uuid().unwrap(),
|
||||
uuid::Uuid::parse_str("a1b49d2d-8a7e-4b04-ab2a-89f3408fd198").unwrap()
|
||||
)
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
#![deny(warnings)]
|
||||
#[warn(unused_must_use)]
|
||||
extern crate cryptsetup_rs;
|
||||
extern crate env_logger;
|
||||
|
||||
use cryptsetup_rs::*;
|
||||
use std::env;
|
||||
|
||||
fn dump_slot(crypt_device: &Luks1CryptDevice, slot: Keyslot) -> Result<()> {
|
||||
let status = match crypt_device.keyslot_status(slot) {
|
||||
crypt_keyslot_info::CRYPT_SLOT_INVALID => "INVALID",
|
||||
crypt_keyslot_info::CRYPT_SLOT_INACTIVE => "DISABLED",
|
||||
crypt_keyslot_info::CRYPT_SLOT_ACTIVE | crypt_keyslot_info::CRYPT_SLOT_ACTIVE_LAST => "ENABLED",
|
||||
};
|
||||
|
||||
println!("Key Slot {}: {}", slot, status);
|
||||
match status {
|
||||
"ENABLED" => /* TODO add keyslot information */ (),
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dump(device_path: &str) -> Result<()> {
|
||||
let dev = open(device_path)?.luks1()?;
|
||||
|
||||
println!("LUKS header information for {}", dev.device_name());
|
||||
println!();
|
||||
println!("{:<16}{}", "Version:", "1");
|
||||
println!("{:<16}{}", "Cipher name:", dev.cipher());
|
||||
println!("{:<16}{}", "Cipher mode:", dev.cipher_mode());
|
||||
println!("{:<16}{}", "Hash spec:", dev.hash_spec());
|
||||
println!("{:<16}{}", "Payload offset:", dev.payload_offset());
|
||||
println!("{:<16}{}", "MK bits:", dev.mk_bits());
|
||||
|
||||
print!("{:<16}", "MK digest:");
|
||||
for b in dev.mk_digest().iter() {
|
||||
print!("{:x} ", b);
|
||||
}
|
||||
println!();
|
||||
|
||||
let salt = dev.mk_salt();
|
||||
print!("{:<16}", "MK salt:");
|
||||
for b in salt[0..16].iter() {
|
||||
print!("{:x} ", b);
|
||||
}
|
||||
println!();
|
||||
print!("{:<16}", "");
|
||||
for b in salt[16..32].iter() {
|
||||
print!("{:x} ", b);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("{:<16}{}", "MK iterations:", dev.mk_iterations());
|
||||
println!("{:<16}{}", "UUID:", dev.uuid());
|
||||
|
||||
println!();
|
||||
|
||||
for slot in 0..8 {
|
||||
dump_slot(&dev, slot)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
if args.len() != 1 {
|
||||
println!("Usage: luks_dump <device path>");
|
||||
::std::process::exit(1);
|
||||
}
|
||||
let device_path = args[0].as_str();
|
||||
|
||||
if let Err(e) = dump(device_path) {
|
||||
println!("Error: {:?}", e);
|
||||
::std::process::exit(2);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "libcryptsetup-sys"
|
||||
version = "0.1.1"
|
||||
authors = ["Vladimir Lushnikov <vladimir@solidninja.is>"]
|
||||
license = "LGPL-3.0"
|
||||
description = "FFI bindings to the libcryptsetup library"
|
||||
homepage = "https://github.com/solidninja/cryptsetup-rs"
|
||||
|
||||
links = "cryptsetup"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "libcryptsetup_sys"
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3"
|
||||
|
@ -1,8 +0,0 @@
|
||||
extern crate pkg_config;
|
||||
|
||||
fn main() {
|
||||
pkg_config::Config::new()
|
||||
.statik(true)
|
||||
.find("libcryptsetup")
|
||||
.unwrap();
|
||||
}
|
@ -1,492 +0,0 @@
|
||||
#![deny(warnings)]
|
||||
#![allow(non_camel_case_types)]
|
||||
extern crate libc;
|
||||
|
||||
use libc::{c_char, c_double, c_int, c_uint, c_void, size_t};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub enum crypt_device {}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_log_level {
|
||||
CRYPT_LOG_NORMAL = 0,
|
||||
CRYPT_LOG_ERROR = 1,
|
||||
CRYPT_LOG_VERBOSE = 2,
|
||||
CRYPT_LOG_DEBUG = -1,
|
||||
}
|
||||
|
||||
pub type crypt_log_cb = extern "C" fn(crypt_log_level, *const c_char, *mut c_void);
|
||||
pub type crypt_confirm_cb = extern "C" fn(*const c_char, *mut c_void) -> c_int;
|
||||
pub type crypt_password_cb =
|
||||
extern "C" fn(*const c_char, *mut c_char, size_t, *mut c_void) -> c_int;
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_rng_type {
|
||||
CRYPT_RNG_URANDOM = 0,
|
||||
CRYPT_RNG_RANDOM = 1,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_device_type {
|
||||
PLAIN,
|
||||
LUKS1,
|
||||
LOOPAES,
|
||||
VERITY,
|
||||
TCRYPT,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct crypt_params_plain {
|
||||
pub hash: *const c_char,
|
||||
pub offset: u64,
|
||||
pub skip: u64,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct crypt_params_luks1 {
|
||||
pub hash: *const c_char,
|
||||
pub data_alignment: size_t,
|
||||
pub data_device: *const c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct crypt_params_loopaes {
|
||||
pub hash: *const c_char,
|
||||
pub offset: u64,
|
||||
pub skip: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct crypt_params_verity {
|
||||
pub hash_name: *const c_char,
|
||||
pub data_device: *const c_char,
|
||||
pub hash_device: *const c_char,
|
||||
pub salt: *const c_char,
|
||||
pub salt_size: u32,
|
||||
pub hash_type: u32,
|
||||
pub data_block_size: u32,
|
||||
pub hash_block_size: u32,
|
||||
pub data_size: u64,
|
||||
pub hash_area_offset: u64,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_verity_flag {
|
||||
CRYPT_VERITY_NO_HEADER = (1 << 0),
|
||||
CRYPT_VERITY_CHECK_HASH = (1 << 1),
|
||||
CRYPT_VERITY_CREATE_HASH = (1 << 2),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct crypt_params_tcrypt {
|
||||
pub passphrase: *const c_char,
|
||||
pub passphrase_size: size_t,
|
||||
pub keyfiles: *const *const c_char,
|
||||
pub keyfiles_count: c_uint,
|
||||
pub hash_name: *const c_char,
|
||||
pub cipher: *const c_char,
|
||||
pub mode: *const c_char,
|
||||
pub key_size: size_t,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_tcrypt_flag {
|
||||
CRYPT_TCRYPT_LEGACY_MODES = (1 << 0),
|
||||
CRYPT_TCRYPT_HIDDEN_HEADER = (1 << 1),
|
||||
CRYPT_TCRYPT_BACKUP_HEADER = (1 << 2),
|
||||
CRYPT_TCRYPT_SYSTEM_HEADER = (1 << 3),
|
||||
CRYPT_TCRYPT_VERA_MODES = (1 << 4),
|
||||
}
|
||||
|
||||
pub const CRYPT_ANY_SLOT: c_int = -1;
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_activation_flag {
|
||||
CRYPT_ACTIVATE_READONLY = (1 << 0),
|
||||
CRYPT_ACTIVATE_NO_UUID = (1 << 1),
|
||||
CRYPT_ACTIVATE_SHARED = (1 << 2),
|
||||
CRYPT_ACTIVATE_ALLOW_DISCARDS = (1 << 3),
|
||||
CRYPT_ACTIVATE_PRIVATE = (1 << 4),
|
||||
CRYPT_ACTIVATE_CORRUPTED = (1 << 5),
|
||||
CRYPT_ACTIVATE_SAME_CPU_CRYPT = (1 << 6),
|
||||
CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS = (1 << 7),
|
||||
CRYPT_ACTIVATE_IGNORE_CORRUPTION = (1 << 8),
|
||||
CRYPT_ACTIVATE_RESTART_ON_CORRUPTION = (1 << 9),
|
||||
CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS = (1 << 10),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct crypt_active_device {
|
||||
pub offset: u64,
|
||||
pub iv_offset: u64,
|
||||
pub size: u64,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_status_info {
|
||||
CRYPT_INVALID,
|
||||
CRYPT_INACTIVE,
|
||||
CRYPT_ACTIVE,
|
||||
CRYPT_BUSY,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_keyslot_info {
|
||||
CRYPT_SLOT_INVALID,
|
||||
CRYPT_SLOT_INACTIVE,
|
||||
CRYPT_SLOT_ACTIVE,
|
||||
CRYPT_SLOT_ACTIVE_LAST,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum crypt_debug_level {
|
||||
CRYPT_DEBUG_ALL = -1,
|
||||
CRYPT_DEBUG_NONE = 0,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn crypt_init(cd: *mut *mut crypt_device, device: *const c_char) -> c_int;
|
||||
pub fn crypt_init_by_name_and_header(
|
||||
cd: *mut *mut crypt_device,
|
||||
name: *const c_char,
|
||||
header_device: *const c_char,
|
||||
) -> c_int;
|
||||
pub fn crypt_init_by_name(cd: *mut *mut crypt_device, name: *const c_char) -> c_int;
|
||||
|
||||
pub fn crypt_set_log_callback(
|
||||
cd: *mut crypt_device,
|
||||
log: Option<crypt_log_cb>,
|
||||
usrptr: *mut c_void,
|
||||
);
|
||||
pub fn crypt_log(cd: *mut crypt_device, level: crypt_log_level, msg: *const c_char);
|
||||
|
||||
pub fn crypt_set_confirm_callback(
|
||||
cd: *mut crypt_device,
|
||||
confirm: crypt_confirm_cb,
|
||||
usrptr: *mut c_void,
|
||||
);
|
||||
#[deprecated]
|
||||
pub fn crypt_set_password_callback(
|
||||
cd: *mut crypt_device,
|
||||
password: crypt_password_cb,
|
||||
usrptr: *mut c_void,
|
||||
);
|
||||
#[deprecated]
|
||||
pub fn crypt_set_timeout(cd: *mut crypt_device, timeout: u64);
|
||||
#[deprecated]
|
||||
pub fn crypt_set_password_retry(cd: *mut crypt_device, tries: c_int);
|
||||
pub fn crypt_set_iteration_time(cd: *mut crypt_device, iteration_time_ms: u64);
|
||||
#[deprecated]
|
||||
pub fn crypt_set_password_verify(cd: *mut crypt_device, password_verify: c_int);
|
||||
pub fn crypt_set_data_device(cd: *mut crypt_device, device: *const c_char) -> c_int;
|
||||
|
||||
pub fn crypt_set_rng_type(cd: *mut crypt_device, rng_type: crypt_rng_type);
|
||||
pub fn crypt_get_rng_type(cd: *mut crypt_device) -> c_int;
|
||||
|
||||
pub fn crypt_memory_lock(cd: *mut crypt_device, lock: c_int) -> c_int;
|
||||
|
||||
pub fn crypt_get_type(cd: *mut crypt_device) -> *const c_char;
|
||||
|
||||
pub fn crypt_format(
|
||||
cd: *mut crypt_device,
|
||||
crypt_type: *const c_char,
|
||||
cipher: *const c_char,
|
||||
cipher_mode: *const c_char,
|
||||
uuid: *const c_char,
|
||||
volume_key: *const c_char,
|
||||
volume_key_size: size_t,
|
||||
params: *mut c_void,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_set_uuid(cd: *mut crypt_device, uuid: *const c_char) -> c_int;
|
||||
|
||||
pub fn crypt_load(
|
||||
cd: *mut crypt_device,
|
||||
requested_type: *const c_char,
|
||||
params: *mut c_void,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_repair(
|
||||
cd: *mut crypt_device,
|
||||
requested_type: *const c_char,
|
||||
params: *mut c_void,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_resize(cd: *mut crypt_device, name: *const c_char, new_size: u64) -> c_int;
|
||||
|
||||
pub fn crypt_suspend(cd: *mut crypt_device, name: *const c_char) -> c_int;
|
||||
|
||||
pub fn crypt_resume_by_passphrase(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
keyslot: c_int,
|
||||
passphrase: *const c_char,
|
||||
passphrase_size: size_t,
|
||||
) -> c_int;
|
||||
pub fn crypt_resume_by_keyfile_offset(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
keyslot: c_int,
|
||||
keyfile: *const c_char,
|
||||
keyfile_size: size_t,
|
||||
keyfile_offset: size_t,
|
||||
) -> c_int;
|
||||
pub fn crypt_resume_by_keyfile(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
keyslot: c_int,
|
||||
keyfile: *const c_char,
|
||||
keyfile_size: size_t,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_free(cd: *mut crypt_device);
|
||||
|
||||
pub fn crypt_keyslot_add_by_passphrase(
|
||||
cd: *mut crypt_device,
|
||||
keyslot: c_int,
|
||||
passphrase: *const c_char,
|
||||
passphrase_size: size_t,
|
||||
new_passphrase: *const c_char,
|
||||
new_passphrase_size: size_t,
|
||||
) -> c_int;
|
||||
pub fn crypt_keyslot_change_by_passphrase(
|
||||
cd: *mut crypt_device,
|
||||
keyslot_old: c_int,
|
||||
keyslot_new: c_int,
|
||||
passphrase: *const c_char,
|
||||
passphrase_size: size_t,
|
||||
new_passphrase: *const c_char,
|
||||
new_passphrase_size: size_t,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_keyslot_add_by_keyfile_offset(
|
||||
cd: *mut crypt_device,
|
||||
keyslot: c_int,
|
||||
keyfile: *const c_char,
|
||||
keyfile_size: size_t,
|
||||
keyfile_offset: size_t,
|
||||
new_keyfile: *const c_char,
|
||||
new_keyfile_size: size_t,
|
||||
new_keyfile_offset: size_t,
|
||||
) -> c_int;
|
||||
pub fn crypt_keyslot_add_by_keyfile(
|
||||
cd: *mut crypt_device,
|
||||
keyslot: c_int,
|
||||
keyfile: *const c_char,
|
||||
keyfile_size: size_t,
|
||||
new_keyfile: *const c_char,
|
||||
new_keyfile_size: size_t,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_keyslot_add_by_volume_key(
|
||||
cd: *mut crypt_device,
|
||||
keyslot: c_int,
|
||||
volume_key: *const c_char,
|
||||
volume_key_size: size_t,
|
||||
passphrase: *const c_char,
|
||||
passphrase_size: size_t,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_keyslot_destroy(cd: *mut crypt_device, keyslot: c_int) -> c_int;
|
||||
|
||||
pub fn crypt_get_active_device(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
cad: *mut crypt_active_device,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_activate_by_passphrase(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
keyslot: c_int,
|
||||
passphrase: *const c_char,
|
||||
passphrase_size: size_t,
|
||||
flags: u32,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_activate_by_keyfile_offset(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
keyslot: c_int,
|
||||
keyfile: *const c_char,
|
||||
keyfile_size: size_t,
|
||||
keyfile_offset: size_t,
|
||||
flags: u32,
|
||||
) -> c_int;
|
||||
pub fn crypt_activate_by_keyfile(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
keyslot: c_int,
|
||||
keyfile: *const c_char,
|
||||
keyfile_size: size_t,
|
||||
flags: u32,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_activate_by_volume_key(
|
||||
cd: *mut crypt_device,
|
||||
name: *const c_char,
|
||||
volume_key: *const c_char,
|
||||
volume_key_size: size_t,
|
||||
flags: u32,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_deactivate(cd: *mut crypt_device, name: *const c_char) -> c_int;
|
||||
|
||||
pub fn crypt_volume_key_get(
|
||||
cd: *mut crypt_device,
|
||||
keyslot: c_int,
|
||||
volume_key: *mut c_char,
|
||||
volume_key_size: *mut size_t,
|
||||
passphrase: *const c_char,
|
||||
passphrase_size: size_t,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_volume_key_verify(
|
||||
cd: *mut crypt_device,
|
||||
volume_key: *const c_char,
|
||||
volume_key_size: size_t,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_status(cd: *mut crypt_device, name: *const c_char) -> crypt_status_info;
|
||||
|
||||
pub fn crypt_dump(cd: *mut crypt_device) -> c_int;
|
||||
|
||||
pub fn crypt_get_cipher(cd: *mut crypt_device) -> *const c_char;
|
||||
pub fn crypt_get_cipher_mode(cd: *mut crypt_device) -> *const c_char;
|
||||
pub fn crypt_get_uuid(cd: *mut crypt_device) -> *const c_char;
|
||||
pub fn crypt_get_device_name(cd: *mut crypt_device) -> *const c_char;
|
||||
pub fn crypt_get_data_offset(cd: *mut crypt_device) -> u64;
|
||||
pub fn crypt_get_iv_offset(cd: *mut crypt_device) -> u64;
|
||||
pub fn crypt_get_volume_key_size(cd: *mut crypt_device) -> c_int;
|
||||
pub fn crypt_get_verity_info(cd: *mut crypt_device, vp: *mut crypt_params_verity);
|
||||
|
||||
pub fn crypt_benchmark(
|
||||
cd: *mut crypt_device,
|
||||
cipher: *const c_char,
|
||||
cipher_mode: *const c_char,
|
||||
volume_key_size: size_t,
|
||||
iv_size: size_t,
|
||||
buffer_size: size_t,
|
||||
encryption_mbs: *mut c_double,
|
||||
decryption_mbs: *mut c_double,
|
||||
) -> c_int;
|
||||
pub fn crypt_benchmark_kdf(
|
||||
cd: *mut crypt_device,
|
||||
kdf: *const c_char,
|
||||
hash: *const c_char,
|
||||
password: *const c_char,
|
||||
password_size: size_t,
|
||||
salt: *const c_char,
|
||||
salt_size: size_t,
|
||||
iterations_sec: *mut u64,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_keyslot_status(cd: *mut crypt_device, keyslot: c_int) -> crypt_keyslot_info;
|
||||
|
||||
pub fn crypt_keyslot_max(crypt_device_type: *const c_char) -> c_int;
|
||||
|
||||
pub fn crypt_keyslot_area(
|
||||
cd: *mut crypt_device,
|
||||
keyslot: c_int,
|
||||
offset: *mut u64,
|
||||
length: *mut u64,
|
||||
) -> c_int;
|
||||
|
||||
pub fn crypt_header_backup(
|
||||
cd: *mut crypt_device,
|
||||
requested_type: *const c_char,
|
||||
backup_file: *const c_char,
|
||||
) -> c_int;
|
||||
pub fn crypt_header_restore(
|
||||
cd: *mut crypt_device,
|
||||
requested_type: *const c_char,
|
||||
backup_file: *const c_char,
|
||||
) -> c_int;
|
||||
|
||||
#[deprecated]
|
||||
pub fn crypt_last_error(cd: *mut crypt_device, buf: *mut c_char, size: size_t);
|
||||
#[deprecated]
|
||||
pub fn crypt_get_error(buf: *mut c_char, size: size_t);
|
||||
|
||||
pub fn crypt_get_dir() -> *const c_char;
|
||||
|
||||
pub fn crypt_set_debug_level(level: crypt_debug_level);
|
||||
}
|
||||
|
||||
impl FromStr for crypt_device_type {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<crypt_device_type, ()> {
|
||||
match s {
|
||||
"PLAIN" => Ok(crypt_device_type::PLAIN),
|
||||
"LUKS1" => Ok(crypt_device_type::LUKS1),
|
||||
"LOOPAES" => Ok(crypt_device_type::LOOPAES),
|
||||
"VERITY" => Ok(crypt_device_type::VERITY),
|
||||
"TCRYPT" => Ok(crypt_device_type::TCRYPT),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crypt_device_type {
|
||||
pub fn to_str(&self) -> &str {
|
||||
match self {
|
||||
&crypt_device_type::PLAIN => "PLAIN",
|
||||
&crypt_device_type::LUKS1 => "LUKS1",
|
||||
&crypt_device_type::LOOPAES => "LOOPAES",
|
||||
&crypt_device_type::VERITY => "VERITY",
|
||||
&crypt_device_type::TCRYPT => "TCRYPT",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CString;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_device_type_conversion() {
|
||||
assert_eq!(
|
||||
Ok(crypt_device_type::PLAIN),
|
||||
crypt_device_type::from_str("PLAIN")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(crypt_device_type::LUKS1),
|
||||
crypt_device_type::from_str("LUKS1")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(crypt_device_type::LOOPAES),
|
||||
crypt_device_type::from_str("LOOPAES")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(crypt_device_type::VERITY),
|
||||
crypt_device_type::from_str("VERITY")
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(crypt_device_type::TCRYPT),
|
||||
crypt_device_type::from_str("TCRYPT")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyslot_max_gt_zero() {
|
||||
unsafe {
|
||||
let luks_type = CString::new("LUKS1").unwrap();
|
||||
assert!(crypt_keyslot_max(luks_type.as_ptr()) > 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
max_width = 120
|
||||
format_strings = false
|
@ -1,377 +0,0 @@
|
||||
//! High-level API to work with `libcryptsetup` supported devices (disks)
|
||||
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
|
||||
use blkid_rs::{BlockDevice, LuksHeader};
|
||||
|
||||
use device;
|
||||
pub use device::enable_debug;
|
||||
use device::RawDevice;
|
||||
pub use device::{Error, Keyslot, Result};
|
||||
use raw;
|
||||
use uuid;
|
||||
|
||||
pub type Luks1CryptDeviceHandle = CryptDeviceHandle<Luks1Params>;
|
||||
|
||||
/// Builder to open a crypt device at the specified path
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use cryptsetup_rs::*;
|
||||
/// # fn foo() -> Result<()> {
|
||||
/// let device = open("/dev/loop0")?.luks1()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<CryptDeviceOpenBuilder> {
|
||||
let cd = device::init(path.as_ref())?;
|
||||
Ok(CryptDeviceOpenBuilder {
|
||||
path: path.as_ref().to_owned(),
|
||||
cd,
|
||||
})
|
||||
}
|
||||
|
||||
/// Builder to format a crypt device at the specified path
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate uuid;
|
||||
/// # extern crate cryptsetup_rs;
|
||||
/// use cryptsetup_rs::*;
|
||||
/// use uuid::Uuid;
|
||||
///
|
||||
/// # fn foo() -> Result<()> {
|
||||
/// let uuid = Uuid::new_v4();
|
||||
/// let device = format("/dev/loop0")?
|
||||
/// .rng_type(crypt_rng_type::CRYPT_RNG_URANDOM)
|
||||
/// .iteration_time(5000)
|
||||
/// .luks1("aes", "xts-plain", "sha256", 256, Some(&uuid))?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn format<P: AsRef<Path>>(path: P) -> Result<CryptDeviceFormatBuilder> {
|
||||
let cd = device::init(path.as_ref())?;
|
||||
Ok(CryptDeviceFormatBuilder {
|
||||
path: path.as_ref().to_owned(),
|
||||
cd,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read the UUID of a LUKS1 container without opening the device
|
||||
pub fn luks1_uuid<P: AsRef<Path>>(path: P) -> Result<uuid::Uuid> {
|
||||
let device_file = File::open(path.as_ref())?;
|
||||
let luks_phdr = BlockDevice::read_luks_header(device_file)?;
|
||||
let uuid = luks_phdr.uuid()?;
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
fn load_luks1_params<P: AsRef<Path>>(path: P) -> Result<Luks1Params> {
|
||||
let device_file = File::open(path.as_ref())?;
|
||||
let luks_phdr = BlockDevice::read_luks_header(device_file)?;
|
||||
Luks1Params::from(luks_phdr)
|
||||
}
|
||||
|
||||
/// Struct containing state for the `open()` builder
|
||||
pub struct CryptDeviceOpenBuilder {
|
||||
path: PathBuf,
|
||||
cd: RawDevice,
|
||||
}
|
||||
|
||||
impl CryptDeviceOpenBuilder {
|
||||
/// Loads an existing LUKS1 crypt device
|
||||
pub fn luks1(self: CryptDeviceOpenBuilder) -> Result<CryptDeviceHandle<Luks1Params>> {
|
||||
let _ = device::load(&self.cd, raw::crypt_device_type::LUKS1);
|
||||
let params = load_luks1_params(&self.path)?;
|
||||
Ok(CryptDeviceHandle {
|
||||
cd: self.cd,
|
||||
path: self.path,
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct containing state for the `format()` builder
|
||||
pub struct CryptDeviceFormatBuilder {
|
||||
path: PathBuf,
|
||||
cd: RawDevice,
|
||||
}
|
||||
|
||||
impl CryptDeviceFormatBuilder {
|
||||
/// Set the iteration time for the `PBKDF2` function. Note that this does not affect the MK iterations.
|
||||
pub fn iteration_time(mut self, iteration_time_ms: u64) -> Self {
|
||||
device::set_iteration_time(&mut self.cd, iteration_time_ms);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the random number generator to use
|
||||
pub fn rng_type(mut self, rng_type: raw::crypt_rng_type) -> Self {
|
||||
device::set_rng_type(&mut self.cd, rng_type);
|
||||
self
|
||||
}
|
||||
|
||||
/// Formats a new block device as a LUKS1 crypt device with the specified parameters
|
||||
pub fn luks1(
|
||||
mut self: CryptDeviceFormatBuilder,
|
||||
cipher: &str,
|
||||
cipher_mode: &str,
|
||||
hash: &str,
|
||||
mk_bits: usize,
|
||||
maybe_uuid: Option<&uuid::Uuid>,
|
||||
) -> Result<CryptDeviceHandle<Luks1Params>> {
|
||||
let _ = device::luks1_format(&mut self.cd, cipher, cipher_mode, hash, mk_bits, maybe_uuid)?;
|
||||
let params = load_luks1_params(&self.path)?;
|
||||
Ok(CryptDeviceHandle {
|
||||
cd: self.cd,
|
||||
path: self.path,
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing common operations on a crypt device
|
||||
pub trait CryptDevice {
|
||||
/// Path the device was opened/created with
|
||||
fn path(&self) -> &Path;
|
||||
|
||||
/// Name of cipher used
|
||||
fn cipher(&self) -> &str;
|
||||
|
||||
/// Name of cipher mode used
|
||||
fn cipher_mode(&self) -> &str;
|
||||
|
||||
/// Path to the underlying device (as reported by `libcryptsetup`)
|
||||
fn device_name(&self) -> &str;
|
||||
|
||||
/// Random number generator used for operations on this crypt device
|
||||
fn rng_type(&self) -> raw::crypt_rng_type;
|
||||
|
||||
/// Sets the random number generator to use
|
||||
fn set_rng_type(&mut self, rng_type: raw::crypt_rng_type);
|
||||
|
||||
/// Sets the iteration time for the `PBKDF2` function. Note that this does not affect the MK iterations.
|
||||
fn set_iteration_time(&mut self, iteration_time_ms: u64);
|
||||
|
||||
/// Volume key size (in bytes)
|
||||
fn volume_key_size(&self) -> u8;
|
||||
}
|
||||
|
||||
/// Trait for querying the device type at runtime
|
||||
pub trait CryptDeviceType {
|
||||
/// Type of the crypt device
|
||||
fn device_type(&self) -> raw::crypt_device_type;
|
||||
}
|
||||
|
||||
/// Trait representing specific operations on a LUKS1 device
|
||||
pub trait Luks1CryptDevice {
|
||||
/// Activate the crypt device, and give it the specified name
|
||||
fn activate(&mut self, name: &str, key: &[u8]) -> Result<Keyslot>;
|
||||
|
||||
/// Add a new keyslot with the specified key
|
||||
fn add_keyslot(
|
||||
&mut self,
|
||||
key: &[u8],
|
||||
maybe_prev_key: Option<&[u8]>,
|
||||
maybe_keyslot: Option<Keyslot>,
|
||||
) -> Result<Keyslot>;
|
||||
|
||||
/// Replace an old key with a new one
|
||||
fn update_keyslot(&mut self, key: &[u8], prev_key: &[u8], maybe_keyslot: Option<Keyslot>) -> Result<Keyslot>;
|
||||
|
||||
/// Destroy (and disable) key slot
|
||||
fn destroy_keyslot(&mut self, slot: Keyslot) -> Result<()>;
|
||||
|
||||
/// Dump text-formatted information about the current device to stdout
|
||||
fn dump(&self);
|
||||
|
||||
/// Get the hash algorithm used
|
||||
fn hash_spec(&self) -> &str;
|
||||
|
||||
/// Get status of key slot
|
||||
fn keyslot_status(&self, keyslot: Keyslot) -> raw::crypt_keyslot_info;
|
||||
|
||||
/// Number of bits in the master key
|
||||
fn mk_bits(&self) -> u32;
|
||||
|
||||
/// Master key header digest
|
||||
fn mk_digest(&self) -> &[u8; 20];
|
||||
|
||||
/// Master key `PBKDF2` iterations
|
||||
fn mk_iterations(&self) -> u32;
|
||||
|
||||
/// Master key salt
|
||||
fn mk_salt(&self) -> &[u8; 32];
|
||||
|
||||
/// Get the offset of the payload
|
||||
fn payload_offset(&self) -> u32;
|
||||
|
||||
/// UUID of the current device
|
||||
fn uuid(&self) -> uuid::Uuid;
|
||||
}
|
||||
|
||||
/// An opaque handle on an initialized crypt device
|
||||
#[derive(PartialEq)]
|
||||
pub struct CryptDeviceHandle<P: fmt::Debug> {
|
||||
/// Pointer to the raw device
|
||||
cd: RawDevice,
|
||||
|
||||
/// Path to the crypt device (useful for diagnostics)
|
||||
path: PathBuf,
|
||||
|
||||
/// Additional parameters depending on type of crypt device opened
|
||||
params: P,
|
||||
}
|
||||
|
||||
impl<P: fmt::Debug> fmt::Debug for CryptDeviceHandle<P> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"CryptDeviceHandle(path={}, raw={:p}, params={:?})",
|
||||
self.path.display(),
|
||||
self.cd,
|
||||
self.params
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: fmt::Debug> Drop for CryptDeviceHandle<P> {
|
||||
fn drop(&mut self) {
|
||||
device::free(&mut self.cd);
|
||||
self.cd = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: fmt::Debug> CryptDevice for CryptDeviceHandle<P> {
|
||||
fn path(&self) -> &Path {
|
||||
self.path.as_ref()
|
||||
}
|
||||
|
||||
fn cipher(&self) -> &str {
|
||||
device::cipher(&self.cd).expect("Initialised device should have cipher")
|
||||
}
|
||||
|
||||
fn cipher_mode(&self) -> &str {
|
||||
device::cipher_mode(&self.cd).expect("Initialised device should have cipher mode")
|
||||
}
|
||||
|
||||
fn device_name(&self) -> &str {
|
||||
device::device_name(&self.cd).expect("Initialised device should have an underlying path")
|
||||
}
|
||||
|
||||
fn rng_type(&self) -> raw::crypt_rng_type {
|
||||
device::rng_type(&self.cd)
|
||||
}
|
||||
|
||||
fn set_rng_type(&mut self, rng_type: raw::crypt_rng_type) {
|
||||
device::set_rng_type(&mut self.cd, rng_type)
|
||||
}
|
||||
|
||||
fn set_iteration_time(&mut self, iteration_time_ms: u64) {
|
||||
device::set_iteration_time(&mut self.cd, iteration_time_ms)
|
||||
}
|
||||
|
||||
fn volume_key_size(&self) -> u8 {
|
||||
device::volume_key_size(&self.cd)
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct for storing LUKS1 parameters in memory
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Luks1Params {
|
||||
hash_spec: String,
|
||||
payload_offset: u32,
|
||||
mk_bits: u32,
|
||||
mk_digest: [u8; 20],
|
||||
mk_salt: [u8; 32],
|
||||
mk_iterations: u32,
|
||||
}
|
||||
|
||||
impl Luks1Params {
|
||||
fn from(header: impl LuksHeader) -> Result<Luks1Params> {
|
||||
let hash_spec = header.hash_spec()?.to_owned();
|
||||
let payload_offset = header.payload_offset();
|
||||
let mk_bits = header.key_bytes() * 8;
|
||||
let mut mk_digest = [0u8; 20];
|
||||
mk_digest.copy_from_slice(header.mk_digest());
|
||||
let mut mk_salt = [0u8; 32];
|
||||
mk_salt.copy_from_slice(header.mk_digest_salt());
|
||||
let mk_iterations = header.mk_digest_iterations();
|
||||
Ok(Luks1Params {
|
||||
hash_spec,
|
||||
payload_offset,
|
||||
mk_bits,
|
||||
mk_digest,
|
||||
mk_salt,
|
||||
mk_iterations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Luks1CryptDevice for CryptDeviceHandle<Luks1Params> {
|
||||
fn activate(&mut self, name: &str, key: &[u8]) -> Result<Keyslot> {
|
||||
device::luks_activate(&mut self.cd, name, key)
|
||||
}
|
||||
|
||||
fn add_keyslot(
|
||||
&mut self,
|
||||
key: &[u8],
|
||||
maybe_prev_key: Option<&[u8]>,
|
||||
maybe_keyslot: Option<Keyslot>,
|
||||
) -> Result<Keyslot> {
|
||||
device::luks_add_keyslot(&mut self.cd, key, maybe_prev_key, maybe_keyslot)
|
||||
}
|
||||
|
||||
fn update_keyslot(&mut self, key: &[u8], prev_key: &[u8], maybe_keyslot: Option<Keyslot>) -> Result<Keyslot> {
|
||||
device::luks_update_keyslot(&mut self.cd, key, prev_key, maybe_keyslot)
|
||||
}
|
||||
|
||||
fn destroy_keyslot(&mut self, slot: Keyslot) -> Result<()> {
|
||||
device::luks_destroy_keyslot(&mut self.cd, slot)
|
||||
}
|
||||
|
||||
fn dump(&self) {
|
||||
device::dump(&self.cd).expect("Dump should be fine for initialised device")
|
||||
}
|
||||
|
||||
fn hash_spec(&self) -> &str {
|
||||
self.params.hash_spec.as_ref()
|
||||
}
|
||||
|
||||
fn keyslot_status(&self, keyslot: Keyslot) -> raw::crypt_keyslot_info {
|
||||
device::keyslot_status(&self.cd, keyslot)
|
||||
}
|
||||
|
||||
fn mk_bits(&self) -> u32 {
|
||||
self.params.mk_bits
|
||||
}
|
||||
|
||||
fn mk_digest(&self) -> &[u8; 20] {
|
||||
&self.params.mk_digest
|
||||
}
|
||||
|
||||
fn mk_iterations(&self) -> u32 {
|
||||
self.params.mk_iterations
|
||||
}
|
||||
|
||||
fn mk_salt(&self) -> &[u8; 32] {
|
||||
&self.params.mk_salt
|
||||
}
|
||||
|
||||
fn payload_offset(&self) -> u32 {
|
||||
self.params.payload_offset
|
||||
}
|
||||
|
||||
fn uuid(&self) -> uuid::Uuid {
|
||||
device::uuid(&self.cd).expect("LUKS1 device should have UUID")
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptDeviceType for CryptDeviceHandle<Luks1Params> {
|
||||
fn device_type(&self) -> raw::crypt_device_type {
|
||||
raw::crypt_device_type::LUKS1
|
||||
}
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
//! Low-level cryptsetup binding that sits directly on top of the `libcryptsetup` C API
|
||||
//!
|
||||
//! Consider using the high-level binding in the `api` module instead
|
||||
|
||||
use std::ffi;
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::result;
|
||||
use std::str;
|
||||
|
||||
use blkid_rs;
|
||||
use errno;
|
||||
use libc;
|
||||
use raw;
|
||||
use uuid;
|
||||
|
||||
/// Raw pointer to the underlying `crypt_device` opaque struct
|
||||
pub type RawDevice = *mut raw::crypt_device;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error that originates from `libcryptsetup` (with numeric error code)
|
||||
CryptsetupError(errno::Errno),
|
||||
/// IO error
|
||||
IOError(::std::io::Error),
|
||||
/// Error from the blkid-rs library (while reading LUKS1 header)
|
||||
BlkidError(blkid_rs::Error),
|
||||
}
|
||||
|
||||
impl From<::std::io::Error> for Error {
|
||||
fn from(e: ::std::io::Error) -> Self {
|
||||
Error::IOError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<blkid_rs::Error> for Error {
|
||||
fn from(e: blkid_rs::Error) -> Self {
|
||||
Error::BlkidError(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
pub type Keyslot = u8;
|
||||
|
||||
const ANY_KEYSLOT: libc::c_int = -1 as libc::c_int;
|
||||
|
||||
fn str_from_c_str<'a>(c_str: *const libc::c_char) -> Option<&'a str> {
|
||||
if c_str.is_null() {
|
||||
None
|
||||
} else {
|
||||
unsafe { Some(ffi::CStr::from_ptr(c_str).to_str().unwrap()) }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! crypt_error {
|
||||
($res:expr) => {
|
||||
Err(Error::CryptsetupError(errno::Errno(-$res)))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_crypt_error {
|
||||
($res:expr) => {
|
||||
if $res != 0 {
|
||||
crypt_error!($res)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Log function callback used by `libcryptsetup`
|
||||
#[allow(unused)]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cryptsetup_rs_log_callback(
|
||||
level: raw::crypt_log_level,
|
||||
message: *const libc::c_char,
|
||||
usrptr: *mut libc::c_void,
|
||||
) {
|
||||
let msg = str_from_c_str(message).unwrap();
|
||||
match level {
|
||||
raw::crypt_log_level::CRYPT_LOG_NORMAL => info!("{}", msg.trim_right()),
|
||||
raw::crypt_log_level::CRYPT_LOG_ERROR => error!("{}", msg.trim_right()),
|
||||
raw::crypt_log_level::CRYPT_LOG_VERBOSE => debug!("{}", msg.trim_right()),
|
||||
raw::crypt_log_level::CRYPT_LOG_DEBUG => debug!("{}", msg.trim_right()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable internal `libcryptsetup` debugging
|
||||
pub fn enable_debug(debug: bool) {
|
||||
if debug {
|
||||
unsafe { raw::crypt_set_debug_level(raw::crypt_debug_level::CRYPT_DEBUG_ALL) };
|
||||
} else {
|
||||
unsafe { raw::crypt_set_debug_level(raw::crypt_debug_level::CRYPT_DEBUG_NONE) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise crypt device and check if provided device exists
|
||||
pub fn init<P: AsRef<Path>>(path: P) -> Result<RawDevice> {
|
||||
let mut cd = ptr::null_mut();
|
||||
let c_path = ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap();
|
||||
|
||||
let res = unsafe { raw::crypt_init(&mut cd as *mut *mut raw::crypt_device, c_path.as_ptr()) };
|
||||
|
||||
if res != 0 {
|
||||
crypt_error!(res)
|
||||
} else {
|
||||
unsafe {
|
||||
raw::crypt_set_log_callback(cd, Some(cryptsetup_rs_log_callback), ptr::null_mut());
|
||||
}
|
||||
Ok(cd)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load crypt device parameters from the on-disk header
|
||||
///
|
||||
/// Note that typically you cannot query the crypt device for information before this function is
|
||||
/// called.
|
||||
pub fn load(cd: &RawDevice, requested_type: raw::crypt_device_type) -> Result<()> {
|
||||
let c_type = ffi::CString::new(requested_type.to_str()).unwrap();
|
||||
|
||||
let res = unsafe { raw::crypt_load(*cd, c_type.as_ptr(), ptr::null_mut()) };
|
||||
|
||||
check_crypt_error!(res)
|
||||
}
|
||||
|
||||
/// Get the cipher used by this crypt device
|
||||
pub fn cipher<'a>(cd: &'a RawDevice) -> Option<&'a str> {
|
||||
let c_cipher = unsafe { raw::crypt_get_cipher(*cd) };
|
||||
str_from_c_str(c_cipher)
|
||||
}
|
||||
|
||||
/// Get the cipher mode used by this crypt device
|
||||
pub fn cipher_mode<'a>(cd: &'a RawDevice) -> Option<&'a str> {
|
||||
let c_cipher_mode = unsafe { raw::crypt_get_cipher_mode(*cd) };
|
||||
str_from_c_str(c_cipher_mode)
|
||||
}
|
||||
|
||||
/// Get the path to the device (as `libcryptsetup` sees it)
|
||||
pub fn device_name<'a>(cd: &'a RawDevice) -> Option<&'a str> {
|
||||
let c_device_name = unsafe { raw::crypt_get_device_name(*cd) };
|
||||
str_from_c_str(c_device_name)
|
||||
}
|
||||
|
||||
/// Dump text-formatted information about this device to the console
|
||||
pub fn dump(cd: &RawDevice) -> Result<()> {
|
||||
let res = unsafe { raw::crypt_dump(*cd) };
|
||||
check_crypt_error!(res)
|
||||
}
|
||||
|
||||
/// Releases crypt device context and memory
|
||||
pub fn free(cd: &mut RawDevice) {
|
||||
unsafe { raw::crypt_free(*cd) }
|
||||
}
|
||||
|
||||
/// Activate device based on provided key ("passphrase")
|
||||
pub fn luks_activate(cd: &mut RawDevice, name: &str, key: &[u8]) -> Result<Keyslot> {
|
||||
let c_name = ffi::CString::new(name).unwrap();
|
||||
let c_passphrase_len = key.len() as libc::size_t;
|
||||
// cast the passphrase to a pointer directly - it will not be NUL terminated but the passed length is used
|
||||
let c_passphrase = key as *const [u8] as *const libc::c_char;
|
||||
|
||||
let res = unsafe {
|
||||
raw::crypt_activate_by_passphrase(*cd, c_name.as_ptr(), ANY_KEYSLOT, c_passphrase, c_passphrase_len, 0u32)
|
||||
};
|
||||
|
||||
if res < 0 {
|
||||
crypt_error!(res)
|
||||
} else {
|
||||
Ok(res as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add key slot using provided passphrase. If there is no previous passphrase, use the volume key
|
||||
/// that is in-memory to add the new key slot.
|
||||
pub fn luks_add_keyslot(
|
||||
cd: &mut RawDevice,
|
||||
key: &[u8],
|
||||
maybe_prev_key: Option<&[u8]>,
|
||||
maybe_keyslot: Option<Keyslot>,
|
||||
) -> Result<Keyslot> {
|
||||
let c_key_len = key.len() as libc::size_t;
|
||||
let c_key = key as *const [u8] as *const libc::c_char;;
|
||||
let c_keyslot = maybe_keyslot
|
||||
.map(|k| k as libc::c_int)
|
||||
.unwrap_or(ANY_KEYSLOT as libc::c_int);
|
||||
|
||||
let res = if let Some(prev_key) = maybe_prev_key {
|
||||
let c_prev_key_len = prev_key.len() as libc::size_t;
|
||||
let c_prev_key = prev_key as *const [u8] as *const libc::c_char;;
|
||||
|
||||
unsafe { raw::crypt_keyslot_add_by_passphrase(*cd, c_keyslot, c_prev_key, c_prev_key_len, c_key, c_key_len) }
|
||||
} else {
|
||||
unsafe {
|
||||
raw::crypt_keyslot_add_by_volume_key(*cd, c_keyslot, ptr::null(), 0 as libc::size_t, c_key, c_key_len)
|
||||
}
|
||||
};
|
||||
|
||||
if res < 0 {
|
||||
crypt_error!(res)
|
||||
} else {
|
||||
Ok(res as Keyslot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add key slot using provided passphrase. If there is no previous passphrase, use the volume key
|
||||
/// that is in-memory to add the new key slot.
|
||||
pub fn luks_update_keyslot(
|
||||
cd: &mut RawDevice,
|
||||
key: &[u8],
|
||||
prev_key: &[u8],
|
||||
maybe_keyslot: Option<Keyslot>,
|
||||
) -> Result<Keyslot> {
|
||||
let c_key_len = key.len() as libc::size_t;
|
||||
let c_key = key as *const [u8] as *const libc::c_char;;
|
||||
let c_keyslot = maybe_keyslot
|
||||
.map(|k| k as libc::c_int)
|
||||
.unwrap_or(ANY_KEYSLOT as libc::c_int);
|
||||
|
||||
let c_prev_key_len = prev_key.len() as libc::size_t;
|
||||
let c_prev_key = prev_key as *const [u8] as *const libc::c_char;;
|
||||
|
||||
let res = unsafe {
|
||||
raw::crypt_keyslot_change_by_passphrase(*cd, c_keyslot, c_keyslot, c_prev_key, c_prev_key_len, c_key, c_key_len)
|
||||
};
|
||||
|
||||
if res < 0 {
|
||||
crypt_error!(res)
|
||||
} else {
|
||||
Ok(res as Keyslot)
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy (and disable) key slot
|
||||
pub fn luks_destroy_keyslot(cd: &mut RawDevice, keyslot: Keyslot) -> Result<()> {
|
||||
let res = unsafe { raw::crypt_keyslot_destroy(*cd, keyslot as libc::c_int) };
|
||||
if res < 0 {
|
||||
crypt_error!(res)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a new crypt device but do not activate it
|
||||
///
|
||||
/// Note this does not add an active keyslot
|
||||
pub fn luks1_format(
|
||||
cd: &mut RawDevice,
|
||||
cipher: &str,
|
||||
cipher_mode: &str,
|
||||
hash: &str,
|
||||
mk_bits: usize,
|
||||
maybe_uuid: Option<&uuid::Uuid>,
|
||||
) -> Result<()> {
|
||||
let c_cipher = ffi::CString::new(cipher).unwrap();
|
||||
let c_cipher_mode = ffi::CString::new(cipher_mode).unwrap();
|
||||
let c_hash = ffi::CString::new(hash).unwrap();
|
||||
let c_uuid = maybe_uuid.map(|uuid| ffi::CString::new(uuid.hyphenated().to_string()).unwrap());
|
||||
|
||||
let mut luks_params = raw::crypt_params_luks1 {
|
||||
hash: c_hash.as_ptr(),
|
||||
data_alignment: 0,
|
||||
data_device: ptr::null(),
|
||||
};
|
||||
let c_luks_params: *mut raw::crypt_params_luks1 = &mut luks_params;
|
||||
let c_luks_type = ffi::CString::new(raw::crypt_device_type::LUKS1.to_str()).unwrap();
|
||||
let c_uuid_ptr = c_uuid.as_ref().map(|u| u.as_ptr()).unwrap_or(ptr::null());
|
||||
let res = unsafe {
|
||||
raw::crypt_format(
|
||||
*cd,
|
||||
c_luks_type.as_ptr(),
|
||||
c_cipher.as_ptr(),
|
||||
c_cipher_mode.as_ptr(),
|
||||
c_uuid_ptr,
|
||||
ptr::null(),
|
||||
mk_bits / 8,
|
||||
c_luks_params as *mut libc::c_void,
|
||||
)
|
||||
};
|
||||
|
||||
check_crypt_error!(res)
|
||||
}
|
||||
|
||||
/// Get which RNG is used
|
||||
pub fn rng_type(cd: &RawDevice) -> raw::crypt_rng_type {
|
||||
unsafe {
|
||||
let res = raw::crypt_get_rng_type(*cd);
|
||||
mem::transmute(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the number of milliseconds for `PBKDF2` function iteration
|
||||
pub fn set_iteration_time(cd: &mut RawDevice, iteration_time_ms: u64) {
|
||||
unsafe {
|
||||
raw::crypt_set_iteration_time(*cd, iteration_time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set which RNG is used
|
||||
pub fn set_rng_type(cd: &mut RawDevice, rng_type: raw::crypt_rng_type) {
|
||||
unsafe { raw::crypt_set_rng_type(*cd, rng_type) }
|
||||
}
|
||||
|
||||
/// Get information about a keyslot
|
||||
pub fn keyslot_status(cd: &RawDevice, slot: Keyslot) -> raw::crypt_keyslot_info {
|
||||
unsafe { raw::crypt_keyslot_status(*cd, slot as libc::c_int) }
|
||||
}
|
||||
|
||||
/// Get size in bytes of the volume key
|
||||
pub fn volume_key_size(cd: &RawDevice) -> u8 {
|
||||
let res = unsafe { raw::crypt_get_volume_key_size(*cd) };
|
||||
res as u8
|
||||
}
|
||||
|
||||
/// Get device UUID
|
||||
pub fn uuid<'a>(cd: &'a RawDevice) -> Option<uuid::Uuid> {
|
||||
let c_uuid_str = unsafe { raw::crypt_get_uuid(*cd) };
|
||||
str_from_c_str(c_uuid_str).and_then(|uuid_str| uuid::Uuid::parse_str(uuid_str).ok())
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
//! Rust bindings to `libcryptsetup` - working with encrypted disks on Linux
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! See `api` module documentation for more.
|
||||
//!
|
||||
//! ```
|
||||
//! use cryptsetup_rs::*;
|
||||
//! # fn foo() -> Result<()> {
|
||||
//! let device = open("/dev/loop0")?.luks1()?;
|
||||
//! println!("Device UUID: {}", device.uuid());
|
||||
//! println!("Device cipher: {}", device.cipher());
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#[warn(unused_must_use)]
|
||||
extern crate blkid_rs;
|
||||
extern crate errno;
|
||||
extern crate libc;
|
||||
extern crate libcryptsetup_sys as raw;
|
||||
extern crate uuid;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod api;
|
||||
pub mod device;
|
||||
|
||||
pub use api::{enable_debug, format, luks1_uuid, open};
|
||||
pub use api::{CryptDevice, CryptDeviceType, Error, Keyslot, Luks1CryptDevice, Luks1CryptDeviceHandle, Result};
|
||||
pub use raw::{crypt_device_type, crypt_keyslot_info, crypt_rng_type};
|
@ -1,72 +0,0 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate cryptsetup_rs;
|
||||
extern crate env_logger;
|
||||
extern crate log;
|
||||
extern crate tempdir;
|
||||
extern crate uuid;
|
||||
|
||||
#[macro_use]
|
||||
extern crate expectest;
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
use expectest::prelude::*;
|
||||
use tempdir::TempDir;
|
||||
use uuid::Uuid;
|
||||
|
||||
use cryptsetup_rs::*;
|
||||
|
||||
struct TestContext {
|
||||
dir: TempDir,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
fn new(name: String) -> TestContext {
|
||||
env_logger::init();
|
||||
cryptsetup_rs::enable_debug(true);
|
||||
let dir = tempdir::TempDir::new(&name).unwrap();
|
||||
TestContext { name, dir }
|
||||
}
|
||||
|
||||
fn new_crypt_device(&self) -> api::CryptDeviceFormatBuilder {
|
||||
let crypt_file = self.dir.path().join(format!("{}.image", self.name));
|
||||
let dd_status = Command::new("dd")
|
||||
.arg("if=/dev/zero")
|
||||
.arg(format!("of={}", crypt_file.display()))
|
||||
.arg("bs=1M")
|
||||
.arg("count=10")
|
||||
.status()
|
||||
.unwrap();
|
||||
if !dd_status.success() {
|
||||
panic!("Failed to create disk image at {}", crypt_file.display());
|
||||
}
|
||||
|
||||
cryptsetup_rs::format(crypt_file).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_new_luks1_cryptdevice_no_errors() {
|
||||
let ctx = TestContext::new("new_luks1_cryptdevice".to_string());
|
||||
let uuid = Uuid::new_v4();
|
||||
|
||||
let device_format = ctx.new_crypt_device()
|
||||
.rng_type(crypt_rng_type::CRYPT_RNG_URANDOM)
|
||||
.iteration_time(42);
|
||||
|
||||
let mut dev = device_format
|
||||
.luks1("aes", "xts-plain", "sha256", 256, Some(&uuid))
|
||||
.expect("LUKS format should succeed");
|
||||
|
||||
dev.dump();
|
||||
|
||||
expect!(dev.uuid()).to(be_equal_to(uuid));
|
||||
expect!(dev.device_type()).to(be_equal_to(crypt_device_type::LUKS1));
|
||||
expect!(dev.cipher()).to(be_equal_to("aes"));
|
||||
expect!(dev.cipher_mode()).to(be_equal_to("xts-plain"));
|
||||
expect!(dev.volume_key_size()).to(be_equal_to(32));
|
||||
|
||||
expect!(dev.add_keyslot(b"hello world", None, Some(3))).to(be_ok().value(3));
|
||||
}
|
4
patch/ctap/.gitignore
vendored
4
patch/ctap/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
src/bin
|
@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "ctap"
|
||||
description = "A Rust implementation of the FIDO2 CTAP protocol"
|
||||
version = "0.1.0"
|
||||
license = "Apache-2.0/MIT"
|
||||
homepage = "https://github.com/ArdaXi/ctap"
|
||||
repository = "https://github.com/ArdaXi/ctap"
|
||||
authors = ["Arda Xi <arda@ardaxi.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.6"
|
||||
failure = "0.1"
|
||||
failure_derive = "0.1"
|
||||
num-traits = "0.2"
|
||||
num-derive = "0.2"
|
||||
byteorder = "1"
|
||||
cbor-codec = "0.7"
|
||||
ring = "0.13"
|
||||
untrusted = "0.6"
|
||||
rust-crypto = "0.2"
|
@ -1,59 +0,0 @@
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
# ctap
|
||||
|
||||
ctap is a library implementing the [FIDO2 CTAP](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html) protocol.
|
||||
|
||||
## Usage example
|
||||
|
||||
```rust
|
||||
let devices = ctap::get_devices()?;
|
||||
let device_info = &devices[0];
|
||||
let mut device = ctap::FidoDevice::new(device_info)?;
|
||||
|
||||
// This can be omitted if the FIDO device is not configured with a PIN.
|
||||
let pin = "test";
|
||||
device.unlock(pin)?;
|
||||
|
||||
// In a real application these values would come from the requesting app.
|
||||
let rp_id = "rp_id";
|
||||
let user_id = [0];
|
||||
let user_name = "user_name";
|
||||
let client_data_hash = [0; 32];
|
||||
let cred = device.make_credential(
|
||||
rp_id,
|
||||
&user_id,
|
||||
user_name,
|
||||
&client_data_hash
|
||||
)?;
|
||||
|
||||
// In a real application the credential would be stored and used later.
|
||||
let result = device.get_assertion(&cred, &client_data_hash);
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
Currently, this library only supports Linux. Testing and contributions for
|
||||
other platforms is welcome.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||
license, shall be dual licensed as above, without any additional terms or
|
||||
conditions.
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,19 +0,0 @@
|
||||
Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,715 +0,0 @@
|
||||
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
|
||||
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use cbor_codec::value;
|
||||
use cbor_codec::value::Value;
|
||||
use cbor_codec::{Config, Decoder, Encoder, GenericDecoder, GenericEncoder};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
|
||||
use failure::ResultExt;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
|
||||
use super::error::*;
|
||||
|
||||
pub enum Request<'a> {
|
||||
MakeCredential(MakeCredentialRequest<'a>),
|
||||
GetAssertion(GetAssertionRequest<'a>),
|
||||
GetInfo,
|
||||
ClientPin(ClientPinRequest<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Request<'a> {
|
||||
pub fn encode<W: WriteBytesExt>(&self, writer: &mut W) -> FidoResult<()> {
|
||||
let mut encoder = Encoder::new(writer);
|
||||
match self {
|
||||
Request::MakeCredential(req) => req.encode(&mut encoder),
|
||||
Request::GetAssertion(req) => req.encode(&mut encoder),
|
||||
Request::GetInfo => encoder
|
||||
.writer()
|
||||
.write_u8(0x04)
|
||||
.context(FidoErrorKind::CborEncode)
|
||||
.map_err(From::from),
|
||||
Request::ClientPin(req) => req.encode(&mut encoder),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode<R: ReadBytesExt>(&self, reader: R) -> FidoResult<Response> {
|
||||
Ok(match self {
|
||||
Request::MakeCredential(_) => {
|
||||
Response::MakeCredential(MakeCredentialResponse::decode(reader)?)
|
||||
}
|
||||
Request::GetAssertion(_) => {
|
||||
Response::GetAssertion(GetAssertionResponse::decode(reader)?)
|
||||
}
|
||||
Request::GetInfo => Response::GetInfo(GetInfoResponse::decode(reader)?),
|
||||
Request::ClientPin(_) => Response::ClientPin(ClientPinResponse::decode(reader)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Response {
|
||||
MakeCredential(MakeCredentialResponse),
|
||||
GetAssertion(GetAssertionResponse),
|
||||
GetInfo(GetInfoResponse),
|
||||
ClientPin(ClientPinResponse),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct MakeCredentialRequest<'a> {
|
||||
pub client_data_hash: &'a [u8],
|
||||
pub rp: PublicKeyCredentialRpEntity<'a>,
|
||||
pub user: PublicKeyCredentialUserEntity<'a>,
|
||||
pub pub_key_cred_params: &'a [(&'a str, i32)],
|
||||
pub exclude_list: &'a [PublicKeyCredentialDescriptor],
|
||||
pub extensions: &'a [(&'a str, &'a Value)],
|
||||
pub options: Option<AuthenticatorOptions>,
|
||||
pub pin_auth: Option<[u8; 16]>,
|
||||
pub pin_protocol: Option<u8>,
|
||||
}
|
||||
|
||||
impl<'a> MakeCredentialRequest<'a> {
|
||||
pub fn encode<W: WriteBytesExt>(&self, mut encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
encoder
|
||||
.writer()
|
||||
.write_u8(0x01)
|
||||
.context(FidoErrorKind::CborEncode)?; // authenticatorMakeCredential
|
||||
let mut length = 4;
|
||||
length += !self.exclude_list.is_empty() as usize;
|
||||
length += !self.extensions.is_empty() as usize;
|
||||
length += self.options.is_some() as usize;
|
||||
length += self.pin_auth.is_some() as usize;
|
||||
length += self.pin_protocol.is_some() as usize;
|
||||
encoder.object(length)?;
|
||||
encoder.u8(0x01)?; // clientDataHash
|
||||
encoder.bytes(&self.client_data_hash)?;
|
||||
encoder.u8(0x02)?; // rp
|
||||
self.rp.encode(&mut encoder)?;
|
||||
encoder.u8(0x03)?; // user
|
||||
self.user.encode(&mut encoder)?;
|
||||
encoder.u8(0x04)?; // pubKeyCredParams
|
||||
encoder.array(self.pub_key_cred_params.len())?;
|
||||
for (cred_type, alg) in self.pub_key_cred_params {
|
||||
encoder.object(2)?;
|
||||
encoder.text("alg")?;
|
||||
encoder.i32(*alg)?;
|
||||
encoder.text("type")?;
|
||||
encoder.text(&cred_type)?;
|
||||
}
|
||||
if self.exclude_list.len() > 0 {
|
||||
encoder.u8(0x05)?; // excludeList
|
||||
encoder.array(self.exclude_list.len())?;
|
||||
for item in self.exclude_list {
|
||||
item.encode(&mut encoder)?;
|
||||
}
|
||||
}
|
||||
if self.extensions.len() > 0 {
|
||||
encoder.u8(0x06)?; // extensions
|
||||
encoder.object(self.extensions.len())?;
|
||||
for (key, value) in self.extensions {
|
||||
encoder.text(key)?;
|
||||
let mut generic = GenericEncoder::new(encoder.writer());
|
||||
generic.value(value)?;
|
||||
}
|
||||
}
|
||||
if let Some(options) = &self.options {
|
||||
if options.encoded() {
|
||||
encoder.u8(0x07)?; // options
|
||||
options.encode(&mut encoder)?;
|
||||
}
|
||||
}
|
||||
if let Some(pin_auth) = &self.pin_auth {
|
||||
encoder.u8(0x08)?; // pinAuth
|
||||
encoder.bytes(pin_auth)?;
|
||||
}
|
||||
if let Some(pin_protocol) = &self.pin_protocol {
|
||||
encoder.u8(0x09)?; // pinProtocol
|
||||
encoder.u8(*pin_protocol)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MakeCredentialResponse {
|
||||
pub format: String,
|
||||
pub auth_data: AuthenticatorData,
|
||||
}
|
||||
|
||||
impl MakeCredentialResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = MakeCredentialResponse::default();
|
||||
for _ in 0..decoder.object()? {
|
||||
let key = decoder.u8()?;
|
||||
match key {
|
||||
0x01 => response.format = decoder.text()?,
|
||||
0x02 => response.auth_data = AuthenticatorData::from_bytes(&decoder.bytes()?)?,
|
||||
0x03 => break, // TODO: parse attestation
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GetAssertionRequest<'a> {
|
||||
pub rp_id: &'a str,
|
||||
pub client_data_hash: &'a [u8],
|
||||
pub allow_list: &'a [PublicKeyCredentialDescriptor],
|
||||
pub extensions: &'a [(&'a str, &'a Value)],
|
||||
pub options: Option<AuthenticatorOptions>,
|
||||
pub pin_auth: Option<[u8; 16]>,
|
||||
pub pin_protocol: Option<u8>,
|
||||
}
|
||||
|
||||
impl<'a> GetAssertionRequest<'a> {
|
||||
pub fn encode<W: WriteBytesExt>(&self, mut encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
encoder
|
||||
.writer()
|
||||
.write_u8(0x02)
|
||||
.context(FidoErrorKind::CborEncode)?; // authenticatorGetAssertion
|
||||
let mut length = 2;
|
||||
length += !self.allow_list.is_empty() as usize;
|
||||
length += !self.extensions.is_empty() as usize;
|
||||
length += self.options.is_some() as usize;
|
||||
length += self.pin_auth.is_some() as usize;
|
||||
length += self.pin_protocol.is_some() as usize;
|
||||
encoder.object(length)?;
|
||||
encoder.u8(0x01)?; // rpId
|
||||
encoder.text(&self.rp_id)?;
|
||||
encoder.u8(0x02)?; // clientDataHash
|
||||
encoder.bytes(self.client_data_hash)?;
|
||||
if !self.allow_list.is_empty() {
|
||||
encoder.u8(0x03)?; // allowList
|
||||
encoder.array(self.allow_list.len())?;
|
||||
for item in self.allow_list {
|
||||
item.encode(&mut encoder)?;
|
||||
}
|
||||
}
|
||||
if self.extensions.len() > 0 {
|
||||
encoder.u8(0x04)?; // extensions
|
||||
encoder.object(self.extensions.len())?;
|
||||
for (key, value) in self.extensions {
|
||||
encoder.text(key)?;
|
||||
let mut generic = GenericEncoder::new(encoder.writer());
|
||||
generic.value(value)?;
|
||||
}
|
||||
}
|
||||
if let Some(options) = &self.options {
|
||||
if options.encoded() {
|
||||
encoder.u8(0x05)?; // options
|
||||
options.encode(&mut encoder)?;
|
||||
}
|
||||
}
|
||||
if let Some(pin_auth) = &self.pin_auth {
|
||||
encoder.u8(0x06)?; // pinAuth
|
||||
encoder.bytes(pin_auth)?;
|
||||
}
|
||||
if let Some(pin_protocol) = &self.pin_protocol {
|
||||
encoder.u8(0x07)?; // pinProtocol
|
||||
encoder.u8(*pin_protocol)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GetAssertionResponse {
|
||||
pub credential: Option<PublicKeyCredentialDescriptor>,
|
||||
pub auth_data_bytes: Vec<u8>,
|
||||
pub auth_data: AuthenticatorData,
|
||||
pub signature: Vec<u8>,
|
||||
}
|
||||
|
||||
impl GetAssertionResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = GetAssertionResponse::default();
|
||||
for _ in 0..decoder.object()? {
|
||||
let key = decoder.u8()?;
|
||||
match key {
|
||||
0x01 => {
|
||||
response.credential = Some(PublicKeyCredentialDescriptor::decode(&mut decoder)?)
|
||||
}
|
||||
0x02 => {
|
||||
response.auth_data_bytes = decoder.bytes()?;
|
||||
response.auth_data = AuthenticatorData::from_bytes(&response.auth_data_bytes)?;
|
||||
}
|
||||
0x03 => response.signature = decoder.bytes()?,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GetInfoResponse {
|
||||
pub versions: Vec<String>,
|
||||
pub extensions: Vec<String>,
|
||||
pub aaguid: [u8; 16],
|
||||
pub options: OptionsInfo,
|
||||
pub max_msg_size: u16,
|
||||
pub pin_protocols: Vec<u8>,
|
||||
}
|
||||
|
||||
impl GetInfoResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = GetInfoResponse::default();
|
||||
for _ in 0..decoder.object()? {
|
||||
match decoder.u8()? {
|
||||
0x01 => {
|
||||
for _ in 0..decoder.array()? {
|
||||
response.versions.push(decoder.text()?);
|
||||
}
|
||||
}
|
||||
0x02 => {
|
||||
for _ in 0..decoder.array()? {
|
||||
response.extensions.push(decoder.text()?);
|
||||
}
|
||||
}
|
||||
0x03 => response.aaguid.copy_from_slice(&decoder.bytes()?[..]),
|
||||
0x04 => response.options = OptionsInfo::decode(&mut decoder)?,
|
||||
0x05 => response.max_msg_size = decoder.u16()?,
|
||||
0x06 => {
|
||||
for _ in 0..decoder.array()? {
|
||||
response.pin_protocols.push(decoder.u8()?);
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ClientPinRequest<'a> {
|
||||
pub pin_protocol: u8,
|
||||
pub sub_command: u8,
|
||||
pub key_agreement: Option<&'a CoseKey>,
|
||||
pub pin_auth: Option<[u8; 16]>,
|
||||
pub new_pin_enc: Option<Vec<u8>>,
|
||||
pub pin_hash_enc: Option<[u8; 16]>,
|
||||
}
|
||||
|
||||
impl<'a> ClientPinRequest<'a> {
|
||||
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
encoder
|
||||
.writer()
|
||||
.write_u8(0x06)
|
||||
.context(FidoErrorKind::CborEncode)?; // authenticatorClientPIN
|
||||
let mut length = 2;
|
||||
length += self.key_agreement.is_some() as usize;
|
||||
length += self.pin_auth.is_some() as usize;
|
||||
length += self.new_pin_enc.is_some() as usize;
|
||||
length += self.pin_hash_enc.is_some() as usize;
|
||||
encoder.object(length)?;
|
||||
encoder.u8(0x01)?; // pinProtocol
|
||||
encoder.u8(self.pin_protocol)?;
|
||||
encoder.u8(0x02)?; // subCommand
|
||||
encoder.u8(self.sub_command)?;
|
||||
if let Some(key_agreement) = self.key_agreement {
|
||||
encoder.u8(0x03)?; // keyAgreement
|
||||
key_agreement.encode(encoder)?;
|
||||
}
|
||||
if let Some(pin_auth) = &self.pin_auth {
|
||||
encoder.u8(0x04)?; // pinAuth
|
||||
encoder.bytes(pin_auth)?;
|
||||
}
|
||||
if let Some(new_pin_enc) = &self.new_pin_enc {
|
||||
encoder.u8(0x05)?; // newPinEnc
|
||||
encoder.bytes(&new_pin_enc)?;
|
||||
}
|
||||
if let Some(pin_hash_enc) = &self.pin_hash_enc {
|
||||
encoder.u8(0x06)?; // pinHashEnc
|
||||
encoder.bytes(pin_hash_enc)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ClientPinResponse {
|
||||
pub key_agreement: Option<CoseKey>,
|
||||
pub pin_token: Option<[u8; 16]>,
|
||||
pub retries: Option<u8>,
|
||||
}
|
||||
|
||||
impl ClientPinResponse {
|
||||
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
|
||||
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
|
||||
if status != 0 {
|
||||
Err(FidoErrorKind::CborError(status))?
|
||||
}
|
||||
let mut decoder = Decoder::new(Config::default(), reader);
|
||||
let mut response = ClientPinResponse::default();
|
||||
for _ in 0..decoder.object()? {
|
||||
match decoder.u8()? {
|
||||
0x01 => {
|
||||
let mut generic = GenericDecoder::from_decoder(decoder);
|
||||
response.key_agreement = Some(CoseKey::decode(&mut generic)?);
|
||||
decoder = generic.into_inner();
|
||||
}
|
||||
0x02 => {
|
||||
let mut pin_token = [0; 16];
|
||||
pin_token.copy_from_slice(&decoder.bytes()?[..]);
|
||||
response.pin_token = Some(pin_token)
|
||||
}
|
||||
0x03 => response.retries = Some(decoder.u8()?),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OptionsInfo {
|
||||
pub plat: bool,
|
||||
pub rk: bool,
|
||||
pub client_pin: Option<bool>,
|
||||
pub up: bool,
|
||||
pub uv: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for OptionsInfo {
|
||||
fn default() -> Self {
|
||||
OptionsInfo {
|
||||
plat: false,
|
||||
rk: false,
|
||||
client_pin: None,
|
||||
up: true,
|
||||
uv: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionsInfo {
|
||||
pub fn decode<R: ReadBytesExt>(decoder: &mut Decoder<R>) -> FidoResult<Self> {
|
||||
let mut options = OptionsInfo::default();
|
||||
for _ in 0..decoder.object()? {
|
||||
match decoder.text()?.as_ref() {
|
||||
"plat" => options.plat = decoder.bool()?,
|
||||
"rk" => options.rk = decoder.bool()?,
|
||||
"clientPin" => options.client_pin = Some(decoder.bool()?),
|
||||
"up" => options.up = decoder.bool()?,
|
||||
"uv" => options.uv = Some(decoder.bool()?),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(options)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AuthenticatorData {
|
||||
pub rp_id_hash: [u8; 32],
|
||||
pub up: bool,
|
||||
pub uv: bool,
|
||||
pub sign_count: u32,
|
||||
pub attested_credential_data: AttestedCredentialData,
|
||||
pub extensions: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
impl AuthenticatorData {
|
||||
pub fn from_bytes(bytes: &[u8]) -> FidoResult<Self> {
|
||||
let mut data = AuthenticatorData::default();
|
||||
data.rp_id_hash.copy_from_slice(&bytes[0..32]);
|
||||
let flags = bytes[32];
|
||||
data.up = (flags & 0x01) == 0x01;
|
||||
data.uv = (flags & 0x02) == 0x02;
|
||||
let is_attested = (flags & 0x40) == 0x40;
|
||||
let has_extension_data = (flags & 0x80) == 0x80;
|
||||
data.sign_count = BigEndian::read_u32(&bytes[33..37]);
|
||||
if bytes.len() < 38 {
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
let mut cur = Cursor::new(&bytes[37..]);
|
||||
if is_attested {
|
||||
let attested_credential_data = AttestedCredentialData::from_bytes(&mut cur)?;
|
||||
data.attested_credential_data = attested_credential_data;
|
||||
if cur.position() >= (bytes.len() - 37) as u64 {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
if has_extension_data {
|
||||
let mut decoder = GenericDecoder::new(Config::default(), cur);
|
||||
for _ in 0..decoder.borrow_mut().object()? {
|
||||
let key = decoder.borrow_mut().text()?;
|
||||
let value = decoder.value()?;
|
||||
data.extensions.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AttestedCredentialData {
|
||||
pub aaguid: [u8; 16],
|
||||
pub credential_id: Vec<u8>,
|
||||
pub credential_public_key: CoseKey,
|
||||
}
|
||||
|
||||
impl AttestedCredentialData {
|
||||
pub fn from_bytes(cur: &mut Cursor<&[u8]>) -> FidoResult<Self> {
|
||||
let mut response = AttestedCredentialData::default();
|
||||
let bytes = cur.get_ref();
|
||||
if bytes.is_empty() {
|
||||
return Ok(response);
|
||||
}
|
||||
response.aaguid.copy_from_slice(&bytes[0..16]);
|
||||
let id_length = BigEndian::read_u16(&bytes[16..18]) as usize;
|
||||
response.credential_id = Vec::from(&bytes[18..(18 + id_length)]);
|
||||
cur.set_position(18 + id_length as u64);
|
||||
let mut decoder = GenericDecoder::new(Config::default(), cur);
|
||||
response.credential_public_key = CoseKey::decode(&mut decoder)?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct P256Key {
|
||||
x: [u8; 32],
|
||||
y: [u8; 32],
|
||||
}
|
||||
|
||||
impl P256Key {
|
||||
pub fn from_cose(cose: &CoseKey) -> FidoResult<Self> {
|
||||
if cose.key_type != 2 || cose.algorithm != -7 {
|
||||
Err(FidoErrorKind::KeyType)?
|
||||
}
|
||||
if let (
|
||||
Some(Value::U8(curve)),
|
||||
Some(Value::Bytes(value::Bytes::Bytes(x))),
|
||||
Some(Value::Bytes(value::Bytes::Bytes(y))),
|
||||
) = (
|
||||
cose.parameters.get(&-1),
|
||||
cose.parameters.get(&-2),
|
||||
cose.parameters.get(&-3),
|
||||
) {
|
||||
if *curve != 1 {
|
||||
Err(FidoErrorKind::KeyType)?
|
||||
}
|
||||
let mut key = P256Key::default();
|
||||
key.x.copy_from_slice(&x);
|
||||
key.y.copy_from_slice(&y);
|
||||
return Ok(key);
|
||||
}
|
||||
Err(FidoErrorKind::KeyType)?
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> FidoResult<Self> {
|
||||
if bytes.len() != 65 || bytes[0] != 0x04 {
|
||||
Err(FidoErrorKind::CborDecode)?
|
||||
}
|
||||
let mut res = P256Key::default();
|
||||
res.x.copy_from_slice(&bytes[1..33]);
|
||||
res.y.copy_from_slice(&bytes[33..65]);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn to_cose(&self) -> CoseKey {
|
||||
CoseKey {
|
||||
key_type: 2,
|
||||
algorithm: -7,
|
||||
parameters: [
|
||||
(-1, Value::U8(1)),
|
||||
(-2, Value::Bytes(value::Bytes::Bytes(self.x.to_vec()))),
|
||||
(-3, Value::Bytes(value::Bytes::Bytes(self.y.to_vec()))),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(&self) -> [u8; 65] {
|
||||
let mut bytes = [0; 65];
|
||||
bytes[0] = 0x04;
|
||||
bytes[1..33].copy_from_slice(&self.x);
|
||||
bytes[33..65].copy_from_slice(&self.y);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CoseKey {
|
||||
key_type: u16,
|
||||
algorithm: i32,
|
||||
parameters: HashMap<i16, Value>,
|
||||
}
|
||||
|
||||
impl CoseKey {
|
||||
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
let size = 1 + self.parameters.len();
|
||||
encoder.object(size)?;
|
||||
encoder.i16(0x01)?; // keyType
|
||||
encoder.u16(self.key_type)?;
|
||||
//encoder.i16(0x02)?; // algorithm
|
||||
//encoder.i32(self.algorithm)?;
|
||||
for (key, value) in self.parameters.iter() {
|
||||
encoder.i16(*key)?;
|
||||
let mut generic = GenericEncoder::new(encoder.writer());
|
||||
generic.value(value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decode<R: ReadBytesExt>(generic: &mut GenericDecoder<R>) -> FidoResult<Self> {
|
||||
let items;
|
||||
{
|
||||
let decoder = generic.borrow_mut();
|
||||
items = decoder.object()?;
|
||||
}
|
||||
let mut cose_key = CoseKey::default();
|
||||
cose_key.algorithm = -7;
|
||||
for _ in 0..items {
|
||||
match generic.borrow_mut().i16()? {
|
||||
0x01 => cose_key.key_type = generic.borrow_mut().u16()?,
|
||||
0x02 => cose_key.algorithm = generic.borrow_mut().i32()?,
|
||||
key if key < 0 => {
|
||||
cose_key.parameters.insert(key, generic.value()?);
|
||||
}
|
||||
_ => {
|
||||
generic.value()?; // skip unknown parameter
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(cose_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PublicKeyCredentialRpEntity<'a> {
|
||||
pub id: &'a str,
|
||||
pub name: Option<&'a str>,
|
||||
pub icon: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> PublicKeyCredentialRpEntity<'a> {
|
||||
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
let mut length = 1;
|
||||
length += self.name.is_some() as usize;
|
||||
length += self.icon.is_some() as usize;
|
||||
encoder.object(length)?;
|
||||
encoder.text("id")?;
|
||||
encoder.text(&self.id)?;
|
||||
if let Some(icon) = &self.icon {
|
||||
encoder.text("icon")?;
|
||||
encoder.text(&icon)?;
|
||||
}
|
||||
if let Some(name) = &self.name {
|
||||
encoder.text("name")?;
|
||||
encoder.text(&name)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PublicKeyCredentialUserEntity<'a> {
|
||||
pub id: &'a [u8],
|
||||
pub name: &'a str,
|
||||
pub icon: Option<&'a str>,
|
||||
pub display_name: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> PublicKeyCredentialUserEntity<'a> {
|
||||
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
let mut length = 2;
|
||||
length += self.icon.is_some() as usize;
|
||||
length += self.display_name.is_some() as usize;
|
||||
encoder.object(length)?;
|
||||
encoder.text("id")?;
|
||||
encoder.bytes(&self.id)?;
|
||||
if let Some(icon) = &self.icon {
|
||||
encoder.text("icon")?;
|
||||
encoder.text(&icon)?;
|
||||
}
|
||||
encoder.text("name")?;
|
||||
encoder.text(&self.name)?;
|
||||
if let Some(display_name) = &self.display_name {
|
||||
encoder.text("displayName")?;
|
||||
encoder.text(&display_name)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PublicKeyCredentialDescriptor {
|
||||
pub cred_type: String,
|
||||
pub id: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PublicKeyCredentialDescriptor {
|
||||
pub fn decode<R: ReadBytesExt>(decoder: &mut Decoder<R>) -> FidoResult<Self> {
|
||||
let mut response = PublicKeyCredentialDescriptor::default();
|
||||
for _ in 0..decoder.object()? {
|
||||
match decoder.text()?.as_ref() {
|
||||
"id" => response.id = decoder.bytes()?,
|
||||
"type" => response.cred_type = decoder.text()?,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
encoder.object(2)?;
|
||||
encoder.text("id")?;
|
||||
encoder.bytes(&self.id)?;
|
||||
encoder.text("type")?;
|
||||
encoder.text(&self.cred_type)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuthenticatorOptions {
|
||||
pub rk: bool,
|
||||
pub uv: bool,
|
||||
}
|
||||
|
||||
impl AuthenticatorOptions {
|
||||
pub fn encoded(&self) -> bool {
|
||||
self.rk || self.uv
|
||||
}
|
||||
|
||||
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
|
||||
let length = (self.rk as usize) + (self.uv as usize);
|
||||
encoder.object(length)?;
|
||||
if self.rk {
|
||||
encoder.text("rk")?;
|
||||
encoder.bool(true)?;
|
||||
}
|
||||
if self.uv {
|
||||
encoder.text("uv")?;
|
||||
encoder.bool(true)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
|
||||
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use super::cbor::{CoseKey, P256Key};
|
||||
use super::error::*;
|
||||
use failure::ResultExt;
|
||||
use ring::error::Unspecified;
|
||||
use ring::{agreement, digest, hmac, rand, signature};
|
||||
use rust_crypto::aes;
|
||||
use rust_crypto::blockmodes::NoPadding;
|
||||
use rust_crypto::buffer::{RefReadBuffer, RefWriteBuffer};
|
||||
use rust_crypto::symmetriccipher::{Decryptor, Encryptor};
|
||||
use untrusted::Input;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SharedSecret {
|
||||
pub public_key: CoseKey,
|
||||
pub shared_secret: [u8; 32],
|
||||
}
|
||||
|
||||
impl SharedSecret {
|
||||
pub fn new(peer_key: &CoseKey) -> FidoResult<Self> {
|
||||
let rng = rand::SystemRandom::new();
|
||||
let private = agreement::EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng)
|
||||
.context(FidoErrorKind::GenerateKey)?;
|
||||
let public = &mut [0u8; agreement::PUBLIC_KEY_MAX_LEN][..private.public_key_len()];
|
||||
private
|
||||
.compute_public_key(public)
|
||||
.context(FidoErrorKind::GenerateKey)?;
|
||||
let peer = P256Key::from_cose(peer_key)
|
||||
.context(FidoErrorKind::ParsePublic)?
|
||||
.bytes();
|
||||
let peer = Input::from(&peer);
|
||||
let shared_secret = agreement::agree_ephemeral(
|
||||
private,
|
||||
&agreement::ECDH_P256,
|
||||
peer,
|
||||
Unspecified,
|
||||
|material| Ok(digest::digest(&digest::SHA256, material)),
|
||||
)
|
||||
.context(FidoErrorKind::GenerateSecret)?;
|
||||
let mut res = SharedSecret {
|
||||
public_key: P256Key::from_bytes(&public)
|
||||
.context(FidoErrorKind::ParsePublic)?
|
||||
.to_cose(),
|
||||
shared_secret: [0; 32],
|
||||
};
|
||||
res.shared_secret.copy_from_slice(shared_secret.as_ref());
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn encryptor(&self) -> Box<dyn Encryptor + 'static> {
|
||||
aes::cbc_encryptor(
|
||||
aes::KeySize::KeySize256,
|
||||
&self.shared_secret,
|
||||
&[0u8; 16],
|
||||
NoPadding,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn encrypt_pin(&self, pin: &str) -> FidoResult<[u8; 16]> {
|
||||
let mut encryptor = self.encryptor();
|
||||
let pin_bytes = pin.as_bytes();
|
||||
let hash = digest::digest(&digest::SHA256, &pin_bytes);
|
||||
let in_bytes = &hash.as_ref()[0..16];
|
||||
let mut input = RefReadBuffer::new(&in_bytes);
|
||||
let mut out_bytes = [0; 16];
|
||||
let mut output = RefWriteBuffer::new(&mut out_bytes);
|
||||
encryptor
|
||||
.encrypt(&mut input, &mut output, true)
|
||||
.map_err(|_| FidoErrorKind::EncryptPin)?;
|
||||
Ok(out_bytes)
|
||||
}
|
||||
|
||||
pub fn decryptor(&self) -> Box<dyn Decryptor + 'static> {
|
||||
aes::cbc_decryptor(
|
||||
aes::KeySize::KeySize256,
|
||||
&self.shared_secret,
|
||||
&[0u8; 16],
|
||||
NoPadding,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn decrypt_token(&self, data: &mut [u8]) -> FidoResult<PinToken> {
|
||||
let mut decryptor = self.decryptor();
|
||||
let mut input = RefReadBuffer::new(data);
|
||||
let mut out_bytes = [0; 16];
|
||||
let mut output = RefWriteBuffer::new(&mut out_bytes);
|
||||
decryptor
|
||||
.decrypt(&mut input, &mut output, true)
|
||||
.map_err(|_| FidoErrorKind::DecryptPin)?;
|
||||
Ok(PinToken(hmac::SigningKey::new(&digest::SHA256, &out_bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PinToken(hmac::SigningKey);
|
||||
|
||||
impl PinToken {
|
||||
pub fn auth(&self, data: &[u8]) -> [u8; 16] {
|
||||
let signature = hmac::sign(&self.0, &data);
|
||||
let mut out = [0; 16];
|
||||
out.copy_from_slice(&signature.as_ref()[0..16]);
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_signature(
|
||||
public_key: &[u8],
|
||||
client_data: &[u8],
|
||||
auth_data: &[u8],
|
||||
signature: &[u8],
|
||||
) -> bool {
|
||||
let public_key = Input::from(&public_key);
|
||||
let msg_len = client_data.len() + auth_data.len();
|
||||
let mut msg = Vec::with_capacity(msg_len);
|
||||
msg.extend_from_slice(auth_data);
|
||||
msg.extend_from_slice(client_data);
|
||||
let msg = Input::from(&msg);
|
||||
let signature = Input::from(signature);
|
||||
signature::verify(
|
||||
&signature::ECDSA_P256_SHA256_ASN1,
|
||||
public_key,
|
||||
msg,
|
||||
signature,
|
||||
)
|
||||
.is_ok()
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
|
||||
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use cbor_codec::{DecodeError, EncodeError};
|
||||
|
||||
use failure::{Backtrace, Context, Fail};
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
|
||||
pub type FidoResult<T> = Result<T, FidoError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FidoError(Context<FidoErrorKind>);
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
|
||||
pub enum FidoErrorKind {
|
||||
#[fail(display = "Read/write error with device.")]
|
||||
Io,
|
||||
#[fail(display = "Error while reading packet from device.")]
|
||||
ReadPacket,
|
||||
#[fail(display = "Error while writing packet to device.")]
|
||||
WritePacket,
|
||||
#[fail(display = "Error while parsing CTAP from device.")]
|
||||
ParseCtap,
|
||||
#[fail(display = "Error while encoding CBOR for device.")]
|
||||
CborEncode,
|
||||
#[fail(display = "Error while decoding CBOR from device.")]
|
||||
CborDecode,
|
||||
#[fail(display = "Packets received from device in the wrong order.")]
|
||||
InvalidSequence,
|
||||
#[fail(display = "Failed to generate private keypair.")]
|
||||
GenerateKey,
|
||||
#[fail(display = "Failed to generate shared secret.")]
|
||||
GenerateSecret,
|
||||
#[fail(display = "Failed to parse public key.")]
|
||||
ParsePublic,
|
||||
#[fail(display = "Failed to encrypt PIN.")]
|
||||
EncryptPin,
|
||||
#[fail(display = "Failed to decrypt PIN.")]
|
||||
DecryptPin,
|
||||
#[fail(display = "Supplied key has incorrect type.")]
|
||||
KeyType,
|
||||
#[fail(display = "Device returned error: 0x{:x}", _0)]
|
||||
CborError(u8),
|
||||
#[fail(display = "Device does not support FIDO2")]
|
||||
DeviceUnsupported,
|
||||
#[fail(display = "This operating requires a PIN but none was provided.")]
|
||||
PinRequired,
|
||||
}
|
||||
|
||||
impl Fail for FidoError {
|
||||
fn cause(&self) -> Option<&dyn Fail> {
|
||||
self.0.cause()
|
||||
}
|
||||
|
||||
fn backtrace(&self) -> Option<&Backtrace> {
|
||||
self.0.backtrace()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FidoError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FidoError {
|
||||
pub fn kind(&self) -> FidoErrorKind {
|
||||
*self.0.get_context()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FidoErrorKind> for FidoError {
|
||||
#[inline(always)]
|
||||
fn from(kind: FidoErrorKind) -> FidoError {
|
||||
FidoError(Context::new(kind))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Context<FidoErrorKind>> for FidoError {
|
||||
fn from(inner: Context<FidoErrorKind>) -> FidoError {
|
||||
FidoError(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncodeError> for FidoError {
|
||||
#[inline(always)]
|
||||
fn from(err: EncodeError) -> FidoError {
|
||||
FidoError(err.context(FidoErrorKind::CborEncode))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for FidoError {
|
||||
#[inline(always)]
|
||||
fn from(err: DecodeError) -> FidoError {
|
||||
FidoError(err.context(FidoErrorKind::CborDecode))
|
||||
}
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
use crate::cbor;
|
||||
use crate::{FidoCredential, FidoDevice, FidoErrorKind, FidoResult};
|
||||
use cbor_codec::value::{Bytes, Int, Key, Text, Value};
|
||||
use cbor_codec::Encoder;
|
||||
use cbor_codec::{Config, GenericDecoder};
|
||||
use rust_crypto::buffer::{RefReadBuffer, RefWriteBuffer};
|
||||
use rust_crypto::digest::Digest;
|
||||
use rust_crypto::hmac::Hmac;
|
||||
use rust_crypto::mac::Mac;
|
||||
use rust_crypto::sha2::Sha256;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FidoHmacCredential {
|
||||
pub id: Vec<u8>,
|
||||
pub rp_id: String,
|
||||
}
|
||||
|
||||
impl From<FidoCredential> for FidoHmacCredential {
|
||||
fn from(cred: FidoCredential) -> Self {
|
||||
FidoHmacCredential {
|
||||
id: cred.id,
|
||||
rp_id: cred.rp_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HmacExtension {
|
||||
fn extension_name() -> &'static str {
|
||||
"hmac-secret"
|
||||
}
|
||||
|
||||
fn get_dict(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value> {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(
|
||||
Key::Text(Text::Text(Self::extension_name().to_owned())),
|
||||
self.get_data(salt, salt2)?,
|
||||
);
|
||||
Ok(Value::Map(map))
|
||||
}
|
||||
|
||||
fn get_data(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value>;
|
||||
|
||||
fn make_hmac_credential(&mut self) -> FidoResult<FidoHmacCredential>;
|
||||
|
||||
fn get_hmac_assertion(
|
||||
&mut self,
|
||||
credential: &FidoHmacCredential,
|
||||
salt: &[u8; 32],
|
||||
salt2: Option<&[u8; 32]>,
|
||||
) -> FidoResult<([u8; 32], Option<[u8; 32]>)>;
|
||||
|
||||
fn hmac_challange(
|
||||
&mut self,
|
||||
credential: &FidoHmacCredential,
|
||||
input: &[u8],
|
||||
) -> FidoResult<[u8; 32]> {
|
||||
let mut salt = [0u8; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input(input);
|
||||
digest.result(&mut salt);
|
||||
self.get_hmac_assertion(credential, &salt, None)
|
||||
.map(|secret| secret.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl HmacExtension for FidoDevice {
|
||||
fn get_data(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value> {
|
||||
let shared_secret = self.shared_secret.as_ref().unwrap();
|
||||
let mut encryptor = shared_secret.encryptor();
|
||||
let mut salt_enc = [0u8; 64];
|
||||
let mut output = RefWriteBuffer::new(&mut salt_enc);
|
||||
let mut encrypt = || {
|
||||
encryptor.encrypt(&mut RefReadBuffer::new(salt), &mut output, salt2.is_none())?;
|
||||
if let Some(salt2) = salt2 {
|
||||
encryptor
|
||||
.encrypt(&mut RefReadBuffer::new(salt2), &mut output, true)
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
encrypt().map_err(|_| FidoErrorKind::Io)?;
|
||||
|
||||
let key_agreement = || {
|
||||
let mut cur = Cursor::new(Vec::new());
|
||||
let mut encoder = Encoder::new(&mut cur);
|
||||
shared_secret.public_key.encode(&mut encoder).unwrap();
|
||||
cur.set_position(0);
|
||||
let mut dec = GenericDecoder::new(Config::default(), cur);
|
||||
dec.value()
|
||||
};
|
||||
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(
|
||||
Key::Int(Int::from_i64(0x01)),
|
||||
key_agreement().map_err(|_| FidoErrorKind::Io)?,
|
||||
);
|
||||
map.insert(
|
||||
Key::Int(Int::from_i64(0x02)),
|
||||
Value::Bytes(Bytes::Bytes(
|
||||
salt_enc[0..((salt2.is_some() as usize + 1) * 32)].to_vec(),
|
||||
)),
|
||||
);
|
||||
|
||||
let mut salt_hmac = Hmac::new(Sha256::new(), &shared_secret.shared_secret);
|
||||
salt_hmac.input(&salt_enc[0..((salt2.is_some() as usize + 1) * 32)]);
|
||||
|
||||
let mut authed_salt_enc = [0u8; 32];
|
||||
authed_salt_enc.copy_from_slice(salt_hmac.result().code());
|
||||
|
||||
map.insert(
|
||||
Key::Int(Int::from_i64(0x03)),
|
||||
Value::Bytes(Bytes::Bytes(authed_salt_enc[0..16].to_vec())),
|
||||
);
|
||||
|
||||
Ok(Value::Map(map))
|
||||
}
|
||||
|
||||
fn make_hmac_credential(&mut self) -> FidoResult<FidoHmacCredential> {
|
||||
self.make_credential("hmac", &[0u8], "commandline", &[0u8; 32])
|
||||
.map(|cred| cred.into())
|
||||
}
|
||||
|
||||
fn get_hmac_assertion(
|
||||
&mut self,
|
||||
credential: &FidoHmacCredential,
|
||||
salt: &[u8; 32],
|
||||
salt2: Option<&[u8; 32]>,
|
||||
) -> FidoResult<([u8; 32], Option<[u8; 32]>)> {
|
||||
let client_data_hash = [0u8; 32];
|
||||
while self.shared_secret.is_none() {
|
||||
self.init_shared_secret()?;
|
||||
}
|
||||
if self.needs_pin && self.pin_token.is_none() {
|
||||
Err(FidoErrorKind::PinRequired)?
|
||||
}
|
||||
|
||||
if client_data_hash.len() != 32 {
|
||||
Err(FidoErrorKind::CborEncode)?
|
||||
}
|
||||
let pin_auth = self
|
||||
.pin_token
|
||||
.as_ref()
|
||||
.map(|token| token.auth(&client_data_hash));
|
||||
let ext_data: Value = self.get_data(salt, salt2)?;
|
||||
let allow_list = [cbor::PublicKeyCredentialDescriptor {
|
||||
cred_type: String::from("public-key"),
|
||||
id: credential.id.clone(),
|
||||
}];
|
||||
let request = cbor::GetAssertionRequest {
|
||||
rp_id: &credential.rp_id,
|
||||
client_data_hash: &client_data_hash,
|
||||
allow_list: &allow_list,
|
||||
extensions: &[(<Self as HmacExtension>::extension_name(), &ext_data)],
|
||||
options: Some(cbor::AuthenticatorOptions {
|
||||
rk: false,
|
||||
uv: true,
|
||||
}),
|
||||
pin_auth,
|
||||
pin_protocol: pin_auth.and(Some(0x01)),
|
||||
};
|
||||
let response = match self.cbor(cbor::Request::GetAssertion(request))? {
|
||||
cbor::Response::GetAssertion(resp) => resp,
|
||||
_ => Err(FidoErrorKind::CborDecode)?,
|
||||
};
|
||||
let shared_secret = self.shared_secret.as_ref().unwrap();
|
||||
let mut decryptor = shared_secret.decryptor();
|
||||
let mut hmac_secret_combined = [0u8; 64];
|
||||
let _output = RefWriteBuffer::new(&mut hmac_secret_combined);
|
||||
let hmac_secret_enc = match response
|
||||
.auth_data
|
||||
.extensions
|
||||
.get(<Self as HmacExtension>::extension_name())
|
||||
.ok_or(FidoErrorKind::CborDecode)?
|
||||
{
|
||||
Value::Bytes(hmac_ciphered) => Ok(match hmac_ciphered {
|
||||
Bytes::Bytes(hmac_ciphered) => hmac_ciphered.to_vec(),
|
||||
Bytes::Chunks(hmac_ciphered) => hmac_ciphered.iter().fold(Vec::new(), |s, i| {
|
||||
let mut s = s;
|
||||
s.extend_from_slice(&i);
|
||||
s
|
||||
}),
|
||||
}),
|
||||
_ => Err(FidoErrorKind::CborDecode),
|
||||
}?;
|
||||
let mut hmac_secret = ([0u8; 32], [0u8; 32]);
|
||||
decryptor
|
||||
.decrypt(
|
||||
&mut RefReadBuffer::new(&hmac_secret_enc),
|
||||
&mut RefWriteBuffer::new(unsafe {
|
||||
std::mem::transmute::<_, &mut [u8; 64]>(&mut hmac_secret)
|
||||
}),
|
||||
true,
|
||||
)
|
||||
.expect("failed to decrypt secret");
|
||||
Ok((hmac_secret.0, salt2.map(|_| hmac_secret.1)))
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
pub mod hmac;
|
@ -1,16 +0,0 @@
|
||||
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
|
||||
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Storage for device related information
|
||||
pub struct DeviceInfo {
|
||||
pub path: PathBuf,
|
||||
pub usage_page: u16,
|
||||
pub usage: u16,
|
||||
pub report_size: u16,
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
|
||||
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
pub use super::hid_common::*;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
static REPORT_DESCRIPTOR_KEY_MASK: u8 = 0xfc;
|
||||
static LONG_ITEM_ENCODING: u8 = 0xfe;
|
||||
static USAGE_PAGE: u8 = 0x04;
|
||||
static USAGE: u8 = 0x08;
|
||||
static REPORT_SIZE: u8 = 0x74;
|
||||
|
||||
pub fn enumerate() -> io::Result<impl Iterator<Item = DeviceInfo>> {
|
||||
fs::read_dir("/sys/class/hidraw").map(|entries| {
|
||||
entries
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| path_to_device(&entry.path()).ok())
|
||||
})
|
||||
}
|
||||
|
||||
fn path_to_device(path: &PathBuf) -> io::Result<DeviceInfo> {
|
||||
let mut rd_path = path.clone();
|
||||
rd_path.push("device/report_descriptor");
|
||||
let rd = fs::read(rd_path)?;
|
||||
let mut usage_page: u16 = 0;
|
||||
let mut usage: u16 = 0;
|
||||
let mut report_size: u16 = 0;
|
||||
let mut pos: usize = 0;
|
||||
|
||||
while pos < rd.len() {
|
||||
let key = rd[pos];
|
||||
let mut key_size: usize = 1;
|
||||
let mut size: u8;
|
||||
|
||||
if key == LONG_ITEM_ENCODING {
|
||||
key_size = 3;
|
||||
size = rd[pos + 1];
|
||||
} else {
|
||||
size = key & 0x03;
|
||||
|
||||
if size == 0x03 {
|
||||
size = 0x04
|
||||
}
|
||||
}
|
||||
|
||||
if key & REPORT_DESCRIPTOR_KEY_MASK == USAGE_PAGE {
|
||||
if size != 2 {
|
||||
usage_page = u16::from(rd[pos + 1])
|
||||
} else {
|
||||
usage_page = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]);
|
||||
}
|
||||
}
|
||||
|
||||
if key & REPORT_DESCRIPTOR_KEY_MASK == USAGE {
|
||||
if size != 2 {
|
||||
usage = u16::from(rd[pos + 1])
|
||||
} else {
|
||||
usage = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]);
|
||||
}
|
||||
}
|
||||
|
||||
if key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_SIZE {
|
||||
if size != 2 {
|
||||
report_size = u16::from(rd[pos + 1])
|
||||
} else {
|
||||
report_size = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]);
|
||||
}
|
||||
}
|
||||
|
||||
pos = pos + key_size + size as usize;
|
||||
}
|
||||
|
||||
let mut device_path = PathBuf::from("/dev");
|
||||
device_path.push(path.file_name().unwrap());
|
||||
|
||||
Ok(DeviceInfo {
|
||||
path: device_path,
|
||||
usage_page,
|
||||
usage,
|
||||
report_size,
|
||||
})
|
||||
}
|
@ -1,402 +0,0 @@
|
||||
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
|
||||
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
//! An implementation of the CTAP2 protocol over USB.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! # fn do_fido() -> ctap::FidoResult<()> {
|
||||
//! let mut devices = ctap::get_devices()?;
|
||||
//! let device_info = &devices.next().unwrap();
|
||||
//! let mut device = ctap::FidoDevice::new(device_info)?;
|
||||
//!
|
||||
//! // This can be omitted if the FIDO device is not configured with a PIN.
|
||||
//! let pin = "test";
|
||||
//! device.unlock(pin)?;
|
||||
//!
|
||||
//! // In a real application these values would come from the requesting app.
|
||||
//! let rp_id = "rp_id";
|
||||
//! let user_id = [0];
|
||||
//! let user_name = "user_name";
|
||||
//! let client_data_hash = [0; 32];
|
||||
//! let cred = device.make_credential(
|
||||
//! rp_id,
|
||||
//! &user_id,
|
||||
//! user_name,
|
||||
//! &client_data_hash
|
||||
//! )?;
|
||||
//!
|
||||
//! // In a real application the credential would be stored and used later.
|
||||
//! let result = device.get_assertion(&cred, &client_data_hash);
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
extern crate failure;
|
||||
extern crate rand;
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
#[macro_use]
|
||||
extern crate num_derive;
|
||||
extern crate byteorder;
|
||||
extern crate cbor as cbor_codec;
|
||||
extern crate crypto as rust_crypto;
|
||||
extern crate num_traits;
|
||||
extern crate ring;
|
||||
extern crate untrusted;
|
||||
|
||||
mod cbor;
|
||||
mod crypto;
|
||||
mod error;
|
||||
pub mod extensions;
|
||||
mod hid_common;
|
||||
mod hid_linux;
|
||||
mod packet;
|
||||
|
||||
use std::cmp;
|
||||
use std::fs;
|
||||
use std::io::{Cursor, Write};
|
||||
use std::u16;
|
||||
use std::u8;
|
||||
|
||||
pub use self::error::*;
|
||||
use self::hid_linux as hid;
|
||||
use self::packet::CtapCommand;
|
||||
use failure::{Fail, ResultExt};
|
||||
use num_traits::FromPrimitive;
|
||||
use rand::prelude::*;
|
||||
|
||||
static BROADCAST_CID: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
|
||||
|
||||
/// Looks for any connected HID devices and returns those that support FIDO.
|
||||
pub fn get_devices() -> FidoResult<impl Iterator<Item = hid::DeviceInfo>> {
|
||||
hid::enumerate()
|
||||
.context(FidoErrorKind::Io)
|
||||
.map(|devices| devices.filter(|dev| dev.usage_page == 0xf1d0 && dev.usage == 0x21))
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
/// A credential created by a FIDO2 authenticator.
|
||||
#[derive(Debug)]
|
||||
pub struct FidoCredential {
|
||||
/// The ID provided by the authenticator.
|
||||
pub id: Vec<u8>,
|
||||
/// The public key provided by the authenticator, in uncompressed form.
|
||||
pub public_key: Vec<u8>,
|
||||
/// The Relying Party ID provided by the platform when this key was generated.
|
||||
pub rp_id: String,
|
||||
}
|
||||
|
||||
/// An opened FIDO authenticator.
|
||||
pub struct FidoDevice {
|
||||
device: fs::File,
|
||||
packet_size: u16,
|
||||
channel_id: [u8; 4],
|
||||
needs_pin: bool,
|
||||
shared_secret: Option<crypto::SharedSecret>,
|
||||
pin_token: Option<crypto::PinToken>,
|
||||
aaguid: [u8; 16],
|
||||
}
|
||||
|
||||
impl FidoDevice {
|
||||
/// Open and initialize a given device. DeviceInfo is provided by the `get_devices`
|
||||
/// function. This method will allocate a channel for this application, verify that
|
||||
/// it supports FIDO2, and checks if a PIN is set.
|
||||
///
|
||||
/// This method will fail if the device can't be opened, if the device returns
|
||||
/// malformed data or if the device is not supported.
|
||||
pub fn new(device: &hid::DeviceInfo) -> error::FidoResult<Self> {
|
||||
let mut options = fs::OpenOptions::new();
|
||||
options.read(true).write(true);
|
||||
let mut dev = FidoDevice {
|
||||
device: options.open(&device.path).context(FidoErrorKind::Io)?,
|
||||
packet_size: 64,
|
||||
channel_id: BROADCAST_CID,
|
||||
needs_pin: false,
|
||||
shared_secret: None,
|
||||
pin_token: None,
|
||||
aaguid: [0; 16],
|
||||
};
|
||||
dev.init()?;
|
||||
Ok(dev)
|
||||
}
|
||||
|
||||
fn init(&mut self) -> FidoResult<()> {
|
||||
let mut nonce = [0u8; 8];
|
||||
thread_rng().fill_bytes(&mut nonce);
|
||||
let response = self.exchange(CtapCommand::Init, &nonce)?;
|
||||
if response.len() < 17 || response[0..8] != nonce {
|
||||
Err(FidoErrorKind::ParseCtap)?
|
||||
}
|
||||
let flags = response[16];
|
||||
if flags & 0x04 == 0 {
|
||||
Err(FidoErrorKind::DeviceUnsupported)?
|
||||
}
|
||||
self.channel_id.copy_from_slice(&response[8..12]);
|
||||
let response = match self.cbor(cbor::Request::GetInfo)? {
|
||||
cbor::Response::GetInfo(resp) => resp,
|
||||
_ => Err(FidoErrorKind::CborDecode)?,
|
||||
};
|
||||
if !response.versions.iter().any(|ver| ver == "FIDO_2_0") {
|
||||
Err(FidoErrorKind::DeviceUnsupported)?
|
||||
}
|
||||
if !response.pin_protocols.iter().any(|ver| *ver == 1) {
|
||||
Err(FidoErrorKind::DeviceUnsupported)?
|
||||
}
|
||||
self.needs_pin = response.options.client_pin == Some(true);
|
||||
self.aaguid = response.aaguid;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the authenticator's AAGUID. This is not unique to an authenticator,
|
||||
/// but it is unique to the specific brand and model.
|
||||
pub fn aaguid(&self) -> &[u8] {
|
||||
&self.aaguid
|
||||
}
|
||||
|
||||
fn init_shared_secret(&mut self) -> FidoResult<()> {
|
||||
let mut request = cbor::ClientPinRequest::default();
|
||||
request.pin_protocol = 1;
|
||||
request.sub_command = 0x02; // getKeyAgreement
|
||||
let response = match self.cbor(cbor::Request::ClientPin(request))? {
|
||||
cbor::Response::ClientPin(resp) => resp,
|
||||
_ => Err(FidoErrorKind::CborDecode)?,
|
||||
};
|
||||
if let Some(key_agreement) = response.key_agreement {
|
||||
self.shared_secret = Some(crypto::SharedSecret::new(&key_agreement)?);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FidoErrorKind::CborDecode)?
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlock the device with the provided PIN. Internally this will generate
|
||||
/// an ECDH keypair, send the encrypted PIN to the device and store the PIN
|
||||
/// token that the device generates on every power cycle. The PIN itself is
|
||||
/// not stored.
|
||||
///
|
||||
/// This method will fail if the device returns malformed data or the PIN is
|
||||
/// incorrect.
|
||||
pub fn unlock(&mut self, pin: &str) -> FidoResult<()> {
|
||||
while self.shared_secret.is_none() {
|
||||
self.init_shared_secret()?;
|
||||
}
|
||||
// If the PIN is invalid the device should create a new agreementKey,
|
||||
// so we only replace shared_secret on success.
|
||||
let shared_secret = self.shared_secret.take().unwrap();
|
||||
let mut request = cbor::ClientPinRequest::default();
|
||||
request.pin_protocol = 1;
|
||||
request.sub_command = 0x05; // getPINToken
|
||||
request.key_agreement = Some(&shared_secret.public_key);
|
||||
request.pin_hash_enc = Some(shared_secret.encrypt_pin(pin)?);
|
||||
let response = match self.cbor(cbor::Request::ClientPin(request))? {
|
||||
cbor::Response::ClientPin(resp) => resp,
|
||||
_ => Err(FidoErrorKind::CborDecode)?,
|
||||
};
|
||||
if let Some(mut pin_token) = response.pin_token {
|
||||
self.pin_token = Some(shared_secret.decrypt_token(&mut pin_token)?);
|
||||
self.shared_secret = Some(shared_secret);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(FidoErrorKind::CborDecode)?
|
||||
}
|
||||
}
|
||||
|
||||
/// Request a new credential from the authenticator. The `rp_id` should be
|
||||
/// a stable string used to identify the party for whom the credential is
|
||||
/// created, for convenience it will be returned with the credential.
|
||||
/// `user_id` and `user_name` are not required when requesting attestations
|
||||
/// but they MAY be displayed to the user and MAY be stored on the device
|
||||
/// to be returned with an attestation if the device supports this.
|
||||
/// `client_data_hash` SHOULD be a SHA256 hash of provided `client_data`,
|
||||
/// this is only used to verify the attestation provided by the
|
||||
/// authenticator. When not implementing WebAuthN this can be any random
|
||||
/// 32-byte array.
|
||||
///
|
||||
/// This method will fail if a PIN is required but the device is not
|
||||
/// unlocked or if the device returns malformed data.
|
||||
pub fn make_credential(
|
||||
&mut self,
|
||||
rp_id: &str,
|
||||
user_id: &[u8],
|
||||
user_name: &str,
|
||||
client_data_hash: &[u8],
|
||||
) -> FidoResult<FidoCredential> {
|
||||
if self.needs_pin && self.pin_token.is_none() {
|
||||
Err(FidoErrorKind::PinRequired)?
|
||||
}
|
||||
if client_data_hash.len() != 32 {
|
||||
Err(FidoErrorKind::CborEncode)?
|
||||
}
|
||||
let pin_auth = self
|
||||
.pin_token
|
||||
.as_ref()
|
||||
.map(|token| token.auth(&client_data_hash));
|
||||
let rp = cbor::PublicKeyCredentialRpEntity {
|
||||
id: rp_id,
|
||||
name: None,
|
||||
icon: None,
|
||||
};
|
||||
let user = cbor::PublicKeyCredentialUserEntity {
|
||||
id: user_id,
|
||||
name: user_name,
|
||||
icon: None,
|
||||
display_name: None,
|
||||
};
|
||||
let pub_key_cred_params = [("public-key", -7)];
|
||||
let request = cbor::MakeCredentialRequest {
|
||||
client_data_hash,
|
||||
rp,
|
||||
user,
|
||||
pub_key_cred_params: &pub_key_cred_params,
|
||||
exclude_list: Default::default(),
|
||||
extensions: Default::default(),
|
||||
options: Some(cbor::AuthenticatorOptions {
|
||||
rk: false,
|
||||
uv: true,
|
||||
}),
|
||||
pin_auth,
|
||||
pin_protocol: pin_auth.and(Some(0x01)),
|
||||
};
|
||||
let response = match self.cbor(cbor::Request::MakeCredential(request))? {
|
||||
cbor::Response::MakeCredential(resp) => resp,
|
||||
_ => Err(FidoErrorKind::CborDecode)?,
|
||||
};
|
||||
let public_key = cbor::P256Key::from_cose(
|
||||
&response
|
||||
.auth_data
|
||||
.attested_credential_data
|
||||
.credential_public_key,
|
||||
)?
|
||||
.bytes();
|
||||
Ok(FidoCredential {
|
||||
id: response.auth_data.attested_credential_data.credential_id,
|
||||
rp_id: String::from(rp_id),
|
||||
public_key: Vec::from(&public_key[..]),
|
||||
})
|
||||
}
|
||||
|
||||
/// Request an assertion from the authenticator for a given credential.
|
||||
/// `client_data_hash` SHOULD be a SHA256 hash of provided `client_data`,
|
||||
/// this is signed and verified as part of the attestation. When not
|
||||
/// implementing WebAuthN this can be any random 32-byte array.
|
||||
///
|
||||
/// This method will return whether the assertion matches the credential
|
||||
/// provided, and will fail if a PIN is required but not provided or if the
|
||||
/// device returns malformed data.
|
||||
pub fn get_assertion(
|
||||
&mut self,
|
||||
credential: &FidoCredential,
|
||||
client_data_hash: &[u8],
|
||||
) -> FidoResult<bool> {
|
||||
if self.needs_pin && self.pin_token.is_none() {
|
||||
Err(FidoErrorKind::PinRequired)?
|
||||
}
|
||||
if client_data_hash.len() != 32 {
|
||||
Err(FidoErrorKind::CborEncode)?
|
||||
}
|
||||
let pin_auth = self
|
||||
.pin_token
|
||||
.as_ref()
|
||||
.map(|token| token.auth(&client_data_hash));
|
||||
let allow_list = [cbor::PublicKeyCredentialDescriptor {
|
||||
cred_type: String::from("public-key"),
|
||||
id: credential.id.clone(),
|
||||
}];
|
||||
let request = cbor::GetAssertionRequest {
|
||||
rp_id: &credential.rp_id,
|
||||
client_data_hash: client_data_hash,
|
||||
allow_list: &allow_list,
|
||||
extensions: Default::default(),
|
||||
options: Some(cbor::AuthenticatorOptions {
|
||||
rk: false,
|
||||
uv: true,
|
||||
}),
|
||||
pin_auth,
|
||||
pin_protocol: pin_auth.and(Some(0x01)),
|
||||
};
|
||||
let response = match self.cbor(cbor::Request::GetAssertion(request))? {
|
||||
cbor::Response::GetAssertion(resp) => resp,
|
||||
_ => Err(FidoErrorKind::CborDecode)?,
|
||||
};
|
||||
Ok(crypto::verify_signature(
|
||||
&credential.public_key,
|
||||
&client_data_hash,
|
||||
&response.auth_data_bytes,
|
||||
&response.signature,
|
||||
))
|
||||
}
|
||||
|
||||
fn cbor(&mut self, request: cbor::Request) -> FidoResult<cbor::Response> {
|
||||
let mut buf = Cursor::new(Vec::new());
|
||||
request
|
||||
.encode(&mut buf)
|
||||
.context(FidoErrorKind::CborEncode)?;
|
||||
let response = self.exchange(CtapCommand::Cbor, &buf.into_inner())?;
|
||||
request
|
||||
.decode(Cursor::new(response))
|
||||
.context(FidoErrorKind::CborDecode)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
fn exchange(&mut self, cmd: CtapCommand, payload: &[u8]) -> FidoResult<Vec<u8>> {
|
||||
self.send(&cmd, payload)?;
|
||||
self.receive(&cmd)
|
||||
}
|
||||
|
||||
fn send(&mut self, cmd: &CtapCommand, payload: &[u8]) -> FidoResult<()> {
|
||||
if payload.is_empty() || payload.len() > u16::MAX as usize {
|
||||
Err(FidoErrorKind::WritePacket)?
|
||||
}
|
||||
let to_send = payload.len() as u16;
|
||||
let max_payload = (self.packet_size - 7) as usize;
|
||||
let (frame, payload) = payload.split_at(cmp::min(payload.len(), max_payload));
|
||||
packet::write_init_packet(&mut self.device, 64, &self.channel_id, cmd, to_send, frame)?;
|
||||
if payload.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let max_payload = (self.packet_size - 5) as usize;
|
||||
for (seq, frame) in (0..u8::MAX).zip(payload.chunks(max_payload)) {
|
||||
packet::write_cont_packet(&mut self.device, 64, &self.channel_id, seq, frame)?;
|
||||
}
|
||||
self.device.flush().context(FidoErrorKind::WritePacket)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive(&mut self, cmd: &CtapCommand) -> FidoResult<Vec<u8>> {
|
||||
let mut first_packet: Option<packet::InitPacket> = None;
|
||||
while first_packet.is_none() {
|
||||
let packet = packet::InitPacket::from_reader(&mut self.device, 64)?;
|
||||
if packet.cmd == CtapCommand::Error {
|
||||
Err(packet::CtapError::from_u8(packet.payload[0])
|
||||
.unwrap_or(packet::CtapError::Other)
|
||||
.context(FidoErrorKind::ParseCtap))?
|
||||
}
|
||||
if packet.cid == self.channel_id && &packet.cmd == cmd {
|
||||
first_packet = Some(packet);
|
||||
}
|
||||
}
|
||||
let first_packet = first_packet.unwrap();
|
||||
let mut data = first_packet.payload;
|
||||
let mut to_read = (first_packet.size as isize) - data.len() as isize;
|
||||
let mut seq = 0;
|
||||
while to_read > 0 {
|
||||
let packet = packet::ContPacket::from_reader(&mut self.device, 64, to_read as usize)?;
|
||||
if packet.cid != self.channel_id {
|
||||
continue;
|
||||
}
|
||||
if packet.seq != seq {
|
||||
Err(FidoErrorKind::InvalidSequence)?
|
||||
}
|
||||
to_read -= packet.payload.len() as isize;
|
||||
data.extend(&packet.payload);
|
||||
seq += 1;
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
|
||||
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
use super::error::*;
|
||||
use failure::ResultExt;
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
static FRAME_INIT: u8 = 0x80;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(FromPrimitive, ToPrimitive, PartialEq)]
|
||||
pub enum CtapCommand {
|
||||
Invalid = 0x00,
|
||||
Ping = 0x01,
|
||||
Msg = 0x03,
|
||||
Lock = 0x04,
|
||||
Init = 0x06,
|
||||
Wink = 0x08,
|
||||
Cbor = 0x10,
|
||||
Cancel = 0x11,
|
||||
Keepalive = 0x3b,
|
||||
Error = 0x3f,
|
||||
}
|
||||
|
||||
impl CtapCommand {
|
||||
pub fn to_wire_format(&self) -> u8 {
|
||||
match self.to_u8() {
|
||||
Some(x) => x,
|
||||
None => 0x00,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(FromPrimitive, Fail, Debug)]
|
||||
pub enum CtapError {
|
||||
#[fail(display = "The command in the request is invalid")]
|
||||
InvalidCmd = 0x01,
|
||||
#[fail(display = "The parameter(s) in the request is invalid")]
|
||||
InvalidPar = 0x02,
|
||||
#[fail(display = "The length field (BCNT) is invalid for the request ")]
|
||||
InvalidLen = 0x03,
|
||||
#[fail(display = "The sequence does not match expected value ")]
|
||||
InvalidSeq = 0x04,
|
||||
#[fail(display = "The message has timed out ")]
|
||||
MsgTimeout = 0x05,
|
||||
#[fail(display = "The device is busy for the requesting channel ")]
|
||||
ChannelBusy = 0x06,
|
||||
#[fail(display = "Command requires channel lock ")]
|
||||
LockRequired = 0x0A,
|
||||
#[fail(display = "Reserved error")]
|
||||
NA = 0x0B,
|
||||
#[fail(display = "Unspecified error")]
|
||||
Other = 0x7F,
|
||||
}
|
||||
|
||||
pub fn write_init_packet<W: Write>(
|
||||
mut writer: W,
|
||||
report_size: usize,
|
||||
cid: &[u8],
|
||||
cmd: &CtapCommand,
|
||||
size: u16,
|
||||
payload: &[u8],
|
||||
) -> FidoResult<()> {
|
||||
if cid.len() != 4 {
|
||||
Err(FidoErrorKind::WritePacket)?
|
||||
}
|
||||
let mut packet = Vec::with_capacity(report_size);
|
||||
packet.push(0);
|
||||
packet.extend_from_slice(cid);
|
||||
packet.push(FRAME_INIT | cmd.to_wire_format());
|
||||
packet.push(((size >> 8) & 0xff) as u8);
|
||||
packet.push((size & 0xff) as u8);
|
||||
packet.extend_from_slice(payload);
|
||||
if packet.len() > report_size + 1 {
|
||||
Err(FidoErrorKind::WritePacket)?
|
||||
}
|
||||
packet.resize(report_size + 1, 0);
|
||||
writer
|
||||
.write_all(&packet)
|
||||
.context(FidoErrorKind::WritePacket)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct InitPacket {
|
||||
pub cid: [u8; 4],
|
||||
pub cmd: CtapCommand,
|
||||
pub size: u16,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl InitPacket {
|
||||
pub fn from_reader<R: Read>(mut reader: R, report_size: usize) -> FidoResult<InitPacket> {
|
||||
let mut buf = Vec::with_capacity(report_size);
|
||||
buf.resize(report_size, 0);
|
||||
reader
|
||||
.read_exact(&mut buf[0..report_size])
|
||||
.context(FidoErrorKind::ReadPacket)?;
|
||||
let mut cid = [0; 4];
|
||||
cid.copy_from_slice(&buf[0..4]);
|
||||
let cmd = match CtapCommand::from_u8(buf[4] ^ FRAME_INIT) {
|
||||
Some(cmd) => cmd,
|
||||
None => CtapCommand::Invalid,
|
||||
};
|
||||
let size = ((u16::from(buf[5])) << 8) | u16::from(buf[6]);
|
||||
let payload_end = if (size as usize) >= (report_size - 7) {
|
||||
report_size
|
||||
} else {
|
||||
size as usize + 7
|
||||
};
|
||||
let payload = buf.drain(7..payload_end).collect();
|
||||
Ok(InitPacket {
|
||||
cid,
|
||||
cmd,
|
||||
size,
|
||||
payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_cont_packet<W: Write>(
|
||||
mut writer: W,
|
||||
report_size: usize,
|
||||
cid: &[u8],
|
||||
seq: u8,
|
||||
payload: &[u8],
|
||||
) -> FidoResult<()> {
|
||||
if cid.len() != 4 {
|
||||
Err(FidoErrorKind::WritePacket)?
|
||||
}
|
||||
let mut packet = Vec::with_capacity(report_size);
|
||||
packet.push(0);
|
||||
packet.extend_from_slice(cid);
|
||||
packet.push(seq);
|
||||
packet.extend_from_slice(payload);
|
||||
if packet.len() > report_size + 1 {
|
||||
Err(FidoErrorKind::WritePacket)?
|
||||
}
|
||||
packet.resize(report_size + 1, 0);
|
||||
writer
|
||||
.write_all(&packet)
|
||||
.context(FidoErrorKind::WritePacket)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct ContPacket {
|
||||
pub cid: [u8; 4],
|
||||
pub seq: u8,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ContPacket {
|
||||
pub fn from_reader<R: Read>(
|
||||
mut reader: R,
|
||||
report_size: usize,
|
||||
expected_data: usize,
|
||||
) -> FidoResult<ContPacket> {
|
||||
let mut buf = Vec::with_capacity(report_size);
|
||||
buf.resize(report_size, 0);
|
||||
reader
|
||||
.read_exact(&mut buf[0..report_size])
|
||||
.context(FidoErrorKind::ReadPacket)?;
|
||||
let mut cid = [0; 4];
|
||||
cid.copy_from_slice(&buf[0..4]);
|
||||
let seq = buf[4];
|
||||
let payload_end = if expected_data >= (report_size - 5) {
|
||||
report_size
|
||||
} else {
|
||||
expected_data + 5
|
||||
};
|
||||
let payload = buf.drain(5..payload_end).collect();
|
||||
Ok(ContPacket { cid, seq, payload })
|
||||
}
|
||||
}
|
643
src/cli.rs
643
src/cli.rs
@ -1,264 +1,339 @@
|
||||
use crate::error::*;
|
||||
use crate::luks::{Fido2LuksToken, LuksDevice};
|
||||
use crate::util::sha256;
|
||||
use crate::*;
|
||||
use cli_args::*;
|
||||
|
||||
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::clap::Shell;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::exit;
|
||||
use ctap::{FidoCredential, FidoErrorKind};
|
||||
|
||||
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!()
|
||||
}
|
||||
let dev =
|
||||
|| -> luks::device::Result<CryptDeviceOpenBuilder> { luks::open(&device.canonicalize()?) };
|
||||
use std::io::{Read, Write};
|
||||
use std::str::FromStr;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
let prev_key = old_secret()?;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::time::SystemTime;
|
||||
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(slot)
|
||||
}
|
||||
pub use cli_args::Args;
|
||||
|
||||
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)?)
|
||||
fn read_pin(ap: &AuthenticatorParameters) -> Fido2LuksResult<String> {
|
||||
if let Some(src) = ap.pin_source.as_ref() {
|
||||
let mut pin = String::new();
|
||||
File::open(src)?.read_to_string(&mut pin)?;
|
||||
Ok(pin.trim_end_matches("\n").to_string()) //remove trailing newline
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let slot = handle.update_keyslot(&new_secret()?[..], &secret[..], prev_slot)?;
|
||||
Ok(slot)
|
||||
util::read_password("Authenticator PIN", false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn authenticator_connected() -> Fido2LuksResult<bool> {
|
||||
Ok(!device::get_devices()?.is_empty())
|
||||
}
|
||||
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();
|
||||
|
||||
#[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 SecretGeneration {
|
||||
/// FIDO credential id, generate using fido2luks credential
|
||||
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
|
||||
pub credential_id: String,
|
||||
/// Salt for secret generation, defaults to Password
|
||||
#[structopt(name = "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/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;
|
||||
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)?;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum Command {
|
||||
#[structopt(name = "print-secret")]
|
||||
PrintSecret {
|
||||
/// Prints the secret as binary instead of hex encoded
|
||||
#[structopt(short = "b", long = "bin")]
|
||||
binary: bool,
|
||||
#[structopt(flatten)]
|
||||
secret_gen: SecretGeneration,
|
||||
},
|
||||
/// Adds a generated key to the specified LUKS device
|
||||
#[structopt(name = "add-key")]
|
||||
AddKey {
|
||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||
device: PathBuf,
|
||||
/// 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>,
|
||||
#[structopt(flatten)]
|
||||
secret_gen: SecretGeneration,
|
||||
},
|
||||
|
||||
/// Replace a previously added key with a password
|
||||
#[structopt(name = "replace-key")]
|
||||
ReplaceKey {
|
||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||
device: PathBuf,
|
||||
/// 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>,
|
||||
#[structopt(flatten)]
|
||||
secret_gen: SecretGeneration,
|
||||
},
|
||||
/// Open the LUKS device
|
||||
#[structopt(name = "open")]
|
||||
Open {
|
||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||
device: PathBuf,
|
||||
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
|
||||
name: String,
|
||||
#[structopt(flatten)]
|
||||
secret_gen: SecretGeneration,
|
||||
},
|
||||
/// Generate a new FIDO credential
|
||||
#[structopt(name = "credential")]
|
||||
Credential,
|
||||
/// Check if an authenticator is connected
|
||||
#[structopt(name = "connected")]
|
||||
Connected,
|
||||
Ok((sha256(&[salt, &unsalted[..]]), cred.clone()))
|
||||
}
|
||||
|
||||
pub fn parse_cmdline() -> Args {
|
||||
Args::from_args()
|
||||
}
|
||||
|
||||
pub fn prompt_interaction(interactive: bool) {
|
||||
if interactive {
|
||||
println!("Authorize using your FIDO device");
|
||||
}
|
||||
}
|
||||
|
||||
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(authenticator)?;
|
||||
Some(pin_string.as_ref())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cred = make_credential_id(Some(name.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(authenticator)?;
|
||||
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_sha256(&secret.password_helper)
|
||||
}?;
|
||||
prompt_interaction(interactive);
|
||||
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(authenticator)?)
|
||||
} 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_sha256(&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, ..
|
||||
} => {
|
||||
prompt_interaction(interactive);
|
||||
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 = |q: &str, verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> {
|
||||
prompt_interaction(interactive);
|
||||
derive_secret(
|
||||
&credentials.ids.0,
|
||||
&salt(q, verify)?,
|
||||
authenticator.await_time,
|
||||
pin.as_deref(),
|
||||
)
|
||||
};
|
||||
let mut luks_dev = LuksDevice::load(&luks.device)?;
|
||||
// Non overlap
|
||||
match &args.command {
|
||||
Command::AddKey { exclusive, .. } => {
|
||||
let (existing_secret, _) = other_secret("Current password", false)?;
|
||||
let (new_secret, cred) = secret("Password to be added", true)?;
|
||||
let added_slot = luks_dev.add_key(
|
||||
&new_secret,
|
||||
&existing_secret[..],
|
||||
luks_mod.kdf_time.or(Some(10)),
|
||||
Some(&cred.id[..]).filter(|_| *token),
|
||||
)?;
|
||||
if *exclusive {
|
||||
let destroyed = luks_dev.remove_keyslots(&[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("Current password", false)?;
|
||||
let (replacement_secret, cred) = other_secret("Replacement password", true)?;
|
||||
let slot = if *add_password {
|
||||
luks_dev.add_key(
|
||||
&replacement_secret[..],
|
||||
&existing_secret,
|
||||
luks_mod.kdf_time,
|
||||
cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]),
|
||||
)
|
||||
} else {
|
||||
luks_dev.replace_key(
|
||||
&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,
|
||||
allow_discards,
|
||||
..
|
||||
}
|
||||
| Command::OpenToken {
|
||||
luks,
|
||||
authenticator,
|
||||
secret,
|
||||
name,
|
||||
retries,
|
||||
allow_discards,
|
||||
} => {
|
||||
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(authenticator)?;
|
||||
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_sha256(&secret.password_helper)
|
||||
}
|
||||
};
|
||||
|
||||
// Cow shouldn't be necessary
|
||||
let secret = |credentials: Cow<'_, Vec<HexEncoded>>| {
|
||||
prompt_interaction(interactive);
|
||||
derive_secret(
|
||||
credentials.as_ref(),
|
||||
&salt("Password", false)?,
|
||||
authenticator.await_time,
|
||||
pin,
|
||||
)
|
||||
};
|
||||
|
||||
let mut retries = *retries;
|
||||
let mut luks_dev = LuksDevice::load(&luks.device)?;
|
||||
loop {
|
||||
let secret = match &args.command {
|
||||
Command::Open { credentials, .. } => secret(Cow::Borrowed(&credentials.ids.0))
|
||||
.and_then(|(secret, _cred)| {
|
||||
luks_dev.activate(&name, &secret, luks.slot, *allow_discards)
|
||||
}),
|
||||
Command::OpenToken { .. } => luks_dev.activate_token(
|
||||
&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)))
|
||||
}),
|
||||
luks.slot,
|
||||
*allow_discards,
|
||||
),
|
||||
_ => 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.map(|_| ()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Connected => match get_devices() {
|
||||
Ok(ref devs) if !devs.is_empty() => {
|
||||
@ -267,5 +342,141 @@ pub fn run_cli() -> Fido2LuksResult<()> {
|
||||
}
|
||||
_ => exit(1),
|
||||
},
|
||||
Command::Token(cmd) => match cmd {
|
||||
TokenCommand::List {
|
||||
device,
|
||||
csv: dump_credentials,
|
||||
} => {
|
||||
let mut dev = LuksDevice::load(device)?;
|
||||
let mut creds = Vec::new();
|
||||
for token in dev.tokens()? {
|
||||
let (id, token) = token?;
|
||||
for cred in token.credential.iter() {
|
||||
if !creds.contains(cred) {
|
||||
creds.push(cred.clone());
|
||||
if *dump_credentials {
|
||||
print!("{}{}", if creds.len() == 1 { "" } else { "," }, cred);
|
||||
}
|
||||
}
|
||||
}
|
||||
if *dump_credentials {
|
||||
continue;
|
||||
}
|
||||
println!(
|
||||
"{}:\n\tSlots: {}\n\tCredentials: {}",
|
||||
id,
|
||||
if token.keyslots.is_empty() {
|
||||
"None".into()
|
||||
} else {
|
||||
token.keyslots.iter().cloned().collect::<Vec<_>>().join(",")
|
||||
},
|
||||
token
|
||||
.credential
|
||||
.iter()
|
||||
.map(|cred| format!(
|
||||
"{} ({})",
|
||||
cred,
|
||||
creds.iter().position(|c| c == cred).unwrap().to_string()
|
||||
))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
);
|
||||
}
|
||||
if *dump_credentials {
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
TokenCommand::Add {
|
||||
device,
|
||||
credentials,
|
||||
slot,
|
||||
} => {
|
||||
let mut dev = LuksDevice::load(device)?;
|
||||
let mut tokens = Vec::new();
|
||||
for token in dev.tokens()? {
|
||||
let (id, token) = token?;
|
||||
if token.keyslots.contains(&slot.to_string()) {
|
||||
tokens.push((id, token));
|
||||
}
|
||||
}
|
||||
let count = if tokens.is_empty() {
|
||||
dev.add_token(&Fido2LuksToken::with_credentials(&credentials.ids.0, *slot))?;
|
||||
1
|
||||
} else {
|
||||
tokens.len()
|
||||
};
|
||||
for (id, mut token) in tokens {
|
||||
token
|
||||
.credential
|
||||
.extend(credentials.ids.0.iter().map(|h| h.to_string()));
|
||||
dev.update_token(id, &token)?;
|
||||
}
|
||||
println!("Updated {} tokens", count);
|
||||
Ok(())
|
||||
}
|
||||
TokenCommand::Remove {
|
||||
device,
|
||||
credentials,
|
||||
token_id,
|
||||
} => {
|
||||
let mut dev = LuksDevice::load(device)?;
|
||||
let mut tokens = Vec::new();
|
||||
for token in dev.tokens()? {
|
||||
let (id, token) = token?;
|
||||
if let Some(token_id) = token_id {
|
||||
if id == *token_id {
|
||||
tokens.push((id, token));
|
||||
}
|
||||
} else {
|
||||
tokens.push((id, token));
|
||||
}
|
||||
}
|
||||
let count = tokens.len();
|
||||
for (id, mut token) in tokens {
|
||||
token.credential = token
|
||||
.credential
|
||||
.into_iter()
|
||||
.filter(|cred| !credentials.ids.0.iter().any(|h| &h.to_string() == cred))
|
||||
.collect();
|
||||
dev.update_token(id, &token)?;
|
||||
}
|
||||
println!("Updated {} tokens", count);
|
||||
Ok(())
|
||||
}
|
||||
TokenCommand::GC { device } => {
|
||||
let mut dev = LuksDevice::load(device)?;
|
||||
let mut creds: HashSet<String> = HashSet::new();
|
||||
let mut remove = Vec::new();
|
||||
for token in dev.tokens()? {
|
||||
let (id, token) = token?;
|
||||
if token.keyslots.is_empty() || token.credential.is_empty() {
|
||||
creds.extend(token.credential);
|
||||
remove.push(id);
|
||||
}
|
||||
}
|
||||
for id in remove.iter().rev() {
|
||||
dev.remove_token(*id)?;
|
||||
}
|
||||
println!(
|
||||
"Removed {} tokens, affected credentials: {}",
|
||||
remove.len(),
|
||||
creds.into_iter().collect::<Vec<_>>().join(",")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Command::GenerateCompletions { shell, out_dir } => {
|
||||
Args::clap().gen_completions(
|
||||
env!("CARGO_PKG_NAME"),
|
||||
match shell.as_ref() {
|
||||
"bash" => Shell::Bash,
|
||||
"fish" => Shell::Fish,
|
||||
_ => unreachable!("structopt shouldn't allow us to reach this point"),
|
||||
},
|
||||
&out_dir,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
209
src/cli_args/config.rs
Normal file
209
src/cli_args/config.rs
Normal file
@ -0,0 +1,209 @@
|
||||
use crate::error::*;
|
||||
use crate::*;
|
||||
use ring::digest;
|
||||
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SecretInput {
|
||||
AskPassword,
|
||||
String(String),
|
||||
File { path: PathBuf },
|
||||
}
|
||||
|
||||
impl Default for SecretInput {
|
||||
fn default() -> Self {
|
||||
SecretInput::AskPassword
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for SecretInput {
|
||||
fn from(s: &str) -> Self {
|
||||
let mut parts = s.split(':');
|
||||
match parts.next() {
|
||||
Some("ask") | Some("Ask") => SecretInput::AskPassword,
|
||||
Some("file") => SecretInput::File {
|
||||
path: parts.collect::<Vec<_>>().join(":").into(),
|
||||
},
|
||||
Some("string") => SecretInput::String(parts.collect::<Vec<_>>().join(":")),
|
||||
_ => Self::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SecretInput {
|
||||
type Err = Fido2LuksError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SecretInput {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.write_str(&match self {
|
||||
SecretInput::AskPassword => "ask".to_string(),
|
||||
SecretInput::String(s) => ["string", s].join(":"),
|
||||
SecretInput::File { path } => ["file", path.display().to_string().as_str()].join(":"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretInput {
|
||||
pub fn obtain_string(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<String> {
|
||||
Ok(String::from_utf8(self.obtain(password_helper)?)?)
|
||||
}
|
||||
|
||||
pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<Vec<u8>> {
|
||||
let mut secret = Vec::new();
|
||||
match self {
|
||||
SecretInput::File { path } => {
|
||||
//TODO: replace with try_blocks
|
||||
let mut do_io = || File::open(path)?.read_to_end(&mut secret);
|
||||
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
|
||||
}
|
||||
SecretInput::AskPassword => {
|
||||
secret.extend_from_slice(password_helper.obtain()?.as_bytes())
|
||||
}
|
||||
|
||||
SecretInput::String(s) => secret.extend_from_slice(s.as_bytes()),
|
||||
}
|
||||
Ok(secret)
|
||||
}
|
||||
|
||||
pub fn obtain_sha256(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> {
|
||||
let mut digest = digest::Context::new(&digest::SHA256);
|
||||
match self {
|
||||
SecretInput::File { path } => {
|
||||
let mut do_io = || {
|
||||
let mut reader = File::open(path)?;
|
||||
let mut buf = [0u8; 512];
|
||||
loop {
|
||||
let red = reader.read(&mut buf)?;
|
||||
digest.update(&buf[0..red]);
|
||||
if red == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
|
||||
}
|
||||
_ => digest.update(self.obtain(password_helper)?.as_slice()),
|
||||
}
|
||||
let mut salt = [0u8; 32];
|
||||
salt.as_mut().copy_from_slice(digest.finish().as_ref());
|
||||
Ok(salt)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PasswordHelper {
|
||||
Script(String),
|
||||
#[allow(dead_code)]
|
||||
Systemd,
|
||||
Stdin,
|
||||
}
|
||||
|
||||
impl Default for PasswordHelper {
|
||||
fn default() -> Self {
|
||||
PasswordHelper::Script(
|
||||
"/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PasswordHelper {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"stdin" => PasswordHelper::Stdin,
|
||||
s => PasswordHelper::Script(s.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PasswordHelper {
|
||||
type Err = Fido2LuksError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PasswordHelper {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.write_str(&match self {
|
||||
PasswordHelper::Stdin => "stdin".to_string(),
|
||||
PasswordHelper::Systemd => "systemd".to_string(),
|
||||
PasswordHelper::Script(path) => path.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PasswordHelper {
|
||||
pub fn obtain(&self) -> Fido2LuksResult<String> {
|
||||
use PasswordHelper::*;
|
||||
match self {
|
||||
Systemd => unimplemented!(),
|
||||
Stdin => Ok(util::read_password("Password", true)?),
|
||||
Script(password_helper) => {
|
||||
let password = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(&password_helper)
|
||||
.output()
|
||||
.map_err(|e| Fido2LuksError::AskPassError {
|
||||
cause: error::AskPassError::IO(e),
|
||||
})?
|
||||
.stdout;
|
||||
Ok(String::from_utf8(password)?.trim().to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn input_salt_from_str() {
|
||||
assert_eq!(
|
||||
"file:/tmp/abc".parse::<SecretInput>().unwrap(),
|
||||
SecretInput::File {
|
||||
path: "/tmp/abc".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
"string:abc".parse::<SecretInput>().unwrap(),
|
||||
SecretInput::String("abc".into())
|
||||
);
|
||||
assert_eq!(
|
||||
"ask".parse::<SecretInput>().unwrap(),
|
||||
SecretInput::AskPassword
|
||||
);
|
||||
assert_eq!(
|
||||
"lol".parse::<SecretInput>().unwrap(),
|
||||
SecretInput::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_salt_obtain() {
|
||||
assert_eq!(
|
||||
SecretInput::String("abc".into())
|
||||
.obtain_sha256(&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
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
299
src/cli_args/mod.rs
Normal file
299
src/cli_args/mod.rs
Normal file
@ -0,0 +1,299 @@
|
||||
use std::fmt::{Display, Error, Formatter};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use structopt::clap::AppSettings;
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod config;
|
||||
|
||||
pub use config::*;
|
||||
|
||||
#[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))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for HexEncoded {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for HexEncoded {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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 Credentials {
|
||||
/// FIDO credential ids, separated 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,
|
||||
|
||||
/// Location to read PIN from
|
||||
#[structopt(long = "pin-source", env = "FIDO2LUKS_PIN_SOURCE")]
|
||||
pub pin_source: Option<PathBuf>,
|
||||
|
||||
/// 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")]
|
||||
pub device: PathBuf,
|
||||
|
||||
/// Try to unlock the device using a specifc keyslot, ignore all other slots
|
||||
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
|
||||
pub slot: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt, Clone)]
|
||||
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")]
|
||||
pub kdf_time: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct SecretParameters {
|
||||
/// 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: SecretInput,
|
||||
/// 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,
|
||||
}
|
||||
#[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")]
|
||||
pub 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")]
|
||||
pub fido_device: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum Command {
|
||||
#[structopt(name = "print-secret")]
|
||||
PrintSecret {
|
||||
/// Prints the secret as binary instead of hex encoded
|
||||
#[structopt(short = "b", long = "bin")]
|
||||
binary: bool,
|
||||
#[structopt(flatten)]
|
||||
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(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,
|
||||
/// Will add an token to your LUKS 2 header, including the credential id
|
||||
#[structopt(short = "t", long = "token")]
|
||||
token: bool,
|
||||
#[structopt(flatten)]
|
||||
existing_secret: OtherSecret,
|
||||
#[structopt(flatten)]
|
||||
luks_mod: LuksModParameters,
|
||||
},
|
||||
/// Replace a previously added key with a password
|
||||
#[structopt(name = "replace-key")]
|
||||
ReplaceKey {
|
||||
#[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,
|
||||
/// Will add an token to your LUKS 2 header, including the credential id
|
||||
#[structopt(short = "t", long = "token")]
|
||||
token: bool,
|
||||
#[structopt(flatten)]
|
||||
replacement: OtherSecret,
|
||||
#[structopt(flatten)]
|
||||
luks_mod: LuksModParameters,
|
||||
},
|
||||
/// Open the LUKS device
|
||||
#[structopt(name = "open")]
|
||||
Open {
|
||||
#[structopt(flatten)]
|
||||
luks: LuksParameters,
|
||||
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
|
||||
name: String,
|
||||
#[structopt(flatten)]
|
||||
credentials: Credentials,
|
||||
#[structopt(flatten)]
|
||||
authenticator: AuthenticatorParameters,
|
||||
#[structopt(flatten)]
|
||||
secret: SecretParameters,
|
||||
#[structopt(short = "r", long = "max-retries", default_value = "0")]
|
||||
retries: i32,
|
||||
/// Pass SSD trim instructions to the underlying block device
|
||||
#[structopt(long = "allow-discards")]
|
||||
allow_discards: bool,
|
||||
},
|
||||
/// 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,
|
||||
/// Pass SSD trim instructions to the underlying block device
|
||||
#[structopt(long = "allow-discards")]
|
||||
allow_discards: bool,
|
||||
},
|
||||
/// Generate a new FIDO credential
|
||||
#[structopt(name = "credential")]
|
||||
Credential {
|
||||
#[structopt(flatten)]
|
||||
authenticator: AuthenticatorParameters,
|
||||
/// Name to be displayed on the authenticator display
|
||||
#[structopt(env = "FIDO2LUKS_CREDENTIAL_NAME", default_value = "fido2luks")]
|
||||
name: String,
|
||||
},
|
||||
/// Check if an authenticator is connected
|
||||
#[structopt(name = "connected")]
|
||||
Connected,
|
||||
Token(TokenCommand),
|
||||
/// Generate bash completion scripts
|
||||
#[structopt(name = "completions", setting = AppSettings::Hidden)]
|
||||
GenerateCompletions {
|
||||
/// Shell to generate completions for: bash, fish
|
||||
#[structopt(possible_values = &["bash", "fish"])]
|
||||
shell: String,
|
||||
out_dir: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
///LUKS2 token related operations
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum TokenCommand {
|
||||
/// List all tokens associated with the specified device
|
||||
List {
|
||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||
device: PathBuf,
|
||||
/// Dump all credentials as CSV
|
||||
#[structopt(long = "csv")]
|
||||
csv: bool,
|
||||
},
|
||||
/// Add credential to a keyslot
|
||||
Add {
|
||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||
device: PathBuf,
|
||||
#[structopt(flatten)]
|
||||
credentials: Credentials,
|
||||
/// Slot to which the credentials will be added
|
||||
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
|
||||
slot: u32,
|
||||
},
|
||||
/// Remove credentials from token(s)
|
||||
Remove {
|
||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||
device: PathBuf,
|
||||
#[structopt(flatten)]
|
||||
credentials: Credentials,
|
||||
/// Token from which the credentials will be removed
|
||||
#[structopt(long = "token")]
|
||||
token_id: Option<u32>,
|
||||
},
|
||||
/// Remove all unassigned tokens
|
||||
GC {
|
||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||
device: PathBuf,
|
||||
},
|
||||
}
|
150
src/config.rs
150
src/config.rs
@ -1,150 +0,0 @@
|
||||
use crate::error::*;
|
||||
use crate::*;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha2::Sha256;
|
||||
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum InputSalt {
|
||||
AskPassword,
|
||||
File { path: PathBuf },
|
||||
Both { path: PathBuf },
|
||||
}
|
||||
|
||||
impl Default for InputSalt {
|
||||
fn default() -> Self {
|
||||
InputSalt::AskPassword
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for InputSalt {
|
||||
fn from(s: &str) -> Self {
|
||||
if PathBuf::from(s).exists() && s != "Ask" {
|
||||
InputSalt::File { path: s.into() }
|
||||
} else {
|
||||
InputSalt::AskPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for InputSalt {
|
||||
type Err = Fido2LuksError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InputSalt {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.write_str(&match self {
|
||||
InputSalt::AskPassword => "ask".to_string(),
|
||||
InputSalt::File { path } => path.display().to_string(),
|
||||
InputSalt::Both { path } => ["ask", path.display().to_string().as_str()].join(" + "),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSalt {
|
||||
pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> {
|
||||
let mut digest = Sha256::new();
|
||||
match self {
|
||||
InputSalt::File { path } => {
|
||||
let mut do_io = || {
|
||||
let mut reader = File::open(path)?;
|
||||
let mut buf = [0u8; 512];
|
||||
loop {
|
||||
let red = reader.read(&mut buf)?;
|
||||
digest.input(&buf[0..red]);
|
||||
if red == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
|
||||
}
|
||||
InputSalt::AskPassword => {
|
||||
digest.input(password_helper.obtain()?.as_bytes());
|
||||
}
|
||||
InputSalt::Both { path } => {
|
||||
digest.input(&InputSalt::AskPassword.obtain(password_helper)?);
|
||||
digest.input(&InputSalt::File { path: path.clone() }.obtain(password_helper)?)
|
||||
}
|
||||
}
|
||||
let mut salt = [0u8; 32];
|
||||
digest.result(&mut salt);
|
||||
Ok(salt)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PasswordHelper {
|
||||
Script(String),
|
||||
Systemd,
|
||||
Stdin,
|
||||
}
|
||||
|
||||
impl Default for PasswordHelper {
|
||||
fn default() -> Self {
|
||||
PasswordHelper::Script(
|
||||
"/usr/bin/systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PasswordHelper {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"stdin" => PasswordHelper::Stdin,
|
||||
s => PasswordHelper::Script(s.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PasswordHelper {
|
||||
type Err = Fido2LuksError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PasswordHelper {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.write_str(&match self {
|
||||
PasswordHelper::Stdin => "stdin".to_string(),
|
||||
PasswordHelper::Systemd => "systemd".to_string(),
|
||||
PasswordHelper::Script(path) => path.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PasswordHelper {
|
||||
pub fn obtain(&self) -> Fido2LuksResult<String> {
|
||||
use PasswordHelper::*;
|
||||
match self {
|
||||
Systemd => unimplemented!(),
|
||||
Stdin => Ok(util::read_password("Password", true)?),
|
||||
Script(password_helper) => {
|
||||
let mut helper_parts = password_helper.split(" ");
|
||||
|
||||
let password = Command::new((&mut helper_parts).next().unwrap())
|
||||
.args(helper_parts)
|
||||
.output()
|
||||
.map_err(|e| Fido2LuksError::AskPassError {
|
||||
cause: error::AskPassError::IO(e),
|
||||
})?
|
||||
.stdout;
|
||||
Ok(String::from_utf8(password)?.trim().to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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),
|
||||
}
|
||||
|
72
src/error.rs
72
src/error.rs
@ -1,5 +1,9 @@
|
||||
use ctap::FidoError;
|
||||
use libcryptsetup_rs::LibcryptErr;
|
||||
use std::io;
|
||||
use std::io::ErrorKind;
|
||||
use std::string::FromUtf8Error;
|
||||
use Fido2LuksError::*;
|
||||
|
||||
pub type Fido2LuksResult<T> = Result<T, Fido2LuksError>;
|
||||
|
||||
@ -11,11 +15,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 +31,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,8 +52,38 @@ pub enum AskPassError {
|
||||
Mismatch,
|
||||
}
|
||||
|
||||
use std::string::FromUtf8Error;
|
||||
use Fido2LuksError::*;
|
||||
#[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 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LuksError> for Fido2LuksError {
|
||||
fn from(e: LuksError) -> Self {
|
||||
Fido2LuksError::LuksError { cause: e }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FidoError> for Fido2LuksError {
|
||||
fn from(e: FidoError) -> Self {
|
||||
@ -40,17 +91,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 }
|
||||
|
333
src/luks.rs
Normal file
333
src/luks.rs
Normal file
@ -0,0 +1,333 @@
|
||||
use crate::error::*;
|
||||
|
||||
use libcryptsetup_rs::{
|
||||
CryptActivateFlag, CryptActivateFlags, CryptDevice, CryptInit, CryptTokenInfo,
|
||||
EncryptionFormat, KeyslotInfo, TokenInput,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct LuksDevice {
|
||||
device: CryptDevice,
|
||||
luks2: Option<bool>,
|
||||
}
|
||||
|
||||
impl LuksDevice {
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Fido2LuksResult<LuksDevice> {
|
||||
let mut device = CryptInit::init(path.as_ref())?;
|
||||
device.context_handle().load::<()>(None, None)?;
|
||||
Ok(Self {
|
||||
device,
|
||||
luks2: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_luks2(&mut self) -> Fido2LuksResult<bool> {
|
||||
if let Some(luks2) = self.luks2 {
|
||||
Ok(luks2)
|
||||
} else {
|
||||
self.luks2 = Some(match self.device.format_handle().get_type()? {
|
||||
EncryptionFormat::Luks2 => true,
|
||||
_ => false,
|
||||
});
|
||||
self.is_luks2()
|
||||
}
|
||||
}
|
||||
|
||||
fn require_luks2(&mut self) -> Fido2LuksResult<()> {
|
||||
if !self.is_luks2()? {
|
||||
return Err(LuksError::Luks2Required.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tokens<'a>(
|
||||
&'a mut self,
|
||||
) -> Fido2LuksResult<Box<dyn Iterator<Item = Fido2LuksResult<(u32, Fido2LuksToken)>> + 'a>>
|
||||
{
|
||||
self.require_luks2()?;
|
||||
Ok(Box::new(
|
||||
(0..32)
|
||||
.map(move |i| {
|
||||
let status = match self.device.token_handle().status(i) {
|
||||
Ok(status) => status,
|
||||
Err(err) => return Some(Err(Fido2LuksError::from(err))),
|
||||
};
|
||||
match status {
|
||||
CryptTokenInfo::Inactive => return None,
|
||||
CryptTokenInfo::Internal(s)
|
||||
| CryptTokenInfo::InternalUnknown(s)
|
||||
| CryptTokenInfo::ExternalUnknown(s)
|
||||
| CryptTokenInfo::External(s)
|
||||
if &s != Fido2LuksToken::default_type() =>
|
||||
{
|
||||
return None
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
let json = match self.device.token_handle().json_get(i) {
|
||||
Ok(json) => json,
|
||||
Err(err) => return Some(Err(Fido2LuksError::from(err))),
|
||||
};
|
||||
let info: Fido2LuksToken =
|
||||
match serde_json::from_value(json.clone()).map_err(|_| {
|
||||
Fido2LuksError::LuksError {
|
||||
cause: LuksError::InvalidToken(json.to_string()),
|
||||
}
|
||||
}) {
|
||||
Ok(info) => info,
|
||||
Err(err) => return Some(Err(Fido2LuksError::from(err))),
|
||||
};
|
||||
Some(Ok((i, info)))
|
||||
})
|
||||
.filter_map(|o| o),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn find_token(&mut self, slot: u32) -> Fido2LuksResult<Option<(u32, Fido2LuksToken)>> {
|
||||
let slot_str = slot.to_string();
|
||||
for token in self.tokens()? {
|
||||
let (id, token) = token?;
|
||||
if token.keyslots.contains(&slot_str) {
|
||||
return Ok(Some((id, token)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn add_token(&mut self, data: &Fido2LuksToken) -> Fido2LuksResult<()> {
|
||||
self.require_luks2()?;
|
||||
self.device
|
||||
.token_handle()
|
||||
.json_set(TokenInput::AddToken(&serde_json::to_value(&data).unwrap()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> {
|
||||
self.require_luks2()?;
|
||||
self.device
|
||||
.token_handle()
|
||||
.json_set(TokenInput::RemoveToken(token))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_token(&mut self, token: u32, data: &Fido2LuksToken) -> Fido2LuksResult<()> {
|
||||
self.require_luks2()?;
|
||||
self.device
|
||||
.token_handle()
|
||||
.json_set(TokenInput::ReplaceToken(
|
||||
token,
|
||||
&serde_json::to_value(&data).unwrap(),
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_key(
|
||||
&mut self,
|
||||
secret: &[u8],
|
||||
old_secret: &[u8],
|
||||
iteration_time: Option<u64>,
|
||||
credential_id: Option<&[u8]>,
|
||||
) -> Fido2LuksResult<u32> {
|
||||
if let Some(millis) = iteration_time {
|
||||
self.device.settings_handle().set_iteration_time(millis)
|
||||
}
|
||||
let slot = self
|
||||
.device
|
||||
.keyslot_handle()
|
||||
.add_by_passphrase(None, old_secret, secret)?;
|
||||
if let Some(id) = credential_id {
|
||||
self.device.token_handle().json_set(TokenInput::AddToken(
|
||||
&serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap(),
|
||||
))?;
|
||||
}
|
||||
|
||||
Ok(slot)
|
||||
}
|
||||
|
||||
pub fn remove_keyslots(&mut self, exclude: &[u32]) -> Fido2LuksResult<u32> {
|
||||
let mut destroyed = 0;
|
||||
let mut tokens = Vec::new();
|
||||
for slot in 0..256 {
|
||||
match self.device.keyslot_handle().status(slot)? {
|
||||
KeyslotInfo::Inactive => continue,
|
||||
KeyslotInfo::Active | KeyslotInfo::ActiveLast if !exclude.contains(&slot) => {
|
||||
if self.is_luks2()? {
|
||||
if let Some((id, _token)) = self.find_token(slot)? {
|
||||
tokens.push(id);
|
||||
}
|
||||
}
|
||||
self.device.keyslot_handle().destroy(slot)?;
|
||||
destroyed += 1;
|
||||
}
|
||||
KeyslotInfo::ActiveLast => break,
|
||||
_ => (),
|
||||
}
|
||||
if self.device.keyslot_handle().status(slot)? == KeyslotInfo::ActiveLast {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Ensure indices stay valid
|
||||
tokens.sort();
|
||||
for token in tokens.iter().rev() {
|
||||
self.remove_token(*token)?;
|
||||
}
|
||||
Ok(destroyed)
|
||||
}
|
||||
|
||||
pub fn replace_key(
|
||||
&mut self,
|
||||
secret: &[u8],
|
||||
old_secret: &[u8],
|
||||
iteration_time: Option<u64>,
|
||||
credential_id: Option<&[u8]>,
|
||||
) -> Fido2LuksResult<u32> {
|
||||
if let Some(millis) = iteration_time {
|
||||
self.device.settings_handle().set_iteration_time(millis)
|
||||
}
|
||||
// Use activate dry-run to locate keyslot
|
||||
let slot = self.device.activate_handle().activate_by_passphrase(
|
||||
None,
|
||||
None,
|
||||
old_secret,
|
||||
CryptActivateFlags::empty(),
|
||||
)?;
|
||||
self.device.keyslot_handle().change_by_passphrase(
|
||||
Some(slot),
|
||||
Some(slot),
|
||||
old_secret,
|
||||
secret,
|
||||
)? as u32;
|
||||
if let Some(id) = credential_id {
|
||||
if self.is_luks2()? {
|
||||
let token = self.find_token(slot)?.map(|(t, _)| t);
|
||||
let json = serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap();
|
||||
if let Some(token) = token {
|
||||
self.device
|
||||
.token_handle()
|
||||
.json_set(TokenInput::ReplaceToken(token, &json))?;
|
||||
} else {
|
||||
self.device
|
||||
.token_handle()
|
||||
.json_set(TokenInput::AddToken(&json))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(slot)
|
||||
}
|
||||
|
||||
pub fn activate(
|
||||
&mut self,
|
||||
name: &str,
|
||||
secret: &[u8],
|
||||
slot_hint: Option<u32>,
|
||||
allow_discard: bool,
|
||||
) -> Fido2LuksResult<u32> {
|
||||
let mut flags = CryptActivateFlags::empty();
|
||||
if allow_discard {
|
||||
flags = CryptActivateFlags::new(vec![CryptActivateFlag::AllowDiscards]);
|
||||
}
|
||||
self.device
|
||||
.activate_handle()
|
||||
.activate_by_passphrase(Some(name), slot_hint, secret, flags)
|
||||
.map_err(LuksError::activate)
|
||||
}
|
||||
|
||||
pub fn activate_token(
|
||||
&mut self,
|
||||
name: &str,
|
||||
secret: impl Fn(Vec<String>) -> Fido2LuksResult<([u8; 32], String)>,
|
||||
slot_hint: Option<u32>,
|
||||
allow_discard: bool,
|
||||
) -> Fido2LuksResult<u32> {
|
||||
if !self.is_luks2()? {
|
||||
return Err(LuksError::Luks2Required.into());
|
||||
}
|
||||
let mut creds: HashMap<String, HashSet<u32>> = HashMap::new();
|
||||
for token in self.tokens()? {
|
||||
let token = match token {
|
||||
Ok((_id, t)) => t,
|
||||
_ => continue, // An corrupted token should't lock the user out
|
||||
};
|
||||
let slots = || {
|
||||
token
|
||||
.keyslots
|
||||
.iter()
|
||||
.filter_map(|slot| slot.parse::<u32>().ok())
|
||||
};
|
||||
for cred in token.credential.iter() {
|
||||
creds
|
||||
.entry(cred.clone())
|
||||
.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 empty;
|
||||
let slots = if let Some(slots) = creds.get(&credential) {
|
||||
slots
|
||||
} else {
|
||||
empty = HashSet::new();
|
||||
&empty
|
||||
};
|
||||
//Try slots associated with the credential used
|
||||
let slots = slots.iter().cloned().map(Option::Some).chain(
|
||||
std::iter::once(slot_hint) // Try slot hint if there is one
|
||||
.take(slot_hint.is_some() as usize)
|
||||
.chain(std::iter::once(None).take(slots.is_empty() as usize)), // Try all slots as last resort
|
||||
);
|
||||
for slot in slots {
|
||||
match self.activate(name, &secret, slot, allow_discard) {
|
||||
Err(Fido2LuksError::WrongSecret) => (),
|
||||
res => return res,
|
||||
}
|
||||
}
|
||||
Err(Fido2LuksError::WrongSecret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Fido2LuksToken {
|
||||
#[serde(rename = "type")]
|
||||
pub type_: String,
|
||||
pub credential: HashSet<String>,
|
||||
pub keyslots: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Fido2LuksToken {
|
||||
pub fn new(credential_id: impl AsRef<[u8]>, slot: u32) -> Self {
|
||||
Self::with_credentials(std::iter::once(credential_id), slot)
|
||||
}
|
||||
|
||||
pub fn with_credentials<I: IntoIterator<Item = B>, B: AsRef<[u8]>>(
|
||||
credentials: I,
|
||||
slot: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
credential: credentials
|
||||
.into_iter()
|
||||
.map(|cred| hex::encode(cred.as_ref()))
|
||||
.collect(),
|
||||
keyslots: vec![slot.to_string()].into_iter().collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn default_type() -> &'static str {
|
||||
"fido2luks"
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Fido2LuksToken {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
type_: Self::default_type().into(),
|
||||
credential: HashSet::new(),
|
||||
keyslots: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
39
src/main.rs
39
src/main.rs
@ -1,38 +1,27 @@
|
||||
#[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::path::PathBuf;
|
||||
use std::io;
|
||||
use std::process::exit;
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
pub mod cli_args;
|
||||
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) => {
|
||||
eprintln!("{:?}", e);
|
||||
exit(e.exit_code())
|
||||
}
|
||||
_ => exit(0),
|
||||
}
|
||||
}
|
||||
|
17
src/util.rs
17
src/util.rs
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user