diff --git a/.github/workflows/install-dependencies b/.github/workflows/install-dependencies
index 87b52b5f..987230d4 100755
--- a/.github/workflows/install-dependencies
+++ b/.github/workflows/install-dependencies
@@ -23,8 +23,7 @@ debian:*|ubuntu:*)
while ! apt-get -y install ${COMMON} \
build-essential pkg-config libssl-dev libjansson-dev libjose-dev \
luksmeta libluksmeta-dev libpwquality-tools libglib2.0-dev \
- libudisks2-dev libaudit-dev systemd; do
-
+ libudisks2-dev libaudit-dev systemd opensc pcscd libsofthsm2-dev; do
sleep 5
done
;;
@@ -33,8 +32,10 @@ debian:*|ubuntu:*)
printf 'max_parallel_downloads=10\nfastestmirror=1\n' >> /etc/dnf/dnf.conf
dnf -y clean all
dnf -y --setopt=deltarpm=0 update
- dnf -y install dnf-utils jq socat cryptsetup keyutils cracklib-dicts lsof
- command -v dnf5 && dnf5 -y install dnf5-command\(builddep\) || dnf -y install dnf-command\(builddep\)
+ dnf -y install dnf-utils jq socat cryptsetup keyutils cracklib-dicts lsof \
+ opensc pcsc-lite softhsm
+ command -v dnf5 && dnf5 -y install dnf5-command\(builddep\) \
+ || dnf -y install dnf-command\(builddep\)
dnf -y builddep clevis
;;
@@ -48,7 +49,8 @@ debian:*|ubuntu:*)
yum -y --allowerasing install ${COMMON}
yum -y install pkgconfig openssl-devel openssl zlib-devel \
jansson-devel findutils gcc libjose-devel luksmeta libluksmeta-devel \
- audit-libs-devel tpm2-tools desktop-file-utils cracklib-dicts
+ audit-libs-devel tpm2-tools desktop-file-utils cracklib-dicts opensc \
+ pcsc-lite softhsm
sed -i 's|>=1\.0\.2|>=1\.0\.1|' meson.build
;;
esac
diff --git a/src/luks/clevis-luks-common-functions.in b/src/luks/clevis-luks-common-functions.in
index 59ac9af5..29e4631d 100644
--- a/src/luks/clevis-luks-common-functions.in
+++ b/src/luks/clevis-luks-common-functions.in
@@ -177,6 +177,22 @@ clevis_luks_print_pin_config() {
local pin=
case "${P}" in
+ pkcs11)
+ local uri
+ uri="$(jose fmt -j- -g uri -u- <<< "${content}")"
+ mechanism="$(jose fmt -j- -g mechanism -u- <<< "${content}")"
+ if [ -z "${mechanism}" ]; then
+ pin=$(printf '{"uri":"%s"}' "${uri}")
+ else
+ pin=$(printf '{"uri":"%s", "mechanism":"%s"}' "${uri}" "${mechanism}")
+ fi
+ printf "pkcs11 '%s'" "${pin}"
+ ;;
+ sss)
+ local threshold
+ threshold=$(jose fmt -j- -Og t -o- <<< "${content}")
+ clevis_luks_process_sss_pin "${content}" "${threshold}"
+ ;;
tang)
local url
url="$(jose fmt -j- -g url -u- <<< "${content}")"
@@ -197,11 +213,6 @@ clevis_luks_print_pin_config() {
pin=${pin/#,/}
printf "tpm2 '{%s}'" "${pin}"
;;
- sss)
- local threshold
- threshold=$(jose fmt -j- -Og t -o- <<< "${content}")
- clevis_luks_process_sss_pin "${content}" "${threshold}"
- ;;
*)
printf "unknown pin '%s'" "${P}"
;;
@@ -241,6 +252,7 @@ clevis_luks_process_sss_pin() {
local jwe="${1}"
local threshold="${2}"
+ local sss_pkcs11
local sss_tang
local sss_tpm2
local sss
@@ -255,6 +267,9 @@ clevis_luks_process_sss_pin() {
fi
read -r pin cfg <<< "${pin_cfg}"
case "${pin}" in
+ pkcs11)
+ sss_pkcs11="${sss_pkcs11},${cfg}"
+ ;;
tang)
sss_tang="${sss_tang},${cfg}"
;;
@@ -276,6 +291,10 @@ clevis_luks_process_sss_pin() {
cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm2" "${sss_tpm2}")
fi
+ if [ -n "${sss_pkcs11}" ]; then
+ cfg="${cfg},"$(clevis_luks_join_sss_cfg "pkcs11" "${sss_pkcs11}")
+ fi
+
if [ -n "${sss}" ]; then
cfg=$(printf '%s,"sss":%s' "${cfg}" "${sss}")
fi
diff --git a/src/luks/dracut/clevis-pin-pkcs11/clevis-pkcs11-hook.sh b/src/luks/dracut/clevis-pin-pkcs11/clevis-pkcs11-hook.sh
new file mode 100755
index 00000000..9627b375
--- /dev/null
+++ b/src/luks/dracut/clevis-pin-pkcs11/clevis-pkcs11-hook.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# Copyright (c) 2024 Red Hat, Inc.
+#
+# 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 .
+#
+pcscd --disable-polkit
diff --git a/src/luks/dracut/clevis-pin-pkcs11/meson.build b/src/luks/dracut/clevis-pin-pkcs11/meson.build
new file mode 100644
index 00000000..96c6b282
--- /dev/null
+++ b/src/luks/dracut/clevis-pin-pkcs11/meson.build
@@ -0,0 +1,18 @@
+dracut = dependency('dracut', required: false)
+
+if dracut.found()
+ dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + '-pin-pkcs11'
+
+ configure_file(
+ input: 'module-setup.sh.in',
+ output: 'module-setup.sh',
+ install_dir: dracutdir,
+ configuration: data,
+ )
+
+ # TODO: install hook for pcscd start
+ install_data('clevis-pkcs11-hook.sh', install_dir: dracutdir)
+
+else
+ warning('Will not install dracut module clevis-pin-pkcs11 due to missing dependencies!')
+endif
diff --git a/src/luks/dracut/clevis-pin-pkcs11/module-setup.sh.in b/src/luks/dracut/clevis-pin-pkcs11/module-setup.sh.in
new file mode 100755
index 00000000..6dcfeda5
--- /dev/null
+++ b/src/luks/dracut/clevis-pin-pkcs11/module-setup.sh.in
@@ -0,0 +1,41 @@
+#!/bin/bash
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2024 Red Hat, Inc.
+#
+# 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 .
+#
+# shellcheck disable=SC2154
+#
+depends() {
+ echo clevis
+ return 255
+}
+
+install() {
+ inst_hook initqueue/online 60 "${moddir}/clevis-pkcs11-hook.sh"
+ inst_hook initqueue/settled 60 "${moddir}/clevis-pkcs11-hook.sh"
+
+ inst_multiple \
+ pcscd \
+ /usr/lib64/pcsc/drivers/ifd-ccid.bundle/Contents/Linux/libccid.so \
+ /usr/lib64/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist \
+ /usr/lib64/libykcs11.so.2 \
+ /usr/lib64/opensc-pkcs11.so \
+ /usr/lib64/pkcs11/opensc-pkcs11.so \
+ pkcs11-tool \
+ clevis-decrypt-pkcs11
+
+ dracut_need_initqueue
+}
diff --git a/src/luks/dracut/meson.build b/src/luks/dracut/meson.build
index 7ad5b14c..99282309 100644
--- a/src/luks/dracut/meson.build
+++ b/src/luks/dracut/meson.build
@@ -3,3 +3,4 @@ subdir('clevis-pin-tang')
subdir('clevis-pin-tpm2')
subdir('clevis-pin-sss')
subdir('clevis-pin-null')
+subdir('clevis-pin-pkcs11')
diff --git a/src/luks/systemd/clevis-luks-pkcs11-askpass.in b/src/luks/systemd/clevis-luks-pkcs11-askpass.in
new file mode 100755
index 00000000..af299dab
--- /dev/null
+++ b/src/luks/systemd/clevis-luks-pkcs11-askpass.in
@@ -0,0 +1,4 @@
+#!/bin/bash
+/usr/libexec/clevis-luks-pkcs11-askpin &
+# Wait 30 seconds to attend keys. If control socket receives information, this time is cancelled
+clevis-pkcs11-afunix-socket-unlock -f /run/systemd/clevis-pkcs11.sock -s 30
diff --git a/src/luks/systemd/clevis-luks-pkcs11-askpass.service.in b/src/luks/systemd/clevis-luks-pkcs11-askpass.service.in
new file mode 100644
index 00000000..60b5871e
--- /dev/null
+++ b/src/luks/systemd/clevis-luks-pkcs11-askpass.service.in
@@ -0,0 +1,8 @@
+[Unit]
+Description=Unencrypt through PKCS11
+DefaultDependencies=no
+PartOf=clevis-luks-pkcs11-askpass.socket
+
+[Service]
+Type=simple
+ExecStart=/usr/libexec/clevis-luks-pkcs11-askpass
diff --git a/src/luks/systemd/clevis-luks-pkcs11-askpass.socket b/src/luks/systemd/clevis-luks-pkcs11-askpass.socket
new file mode 100644
index 00000000..1fe92229
--- /dev/null
+++ b/src/luks/systemd/clevis-luks-pkcs11-askpass.socket
@@ -0,0 +1,13 @@
+# clevis pkcs11 socket
+[Unit]
+Description=Clevis PKCS11 socket handler
+Requires=clevis-luks-pkcs11-askpass.service
+Wants=cryptsetup-pre.target
+After=sockets.target systemd-ask-password-wall.service
+
+[Socket]
+ListenDatagram=/run/systemd/clevis-pkcs11.sock
+
+[Install]
+RequiredBy=cryptsetup-pre.target
+WantedBy=sockets.target systemd-ask-password-wall.service
diff --git a/src/luks/systemd/clevis-luks-pkcs11-askpin.in b/src/luks/systemd/clevis-luks-pkcs11-askpin.in
new file mode 100755
index 00000000..d9ca5046
--- /dev/null
+++ b/src/luks/systemd/clevis-luks-pkcs11-askpin.in
@@ -0,0 +1,94 @@
+#!/bin/bash
+. clevis-luks-common-functions
+. clevis-pkcs11-common
+
+pkcs11_device=""
+
+get_pkcs11_error() {
+ if journalctl -u clevis-luks-pkcs11-askpass.service -b 0 | tail -3 \
+ | egrep -E "A TPM2 device.{1,}needed" >/dev/null 2>&1;
+ then
+ echo "ERROR:TPM2 device not found. "
+ elif journalctl -u clevis-luks-pkcs11-askpass.service -b 0 | tail -3 \
+ | egrep -E "Error.{1,}server" >/dev/null 2>&1;
+ then
+ echo "ERROR:Tang communication error. "
+ elif journalctl -u clevis-luks-pkcs11-askpass.service -b 0 | tail -3 \
+ | grep "Invalid PIN" >/dev/null 2>&1;
+ then
+ echo "ERROR:Invalid PIN. "
+ else
+ echo "ERROR:Unknown error. "
+ fi
+ return 0
+}
+
+if command -v pcscd; then
+ echo "clevis-pkcs11: starting pcscd if not available ..."
+ PCSCD_PID=$(ps auxf | grep "[p]cscd")
+ echo -e "clevis-pkcs11: pcscd running?:[${PCSCD_PID}]\n"
+ if ! ps auxf | grep "[p]cscd";
+ then
+ echo "clevis-pkcs11: starting pcscd ..."
+ pcscd --disable-polkit
+ fi
+fi
+
+pkcs11-tool -L
+if ! pkcs11_device=$(pkcs11-tool -L | grep "Slot" | head -1 | awk -F ":" '{print $2}' | sed -e 's@^ *@@g'); then
+ echo "No PKCS11 device detected / pkcs11-tool error"
+ exit 1
+fi
+
+while [ -z "${pkcs11_device}" ]; do
+ option=$(systemd-ask-password --echo "Detected an empty PKCS#11 device, continue PKCS#11 detection? [yY/nN]")
+ if [ "${option}" == "N" ] || [ "${option}" == "n" ] ; then
+ echo "Won't continue PKCS11 device detection"
+ exit 1
+ fi
+ pkcs11_device=$(pkcs11-tool -L | grep "Slot" | head -1 | awk -F ":" '{print $2}' | sed -e 's@^ *@@g')
+done
+echo "Detected PKCS11 device:${pkcs11_device}"
+
+# Let's analyze all entries from /etc/crypttab that contain clevis-pkcs11.sock entries
+grep -v "^#" /etc/crypttab | while read -r line;
+do
+ if echo "${line}" | grep -E "clevis-pkcs11.sock" 1>/dev/null;
+ then
+ next_device=0
+ msg=""
+ while [ ${next_device} -ne 1 ]; do
+ uuid=$(echo "${line}" | awk '{print $2}')
+ noUUID=$(echo "${uuid}" | sed -e 's@UUID=@@g')
+ if ! mapped_device=$(clevis_map_device "${uuid}"); then
+ echo "Could not check mapped device for UID:${uuid}"
+ next_device=1
+ continue
+ fi
+ # If no PKCS#11 configuration, advance to next device
+ if ! clevis luks list -d "${mapped_device}" | grep pkcs11 >/dev/null 2>&1; then
+ echo "Device:${mapped_device} does not contain PKCS#11 configuration"
+ next_device=1
+ continue
+ fi
+ # Check if configuration contains a pin
+ uri=$(clevis luks list -d "${mapped_device}" | awk -F '"uri":' '{print $2}' | awk -F '"' '{print $2}' | awk -F '"' '{print $1}')
+ if ! pin=$(clevis_get_pin_value_from_uri "${uri}"); then
+ pin=$(systemd-ask-password "${msg}Please, insert PIN for ${pkcs11_device} (${uuid}):")
+ fi
+ # Get key from PKCS11 pin here and feed AF_UNIX socket program
+ echo "${pin}" > /run/systemd/clevis-pkcs11.pin
+ if ! passphrase=$(clevis_luks_unlock_device "${mapped_device}") || [ -z "${passphrase}" ]; then
+ echo "Could not unlock device:${mapped_device}"
+ msg="$(get_pkcs11_error)"
+ continue
+ fi
+ next_device=1
+ echo "Device:${mapped_device} unlocked successfully"
+ echo "${passphrase}" > /run/systemd/clevis-pkcs11."${noUUID}".passphrase
+ # Send passphrase to control socket
+ luks_device="luks-${noUUID}"
+ echo -n "${luks_device},${passphrase}" | socat UNIX-CONNECT:/run/systemd/clevis-pkcs11.control.sock -
+ done
+ fi
+done
diff --git a/src/luks/systemd/meson.build b/src/luks/systemd/meson.build
index b10494e3..eda879b4 100644
--- a/src/luks/systemd/meson.build
+++ b/src/luks/systemd/meson.build
@@ -19,15 +19,33 @@ if systemd.found() and sd_reply_pass.found()
install_dir: unitdir,
configuration: data,
)
-
+ configure_file(
+ input: 'clevis-luks-pkcs11-askpass.service.in',
+ output: 'clevis-luks-pkcs11-askpass.service',
+ install_dir: unitdir,
+ configuration: data,
+ )
configure_file(
input: 'clevis-luks-askpass.in',
output: 'clevis-luks-askpass',
install_dir: libexecdir,
configuration: data
)
+ configure_file(
+ input: 'clevis-luks-pkcs11-askpass.in',
+ output: 'clevis-luks-pkcs11-askpass',
+ install_dir: libexecdir,
+ configuration: data
+ )
+ configure_file(
+ input: 'clevis-luks-pkcs11-askpin.in',
+ output: 'clevis-luks-pkcs11-askpin',
+ install_dir: libexecdir,
+ configuration: data
+ )
install_data('clevis-luks-askpass.path', install_dir: unitdir)
+ install_data('clevis-luks-pkcs11-askpass.socket', install_dir: unitdir)
else
warning('Will not install systemd support due to missing dependencies!')
endif
diff --git a/src/pins/meson.build b/src/pins/meson.build
index 12670ae8..a115e1eb 100644
--- a/src/pins/meson.build
+++ b/src/pins/meson.build
@@ -1,3 +1,4 @@
subdir('sss')
subdir('tang')
subdir('tpm2')
+subdir('pkcs11')
diff --git a/src/pins/pkcs11/clevis-decrypt-pkcs11 b/src/pins/pkcs11/clevis-decrypt-pkcs11
new file mode 100755
index 00000000..3b02bd2f
--- /dev/null
+++ b/src/pins/pkcs11/clevis-decrypt-pkcs11
@@ -0,0 +1,134 @@
+#!/bin/bash
+set -eo pipefail
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2021 Red Hat, Inc.
+# Author: Sergio Correia
+#
+# 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 .
+#
+PIN_NAME=pkcs11
+PIN_FILE="/run/systemd/clevis-pkcs11.pin"
+DEFAULT_MODULE_PATH=/usr/lib64/pkcs11/opensc-pkcs11.so
+. clevis-pkcs11-common
+
+[ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2
+
+if [ -t 0 ]; then
+ exec >&2
+ echo
+ echo "Usage: clevis decrypt pkcs11 < JWE > PLAINTEXT"
+ echo
+ exit 2
+fi
+
+on_exit() {
+ [ -d "${CLEVIS_PKCS11}" ] || exit 0
+ rm -rf "${CLEVIS_PKCS11}"
+}
+
+unset CLEVIS_PKCS11
+
+read -r -d . hdr64
+
+# Check for corruption in the header.
+if ! hdr="$(jose fmt --quote="${hdr64}" --string --b64load --object \
+ --output=- 2>/dev/null)" ; then
+ echo "JWE header corrupt" >&2
+ exit 1
+fi
+
+# Check if the pin is the expected one.
+if ! pin="$(jose fmt --json="${hdr}" --get clevis --get pin \
+ --unquote=- 2>/dev/null)" || [ -z "${pin}" ]; then
+ echo "Invalid JWE header: unable to identify 'pin'" >&2
+ exit 1
+fi
+if [ "${pin}" != "${PIN_NAME}" ]; then
+ echo "JWE pin mismatch: found: ${pin}; expected: ${PIN_NAME}" >&2
+ exit 1
+fi
+
+if ! uri="$(jose fmt -j- -Og clevis -g "${PIN_NAME}" -g uri -Su- <<< "${hdr}")"; then
+ echo "URI missing required 'clevis.pkcs11.uri' header parameter!" >&2
+ exit 1
+fi
+
+mechanism_option=""
+mechanism="$(jose fmt -j- -Og clevis -g "${PIN_NAME}" -g mechanism -Su- <<< "${hdr}")" \
+ 2>/dev/null || :
+if [ -n "${mechanism}" ]; then
+ mechanism_option="--mechanism ${mechanism}"
+fi
+
+if ! clevis_valid_pkcs11_uri "${uri}"; then
+ echo "PKCS#11 URI with invalid format:[${uri}]" >&2
+ echo "PKCS#11 URI expected format:[${URI_EXPECTED_FORMAT}]" >&2
+ exit 1
+fi
+
+module_path=""
+if ! module_path="$(clevis_get_module_path_from_uri ${uri})"; then
+ module_path="${DEFAULT_MODULE_PATH}"
+fi
+
+# Check if key parameter is present.
+if ! enc_jwk="$(jose fmt --json="${hdr}" --get clevis --get "${PIN_NAME}" \
+ --get key --unquote=- 2>/dev/null)" \
+ || [ -z "${enc_jwk}" ]; then
+ echo "JWE missing 'clevis.${PIN_NAME}.key' header parameter" >&2
+ exit 1
+fi
+
+if ! CLEVIS_PKCS11="$(mktemp -d)" || [ -z "${CLEVIS_PKCS11}" ]; then
+ echo "Creating a temporary dir for PKCS11 files failed" >&2
+ exit 1
+fi
+trap 'on_exit' EXIT
+
+# Error file
+ERR="${CLEVIS_PKCS11}/decerr"
+
+# Decrypt the key.
+ENC="${CLEVIS_PKCS11}/enc"
+if ! printf '%s' "${enc_jwk}" | jose b64 dec -i- > "${ENC}" 2>"${ERR}"; then
+ cat "${ERR}" >&2
+ echo "Unable to base64-decode the JWK" >&2
+ exit 1
+fi
+
+PIN_value=""
+if ! PIN_value="$(clevis_get_pin_value_from_uri ${uri})"; then
+ PIN_value=$(cat "${PIN_FILE}" 2>/dev/null || :)
+fi
+
+if ! jwk="$(pkcs11-tool --login --decrypt --input-file ${ENC} \
+ -p ${PIN_value} --module ${module_path} ${mechanism_option} 2>${ERR})" \
+ || [ -z "${jwk}" ]; then
+ cat "${ERR}" >&2
+ echo "Unable to decrypt the JWK" >&2
+ # Check if it is an issue with PIN (to dump error appropriately)
+ if ! pkcs11-tool -pkcs11-tool --login --test -p "${PIN_value}"-login --test \
+ -p "${PIN_value}" --module "${module_path}" \
+ 2>"${ERR}" >/dev/null; then
+ cat "${ERR}" >&2
+ echo "Invalid PIN" >&2
+ fi
+ exit 1
+fi
+
+rm -rf "${PIN_FILE}" 2>/dev/null || :
+
+# Decrypt the data using the decrypted JWK.
+( printf '%s' "${jwk}${hdr64}." ; cat ) | exec jose jwe dec --key=- --input=-
diff --git a/src/pins/pkcs11/clevis-encrypt-pkcs11 b/src/pins/pkcs11/clevis-encrypt-pkcs11
new file mode 100755
index 00000000..61c6f879
--- /dev/null
+++ b/src/pins/pkcs11/clevis-encrypt-pkcs11
@@ -0,0 +1,129 @@
+#!/bin/bash
+set -eo pipefail
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2021 Red Hat, Inc.
+# Author: Sergio Correia
+#
+# 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 .
+
+PIN_NAME=pkcs11
+
+SUMMARY="Encrypts using a PKCS#11 token"
+
+if [ "$1" == "--summary" ]; then
+ echo "$SUMMARY"
+ exit 0
+fi
+
+if [ -t 0 ]; then
+ exec >&2
+ echo
+ echo "Usage: clevis encrypt pkcs11 CONFIG < PLAINTEXT > JWE"
+ echo
+ echo "$SUMMARY"
+ echo
+ echo "This command uses the following configuration properties:"
+ echo
+ echo " uri: The PKCS#11 URI (REQUIRED)"
+ echo
+ exit 2
+fi
+
+. clevis-pkcs11-common
+
+on_exit() {
+ [ -d "${CLEVIS_PKCS11}" ] || exit 0
+ rm -rf "${CLEVIS_PKCS11}"
+}
+unset CLEVIS_PKCS11
+
+# We first check whether the configuration is a valid JSON.
+if ! cfg="$(jose fmt --json="$1" --object --output=-)" ; then
+ echo 'Configuration is malformed' >&2
+ exit 1
+fi
+
+if ! uri="$(jose fmt -j- -Og uri -u- <<< "$cfg")"; then
+ echo "Missing the required PKCS#11 'uri' property!" >&2
+ exit 1
+fi
+
+mechanism="$(jose fmt -j- -Og mechanism -u- <<< "$cfg" 2>/dev/null || :)"
+
+if ! clevis_valid_pkcs11_uri "${uri}"; then
+ echo "PKCS#11 URI with invalid format:[${uri}]" >&2
+ echo "PKCS#11 URI expected format:[${URI_EXPECTED_FORMAT}]" >&2
+ exit 1
+fi
+
+if ! module_path=$(clevis_get_module_path_from_uri "${uri}"); then
+ module_opt=""
+else
+ module_opt=" --module ${module_path}"
+fi
+
+if ! CLEVIS_PKCS11="$(mktemp -d)" || [ -z "${CLEVIS_PKCS11}" ]; then
+ echo "Creating a temporary dir for PKCS11 files failed" >&2
+ exit 1
+fi
+trap 'on_exit' EXIT
+
+# Error file
+ERR="${CLEVIS_PKCS11}/encerr"
+
+# Let's generate a key.
+if ! jwk="$(jose jwk gen --input='{"alg":"A256GCM"}')" \
+ || [ -z "${jwk}" ]; then
+ echo "Unable to generate JWK" >&2
+ exit 1
+fi
+
+# Now let's encrypt it with the device public key.
+PKEY="${CLEVIS_PKCS11}/pubkey"
+if ! id=$(pkcs11-tool -O 2>${ERR} ${module_opt} \
+ | grep -i 'Public' -A10 2>${ERR} | grep 'ID:' \
+ | head -1 | awk -F 'ID:' '{print $2}' | tr -d ' '); then
+ cat "${ERR}" >&2
+ echo "Unable to obtain public key ID from PKCS#11 device" >&2
+ exit 1
+fi
+
+if ! pkcs11-tool ${module_opt} --read-object --type pubkey --id "${id}" \
+ 2> "${ERR}" > "${PKEY}"; then
+ cat "${ERR}" >&2
+ echo "Unable to obtain a public key from PKCS#11 device" >&2
+ exit 1
+fi
+
+if ! jwk_enc="$(printf '%s' "${jwk}" | openssl rsautl -encrypt -pubin \
+ -inkey "${PKEY}" 2>${ERR} \
+ | jose b64 enc -I-)"; then
+ cat "${ERR}" >&2
+ echo "Unable to encrypt JWK with PKCS#11 public key" >&2
+ exit 1
+fi
+
+# And the JWE.
+template=$(printf '{"protected":{"clevis":{"pin":"%s","%s":{"uri":"%s", "mechanism":"%s"}}}}' \
+ "${PIN_NAME}" "${PIN_NAME}" "${uri}" "${mechanism}")
+
+# Save key.
+jwe="$(jose fmt --json="${template}" --get protected --get clevis \
+ --get "${PIN_NAME}" --quote "${jwk_enc}" --set key -UUUU --output=-)"
+
+# Now we encrypt the data using our key.
+( printf '%s' "${jwe}${jwk}" ; cat ) | exec jose jwe enc \
+ --input=- --key=- \
+ --detached=- --compact
diff --git a/src/pins/pkcs11/clevis-pkcs11-afunix-socket-unlock.c b/src/pins/pkcs11/clevis-pkcs11-afunix-socket-unlock.c
new file mode 100644
index 00000000..7fc29f9b
--- /dev/null
+++ b/src/pins/pkcs11/clevis-pkcs11-afunix-socket-unlock.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2024 Red Hat, Inc.
+ * Author: Red Hat Inc.
+ *
+ * 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 .
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef GIT_VERSION
+const char* VERSION = GIT_VERSION;
+#else
+const char* VERSION = "v0.0.0";
+#endif
+
+#define MAX_DEVICE 1024
+#define MAX_ENTRIES 1024
+#define MAX_KEY 1024
+
+const uint16_t MAX_ITERATIONS = 3;
+const uint16_t MAX_PATH = 1024;
+const uint16_t MAX_CONTROL_MSG = 1024;
+
+// Time to wait before trying to write key
+const uint16_t START_DELAY = 0;
+
+typedef struct {
+ char dev[MAX_DEVICE];
+ char key[MAX_KEY];
+} key_entry_t;
+key_entry_t keys[MAX_ENTRIES];
+uint16_t entry_counter = 0;
+uint8_t thread_loop = 1;
+uint8_t control_thread_info = 0;
+pthread_mutex_t mutex;
+
+static void
+get_control_socket_name(const char* file_sock, char* control_sock, uint32_t control_sock_len) {
+ char *p = strstr(file_sock, ".sock");
+ size_t prefix_length = strlen(file_sock) - strlen(p);
+ memset(control_sock, 0, control_sock_len);
+ memcpy(control_sock, file_sock, prefix_length);
+ if (prefix_length + strlen(".control.sock") < control_sock_len) {
+ strcat(control_sock + prefix_length, ".control.sock");
+ }
+}
+
+static void insert_device(const char* dev) {
+ if(entry_counter == MAX_ENTRIES) {
+ perror("No more entries accepted\n");
+ }
+ pthread_mutex_lock(&mutex);
+ strncpy(keys[entry_counter].dev, dev, MAX_DEVICE);
+ pthread_mutex_unlock(&mutex);
+}
+
+static void insert_key(const char* key) {
+ if(entry_counter == MAX_ENTRIES) {
+ perror("No more entries accepted\n");
+ }
+ pthread_mutex_lock(&mutex);
+ strncpy(keys[entry_counter++].key, key, MAX_KEY);
+ pthread_mutex_unlock(&mutex);
+}
+
+
+static const char* get_key(const char* dev) {
+ for(int e = 0; e < entry_counter; e++) {
+ pthread_mutex_lock(&mutex);
+ if(0 == strcmp(keys[e].dev, dev)) {
+ pthread_mutex_unlock(&mutex);
+ return keys[e].key;
+ }
+ pthread_mutex_unlock(&mutex);
+ }
+ return NULL;
+}
+
+static void* control_thread(void *targ) {
+ // Create a socket to listen on control socket
+ struct sockaddr_un control_addr, accept_addr;
+ int s, a, ret;
+ char control_msg[MAX_CONTROL_MSG];
+ const char* control_sock = (const char*)targ;
+ socklen_t len;
+ memset(&control_addr, 0, sizeof(control_addr));
+ control_addr.sun_family = AF_UNIX;
+ strcpy(control_addr.sun_path, control_sock);
+ unlink(control_sock);
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s == -1) {
+ perror("control socket");
+ pthread_exit("control socket");
+ }
+ ret = bind(s, (struct sockaddr *)&control_addr, sizeof(control_addr));
+ if (ret == -1) {
+ perror("control bind");
+ pthread_exit("control bind");
+ }
+ ret = listen(s, SOMAXCONN);
+ if (ret == -1) {
+ perror("control listen");
+ pthread_exit("control listen");
+ }
+ while (thread_loop) {
+ a = accept(s, (struct sockaddr *)&accept_addr, &len);
+ if (a == -1) {
+ perror("control accept");
+ pthread_exit("control accept");
+ }
+ memset(control_msg, 0, MAX_CONTROL_MSG);
+ recv(a, control_msg, MAX_CONTROL_MSG, 0);
+ char* t = control_msg;
+ int is_device = 1;
+ while((t = strtok(t, ","))) {
+ if (is_device) {
+ printf("Adding device:%s\n", t);
+ insert_device(t);
+ is_device = 0;
+ } else {
+ printf("Adding key:%s\n", t);
+ insert_key(t);
+ // As long as some key is inserted, we store it in the control_thread_info variable
+ control_thread_info = 1;
+ }
+ t = strtok(NULL, ",");
+ }
+ }
+ return NULL;
+}
+
+static int usage(const char* name, uint32_t ecode) {
+ printf("\nUsage:\n\t%s -f socket_file [-c control_socket] [-k key] "
+ "[-t iterations, 3 by default] "
+ "[-s start delay, 0s by default]\n\n", name);
+ exit(ecode);
+}
+
+int main(int argc, char* argv[]) {
+ int s, a, opt, ret;
+ // The device entries
+ for (uint16_t e = 0; e < MAX_ENTRIES; e++) {
+ memset(&keys[e], 0, sizeof(key_entry_t));
+ }
+ uint32_t iterations = MAX_ITERATIONS, startdelay = START_DELAY;
+ uint32_t ic = 0;
+ uint32_t time = 0;
+ char sock_file[MAX_PATH];
+ char sock_control_file[MAX_PATH];
+ char key[MAX_KEY];
+ memset(sock_file, 0, MAX_PATH);
+ memset(sock_control_file, 0, MAX_PATH);
+ memset(key, 0, MAX_KEY);
+ struct sockaddr_un sock_addr, accept_addr, peer_addr;
+ socklen_t len;
+ socklen_t pathlen;
+
+ while ((opt = getopt(argc, argv, "c:f:k:i:s:t:h")) != -1) {
+ int ret_code = EXIT_FAILURE;
+ switch (opt) {
+ case 'c':
+ strncpy(sock_control_file, optarg, MAX_PATH - 1);
+ break;
+ case 'f':
+ strncpy(sock_file, optarg, MAX_PATH - 1);
+ break;
+ case 'k':
+ strncpy(key, optarg, MAX_KEY - 1);
+ break;
+ case 't':
+ iterations = strtoul(optarg, 0, 10);
+ break;
+ case 's':
+ startdelay = strtoul(optarg, 0, 10);
+ break;
+ case 'h':
+ ret_code = EXIT_SUCCESS;
+ __attribute__ ((fallthrough));
+ default:
+ usage(argv[0], ret_code);
+ }
+ }
+ if(0 == strlen(sock_file)) {
+ fprintf(stderr, "Socket file name must be provided\n");
+ usage(argv[0], EXIT_FAILURE);
+ }
+ printf("VERSION: [%s]\n", VERSION);
+ printf("KEY: [%s]\n", key);
+ printf("TRY ITERATIONS: [%u]\n", iterations);
+ printf("START DELAY: [%u] seconds\n", startdelay);
+ printf("FILE: [%s]\n", sock_file);
+ if(0 == strlen(sock_control_file) ) {
+ get_control_socket_name(sock_file, sock_control_file, MAX_PATH);
+ }
+ printf("CONTROL FILE: [%s]\n", sock_control_file);
+
+ pthread_t thid;
+ void* tret;
+ // Create control socket thread
+ if (pthread_create(&thid, NULL, control_thread, sock_control_file) != 0) {
+ perror("pthread_create() error");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&sock_addr, 0, sizeof(sock_addr));
+ sock_addr.sun_family = AF_UNIX;
+ strcpy(sock_addr.sun_path, sock_file);
+ unlink(sock_file);
+
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s == -1) {
+ perror("socket");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = bind(s, (struct sockaddr *)&sock_addr, sizeof(sock_addr));
+ if (ret == -1) {
+ perror("bind");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = listen(s, SOMAXCONN);
+ if (ret == -1) {
+ perror("listen");
+ exit(EXIT_FAILURE);
+ }
+
+ len = sizeof(accept_addr);
+
+ while (ic < iterations) {
+ if (time++ < startdelay && !control_thread_info) {
+ sleep(1);
+ printf("Start time elapsed: [%u/%u] seconds\n", time, startdelay);
+ continue;
+ }
+ a = accept(s, (struct sockaddr *)&accept_addr, &len);
+ if (a == -1) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+ pathlen = len - offsetof(struct sockaddr_un, sun_path);
+ len = sizeof(peer_addr);
+ ret = getpeername(a, (struct sockaddr *)&peer_addr, &len);
+ if (ret == -1) {
+ perror("getpeername");
+ exit(EXIT_FAILURE);
+ }
+
+ pathlen = len - offsetof(struct sockaddr_un, sun_path);
+ char peer[pathlen];
+ memset(peer, 0, pathlen);
+ strncpy(peer, peer_addr.sun_path+1, pathlen-1);
+ printf("Try: [%u/%u]\n", ic, iterations);
+ printf("getpeername sun_path(peer): [%s]\n", peer);
+ char* t = peer;
+ const char* unlocking_device = "";
+ while((t = strtok(t, "/"))) {
+ if(t) {
+ unlocking_device = t;
+ }
+ t = strtok(NULL, ",");
+ }
+ printf("Trying to unlock device:[%s]\n", unlocking_device);
+ // Now we have all the information in peer, something like:
+ // \099226072855ae2d8/cryptsetup/luks-6e38d5e1-7f83-43cc-819a-7416bcbf9f84
+ // NUL random /cryptsetup/ DEVICE
+ // If we need to unencrypt device, pick it from peer information
+ // To return the key, just respond to socket returned by accept
+ if(strlen(key)) {
+ send(a, key, strlen(key), 0);
+ } else {
+ const char* entry_key;
+ if((entry_key = get_key(unlocking_device))) {
+ send(a, entry_key, strlen(entry_key), 0);
+ } else {
+ printf("Device not found: [%s]\n", unlocking_device);
+ }
+ }
+ close(a);
+ ic++;
+ }
+ printf("Closing (max tries reached)\n");
+ pthread_kill(thid, SIGKILL);
+ thread_loop = 0;
+ if (pthread_join(thid, &tret) != 0) {
+ perror("pthread_join error");
+ exit(EXIT_FAILURE);
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/src/pins/pkcs11/clevis-pkcs11-common b/src/pins/pkcs11/clevis-pkcs11-common
new file mode 100755
index 00000000..b46a4881
--- /dev/null
+++ b/src/pins/pkcs11/clevis-pkcs11-common
@@ -0,0 +1,39 @@
+#!/bin/bash
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2024 Red Hat, Inc.
+# Author: Sergio Arroutbi
+#
+# 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 .
+#
+if [ "$1" == "--summary" ]; then
+ exit 1
+fi
+
+URI_EXPECTED_FORMAT="pkcs11:"
+
+clevis_valid_pkcs11_uri() {
+ echo "$1" | grep -E "^${URI_EXPECTED_FORMAT}" >/dev/null 2>&1 || return 1
+}
+
+clevis_get_module_path_from_uri() {
+ echo "$1" | grep -E "module-path=" >/dev/null 2>&1 || return 1
+ echo "$1" | awk -F 'module-path=' '{print $2}' | awk -F ";" '{print $1}' \
+ | awk -F "?" '{print $1}'
+}
+
+clevis_get_pin_value_from_uri() {
+ echo "$1" | grep -E "pin-value=" >/dev/null 2>&1 || return 1
+ echo "$1" | awk -F 'pin-value=' '{print $2}' | awk -F ";" '{print $1}'
+}
diff --git a/src/pins/pkcs11/meson.build b/src/pins/pkcs11/meson.build
new file mode 100644
index 00000000..05415d19
--- /dev/null
+++ b/src/pins/pkcs11/meson.build
@@ -0,0 +1,43 @@
+pcscd = find_program('pcscd', required: false)
+pkcs11tool = find_program('pkcs11-tool', required: false)
+pcscd_disable_polkit = false
+git = find_program('git', required: false)
+
+if git.found()
+ git_version_cmd = run_command('git', 'rev-parse', '--short', 'HEAD', check: false)
+ git_version = 'release-v' + meson.project_version() + '-' + git_version_cmd.stdout().strip()
+else
+ git_version = 'release-v' + meson.project_version() + '-' + 'UNKNOWN_GIT_VERSION'
+endif
+GIT_VERSION_FLAG = '-DGIT_VERSION="' + git_version + '"'
+
+if pcscd.found()
+ pcscd_options = run_command('pcscd', '--help', check: false)
+ pcscd_disable_polkit = pcscd_options.stdout().strip().contains('disable-polkit')
+ if not pcscd_disable_polkit
+ warning('pcscd does not have --disable-polkit option')
+ endif
+endif
+
+if pcscd.found() and pkcs11tool.found()
+ bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-pkcs11')
+ bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-pkcs11')
+ bins += join_paths(meson.current_source_dir(), 'clevis-pkcs11-common')
+ ### TODO: Include man pages
+ # mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-pkcs11.1')
+ # mans += join_paths(meson.current_source_dir(), 'clevis-decrypt-pkcs11.1')
+ subdir('tests')
+ executable('clevis-pkcs11-afunix-socket-unlock', ['clevis-pkcs11-afunix-socket-unlock.c'],
+ install_dir: bindir,
+ install: true,
+ c_args: GIT_VERSION_FLAG
+ )
+else
+ warning('Will not install pkcs11 pin due to missing dependencies!')
+ if not pcscd.found()
+ warning('pcscd not found')
+ endif
+ if not pkcs11tool.found()
+ warning('pkcs11-tool not found')
+ endif
+endif
diff --git a/src/pins/pkcs11/tests/meson.build b/src/pins/pkcs11/tests/meson.build
new file mode 100644
index 00000000..9212b60f
--- /dev/null
+++ b/src/pins/pkcs11/tests/meson.build
@@ -0,0 +1,23 @@
+env = environment()
+env.prepend('PATH',
+ join_paths(meson.source_root(), 'src'),
+ join_paths(meson.source_root(), 'src', 'pins', 'pkcs11'),
+ join_paths(meson.source_root(), 'src', 'pins', 'pkcs11', 'tests'),
+ join_paths(meson.build_root(), 'src'),
+ join_paths(meson.build_root(), 'src', 'pins'),
+ join_paths(meson.build_root(), 'src', 'pins', 'pkcs11'),
+ join_paths(meson.build_root(), 'src', 'pins', 'pkcs11', 'tests'),
+ join_paths(meson.source_root(), 'src'),
+ join_paths(meson.source_root(), 'src', 'luks'),
+ join_paths(meson.source_root(), 'src', 'luks', 'tests'),
+ join_paths(meson.build_root(), 'src'),
+ join_paths(meson.build_root(), 'src', 'luks'),
+ join_paths(meson.build_root(), 'src', 'luks', 'tests'),
+ join_paths(meson.source_root(), 'src', 'pins', 'tang'),
+ join_paths(meson.source_root(), 'src', 'pins', 'tang', 'tests'),
+ join_paths(meson.build_root(), 'src', 'pins', 'tang'),
+ join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'),
+ separator: ':'
+)
+
+test('pin-pkcs11', find_program('pin-pkcs11'), env: env)
diff --git a/src/pins/pkcs11/tests/pin-pkcs11 b/src/pins/pkcs11/tests/pin-pkcs11
new file mode 100755
index 00000000..47486812
--- /dev/null
+++ b/src/pins/pkcs11/tests/pin-pkcs11
@@ -0,0 +1,151 @@
+#!/bin/bash -xe
+# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
+#
+# Copyright (c) 2024 Red Hat, Inc.
+# Author: Sergio Arroutbi
+#
+# 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 .
+#
+# shellcheck disable=SC1091
+. pkcs11-common-tests
+. tests-common-functions
+. clevis-luks-common-functions
+
+on_exit() {
+ exit_status=$?
+ [ -d "$TMP" ] && rm -rf "$TMP"
+ exit "${exit_status}"
+}
+
+if [[ ! -f "${P11LIB}" ]]; then
+ echo "WARNING: The SoftHSM is not installed. Can not run this test"
+ exit 77;
+fi
+
+trap 'on_exit' EXIT
+
+TMP="$(mktemp -d)"
+
+softhsm_lib_setup
+test "$?" == 0
+
+SECRET_WORD="secret"
+SUPPORTED_MECHANISM="RSA-PKCS"
+CLEVIS_PIN="pkcs11"
+
+sword=$(echo "${SECRET_WORD}" | clevis encrypt pkcs11 \
+"{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=${PIN}\"\
+,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" | clevis decrypt)
+test "${sword}" == "${SECRET_WORD}"
+
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \
+"{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=${PIN}\" \
+,\"mechanism\":\"INVALID_MECHANISM\"}" 2>/dev/null
+
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \
+"{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=${PIN}\"\
+,\"mechanism\":\"INVALID_MECHANISM\"}" 2>/dev/null
+
+sword=$(echo "${SECRET_WORD}" | clevis encrypt pkcs11 \
+"{\"uri\":\"pkcs11:module-path=${P11LIB}?pin-value=INVALID_PIN\"\
+,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" 2>/dev/null | clevis decrypt 2>/dev/null || :)
+test "${sword}" != "${SECRET_WORD}" 2>/dev/null
+
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \
+"{\"uri\":\"pkcs11:module-path=/usr/lib/wrong_modulepath.so?pin-value=${PIN}\"\
+,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" 2>/dev/null
+
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 \
+"{\"uri\":\"pkcs11:?pin-value=${PIN}\"\
+,\"mechanism\":\"${SUPPORTED_MECHANISM}\"}" 2>/dev/null
+
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":\"pkcs12:\"}" \
+2>/dev/null
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":\":\"}" 2>/dev/null
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":\"\"}" 2>/dev/null
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{\"uri\":}" 2>/dev/null
+! echo "${SECRET_WORD}" | clevis encrypt pkcs11 "{}" 2>/dev/null
+
+# Let's try some bindings
+# LUKS2.
+DEV="${TMP}/luks2-device"
+new_device "luks2" "${DEV}"
+
+CFG=$(printf '{"uri": "pkcs11:module-path=%s?pin-value=%s", "mechanism":"%s"}' \
+"${P11LIB}" "${PIN}" "${SUPPORTED_MECHANISM}")
+if ! clevis luks bind -f -d "${DEV}" "${CLEVIS_PIN}" "${CFG}" <<< \
+"${DEFAULT_PASS}"; then
+ error "${TEST}: Binding is expected to succeed when given a correct \
+(${DEFAULT_PASS}) password."
+fi
+
+SLT=1
+if ! read -r slot pin cfg < <(clevis luks list -d "${DEV}" -s "${SLT}"); then
+ error "${TEST}: clevis luks list is expected to succeed for device(${DEV}) \
+and slot (${SLT})"
+fi
+
+if [[ "${slot}" != "${SLT}:" ]]; then
+ error "${TEST}: slot (${slot}) is expected to be ${SLT}"
+fi
+
+if [[ "${pin}" != "${CLEVIS_PIN}" ]]; then
+ error "${TEST}: pin (${pin}) is expected to be '${CLEVIS_PIN}'"
+fi
+
+# Check configuration has "uri:"
+if ! [[ "${cfg}" == *"uri"* ]]; then
+ error "${TEST}: configuration (${cfg}) is expected to be contain uri"
+fi
+
+# Test the passphrase
+SLT=1
+PASS=$(clevis luks pass -d "${DEV}" -s "${SLT}")
+if ! clevis_luks_check_valid_key_or_keyfile "${DEV}" "${PASS}" "" "${SLT}"; then
+ error "Passphrase obtained from clevis luks pass failed."
+fi
+
+if ! clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then
+ error "${TEST}: Unbind is expected to succeed for device ${DEV} and slot ${SLT}"
+fi
+
+SLT=0
+if clevis luks unbind -f -d "${DEV}" -s "${SLT}"; then
+ error "${TEST}: Unbind is expected to fail for device ${DEV}:${SLT} \
+that is not bound with clevis"
+fi
+
+WRONGCFG=$(printf '{"uri": "pkcs12:"}')
+if clevis luks bind -f -d "${DEV}" "${CLEVIS_PIN}" "${WRONGCFG}" <<< "${DEFAULT_PASS}"; \
+then
+ error "${TEST}: Binding is expected to fail when given an incorrect configuration:\
+(${WRONGCFG})"
+fi
+
+WRONGCFG=$(printf '{}')
+if clevis luks bind -f -d "${DEV}" "${CLEVIS_PIN}" "${WRONGCFG}" <<< "${DEFAULT_PASS}"; \
+then
+ error "${TEST}: Binding is expected to fail when given an empty configuration:\
+(${WRONGCFG})"
+fi
+
+WRONGCFG=$(printf '{"uri":""}')
+if clevis luks bind -f -d "${DEV}" "${CLEVIS_PIN}" "${WRONGCFG}" <<< "${DEFAULT_PASS}"; \
+then
+ error "${TEST}: Binding is expected to fail when given an empty uri:\
+(${WRONGCFG})"
+fi
+
+softhsm_lib_cleanup
+test "$?" == 0
diff --git a/src/pins/pkcs11/tests/pkcs11-common-tests b/src/pins/pkcs11/tests/pkcs11-common-tests
new file mode 100644
index 00000000..d197876f
--- /dev/null
+++ b/src/pins/pkcs11/tests/pkcs11-common-tests
@@ -0,0 +1,105 @@
+#!/bin/bash
+#
+# Copyright (c) 2024 Red Hat, Inc.
+#
+# 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
+#
+# This file is based on OpenSC common test infrastructure:
+# https://github.com/OpenSC/OpenSC/blob/master/tests/common.sh
+#
+SOPIN="12345678"
+PIN="123456"
+P11LIB=""
+SOFTHSM_PATHS="/usr/local/lib/softhsm/libsofthsm2.so \
+/usr/lib/softhsm/libsofthsm2.so \
+/usr/lib64/pkcs11/libsofthsm2.so"
+
+for hsmlib in ${SOFTHSM_PATHS}; do
+ if [[ -f "${hsmlib}" ]]; then
+ P11LIB="${hsmlib}"
+ break
+ fi
+done
+
+# Check required libraries
+if [[ -z "${P11LIB}" ]]; then
+ echo "Warning: Could not find the softhsm pkcs11 module library"
+ echo "Warning: Searched files:${SOFTHSM_PATHS}"
+fi
+
+# Check required commands
+if ! type pkcs11-tool; then
+ echo "Warning: Could not find pkcs11-tool"
+fi
+if ! type openssl; then
+ echo "Warning: Could not find openssl command"
+fi
+
+function generate_public_key() {
+ TYPE="$1"
+ ID="$2"
+ LABEL="$3"
+
+ # Generate key pair
+ if ! pkcs11-tool --keypairgen --key-type="${TYPE}" --login --pin="${PIN}" \
+ --module="${P11LIB}" --label="${LABEL}" --id="${ID}"; then
+ echo "Couldn't generate ${TYPE} key pair"
+ return 1
+ fi
+
+ # Extract public key
+ if ! pkcs11-tool --read-object --id "${ID}" --type pubkey --output-file \
+ "${ID}".der --module="${P11LIB}";
+ then
+ echo "Couldn't read generated ${TYPE} public key"
+ return 1
+ fi
+
+ if [[ ${TYPE:0:3} == "RSA" ]]; then
+ openssl rsa -inform DER -outform PEM -in "${ID}".der -pubin > "${ID}".pub
+ else
+ echo "Unsupported key type:${TYPE}"
+ return 1
+ fi
+ rm "${ID}".der
+}
+
+function softhsm_initialize() {
+ echo "directories.tokendir = $(realpath .tokens)" > .softhsm2.conf
+ if [ -d ".tokens" ]; then
+ rm -rf ".tokens"
+ fi
+ mkdir ".tokens"
+ SOFTHSM2_CONF=$(realpath ".softhsm2.conf")
+ export SOFTHSM2_CONF
+ # Init token
+ softhsm2-util --init-token --slot 0 --label "Clevis PKCS11 test" \
+ --so-pin="${SOPIN}" --pin="${PIN}"
+}
+
+function softhsm_lib_setup() {
+ softhsm_initialize
+ # Generate 2048b RSA Key pair (this will generate 00.pub file)
+ generate_public_key "RSA:2048" "00" "RSA2048" || return 1
+}
+
+function softhsm_cleanup() {
+ rm -rf .softhsm2.conf
+ rm -rf ".tokens"
+}
+
+function softhsm_lib_cleanup() {
+ softhsm_cleanup
+ rm 00.pub
+}