diff --git a/src/pins/fido2/clevis-decrypt-fido2 b/src/pins/fido2/clevis-decrypt-fido2 new file mode 100755 index 00000000..cca14a9d --- /dev/null +++ b/src/pins/fido2/clevis-decrypt-fido2 @@ -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 . + +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 | base64 -d | jose b64 enc -I -)" + +# use the secret in a key wrapping key +jwk='{"alg":"A256GCM", "kty":"oct"}' +jwk="$(jose fmt -j "${jwk}" -q "${hmac}" -s k -Uo-)" + +( printf '%s' "$jwk$hdr64." ; cat ) | exec jose jwe dec --key=- --input=- diff --git a/src/pins/fido2/clevis-encrypt-fido2 b/src/pins/fido2/clevis-encrypt-fido2 new file mode 100755 index 00000000..d380bb96 --- /dev/null +++ b/src/pins/fido2/clevis-encrypt-fido2 @@ -0,0 +1,108 @@ +#!/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 . + +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 | base64 -d | 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='fido2.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 + +jwk='{"kty":"oct", "alg":"A256GCM"}' +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) diff --git a/src/pins/fido2/clevis-encrypt-fido2.1.adoc b/src/pins/fido2/clevis-encrypt-fido2.1.adoc new file mode 100644 index 00000000..496b4d41 --- /dev/null +++ b/src/pins/fido2/clevis-encrypt-fido2.1.adoc @@ -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: 'fido2.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)]