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)]