-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add fido2 pin #399
Closed
Closed
Add fido2 pin #399
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d3067bc
feat: add fido2 pin
a44fa31
chore: change comment
c57c349
chore: remove kid field
2130cbe
fix: resolve shellcheck errors in clevis-decrypt-fido2
olastor 4899702
refactor: use AES256GCM as jwk alg + change default rp_id to 'fido2.c…
olastor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
#!/bin/bash | ||
|
||
# Copyright (c) 2023 Sebastian Kussl | ||
# Author: Sebastian Kussl | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
set -eu | ||
|
||
read -r -d . hdr64 | ||
if ! hdr="$(jose fmt --quote="$hdr64" --string --b64load --object --output=-)" ; then | ||
echo 'JWE header corrupt' >&2 | ||
exit 1 | ||
fi | ||
if [ "$(jose fmt --json="$hdr" --get clevis --get pin --unquote=-)" != 'fido2' ] ; then | ||
echo 'JWE pin mismatch!' >&2 | ||
exit 1 | ||
fi | ||
if ! hmac_salt="$(jose fmt --json="$hdr" --get clevis --get fido2 --get hmac_salt --unquote=-)" ; then | ||
echo "JWE missing 'hmac_salt' header parameter!" >&2 | ||
exit 1 | ||
fi | ||
|
||
if ! rp_id="$(jose fmt --json="$hdr" --get clevis --get fido2 --get rp_id --unquote=-)" ; then | ||
echo "JWE missing 'rp_id' header parameter!" >&2 | ||
exit 1 | ||
fi | ||
if ! cred_id="$(jose fmt --json="$hdr" --get clevis --get fido2 --get cred_id --unquote=-)" ; then | ||
echo "JWE missing 'cred_id' header parameter!" >&2 | ||
exit 1 | ||
fi | ||
if ! uv="$(jose fmt --json="$hdr" --get clevis --get fido2 --get uv --unquote=-)" ; then | ||
echo "JWE missing 'uv' header parameter!" >&2 | ||
exit 1 | ||
fi | ||
if ! up="$(jose fmt --json="$hdr" --get clevis --get fido2 --get up --unquote=-)" ; then | ||
echo "JWE missing 'up' header parameter!" >&2 | ||
exit 1 | ||
fi | ||
if ! pin="$(jose fmt --json="$hdr" --get clevis --get fido2 --get pin --unquote=-)" ; then | ||
echo "JWE missing 'pin' header parameter!" >&2 | ||
exit 1 | ||
fi | ||
|
||
fido2_tokens="$(fido2-token -L)" | ||
|
||
if [ -z "${fido2_tokens}" ]; then | ||
echo "Please insert your FIDO2 token." >&2 | ||
exit 1 | ||
fi | ||
|
||
num_tokens="$(echo "${fido2_tokens}" | wc -l)" | ||
if ((num_tokens > 1)); then | ||
echo "Warning: There are multiple tokens. Will use the first one." >&2 | ||
fi | ||
|
||
fido2_token="$(echo "${fido2_tokens}" | head -n1 | cut -d':' -f1)" | ||
|
||
client_hash="$(dd if=/dev/urandom bs=1 count=32 status=none | base64 -w0)" | ||
|
||
hmac="$(printf '%s\n%s\n%s\n%s\n' "${client_hash}" "${rp_id}" "${cred_id}" "${hmac_salt}" | \ | ||
fido2-assert -G -t "uv=${uv}" -t "up=${up}" -t "pin=${pin}" -h "${fido2_token}" | \ | ||
head -n5 | tail -n1 | jose b64 enc -I -)" | ||
|
||
# use the secret in a key wrapping key | ||
jwk='{"alg":"PBES2-HS512+A256KW", "kty":"oct"}' | ||
jwk="$(jose fmt -j "${jwk}" -q "${hmac}" -s k -Uo-)" | ||
|
||
( printf '%s' "$jwk$hdr64." ; cat ) | exec jose jwe dec --key=- --input=- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#!/bin/bash | ||
|
||
# Copyright (c) 2023 Sebastian Kussl | ||
# Author: Sebastian Kussl | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
function create_credential () { | ||
local device | ||
local rp_id | ||
local type | ||
|
||
device="$1" | ||
rp_id="$2" | ||
type="$3" | ||
client_data="$(dd if=/dev/urandom bs=1 count=32 status=none | base64 -w0)" | ||
user_id="$(echo -n 'clevis' | base64 -w0)" | ||
cred_id="$(printf '%s\n%s\n%s\n%s\n' "${client_data}" "${rp_id}" 'clevis' "${user_id}" \ | ||
| fido2-cred -M -h "${device}" "${type}" \ | ||
| head -n5 | tail -n1)" >&2 | ||
|
||
echo -n "${cred_id}" | ||
} | ||
|
||
function generate_hmac () { | ||
local device | ||
local rp_id | ||
local cred_id | ||
local hmac_salt | ||
|
||
device="$1" | ||
rp_id="$2" | ||
cred_id="$3" | ||
hmac_salt="$4" | ||
|
||
client_hash="$(dd if=/dev/urandom bs=1 count=32 status=none | base64 -w0)" | ||
hmac="$(printf '%s\n%s\n%s\n%s\n' "${client_hash}" "${rp_id}" "${cred_id}" "${hmac_salt}" | \ | ||
fido2-assert -G -h -t "uv=${uv}" -t "up=${up}" -t "pin=${pin}" "${device}" | \ | ||
head -n5 | tail -n1 | jose b64 enc -I -)" >&2 | ||
|
||
echo -n "${hmac}" | ||
} | ||
|
||
cfg='' | ||
|
||
if ! cfg="$(jose fmt -j- -Oo- <<< "$1" 2>/dev/null)"; then | ||
echo "Error: Configuration is malformed!" >&2 | ||
exit 1 | ||
fi | ||
|
||
type="$(jose fmt -j- -Og type -Bo- <<< "$cfg")" || type='es256' | ||
uv="$(jose fmt -j- -Og uv -Bo- <<< "$cfg")" || uv='true' | ||
up="$(jose fmt -j- -Og up -Bo- <<< "$cfg")" || up='true' | ||
pin="$(jose fmt -j- -Og pin -Bo- <<< "$cfg")" || pin='false' | ||
rp_id="$(jose fmt -j- -Og rp_id -Su- <<< "$cfg")" || rp_id='clevis' | ||
|
||
if ! fido2_token="$(jose fmt -j- -Og device -u- <<< "$cfg")"; then | ||
fido2_tokens="$(fido2-token -L)" | ||
|
||
if [ -z "${fido2_tokens}" ]; then | ||
echo "Please insert your FIDO2 token." >&2 | ||
exit 1 | ||
fi | ||
|
||
fido2_token="$(echo "${fido2_tokens}" | head -n1 | cut -d':' -f1)" | ||
num_tokens="$(echo "${fido2_tokens}" | wc -l)" | ||
if ((num_tokens > 1)); then | ||
echo "Warning: There are multiple tokens. Will use the first one (${fido2_token})." >&2 | ||
fi | ||
fi | ||
|
||
cred_id="$(jose fmt -j- -Og cred_id -Su- <<< "$cfg")" || cred_id="$(create_credential "${fido2_token}" "${rp_id}" "${type}")" | ||
|
||
# generate a random salt for each encrypted payload | ||
hmac_salt="$(dd if=/dev/urandom bs=1 count=32 status=none | base64 -w0)" | ||
|
||
# retrieve the hmac result which will be the password to use for key wrapping a CEK. | ||
hmac="$(generate_hmac "${fido2_token}" "${rp_id}" "${cred_id}" "${hmac_salt}")" | ||
|
||
if [ -z "${hmac}" ]; then | ||
echo "Error: could not generate key." | ||
exit 1 | ||
fi | ||
|
||
# use the secret in a key wrapping key | ||
jwk='{"kty":"oct", "alg":"PBES2-HS512+A256KW"}' | ||
jwk="$(jose fmt -j "${jwk}" -q "${hmac}" -s k -Uo-)" | ||
|
||
jwe='{"protected":{"enc":"A256GCM","clevis":{"pin":"fido2","fido2":{}}}}' | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g fido2 -q "${type}" -s type -UUUUo-)" | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g fido2 -q "${hmac_salt}" -s hmac_salt -UUUUo-)" | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g fido2 -q "${rp_id}" -s rp_id -UUUUo-)" | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g fido2 -q "${cred_id}" -s cred_id -UUUUo-)" | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g fido2 -q "${uv}" -s uv -UUUUo-)" | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g fido2 -q "${up}" -s up -UUUUo-)" | ||
jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g fido2 -q "${pin}" -s pin -UUUUo-)" | ||
|
||
exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
CLEVIS-ENCRYPT-FIDO2(1) | ||
====================== | ||
:doctype: manpage | ||
|
||
|
||
== NAME | ||
|
||
clevis-encrypt-fido2 - Encrypts using a FIDO2 token by using the hmac-secret extension for generating a symmetric key. | ||
|
||
== SYNOPSIS | ||
|
||
*clevis encrypt fido2* CONFIG [-y] < PT > JWE | ||
|
||
== OVERVIEW | ||
|
||
The *clevis encrypt fido2* command encrypts using a FIDO2 token. | ||
Its only argument is the JSON configuration object. | ||
|
||
FIDO2 is a standard for web authentication using secure tokens, such as a security key. | ||
For symmetrically encrypting data using a FIDO2 token, the token must support the hmac-secret | ||
extension. The encryption then works by generating a random 32 byte public hmac-salt that is | ||
sent to the token/authenticator, where an hmac over the salt is created using a key only known | ||
to the authenticator. This secret value is then used to as a "keyWrap" JWK. | ||
|
||
Clevis provides support for encrypting data using such symmetric keys derived from a FIDO2 | ||
hardware token. The following shows a basic example, using the default configuration options: | ||
|
||
$ clevis encrypt fido2 '{}' < PT > JWE | ||
Enter PIN for /dev/hidraw0: | ||
|
||
By default, a new (non-discoverable) credential will be generated and its credential id, as well | ||
as the randomly generated hmac-salt, is stored as metadata along with the ciphertext. Creating | ||
the credential might require entering the device PIN (as shown above) and verifying user presence | ||
by touching the token. If the "pin" option is set to true, the PIN must be entered again and at | ||
every decryption. For example: | ||
|
||
$ clevis encrypt fido2 '{"pin": true}' < PT > JWE | ||
Enter PIN for /dev/hidraw0: | ||
Enter PIN for /dev/hidraw0: | ||
|
||
The options "up" and "uv" can be used to set the desired behaviour for user presence and user | ||
verification when decrypting the ciphertext (see below). In a "headless" setup, e.g., when | ||
encrypting a LUKS partition, those could be set to "false" in order to automatically decrypt | ||
without any user actions. Note that there are currently no prompts when you need to tap on | ||
the device, but the token might signal that by blinking. | ||
|
||
== CONFIG | ||
|
||
This command uses the following configuration properties: | ||
|
||
* *type* (string) : | ||
The type of the credential, as supported by libfido2, i.e., "es256", "rs256" or "eddsa". | ||
Default: "es256". | ||
|
||
* *cred_id* (string) : | ||
A credential id generated for the specific token. If not specified, a new | ||
(non-discoverable) will be generated using the **fido2-cred** command. Please | ||
note that the credential must have the "hmac-extension" enabled. | ||
|
||
* *rp_id* (string) : | ||
The reyling party id of the credential (that will be created or is provided via | ||
the "cred_id" field). | ||
Default: 'clevis'. | ||
|
||
* *up* (boolean) : | ||
Whether or not to ask the authenticator to require user presence. | ||
Default: true. | ||
|
||
* *uv* (string) : | ||
Whether or not to ask the authenticator to require user verification. | ||
Default: true. | ||
|
||
* *pin* (string) : | ||
Whether or not to ask the authenticator to require the PIN and user verification. | ||
Default: false. | ||
|
||
* *device* (string) : | ||
The device, i.e., the fido2 token, to use (e.g., "/dev/hidraw0"). If not specified, | ||
the first device from the list of connected tokens will be used. When setting this | ||
option, you should be sure that the token's slot remains the same, as the decrypt | ||
command will not be able to find the device, otherwise. | ||
|
||
== SEE ALSO | ||
|
||
link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If anyone is reviewing this, I'd like to know your opinion on my choice of this "alg" here (if any).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't have a strong opinion regarding algorithm selection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: I changed this to AES256GCM. The thinking behind the exotic first choice of algorithm here was that it would be better to additionally do some key derivation from the hmac secret retrieved from the fido2 token, and also because I experienced some wrapping errors when using the raw value returned. Coming back to this, I changed my mind. I don't think an additional key derivation might be needed here as the hmac over the random salt should be sufficient (but I am happy to be proven wrong about it). The wrapping errors I experienced with the raw value were likely due to libfido2 returning values encoded in base64, and I probably didn't get the conversion to base64url right, which should now be fixed. So now the hmac output is used as a simple
AES256GCM
type key.