diff --git a/README.md b/README.md
index 67849e4a..e578c2af 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,8 @@ Or, if you are in a hurry, kill all processes with open files inside your tomb a
```
$ tomb slam
```
+
+Tomb also supports two-factor unlocking with a FIDO2 passkey; see `doc/FIDO2.md` for setup details.
## 📖 [Get started on dyne.org/tomb](https://dyne.org/tomb)
diff --git a/doc/FIDO2.md b/doc/FIDO2.md
new file mode 100644
index 00000000..df0ef2da
--- /dev/null
+++ b/doc/FIDO2.md
@@ -0,0 +1,52 @@
+# FIDO2 / Passkey Support in Tomb
+
+Tomb can protect a tomb key with a FIDO2/WebAuthn passkey instead of a traditional passphrase. The secret material that unlocks the key is derived from the authenticator, so opening the tomb requires:
+
+- Physical presence on the passkey (touch/UV).
+- The authenticator PIN (if configured).
+
+This effectively replaces the passphrase with two‑factor authentication: you need both the hardware token and its PIN.
+
+## How it works
+
+- Tomb registers a discoverable credential on your authenticator using the `hmac-secret` extension.
+- During forge (`tomb forge --fido2 -k …`) Tomb asks the device for an HMAC secret derived from:
+ - a random challenge,
+ - the relying party (`tomb.dyne.org` by default),
+ - the credential id, and
+ - a random salt Tomb stores in the key header.
+- The returned 32‑byte secret (plus optional extra material) is used as the passphrase to encrypt the tomb key. Tomb embeds the FIDO2 metadata (`rp`, `cred`, `salt`, optional `device`) in the key header.
+
+When opening/locking with `--fido2`, Tomb:
+
+1) Reads the `_FIDO2 …` header from the key file.
+2) Requests a new `hmac-secret` from the authenticator with the stored RP, credential id, and salt.
+3) Uses that secret to decrypt the key and unlock the tomb.
+
+## Usage
+
+- Forge a FIDO2 key:
+ ```
+ tomb forge --fido2 -k secrets.tomb.key
+ ```
+- Lock/open with the passkey:
+ ```
+ tomb lock --fido2 -k secrets.tomb.key secrets.tomb
+ tomb open --fido2 -k secrets.tomb.key secrets.tomb
+ ```
+- If you have multiple authenticators, point to one explicitly: `--fido2-device /dev/hidrawX`.
+
+## Hardware requirements
+
+- The authenticator must advertise the `hmac-secret` extension (`fido2-token -I` should list `extension strings: hmac-secret`).
+- A PIN/UV-capable device is required; Tomb requests user verification.
+
+### DIY option: Fidelio on Raspberry Pi Pico
+
+If you need a compliant token and want to build it yourself, you can flash the open-source [Fidelio](https://github.com/danielinux/fidelio) firmware on a Raspberry Pi Pico to create a FIDO2 authenticator with `hmac-secret` support.
+
+## Security notes
+
+- The passphrase is never stored; the authenticator derives it on demand.
+- Losing the registered passkey means losing access to the tomb unless you keep another copy of the key protected differently.
+- The FIDO2 metadata in the key header is not secret; protect the key file itself as usual.
diff --git a/doc/tomb.1 b/doc/tomb.1
index 13d4aae3..19631791 100644
--- a/doc/tomb.1
+++ b/doc/tomb.1
@@ -280,6 +280,24 @@ Tell tomb to use an asymmetric GnuPG key encryption instead of a
symmetric passphrase to protect a tomb key. This option can be
followed by \fI-r\fR when the command needs to specify recipient(s).
.B
+.IP "--fido2"
+Use a FIDO2/WebAuthn passkey (hmac-secret) instead of a passphrase to protect
+the tomb key. Requires an authenticator that advertises the hmac-secret
+extension; during forge/open/lock Tomb will prompt for PIN/UV and device
+presence. For details see doc/FIDO2.md.
+.B
+.IP "--fido2-device \fI\fR"
+Select a specific FIDO2 authenticator (e.g. /dev/hidrawX). If omitted Tomb
+auto-detects.
+.B
+.IP "--fido2-rp \fI\fR"
+Override the relying party id used when registering the passkey (default:
+tomb.dyne.org).
+.B
+.IP "--fido2-user \fI\fR"
+Override the username stored in the authenticator when registering the passkey
+(default: the current user).
+.B
.IP "-r \fI[,]\fR"
Provide a new set of recipient(s) to encrypt a tomb key. \fIgpg_ids\fR
can be one or more GPG key ID, comma separated. All GPG keys must be
diff --git a/tomb b/tomb
index 157512d2..58ab90e0 100755
--- a/tomb
+++ b/tomb
@@ -73,6 +73,15 @@ typeset -i ACL=1
typeset -i ARGON2=1
typeset -i SEARCH=1
+# FIDO2 support (see _ensure_dependencies)
+typeset -i FIDO2=1
+typeset -i TOMB_FIDO2=0
+typeset -H TOMB_FIDO2_HEADER
+typeset -H TOMB_FIDO2_RP
+typeset -H TOMB_FIDO2_CRED
+typeset -H TOMB_FIDO2_SALT
+typeset -H TOMB_FIDO2_DEVICE
+typeset -H TOMB_FIDO2_SECRET
# Default mount options
typeset MOUNTOPTS="rw,noatime,nodev"
@@ -292,6 +301,7 @@ _tmp_create() {
[[ $? == 0 ]] || _failure "Fatal error creating the temporary directory: ::1 temp dir::" "$TMPDIR"
}
+ local tfile
# We're going to add one more $RANDOM for each time someone complains
# about this being too weak of a random.
tfile="${TMPDIR}/$RANDOM$RANDOM$RANDOM$RANDOM" # Temporary file
@@ -746,6 +756,10 @@ usage() {
_print " -g use a GnuPG key to encrypt a tomb key"
_print " -r provide GnuPG recipients (separated by comma)"
_print " -R provide GnuPG hidden recipients (separated by comma)"
+ _print " --fido2 encrypt the tomb key using a FIDO2/WebAuthn passkey"
+ _print " --fido2-device path to a specific FIDO2 authenticator (auto-detected by default)"
+ _print " --fido2-rp relying party id to register on the passkey (default: tomb.dyne.org)"
+ _print " --fido2-user username to register on the passkey (default: current user)"
_print " --sudo super user exec alternative to sudo (doas or none)"
[[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
@@ -903,6 +917,7 @@ _list_optional_tools() {
typeset -a _deps
_deps=(gettext dcfldd shred steghide)
_deps+=(resize2fs tomb-kdb-pbkdf2 argon2 qrencode recoll unoconv lsof setfacl mlocate plocate)
+ _deps+=(fido2-cred fido2-assert fido2-token)
for d in $_deps; do
_print "`which $d`"
done
@@ -969,6 +984,10 @@ _ensure_dependencies() {
_is_found setfacl || ACL=0
# Check for plocate/mlocate
_is_found plocate || _is_found mlocate || SEARCH=0
+ # Check for FIDO2 helpers
+ _is_found fido2-cred || FIDO2=0
+ _is_found fido2-assert || FIDO2=0
+ _is_found fido2-token || FIDO2=0
}
# }}} - Commandline interaction
@@ -1034,6 +1053,120 @@ _gpg_uid() {
_get_username() { getent passwd ${1} | awk -F: '{print $1}' }
_get_home() { getent passwd ${1} | awk -F: '{print $6}' }
+# Reset all FIDO2-related globals
+_fido2_reset_metadata() {
+ TOMB_FIDO2=0
+ TOMB_FIDO2_HEADER=""
+ TOMB_FIDO2_RP=""
+ TOMB_FIDO2_CRED=""
+ TOMB_FIDO2_SALT=""
+ TOMB_FIDO2_DEVICE=""
+ TOMB_FIDO2_SECRET=""
+}
+
+_require_fido2_available() {
+ [[ $FIDO2 == 1 ]] || {
+ _failure "FIDO2 helpers are not available (install fido2-cred, fido2-assert, fido2-token)." }
+}
+
+_fido2_random_b64() {
+ local bytes="${1:-32}"
+ dd if=/dev/urandom bs=1 count=$bytes 2>/dev/null | base64 | tr -d '\r\n'
+}
+
+_fido2_autodetect_device() {
+ local line
+ while read line; do
+ [[ -z "$line" ]] && continue
+ # Typical outputs:
+ # /dev/hidraw4: vendor=0x1050 product=0x0407 (YubiKey OTP+FIDO+CCID)
+ # path=/dev/hidraw4 vendor=0x1050 product=0x0407
+ line=${line#path=}
+ case "$line" in
+ /dev/hidraw[0-9]*)
+ print -n -- "${line%:}"
+ return 0
+ ;;
+ /dev/hidraw[0-9]*:*)
+ line=${line%%:*}
+ print -n -- "$line"
+ return 0
+ ;;
+ esac
+ done <<<"$(fido2-token -L 2>/dev/null)"
+ return 1
+}
+
+_fido2_pick_device() {
+ local device
+
+ option_is_set --fido2-device && device=$(option_value --fido2-device)
+ [[ -z $device && -n $TOMB_FIDO2_DEVICE ]] && device=$TOMB_FIDO2_DEVICE
+ [[ -z $device ]] && device=$(_fido2_autodetect_device)
+
+ [[ -z $device ]] && {
+ _failure "No FIDO2 authenticator found. Use --fido2-device to point to one." }
+
+ device="${device%% *}"
+ device="${device%%:*}"
+
+ print -n -- $device
+}
+
+_fido2_require_extension() {
+ local device="$1" ext="$2"
+ local info line
+ info=$(fido2-token -I "$device" 2>/dev/null) || return 1
+ for line in ${(f)info}; do
+ # match prefix case-insensitively
+ if [[ "${(L)line}" == "extension strings:"* ]]; then
+ line=${line#*:}
+ for word in ${(s: :)line}; do
+ [[ "$word" == "$ext" ]] && return 0
+ done
+ fi
+ done
+ return 1
+}
+
+_fido2_refresh_header() {
+ [[ -z $TOMB_FIDO2_RP || -z $TOMB_FIDO2_CRED || -z $TOMB_FIDO2_SALT ]] && return 0
+ TOMB_FIDO2_HEADER="_FIDO2 rp=$TOMB_FIDO2_RP cred=$TOMB_FIDO2_CRED salt=$TOMB_FIDO2_SALT"
+ if [[ -n $TOMB_FIDO2_DEVICE ]]; then
+ local _dev="${TOMB_FIDO2_DEVICE// /%20}"
+ TOMB_FIDO2_HEADER+=" device=${_dev}"
+ fi
+}
+
+_fido2_parse_header() {
+ local header="$1" tok key val
+
+ _fido2_reset_metadata
+ TOMB_FIDO2=1
+ header="${header#_FIDO2 }"
+ for tok in ${(s: :)header}; do
+ key=${tok%%=*}
+ val=${tok#*=}
+ case $key in
+ rp) TOMB_FIDO2_RP="$val" ;;
+ cred) TOMB_FIDO2_CRED="$val" ;;
+ salt) TOMB_FIDO2_SALT="$val" ;;
+ device) TOMB_FIDO2_DEVICE="${val//%20/ }" ;;
+ esac
+ done
+ _fido2_refresh_header
+}
+
+_parse_key_headers() {
+ local line
+
+ _fido2_reset_metadata
+ for line in ${(f)TOMBKEY}; do
+ [[ "$line" == "-----BEGIN PGP MESSAGE-----" ]] && break
+ [[ "$line" == "_FIDO2"* ]] && _fido2_parse_header "$line"
+ done
+}
+
# $1 is the encrypted key contents we are checking
is_valid_key() {
local key="$1" # Unique argument is an encrypted key to test
@@ -1061,13 +1194,17 @@ is_valid_key() {
_message "Key is an image, it might be valid."
return 0 }
- [[ $KDF == 1 ]] && { ! option_is_set -g } && { option_is_set --kdf } && {
- _head="${key[(f)1]}"
- [[ $_head =~ '^_KDF_' ]] || {
+ if [[ $KDF == 1 ]] && { ! option_is_set -g } && { option_is_set --kdf }; then
+ local _head
+ for _head in ${(f)key}; do
+ [[ "$_head" == "-----BEGIN PGP MESSAGE-----" ]] && break
+ [[ "$_head" =~ '^_KDF_' ]] && break
+ done
+ [[ "$_head" =~ '^_KDF_' ]] || {
_warning "Key is missing KDF header."
return 1
}
- }
+ fi
[[ $key =~ "BEGIN PGP" ]] && {
_message "Key is valid."
@@ -1079,14 +1216,17 @@ is_valid_key() {
# $1 is a string containing an encrypted key
recover_key() {
local key="${1}" # Unique argument is an encrypted key
+ local _head
_warning "Attempting key recovery."
- _head="${key[(f)1]}" # take the first line
-
TOMBKEY="" # Reset global variable
- [[ $_head =~ "^_KDF_" ]] && TOMBKEY+="$_head\n"
+ for _head in ${(f)key}; do
+ [[ "$_head" == "-----BEGIN PGP MESSAGE-----" ]] && break
+ [[ "$_head" =~ "^_KDF_" ]] && TOMBKEY+="$_head\n"
+ [[ "$_head" =~ "^_FIDO2" ]] && TOMBKEY+="$_head\n"
+ done
TOMBKEY+="-----BEGIN PGP MESSAGE-----\n"
TOMBKEY+="$key\n"
@@ -1136,6 +1276,8 @@ _load_key() {
_failure "Key not found, specify one using -k."
}
+ _parse_key_headers
+
is_valid_key $TOMBKEY || {
_warning "The key seems invalid or its format is not known by this version of Tomb."
recover_key $TOMBKEY
@@ -1207,19 +1349,22 @@ get_lukskey() {
_verbose "get_lukskey"
_password="$1"
+ local headerline="" kdf_hash kdf_salt kdf_ic kdf_len kdf_mem kdf_par
-
- firstline="${TOMBKEY[(f)1]}"
+ for headerline in ${(f)TOMBKEY}; do
+ [[ "$headerline" == "-----BEGIN PGP MESSAGE-----" ]] && break
+ [[ "$headerline" =~ '^_KDF_' ]] && break
+ done
# key is KDF encoded
- if [[ $firstline =~ '^_KDF_' ]]; then
- kdf_hash="${firstline[(ws:_:)2]}"
+ if [[ $headerline =~ '^_KDF_' ]]; then
+ kdf_hash="${headerline[(ws:_:)2]}"
_verbose "KDF: ::1 kdf::" "$kdf_hash"
case "$kdf_hash" in
"pbkdf2sha1")
- kdf_salt="${firstline[(ws:_:)3]}"
- kdf_ic="${firstline[(ws:_:)4]}"
- kdf_len="${firstline[(ws:_:)5]}"
+ kdf_salt="${headerline[(ws:_:)3]}"
+ kdf_ic="${headerline[(ws:_:)4]}"
+ kdf_len="${headerline[(ws:_:)5]}"
_message "Unlocking KDF key protection (::1 kdf::)" $kdf_hash
_verbose "KDF salt: $kdf_salt"
_verbose "KDF ic: $kdf_ic"
@@ -1228,10 +1373,10 @@ get_lukskey() {
;;
"argon2")
- kdf_salt="${firstline[(ws:_:)3]}"
- kdf_ic="${firstline[(ws:_:)4]}"
- kdf_mem="${firstline[(ws:_:)5]}"
- kdf_par="${firstline[(ws:_:)6]}"
+ kdf_salt="${headerline[(ws:_:)3]}"
+ kdf_ic="${headerline[(ws:_:)4]}"
+ kdf_mem="${headerline[(ws:_:)5]}"
+ kdf_par="${headerline[(ws:_:)6]}"
# ToDo also parse kdf_len?
_message "Unlocking KDF key protection (::1 kdf::)" $kdf_hash
_verbose "KDF salt: $kdf_salt"
@@ -1277,6 +1422,130 @@ get_lukskey() {
return $ret
}
+_fido2_require_metadata() {
+ [[ -n $TOMB_FIDO2_RP && -n $TOMB_FIDO2_CRED && -n $TOMB_FIDO2_SALT ]] || {
+ _failure "Key is missing required FIDO2 metadata."
+ }
+}
+
+_fido2_create_binding() {
+ local device rp user challenge user_id cred_input cred_output cred_id
+
+ _require_fido2_available
+ device=$(_fido2_pick_device)
+ _fido2_require_extension "$device" "hmac-secret" || {
+ _failure "FIDO2 authenticator does not advertise hmac-secret. Please enable hmac-secret on the key or use a compatible device."
+ }
+ rp="$(option_value --fido2-rp)"
+ rp=${rp:-$TOMB_FIDO2_RP}
+ rp=${rp:-tomb.dyne.org}
+ user="$(option_value --fido2-user)"
+ user=${user:-$_USER}
+ user=${user:-tomb}
+ TOMB_FIDO2_RP="$rp"
+ [[ -n $TOMB_FIDO2_SALT ]] || TOMB_FIDO2_SALT=$(_fido2_random_b64 32)
+
+ challenge=$(_fido2_random_b64 32)
+ user_id=$(_fido2_random_b64 32)
+
+ _tmp_create
+ cred_input=$TOMBTMP
+ print $challenge > $cred_input
+ print $rp >> $cred_input
+ print $user >> $cred_input
+ print $user_id >> $cred_input
+
+ _tmp_create
+ cred_output=$TOMBTMP
+ _message "Creating a FIDO2 binding on ::1::" $device
+ _verbose "Running: fido2-cred -M -h -v -i ::1:: ::2::" $cred_input $device
+ fido2-cred -M -h -v -i $cred_input $device > $cred_output
+ [[ $? != 0 ]] && _failure "FIDO2 credential creation failed."
+
+ cred_id="$(sed -n '5p' $cred_output | tr -d '\r\n')"
+ [[ -z $cred_id ]] && _failure "Cannot read credential id from FIDO2 authenticator."
+
+ TOMB_FIDO2=1
+ TOMB_FIDO2_CRED="$cred_id"
+ TOMB_FIDO2_DEVICE="$device"
+ _fido2_refresh_header
+ option_is_set -D && {
+ _verbose "FIDO2 rp=::1:: cred=::2:: salt=::3:: device=::4::" "$TOMB_FIDO2_RP" "$TOMB_FIDO2_CRED" "$TOMB_FIDO2_SALT" "$TOMB_FIDO2_DEVICE"
+ _verbose "FIDO2 header: ::1::" "$TOMB_FIDO2_HEADER"
+ }
+ [[ -z $TOMB_FIDO2_HEADER ]] && {
+ option_is_set -D && cat $cred_output >&2
+ _failure "Internal error: missing FIDO2 metadata after credential creation."
+ }
+}
+
+_fido2_collect_secret() {
+ local extra="$1" device assertion_input assertion_output challenge secret
+
+ _require_fido2_available
+ _fido2_require_metadata
+
+ device=$(_fido2_pick_device)
+ challenge=$(_fido2_random_b64 32)
+
+ _tmp_create
+ assertion_input=$TOMBTMP
+ print $challenge > $assertion_input
+ print $TOMB_FIDO2_RP >> $assertion_input
+ print $TOMB_FIDO2_CRED >> $assertion_input
+ print $TOMB_FIDO2_SALT >> $assertion_input
+
+ _tmp_create
+ assertion_output=$TOMBTMP
+ _message "Waiting for FIDO2 authentication on ::1::" $device
+ # -h requests hmac-secret, -v asks for user verification (PIN/touch)
+ _verbose "Running: fido2-assert -G -h -v -i ::1:: ::2::" $assertion_input $device
+ fido2-assert -G -h -v -i $assertion_input $device > $assertion_output
+ local _fido_rc=$?
+
+ local -a _assert_lines
+ _assert_lines=(${(f)"$(cat $assertion_output)"})
+ _verbose "fido2-assert output lines: ${#_assert_lines}"
+ if [[ ${#_assert_lines} -lt 5 ]]; then
+ _warning "FIDO2 device did not provide an hmac-secret (unexpected output)."
+ option_is_set -D && cat $assertion_output >&2
+ [[ $_fido_rc != 0 ]] && _warning "fido2-assert exit code: $_fido_rc"
+ return 1
+ fi
+
+ # For non-resident creds without largeBlob, last line should be the hmac secret
+ secret=${_assert_lines[-1]}
+ secret=${secret//$'\r'/}
+ [[ -z $secret ]] && {
+ _warning "FIDO2 device did not provide an hmac-secret."
+ option_is_set -D && cat $assertion_output >&2
+ [[ $_fido_rc != 0 ]] && _warning "fido2-assert exit code: $_fido_rc"
+ return 1
+ }
+
+ TOMB_FIDO2_DEVICE="$device"
+ _fido2_refresh_header
+
+ TOMB_FIDO2_SECRET="${secret}${extra}"
+ print -n -- "$TOMB_FIDO2_SECRET"
+}
+
+_fido2_prepare_passphrase() {
+ local extra="$1" rpopt secret
+
+ _require_fido2_available
+ [[ $TOMB_FIDO2 == 1 ]] || TOMB_FIDO2=1
+ rpopt="$(option_value --fido2-rp)"
+ [[ -z $TOMB_FIDO2_RP ]] && TOMB_FIDO2_RP=${rpopt:-tomb.dyne.org}
+ [[ -z $TOMB_FIDO2_SALT ]] && TOMB_FIDO2_SALT=$(_fido2_random_b64 32)
+
+ [[ -n $TOMB_FIDO2_CRED ]] || _fido2_create_binding
+ _fido2_refresh_header
+
+ _fido2_collect_secret "$extra" || return 1
+ print -n -- "$TOMB_FIDO2_SECRET"
+}
+
# This function asks the user for the password to use the key it tests
# it against the return code of gpg on success returns 0 and saves
# the password in the global variable $TOMBPASSWORD
@@ -1284,18 +1553,45 @@ ask_key_password() {
[[ -z "$TOMBKEYFILE" ]] && {
_failure "Internal error: ask_key_password() called before _load_key()." }
+ local passok=0 tombpass=""
+
[[ "$TOMBKEYFILE" = "cleartext" ]] && {
_verbose "no password needed, using secret bytes from stdin"
return 0 }
+ if option_is_set --fido2 && [[ $TOMB_FIDO2 == 0 ]]; then
+ _failure "this key was forged without the --fido2 option"
+ fi
+
if option_is_set -g; then
_verbose "no password needed, using GPG key"
get_lukskey
return $?
fi
+ passok=0
+
+ if [[ $TOMB_FIDO2 == 1 ]]; then
+ local extra_pass=""
+ [[ -n $1 ]] && extra_pass="$1"
+ _message "a FIDO2 passkey is required to use ::1 key::" $TOMBKEYFILE
+
+ for c in 1 2 3; do
+ (( c > 1 )) && _warning "Retrying FIDO2 authentication (attempt $c)."
+ _fido2_collect_secret "$extra_pass" || continue
+ tombpass="$TOMB_FIDO2_SECRET"
+ get_lukskey "$tombpass"
+ [[ $? = 0 ]] && {
+ passok=1; _message "Password OK."
+ break;
+ }
+ done
+
+ TOMBPASSWORD=$tombpass
+ [[ $passok == 1 ]] || return 1
+ return 0
+ fi
_message "A password is required to use key ::1 key::" $TOMBKEYFILE
- passok=0
tombpass=""
if [[ -z $1 ]]; then
@@ -1414,9 +1710,11 @@ gen_key() {
local gpgpass opt
local recipients_opt
typeset -a gpgopt
+ typeset -a keyheaders
# here user is prompted for key password
tombpass=""
tombpasstmp=""
+ [[ $TOMB_FIDO2 == 0 ]] && { option_is_set --fido2 } && TOMB_FIDO2=1
if option_is_set -g; then
gpgopt=(--encrypt)
@@ -1452,7 +1750,22 @@ gen_key() {
gpgpass="$TOMBSECRET"
opt=''
else
- if [ "$2" = "" ]; then
+ if [[ $TOMB_FIDO2 == 1 ]]; then
+ local extra_pass="$2"
+ [[ -n $extra_pass ]] && _verbose "gen_key combines FIDO2 secret with extra material."
+ _fido2_prepare_passphrase "$extra_pass" || _failure "Unable to obtain FIDO2 secret for the key."
+ tombpass="$TOMB_FIDO2_SECRET"
+ _fido2_refresh_header
+ if [[ -z $TOMB_FIDO2_HEADER ]]; then
+ _verbose "Rebuilding missing FIDO2 header rp=::1:: cred=::2:: salt=::3:: dev=::4::" "$TOMB_FIDO2_RP" "$TOMB_FIDO2_CRED" "$TOMB_FIDO2_SALT" "$TOMB_FIDO2_DEVICE"
+ if [[ -n $TOMB_FIDO2_RP && -n $TOMB_FIDO2_CRED && -n $TOMB_FIDO2_SALT ]]; then
+ TOMB_FIDO2_HEADER="_FIDO2 rp=$TOMB_FIDO2_RP cred=$TOMB_FIDO2_CRED salt=$TOMB_FIDO2_SALT"
+ [[ -n $TOMB_FIDO2_DEVICE ]] && TOMB_FIDO2_HEADER+=" device=${TOMB_FIDO2_DEVICE// /%20}"
+ else
+ _failure "Internal error: missing FIDO2 metadata after registration."
+ fi
+ fi
+ elif [ "$2" = "" ]; then
while true; do
# 3 tries to write two times a matching password
tombpass=`ask_password "Type the new password to secure your key"`
@@ -1478,7 +1791,8 @@ gen_key() {
_verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass
fi
- header=""
+ [[ -n $TOMB_FIDO2_HEADER ]] && keyheaders+=("$TOMB_FIDO2_HEADER")
+
[[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
{ option_is_set --kdf } && {
# KDF is a key strengthening technique against brute forcing
@@ -1513,7 +1827,7 @@ gen_key() {
_message "iterations: ::1 pbkdf2_iter::" $pbkdf2_iter
# We use a length of 64bytes = 512bits (more than needed!?)
tombpass=`tomb-kdb-pbkdf2 $kdfsalt $pbkdf2_iter 64 <<<"${tombpass}"`
- header="_KDF_pbkdf2sha1_${kdfsalt}_${pbkdf2_iter}_64\n"
+ keyheaders+=("_KDF_pbkdf2sha1_${kdfsalt}_${pbkdf2_iter}_64")
;;
argon2)
_success "Using Argon2 as KDF"
@@ -1525,7 +1839,7 @@ gen_key() {
kdfpar=${kdfpar:-1}
_message "parallelismn: ::1 kdfparallel::" $kdfpar
tombpass=`argon2 $kdfsalt -m $kdfmem -t $itertime -p $kdfpar -l 64 -r <<<"${tombpass}"`
- header="_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_${kdfpar}_64\n"
+ keyheaders+=("_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_${kdfpar}_64")
;;
*)
_warning "unrecognized KDF ::1::" $kdftype
@@ -1535,7 +1849,15 @@ gen_key() {
esac
}
}
- print $header >> "$1"
+
+ if [[ ${#keyheaders[@]} -gt 0 ]]; then
+ local hdr
+ for hdr in $keyheaders; do
+ print $hdr >> "$1"
+ done
+ else
+ print >> "$1"
+ fi
# Set gpg inputs and options
gpgpass="${tombpass}\n$TOMBSECRET"
@@ -1958,6 +2280,8 @@ forge_key() {
# can be specified both as simple argument or using -k
local destkey="$1"
{ option_is_set -k } && { destkey=$(option_value -k) }
+ _fido2_reset_metadata
+ { option_is_set --fido2 } && TOMB_FIDO2=1
local algo="AES256" # Default encryption algorithm
@@ -1995,6 +2319,7 @@ forge_key() {
[[ $KDF == 1 ]] && { ! option_is_set -g } && {
_message "Using KDF to protect the key password"
}
+ [[ $TOMB_FIDO2 == 1 ]] && _require_fido2_available
TOMBKEYFILE="$destkey" # Set global variable
@@ -3137,7 +3462,7 @@ main() {
# can only use the non-abbreviated long-option version like:
# -force and NOT -f
#
- main_opts=(q -quiet=q D -debug=D h -help=h v -version=v f -force=f -tmp: U: G: T: -no-color -unsafe g -gpgkey=g -sudo: -pinentry:)
+ main_opts=(q -quiet=q D -debug=D h -help=h v -version=v f -force=f -tmp: U: G: T: -no-color -unsafe g -gpgkey=g -sudo: -pinentry: -fido2 -fido2-device: -fido2-rp: -fido2-user:)
subcommands_opts[__default]=""
# -o in open and mount is used to pass alternate mount options
subcommands_opts[open]="n -nohook=n k: o: -ignore-swap -tomb-pwd: r: R: p -preserve-ownership=p"