Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
c44b0c35ba
|
33
.drone.yml
33
.drone.yml
@@ -3,27 +3,32 @@ name: default
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: fmt
|
- name: fmt
|
||||||
image: rust:1.43.0
|
image: rust:1.37.0
|
||||||
commands:
|
commands:
|
||||||
- rustup component add rustfmt
|
- rustup component add rustfmt
|
||||||
- cargo fmt --all -- --check
|
- cargo fmt --all -- --check
|
||||||
- name: test
|
- name: test
|
||||||
image: rust:1.43.0
|
image: rust:1.37.0
|
||||||
commands:
|
commands:
|
||||||
- apt update && apt install -y libkeyutils-dev libclang-dev clang pkg-config
|
- apt update && apt install -y libcryptsetup-dev libkeyutils-dev
|
||||||
- echo 'deb http://http.us.debian.org/debian unstable main non-free contrib' >> /etc/apt/sources.list.d/unstable.list && apt update && apt install -y libcryptsetup-dev
|
|
||||||
- cargo test
|
- cargo test
|
||||||
|
|
||||||
- name: publish
|
- name: build
|
||||||
image: rust:1.43.0
|
image: rust:1.37.0
|
||||||
environment:
|
|
||||||
CARGO_REGISTRY_TOKEN:
|
|
||||||
from_secret: cargo_tkn
|
|
||||||
commands:
|
commands:
|
||||||
- grep -E 'version ?= ?"${DRONE_TAG}"' -i Cargo.toml || (printf "incorrect crate/tag version" && exit 1)
|
- apt update && apt install -y libcryptsetup-dev libkeyutils-dev
|
||||||
- apt update && apt install -y libkeyutils-dev libclang-dev clang pkg-config
|
- cargo install -f --path . --root .
|
||||||
- echo 'deb http://http.us.debian.org/debian unstable main non-free contrib' >> /etc/apt/sources.list.d/unstable.list && apt update && apt install -y libcryptsetup-dev
|
|
||||||
- cargo package --all-features
|
|
||||||
- cargo publish --all-features
|
|
||||||
when:
|
when:
|
||||||
event: tag
|
event: tag
|
||||||
|
- name: publish
|
||||||
|
image: plugins/github-release
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: github_release
|
||||||
|
files:
|
||||||
|
- bin/fido2luks
|
||||||
|
checksum:
|
||||||
|
- md5
|
||||||
|
- sha256
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
969
Cargo.lock
generated
969
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
43
Cargo.toml
43
Cargo.toml
@@ -1,29 +1,28 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fido2luks"
|
name = "fido2luks"
|
||||||
version = "0.2.9"
|
version = "0.2.1"
|
||||||
authors = ["shimunn <shimun@shimun.net>"]
|
authors = ["shimunn <shimun@shimun.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
|
description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
|
||||||
documentation = "https://github.com/shimunn/fido2luks/blob/master/README.md"
|
license = "GPL v3"
|
||||||
homepage = "https://github.com/shimunn/fido2luks"
|
|
||||||
repository = "https://github.com/shimunn/fido2luks"
|
repository = "https://github.com/shimunn/fido2luks"
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["luks", "fido2", "u2f"]
|
|
||||||
categories = ["command-line-utilities"]
|
|
||||||
license-file = "LICENSE"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ctap_hmac = { version="0.4.2", features = ["request_multiple"] }
|
ctap = "0.1.0"
|
||||||
|
cryptsetup-rs = "0.2.0"
|
||||||
|
libcryptsetup-sys = "0.1.1"
|
||||||
|
|
||||||
hex = "0.3.2"
|
hex = "0.3.2"
|
||||||
ring = "0.13.5"
|
rust-crypto = "0.2.36"
|
||||||
failure = "0.1.5"
|
failure = "0.1.5"
|
||||||
rpassword = "4.0.1"
|
rpassword = "4.0.1"
|
||||||
structopt = "0.3.2"
|
structopt = "0.3.2"
|
||||||
libcryptsetup-rs = "0.4.1"
|
|
||||||
serde_json = "1.0.51"
|
[patch.crates-io]
|
||||||
serde_derive = "1.0.106"
|
#Until https://github.com/solidninja/cryptsetup-rs/pull/2 merges
|
||||||
serde = "1.0.106"
|
cryptsetup-rs = { path = "./patch/cryptsetup-rs" }
|
||||||
|
libcryptsetup-sys = { path = "./patch/cryptsetup-rs/libcryptsetup-sys" }
|
||||||
|
ctap = { path = "./patch/ctap" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
@@ -32,14 +31,8 @@ panic = 'abort'
|
|||||||
incremental = false
|
incremental = false
|
||||||
overflow-checks = false
|
overflow-checks = false
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.rpm]
|
||||||
depends = "$auto, cryptsetup"
|
buildflags = ["--release"]
|
||||||
build-depends = "libclang-dev, libcryptsetup-dev"
|
|
||||||
extended-description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
|
[package.metadata.rpm.targets]
|
||||||
license-file = ["LICENSE", "0"]
|
fido2luks = { path = "/usr/bin/fido2luks" }
|
||||||
assets = [
|
|
||||||
["target/release/fido2luks", "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"],
|
|
||||||
]
|
|
||||||
|
76
README.md
76
README.md
@@ -1,15 +1,15 @@
|
|||||||
# fido2luks [](https://crates.io/crates/fido2luks)
|
# fido2luks
|
||||||
|
|
||||||
This will allow you to unlock your luks encrypted disk with an fido2 compatible key
|
This will allow you to unlock your luks encrypted disk with an fido2 compatable 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
|
Note: This has only been tested under Fedora 30 using a Solo Key
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
```
|
```
|
||||||
dnf install clang cargo cryptsetup-devel -y
|
dnf install cargo cryptsetup-devel -y
|
||||||
```
|
```
|
||||||
|
|
||||||
### Device
|
### Device
|
||||||
@@ -17,24 +17,18 @@ dnf install clang cargo cryptsetup-devel -y
|
|||||||
```
|
```
|
||||||
git clone https://github.com/shimunn/fido2luks.git && cd fido2luks
|
git clone https://github.com/shimunn/fido2luks.git && cd fido2luks
|
||||||
|
|
||||||
# Alternativly cargo build --release && sudo cp target/release/fido2luks /usr/bin/
|
#Alternativly cargo build --release && sudo cp target/release/fido2luks /usr/bin/
|
||||||
sudo -E cargo install -f --path . --root /usr
|
CARGO_INSTALL_ROOT=/usr sudo -E cargo install -f --path .
|
||||||
|
|
||||||
# Copy template
|
echo FIDO2LUKS_CREDENTIAL_ID=$(fido2luks credential) >> fido2luks.conf
|
||||||
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
|
set -a
|
||||||
. /etc/fido2luks.conf
|
. 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>
|
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>
|
sudo -E fido2luks -i open /dev/disk/by-uuid/<DISK_UUID> luks-<DISK_UUID>
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -49,61 +43,21 @@ sudo make install
|
|||||||
|
|
||||||
### Grub
|
### Grub
|
||||||
|
|
||||||
Add `rd.luks.2fa=<CREDENTIAL_ID>:<DISK_UUID>` to `GRUB_CMDLINE_LINUX` in /etc/default/grub
|
Add `rd.luks.2fa=<CREDENTIAL_ID>:<DISK_UUID>` to `GRUB_CMDLINE_LINUX`
|
||||||
|
|
||||||
Note: This is only required for your root disk, systemd will try to unlock all other LUKS partions using the same key if you added it using `fido2luks add-key`
|
Note: This is only required for your root disk, systemd will try to unlock all other luks partions using the same key if you added it using `fido2luks addkey`
|
||||||
|
|
||||||
```
|
```
|
||||||
grub2-mkconfig > /boot/grub2/grub.cfg
|
grub2-mkconfig > /boot/grub2/grub.cfg
|
||||||
```
|
```
|
||||||
|
|
||||||
I'd also recommend to copy the executable onto /boot so that it is accessible in case you have to access your disk from a rescue system
|
|
||||||
|
|
||||||
```
|
|
||||||
mkdir /boot/fido2luks/
|
|
||||||
cp /usr/bin/fido2luks /boot/fido2luks/
|
|
||||||
cp /etc/fido2luks.conf /boot/fido2luks/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
||||||
Just reboot and see if it works, if that's the case you should remove your old less secure password from your LUKS header:
|
Just reboot and see if it works, if thats the case you should remove your old less secure password from your luks header:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Recommend in case you lose your authenticator, store this backupfile somewhere safe
|
#Recommend in case you lose your authenticator, store this backupfile somewhere safe
|
||||||
cryptsetup luksHeaderBackup /dev/disk/by-uuid/<DISK_UUID> --header-backup-file luks_backup_<DISK_UUID>
|
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>
|
fido2luks -i add-key --exclusive /dev/disk/by-uuid/<DISK_UUID>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Addtional settings
|
|
||||||
|
|
||||||
### Password less
|
|
||||||
|
|
||||||
Remove your previous secret as described in the next section, in case you've already added one.
|
|
||||||
|
|
||||||
Open `/etc/fido2luks.conf` and replace `FIDO2LUKS_SALT=Ask` with `FIDO2LUKS_SALT=string:<YOUR_RANDOM_STRING>`
|
|
||||||
but be warned that this password will be included to into your initramfs.
|
|
||||||
|
|
||||||
Import the new config into env:
|
|
||||||
|
|
||||||
```
|
|
||||||
set -a
|
|
||||||
. /etc/fido2luks.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
Then add the new secret to each device and update dracut afterwards `dracut -f`
|
|
||||||
|
|
||||||
## Removal
|
|
||||||
|
|
||||||
Remove `rd.luks.2fa` from `GRUB_CMDLINE_LINUX` in /etc/default/grub
|
|
||||||
|
|
||||||
```
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
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)
|
UMOUNT=$(command -v umount)
|
||||||
|
|
||||||
TIMEOUT=120
|
TIMEOUT=120
|
||||||
CON_MSG="Please connect your authenticator"
|
CON_MSG="Please connect your authenicator"
|
||||||
|
|
||||||
generate_service () {
|
generate_service () {
|
||||||
local credential_id=$1 target_uuid=$2 timeout=$3 sd_dir=${4:-$NORMAL_DIR}
|
local credential_id=$1 target_uuid=$2 timeout=$3 sd_dir=${4:-$NORMAL_DIR}
|
||||||
@@ -19,10 +19,6 @@ generate_service () {
|
|||||||
|
|
||||||
local crypto_target_service="systemd-cryptsetup@luks\x2d${sd_target_uuid}.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 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 -- "[Unit]"
|
||||||
printf -- "\nDescription=%s" "2fa for luks"
|
printf -- "\nDescription=%s" "2fa for luks"
|
||||||
@@ -31,15 +27,19 @@ generate_service () {
|
|||||||
printf -- "\nBefore=%s umount.target luks-2fa.target" "$crypto_target_service"
|
printf -- "\nBefore=%s umount.target luks-2fa.target" "$crypto_target_service"
|
||||||
printf -- "\nConflicts=umount.target"
|
printf -- "\nConflicts=umount.target"
|
||||||
printf -- "\nDefaultDependencies=no"
|
printf -- "\nDefaultDependencies=no"
|
||||||
[ ! -z "$timeout" ] && printf -- "\nJobTimeoutSec=%s" "$timeout"
|
printf -- "\nJobTimeoutSec=%s" "$timeout"
|
||||||
|
|
||||||
printf -- "\n\n[Service]"
|
printf -- "\n\n[Service]"
|
||||||
printf -- "\nType=oneshot"
|
printf -- "\nType=oneshot"
|
||||||
printf -- "\nRemainAfterExit=yes"
|
printf -- "\nRemainAfterExit=yes"
|
||||||
printf -- "\nEnvironmentFile=%s" "/etc/fido2luks.conf"
|
printf -- "\nEnvironment=FIDO2LUKS_CREDENTIAL_ID='%s'" "$credential_id"
|
||||||
[ ! -z "$credential_id" ] && printf -- "\nEnvironment=FIDO2LUKS_CREDENTIAL_ID='%s'" "$credential_id"
|
printf -- "\nEnvironment=FIDO2LUKS_SALT='%s'" "Ask"
|
||||||
|
printf -- "\nEnvironment=FIDO2LUKS_PASSWORD_HELPER='%s'" "/usr/bin/systemd-ask-password Disk 2fa password"
|
||||||
printf -- "\nKeyringMode=%s" "shared"
|
printf -- "\nKeyringMode=%s" "shared"
|
||||||
printf -- "\nExecStartPre=-/usr/bin/plymouth display-message --text \"${CON_MSG}\""
|
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 -- "\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 -- "\nExecStop=${CRYPTSETUP} detach 'luks-%s'" "$target_uuid"
|
printf -- "\nExecStop=${CRYPTSETUP} detach 'luks-%s'" "$target_uuid"
|
||||||
} > "$sd_service"
|
} > "$sd_service"
|
||||||
|
|
||||||
|
@@ -18,7 +18,6 @@ depends () {
|
|||||||
install () {
|
install () {
|
||||||
inst "$moddir/luks-2fa-generator.sh" "/etc/systemd/system-generators/luks-2fa-generator.sh"
|
inst "$moddir/luks-2fa-generator.sh" "/etc/systemd/system-generators/luks-2fa-generator.sh"
|
||||||
inst_simple "/usr/bin/fido2luks" "/usr/bin/fido2luks"
|
inst_simple "/usr/bin/fido2luks" "/usr/bin/fido2luks"
|
||||||
inst_simple "/etc/fido2luks.conf" "/etc/fido2luks.conf"
|
|
||||||
inst "$systemdutildir/systemd-cryptsetup"
|
inst "$systemdutildir/systemd-cryptsetup"
|
||||||
mkdir -p "$initdir/luks-2fa"
|
mkdir -p "$initdir/luks-2fa"
|
||||||
|
|
||||||
|
@@ -15,7 +15,6 @@ help:
|
|||||||
install:
|
install:
|
||||||
cp ${MODULE_CONF_D}/${MODULE_CONF} ${DRACUT_CONF_D}/
|
cp ${MODULE_CONF_D}/${MODULE_CONF} ${DRACUT_CONF_D}/
|
||||||
cp -r ${MODULE_DIR} ${DRACUT_MODULES_D}/
|
cp -r ${MODULE_DIR} ${DRACUT_MODULES_D}/
|
||||||
cp ${MODULE_DIR}/fido2luks.conf /etc/fido2luks.conf
|
|
||||||
dracut -fv
|
dracut -fv
|
||||||
clean:
|
clean:
|
||||||
rm ${DRACUT_CONF_D}/${MODULE_CONF}
|
rm ${DRACUT_CONF_D}/${MODULE_CONF}
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
.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
|
|
@@ -1,13 +0,0 @@
|
|||||||
## Initramfs-tools based systems(Ubuntu and derivatives)
|
|
||||||
|
|
||||||
After installation generate your credentials and add keys to your disk as described in the top-level README
|
|
||||||
then add `initramfs,keyscript=fido2luks` to your `/etc/crypttab`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
sda6_crypt UUID=9793d81a-4cfb-4712-85f3-c7a8d715112c none luks,discard,initramfs,keyscript=fido2luks
|
|
||||||
```
|
|
||||||
|
|
||||||
But don't forget to run `make install` which will install all necessary scripts and regenerate your intrid.
|
|
||||||
|
|
||||||
[Recording showing part of the setup](https://shimun.net/fido2luks/setup.svg)
|
|
@@ -1,3 +0,0 @@
|
|||||||
FIDO2LUKS_SALT=Ask
|
|
||||||
#FIDO2LUKS_PASSWORD_HELPER="/usr/bin/plymouth ask-for-password --promt 'FIDO2 password salt'"
|
|
||||||
FIDO2LUKS_CREDENTIAL_ID=
|
|
@@ -1,14 +0,0 @@
|
|||||||
#!/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
|
|
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -a
|
|
||||||
. /etc/fido2luks.conf
|
|
||||||
|
|
||||||
if [ -z "$FIDO2LUKS_PASSWORD_HELPER" ]; then
|
|
||||||
export FIDO2LUKS_PASSWORD_HELPER="plymouth ask-for-password --promt 'FIDO2 password salt for $CRYPTTAB_NAME'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fido2luks print-secret --bin
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 550 KiB |
4
patch/cryptsetup-rs/.gitignore
vendored
Normal file
4
patch/cryptsetup-rs/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
|
*.iml
|
||||||
|
.idea/
|
22
patch/cryptsetup-rs/.travis.yml
Normal file
22
patch/cryptsetup-rs/.travis.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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
|
32
patch/cryptsetup-rs/Cargo.toml
Normal file
32
patch/cryptsetup-rs/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
[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"
|
165
patch/cryptsetup-rs/LICENSE
Normal file
165
patch/cryptsetup-rs/LICENSE
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
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.
|
29
patch/cryptsetup-rs/README.md
Normal file
29
patch/cryptsetup-rs/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[](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
Normal file
2
patch/cryptsetup-rs/blkid-rs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
target/
|
||||||
|
Cargo.lock
|
18
patch/cryptsetup-rs/blkid-rs/Cargo.toml
Normal file
18
patch/cryptsetup-rs/blkid-rs/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[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"
|
286
patch/cryptsetup-rs/blkid-rs/src/lib.rs
Normal file
286
patch/cryptsetup-rs/blkid-rs/src/lib.rs
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
// 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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
78
patch/cryptsetup-rs/examples/luks_dump.rs
Normal file
78
patch/cryptsetup-rs/examples/luks_dump.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#![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);
|
||||||
|
}
|
||||||
|
}
|
21
patch/cryptsetup-rs/libcryptsetup-sys/Cargo.toml
Normal file
21
patch/cryptsetup-rs/libcryptsetup-sys/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[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"
|
||||||
|
|
8
patch/cryptsetup-rs/libcryptsetup-sys/build.rs
Normal file
8
patch/cryptsetup-rs/libcryptsetup-sys/build.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
extern crate pkg_config;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
pkg_config::Config::new()
|
||||||
|
.statik(true)
|
||||||
|
.find("libcryptsetup")
|
||||||
|
.unwrap();
|
||||||
|
}
|
492
patch/cryptsetup-rs/libcryptsetup-sys/lib.rs
Normal file
492
patch/cryptsetup-rs/libcryptsetup-sys/lib.rs
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
#![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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
patch/cryptsetup-rs/rustfmt.toml
Normal file
2
patch/cryptsetup-rs/rustfmt.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
max_width = 120
|
||||||
|
format_strings = false
|
377
patch/cryptsetup-rs/src/api.rs
Normal file
377
patch/cryptsetup-rs/src/api.rs
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
//! 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
|
||||||
|
}
|
||||||
|
}
|
319
patch/cryptsetup-rs/src/device.rs
Normal file
319
patch/cryptsetup-rs/src/device.rs
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
//! 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())
|
||||||
|
}
|
32
patch/cryptsetup-rs/src/lib.rs
Normal file
32
patch/cryptsetup-rs/src/lib.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//! 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};
|
72
patch/cryptsetup-rs/tests/tests.rs
Normal file
72
patch/cryptsetup-rs/tests/tests.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#![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
Normal file
4
patch/ctap/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
src/bin
|
21
patch/ctap/Cargo.toml
Normal file
21
patch/ctap/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[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"
|
59
patch/ctap/README.md
Normal file
59
patch/ctap/README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
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.
|
201
patch/ctap/src/LICENSE-APACHE
Normal file
201
patch/ctap/src/LICENSE-APACHE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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.
|
19
patch/ctap/src/LICENSE-MIT
Normal file
19
patch/ctap/src/LICENSE-MIT
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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.
|
715
patch/ctap/src/cbor.rs
Normal file
715
patch/ctap/src/cbor.rs
Normal file
@@ -0,0 +1,715 @@
|
|||||||
|
// 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(())
|
||||||
|
}
|
||||||
|
}
|
130
patch/ctap/src/crypto.rs
Normal file
130
patch/ctap/src/crypto.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
// 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()
|
||||||
|
}
|
101
patch/ctap/src/error.rs
Normal file
101
patch/ctap/src/error.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// 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))
|
||||||
|
}
|
||||||
|
}
|
200
patch/ctap/src/extensions/hmac.rs
Normal file
200
patch/ctap/src/extensions/hmac.rs
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
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
patch/ctap/src/extensions/mod.rs
Normal file
1
patch/ctap/src/extensions/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod hmac;
|
16
patch/ctap/src/hid_common.rs
Normal file
16
patch/ctap/src/hid_common.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
88
patch/ctap/src/hid_linux.rs
Normal file
88
patch/ctap/src/hid_linux.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// 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,
|
||||||
|
})
|
||||||
|
}
|
402
patch/ctap/src/lib.rs
Normal file
402
patch/ctap/src/lib.rs
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
179
patch/ctap/src/packet.rs
Normal file
179
patch/ctap/src/packet.rs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// 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 })
|
||||||
|
}
|
||||||
|
}
|
781
src/cli.rs
781
src/cli.rs
@@ -1,172 +1,77 @@
|
|||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
use cryptsetup_rs as luks;
|
||||||
|
use cryptsetup_rs::api::{CryptDeviceHandle, CryptDeviceOpenBuilder, Luks1Params};
|
||||||
|
use cryptsetup_rs::{CryptDevice, Luks1CryptDevice};
|
||||||
|
|
||||||
|
use libcryptsetup_sys::crypt_keyslot_info;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use ctap::{FidoCredential, FidoErrorKind};
|
|
||||||
use failure::_core::fmt::{Display, Error, Formatter};
|
|
||||||
use failure::_core::str::FromStr;
|
|
||||||
use failure::_core::time::Duration;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use crate::luks::{Fido2LuksToken, LuksDevice};
|
pub fn add_key_to_luks(
|
||||||
use crate::util::sha256;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
|
||||||
pub struct HexEncoded(pub Vec<u8>);
|
|
||||||
|
|
||||||
impl Display for HexEncoded {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
||||||
f.write_str(&hex::encode(&self.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
|
|
||||||
/// Await for an authenticator to be connected, timeout after n seconds
|
|
||||||
#[structopt(
|
|
||||||
long = "await-dev",
|
|
||||||
name = "await-dev",
|
|
||||||
env = "FIDO2LUKS_DEVICE_AWAIT",
|
|
||||||
default_value = "15"
|
|
||||||
)]
|
|
||||||
pub await_time: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
|
||||||
pub struct LuksParameters {
|
|
||||||
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
|
||||||
device: PathBuf,
|
device: PathBuf,
|
||||||
|
secret: &[u8; 32],
|
||||||
/// Try to unlock the device using a specifc keyslot, ignore all other slots
|
old_secret: Box<dyn Fn() -> Fido2LuksResult<Vec<u8>>>,
|
||||||
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
|
exclusive: bool,
|
||||||
slot: Option<u32>,
|
) -> Fido2LuksResult<u8> {
|
||||||
}
|
fn offer_format(
|
||||||
|
_dev: CryptDeviceOpenBuilder,
|
||||||
#[derive(Debug, StructOpt, Clone)]
|
) -> Fido2LuksResult<CryptDeviceHandle<Luks1Params>> {
|
||||||
pub struct LuksModParameters {
|
unimplemented!()
|
||||||
/// Number of milliseconds required to derive the volume decryption key
|
|
||||||
/// Defaults to 10ms when using an authenticator or the default by cryptsetup when using a password
|
|
||||||
#[structopt(long = "kdf-time", name = "kdf-time")]
|
|
||||||
kdf_time: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
|
||||||
pub struct SecretParameters {
|
|
||||||
/// Salt for secret generation, defaults to 'ask'
|
|
||||||
///
|
|
||||||
/// Options:{n}
|
|
||||||
/// - ask : Prompt user using password helper{n}
|
|
||||||
/// - file:<PATH> : Will read <FILE>{n}
|
|
||||||
/// - string:<STRING> : Will use <STRING>, which will be handled like a password provided to the 'ask' option{n}
|
|
||||||
#[structopt(
|
|
||||||
name = "salt",
|
|
||||||
long = "salt",
|
|
||||||
env = "FIDO2LUKS_SALT",
|
|
||||||
default_value = "ask"
|
|
||||||
)]
|
|
||||||
pub salt: InputSalt,
|
|
||||||
/// Script used to obtain passwords, overridden by --interactive flag
|
|
||||||
#[structopt(
|
|
||||||
name = "password-helper",
|
|
||||||
env = "FIDO2LUKS_PASSWORD_HELPER",
|
|
||||||
default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
|
|
||||||
)]
|
|
||||||
pub password_helper: PasswordHelper,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn derive_secret(
|
|
||||||
credentials: &[HexEncoded],
|
|
||||||
salt: &[u8; 32],
|
|
||||||
timeout: u64,
|
|
||||||
pin: Option<&str>,
|
|
||||||
) -> Fido2LuksResult<([u8; 32], FidoCredential)> {
|
|
||||||
let timeout = Duration::from_secs(timeout);
|
|
||||||
let start = SystemTime::now();
|
|
||||||
|
|
||||||
while let Ok(el) = start.elapsed() {
|
|
||||||
if el > timeout {
|
|
||||||
return Err(error::Fido2LuksError::NoAuthenticatorError);
|
|
||||||
}
|
|
||||||
if get_devices()
|
|
||||||
.map(|devices| !devices.is_empty())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
|
||||||
}
|
}
|
||||||
|
let dev =
|
||||||
|
|| -> luks::device::Result<CryptDeviceOpenBuilder> { luks::open(&device.canonicalize()?) };
|
||||||
|
|
||||||
let credentials = credentials
|
let prev_key = old_secret()?;
|
||||||
.iter()
|
|
||||||
.map(|hex| FidoCredential {
|
|
||||||
id: hex.0.clone(),
|
|
||||||
public_key: None,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let credentials = credentials.iter().collect::<Vec<_>>();
|
|
||||||
let (unsalted, cred) =
|
|
||||||
perform_challenge(&credentials, salt, timeout - start.elapsed().unwrap(), pin)?;
|
|
||||||
|
|
||||||
Ok((sha256(&[salt, &unsalted[..]]), cred.clone()))
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_pin() -> Fido2LuksResult<String> {
|
pub fn add_password_to_luks(
|
||||||
util::read_password("Authenticator PIN", false)
|
device: PathBuf,
|
||||||
|
secret: &[u8; 32],
|
||||||
|
new_secret: Box<dyn Fn() -> Fido2LuksResult<Vec<u8>>>,
|
||||||
|
add_password: bool,
|
||||||
|
) -> Fido2LuksResult<u8> {
|
||||||
|
let dev = luks::open(&device.canonicalize()?)?;
|
||||||
|
let mut handle = dev.luks1()?;
|
||||||
|
let prev_slot = if add_password {
|
||||||
|
Some(handle.add_keyslot(&secret[..], Some(&secret[..]), None)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let slot = handle.update_keyslot(&new_secret()?[..], &secret[..], prev_slot)?;
|
||||||
|
Ok(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn authenticator_connected() -> Fido2LuksResult<bool> {
|
||||||
|
Ok(!device::get_devices()?.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
@@ -179,14 +84,38 @@ pub struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt, Clone)]
|
#[derive(Debug, StructOpt, Clone)]
|
||||||
pub struct OtherSecret {
|
pub struct SecretGeneration {
|
||||||
/// Use a keyfile instead of a password
|
/// FIDO credential id, generate using fido2luks credential
|
||||||
#[structopt(short = "d", long = "keyfile", conflicts_with = "fido_device")]
|
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
|
||||||
keyfile: Option<PathBuf>,
|
pub credential_id: String,
|
||||||
/// Use another fido device instead of a password
|
/// Salt for secret generation, defaults to Password
|
||||||
/// Note: this requires for the credential fot the other device to be passed as argument as well
|
#[structopt(name = "salt", env = "FIDO2LUKS_SALT", default_value = "Ask")]
|
||||||
#[structopt(short = "f", long = "fido-device", conflicts_with = "keyfile")]
|
pub salt: InputSalt,
|
||||||
fido_device: bool,
|
/// 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;
|
||||||
|
}
|
||||||
|
me
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
@@ -197,137 +126,53 @@ pub enum Command {
|
|||||||
#[structopt(short = "b", long = "bin")]
|
#[structopt(short = "b", long = "bin")]
|
||||||
binary: bool,
|
binary: bool,
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
credentials: Credentials,
|
secret_gen: SecretGeneration,
|
||||||
#[structopt(flatten)]
|
|
||||||
authenticator: AuthenticatorParameters,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
secret: SecretParameters,
|
|
||||||
},
|
},
|
||||||
/// Adds a generated key to the specified LUKS device
|
/// Adds a generated key to the specified LUKS device
|
||||||
#[structopt(name = "add-key")]
|
#[structopt(name = "add-key")]
|
||||||
AddKey {
|
AddKey {
|
||||||
#[structopt(flatten)]
|
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||||
luks: LuksParameters,
|
device: PathBuf,
|
||||||
#[structopt(flatten)]
|
|
||||||
credentials: Credentials,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
authenticator: AuthenticatorParameters,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
secret: SecretParameters,
|
|
||||||
/// Will wipe all other keys
|
/// Will wipe all other keys
|
||||||
#[structopt(short = "e", long = "exclusive")]
|
#[structopt(short = "e", long = "exclusive")]
|
||||||
exclusive: bool,
|
exclusive: bool,
|
||||||
/// Will add an token to your LUKS 2 header, including the credential id
|
/// Use a keyfile instead of a password
|
||||||
#[structopt(short = "t", long = "token")]
|
#[structopt(short = "d", long = "keyfile")]
|
||||||
token: bool,
|
keyfile: Option<PathBuf>,
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
existing_secret: OtherSecret,
|
secret_gen: SecretGeneration,
|
||||||
#[structopt(flatten)]
|
|
||||||
luks_mod: LuksModParameters,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Replace a previously added key with a password
|
/// Replace a previously added key with a password
|
||||||
#[structopt(name = "replace-key")]
|
#[structopt(name = "replace-key")]
|
||||||
ReplaceKey {
|
ReplaceKey {
|
||||||
#[structopt(flatten)]
|
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||||
luks: LuksParameters,
|
device: PathBuf,
|
||||||
#[structopt(flatten)]
|
|
||||||
credentials: Credentials,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
authenticator: AuthenticatorParameters,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
secret: SecretParameters,
|
|
||||||
/// Add the password and keep the key
|
/// Add the password and keep the key
|
||||||
#[structopt(short = "a", long = "add-password")]
|
#[structopt(short = "a", long = "add-password")]
|
||||||
add_password: bool,
|
add_password: bool,
|
||||||
/// Will add an token to your LUKS 2 header, including the credential id
|
/// Use a keyfile instead of a password
|
||||||
#[structopt(short = "t", long = "token")]
|
#[structopt(short = "d", long = "keyfile")]
|
||||||
token: bool,
|
keyfile: Option<PathBuf>,
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
replacement: OtherSecret,
|
secret_gen: SecretGeneration,
|
||||||
#[structopt(flatten)]
|
|
||||||
luks_mod: LuksModParameters,
|
|
||||||
},
|
},
|
||||||
/// Open the LUKS device
|
/// Open the LUKS device
|
||||||
#[structopt(name = "open")]
|
#[structopt(name = "open")]
|
||||||
Open {
|
Open {
|
||||||
#[structopt(flatten)]
|
#[structopt(env = "FIDO2LUKS_DEVICE")]
|
||||||
luks: LuksParameters,
|
device: PathBuf,
|
||||||
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
|
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
|
||||||
name: String,
|
name: String,
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
credentials: Credentials,
|
secret_gen: SecretGeneration,
|
||||||
#[structopt(flatten)]
|
|
||||||
authenticator: AuthenticatorParameters,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
secret: SecretParameters,
|
|
||||||
#[structopt(short = "r", long = "max-retries", default_value = "0")]
|
|
||||||
retries: i32,
|
|
||||||
},
|
|
||||||
/// Open the LUKS device using credentials embedded in the LUKS 2 header
|
|
||||||
#[structopt(name = "open-token")]
|
|
||||||
OpenToken {
|
|
||||||
#[structopt(flatten)]
|
|
||||||
luks: LuksParameters,
|
|
||||||
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
|
|
||||||
name: String,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
authenticator: AuthenticatorParameters,
|
|
||||||
#[structopt(flatten)]
|
|
||||||
secret: SecretParameters,
|
|
||||||
#[structopt(short = "r", long = "max-retries", default_value = "0")]
|
|
||||||
retries: i32,
|
|
||||||
},
|
},
|
||||||
/// Generate a new FIDO credential
|
/// Generate a new FIDO credential
|
||||||
#[structopt(name = "credential")]
|
#[structopt(name = "credential")]
|
||||||
Credential {
|
Credential,
|
||||||
#[structopt(flatten)]
|
|
||||||
authenticator: AuthenticatorParameters,
|
|
||||||
/// Name to be displayed on the authenticator if it has a display
|
|
||||||
#[structopt(env = "FIDO2LUKS_CREDENTIAL_NAME")]
|
|
||||||
name: Option<String>,
|
|
||||||
},
|
|
||||||
/// Check if an authenticator is connected
|
/// Check if an authenticator is connected
|
||||||
#[structopt(name = "connected")]
|
#[structopt(name = "connected")]
|
||||||
Connected,
|
Connected,
|
||||||
Token(TokenCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
///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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_cmdline() -> Args {
|
pub fn parse_cmdline() -> Args {
|
||||||
@@ -337,249 +182,83 @@ pub fn parse_cmdline() -> Args {
|
|||||||
pub fn run_cli() -> Fido2LuksResult<()> {
|
pub fn run_cli() -> Fido2LuksResult<()> {
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
let args = parse_cmdline();
|
let args = parse_cmdline();
|
||||||
let interactive = args.interactive;
|
|
||||||
match &args.command {
|
match &args.command {
|
||||||
Command::Credential {
|
Command::Credential => {
|
||||||
authenticator,
|
let cred = make_credential_id()?;
|
||||||
name,
|
|
||||||
} => {
|
|
||||||
let pin_string;
|
|
||||||
let pin = if authenticator.pin {
|
|
||||||
pin_string = read_pin()?;
|
|
||||||
Some(pin_string.as_ref())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let cred = make_credential_id(name.as_ref().map(|n| n.as_ref()), pin)?;
|
|
||||||
println!("{}", hex::encode(&cred.id));
|
println!("{}", hex::encode(&cred.id));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Command::PrintSecret {
|
Command::PrintSecret {
|
||||||
binary,
|
binary,
|
||||||
authenticator,
|
ref secret_gen,
|
||||||
credentials,
|
|
||||||
secret,
|
|
||||||
} => {
|
} => {
|
||||||
let pin_string;
|
let secret = secret_gen.patch(&args).obtain_secret()?;
|
||||||
let pin = if authenticator.pin {
|
|
||||||
pin_string = read_pin()?;
|
|
||||||
Some(pin_string.as_ref())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let salt = if interactive || secret.password_helper == PasswordHelper::Stdin {
|
|
||||||
util::read_password_hashed("Password", false)
|
|
||||||
} else {
|
|
||||||
secret.salt.obtain(&secret.password_helper)
|
|
||||||
}?;
|
|
||||||
let (secret, _cred) = derive_secret(
|
|
||||||
credentials.ids.0.as_slice(),
|
|
||||||
&salt,
|
|
||||||
authenticator.await_time,
|
|
||||||
pin,
|
|
||||||
)?;
|
|
||||||
if *binary {
|
if *binary {
|
||||||
stdout.write_all(&secret[..])?;
|
stdout.write(&secret[..])?;
|
||||||
} else {
|
} else {
|
||||||
stdout.write_all(hex::encode(&secret[..]).as_bytes())?;
|
stdout.write(hex::encode(&secret[..]).as_bytes())?;
|
||||||
}
|
}
|
||||||
Ok(stdout.flush()?)
|
Ok(stdout.flush()?)
|
||||||
}
|
}
|
||||||
Command::AddKey {
|
Command::AddKey {
|
||||||
luks,
|
device,
|
||||||
authenticator,
|
exclusive,
|
||||||
credentials,
|
keyfile,
|
||||||
secret,
|
ref secret_gen,
|
||||||
luks_mod,
|
|
||||||
existing_secret: other_secret,
|
|
||||||
token,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Command::ReplaceKey {
|
|
||||||
luks,
|
|
||||||
authenticator,
|
|
||||||
credentials,
|
|
||||||
secret,
|
|
||||||
luks_mod,
|
|
||||||
replacement: other_secret,
|
|
||||||
token,
|
|
||||||
..
|
|
||||||
} => {
|
} => {
|
||||||
let pin = if authenticator.pin {
|
let secret = secret_gen.patch(&args).obtain_secret()?;
|
||||||
Some(read_pin()?)
|
let slot = add_key_to_luks(
|
||||||
} else {
|
device.clone(),
|
||||||
None
|
&secret,
|
||||||
};
|
if let Some(keyfile) = keyfile.clone() {
|
||||||
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
|
Box::new(move || util::read_keyfile(keyfile.clone()))
|
||||||
if interactive || secret.password_helper == PasswordHelper::Stdin {
|
|
||||||
util::read_password_hashed(q, verify)
|
|
||||||
} else {
|
} else {
|
||||||
secret.salt.obtain(&secret.password_helper)
|
Box::new(|| {
|
||||||
}
|
util::read_password("Old password", true).map(|p| p.as_bytes().to_vec())
|
||||||
};
|
})
|
||||||
let other_secret = |salt_q: &str,
|
},
|
||||||
verify: bool|
|
*exclusive,
|
||||||
-> Fido2LuksResult<(Vec<u8>, Option<FidoCredential>)> {
|
)?;
|
||||||
match other_secret {
|
println!(
|
||||||
OtherSecret {
|
"Added to key to device {}, slot: {}",
|
||||||
keyfile: Some(file),
|
device.display(),
|
||||||
..
|
slot
|
||||||
} => Ok((util::read_keyfile(file)?, None)),
|
);
|
||||||
OtherSecret {
|
Ok(())
|
||||||
fido_device: true, ..
|
}
|
||||||
} => Ok(derive_secret(
|
Command::ReplaceKey {
|
||||||
&credentials.ids.0,
|
device,
|
||||||
&salt(salt_q, verify)?,
|
add_password,
|
||||||
authenticator.await_time,
|
keyfile,
|
||||||
pin.as_deref(),
|
ref secret_gen,
|
||||||
)
|
} => {
|
||||||
.map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?),
|
let secret = secret_gen.patch(&args).obtain_secret()?;
|
||||||
_ => Ok((
|
let slot = add_password_to_luks(
|
||||||
util::read_password(salt_q, verify)?.as_bytes().to_vec(),
|
device.clone(),
|
||||||
None,
|
&secret,
|
||||||
)),
|
if let Some(keyfile) = keyfile.clone() {
|
||||||
}
|
Box::new(move || util::read_keyfile(keyfile.clone()))
|
||||||
};
|
} else {
|
||||||
let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> {
|
Box::new(|| {
|
||||||
derive_secret(
|
util::read_password("Password to add", true).map(|p| p.as_bytes().to_vec())
|
||||||
&credentials.ids.0,
|
})
|
||||||
&salt("Password", verify)?,
|
},
|
||||||
authenticator.await_time,
|
*add_password,
|
||||||
pin.as_deref(),
|
)?;
|
||||||
)
|
println!(
|
||||||
};
|
"Added to password to device {}, slot: {}",
|
||||||
let mut luks_dev = LuksDevice::load(&luks.device)?;
|
device.display(),
|
||||||
// Non overlap
|
slot
|
||||||
match &args.command {
|
);
|
||||||
Command::AddKey { exclusive, .. } => {
|
Ok(())
|
||||||
let (existing_secret, _) = other_secret("Current password", false)?;
|
|
||||||
let (new_secret, cred) = secret(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(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 {
|
Command::Open {
|
||||||
luks,
|
device,
|
||||||
authenticator,
|
|
||||||
secret,
|
|
||||||
name,
|
name,
|
||||||
retries,
|
ref secret_gen,
|
||||||
..
|
|
||||||
}
|
|
||||||
| Command::OpenToken {
|
|
||||||
luks,
|
|
||||||
authenticator,
|
|
||||||
secret,
|
|
||||||
name,
|
|
||||||
retries,
|
|
||||||
} => {
|
} => {
|
||||||
let pin_string;
|
let secret = secret_gen.patch(&args).obtain_secret()?;
|
||||||
let pin = if authenticator.pin {
|
open_container(&device, &name, &secret)
|
||||||
pin_string = read_pin()?;
|
|
||||||
Some(pin_string.as_ref())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
|
|
||||||
if interactive || secret.password_helper == PasswordHelper::Stdin {
|
|
||||||
util::read_password_hashed(q, verify)
|
|
||||||
} else {
|
|
||||||
secret.salt.obtain(&secret.password_helper)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cow shouldn't be necessary
|
|
||||||
let secret = |credentials: Cow<'_, Vec<HexEncoded>>| {
|
|
||||||
derive_secret(
|
|
||||||
credentials.as_ref(),
|
|
||||||
&salt("Password", false)?,
|
|
||||||
authenticator.await_time,
|
|
||||||
pin,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut retries = *retries;
|
|
||||||
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)),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
_ => 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() {
|
Command::Connected => match get_devices() {
|
||||||
Ok(ref devs) if !devs.is_empty() => {
|
Ok(ref devs) if !devs.is_empty() => {
|
||||||
@@ -588,129 +267,5 @@ pub fn run_cli() -> Fido2LuksResult<()> {
|
|||||||
}
|
}
|
||||||
_ => exit(1),
|
_ => 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(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use ring::digest;
|
use crypto::digest::Digest;
|
||||||
|
use crypto::sha2::Sha256;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
@@ -9,11 +10,11 @@ use std::path::PathBuf;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum InputSalt {
|
pub enum InputSalt {
|
||||||
AskPassword,
|
AskPassword,
|
||||||
String(String),
|
|
||||||
File { path: PathBuf },
|
File { path: PathBuf },
|
||||||
|
Both { path: PathBuf },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for InputSalt {
|
impl Default for InputSalt {
|
||||||
@@ -24,14 +25,10 @@ impl Default for InputSalt {
|
|||||||
|
|
||||||
impl From<&str> for InputSalt {
|
impl From<&str> for InputSalt {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
let mut parts = s.split(':');
|
if PathBuf::from(s).exists() && s != "Ask" {
|
||||||
match parts.next() {
|
InputSalt::File { path: s.into() }
|
||||||
Some("ask") | Some("Ask") => InputSalt::AskPassword,
|
} else {
|
||||||
Some("file") => InputSalt::File {
|
InputSalt::AskPassword
|
||||||
path: parts.collect::<Vec<_>>().join(":").into(),
|
|
||||||
},
|
|
||||||
Some("string") => InputSalt::String(parts.collect::<Vec<_>>().join(":")),
|
|
||||||
_ => Self::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,15 +45,15 @@ impl fmt::Display for InputSalt {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
f.write_str(&match self {
|
f.write_str(&match self {
|
||||||
InputSalt::AskPassword => "ask".to_string(),
|
InputSalt::AskPassword => "ask".to_string(),
|
||||||
InputSalt::String(s) => ["string", s].join(":"),
|
InputSalt::File { path } => path.display().to_string(),
|
||||||
InputSalt::File { path } => ["file", path.display().to_string().as_str()].join(":"),
|
InputSalt::Both { path } => ["ask", path.display().to_string().as_str()].join(" + "),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputSalt {
|
impl InputSalt {
|
||||||
pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> {
|
pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> {
|
||||||
let mut digest = digest::Context::new(&digest::SHA256);
|
let mut digest = Sha256::new();
|
||||||
match self {
|
match self {
|
||||||
InputSalt::File { path } => {
|
InputSalt::File { path } => {
|
||||||
let mut do_io = || {
|
let mut do_io = || {
|
||||||
@@ -64,7 +61,7 @@ impl InputSalt {
|
|||||||
let mut buf = [0u8; 512];
|
let mut buf = [0u8; 512];
|
||||||
loop {
|
loop {
|
||||||
let red = reader.read(&mut buf)?;
|
let red = reader.read(&mut buf)?;
|
||||||
digest.update(&buf[0..red]);
|
digest.input(&buf[0..red]);
|
||||||
if red == 0 {
|
if red == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -74,20 +71,22 @@ impl InputSalt {
|
|||||||
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
|
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
|
||||||
}
|
}
|
||||||
InputSalt::AskPassword => {
|
InputSalt::AskPassword => {
|
||||||
digest.update(password_helper.obtain()?.as_bytes());
|
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)?)
|
||||||
}
|
}
|
||||||
InputSalt::String(s) => digest.update(s.as_bytes()),
|
|
||||||
}
|
}
|
||||||
let mut salt = [0u8; 32];
|
let mut salt = [0u8; 32];
|
||||||
salt.as_mut().copy_from_slice(digest.finish().as_ref());
|
digest.result(&mut salt);
|
||||||
Ok(salt)
|
Ok(salt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PasswordHelper {
|
pub enum PasswordHelper {
|
||||||
Script(String),
|
Script(String),
|
||||||
#[allow(dead_code)]
|
|
||||||
Systemd,
|
Systemd,
|
||||||
Stdin,
|
Stdin,
|
||||||
}
|
}
|
||||||
@@ -95,7 +94,7 @@ pub enum PasswordHelper {
|
|||||||
impl Default for PasswordHelper {
|
impl Default for PasswordHelper {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PasswordHelper::Script(
|
PasswordHelper::Script(
|
||||||
"/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
|
"/usr/bin/systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -135,7 +134,7 @@ impl PasswordHelper {
|
|||||||
Systemd => unimplemented!(),
|
Systemd => unimplemented!(),
|
||||||
Stdin => Ok(util::read_password("Password", true)?),
|
Stdin => Ok(util::read_password("Password", true)?),
|
||||||
Script(password_helper) => {
|
Script(password_helper) => {
|
||||||
let mut helper_parts = password_helper.split(' ');
|
let mut helper_parts = password_helper.split(" ");
|
||||||
|
|
||||||
let password = Command::new((&mut helper_parts).next().unwrap())
|
let password = Command::new((&mut helper_parts).next().unwrap())
|
||||||
.args(helper_parts)
|
.args(helper_parts)
|
||||||
@@ -149,38 +148,3 @@ impl PasswordHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn input_salt_from_str() {
|
|
||||||
assert_eq!(
|
|
||||||
"file:/tmp/abc".parse::<InputSalt>().unwrap(),
|
|
||||||
InputSalt::File {
|
|
||||||
path: "/tmp/abc".into()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
"string:abc".parse::<InputSalt>().unwrap(),
|
|
||||||
InputSalt::String("abc".into())
|
|
||||||
);
|
|
||||||
assert_eq!("ask".parse::<InputSalt>().unwrap(), InputSalt::AskPassword);
|
|
||||||
assert_eq!("lol".parse::<InputSalt>().unwrap(), InputSalt::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn input_salt_obtain() {
|
|
||||||
assert_eq!(
|
|
||||||
InputSalt::String("abc".into())
|
|
||||||
.obtain(&PasswordHelper::Stdin)
|
|
||||||
.unwrap(),
|
|
||||||
[
|
|
||||||
186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97,
|
|
||||||
163, 150, 23, 122, 156, 180, 16, 255, 97, 242, 0, 21, 173
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,61 +1,51 @@
|
|||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
|
||||||
use crate::util;
|
use ctap;
|
||||||
use ctap::{
|
use ctap::extensions::hmac::{FidoHmacCredential, HmacExtension};
|
||||||
self, extensions::hmac::HmacExtension, request_multiple_devices, FidoAssertionRequestBuilder,
|
use ctap::{FidoDevice, FidoError, FidoErrorKind};
|
||||||
FidoCredential, FidoCredentialRequestBuilder, FidoDevice, FidoError, FidoErrorKind,
|
|
||||||
};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
const RP_ID: &str = "fido2luks";
|
pub fn make_credential_id() -> Fido2LuksResult<FidoHmacCredential> {
|
||||||
|
let mut errs = Vec::new();
|
||||||
pub fn make_credential_id(
|
match get_devices()? {
|
||||||
name: Option<&str>,
|
ref devs if devs.is_empty() => Err(Fido2LuksError::NoAuthenticatorError)?,
|
||||||
pin: Option<&str>,
|
devs => {
|
||||||
) -> Fido2LuksResult<FidoCredential> {
|
for mut dev in devs.into_iter() {
|
||||||
let mut request = FidoCredentialRequestBuilder::default().rp_id(RP_ID);
|
match dev.make_hmac_credential() {
|
||||||
if let Some(user_name) = name {
|
Ok(cred) => {
|
||||||
request = request.user_name(user_name);
|
return Ok(cred);
|
||||||
}
|
}
|
||||||
let request = request.build().unwrap();
|
Err(e) => {
|
||||||
let make_credential = |device: &mut FidoDevice| {
|
errs.push(e);
|
||||||
if let Some(pin) = pin {
|
}
|
||||||
device.unlock(pin)?;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
device.make_hmac_credential(&request)
|
}
|
||||||
};
|
Err(errs.pop().ok_or(Fido2LuksError::NoAuthenticatorError)?)?
|
||||||
Ok(request_multiple_devices(
|
|
||||||
get_devices()?
|
|
||||||
.iter_mut()
|
|
||||||
.map(|device| (device, &make_credential)),
|
|
||||||
None,
|
|
||||||
)?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform_challenge<'a>(
|
pub fn perform_challenge(credential_id: &str, salt: &[u8; 32]) -> Fido2LuksResult<[u8; 32]> {
|
||||||
credentials: &'a [&'a FidoCredential],
|
let cred = FidoHmacCredential {
|
||||||
salt: &[u8; 32],
|
id: hex::decode(credential_id).unwrap(),
|
||||||
timeout: Duration,
|
rp_id: "hmac".to_string(),
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
device.get_hmac_assertion(&request, &util::sha256(&[&salt[..]]), None)
|
|
||||||
};
|
};
|
||||||
let (credential, (secret, _)) = request_multiple_devices(
|
let mut errs = Vec::new();
|
||||||
get_devices()?
|
match get_devices()? {
|
||||||
.iter_mut()
|
ref devs if devs.is_empty() => Err(Fido2LuksError::NoAuthenticatorError)?,
|
||||||
.map(|device| (device, &get_assertion)),
|
devs => {
|
||||||
Some(timeout),
|
for mut dev in devs.into_iter() {
|
||||||
)?;
|
match dev.hmac_challange(&cred, &salt[..]) {
|
||||||
Ok((secret, credential))
|
Ok(secret) => {
|
||||||
|
return Ok(secret);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
errs.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(errs.pop().ok_or(Fido2LuksError::NoAuthenticatorError)?)?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_devices() -> Fido2LuksResult<Vec<FidoDevice>> {
|
pub fn get_devices() -> Fido2LuksResult<Vec<FidoDevice>> {
|
||||||
@@ -64,7 +54,7 @@ pub fn get_devices() -> Fido2LuksResult<Vec<FidoDevice>> {
|
|||||||
match FidoDevice::new(&di) {
|
match FidoDevice::new(&di) {
|
||||||
Err(e) => match e.kind() {
|
Err(e) => match e.kind() {
|
||||||
FidoErrorKind::ParseCtap | FidoErrorKind::DeviceUnsupported => (),
|
FidoErrorKind::ParseCtap | FidoErrorKind::DeviceUnsupported => (),
|
||||||
err => return Err(FidoError::from(err).into()),
|
err => Err(FidoError::from(err))?,
|
||||||
},
|
},
|
||||||
Ok(dev) => devices.push(dev),
|
Ok(dev) => devices.push(dev),
|
||||||
}
|
}
|
||||||
|
69
src/error.rs
69
src/error.rs
@@ -11,15 +11,11 @@ pub enum Fido2LuksError {
|
|||||||
KeyfileError { cause: io::Error },
|
KeyfileError { cause: io::Error },
|
||||||
#[fail(display = "authenticator error: {}", cause)]
|
#[fail(display = "authenticator error: {}", cause)]
|
||||||
AuthenticatorError { cause: ctap::FidoError },
|
AuthenticatorError { cause: ctap::FidoError },
|
||||||
#[fail(display = "no authenticator found, please ensure your device is plugged in")]
|
#[fail(display = "no authenticator found, please ensure you device is plugged in")]
|
||||||
NoAuthenticatorError,
|
NoAuthenticatorError,
|
||||||
#[fail(display = " {}", cause)]
|
#[fail(display = "luks err")]
|
||||||
CryptsetupError {
|
LuksError { cause: cryptsetup_rs::device::Error },
|
||||||
cause: libcryptsetup_rs::LibcryptErr,
|
#[fail(display = "no authenticator found, please ensure you device is plugged in")]
|
||||||
},
|
|
||||||
#[fail(display = "{}", cause)]
|
|
||||||
LuksError { cause: LuksError },
|
|
||||||
#[fail(display = "{}", cause)]
|
|
||||||
IoError { cause: io::Error },
|
IoError { cause: io::Error },
|
||||||
#[fail(display = "supplied secret isn't valid for this device")]
|
#[fail(display = "supplied secret isn't valid for this device")]
|
||||||
WrongSecret,
|
WrongSecret,
|
||||||
@@ -27,19 +23,6 @@ pub enum Fido2LuksError {
|
|||||||
StringEncodingError { cause: FromUtf8Error },
|
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)]
|
#[derive(Debug, Fail)]
|
||||||
pub enum AskPassError {
|
pub enum AskPassError {
|
||||||
#[fail(display = "unable to retrieve password: {}", _0)]
|
#[fail(display = "unable to retrieve password: {}", _0)]
|
||||||
@@ -48,41 +31,6 @@ pub enum AskPassError {
|
|||||||
Mismatch,
|
Mismatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub enum LuksError {
|
|
||||||
#[fail(display = "This feature requires to the LUKS device to be formatted as LUKS 2")]
|
|
||||||
Luks2Required,
|
|
||||||
#[fail(display = "Invalid token: {}", _0)]
|
|
||||||
InvalidToken(String),
|
|
||||||
#[fail(display = "No token found")]
|
|
||||||
NoToken,
|
|
||||||
#[fail(display = "The device already exists")]
|
|
||||||
DeviceExists,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuksError {
|
|
||||||
pub fn activate(e: LibcryptErr) -> Fido2LuksError {
|
|
||||||
match e {
|
|
||||||
LibcryptErr::IOError(ref io) => match io.raw_os_error() {
|
|
||||||
Some(1) if io.kind() == ErrorKind::PermissionDenied => Fido2LuksError::WrongSecret,
|
|
||||||
Some(17) => Fido2LuksError::LuksError {
|
|
||||||
cause: LuksError::DeviceExists,
|
|
||||||
},
|
|
||||||
_ => return Fido2LuksError::CryptsetupError { cause: e },
|
|
||||||
},
|
|
||||||
_ => Fido2LuksError::CryptsetupError { cause: e },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LuksError> for Fido2LuksError {
|
|
||||||
fn from(e: LuksError) -> Self {
|
|
||||||
Fido2LuksError::LuksError { cause: e }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use libcryptsetup_rs::LibcryptErr;
|
|
||||||
use std::io::ErrorKind;
|
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
use Fido2LuksError::*;
|
use Fido2LuksError::*;
|
||||||
|
|
||||||
@@ -92,16 +40,17 @@ impl From<FidoError> for Fido2LuksError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LibcryptErr> for Fido2LuksError {
|
impl From<cryptsetup_rs::device::Error> for Fido2LuksError {
|
||||||
fn from(e: LibcryptErr) -> Self {
|
fn from(e: cryptsetup_rs::device::Error) -> Self {
|
||||||
match e {
|
match e {
|
||||||
LibcryptErr::IOError(e) if e.raw_os_error().iter().any(|code| code == &1i32) => {
|
cryptsetup_rs::device::Error::CryptsetupError(error_no) if error_no.0 == 1i32 => {
|
||||||
WrongSecret
|
WrongSecret
|
||||||
}
|
}
|
||||||
_ => CryptsetupError { cause: e },
|
e => LuksError { cause: e },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for Fido2LuksError {
|
impl From<io::Error> for Fido2LuksError {
|
||||||
fn from(e: io::Error) -> Self {
|
fn from(e: io::Error) -> Self {
|
||||||
IoError { cause: e }
|
IoError { cause: e }
|
||||||
|
327
src/luks.rs
327
src/luks.rs
@@ -1,327 +0,0 @@
|
|||||||
use crate::error::*;
|
|
||||||
|
|
||||||
use libcryptsetup_rs::{
|
|
||||||
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>,
|
|
||||||
) -> Fido2LuksResult<u32> {
|
|
||||||
self.device
|
|
||||||
.activate_handle()
|
|
||||||
.activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty())
|
|
||||||
.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>,
|
|
||||||
) -> 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) {
|
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
40
src/main.rs
40
src/main.rs
@@ -1,32 +1,38 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
extern crate ctap_hmac as ctap;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
use crate::cli::*;
|
use crate::cli::*;
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use crate::device::*;
|
use crate::device::*;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use std::io;
|
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::path::PathBuf;
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod device;
|
mod device;
|
||||||
mod error;
|
mod error;
|
||||||
mod luks;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
fn main() -> Fido2LuksResult<()> {
|
fn open_container(device: &PathBuf, name: &str, secret: &[u8; 32]) -> Fido2LuksResult<()> {
|
||||||
match run_cli() {
|
let mut handle = luks::open(device.canonicalize()?)?.luks1()?;
|
||||||
Err(e) => {
|
let _slot = handle.activate(name, &secret[..])?;
|
||||||
#[cfg(debug_assertions)]
|
Ok(())
|
||||||
eprintln!("{:?}", e);
|
}
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
eprintln!("{}", e);
|
fn assemble_secret(hmac_result: &[u8], salt: &[u8]) -> [u8; 32] {
|
||||||
exit(e.exit_code())
|
let mut digest = Sha256::new();
|
||||||
}
|
digest.input(salt);
|
||||||
_ => exit(0),
|
digest.input(hmac_result);
|
||||||
}
|
let mut secret = [0u8; 32];
|
||||||
|
digest.result(&mut secret);
|
||||||
|
secret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Fido2LuksResult<()> {
|
||||||
|
run_cli()
|
||||||
}
|
}
|
||||||
|
17
src/util.rs
17
src/util.rs
@@ -1,19 +1,8 @@
|
|||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use ring::digest;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
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> {
|
pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult<String> {
|
||||||
match rpassword::read_password_from_tty(Some(&[q, ": "].join("")))? {
|
match rpassword::read_password_from_tty(Some(&[q, ": "].join("")))? {
|
||||||
ref pass
|
ref pass
|
||||||
@@ -23,16 +12,12 @@ pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult<String> {
|
|||||||
{
|
{
|
||||||
Err(Fido2LuksError::AskPassError {
|
Err(Fido2LuksError::AskPassError {
|
||||||
cause: AskPassError::Mismatch,
|
cause: AskPassError::Mismatch,
|
||||||
})
|
})?
|
||||||
}
|
}
|
||||||
pass => Ok(pass),
|
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>> {
|
pub fn read_keyfile<P: Into<PathBuf>>(path: P) -> Fido2LuksResult<Vec<u8>> {
|
||||||
let mut file = File::open(path.into())?;
|
let mut file = File::open(path.into())?;
|
||||||
let mut key = Vec::new();
|
let mut key = Vec::new();
|
||||||
|
Reference in New Issue
Block a user