From 8620f436a7db0139d8211c67552fc6bbea3cf7e0 Mon Sep 17 00:00:00 2001 From: Sergio Arroutbi Date: Tue, 28 May 2024 12:56:36 +0200 Subject: [PATCH] Add PKCS#11 pin basic functionality Signed-off-by: Sergio Arroutbi --- .github/workflows/install-dependencies | 5 +- src/luks/clevis-luks-common-functions.in | 29 +- .../clevis-pin-pkcs11/clevis-pkcs11-hook.sh | 18 ++ src/luks/dracut/clevis-pin-pkcs11/meson.build | 18 ++ .../clevis-pin-pkcs11/module-setup.sh.in | 40 +++ src/luks/dracut/meson.build | 1 + .../systemd/clevis-luks-pkcs11-askpass.in | 4 + .../clevis-luks-pkcs11-askpass.service.in | 8 + .../systemd/clevis-luks-pkcs11-askpass.socket | 13 + src/luks/systemd/clevis-luks-pkcs11-askpin.in | 93 ++++++ src/luks/systemd/meson.build | 20 +- src/pins/meson.build | 1 + src/pins/pkcs11/clevis-decrypt-pkcs11 | 135 ++++++++ src/pins/pkcs11/clevis-encrypt-pkcs11 | 129 ++++++++ .../clevis-pkcs11-afunix-socket-unlock.c | 304 ++++++++++++++++++ src/pins/pkcs11/clevis-pkcs11-common | 39 +++ src/pins/pkcs11/meson.build | 33 ++ src/pins/pkcs11/tests/meson.build | 23 ++ src/pins/pkcs11/tests/pin-pkcs11 | 147 +++++++++ src/pins/pkcs11/tests/pkcs11-common-tests | 105 ++++++ 20 files changed, 1157 insertions(+), 8 deletions(-) create mode 100755 src/luks/dracut/clevis-pin-pkcs11/clevis-pkcs11-hook.sh create mode 100644 src/luks/dracut/clevis-pin-pkcs11/meson.build create mode 100755 src/luks/dracut/clevis-pin-pkcs11/module-setup.sh.in create mode 100755 src/luks/systemd/clevis-luks-pkcs11-askpass.in create mode 100644 src/luks/systemd/clevis-luks-pkcs11-askpass.service.in create mode 100644 src/luks/systemd/clevis-luks-pkcs11-askpass.socket create mode 100755 src/luks/systemd/clevis-luks-pkcs11-askpin.in create mode 100755 src/pins/pkcs11/clevis-decrypt-pkcs11 create mode 100755 src/pins/pkcs11/clevis-encrypt-pkcs11 create mode 100644 src/pins/pkcs11/clevis-pkcs11-afunix-socket-unlock.c create mode 100755 src/pins/pkcs11/clevis-pkcs11-common create mode 100644 src/pins/pkcs11/meson.build create mode 100644 src/pins/pkcs11/tests/meson.build create mode 100755 src/pins/pkcs11/tests/pin-pkcs11 create mode 100644 src/pins/pkcs11/tests/pkcs11-common-tests diff --git a/.github/workflows/install-dependencies b/.github/workflows/install-dependencies index 87b52b5f..dffa0481 100755 --- a/.github/workflows/install-dependencies +++ b/.github/workflows/install-dependencies @@ -23,7 +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; do sleep 5 done @@ -48,7 +48,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 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..962ade3c --- /dev/null +++ b/src/luks/dracut/clevis-pin-pkcs11/module-setup.sh.in @@ -0,0 +1,40 @@ +#!/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 . +# + +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..c4fac0cc --- /dev/null +++ b/src/luks/systemd/clevis-luks-pkcs11-askpin.in @@ -0,0 +1,93 @@ +#!/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 ..." + echo -e "clevis-pkcs11: pcscd running?:[$(ps auxf | grep [p]cscd)]\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..57fc6a3a --- /dev/null +++ b/src/pins/pkcs11/clevis-decrypt-pkcs11 @@ -0,0 +1,135 @@ +#!/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 + +DEC="${CLEVIS_PKCS11}/dec" +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..7ff3915e --- /dev/null +++ b/src/pins/pkcs11/clevis-pkcs11-afunix-socket-unlock.c @@ -0,0 +1,304 @@ +/* + * 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; +} + +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_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: + printf("Usage: %s -f socket_file [-c control_socket] [-k key] "\ + "[-t iterations, 3 by default] "\ + "[-s start delay, 0s by default]\n", argv[0]); + exit(ret_code); + } + } + 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..5fabba9c --- /dev/null +++ b/src/pins/pkcs11/meson.build @@ -0,0 +1,33 @@ +pcscd = find_program('pcscd', required: false) +pkcs11tool = find_program('pkcs11-tool', required: false) +pcscd_disable_polkit = false + +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, + ) +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..681e2903 --- /dev/null +++ b/src/pins/pkcs11/tests/pin-pkcs11 @@ -0,0 +1,147 @@ +#!/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" +UUID="" +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 + +# 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 +}