From 531a49181bd1b47e7a64731620d4a94bef15fc06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Fri, 6 Dec 2024 16:22:24 +0100 Subject: [PATCH 01/16] luks: fix tests not testing the values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- src/luks/tests/list-recursive-luks1 | 2 +- src/luks/tests/list-recursive-luks2 | 2 +- src/luks/tests/list-sss-tang-luks1 | 4 ++-- src/luks/tests/list-sss-tang-luks2 | 4 ++-- src/luks/tests/list-tang-luks1 | 2 +- src/luks/tests/list-tang-luks2 | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/luks/tests/list-recursive-luks1 b/src/luks/tests/list-recursive-luks1 index 6c49dd90..e2c95307 100755 --- a/src/luks/tests/list-recursive-luks1 +++ b/src/luks/tests/list-recursive-luks1 @@ -79,7 +79,7 @@ if [[ "${pin}" != "${PIN}" ]]; then fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") -cfg_for_cmp=${cfg//"${to_remove_from_cfg}"/} +cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi diff --git a/src/luks/tests/list-recursive-luks2 b/src/luks/tests/list-recursive-luks2 index 7509faac..7448905c 100755 --- a/src/luks/tests/list-recursive-luks2 +++ b/src/luks/tests/list-recursive-luks2 @@ -79,7 +79,7 @@ if [[ "${pin}" != "${PIN}" ]]; then fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") -cfg_for_cmp=${cfg//"${to_remove_from_cfg}"/} +cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi diff --git a/src/luks/tests/list-sss-tang-luks1 b/src/luks/tests/list-sss-tang-luks1 index 36c26893..ac9fe956 100755 --- a/src/luks/tests/list-sss-tang-luks1 +++ b/src/luks/tests/list-sss-tang-luks1 @@ -70,8 +70,8 @@ if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi -to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") -cfg_for_cmp=${cfg//"${to_remove_from_cfg}"/} +to_remove_from_cfg=$(printf ',"adv":"%s"' "${ADV}") +cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi diff --git a/src/luks/tests/list-sss-tang-luks2 b/src/luks/tests/list-sss-tang-luks2 index c39245b3..4e829796 100755 --- a/src/luks/tests/list-sss-tang-luks2 +++ b/src/luks/tests/list-sss-tang-luks2 @@ -70,8 +70,8 @@ if [[ "${pin}" != "${PIN}" ]]; then error "${TEST}: pin (${pin}) is expected to be '${PIN}'" fi -to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") -cfg_for_cmp=${cfg//"${to_remove_from_cfg}"/} +to_remove_from_cfg=$(printf ',"adv":"%s"' "${ADV}") +cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi diff --git a/src/luks/tests/list-tang-luks1 b/src/luks/tests/list-tang-luks1 index 24c187ba..e20047e2 100755 --- a/src/luks/tests/list-tang-luks1 +++ b/src/luks/tests/list-tang-luks1 @@ -58,7 +58,7 @@ if [[ "${pin}" != "${PIN}" ]]; then fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") -cfg_for_cmp=${cfg//"${to_remove_from_cfg}"/} +cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi diff --git a/src/luks/tests/list-tang-luks2 b/src/luks/tests/list-tang-luks2 index 0be6cfcd..14387cc9 100755 --- a/src/luks/tests/list-tang-luks2 +++ b/src/luks/tests/list-tang-luks2 @@ -58,7 +58,7 @@ if [[ "${pin}" != "${PIN}" ]]; then fi to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") -cfg_for_cmp=${cfg//"${to_remove_from_cfg}"/} +cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi From 36ab10c213b18fae5c4fc285c46e514a27102573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Sat, 4 May 2024 20:30:33 +0200 Subject: [PATCH 02/16] systemd: ensure the shutdown dependency is present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DefaultDependencies=yes option adds conflicting dependency on the shutdown.target automatically to ensure the service is terminated during the shutdown, so add it when we use DefaultDependencies=no. Signed-off-by: Oldřich Jedlička --- src/luks/systemd/clevis-luks-askpass.path | 2 ++ src/luks/systemd/clevis-luks-askpass.service.in | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/luks/systemd/clevis-luks-askpass.path b/src/luks/systemd/clevis-luks-askpass.path index 6c5333e7..dd97049c 100644 --- a/src/luks/systemd/clevis-luks-askpass.path +++ b/src/luks/systemd/clevis-luks-askpass.path @@ -4,6 +4,8 @@ Documentation=man:clevis-luks-unlockers(7) DefaultDependencies=no Before=cryptsetup-pre.target Wants=cryptsetup-pre.target +Before=shutdown.target +Conflicts=shutdown.target [Path] DirectoryNotEmpty=/run/systemd/ask-password diff --git a/src/luks/systemd/clevis-luks-askpass.service.in b/src/luks/systemd/clevis-luks-askpass.service.in index 6b4a7e31..0ffb9d3c 100644 --- a/src/luks/systemd/clevis-luks-askpass.service.in +++ b/src/luks/systemd/clevis-luks-askpass.service.in @@ -2,6 +2,8 @@ Description=Forward Password Requests to Clevis Documentation=man:clevis-luks-unlockers(7) DefaultDependencies=no +Before=shutdown.target +Conflicts=shutdown.target [Service] Type=simple From 72de5e507345932638b3f49a3e059d1ee7aa2437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Sat, 22 Jun 2024 13:13:08 +0200 Subject: [PATCH 03/16] luks: allow reading used set of pins by clevis scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- src/luks/clevis-luks-common-functions.in | 86 ++++++++ src/luks/clevis-luks-list | 12 +- src/luks/clevis-luks-list.1.adoc | 8 + .../dracut/clevis-pin-tang/module-setup.sh.in | 2 +- src/luks/tests/list-multiple-slots-luks1 | 201 ++++++++++++++++++ src/luks/tests/list-multiple-slots-luks2 | 201 ++++++++++++++++++ src/luks/tests/list-recursive-luks1 | 13 ++ src/luks/tests/list-recursive-luks2 | 13 ++ src/luks/tests/list-sss-tang-luks1 | 13 ++ src/luks/tests/list-sss-tang-luks2 | 13 ++ src/luks/tests/list-tang-luks1 | 13 ++ src/luks/tests/list-tang-luks2 | 13 ++ src/luks/tests/meson.build | 2 + 13 files changed, 585 insertions(+), 5 deletions(-) create mode 100755 src/luks/tests/list-multiple-slots-luks1 create mode 100755 src/luks/tests/list-multiple-slots-luks2 diff --git a/src/luks/clevis-luks-common-functions.in b/src/luks/clevis-luks-common-functions.in index 2b393160..52a460d5 100644 --- a/src/luks/clevis-luks-common-functions.in +++ b/src/luks/clevis-luks-common-functions.in @@ -323,6 +323,92 @@ clevis_luks_read_pins_from_slot() { printf "%s: %s\n" "${SLOT}" "${cfg}" } +# clevis_luks_decode_used_pins() will receive a JWE and extract used pins +# (line-separated, unsorted, not deduped) from it. +clevis_luks_decode_used_pins() { + local jwe="${1}" + local pins= + + local decoded + if ! decoded=$(clevis_luks_decode_jwe "${jwe}"); then + return 1 + fi + + local P + if ! P=$(jose fmt -j- -Og clevis -g pin -u- <<< "${decoded}"); then + return 1 + fi + + pins=$(printf "%s\n%s" "${pins}" "${P}") + if [ "${P}" = "sss" ]; then + local sss_jwe + if ! sss_jwe="$(jose fmt -j- -g clevis -g "${P}" -o- <<< "${decoded}")" \ + || [ -z "${sss_jwe}" ]; then + return 1 + fi + + local coded sss_pins + for coded in $(jose fmt -j- -Og jwe -Af- <<< "${sss_jwe}"| tr -d '"'); do + if ! sss_pins="$(clevis_luks_decode_used_pins "${coded}")"; then + continue + fi + pins=$(printf "%s\n%s" "${pins}" "${sss_pins}") + done + fi + + echo "${pins}" +} + +# clevis_luks_read_used_pins_from_slot() will receive a given device and slot +# and will then output space-separated sorted slot-prefixed list of used pins. +clevis_luks_read_used_pins_from_slot() { + local DEV="${1}" + local SLOT="${2}" + + local jwe + if ! jwe=$(clevis_luks_read_slot "${DEV}" "${SLOT}" 2>/dev/null); then + return 1 + fi + + local pins + if ! pins=$(clevis_luks_decode_used_pins "${jwe}"); then + return 1 + fi + + pins=$(echo -n "${pins}" | sed -e '/^$/d' | sort -u | tr '\n' ' ' | sed -e 's/ $//') + printf "%s: %s\n" "${SLOT}" "${pins}" +} + +# clevis_luks_read_used_pins() will receive a given device and will then output +# space-separated sorted list of all used pins in all slots. +clevis_luks_read_used_pins() { + local DEV="${1}" + + [ -z "${DEV}" ] && return 1 + + local used_slots + if ! used_slots=$(clevis_luks_used_slots "${DEV}") \ + || [ -z "${used_slots}" ]; then + return 1 + fi + + local pins= + local slot slot_pins used_pins + for slot in ${used_slots}; do + if ! slot_pins=$(clevis_luks_read_used_pins_from_slot "${DEV}" "${slot}"); then + continue + fi + + read -r _ used_pins <<< "${slot_pins}" + pins=$(printf "%s\n%s" "${pins}" "${used_pins}") + done + + pins=$(echo -n "${pins}" | tr ' ' '\n' | sed -e '/^$/d' | sort -u | tr '\n' ' ' | sed -e 's/ $//') + [ -z "${pins}" ] && return 1 + + echo "${pins}" +} + # clevis_luks_check_valid_key_or_keyfile() receives a devices and either a # passphrase or keyfile and then checks whether it is able to unlock the # device wih the received passphrase/keyfile. diff --git a/src/luks/clevis-luks-list b/src/luks/clevis-luks-list index 40b68b84..759f3e68 100755 --- a/src/luks/clevis-luks-list +++ b/src/luks/clevis-luks-list @@ -25,7 +25,7 @@ SUMMARY="Lists pins bound to a LUKSv1 or LUKSv2 device" function usage() { echo >&2 - echo "Usage: clevis luks list -d DEV [-s SLT]" >&2 + echo "Usage: clevis luks list -d DEV [-s SLT] [-p]" >&2 echo >&2 echo "$SUMMARY": >&2 echo >&2 @@ -33,6 +33,8 @@ function usage() { echo >&2 echo " -s SLOT The slot number to list" >&2 echo >&2 + echo " -p Print only a sorted space-separated list of bound pins" >&2 + echo >&2 exit 1 } @@ -41,10 +43,12 @@ if [ ${#} -eq 1 ] && [ "${1}" = "--summary" ]; then exit 0 fi -while getopts ":d:s:" o; do +luks_function=clevis_luks_read_pins_from_slot +while getopts ":d:s:p" o; do case "$o" in d) DEV=${OPTARG};; s) SLT=${OPTARG};; + p) luks_function=clevis_luks_read_used_pins_from_slot;; *) usage;; esac done @@ -62,7 +66,7 @@ if cryptsetup isLuks --type luks1 "${DEV}"; then fi if [ -n "${SLT}" ]; then - clevis_luks_read_pins_from_slot "${DEV}" "${SLT}" + $luks_function "${DEV}" "${SLT}" else if ! used_slots=$(clevis_luks_used_slots "${DEV}"); then echo "No used slots detected for device ${DEV}!" >&2 @@ -70,7 +74,7 @@ else fi for s in ${used_slots}; do - if ! clevis_luks_read_pins_from_slot "${DEV}" "${s}"; then + if ! $luks_function "${DEV}" "${s}"; then continue fi done diff --git a/src/luks/clevis-luks-list.1.adoc b/src/luks/clevis-luks-list.1.adoc index f00d9bf9..6e85a54e 100644 --- a/src/luks/clevis-luks-list.1.adoc +++ b/src/luks/clevis-luks-list.1.adoc @@ -26,6 +26,9 @@ For example: * *-s* _SLT_ : The slot to use for listing the pin from +* *-p* : + Print only a sorted space-separated list of bound pins + == EXAMPLES clevis luks list -d /dev/sda1 @@ -33,6 +36,11 @@ For example: 2: tang '{"url":"addr"}' 3: tpm2 '{"hash":"sha256","key":"ecc","pcr_bank":"sha1","pcr_ids":"7"}' + clevis luks list -d /dev/sda1 -p + 1: sss tang tpm2 + 2: tang + 3: tpm2 + As we can see in the example above, */dev/sda1* has three slots bound each with a different pin. - Slot #1 is bound with the _sss_ pin, and uses also tang and tpm2 pins in its policy. diff --git a/src/luks/dracut/clevis-pin-tang/module-setup.sh.in b/src/luks/dracut/clevis-pin-tang/module-setup.sh.in index 929b878c..364f866f 100755 --- a/src/luks/dracut/clevis-pin-tang/module-setup.sh.in +++ b/src/luks/dracut/clevis-pin-tang/module-setup.sh.in @@ -27,7 +27,7 @@ have_tang_bindings() { . clevis-luks-common-functions local dev for dev in $(clevis_devices_to_unlock "list-open-devices"); do - if clevis luks list -d "${dev}" | grep -q tang; then + if clevis luks list -d "${dev}" -p | grep -q tang; then return 0 fi done diff --git a/src/luks/tests/list-multiple-slots-luks1 b/src/luks/tests/list-multiple-slots-luks1 new file mode 100755 index 00000000..bb6a11f3 --- /dev/null +++ b/src/luks/tests/list-multiple-slots-luks1 @@ -0,0 +1,201 @@ +#!/bin/bash -ex +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# Author: Oldřich Jedlička +# +# 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 . +# + +TEST=$(basename "${0}") +. luks-common-test-functions + +on_exit() { + [ -d "${TMP}" ] && rm -rf "${TMP}" +} + +trap 'on_exit' EXIT +trap 'exit' ERR + +TMP="$(mktemp -d)" + +ADV="${TMP}/adv.jws" +tang_create_adv "${TMP}" "${ADV}" +PIN1="sss" +PINS1="sss tang" +CFG1=$(printf ' +{ + "t": 1, + "pins": { + "tang": [ + { + "url": "ADDR","adv": "%s" + } + ] + } +} +' "${ADV}") +PIN2="null" +PINS2="null" +CFG2='{}' +ALLPINS="null sss tang" + +# LUKS1 +DEV="${TMP}/luks1-device" +new_device "luks1" "${DEV}" + +if ! clevis luks bind -f -d "${DEV}" "${PIN1}" "${CFG1}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." +fi + +if ! clevis luks bind -f -d "${DEV}" "${PIN2}" "${CFG2}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Binding is expected to succeed for null pin." +fi + +test_values() { + local SLT="$1" + local slot="$2" + local pin="$3" + local cfg="$4" + + case $SLT in + 1) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pin}" != "${PIN1}" ]]; then + error "${TEST}: pin (${pin}) is expected to be '${PIN1}'" + fi + + to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") + cfg_for_cmp=${CFG1//"${to_remove_from_cfg}"/} #" + if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then + error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" + fi + ;; + 2) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pin}" != "${PIN2}" ]]; then + error "${TEST}: pin (${pin}) is expected to be '${PIN2}'" + fi + + if ! pin_cfg_equal "${cfg}" "${CFG2}"; then + error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${CFG2})" + fi + ;; + *) + error "${TEST}: unexpected slot ${SLT}" + ;; + esac +} + +test_pin_values() { + local SLT="$1" + local slot="$2" + local pins="$3" + + case $SLT in + 1) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pins}" != "${PINS1}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS1}'" + fi + ;; + 2) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pins}" != "${PINS2}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS2}'" + fi + ;; + *) + error "${TEST}: unexpected slot ${SLT}" + ;; + esac +} + +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 + +test_values "${SLT}" "${slot}" "${pin}" "${cfg}" + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +test_pin_values "${SLT}" "${slot}" "${pins}" + +SLT=2 +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 + +test_values "${SLT}" "${slot}" "${pin}" "${cfg}" + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +test_pin_values "${SLT}" "${slot}" "${pins}" + +# Test both slots reading +if ! slots=$(clevis luks list -d "${DEV}"); then + error "${TEST}: clevis luks list is expected to succeed for device(${DEV})" +fi + +read_slots= +while read -r slot pin cfg; do + read_slots="${read_slots}${read_slots:+ }${slot%:}" + test_values "${slot%:}" "${slot}" "${pin}" "${cfg}" +done <<< "$slots" + +if [[ "${read_slots}" != "1 2" ]]; then + error "${TEST}: clevis luks list did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" +fi + +if ! slots=$(clevis luks list -d "${DEV}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV})" +fi + +read_slots= +while read -r slot pins; do + read_slots="${read_slots}${read_slots:+ }${slot%:}" + test_pin_values "${slot%:}" "${slot}" "${pins}" +done <<< "$slots" + +if [[ "${read_slots}" != "1 2" ]]; then + error "${TEST}: clevis luks list -p did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" +fi + +# Test clevis_luks_read_used_pins +. clevis-luks-common-functions + +if ! pins=$(clevis_luks_read_used_pins "${DEV}"); then + error "${TEST}: clevis_luks_read_used_pins is expected to succeed for device(${DEV})" +fi + +if [[ "${pins}" != "${ALLPINS}" ]]; then + error "${TEST}: clevis_luks_read_used_pins did not return all expected pins (${ALLPINS}), it was (${pins}) for device(${DEV})" +fi diff --git a/src/luks/tests/list-multiple-slots-luks2 b/src/luks/tests/list-multiple-slots-luks2 new file mode 100755 index 00000000..2d1ccfb4 --- /dev/null +++ b/src/luks/tests/list-multiple-slots-luks2 @@ -0,0 +1,201 @@ +#!/bin/bash -ex +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# Author: Oldřich Jedlička +# +# 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 . +# + +TEST=$(basename "${0}") +. luks-common-test-functions + +on_exit() { + [ -d "${TMP}" ] && rm -rf "${TMP}" +} + +trap 'on_exit' EXIT +trap 'exit' ERR + +TMP="$(mktemp -d)" + +ADV="${TMP}/adv.jws" +tang_create_adv "${TMP}" "${ADV}" +PIN1="sss" +PINS1="sss tang" +CFG1=$(printf ' +{ + "t": 1, + "pins": { + "tang": [ + { + "url": "ADDR","adv": "%s" + } + ] + } +} +' "${ADV}") +PIN2="null" +PINS2="null" +CFG2='{}' +ALLPINS="null sss tang" + +# LUKS2 +DEV="${TMP}/luks1-device" +new_device "luks2" "${DEV}" + +if ! clevis luks bind -f -d "${DEV}" "${PIN1}" "${CFG1}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Binding is expected to succeed when given a correct (${DEFAULT_PASS}) password." +fi + +if ! clevis luks bind -f -d "${DEV}" "${PIN2}" "${CFG2}" <<< "${DEFAULT_PASS}"; then + error "${TEST}: Binding is expected to succeed for null pin." +fi + +test_values() { + local SLT="$1" + local slot="$2" + local pin="$3" + local cfg="$4" + + case $SLT in + 1) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pin}" != "${PIN1}" ]]; then + error "${TEST}: pin (${pin}) is expected to be '${PIN1}'" + fi + + to_remove_from_cfg=$(printf ',"adv": "%s"' "${ADV}") + cfg_for_cmp=${CFG1//"${to_remove_from_cfg}"/} #" + if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then + error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" + fi + ;; + 2) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pin}" != "${PIN2}" ]]; then + error "${TEST}: pin (${pin}) is expected to be '${PIN2}'" + fi + + if ! pin_cfg_equal "${cfg}" "${CFG2}"; then + error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${CFG2})" + fi + ;; + *) + error "${TEST}: unexpected slot ${SLT}" + ;; + esac +} + +test_pin_values() { + local SLT="$1" + local slot="$2" + local pins="$3" + + case $SLT in + 1) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pins}" != "${PINS1}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS1}'" + fi + ;; + 2) + if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" + fi + + if [[ "${pins}" != "${PINS2}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS2}'" + fi + ;; + *) + error "${TEST}: unexpected slot ${SLT}" + ;; + esac +} + +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 + +test_values "${SLT}" "${slot}" "${pin}" "${cfg}" + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +test_pin_values "${SLT}" "${slot}" "${pins}" + +SLT=2 +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 + +test_values "${SLT}" "${slot}" "${pin}" "${cfg}" + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +test_pin_values "${SLT}" "${slot}" "${pins}" + +# Test both slots reading +if ! slots=$(clevis luks list -d "${DEV}"); then + error "${TEST}: clevis luks list is expected to succeed for device(${DEV})" +fi + +read_slots= +while read -r slot pin cfg; do + read_slots="${read_slots}${read_slots:+ }${slot%:}" + test_values "${slot%:}" "${slot}" "${pin}" "${cfg}" +done <<< "$slots" + +if [[ "${read_slots}" != "1 2" ]]; then + error "${TEST}: clevis luks list did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" +fi + +if ! slots=$(clevis luks list -d "${DEV}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV})" +fi + +read_slots= +while read -r slot pins; do + read_slots="${read_slots}${read_slots:+ }${slot%:}" + test_pin_values "${slot%:}" "${slot}" "${pins}" +done <<< "$slots" + +if [[ "${read_slots}" != "1 2" ]]; then + error "${TEST}: clevis luks list -p did not return all expected slots (1 2), it was (${read_slots}) for device(${DEV})" +fi + +# Test clevis_luks_read_used_pins +. clevis-luks-common-functions + +if ! pins=$(clevis_luks_read_used_pins "${DEV}"); then + error "${TEST}: clevis_luks_read_used_pins is expected to succeed for device(${DEV})" +fi + +if [[ "${pins}" != "${ALLPINS}" ]]; then + error "${TEST}: clevis_luks_read_used_pins did not return all expected pins (${ALLPINS}), it was (${pins}) for device(${DEV})" +fi diff --git a/src/luks/tests/list-recursive-luks1 b/src/luks/tests/list-recursive-luks1 index e2c95307..9df3a47c 100755 --- a/src/luks/tests/list-recursive-luks1 +++ b/src/luks/tests/list-recursive-luks1 @@ -33,6 +33,7 @@ TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" +PINS="sss tang" CFG=$(printf ' { "t": 1, @@ -83,3 +84,15 @@ cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" +fi + +if [[ "${pins}" != "${PINS}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" +fi diff --git a/src/luks/tests/list-recursive-luks2 b/src/luks/tests/list-recursive-luks2 index 7448905c..871c60ba 100755 --- a/src/luks/tests/list-recursive-luks2 +++ b/src/luks/tests/list-recursive-luks2 @@ -33,6 +33,7 @@ TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" +PINS="sss tang" CFG=$(printf ' { "t": 1, @@ -83,3 +84,15 @@ cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" +fi + +if [[ "${pins}" != "${PINS}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" +fi diff --git a/src/luks/tests/list-sss-tang-luks1 b/src/luks/tests/list-sss-tang-luks1 index ac9fe956..b3bb5236 100755 --- a/src/luks/tests/list-sss-tang-luks1 +++ b/src/luks/tests/list-sss-tang-luks1 @@ -33,6 +33,7 @@ TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" +PINS="sss tang" CFG=$(printf ' { "t": 2, @@ -75,3 +76,15 @@ cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" +fi + +if [[ "${pins}" != "${PINS}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" +fi diff --git a/src/luks/tests/list-sss-tang-luks2 b/src/luks/tests/list-sss-tang-luks2 index 4e829796..aec92d19 100755 --- a/src/luks/tests/list-sss-tang-luks2 +++ b/src/luks/tests/list-sss-tang-luks2 @@ -33,6 +33,7 @@ TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="sss" +PINS="sss tang" CFG=$(printf ' { "t": 2, @@ -75,3 +76,15 @@ cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" +fi + +if [[ "${pins}" != "${PINS}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" +fi diff --git a/src/luks/tests/list-tang-luks1 b/src/luks/tests/list-tang-luks1 index e20047e2..653acce2 100755 --- a/src/luks/tests/list-tang-luks1 +++ b/src/luks/tests/list-tang-luks1 @@ -33,6 +33,7 @@ TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="tang" +PINS="tang" CFG=$(printf '{"url": "ADDR","adv": "%s"}' "${ADV}") # LUKS1. @@ -62,3 +63,15 @@ cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" +fi + +if [[ "${pins}" != "${PINS}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" +fi diff --git a/src/luks/tests/list-tang-luks2 b/src/luks/tests/list-tang-luks2 index 14387cc9..ee91fd2b 100755 --- a/src/luks/tests/list-tang-luks2 +++ b/src/luks/tests/list-tang-luks2 @@ -33,6 +33,7 @@ TMP="$(mktemp -d)" ADV="${TMP}/adv.jws" tang_create_adv "${TMP}" "${ADV}" PIN="tang" +PINS="tang" CFG=$(printf '{"url": "ADDR","adv": "%s"}' "${ADV}") # LUKS2. @@ -62,3 +63,15 @@ cfg_for_cmp=${CFG//"${to_remove_from_cfg}"/} #" if ! pin_cfg_equal "${cfg}" "${cfg_for_cmp}"; then error "${TEST}: config obtained from clevis luks list (${cfg}) is expected to match the one used to bind the test (${cfg_for_cmp})" fi + +if ! read -r slot pins < <(clevis luks list -d "${DEV}" -s "${SLT}" -p); then + error "${TEST}: clevis luks list -p is expected to succeed for device(${DEV}) and slot (${SLT})" +fi + +if [[ "${slot}" != "${SLT}:" ]]; then + error "${TEST}: used pins slot (${slot}) is expected to be ${SLT}" +fi + +if [[ "${pins}" != "${PINS}" ]]; then + error "${TEST}: used pins (${pins}) are expected to be '${PINS}'" +fi diff --git a/src/luks/tests/meson.build b/src/luks/tests/meson.build index 9e51fb6c..632fe8d3 100644 --- a/src/luks/tests/meson.build +++ b/src/luks/tests/meson.build @@ -54,6 +54,7 @@ if jq.found() test('list-recursive-luks1', find_program('list-recursive-luks1'), env: env) test('list-tang-luks1', find_program('list-tang-luks1'), env: env) test('list-sss-tang-luks1', find_program('list-sss-tang-luks1'), env: env) + test('list-multiple-slots-luks1', find_program('list-multiple-slots-luks1'), env: env) else warning('Will not run "clevis luks list" tests due to missing jq dependency') endif @@ -87,6 +88,7 @@ if luksmeta_data.get('OLD_CRYPTSETUP') == '0' test('list-recursive-luks2', find_program('list-recursive-luks2'), env: env, timeout: 60) test('list-tang-luks2', find_program('list-tang-luks2'), env: env, timeout: 60) test('list-sss-tang-luks2', find_program('list-sss-tang-luks2'), env: env, timeout: 60) + test('list-multiple-slots-luks2', find_program('list-multiple-slots-luks2'), env: env, timeout: 60) endif test('unlock-tang-luks2', find_program('unlock-tang-luks2'), env: env, timeout: 120) From 92d82b38b4de0f7cc00bc07799b83433e1cdb2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Thu, 31 Oct 2024 15:41:06 +0100 Subject: [PATCH 04/16] dracut: supply cryptsetup password through pipe (non-systemd boot) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current Dracut integration for bootup without Systemd ignores all cryptsetup options, which are usually handled by Dracut itself (like reading /etc/crypttab). We need to hook into the Dracut cryptsetup process in order to allow Dracut handling the options and us handling the password only. Dracut uses generated udev rules to create cryptsetup unlocking scripts in initqueue/settled dynamically when the corresponding device appears. The unlocking tries to unlock by the key file first and then by password read from user. We can hook into the key file reading stage by providing our own pipe and send the password via the pipe similarly to how the initramfs-tools clevisloop is doing it. There is one difference, though, we have only one try to unlock, but that should be enough. For the network pins (tang and sss/tang at the moment) we can move the generated Dracut cryptsetup unlocking scripts to initqueue/online to ensure the unlocking happens at the right time. Signed-off-by: Oldřich Jedlička --- .../scripts/local-bottom/clevis.in | 18 +- src/luks/clevis-luks-common-functions.in | 23 +++ ...evis-hook.sh.in => clevis-cleanup-hook.sh} | 7 +- src/luks/dracut/clevis/clevis-cleanup.in | 30 ++++ src/luks/dracut/clevis/clevis-luks-unlocker | 72 -------- src/luks/dracut/clevis/clevis-online-hook.sh | 21 +++ .../clevis/clevis-password-unlocker-hook.sh | 21 +++ .../clevis-password-unlocker-prepare.in | 59 +++++++ .../dracut/clevis/clevis-password-unlocker.in | 160 ++++++++++++++++++ src/luks/dracut/clevis/meson.build | 22 ++- src/luks/dracut/clevis/module-setup.sh.in | 16 +- 11 files changed, 350 insertions(+), 99 deletions(-) rename src/luks/dracut/clevis/{clevis-hook.sh.in => clevis-cleanup-hook.sh} (84%) create mode 100755 src/luks/dracut/clevis/clevis-cleanup.in delete mode 100755 src/luks/dracut/clevis/clevis-luks-unlocker create mode 100755 src/luks/dracut/clevis/clevis-online-hook.sh create mode 100755 src/luks/dracut/clevis/clevis-password-unlocker-hook.sh create mode 100755 src/luks/dracut/clevis/clevis-password-unlocker-prepare.in create mode 100755 src/luks/dracut/clevis/clevis-password-unlocker.in diff --git a/src/initramfs-tools/scripts/local-bottom/clevis.in b/src/initramfs-tools/scripts/local-bottom/clevis.in index 4798f203..9ad782cf 100755 --- a/src/initramfs-tools/scripts/local-bottom/clevis.in +++ b/src/initramfs-tools/scripts/local-bottom/clevis.in @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Copyright (c) 2017 Shawn Rose # @@ -33,20 +33,10 @@ esac [ -s /run/clevis.pid ] || exit 0 +. @bindir@/clevis-luks-common-functions + pid=$(cat /run/clevis.pid) -child_pids="$({ ps -o pid,ppid 2>/dev/null || ps -l || - { echo 'clevis: unable to get list of processes' >&2; exit 1; }; } | - awk -v pid="$pid" ' - NR==1 { - for (i=1; i<=NF; i++) if ($i == "PID") pid_col = i; else if ($i == "PPID") ppid_col = i - if (!pid_col || !ppid_col) { print "clevis: unable to find PID and/or PPID columns in ps output" | "cat >&2"; exit 1 } - next - } - { if ($ppid_col == pid) print $pid_col }')" - -for kill_pid in $pid $child_pids; do - kill "$kill_pid" 2>/dev/null -done +clevis_kill_pid $pid rm -f /run/clevis.pid diff --git a/src/luks/clevis-luks-common-functions.in b/src/luks/clevis-luks-common-functions.in index 52a460d5..f28a0fc5 100644 --- a/src/luks/clevis-luks-common-functions.in +++ b/src/luks/clevis-luks-common-functions.in @@ -1183,3 +1183,26 @@ clevis_luks_type() { fi echo "${luks_type}" } + +# clevis_kill_pid() kills process and its children in a portable way +# Works with both procps ps and Busybox ps. +function clevis_kill_pid() { + local pid="$1" + local child_pids + + [ -z "${pid}" ] && return 1 + + child_pids="$({ ps -Ao pid,ppid 2>/dev/null || ps -o pid,ppid 2>/dev/null || ps -Al 2>/dev/null || ps -l || + { echo 'clevis: unable to get list of processes' >&2; exit 1; }; } | + awk -v pid="$pid" ' + NR==1 { + for (i=1; i<=NF; i++) if ($i == "PID") pid_col = i; else if ($i == "PPID") ppid_col = i + if (!pid_col || !ppid_col) { print "clevis: unable to find PID and/or PPID columns in ps output" | "cat >&2"; exit 1 } + next + } + { if ($ppid_col == pid) print $pid_col }')" + + for kill_pid in $pid $child_pids; do + kill "$kill_pid" 2>/dev/null + done +} diff --git a/src/luks/dracut/clevis/clevis-hook.sh.in b/src/luks/dracut/clevis/clevis-cleanup-hook.sh similarity index 84% rename from src/luks/dracut/clevis/clevis-hook.sh.in rename to src/luks/dracut/clevis/clevis-cleanup-hook.sh index 78921a5d..95ec952f 100755 --- a/src/luks/dracut/clevis/clevis-hook.sh.in +++ b/src/luks/dracut/clevis/clevis-cleanup-hook.sh @@ -1,10 +1,9 @@ #!/bin/sh -set -eu # vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: # -# Copyright (c) 2020-2024 Red Hat, Inc. -# Author: Sergio Correia +# Copyright (c) 2024 Oldřich Jedlička # +# Author: Oldřich Jedlička # # 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 @@ -19,4 +18,4 @@ set -eu # You should have received a copy of the GNU General Public License # along with this program. If not, see . -@libexecdir@/clevis-luks-unlocker -l +/bin/clevis-cleanup diff --git a/src/luks/dracut/clevis/clevis-cleanup.in b/src/luks/dracut/clevis/clevis-cleanup.in new file mode 100755 index 00000000..862e2172 --- /dev/null +++ b/src/luks/dracut/clevis/clevis-cleanup.in @@ -0,0 +1,30 @@ +#!/bin/bash +# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . + +[ -s /run/clevis.pid ] || exit 0 + +. clevis-luks-common-functions + +pid=$(cat /run/clevis.pid) +clevis_kill_pid $pid + +rm -f /run/clevis.pid +rm -f /run/clevis-online +rm -rf /run/cryptroot-ask-pipes diff --git a/src/luks/dracut/clevis/clevis-luks-unlocker b/src/luks/dracut/clevis/clevis-luks-unlocker deleted file mode 100755 index 83063206..00000000 --- a/src/luks/dracut/clevis/clevis-luks-unlocker +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/sh -set -eu -# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: -# -# Copyright (c) 2020-2024 Red Hat, Inc. -# Author: Sergio Correia -# -# Non-systemd clevis unlocker -# Modifications sponsored by PMGA Tech LLP -# -# -# 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 . -# - -. clevis-luks-common-functions - -# Make sure to exit cleanly if SIGTERM is received. -trap 'echo "Exiting due to SIGTERM" && exit 0' TERM - -loop= -while getopts ":l" o; do - case "${o}" in - l) loop=true;; - *) ;; - esac -done - -to_unlock() { - _devices='' - for _d in $(blkid -t TYPE=crypto_LUKS -o device); do - if ! bindings="$(clevis luks list -d "${_d}" 2>/dev/null)" \ - || [ -z "${bindings}" ]; then - continue - fi - _uuid="$(cryptsetup luksUUID "${_d}")" - if clevis_is_luks_device_by_uuid_open "${_uuid}"; then - continue - fi - _devices="$(printf '%s\n%s' "${_devices}" "${_d}")" - done - echo "${_devices}" | sed -e 's/^\n$//' -} - -while true; do - for d in $(to_unlock); do - uuid="$(cryptsetup luksUUID "${d}")" - if ! clevis luks unlock -d "${d}"; then - echo "Unable to unlock ${d} (UUID=${uuid})" >&2 - continue - fi - echo "Unlocked ${d} (UUID=${uuid}) successfully" >&2 - done - - [ "${loop}" != true ] && break - # Checking for pending devices to be unlocked. - if remaining=$(to_unlock) && [ -z "${remaining}" ]; then - break; - fi - - sleep 0.5 -done diff --git a/src/luks/dracut/clevis/clevis-online-hook.sh b/src/luks/dracut/clevis/clevis-online-hook.sh new file mode 100755 index 00000000..0916b2f2 --- /dev/null +++ b/src/luks/dracut/clevis/clevis-online-hook.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +touch /run/clevis-online diff --git a/src/luks/dracut/clevis/clevis-password-unlocker-hook.sh b/src/luks/dracut/clevis/clevis-password-unlocker-hook.sh new file mode 100755 index 00000000..2eaf4948 --- /dev/null +++ b/src/luks/dracut/clevis/clevis-password-unlocker-hook.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +/bin/clevis-password-unlocker-prepare diff --git a/src/luks/dracut/clevis/clevis-password-unlocker-prepare.in b/src/luks/dracut/clevis/clevis-password-unlocker-prepare.in new file mode 100755 index 00000000..16781091 --- /dev/null +++ b/src/luks/dracut/clevis/clevis-password-unlocker-prepare.in @@ -0,0 +1,59 @@ +#!/bin/bash +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +. /lib/dracut-crypt-lib.sh +. clevis-luks-common-functions + +mkdir -p /run/cryptroot-ask-pipes +chmod 0700 /run/cryptroot-ask-pipes + +pipeprefix=run/cryptroot-ask-pipes +ensurestarted=0 + +# shellcheck disable=SC2154 # $hookdir is a dracut variable +for askpass in "$hookdir"/initqueue/settled/cryptroot-ask-*.sh; do + [ ! -e "${askpass}" ] && continue + + if device=$(grep '^[^ ]*/cryptroot-ask ' "${askpass}" | awk '{print $2}'); then + if [ "${1##/dev/dm-}" != "$1" ]; then + device="/dev/mapper/$(dmsetup info -c --noheadings -o name "$1")" + fi + + getkey /tmp/luks.keys "$device" >/dev/null && continue + + detectedfile="/tmp/clevis-device-detected-${device//\//_}" + [ -f "${detectedfile}" ] && continue + : >> "${detectedfile}" + + pins=$(clevis_luks_read_used_pins "${device}") || continue + + if [[ " $pins " == *" tang "* ]] && getargbool 0 rd.neednet && [ ! -f /run/clevis-online ]; then + mv -f "${askpass}" "${askpass/settled/online}" + fi + + pipepath="${pipeprefix}/pipe-${device//\//_}" + echo "rd.luks.key=/:${pipepath}:${device}" >> /etc/cmdline.d/60-clevis-keys.conf + echo "${device}:/:${pipepath}" >> /tmp/luks.keys + mkfifo "/${pipepath}" + ensurestarted=1 + fi +done + +[ $ensurestarted -eq 1 ] && [ ! -s /run/clevis.pid ] && /bin/clevis-password-unlocker diff --git a/src/luks/dracut/clevis/clevis-password-unlocker.in b/src/luks/dracut/clevis/clevis-password-unlocker.in new file mode 100755 index 00000000..58cdf74c --- /dev/null +++ b/src/luks/dracut/clevis/clevis-password-unlocker.in @@ -0,0 +1,160 @@ +#!/bin/bash +# +# Copyright (c) 2017 Red Hat, Inc. +# Copyright (c) 2017 Shawn Rose +# Copyright (c) 2017 Guilhem Moulin +# +# Author: Harald Hoyer +# Author: Nathaniel McCallum +# Author: Shawn Rose +# Author: Guilhem Moulin +# Based-on: src/initramfs-tools/scripts/local-top/clevis.in +# Author: Oldřich Jedlička +# +# 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 . +# + +. /lib/dracut-lib.sh +. /lib/dracut-crypt-lib.sh + +. clevis-luks-common-functions + +# Return fifo path or nothing if not found +get_device_fifo_path() { + local device="$1" + local tmp + + [ -z "${device}" ] && return 0 + + if tmp=$(getkey /tmp/luks.keys "$device"); then + keydev="${tmp%%:*}" + keypath="${tmp#*:}" + + [ "${keydev}" != "/" ] && return 1 + [ "${keypath#run/cryptroot-ask-pipes/}" = "${keypath}" ] && return 1 + + echo "/${keypath}" + return 0 + fi + return 1 +} + +# Gets the luks device to be unlocked and used pins +get_pid_device_pins() { + local pid="$1" + local CRYPTTAB_SOURCE + + CRYPTTAB_SOURCE=$(tr '\0' '\n' 2>/dev/null 3 { exit }') + + # Wrong process, no CRYPTTAB_SOURCE, return error + [ -n "$CRYPTTAB_SOURCE" ] || return 1 + + [ -b "$CRYPTTAB_SOURCE" ] || return 0 + + local cache="/var/cache/clevis-disks/${CRYPTTAB_SOURCE//\//_}" + if [ ! -f "$cache" ]; then + local pins + pins=$(clevis_luks_read_used_pins "$CRYPTTAB_SOURCE") + echo "${CRYPTTAB_SOURCE}:${pins}" > "$cache" + fi + + cat "$cache" + return 0 +} + +# Print colon-separated password-asking info like device, pins and fifo +# path for unlocking with password +get_askpass_info() { + local psinfo pf dev_pins + psinfo=$(ps -A 2>/dev/null || ps) # Doing this so I don't end up matching myself + echo "$psinfo" | awk '/cryptroot-ask/ { print $1 }' | { + while read -r pid; do + if dev_pins=$(get_pid_device_pins "${pid}") && pf=$(get_device_fifo_path "${dev_pins%%:*}"); then + if [[ $pf != "" && $dev_pins != "" ]]; then + # Output only in case of clevis device + echo "${dev_pins}:${pf}" + fi + # Return that we found valid process + return 0 + fi + done + return 1 + } +} + +# Try to decrypt the password to fifo file +luks_decrypt() { + local CRYPTTAB_SOURCE=$1 + local PASSFIFO=$2 + local pt + + if pt=$(clevis_luks_unlock_device "${CRYPTTAB_SOURCE}"); then + echo -n "${pt}" >"${PASSFIFO}" + return 0 + else + return 1 + fi +} + +# Wait for askpass, and then try and decrypt immediately. Just in case +# there are multiple devices that need decrypting, this will loop +# infinitely (The local-bottom script will kill this after decryption) +clevisloop() { + local askpass_info + local sleep_time + local OLD_CRYPTTAB_SOURCE="" + + while true; do + # Re-get the askpass PID in case there are multiple encrypted devices + CRYPTTAB_SOURCE="" + sleep_time=.1 + until [ -n "$CRYPTTAB_SOURCE" ] && [ -p "$PASSFIFO" ]; do + sleep $sleep_time + if askpass_info=$(get_askpass_info); then + IFS=':' read -r CRYPTTAB_SOURCE pins PASSFIFO < "${PASSFIFO}" + sleep 5 + fi + done +} + +mkdir -p /var/cache/clevis-disks +chmod 0700 /var/cache/clevis-disks + +clevisloop & +echo $! >/run/clevis.pid diff --git a/src/luks/dracut/clevis/meson.build b/src/luks/dracut/clevis/meson.build index b05bc101..4fd4486b 100644 --- a/src/luks/dracut/clevis/meson.build +++ b/src/luks/dracut/clevis/meson.build @@ -11,13 +11,29 @@ if dracut.found() ) configure_file( - input: 'clevis-hook.sh.in', - output: 'clevis-hook.sh', + input: 'clevis-cleanup.in', + output: 'clevis-cleanup', install_dir: dracutdir, configuration: data, ) - install_data('clevis-luks-unlocker', install_dir: libexecdir) + configure_file( + input: 'clevis-password-unlocker.in', + output: 'clevis-password-unlocker', + install_dir: dracutdir, + configuration: data, + ) + + configure_file( + input: 'clevis-password-unlocker-prepare.in', + output: 'clevis-password-unlocker-prepare', + install_dir: dracutdir, + configuration: data, + ) + + install_data('clevis-cleanup-hook.sh', install_dir: dracutdir) + install_data('clevis-password-unlocker-hook.sh', install_dir: dracutdir) + install_data('clevis-online-hook.sh', install_dir: dracutdir) else warning('Will not install dracut module due to missing dependencies!') endif diff --git a/src/luks/dracut/clevis/module-setup.sh.in b/src/luks/dracut/clevis/module-setup.sh.in index ef8770da..593d87a4 100755 --- a/src/luks/dracut/clevis/module-setup.sh.in +++ b/src/luks/dracut/clevis/module-setup.sh.in @@ -19,7 +19,7 @@ # depends() { - local __depends=crypt + local __depends="crypt bash" if dracut_module_included "systemd"; then # Dracut v103 introduced a separate systemd-cryptsetup module systemd_cryptsetup_dir=$(dracut_module_path "systemd-cryptsetup") @@ -36,17 +36,20 @@ depends() { install() { if dracut_module_included "systemd"; then inst_multiple \ - $systemdsystemunitdir/clevis-luks-askpass.service \ - $systemdsystemunitdir/clevis-luks-askpass.path \ + "$systemdsystemunitdir"/clevis-luks-askpass.service \ + "$systemdsystemunitdir"/clevis-luks-askpass.path \ @SYSTEMD_REPLY_PASS@ \ @libexecdir@/clevis-luks-askpass systemctl -q --root "$initdir" add-wants cryptsetup.target clevis-luks-askpass.path else - inst_hook initqueue/online 60 "$moddir/clevis-hook.sh" - inst_hook initqueue/settled 60 "$moddir/clevis-hook.sh" + inst_hook initqueue/settled 60 "$moddir"/clevis-password-unlocker-hook.sh + inst_hook initqueue/online 60 "$moddir"/clevis-online-hook.sh + inst_hook cleanup 60 "$moddir"/clevis-cleanup-hook.sh + inst_script "$moddir"/clevis-cleanup /bin/clevis-cleanup + inst_script "$moddir"/clevis-password-unlocker /bin/clevis-password-unlocker + inst_script "$moddir"/clevis-password-unlocker-prepare /bin/clevis-password-unlocker-prepare inst_multiple \ - @libexecdir@/clevis-luks-unlocker \ clevis-luks-unlock \ blkid fi @@ -54,6 +57,7 @@ install() { inst_multiple \ /etc/services \ clevis-luks-common-functions \ + awk date ps sort touch tr \ grep sed cut \ clevis-decrypt \ clevis-luks-list \ From da95e27f635202b6321486a04e67efbd85b023c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Sat, 22 Jun 2024 13:13:28 +0200 Subject: [PATCH 05/16] luks: support printing the null pin configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is useful during testing. Signed-off-by: Oldřich Jedlička --- src/luks/clevis-luks-common-functions.in | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/luks/clevis-luks-common-functions.in b/src/luks/clevis-luks-common-functions.in index f28a0fc5..0459fd62 100644 --- a/src/luks/clevis-luks-common-functions.in +++ b/src/luks/clevis-luks-common-functions.in @@ -177,6 +177,9 @@ clevis_luks_print_pin_config() { local pin= case "${P}" in + null) + printf "null '{}'" + ;; pkcs11) local uri uri="$(jose fmt -j- -g uri -u- <<< "${content}")" @@ -252,6 +255,7 @@ clevis_luks_process_sss_pin() { local jwe="${1}" local threshold="${2}" + local sss_null local sss_pkcs11 local sss_tang local sss_tpm2 @@ -267,6 +271,9 @@ clevis_luks_process_sss_pin() { fi read -r pin cfg <<< "${pin_cfg}" case "${pin}" in + null) + sss_null="${sss_null},${cfg}" + ;; pkcs11) sss_pkcs11="${sss_pkcs11},${cfg}" ;; @@ -283,6 +290,10 @@ clevis_luks_process_sss_pin() { done cfg= + if [ -n "${sss_null}" ]; then + cfg=$(clevis_luks_join_sss_cfg "null" "${sss_null}") + fi + if [ -n "${sss_tang}" ]; then cfg=$(clevis_luks_join_sss_cfg "tang" "${sss_tang}") fi From a44633ba24ef37c59472a98ec791110bd0f089b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Mon, 30 Sep 2024 22:02:22 +0200 Subject: [PATCH 06/16] tests: introduce common test functions shared between tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- .github/workflows/install-dependencies | 2 +- src/luks/tests/assume-yes | 2 +- src/luks/tests/assume-yes-luks2 | 2 +- src/luks/tests/backup-restore-luks1 | 2 +- src/luks/tests/backup-restore-luks2 | 2 +- src/luks/tests/bad-sss | 2 +- .../tests/bind-already-used-luksmeta-slot | 2 +- src/luks/tests/bind-binary-keyfile-luks1 | 2 +- .../tests/bind-key-file-non-interactive-luks1 | 2 +- src/luks/tests/bind-luks1 | 2 +- .../bind-luks1-avoid-luksmeta-corruption | 2 +- src/luks/tests/bind-luks2 | 2 +- src/luks/tests/bind-luks2-ext-token | 2 +- .../bind-pass-with-newline-keyfile-luks1 | 2 +- src/luks/tests/bind-pass-with-newline-luks1 | 2 +- src/luks/tests/bind-wrong-pass-luks1 | 2 +- src/luks/tests/bind-wrong-pass-luks2 | 2 +- src/luks/tests/edit-tang-luks1 | 2 +- src/luks/tests/edit-tang-luks2 | 2 +- src/luks/tests/list-recursive-luks1 | 2 +- src/luks/tests/list-recursive-luks2 | 2 +- src/luks/tests/list-sss-tang-luks1 | 2 +- src/luks/tests/list-sss-tang-luks2 | 2 +- src/luks/tests/list-tang-luks1 | 2 +- src/luks/tests/list-tang-luks2 | 2 +- ...tions.in => luks-common-test-functions.in} | 13 +--- src/luks/tests/meson.build | 4 +- src/luks/tests/pass-tang-luks1 | 2 +- src/luks/tests/pass-tang-luks2 | 2 +- src/luks/tests/regen-inplace-luks1 | 2 +- src/luks/tests/regen-inplace-luks2 | 2 +- src/luks/tests/regen-not-inplace-luks1 | 2 +- src/luks/tests/regen-not-inplace-luks2 | 2 +- src/luks/tests/report-sss-luks1 | 2 +- src/luks/tests/report-sss-luks2 | 2 +- src/luks/tests/report-tang-luks1 | 2 +- src/luks/tests/report-tang-luks2 | 2 +- src/luks/tests/unbind-luks1 | 2 +- src/luks/tests/unbind-luks2 | 2 +- src/luks/tests/unbind-unbound-slot-luks1 | 2 +- src/luks/tests/unbind-unbound-slot-luks2 | 2 +- src/luks/tests/unlock-arbitrary-parameter | 2 +- src/luks/tests/unlock-tang-luks1 | 2 +- src/luks/tests/unlock-tang-luks2 | 2 +- src/pins/pkcs11/tests/pin-pkcs11 | 5 +- src/pins/tang/tests/default-thp-alg | 14 ++-- src/pins/tang/tests/meson.build | 1 + .../tang/tests/tang-common-test-functions.in | 71 ++++--------------- src/pins/tang/tests/tang-validate-adv | 2 +- src/tests-common-functions | 65 +++++++++++++++++ 50 files changed, 138 insertions(+), 121 deletions(-) rename src/luks/tests/{tests-common-functions.in => luks-common-test-functions.in} (98%) create mode 100644 src/tests-common-functions diff --git a/.github/workflows/install-dependencies b/.github/workflows/install-dependencies index 4347a110..474e7803 100755 --- a/.github/workflows/install-dependencies +++ b/.github/workflows/install-dependencies @@ -1,6 +1,6 @@ #!/bin/bash -ex -COMMON="meson curl git make file bzip2 jose tang cryptsetup keyutils jq socat lsof ${CC}" +COMMON="meson curl git make file bzip2 jose tang cryptsetup keyutils jq socat lsof procps ${CC}" case "${DISTRO}" in debian:*|ubuntu:*) diff --git a/src/luks/tests/assume-yes b/src/luks/tests/assume-yes index 7995a1ed..1ed5b0bd 100755 --- a/src/luks/tests/assume-yes +++ b/src/luks/tests/assume-yes @@ -18,7 +18,7 @@ # along with this program. If not, see . TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions diff --git a/src/luks/tests/assume-yes-luks2 b/src/luks/tests/assume-yes-luks2 index dbfff777..7d9842c5 100755 --- a/src/luks/tests/assume-yes-luks2 +++ b/src/luks/tests/assume-yes-luks2 @@ -18,7 +18,7 @@ # along with this program. If not, see . TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions diff --git a/src/luks/tests/backup-restore-luks1 b/src/luks/tests/backup-restore-luks1 index 90a26c74..5a5441ec 100755 --- a/src/luks/tests/backup-restore-luks1 +++ b/src/luks/tests/backup-restore-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions on_exit() { diff --git a/src/luks/tests/backup-restore-luks2 b/src/luks/tests/backup-restore-luks2 index 0f43ace3..d9172d9b 100755 --- a/src/luks/tests/backup-restore-luks2 +++ b/src/luks/tests/backup-restore-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions on_exit() { diff --git a/src/luks/tests/bad-sss b/src/luks/tests/bad-sss index 39372b63..54d11eb5 100755 --- a/src/luks/tests/bad-sss +++ b/src/luks/tests/bad-sss @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-already-used-luksmeta-slot b/src/luks/tests/bind-already-used-luksmeta-slot index 71600066..c3245ba6 100755 --- a/src/luks/tests/bind-already-used-luksmeta-slot +++ b/src/luks/tests/bind-already-used-luksmeta-slot @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-binary-keyfile-luks1 b/src/luks/tests/bind-binary-keyfile-luks1 index 800e0c0d..518b2c2e 100755 --- a/src/luks/tests/bind-binary-keyfile-luks1 +++ b/src/luks/tests/bind-binary-keyfile-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions on_exit() { diff --git a/src/luks/tests/bind-key-file-non-interactive-luks1 b/src/luks/tests/bind-key-file-non-interactive-luks1 index eb563a72..236e3320 100755 --- a/src/luks/tests/bind-key-file-non-interactive-luks1 +++ b/src/luks/tests/bind-key-file-non-interactive-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-luks1 b/src/luks/tests/bind-luks1 index 0eae1be7..0d3ea00b 100755 --- a/src/luks/tests/bind-luks1 +++ b/src/luks/tests/bind-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-luks1-avoid-luksmeta-corruption b/src/luks/tests/bind-luks1-avoid-luksmeta-corruption index c16885b0..ce6a2456 100755 --- a/src/luks/tests/bind-luks1-avoid-luksmeta-corruption +++ b/src/luks/tests/bind-luks1-avoid-luksmeta-corruption @@ -18,7 +18,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ ! -d "${TMP}" ] && return 0 diff --git a/src/luks/tests/bind-luks2 b/src/luks/tests/bind-luks2 index 1965b00e..81a48fdd 100755 --- a/src/luks/tests/bind-luks2 +++ b/src/luks/tests/bind-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-luks2-ext-token b/src/luks/tests/bind-luks2-ext-token index 3dadb602..114be310 100755 --- a/src/luks/tests/bind-luks2-ext-token +++ b/src/luks/tests/bind-luks2-ext-token @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-pass-with-newline-keyfile-luks1 b/src/luks/tests/bind-pass-with-newline-keyfile-luks1 index 7e05be8b..f5eebd45 100755 --- a/src/luks/tests/bind-pass-with-newline-keyfile-luks1 +++ b/src/luks/tests/bind-pass-with-newline-keyfile-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-pass-with-newline-luks1 b/src/luks/tests/bind-pass-with-newline-luks1 index d5813887..97e00f4d 100755 --- a/src/luks/tests/bind-pass-with-newline-luks1 +++ b/src/luks/tests/bind-pass-with-newline-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-wrong-pass-luks1 b/src/luks/tests/bind-wrong-pass-luks1 index b9271ba2..a7c32570 100755 --- a/src/luks/tests/bind-wrong-pass-luks1 +++ b/src/luks/tests/bind-wrong-pass-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/bind-wrong-pass-luks2 b/src/luks/tests/bind-wrong-pass-luks2 index d234e9ad..b34c353b 100755 --- a/src/luks/tests/bind-wrong-pass-luks2 +++ b/src/luks/tests/bind-wrong-pass-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/edit-tang-luks1 b/src/luks/tests/edit-tang-luks1 index 819055b4..22a7b6e2 100755 --- a/src/luks/tests/edit-tang-luks1 +++ b/src/luks/tests/edit-tang-luks1 @@ -18,7 +18,7 @@ # along with this program. If not, see . TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions diff --git a/src/luks/tests/edit-tang-luks2 b/src/luks/tests/edit-tang-luks2 index 12f0311d..70bb8fb7 100755 --- a/src/luks/tests/edit-tang-luks2 +++ b/src/luks/tests/edit-tang-luks2 @@ -18,7 +18,7 @@ # along with this program. If not, see . TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions diff --git a/src/luks/tests/list-recursive-luks1 b/src/luks/tests/list-recursive-luks1 index 9df3a47c..2787cc84 100755 --- a/src/luks/tests/list-recursive-luks1 +++ b/src/luks/tests/list-recursive-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/list-recursive-luks2 b/src/luks/tests/list-recursive-luks2 index 871c60ba..ec9bd1c3 100755 --- a/src/luks/tests/list-recursive-luks2 +++ b/src/luks/tests/list-recursive-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/list-sss-tang-luks1 b/src/luks/tests/list-sss-tang-luks1 index b3bb5236..bc4f3d4e 100755 --- a/src/luks/tests/list-sss-tang-luks1 +++ b/src/luks/tests/list-sss-tang-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/list-sss-tang-luks2 b/src/luks/tests/list-sss-tang-luks2 index aec92d19..5f056965 100755 --- a/src/luks/tests/list-sss-tang-luks2 +++ b/src/luks/tests/list-sss-tang-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/list-tang-luks1 b/src/luks/tests/list-tang-luks1 index 653acce2..e0819ad5 100755 --- a/src/luks/tests/list-tang-luks1 +++ b/src/luks/tests/list-tang-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/list-tang-luks2 b/src/luks/tests/list-tang-luks2 index ee91fd2b..c5b4feeb 100755 --- a/src/luks/tests/list-tang-luks2 +++ b/src/luks/tests/list-tang-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/tests-common-functions.in b/src/luks/tests/luks-common-test-functions.in similarity index 98% rename from src/luks/tests/tests-common-functions.in rename to src/luks/tests/luks-common-test-functions.in index bfd75474..bc24b0bb 100755 --- a/src/luks/tests/tests-common-functions.in +++ b/src/luks/tests/luks-common-test-functions.in @@ -18,27 +18,20 @@ # along with this program. If not, see . # +. tests-common-functions . tang-common-test-functions -error() { - echo "${1}" >&2 - exit 1 -} - -skip_test() { - echo "${1}" >&2 - exit 77 -} - # We require cryptsetup >= 2.0.4 to fully support LUKSv2. # Support is determined at build time. luks2_supported() { + # shellcheck disable=SC2152 return @OLD_CRYPTSETUP@ } # We require cryptsetup >= 2.6.0 to fully support LUKSv2 addkey/open by token ID # Support is determined at build time. luks2_existing_token_id_supported() { + # shellcheck disable=SC2152 return @OLD_CRYPTSETUP_EXISTING_TOKEN_ID@ } diff --git a/src/luks/tests/meson.build b/src/luks/tests/meson.build index 632fe8d3..23577540 100644 --- a/src/luks/tests/meson.build +++ b/src/luks/tests/meson.build @@ -14,8 +14,8 @@ else warning('keyutils not installed, unable to test existing token id binding') endif -common_functions = configure_file(input: 'tests-common-functions.in', - output: 'tests-common-functions', +common_functions = configure_file(input: 'luks-common-test-functions.in', + output: 'luks-common-test-functions', configuration: luksmeta_data, install: false ) diff --git a/src/luks/tests/pass-tang-luks1 b/src/luks/tests/pass-tang-luks1 index 2d69e3cf..85609626 100755 --- a/src/luks/tests/pass-tang-luks1 +++ b/src/luks/tests/pass-tang-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions on_exit() { diff --git a/src/luks/tests/pass-tang-luks2 b/src/luks/tests/pass-tang-luks2 index 0861ff32..5929207a 100755 --- a/src/luks/tests/pass-tang-luks2 +++ b/src/luks/tests/pass-tang-luks2 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions on_exit() { diff --git a/src/luks/tests/regen-inplace-luks1 b/src/luks/tests/regen-inplace-luks1 index 631551f5..0311999e 100755 --- a/src/luks/tests/regen-inplace-luks1 +++ b/src/luks/tests/regen-inplace-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/regen-inplace-luks2 b/src/luks/tests/regen-inplace-luks2 index b9759b7b..4c716e57 100755 --- a/src/luks/tests/regen-inplace-luks2 +++ b/src/luks/tests/regen-inplace-luks2 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/regen-not-inplace-luks1 b/src/luks/tests/regen-not-inplace-luks1 index a5b34fa5..e4e14860 100755 --- a/src/luks/tests/regen-not-inplace-luks1 +++ b/src/luks/tests/regen-not-inplace-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/regen-not-inplace-luks2 b/src/luks/tests/regen-not-inplace-luks2 index 77cf4576..5b2282c1 100755 --- a/src/luks/tests/regen-not-inplace-luks2 +++ b/src/luks/tests/regen-not-inplace-luks2 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/report-sss-luks1 b/src/luks/tests/report-sss-luks1 index 6db5053a..09457dc0 100755 --- a/src/luks/tests/report-sss-luks1 +++ b/src/luks/tests/report-sss-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/report-sss-luks2 b/src/luks/tests/report-sss-luks2 index 37bba52d..949f0d05 100755 --- a/src/luks/tests/report-sss-luks2 +++ b/src/luks/tests/report-sss-luks2 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/report-tang-luks1 b/src/luks/tests/report-tang-luks1 index b90e32af..5dcdef75 100755 --- a/src/luks/tests/report-tang-luks1 +++ b/src/luks/tests/report-tang-luks1 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/report-tang-luks2 b/src/luks/tests/report-tang-luks2 index 92a55111..68f2e332 100755 --- a/src/luks/tests/report-tang-luks2 +++ b/src/luks/tests/report-tang-luks2 @@ -19,7 +19,7 @@ # TEST="${0}" -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions function on_exit() { diff --git a/src/luks/tests/unbind-luks1 b/src/luks/tests/unbind-luks1 index a50e4cc2..1964a5de 100755 --- a/src/luks/tests/unbind-luks1 +++ b/src/luks/tests/unbind-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/unbind-luks2 b/src/luks/tests/unbind-luks2 index 1af5a71f..80ef6463 100755 --- a/src/luks/tests/unbind-luks2 +++ b/src/luks/tests/unbind-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/unbind-unbound-slot-luks1 b/src/luks/tests/unbind-unbound-slot-luks1 index e562a36b..9b7b3443 100755 --- a/src/luks/tests/unbind-unbound-slot-luks1 +++ b/src/luks/tests/unbind-unbound-slot-luks1 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/unbind-unbound-slot-luks2 b/src/luks/tests/unbind-unbound-slot-luks2 index 13642137..69c8f582 100755 --- a/src/luks/tests/unbind-unbound-slot-luks2 +++ b/src/luks/tests/unbind-unbound-slot-luks2 @@ -19,7 +19,7 @@ # TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions on_exit() { [ -d "${TMP}" ] && rm -rf "${TMP}" diff --git a/src/luks/tests/unlock-arbitrary-parameter b/src/luks/tests/unlock-arbitrary-parameter index 0a3f9d1a..6356efec 100755 --- a/src/luks/tests/unlock-arbitrary-parameter +++ b/src/luks/tests/unlock-arbitrary-parameter @@ -15,7 +15,7 @@ # along with this program. If not, see . TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions diff --git a/src/luks/tests/unlock-tang-luks1 b/src/luks/tests/unlock-tang-luks1 index e45000fe..2076c109 100755 --- a/src/luks/tests/unlock-tang-luks1 +++ b/src/luks/tests/unlock-tang-luks1 @@ -18,7 +18,7 @@ # along with this program. If not, see . TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions diff --git a/src/luks/tests/unlock-tang-luks2 b/src/luks/tests/unlock-tang-luks2 index 187c5bca..a582d2fc 100755 --- a/src/luks/tests/unlock-tang-luks2 +++ b/src/luks/tests/unlock-tang-luks2 @@ -18,7 +18,7 @@ # along with this program. If not, see . TEST=$(basename "${0}") -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions diff --git a/src/pins/pkcs11/tests/pin-pkcs11 b/src/pins/pkcs11/tests/pin-pkcs11 index c876ca4f..5d9d8974 100755 --- a/src/pins/pkcs11/tests/pin-pkcs11 +++ b/src/pins/pkcs11/tests/pin-pkcs11 @@ -18,7 +18,7 @@ # # shellcheck disable=SC1091 . pkcs11-common-tests -. tests-common-functions +. luks-common-test-functions . clevis-luks-common-functions . clevis-pkcs11-common @@ -29,8 +29,7 @@ on_exit() { } if [[ ! -f "${P11LIB}" ]]; then - echo "WARNING: The SoftHSM is not installed. Can not run this test" - exit 77; + skip_test "WARNING: The SoftHSM is not installed. Can not run this test" fi trap 'on_exit' EXIT diff --git a/src/pins/tang/tests/default-thp-alg b/src/pins/tang/tests/default-thp-alg index 859f4d0f..1e56d2dd 100755 --- a/src/pins/tang/tests/default-thp-alg +++ b/src/pins/tang/tests/default-thp-alg @@ -70,11 +70,11 @@ for alg in ${CLEVIS_SUPPORTED_THP_ALGS}; do | jose jwe enc --input="${jwe}" --key=- --detached=- --compact) if ! decoded="$(printf '%s' "${encoded}" | clevis decrypt)"; then - tang_error "${TEST}: decoding is expected to work (alg = ${alg})" + error "${TEST}: decoding is expected to work (alg = ${alg})" fi if [ "${decoded}" != "${data}" ]; then - tang_error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})" + error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})" fi done @@ -86,15 +86,15 @@ for alg in ${CLEVIS_SUPPORTED_THP_ALGS}; do | jose jwk thp -i- -a "${alg}")" cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")" if ! encoded=$(printf '%s' "${data}" | clevis encrypt tang "${cfg}"); then - tang_error "${TEST}: tang encryption should have succeeded when providing the thp (${thp}) with any supported algorithm (${alg})" + error "${TEST}: tang encryption should have succeeded when providing the thp (${thp}) with any supported algorithm (${alg})" fi if ! decoded="$(printf '%s' "${encoded}" | clevis decrypt)"; then - tang_error "${TEST}: decoding is expected to work (thp alg = ${alg})" + error "${TEST}: decoding is expected to work (thp alg = ${alg})" fi if [ "${decoded}" != "${data}" ]; then - tang_error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})" + error "${TEST}: tang decrypt should have succeeded decoded[${decoded}] data[${data}] (alg = ${alg})" fi done @@ -107,7 +107,7 @@ for alg in ${UNSUPPORTED}; do | jose jwk thp -i- -a "${alg}")" cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")" if echo foo | clevis encrypt tang "${cfg}" >/dev/null; then - tang_error "${TEST}: tang encryption should have failed when providing the thp (${thp}) with an unsupported algorithm (${alg})" + error "${TEST}: tang encryption should have failed when providing the thp (${thp}) with an unsupported algorithm (${alg})" fi done @@ -115,6 +115,6 @@ done for thp in "" "foo" "invalid"; do cfg="$(printf '{"url":"%s", "thp":"%s"}' "${url}" "${thp}")" if echo foo | clevis encrypt tang "${cfg}" >/dev/null; then - tang_error "${TEST}: tang encryption expected to fail when providing a bad thp" + error "${TEST}: tang encryption expected to fail when providing a bad thp" fi done diff --git a/src/pins/tang/tests/meson.build b/src/pins/tang/tests/meson.build index af3946c3..47fa5d69 100644 --- a/src/pins/tang/tests/meson.build +++ b/src/pins/tang/tests/meson.build @@ -44,6 +44,7 @@ env = environment() env.prepend('PATH', join_paths(meson.source_root(), 'src'), join_paths(meson.source_root(), 'src', 'pins', 'tang'), + join_paths(meson.build_root(), 'src', 'luks', 'tests'), join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'), separator: ':' ) diff --git a/src/pins/tang/tests/tang-common-test-functions.in b/src/pins/tang/tests/tang-common-test-functions.in index 3d13194a..b3ce33f4 100644 --- a/src/pins/tang/tests/tang-common-test-functions.in +++ b/src/pins/tang/tests/tang-common-test-functions.in @@ -18,24 +18,16 @@ # along with this program. If not, see . # +. tests-common-functions + SOCAT="@SOCAT@" TANGD_KEYGEN="@TANGD_KEYGEN@" TANGD="@TANGD@" -tang_error() { - echo "${1}" >&2 - exit 1 -} - -tang_skip() { - echo "${1}" >&2 - exit 77 -} - tang_sanity_check() { [ -n "${SOCAT}" ] && [ -n "${TANGD_KEYGEN}" ] && \ [ -n "${TANGD}" ] && return 0 - tang_skip "tang is not enabled/supported. Check if you have met all the requirements" + skip_test "tang is not enabled/supported. Check if you have met all the requirements" } # Creates a tang adv to be used in the tests. @@ -61,7 +53,7 @@ tang_remove_rotated_keys() { local basedir="${1}" [ -z "${basedir}" ] && \ - tang_error "tang_remove_rotated_keys: please specify 'basedir'" + error "tang_remove_rotated_keys: please specify 'basedir'" local db="${basedir}/db" @@ -80,7 +72,7 @@ tang_new_keys() { local sig_name="${3:-}" local exc_name="${4:-}" - [ -z "${basedir}" ] && tang_error "tang_new_keys: please specify 'basedir'" + [ -z "${basedir}" ] && error "tang_new_keys: please specify 'basedir'" local db="${basedir}/db" mkdir -p "${db}" @@ -102,39 +94,6 @@ tang_new_keys() { return 0 } -# Find listening port of a process -tang_find_port() { - local pid="${1}" - - [ -z "${pid}" ] && \ - tang_error "tang_find_port: please specify 'pid'" - - local port - port=$(lsof -Pan -p "${pid}" -iTCP -sTCP:LISTEN -Fn | grep '^n.*:' | cut -d: -f2) - [ -n "${port}" ] && echo "${port}" -} - -# Wait for the tang server to be operational. -tang_wait_until_ready() { - tang_sanity_check - local pid="${1}" - - [ -z "${pid}" ] && \ - tang_error "tang_wait_until_ready: please specify 'pid'" - - local max_timeout_in_s=5 - local start elapsed - start="${SECONDS}" - while ! tang_find_port "${pid}" >/dev/null; do - elapsed=$((SECONDS - start)) - if [ "${elapsed}" -gt "${max_timeout_in_s}" ]; then - tang_error "Timeout (${max_timeout_in_s}s) waiting for tang server" - fi - sleep 0.1 - echo -n . >&2 - done -} - # Start a test tang server. tang_run() { tang_sanity_check @@ -142,10 +101,10 @@ tang_run() { local sig_name="${2:-}" local exc_name="${3:-}" - [ -z "${basedir}" ] && tang_error "tang_run: please specify 'basedir'" + [ -z "${basedir}" ] && error "tang_run: please specify 'basedir'" if ! tang_new_keys "${basedir}" "" "${sig_name}" "${exc_name}"; then - tang_error "Error creating new keys for tang server" + error "Error creating new keys for tang server" fi local KEYS="${basedir}/db" @@ -159,15 +118,15 @@ tang_run() { pid=$! echo "${pid}" > "${pidfile}" - tang_wait_until_ready "${pid}" - tang_find_port "${pid}" > "${portfile}" + process_wait_until_port_ready "${pid}" + process_find_port "${pid}" "tang" > "${portfile}" } # Stop tang server. tang_stop() { tang_sanity_check local basedir="${1}" - [ -z "${basedir}" ] && tang_error "tang_stop: please specify 'basedir'" + [ -z "${basedir}" ] && error "tang_stop: please specify 'basedir'" local pidfile="${basedir}/tang.pid" [ -f "${pidfile}" ] || return 0 @@ -183,10 +142,10 @@ tang_stop() { tang_get_port() { local basedir="${1}" - [ -z "${basedir}" ] && tang_error "tang_get_port: please specify 'basedir'" + [ -z "${basedir}" ] && error "tang_get_port: please specify 'basedir'" local portfile="${basedir}/tang.port" - [ -f "${portfile}" ] || tang_error "tang_get_port: tang is not running" + [ -f "${portfile}" ] || error "tang_get_port: tang is not running" cat "${portfile}" } @@ -197,7 +156,7 @@ tang_get_adv() { local port="${1}" local adv="${2:-/dev/stdout}" - [ -z "${port}" ] && tang_error "tang_get_adv: please specify 'port'" + [ -z "${port}" ] && error "tang_get_adv: please specify 'port'" curl -L -o "${adv}" "http://localhost:${port}/adv" } @@ -217,6 +176,6 @@ run_test_server() { pid=$! echo "${pid}" > "${pidfile}" - tang_wait_until_ready "${pid}" - tang_find_port "${pid}" > "${portfile}" + process_wait_until_port_ready "${pid}" + process_find_port "${pid}" "tang" > "${portfile}" } diff --git a/src/pins/tang/tests/tang-validate-adv b/src/pins/tang/tests/tang-validate-adv index 5c3ea418..045a7b14 100755 --- a/src/pins/tang/tests/tang-validate-adv +++ b/src/pins/tang/tests/tang-validate-adv @@ -60,7 +60,7 @@ do_test_with_adv() { validate_output() { local output="${1}" if grep -Fq jose "${output}"; then - tang_error "'jose' is not expected to appear in the error output" + error "'jose' is not expected to appear in the error output" fi } diff --git a/src/tests-common-functions b/src/tests-common-functions new file mode 100644 index 00000000..d5ef8d34 --- /dev/null +++ b/src/tests-common-functions @@ -0,0 +1,65 @@ +#!/bin/bash -ex +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2019 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 . +# + +error() { + echo "$@" >&2 + exit 1 +} + +skip_test() { + local message="$*" + [ -n "$message" ] && echo "$message" >&2 + exit 77 +} + +# Find listening port of a process +process_find_port() { + local pid="${1}" + + [ -z "${pid}" ] && \ + error "process_find_port: please specify 'pid'" + + local port + port=$(lsof -Pan -p "${pid}" -iTCP -sTCP:LISTEN -Fn | grep '^n.*:' | head -n1 | cut -d: -f2) + [ -n "${port}" ] && echo "${port}" +} + +# Wait for the server to be operational. +process_wait_until_port_ready() { + local pid="${1}" + local name="${2}" + + [ -z "${pid}" ] && \ + error "process_wait_until_port_ready: please specify 'pid'" + + local max_timeout_in_s=5 + local start elapsed + start="${SECONDS}" + while ! process_find_port "${pid}" >/dev/null; do + elapsed=$((SECONDS - start)) + if ! ps -A -o pid | awk -v pid="${pid}" '$1==pid {found=1} END {exit !found}'; then + error "Failed waiting, ${name:process} terminated" + elif [ "${elapsed}" -gt "${max_timeout_in_s}" ]; then + error "Timeout (${max_timeout_in_s}s) waiting for ${name:process}" + fi + sleep 0.1 + echo -n . >&2 + done +} From ac6a8f5cf4b0f7bfcd1ea24e243456f2cd747783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Fri, 2 Feb 2024 00:15:56 +0100 Subject: [PATCH 07/16] tpm1: added encrypt and decrypt support for TPM 1.2 as tpm1 pin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- src/luks/clevis-luks-common-functions.in | 13 +++ src/pins/meson.build | 1 + src/pins/tpm1/clevis-decrypt-tpm1 | 72 ++++++++++++ src/pins/tpm1/clevis-encrypt-tpm1 | 137 +++++++++++++++++++++++ src/pins/tpm1/meson.build | 13 +++ 5 files changed, 236 insertions(+) create mode 100755 src/pins/tpm1/clevis-decrypt-tpm1 create mode 100755 src/pins/tpm1/clevis-encrypt-tpm1 create mode 100644 src/pins/tpm1/meson.build diff --git a/src/luks/clevis-luks-common-functions.in b/src/luks/clevis-luks-common-functions.in index 0459fd62..d78f1f94 100644 --- a/src/luks/clevis-luks-common-functions.in +++ b/src/luks/clevis-luks-common-functions.in @@ -202,6 +202,11 @@ clevis_luks_print_pin_config() { pin=$(printf '{"url":"%s"}' "${url}") printf "tang '%s'" "${pin}" ;; + tpm1) + pcr_ids="$(jose fmt -j- -g pcr_ids -u- <<< "${content}")" + pin=$(printf '"pcr_ids":"%s"' "${pcr_ids}") + printf "tpm1 '{%s}'" "${pin}" + ;; tpm2) # Valid properties for tpm2 pin are the following: # hash, key, pcr_bank, pcr_ids, pcr_digest. @@ -258,6 +263,7 @@ clevis_luks_process_sss_pin() { local sss_null local sss_pkcs11 local sss_tang + local sss_tpm1 local sss_tpm2 local sss local pin_cfg @@ -280,6 +286,9 @@ clevis_luks_process_sss_pin() { tang) sss_tang="${sss_tang},${cfg}" ;; + tpm1) + sss_tpm1="${sss_tpm1},${cfg}" + ;; tpm2) sss_tpm2="${sss_tpm2},${cfg}" ;; @@ -298,6 +307,10 @@ clevis_luks_process_sss_pin() { cfg=$(clevis_luks_join_sss_cfg "tang" "${sss_tang}") fi + if [ -n "${sss_tpm1}" ]; then + cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm1" "${sss_tpm1}") + fi + if [ -n "${sss_tpm2}" ]; then cfg="${cfg},"$(clevis_luks_join_sss_cfg "tpm2" "${sss_tpm2}") fi diff --git a/src/pins/meson.build b/src/pins/meson.build index a115e1eb..ad822fd2 100644 --- a/src/pins/meson.build +++ b/src/pins/meson.build @@ -1,4 +1,5 @@ subdir('sss') subdir('tang') +subdir('tpm1') subdir('tpm2') subdir('pkcs11') diff --git a/src/pins/tpm1/clevis-decrypt-tpm1 b/src/pins/tpm1/clevis-decrypt-tpm1 new file mode 100755 index 00000000..a4a1b962 --- /dev/null +++ b/src/pins/tpm1/clevis-decrypt-tpm1 @@ -0,0 +1,72 @@ +#!/bin/bash -e +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +[ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 + +if [ -t 0 ]; then + exec >&2 + echo + echo "Usage: clevis decrypt tpm1 < JWE > PLAINTEXT" + echo + exit 2 +fi + +tpm_version_bin="$(command -v tpm_version || echo /usr/sbin/tpm_version)" +if ! "$tpm_version_bin" >/dev/null 2>&1; then + # The tpm_version outputs garbage to stdout on success, so let the + # tpm_version output the error again cleanly now + echo "The tpm1 pin requires tcsd daemon (trousers) running:" >&2 + if [ -x "$tpm_version_bin" ]; then + ( "$tpm_version_bin" 2>&1 | tr '\0' ' ' ) >&2 + else + echo Cannot check, tpm_version from tpm-tools not found >&2 + fi + exit 1 +fi + +read -r -d . hdr + +if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then + echo "Error decoding JWE protected header!" >&2 + exit 1 +fi + +if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "tpm1" ]; then + echo "JWE pin mismatch!" >&2 + exit 1 +fi + +if ! jwk_b64="$(jose fmt -j- -Og clevis -g tpm1 -g jwk -Su- <<< "$jhd")"; then + echo "JWE missing required 'jwk' header parameter!" >&2 + exit 1 +fi + +if ! jwk_sealed="$(jose b64 dec -i- <<< "$jwk_b64")"; then + echo "Decoding jwk from Base64 failed!" >&2 + exit 1 +fi + +if ! jwk="$(tpm_unsealdata -i /dev/stdin -z <<< "$jwk_sealed")"; then + echo "Unable to unseal jwk!" >&2 + exit 1 +fi + +exec jose jwe dec -k- -i- < <(echo -n "$jwk$hdr."; /bin/cat) diff --git a/src/pins/tpm1/clevis-encrypt-tpm1 b/src/pins/tpm1/clevis-encrypt-tpm1 new file mode 100755 index 00000000..5cf4f16b --- /dev/null +++ b/src/pins/tpm1/clevis-encrypt-tpm1 @@ -0,0 +1,137 @@ +#!/bin/bash -e +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +SUMMARY="Encrypts using a TPM1.2 chip binding policy" + +if [ "$1" == "--summary" ]; then + echo "$SUMMARY" + exit 0 +fi + +if [ -t 0 ]; then + exec >&2 + echo + echo "Usage: clevis encrypt tpm1 CONFIG < PLAINTEXT > JWE" + echo + echo "$SUMMARY" + echo + echo "This command uses the following configuration properties:" + echo + echo " pcr_ids: PCR list used for policy. If not present, no policy is used" + echo + exit 2 +fi + +validate_pcrs() { + local _pcr_bank="${1}" + local _pcrs="${2}" + local _pcr + [ -z "${_pcr_bank}" ] && return 1 + [ -z "${_pcrs}" ] && return 0 + + if [ -z "$TSS_TCSD_PORT" ]; then + for _pcr in ${_pcrs//,/ }; do + [ -f "/sys/class/tpm/tpm0/pcr-${_pcr_bank}/${_pcr}" ] || return 1 + done + else + [ "${_pcr_bank}" = "sha1" ] && { : | tpm_sealdata -z${_pcrs//,/ -p } >/dev/null 2>/dev/null; } + fi + + return 0 +} + +tpm_version_bin="$(command -v tpm_version || echo /usr/sbin/tpm_version)" +if ! "$tpm_version_bin" >/dev/null 2>&1; then + # The tpm_version outputs garbage to stdout on success, so let the + # tpm_version output the error again cleanly now + echo "The tpm1 pin requires tcsd daemon (trousers) running:" >&2 + if [ -x "$tpm_version_bin" ]; then + ( "$tpm_version_bin" 2>&1 | tr '\0' ' ' ) >&2 + else + echo Cannot check, tpm_version from tpm-tools not found >&2 + fi + exit 1 +fi + +if ! cfg="$(jose fmt -j "$1" -Oo- 2>/dev/null)"; then + echo "Configuration is malformed!" >&2 + exit 1 +fi + +# TPM1.1 and TPM1.2 has only sha1 +pcr_bank="sha1" + +# Trim the spaces from the config, so that we will not have issues parsing +# the PCR IDs. +pcr_cfg=${cfg//[[:space:]]/} +# Issue #103: We support passing pcr_ids using both a single string, as in +# "1,3", as well as an actual JSON array, such as ["1","3"]. Let's handle both +# cases here. +if jose fmt -j- -Og pcr_ids 2>/dev/null <<< "${pcr_cfg}" \ + && ! pcr_ids="$(jose fmt -j- -Og pcr_ids -u- 2>/dev/null \ + <<< "${pcr_cfg}")"; then + + # We failed to parse a string, so let's try to parse a JSON array instead. + if jose fmt -j- -Og pcr_ids -A 2>/dev/null <<< "${pcr_cfg}"; then + # OK, it is an array, so let's get the items and form a string. + pcr_ids= + for pcr in $(jose fmt -j- -Og pcr_ids -Af- <<< "${pcr_cfg}" \ + | tr -d '"'); do + pcr_ids=$(printf '%s,%s' "${pcr_ids}" "${pcr}") + done + # Now let's remove the leading comma. + pcr_ids=${pcr_ids/#,/} + else + # Not to add a policy that was not intended, in this case, no policy + # at all, let's report the issue and exit. + echo "Parsing the requested PCRs failed!" >&2 + exit 1 + fi +fi + +if ! validate_pcrs "${pcr_bank}" "${pcr_ids}"; then + echo "Unable to validate combination of PCR bank '${pcr_bank}' and PCR IDs '${pcr_ids}'." >&2 + exit 1 +fi + +if ! jwk="$(jose jwk gen -i '{"alg":"A256GCM"}')"; then + echo "Generating a jwk failed!" >&2 + exit 1 +fi + +pcr_args="${pcr_ids:+-p}${pcr_ids//,/ -p}" +if ! jwk_sealed=$(tpm_sealdata -i /dev/stdin -o /dev/stdout ${pcr_args} -z <<< "$jwk"); then + echo "Unable to seal jwk" >&2 + exit 1 +fi + +if ! jwk_b64="$(jose b64 enc -I- <<< "$jwk_sealed")"; then + echo "Encoding sealed jwk in Base64 failed!" >&2 + exit 1 +fi + +jwe='{"protected":{"clevis":{"pin":"tpm1","tpm1":{}}}}' +if [ -n "$pcr_ids" ]; then + jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm1 -q "$pcr_ids" -s pcr_ids -UUUUo-)" +fi +jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tpm1 -q "$jwk_b64" -s jwk -UUUUo-)" + +exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) diff --git a/src/pins/tpm1/meson.build b/src/pins/tpm1/meson.build new file mode 100644 index 00000000..3c1f7d2f --- /dev/null +++ b/src/pins/tpm1/meson.build @@ -0,0 +1,13 @@ +cmds = ['tpm_sealdata', 'tpm_unsealdata'] + +all = true +foreach cmd : cmds + all = all and find_program(cmd, required: false).found() +endforeach + +if all + bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm1') + bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1') +else + warning('Will not install tpm1 pin due to missing dependencies!') +endif From 34d0a81a471ea72674ef073d9ae24340ff6d974b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Sat, 3 Feb 2024 23:38:31 +0100 Subject: [PATCH 08/16] tpm1: added TPM 1.2 (tpm1 pin) tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- src/luks/tests/meson.build | 2 + src/pins/tpm1/meson.build | 1 + src/pins/tpm1/tests/meson.build | 60 +++++++ src/pins/tpm1/tests/pin-tpm1-hw | 27 ++++ src/pins/tpm1/tests/pin-tpm1-sw | 28 ++++ src/pins/tpm1/tests/pin-tpm1-tests | 102 ++++++++++++ src/pins/tpm1/tests/tcsd-patch.c.in | 91 +++++++++++ .../tpm1/tests/tpm1-common-test-functions.in | 146 ++++++++++++++++++ 8 files changed, 457 insertions(+) create mode 100644 src/pins/tpm1/tests/meson.build create mode 100755 src/pins/tpm1/tests/pin-tpm1-hw create mode 100755 src/pins/tpm1/tests/pin-tpm1-sw create mode 100755 src/pins/tpm1/tests/pin-tpm1-tests create mode 100644 src/pins/tpm1/tests/tcsd-patch.c.in create mode 100644 src/pins/tpm1/tests/tpm1-common-test-functions.in diff --git a/src/luks/tests/meson.build b/src/luks/tests/meson.build index 23577540..cd217396 100644 --- a/src/luks/tests/meson.build +++ b/src/luks/tests/meson.build @@ -26,6 +26,7 @@ env.prepend('PATH', join_paths(meson.source_root(), 'src', 'luks'), join_paths(meson.source_root(), 'src', 'pins', 'sss'), join_paths(meson.source_root(), 'src', 'pins', 'tang'), + join_paths(meson.source_root(), 'src', 'pins', 'tpm1'), join_paths(meson.source_root(), 'src', 'pins', 'tpm2'), meson.current_source_dir(), meson.current_build_dir(), @@ -34,6 +35,7 @@ env.prepend('PATH', join_paths(meson.build_root(), 'src', 'pins', 'sss'), join_paths(meson.build_root(), 'src', 'pins', 'tang'), join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'), + join_paths(meson.build_root(), 'src', 'pins', 'tpm1'), join_paths(meson.build_root(), 'src', 'pins', 'tpm2'), separator: ':' ) diff --git a/src/pins/tpm1/meson.build b/src/pins/tpm1/meson.build index 3c1f7d2f..70926c4c 100644 --- a/src/pins/tpm1/meson.build +++ b/src/pins/tpm1/meson.build @@ -8,6 +8,7 @@ endforeach if all bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm1') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1') + subdir('tests') else warning('Will not install tpm1 pin due to missing dependencies!') endif diff --git a/src/pins/tpm1/tests/meson.build b/src/pins/tpm1/tests/meson.build new file mode 100644 index 00000000..d7f24642 --- /dev/null +++ b/src/pins/tpm1/tests/meson.build @@ -0,0 +1,60 @@ +# Tests +env = environment() +env.prepend('PATH', + join_paths(meson.source_root(), 'src'), + join_paths(meson.source_root(), 'src', 'pins', 'tpm1'), + join_paths(meson.source_root(), 'src', 'pins', 'tpm1', 'tests'), + join_paths(meson.build_root(), 'src'), + join_paths(meson.build_root(), 'src', 'luks', 'tests'), + join_paths(meson.build_root(), 'src', 'pins', 'tang', 'tests'), + join_paths(meson.build_root(), 'src', 'pins', 'tpm1'), + join_paths(meson.build_root(), 'src', 'pins', 'tpm1', 'tests'), + separator: ':' +) + +tpm1_data = configuration_data() +tpm1_data.merge_from(data) + +tpm_version = find_program('tpm_version', '/usr/sbin/tpm_version', required: false) +tpm_sealdata = find_program('tpm_sealdata', '/usr/sbin/tpm_sealdata', required: false) +tpm_unsealdata = find_program('tpm_unsealdata', '/usr/sbin/tpm_unsealdata', required: false) +tpm_takeownership = find_program('tpm_takeownership', '/usr/sbin/tpm_takeownership', required: false) +tcsd = find_program('tcsd', '/usr/sbin/tcsd', required: false) +swtpm = find_program('swtpm', '/usr/bin/swtpm', required: false) +swtpm_setup = find_program('swtpm_setup', '/usr/bin/swtpm_setup', required: false) + +tpm1_data.set('TPM_VERSION_BIN', tpm_version.found() ? tpm_version.path() : '') +tpm1_data.set('TPM_SEALDATA_BIN', tpm_sealdata.found() ? tpm_sealdata.path() : '') +tpm1_data.set('TPM_UNSEALDATA_BIN', tpm_unsealdata.found() ? tpm_unsealdata.path() : '') +tpm1_data.set('TPM_TAKEOWNERSHIP_BIN', tpm_takeownership.found() ? tpm_takeownership.path() : '') +tpm1_data.set('TCSD_BIN', tcsd.found() ? tcsd.path() : '') +tpm1_data.set('SWTPM_BIN', swtpm.found() ? swtpm.path() : '') +tpm1_data.set('SWTPM_SETUP_BIN', swtpm_setup.found() ? swtpm_setup.path() : '') + +configure_file( + input: 'tpm1-common-test-functions.in', + output: 'tpm1-common-test-functions', + configuration: tpm1_data, +) + +patch_data = configuration_data() +cc = meson.get_compiler('c') +has___xstat = cc.has_header_symbol('sys/stat.h', '__xstat', args: ['-D_GNU_SOURCE']) + +patch_data.set('HAS___XSTAT', has___xstat) + +tcsd_patch_src = configure_file( + input: 'tcsd-patch.c.in', + output: 'tcsd-patch.c', + configuration: patch_data +) + +libdl_dep = dependency('dl', required: true) +tcsd_patch = shared_library('tcsd-patch', tcsd_patch_src, + dependencies: libdl_dep, + install: false) +tcsd_patch_path = tcsd_patch.full_path() +env.prepend('TCSD_PATCH_LIB', tcsd_patch_path) + +test('pin-tpm1-hw', find_program('pin-tpm1-hw'), env: env, timeout: 120) +test('pin-tpm1-sw', find_program('pin-tpm1-sw'), env: env, timeout: 120) diff --git a/src/pins/tpm1/tests/pin-tpm1-hw b/src/pins/tpm1/tests/pin-tpm1-hw new file mode 100755 index 00000000..db1e8061 --- /dev/null +++ b/src/pins/tpm1/tests/pin-tpm1-hw @@ -0,0 +1,27 @@ +#!/bin/bash -x +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +. tpm1-common-test-functions + +tpm1_hw_check_preconditions +tpm1_working || skip_test "Skipping TPM1 test, TPM is not correctly setup" + +. pin-tpm1-tests diff --git a/src/pins/tpm1/tests/pin-tpm1-sw b/src/pins/tpm1/tests/pin-tpm1-sw new file mode 100755 index 00000000..b4268d41 --- /dev/null +++ b/src/pins/tpm1/tests/pin-tpm1-sw @@ -0,0 +1,28 @@ +#!/bin/bash -x +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +. tpm1-common-test-functions + +tpm1_sw_check_preconditions +tpm1_start_emulation +tpm1_working || skip_test "Unable to setup software emulation of TPM 1, skipping tests" + +. pin-tpm1-tests diff --git a/src/pins/tpm1/tests/pin-tpm1-tests b/src/pins/tpm1/tests/pin-tpm1-tests new file mode 100755 index 00000000..00b6c4eb --- /dev/null +++ b/src/pins/tpm1/tests/pin-tpm1-tests @@ -0,0 +1,102 @@ +#!/bin/bash -x +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +TEST=$(basename "${0}") + +validate_pcrs() { + local _pcrs=",${1}" + [ "${_pcrs}" = "," ] && return 0 + + : | tpm_sealdata -z${_pcrs//,/ -p } >/dev/null 2>/dev/null +} + +decode_jwe() { + local jwe="${1}" + + local coded + if ! coded=$(jose jwe fmt -i- <<< "${jwe}"); then + return 1 + fi + + coded=$(jose fmt -j- -g protected -u- <<< "${coded}" | tr -d '"') + jose b64 dec -i- <<< "${coded}" +} + +test_pcr_ids() { + local orig="${1}" + local cfg="${2}" + local expected_pcr_ids="${3}" + + local enc + if ! enc=$(echo "${orig}" | clevis encrypt tpm1 "${cfg}"); then + echo "${TEST}: encrypt failed for cfg: ${cfg}" >&1 + return 1 + fi + + local pcr_ids + pcr_ids=$(decode_jwe "${enc}" \ + | jose fmt -j- -Og clevis -Og tpm1 -Og pcr_ids -u- 2>/dev/null) + + local dec + dec=$(echo "${enc}" | clevis decrypt) + + if [ "${orig}" != "${dec}" ]; then + echo "${TEST}: decoded text (${dec}) does not match original one (${orig})" >&2 + return 1 + fi + + if [ "${pcr_ids}" != "${expected_pcr_ids}" ]; then + echo "${TEST}: pcr_ids (${pcr_ids}) do not match the expected (${expected_pcr_ids}) result." >&2 + return 1 + fi +} + +test_enc_dec() { + local cfg="${1}" + output=$(echo Working | clevis encrypt tpm1 "${cfg}" | clevis decrypt) + + if [ "$output" != "Working" ]; then + echo "Output after decrypting doesn't match: ${output} != 'Working'" + return 1 + fi +} + +test_enc_dec '{}' || exit 1 +test_pcr_ids "${orig}" '{}' "" || exit 1 +test_pcr_ids "${orig}" '{ }' "" || exit 1 + +# Issue #103: now let's try a few different configs with both strings and +# arrays and check if we get the expected pcr_ids. + +# Let's first make sure this would be a valid configuration. +if validate_pcrs "4,16"; then + test_pcr_ids "${orig}" '{"pcr_ids": "16"}' "16" || exit 1 + test_pcr_ids "${orig}" '{"pcr_ids": ["16"]}' "16" || exit 1 + test_pcr_ids "${orig}" '{"pcr_ids": "4, 16"}' "4,16" || exit 1 + test_pcr_ids "${orig}" '{"pcr_ids": "4,16"}' "4,16" || exit 1 + test_pcr_ids "${orig}" '{"pcr_ids": ["4,16"]}' "4,16" || exit 1 + test_pcr_ids "${orig}" '{"pcr_ids": [4,16]}' "4,16" || exit 1 + test_pcr_ids "${orig}" '{"pcr_ids": [4, 16]}' "4,16" || exit 1 + test_pcr_ids "${orig}" '{"pcr_ids": ["4","16"]}' "4,16" || exit 1 + ! test_pcr_ids "${orig}" '{"pcr_ids": ["4","16"]}' "foo bar" || exit 1 +else + echo "Skipping tests related to issue#103 because the combination of pcr_bank and PCRs is invalid" >&2 +fi diff --git a/src/pins/tpm1/tests/tcsd-patch.c.in b/src/pins/tpm1/tests/tcsd-patch.c.in new file mode 100644 index 00000000..99e85523 --- /dev/null +++ b/src/pins/tpm1/tests/tcsd-patch.c.in @@ -0,0 +1,91 @@ +#define _GNU_SOURCE +#undef _FILE_OFFSET_BITS + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TCSD_CONF "tcsd.conf" + +#mesondefine HAS___XSTAT + +static void stat_process(const char *statfunc, const char *pathname, struct stat *statbuf) { + size_t path_len = strlen(pathname); + size_t tcsd_len = strlen(TCSD_CONF); + if ((path_len >= tcsd_len) + && (strcmp(pathname + path_len - tcsd_len, TCSD_CONF) == 0) + && ((path_len == tcsd_len) + || (pathname[path_len - tcsd_len - 1] == '/'))) { + + + if ((statbuf->st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == (S_IRUSR | S_IWUSR)) { + // Compatibility with old tcsd + // Get the UID for the user 'tss' + struct passwd *pw = getpwnam("tss"); + if (pw != NULL) { + statbuf->st_uid = pw->pw_uid; + fprintf(stderr, "%s(%s) : simulate uid\n", statfunc, pathname); + } + } else { + statbuf->st_uid = 0; + } + + // Get the GID for the group 'tss' + struct group *grp = getgrnam("tss"); + if (grp != NULL) { + statbuf->st_gid = grp->gr_gid; + fprintf(stderr, "%s(%s) : simulate gid\n", statfunc, pathname); + } + } else { + fprintf(stderr, "%s(%s) : passthrough\n", statfunc, pathname); + } +} + +int stat(const char *pathname, struct stat *statbuf) { + static int (*real_stat)(const char *, struct stat *) = NULL; + if (!real_stat) { + real_stat = dlsym(RTLD_NEXT, "stat"); + } + + // Call the original stat function + int result = real_stat(pathname, statbuf); + if (result == 0) { + stat_process("stat", pathname, statbuf); + } + return result; +} + +#ifdef HAS___XSTAT +int __xstat(int ver, const char *pathname, struct stat *statbuf) { + static int (*real___xstat)(int, const char *, struct stat *) = NULL; + if (!real___xstat) { + real___xstat = dlsym(RTLD_NEXT, "__xstat"); + } + int result = real___xstat(ver, pathname, statbuf); + if (result == 0) { + stat_process("__xstat", pathname, statbuf); + } + return result; +} +#endif + +int setuid(uid_t uid) { + return 0; +} + +int setgid(gid_t gid) { + return 0; +} + +static void __attribute ((constructor)) +set_line_buffering (void) +{ + setvbuf(stdout, NULL, _IOLBF, 0); + fprintf(stderr, "set_line_buffering : done\n"); +} diff --git a/src/pins/tpm1/tests/tpm1-common-test-functions.in b/src/pins/tpm1/tests/tpm1-common-test-functions.in new file mode 100644 index 00000000..20b6ee4f --- /dev/null +++ b/src/pins/tpm1/tests/tpm1-common-test-functions.in @@ -0,0 +1,146 @@ +#!/bin/bash -x +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +. tests-common-functions + +TPM_VERSION_BIN="@TPM_VERSION_BIN@" +TPM_SEALDATA_BIN="@TPM_SEALDATA_BIN@" +TPM_UNSEALDATA_BIN="@TPM_UNSEALDATA_BIN@" +TPM_TAKEOWNERSHIP_BIN="@TPM_TAKEOWNERSHIP_BIN@" +TCSD_BIN="@TCSD_BIN@" +SWTPM_BIN="@SWTPM_BIN@" +SWTPM_SETUP_BIN="@SWTPM_SETUP_BIN@" + +SWTPM_SOCKET_PID= +TCSD_PID= + +export -n TCSD_UN_SOCKET_DEVICE_PATH +export -n TSS_TCSD_PORT +export -n TSS_TCSD_HOST + +function on_exit() { + popd || error "Unable to change directory" + if [ ! -d "$TESTDIR" ] || ! rm -rf "$TESTDIR"; then + echo "Delete temporary files failed!" >&2 + echo "You need to clean up: $TESTDIR" >&2 + exit 1 + fi + + # Cleanup sw emulation + [ -n "$TCSD_PID" ] && kill $TCSD_PID >/dev/null 2>&1 + if [ -n "$SWTPM_SOCKET_PID" ]; then + kill $SWTPM_SOCKET_PID >/dev/null 2>&1 + sleep .5 + # swtpm does not always terminate gracefully, so kill it + kill -9 $SWTPM_SOCKET_PID >/dev/null 2>&1 + fi +} +if ! TESTDIR="$(mktemp -d)"; then + echo "Creating a temporary dir for TPM files failed!" >&2 + exit 1 +fi +trap 'on_exit' EXIT +pushd "$TESTDIR" || error "Unable to change directory" + +tpm1_hw_check_preconditions() { + [ -x "${TPM_VERSION_BIN}" ] || skip_test "Skipping TPM1 test, tpm_version from tpm-tools not found" + [ -x "${TPM_SEALDATA_BIN}" ] || skip_test "Skipping TPM1 test, tpm_sealdata from tpm-tools not found" + [ -x "${TPM_UNSEALDATA_BIN}" ] || skip_test "Skipping TPM1 test, tpm_unsealdata from tpm-tools not found" + getent group "tss" >/dev/null 2>&1 || skip_test "Skipping TPM1 test, group tss not found" +} + +tpm1_sw_check_preconditions() { + tpm1_hw_check_preconditions + + [ -x "${TCSD_BIN}" ] || skip_test "Skipping TPM1 test with software emulation, tcsd not found" + [ -x "${SWTPM_BIN}" ] || skip_test "Skipping TPM1 test with software emulation, swtpm not found" + [ -x "${SWTPM_SETUP_BIN}" ] || skip_test "Skipping TPM1 test with software emulation, swtpm_setup not found" + [ -f "${TCSD_PATCH_LIB}" ] || skip_test "Skipping TPM1 test with software emulation, libtcsd_patch.so not found" + + if ! "${SWTPM_BIN}" socket --print-capabilities | jq -e '(.version | test("^0\\.[0-6](\\..*)?$")) or (.features | index("tpm-1.2"))' >/dev/null 2>&1; then + skip_test "Skipping TPM1 test with software emulation, no support for TPM 1.2 in swtpm" + fi +} + +tpm1_working() { + if ! "$TPM_VERSION_BIN" >/dev/null 2>&1; then + # The tpm_version outputs garbage to stdout on success, so let the + # tpm_version output the error again cleanly now + ( "$TPM_VERSION_BIN" 2>&1 | tr '\0' ' ' ) 2>&1 | sed -e 's/^/tpm_version: /' >&2 + return 1 + fi + + echo test | "$TPM_SEALDATA_BIN" -z | "$TPM_UNSEALDATA_BIN" -z -i /dev/stdin >/dev/null 2>&1 \ + && return 0 + + if [ ${PIPESTATUS[0]} -gt 0 ]; then + "$TPM_SEALDATA_BIN" -z < /dev/null >/dev/null 2>&1 | sed -e 's/^/tpm_sealdata: /' >&2 + else + "$TPM_SEALDATA_BIN" -z < /dev/null | "$TPM_UNSEALDATA_BIN" -z -i /dev/stdin 2>&1 >/dev/null | sed -e 's/^/tpm_unsealdata: /' >&2 + fi + return 1 +} + +tpm1_start_emulation() { + local socket_wait + echo "Starting TPM 1 emulation" >&2 + + # Setup TPM 1 data + "${SWTPM_SETUP_BIN}" --tpm-state "$TESTDIR" --createek --display >&2 || error "Unable to setup TPM 1 emulation" + + # Start emulation over socket + "${SWTPM_BIN}" socket --tpmstate dir="$TESTDIR" --ctrl type=unixio,path="$TESTDIR"/swtpm.sock.ctrl --server type=unixio,path="$TESTDIR"/swtpm.sock --flags startup-clear >&2 & + SWTPM_SOCKET_PID=$! + + socket_wait=1 + while [ $socket_wait -le 100 ]; do + [ -S "$TESTDIR"/swtpm.sock ] && break + socket_wait=$((socket_wait + 1)) + sleep 0.1 + done + [ "$socket_wait" -gt 100 ] && error "Unable to start TPM 1 emulation" + + # Run tcds + cat < "$TESTDIR"/tcsd.conf +port = 0 +system_ps_file = $TESTDIR/system.data +EOM + + # Deduce correct permissions, or fallback to latest tcsd expectations + if [ -f "@sysconfdir@/tcsd.conf" ]; then + chmod "$(stat -c "0%a" "@sysconfdir@/tcsd.conf")" "$TESTDIR"/tcsd.conf + else + chmod 0640 "$TESTDIR"/tcsd.conf + fi + + LD_PRELOAD="$TCSD_PATCH_LIB" TCSD_UN_SOCKET_DEVICE_PATH="$TESTDIR"/swtpm.sock "${TCSD_BIN}" -f -e -c "$TESTDIR"/tcsd.conf >&2 Date: Sat, 3 Feb 2024 22:10:43 +0100 Subject: [PATCH 09/16] tpm1: added TPM 1.2 (tpm1 pin) documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- README.md | 249 +++++++++++++++++- src/pins/tpm1/clevis-encrypt-tpm1.1.adoc | 308 +++++++++++++++++++++++ src/pins/tpm1/meson.build | 1 + 3 files changed, 552 insertions(+), 6 deletions(-) create mode 100644 src/pins/tpm1/clevis-encrypt-tpm1.1.adoc diff --git a/README.md b/README.md index 9a339d63..b226a485 100644 --- a/README.md +++ b/README.md @@ -62,20 +62,257 @@ advertisement is stored, or the JSON contents of the advertisement itself. When the advertisement is specified manually like this, Clevis presumes that the advertisement is trusted. -#### PIN: TPM2 +#### PIN: TPM1 and TPM2 -Clevis provides support to encrypt a key in a Trusted Platform Module 2.0 (TPM2) -chip. The cryptographically-strong, random key used for encryption is encrypted -using the TPM2 chip, and is decrypted using TPM2 at the time of decryption to allow clevis to decrypt the secret stored in the JWE. +Clevis provides support to encrypt a key in a Trusted Platform Module 1.2 (TPM1) +and 2.0 (TPM2) chips. The cryptographically-strong, random key used for +encryption is encrypted using the TPM chip, and is decrypted using TPM at the +time of decryption to allow clevis to decrypt the secret stored in the JWE. -For example: +For example for TPM1 pin: + +```bash +$ echo hi | clevis encrypt tpm1 '{}' > hi.jwe +``` + +or TPM2 pin: ```bash $ echo hi | clevis encrypt tpm2 '{}' > hi.jwe ``` Clevis store the public and private keys of the encrypted key in the JWE object, -so those can be fetched on decryption to unseal the key encrypted using the TPM2. +so those can be fetched on decryption to unseal the key encrypted using the TPM +chip. + +Check manual pages for `clevis-encrypt-tpm1` and `clevis-encrypt-tpm2` tools for +more options, like binding to a particular PCR registry states and/or values. + +##### TPM1 PIN Limitations + +To avoid prompting for a password during unlocking, the encryption and +decryption processes require that the well-known Storage Root Key (SRK) be +configured when taking ownership of the TPM 1.2 chip. This means you must have +either run the `tpm_takeownership` command + +```bash +$ tpm_takeownership --srk-well-known +``` + +during setup or executed `tpm_changeownerauth` command + +```bash +$ tpm_changeownerauth --srk --set-well-known +``` + +to configure it. Note that a _well-known_ key is not the same as an empty key. + +> [!IMPORTANT] +> If you have changed the SRK to a _well-known_ key, remember to run +> `update-initramfs` command (on Debian-like systems) +> +> ```bash +> $ update-initramfs -u +> ``` +> +> or `dracut` command (on Fedora-like systems) +> +> ```bash +> $ dracut -f +> ``` +> +> afterward to recreate initramfs image, because `/var/lib/tpm` is +> included in the image. This applies to `initramfs-tools` and Dracut in +> _host-only_ mode. In Dracut's _default_ mode, `/var/lib/tpm` is already +> configured to allow access to the TPM 1.2 chip using a _well-known_ SRK. + +##### Unlocking with a Separately-Encrypted `/var` Volume with TPM1 PIN + +Because TPM1 PIN relies on the `tcsd` daemon from the Trousers project to +access the TPM 1.2 chip, the daemon must start early in the boot process to +unlock the root filesystem automatically. The `/var/lib/tpm` directory +contains runtime data for `tcsd` and must be available before the daemon +starts. + +A minimal copy of the required `/var` files is included in the initramfs +image prepared by Clevis, so the daemon _should_ be able to start during the +_initrd bootup_ phase if everything is configured correctly. After switching +to the real root (`/`) filesystem, the _System Manager bootup_ phase starts +and `/var` is mounted from the actual target. At this point, Clevis cannot +unlock it (`tcsd` would need `/var` to unlock `/var`), so it must already be +unlocked. Refer to the instructions below for `initramfs-tools` and Dracut. + +If the `/var` volume is part of the main LVM volume group (the same as the +root `/` filesystem) and is protected by the same LUKS volume, no special +configuration is needed. However, if the `/var` volume is encrypted separately +(i.e., it uses a different LUKS volume, regardless of whether it has the same +password), follow the instructions below to enable automatic unlocking with +Clevis. + +###### `initramfs-tools` Initrd Bootup + +`initramfs-tools` unlocks the root and swap filesystems by copying the +corresponding option lines from `/etc/crypttab` into the initramfs. To ensure +that `/var` volume options are also included, add the `initramfs` option on +Debian-like system to the relevant line in `/etc/crypttab` as shown in the +following example: + +> `/etc/crypttab` +> ```bash +> … +> luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,initramfs +> … +> ``` + +This line corresponds to the `crypto_LUKS` volume used by the `/var` volume, +as shown by the `lsblk -fp` command: + +> LVM on LUKS +> ```bash +> … +> └─/dev/vda3 crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 +> └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 +> └─/dev/mapper/separate-var xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 753,3M 22% /var +> ``` + +The above example uses an LVM-on-LUKS encryption scheme, but the same applies to +LUKS-on-LVM — just check the `crypto_LUKS` volume UUID. + +> LUKS on LVM +> ```bash +> … +> └─/dev/vda3 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 +> └─/dev/mapper/separate-var crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 +> └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 781,5M 19% /var +> ```` + +> [!IMPORTANT] +> After modifying `/etc/crypttab`, you must run `update-initramfs -u` (on +> Debian-like systems). + +###### Dracut Initrd Bootup + +Dracut automatically unlocks the root and swap filesystems. The operating +system installer ensures that the kernel command line (in `/etc/default/grub`) +contains the necessary parameters for Dracut and Systemd. Dracut considers +both the kernel command line and the lines copied from `/etc/crypttab` for +unlocking. + +By default, the root and swap lines from `/etc/crypttab` are copied into the +initramfs. To ensure the `/var` volume is also unlocked, you must ensure that +its options are included and referenced by the kernel command line (as +described below). + +> [!CAUTION] +> Changing the following options can render the system unbootable, potentially +> requiring a rescue DVD and expert knowledge to recover. Make a full backup +> before proceeding! +> +> For recovery, you may find these commands helpful: +> +> * `cryptsetup open /dev/ ` +> * `mount /dev/mapper/ /` +> * `lvm vgscan` +> * `lvm lvdisplay -o lv_full_name,lv_dm_path` + +To ensure that the `/var` options are included, add either the `x-initrd.attach` +option to the corresponding line in /etc/crypttab (to unlock the `/var` volume) +or the `x-initrd.mount` option to the corresponding line in `/etc/fstab` (to +unlock _and_ mount the `/var` volume). Using both is equivalent to +`x-initrd.mount`. + +> `/etc/crypttab` +> ```bash +> … +> luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,x-initrd.attach +> … +> ``` + +> `/etc/fstab` +> ```bash +> … +> UUID=767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 /var xfs defaults,x-systemd.device-timeout=0,x-initrd.mount 0 0 +> … +> ``` + +Refer to the `initramfs-tools` section for instructions on finding the correct +`/etc/crypttab` line with `lsblk -fp`. The `/etc/fstab` entry is matched by the +UUID of the filesystem (see the line with `/var` in the `lsblk -fp` output). + +> [!IMPORTANT] +> After changing `/etc/crypttab` and/or `/etc/fstab`, run `dracut -f`. + +> [!NOTE] +> If you use `x-initrd.mount`, the volume is mounted during the _initrd bootup_ +> phase. However, this is not strictly necessary. Systemd's startup order +> ensures that `/var` is mounted before `tcsd` starts in the _System Manager +> bootup_ phase, so using `x-initrd.attach` alone is sufficient. + +Next, ensure that the volumes are found and unlocked. Two kernel command line +parameters in `/etc/default/grub` affect this: + +* `rd.luks.uuid` – Either remove all values or add the UUID of the + `crypto_LUKS` volume (optionally prefixed by `luks-`). If this option is + present (it can appear multiple times), only the specified volumes are + initialized from `/etc/crypttab`. If it is missing, all lines from + `/etc/crypttab` are considered. +* `rd.lvm.lv` – Either remove all values or add the full LVM volume name for + `/var`. If this option is present (it can appear multiple times), only the + listed logical volumes are initialized. If it is missing, Dracut + automatically detects LVM volumes during boot. + +> [!NOTE] +> The `rd.lvm.lv` option matters only in the LUKS-on-LVM case, because the +> `crypto_LUKS` volume is accessible only after the LVM logical volume is +> activated. If `rd.lvm.lv` is missing, Dracut will detect LVM volumes +> automatically. If it is present, make sure to include the `/var` full volume +> name. + +For more information, see `man dracut.cmdline` and +`man systemd-cryptsetup-generator`. + +> [!NOTE] +> Dracut internally uses the same Systemd options, so the same logic applies +> even if Systemd is not present in the Dracut initrd environment. + +To find the correct `rd.lvm.lv` value, run: + +```bash +lvs -o lv_full_name,lv_dm_path +``` + +This shows the logical volume's full name and Device Mapper path, which also +appears in the `lsblk -fp` output. For example, if it shows `separate/var` +(see example below), the `rd.lvm.lv` value would be `rd.lvm.lv=separate/var`: + +> ```bash +> LV DMPath +> … +> separate/var /dev/mapper/separate-var +> … +> ``` + +Example of a kernel command line in `/etc/default/grub` with all options +present: + +> `/etc/default/grub` +> ```bash +> GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/root rd.luks.uuid=luks-21a9c1b8-c202-4985-809a-aba2d6fdab01 rd.lvm.lv=separate/var rd.luks.uuid=luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 quiet" +> ``` + +Example of a kernel command line in `/etc/default/grub` when relying on the +configuration copied from `/etc/crypttab` and Dracut’s automatic LVM +detection: + +> `/etc/default/grub` +> ```bash +> GRUB_CMDLINE_LINUX="quiet" +> ``` + +> [!IMPORTANT] +> After changing the kernel command line, update the Grub configuration with +> `update-grub2` (on Debian-like systems) or +> `grub2-mkconfig -o /etc/grub2.cfg` (on Fedora-like systems). #### PIN: PKCS#11 diff --git a/src/pins/tpm1/clevis-encrypt-tpm1.1.adoc b/src/pins/tpm1/clevis-encrypt-tpm1.1.adoc new file mode 100644 index 00000000..6eaff5a2 --- /dev/null +++ b/src/pins/tpm1/clevis-encrypt-tpm1.1.adoc @@ -0,0 +1,308 @@ +CLEVIS-ENCRYPT-TPM1(1) +====================== +:doctype: manpage + + +== NAME + +clevis-encrypt-tpm1 - Encrypts using a TPM 1.2 chip binding policy + +== SYNOPSIS + +*clevis encrypt tpm1* CONFIG < PT > JWE + +== OVERVIEW + +The *clevis encrypt tpm1* command encrypts using a Trusted Platform +Module{nbsp}1.2 (TPM{nbsp}1.2) chip. It might work with Trusted Platform +Module{nbsp}1.1 too, but it is untested. The tpm1 pin does not support Trusted +Platform Module{nbsp}2.0 and higher, please use the tpm2 pin instead. + +The only argument is the JSON configuration object. + +When using the tpm1 pin, we create a new, cryptographically-strong, random key. +This key is encrypted using the TPM{nbsp}1.2 chip. Then at decryption time, the +key is decrypted again using the TPM{nbsp}1.2 chip. + + $ clevis encrypt tpm1 '{}' < PT > JWE + +To decrypt the data, simply provide the ciphertext (JWE): + + $ clevis decrypt < JWE > PT + +Note that like other pins no configuration is used for decryption, this is due +to clevis storing the sealed encryption key in the JWE so clevis can fetch that +key from there and unseal it by using the TPM{nbsp}1.2 chip. + +The pin also supports sealing data to a Platform Configuration Registers (PCR) +state. That way the data can only be unsealed if the PCRs hash values match +the values used when sealing. + +For example, to seal the data to the PCR with indexes 0, 4 and 7, use: + + $ clevis encrypt tpm1 '{"pcr_ids":"0,4,7"}' < PT > JWE + +The BIOS, boot loader, Grub and Linux kernel incrementally add hashes of the +various system states to the registers. The added hashes represent state of +different components such as the BIOS (PCR{nbsp}0 and{nbsp}1), option ROMs +(PCR{nbsp}2 and{nbsp}3), boot loader (PCR{nbsp}4), EFI Secure Boot state +(PCR{nbsp}0 and{nbsp}7), etc. The requirement for exact matching of values +ensures that the TPM{nbsp}1.2 chip unseals the data only when the system state +measured by the selected registers has not changed. + +Although the usage of Platform Configuration Registers is standardized, the BIOS +implementation might differ slightly, so always test how the particular register +value changes when the system is updated, most notably when the Grub boot +loaded or Linux kernel is updated. The PCR values can be checked with: + + $ cat /sys/class/tpm/tpm0/pcrs + +== CONFIG + +This command uses the following configuration properties: + +* *pcr_ids* (string) : + Comma separated list of PCR used for policy. If not present, no policy is used + +== Limitations + +To avoid prompting for a password during unlocking, the encryption and +decryption processes require that the well-known Storage Root Key (SRK) be +configured when taking ownership of the TPM{nbsp}1.2 chip. This means you must +have either run the `tpm_takeownership` command + + $ tpm_takeownership --srk-well-known + +during setup or executed `tpm_changeownerauth` command + + $ tpm_changeownerauth --srk --set-well-known + +to configure it. Note that a _well-known_ key is not the same as an empty key. + +[IMPORTANT] +-- +If you have changed the SRK to a _well-known_ key, remember to run +`update-initramfs` command (on Debian-like systems) + + $ update-initramfs -u + +or `dracut` command (on Fedora-like systems) + + $ dracut -f + +afterward to recreate initramfs image, because `/var/lib/tpm` is +included in the image. This applies to `initramfs-tools` and Dracut in +_host-only_ mode. In Dracut's _default_ mode, `/var/lib/tpm` is already +configured to allow access to the TPM{nbsp}1.2 chip using a _well-known_ SRK. +-- + +== Unlocking with a Separately-Encrypted `/var` Volume with TPM1 PIN + +Because TPM1 PIN relies on the `tcsd` daemon from the Trousers project to +access the TPM{nbsp}1.2 chip, the daemon must start early in the boot process to +unlock the root filesystem automatically. The `/var/lib/tpm` directory +contains runtime data for `tcsd` and must be available before the daemon +starts. + +A minimal copy of the required `/var` files is included in the initramfs +image prepared by Clevis, so the daemon _should_ be able to start during the +_initrd bootup_ phase if everything is configured correctly. After switching +to the real root (`/`) filesystem, the _System Manager bootup_ phase starts +and `/var` is mounted from the actual target. At this point, Clevis cannot +unlock it (`tcsd` would need `/var` to unlock `/var`), so it must already be +unlocked. Refer to the instructions below for `initramfs-tools` and Dracut. + +If the `/var` volume is part of the main LVM volume group (the same as the +root `/` filesystem) and is protected by the same LUKS volume, no special +configuration is needed. However, if the `/var` volume is encrypted separately +(i.e., it uses a different LUKS volume, regardless of whether it has the same +password), follow the instructions below to enable automatic unlocking with +Clevis. + +=== `initramfs-tools` Initrd Bootup + +`initramfs-tools` unlocks the root and swap filesystems by copying the +corresponding option lines from `/etc/crypttab` into the initramfs. To ensure +that `/var` volume options are also included, add the `initramfs` option on +Debian-like system to the relevant line in `/etc/crypttab` as shown in the +following example: + +.`/etc/crypttab` +---- +… +luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,initramfs +… +---- + +This line corresponds to the `crypto_LUKS` volume used by the `/var` volume, +as shown by the `lsblk -fp` command: + +.LVM on LUKS +---- +… +└─/dev/vda3 crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 + └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 + └─/dev/mapper/separate-var xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 753,3M 22% /var +---- + +The above example uses an LVM-on-LUKS encryption scheme, but the same applies to +LUKS-on-LVM — just check the `crypto_LUKS` volume UUID. + +.LUKS on LVM +---- +… +└─/dev/vda3 LVM2_member LVM2 001 lgk4ap-Fo39-PemI-eqKn-fxW2-e3Zt-CPGIv2 + └─/dev/mapper/separate-var crypto_LUKS 2 aa0ce19c-cde9-44a2-adbd-4afb1845a959 + └─/dev/mapper/luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 xfs 767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 781,5M 19% /var +---- + +IMPORTANT: After modifying `/etc/crypttab`, you must run `update-initramfs -u` +(on Debian-like systems). + +=== Dracut Initrd Bootup + +Dracut automatically unlocks the root and swap filesystems. The operating +system installer ensures that the kernel command line (in `/etc/default/grub`) +contains the necessary parameters for Dracut and Systemd. Dracut considers +both the kernel command line and the lines copied from `/etc/crypttab` for +unlocking. + +By default, the root and swap lines from `/etc/crypttab` are copied into the +initramfs. To ensure the `/var` volume is also unlocked, you must ensure that +its options are included and referenced by the kernel command line (as +described below). + +[CAUTION] +-- +Changing the following options can render the system unbootable, potentially +requiring a rescue DVD and expert knowledge to recover. Make a full backup +before proceeding! + +For recovery, you may find these commands helpful: + +* `cryptsetup open /dev/ ` +* `mount /dev/mapper/ /` +* `lvm vgscan` +* `lvm lvdisplay -o lv_full_name,lv_dm_path` +-- + +To ensure that the `/var` options are included, add either the `x-initrd.attach` +option to the corresponding line in /etc/crypttab (to unlock the `/var` volume) +or the `x-initrd.mount` option to the corresponding line in `/etc/fstab` (to +unlock _and_ mount the `/var` volume). Using both is equivalent to +`x-initrd.mount`. + +.`/etc/crypttab` +---- +… +luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 UUID=aa0ce19c-cde9-44a2-adbd-4afb1845a959 none discard,x-initrd.attach +… +---- + +.`/etc/fstab` +---- +… +UUID=767b750e-bba7-4ea7-b2b8-b1e6a2e22e43 /var xfs defaults,x-systemd.device-timeout=0,x-initrd.mount 0 0 +… +---- + +Refer to the `initramfs-tools` section for instructions on finding the correct +`/etc/crypttab` line with `lsblk -fp`. The `/etc/fstab` entry is matched by the +UUID of the filesystem (see the line with `/var` in the `lsblk -fp` output). + +IMPORTANT: After changing `/etc/crypttab` and/or `/etc/fstab`, run `dracut -f`. + +NOTE: If you use `x-initrd.mount`, the volume is mounted during the _initrd +bootup_ phase. However, this is not strictly necessary. Systemd's startup order +ensures that `/var` is mounted before `tcsd` starts in the _System Manager +bootup_ phase, so using `x-initrd.attach` alone is sufficient. + +Next, ensure that the volumes are found and unlocked. Two kernel command line +parameters in `/etc/default/grub` affect this: + +* `rd.luks.uuid` – Either remove all values or add the UUID of the + `crypto_LUKS` volume (optionally prefixed by `luks-`). If this option is + present (it can appear multiple times), only the specified volumes are + initialized from `/etc/crypttab`. If it is missing, all lines from + `/etc/crypttab` are considered. +* `rd.lvm.lv` – Either remove all values or add the full LVM volume name for + `/var`. If this option is present (it can appear multiple times), only the + listed logical volumes are initialized. If it is missing, Dracut + automatically detects LVM volumes during boot. + +NOTE: The `rd.lvm.lv` option matters only in the LUKS-on-LVM case, because the +`crypto_LUKS` volume is accessible only after the LVM logical volume is +activated. If `rd.lvm.lv` is missing, Dracut will detect LVM volumes +automatically. If it is present, make sure to include the `/var` full volume +name. + +For more information, see manual pages of `dracut.cmdline` and +`systemd-cryptsetup-generator`. + +NOTE: Dracut internally uses the same Systemd options, so the same logic applies +even if Systemd is not present in the Dracut initrd environment. + +To find the correct `rd.lvm.lv` value, run: + + $ lvs -o lv_full_name,lv_dm_path + +This shows the logical volume's full name and Device Mapper path, which also +appears in the `lsblk -fp` output. For example, if it shows `separate/var` +(see example below), the `rd.lvm.lv` value would be `rd.lvm.lv=separate/var`: + +---- +LV DMPath +… +separate/var /dev/mapper/separate-var +… +---- + +Example of a kernel command line in `/etc/default/grub` with all options +present: + +.`/etc/default/grub` +---- +GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/root rd.luks.uuid=luks-21a9c1b8-c202-4985-809a-aba2d6fdab01 rd.lvm.lv=separate/var rd.luks.uuid=luks-aa0ce19c-cde9-44a2-adbd-4afb1845a959 quiet" +---- + +Example of a kernel command line in `/etc/default/grub` when relying on the +configuration copied from `/etc/crypttab` and Dracut’s automatic LVM +detection: + +.`/etc/default/grub` +---- +GRUB_CMDLINE_LINUX="quiet" +---- + +IMPORTANT: After changing the kernel command line, update the Grub configuration +with `update-grub2` (on Debian-like systems) or +`grub2-mkconfig -o /etc/grub2.cfg` (on Fedora-like systems). + +== Threat model + +The Clevis security model relies in the fact that an attacker will not be able +to access both the encrypted data and the decryption key. + +For most Clevis pins, the decryption key is not locally stored, so the +decryption policy is only satisfied if the decryption key can be remotely +accessed. It could for example be stored in a remote server or in a hardware +authentication device that has to be plugged into the machine. + +The tpm1 pin is different in this regard, since a key is wrapped by +a{nbsp}TPM{nbsp}1.2 chip that is always present in the machine. This does not +mean that there are not use cases for this pin, but it is important to +understand the fact that an attacker that has access to both the encrypted data +and the local TPM{nbsp}1.2 chip will be able to decrypt the data. + +The use of specific Platform Configuration Registers along with Secure Boot +limits the attack surface because an attacker must reproduce the exact register +values that are present during sealing before the TPM{nbsp}1.2 chip will allow +the encryption key to be unsealed. Careful selection of registers ensures that +any change to the boot process will prevent the TPM{nbsp}1.2 from unsealing the +encryption key. + +== SEE ALSO + +link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)], +link:man:dracut.cmdline(7)[*dracut.cmdline*(7)], +link:man:systemd-cryptsetup-generator(8)[*systemd-cryptsetup-generator*(8)] diff --git a/src/pins/tpm1/meson.build b/src/pins/tpm1/meson.build index 70926c4c..1c07bbb4 100644 --- a/src/pins/tpm1/meson.build +++ b/src/pins/tpm1/meson.build @@ -8,6 +8,7 @@ endforeach if all bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm1') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1') + mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1.1') subdir('tests') else warning('Will not install tpm1 pin due to missing dependencies!') From 3d07c8a362cdab076cd2898b2de3a0b6aef424b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Sun, 23 Jun 2024 13:14:54 +0200 Subject: [PATCH 10/16] systemd: ensure TCSD is started before Clevis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a weak requirement, so when TCSD is missing, it does not influence the Clevis askpass service startup. Similarly if the TCSD startup fails, it does not affect the Clevis askpass service startup. Signed-off-by: Oldřich Jedlička --- src/luks/systemd/clevis-luks-askpass.service.in | 2 ++ src/luks/systemd/clevis-tcsd.conf | 10 ++++++++++ src/luks/systemd/meson.build | 2 ++ 3 files changed, 14 insertions(+) create mode 100644 src/luks/systemd/clevis-tcsd.conf diff --git a/src/luks/systemd/clevis-luks-askpass.service.in b/src/luks/systemd/clevis-luks-askpass.service.in index 0ffb9d3c..97874486 100644 --- a/src/luks/systemd/clevis-luks-askpass.service.in +++ b/src/luks/systemd/clevis-luks-askpass.service.in @@ -2,6 +2,8 @@ Description=Forward Password Requests to Clevis Documentation=man:clevis-luks-unlockers(7) DefaultDependencies=no +After=tcsd.service +Wants=tcsd.service Before=shutdown.target Conflicts=shutdown.target diff --git a/src/luks/systemd/clevis-tcsd.conf b/src/luks/systemd/clevis-tcsd.conf new file mode 100644 index 00000000..edb3c9d7 --- /dev/null +++ b/src/luks/systemd/clevis-tcsd.conf @@ -0,0 +1,10 @@ +[Unit] +DefaultDependencies=no +# /var/lib/tpm is required to run +RequiresMountsFor=/var/lib/tpm +# systemd-remount-fs is required, it makes root read-write +# systemd-modules-load is just to be sure that all required modules are loaded +# No Wants=, we want to start after them, but not start them - initrd not necessarily have them +After=systemd-remount-fs.service systemd-modules-load.service +Before=shutdown.target +Conflicts=shutdown.target diff --git a/src/luks/systemd/meson.build b/src/luks/systemd/meson.build index 3f10f3d9..e1f9180e 100644 --- a/src/luks/systemd/meson.build +++ b/src/luks/systemd/meson.build @@ -14,6 +14,7 @@ if systemd.found() and sd_reply_pass.found() data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') + tcsdoverridedir = join_paths(unitdir, 'tcsd.service.d') configure_file( input: 'clevis-luks-askpass.service.in', @@ -48,6 +49,7 @@ if systemd.found() and sd_reply_pass.found() install_data('clevis-luks-askpass.path', install_dir: unitdir) install_data('clevis-luks-pkcs11-askpass.socket', install_dir: unitdir) + install_data('clevis-tcsd.conf', install_dir: tcsdoverridedir) else warning('Will not install systemd support due to missing dependencies!') endif From be77a06a3ca37596c92a32ab75806058866315d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Wed, 30 Oct 2024 23:51:57 +0100 Subject: [PATCH 11/16] initramfs: added TPM 1.2 support for initramfs-tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- src/initramfs-tools/hooks/clevis.in | 70 +++++- .../scripts/local-bottom/clevis.in | 5 + .../scripts/local-top/clevis.in | 213 ++++++++++-------- src/luks/clevis-luks-tpm1-functions | 97 ++++++++ src/luks/meson.build | 2 + 5 files changed, 286 insertions(+), 101 deletions(-) create mode 100755 src/luks/clevis-luks-tpm1-functions diff --git a/src/initramfs-tools/hooks/clevis.in b/src/initramfs-tools/hooks/clevis.in index 211b6fcd..8a7d76bb 100755 --- a/src/initramfs-tools/hooks/clevis.in +++ b/src/initramfs-tools/hooks/clevis.in @@ -47,13 +47,29 @@ find_binary() { echo "$resolved" } +find_library() { + lib_name="$1" + for lib_path in \ + {/usr,}/libexec/${lib_name} \ + {/usr,}/lib64/${lib_name} \ + {/usr,}/lib/${lib_name} \ + /usr/lib/`uname -m`-linux-gnu*/${lib_name} \ + ; do + if [ -e "$lib_path" ]; then + echo "$lib_path" + return + fi + done + die 1 "Unable to find library ${lib_name}" +} + if [ -n "${FORCE_CLEVIS}" ] && [ "${FORCE_CLEVIS}" != "n" ]; then for f in /sbin/cryptsetup /sbin/dmsetup /lib/cryptsetup/askpass; do if [ ! -e "${DESTDIR}${f}" ]; then die 2 "cryptsetup utility '$f' wasn't found in the generated ramdisk image. " fi done -fi +fi copy_exec @bindir@/clevis-decrypt-tang || die 1 "@bindir@/clevis-decrypt-tang not found" @@ -80,6 +96,58 @@ if [ -x @bindir@/clevis-decrypt-tpm2 ]; then manual_add_modules tpm_crb manual_add_modules tpm_tis fi +if [ -x @bindir@/clevis-decrypt-tpm1 ]; then + copy_exec @bindir@/clevis-decrypt-tpm1 || die 1 "@bindir@/clevis-decrypt-tpm1 not found" + copy_exec @libexecdir@/clevis-luks-tpm1-functions || die 1 "@libexecdir@/clevis-luks-tpm1-functions not found" + + tcsd_bin=$(find_binary "tcsd") + # libgcc_s.so.* is no longer installed for gcc 2.34+ (no link to libpthread) + libgcc_s=$(find_library "libgcc_s.so.[1-9]") + tpm_version_bin=$(find_binary "tpm_version") + tpm_unsealdata_bin=$(find_binary "tpm_unsealdata") + stdbuf_bin=$(find_binary "stdbuf") + libstdbuf_bin=$(find_library "coreutils/libstdbuf.so*") + + copy_exec "${tpm_version_bin}" || die 1 "Unable to copy ${tpm_version_bin}" + copy_exec "${tpm_unsealdata_bin}" || die 1 "Unable to copy ${tpm_unsealdata_bin}" + + copy_exec "${tcsd_bin}" || die 1 "Unable to copy ${tcsd_bin}" + copy_exec "${libgcc_s}" || die 1 "Unable to copy ${libgcc_s}" + copy_file config /etc/tcsd.conf || dia 2 "Unable to copy /etc/tcsd.conf" + + copy_exec "${stdbuf_bin}" || die 1 "Unable to copy ${stdbuf_bin}" + copy_exec "${libstdbuf_bin}" || die 1 "Unable to copy ${libstdbuf_bin}" + + mkdir -p "${DESTDIR}/var/lib/tpm" || die 2 "Unable to create /var/lib/tpm" + cp /var/lib/tpm/* "${DESTDIR}/var/lib/tpm/" || die 2 "Unable to copy /var/lib/tpm" + chown -R tss:tss "${DESTDIR}/var/lib/tpm" || die 2 "Unable to change owner of /var/lib/tpm" + chmod -R u=rwX,go= "${DESTDIR}/var/lib/tpm" || die 2 "Unable to change permissions of /var/lib/tpm" + + mkdir -p "${DESTDIR}/lib/udev/rules.d" || die 2 "Unable to create /lib/udev/rules.d" + # shellcheck disable=SC2043 + for rule in 60-tpm-udev.rules; do + if [ -e /etc/udev/rules.d/$rule ]; then + copy_file udev_rule /etc/udev/rules.d/$rule "/lib/udev/rules.d" || die 2 "Unable to copy $rule" + elif [ -e /lib/udev/rules.d/$rule ]; then + copy_file udev_rule /lib/udev/rules.d/$rule "/lib/udev/rules.d" || die 2 "Unable to copy $rule" + fi + done + + echo "root:x:0:0:root:/root:/bin/bash" >> "${DESTDIR}/etc/passwd" + echo "root:x:0:" >> "${DESTDIR}/etc/group" + + group_id=`id -G tss` || die 2 "Unable to get tss group ID" + user_id=`id -u tss` || die 2 "Unable to get tss user ID" + echo "tss:x:$user_id:$group_id::/var/lib/tpm:/bin/false" >> "${DESTDIR}/etc/passwd" + echo "tss:x:$group_id:" >> "${DESTDIR}/etc/group" + + echo "127.0.0.1 localhost" >> "${DESTDIR}/etc/hosts" + echo "::1 localhost ip6-localhost ip6-loopback" >> "${DESTDIR}/etc/hosts" + echo "ff02::1 ip6-allnodes" >> "${DESTDIR}/etc/hosts" + echo "ff02::2 ip6-allrouters" >> "${DESTDIR}/etc/hosts" + + manual_add_modules tpm_tis +fi luksmeta_bin=$(find_binary "luksmeta") diff --git a/src/initramfs-tools/scripts/local-bottom/clevis.in b/src/initramfs-tools/scripts/local-bottom/clevis.in index 9ad782cf..a9a0c6fd 100755 --- a/src/initramfs-tools/scripts/local-bottom/clevis.in +++ b/src/initramfs-tools/scripts/local-bottom/clevis.in @@ -35,6 +35,11 @@ esac . @bindir@/clevis-luks-common-functions +if [ -f @libexecdir@/clevis-luks-tpm1-functions ]; then + . @libexecdir@/clevis-luks-tpm1-functions + stop_tcsd +fi + pid=$(cat /run/clevis.pid) clevis_kill_pid $pid diff --git a/src/initramfs-tools/scripts/local-top/clevis.in b/src/initramfs-tools/scripts/local-top/clevis.in index 14872647..30e978ab 100755 --- a/src/initramfs-tools/scripts/local-top/clevis.in +++ b/src/initramfs-tools/scripts/local-top/clevis.in @@ -28,87 +28,75 @@ prereqs) exit 0 ;; esac # Return fifo path or nothing if not found -get_fifo_path() { +get_pid_fifo_path() { local pid="$1" for fd in /proc/$pid/fd/*; do if [ -e "$fd" ]; then if [[ $(readlink -f "${fd}") == *"/cryptsetup/passfifo" ]]; then readlink -f "${fd}" + return 0 fi fi done + return 1 } -# Print the PID of the askpass process and fifo path with a file descriptor opened to -get_askpass_pid() { - psinfo=$(ps) # Doing this so I don't end up matching myself - echo "$psinfo" | awk "/$cryptkeyscript/ { print \$1 }" | while read -r pid; do - pf=$(get_fifo_path "${pid}") - if [[ $pf != "" ]]; then - echo "${pid} ${pf}" - break - fi - done -} +# Gets the luks device to be unlocked and used pins +get_pid_device_pins() { + local pid="$1" + local CRYPTTAB_SOURCE -luks1_decrypt() { - local CRYPTTAB_SOURCE=$1 - local PASSFIFO=$2 - UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e - luksmeta show -d "$CRYPTTAB_SOURCE" | while read -r slot state uuid; do - [ "$state" == "active" ] || continue - [ "$uuid" == "$UUID" ] || continue + CRYPTTAB_SOURCE=$(tr '\0' '\n' 2>/dev/null /dev/null) - [ $? -eq 0 ] || continue - - # Fail safe - [ "$decrypted" != "" ] || continue - - echo -n "${decrypted}" >"$PASSFIFO" + if pt=$(clevis_luks_unlock_device "${CRYPTTAB_SOURCE}"); then + echo -n "${pt}" >"${PASSFIFO}" return 0 - done - - return 1 -} - -has_tang_pin() { - local dev="$1" - - clevis luks list -d "${dev}" | grep -q tang + else + return 1 + fi } # Wait for askpass, and then try and decrypt immediately. Just in case @@ -118,6 +106,13 @@ clevisloop() { # Set the path how we want it (Probably not all needed) PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin" + local cryptkeyscript + local askpass_info + local sleep_time + local OLD_CRYPTTAB_SOURCE="" + local netcfg_attempted=0 + local tpm1cfg_attempted=0 + if [ -x /bin/plymouth ] && plymouth --ping; then cryptkeyscript='plymouth ask-for-password' else @@ -125,64 +120,55 @@ clevisloop() { cryptkeyscript='\/lib\/cryptsetup\/askpass' fi - OLD_CRYPTTAB_SOURCE="" - local netcfg_attempted=0 - while true; do - # Re-get the askpass PID in case there are multiple encrypted devices - pid="" - until [ "$pid" ] && [ -p "$PASSFIFO" ]; do - sleep .1 - pid_fifo=$(get_askpass_pid) - pid=$(echo "${pid_fifo}" | cut -d' ' -f1) - PASSFIFO=$(echo "${pid_fifo}" | cut -d' ' -f2-) + CRYPTTAB_SOURCE="" + sleep_time=.1 + until [ -n "$CRYPTTAB_SOURCE" ] && [ -p "$PASSFIFO" ]; do + sleep $sleep_time + if askpass_info=$(get_askpass_info "$cryptkeyscript"); then + # Workaround for initramfs-tools checking the script as sh-compatible + IFS=':' read -r CRYPTTAB_SOURCE pins PASSFIFO <&1); then + if [ -n "$tcsd_output" ]; then + log_failure_msg "failed to start TCSD: $tcsd_output" + else + log_failure_msg "failed to start TCSD" + fi + fi + + log_end_msg +} + +mkdir -p /var/cache/clevis-disks +chmod 0700 /var/cache/clevis-disks + clevisloop & echo $! >/run/clevis.pid diff --git a/src/luks/clevis-luks-tpm1-functions b/src/luks/clevis-luks-tpm1-functions new file mode 100755 index 00000000..6656f7a0 --- /dev/null +++ b/src/luks/clevis-luks-tpm1-functions @@ -0,0 +1,97 @@ +#!/bin/sh +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +start_tcsd() { + [ -s /run/tcsd.pid ] && return 0 + + if ! ip link show up dev lo | grep -qw UP; then + ip link set dev lo up && echo "lo" > /tmp/tcsd.if || : + if ! ip link show up dev lo | grep -qw UP; then + echo "Unable to set-up loopback network device" + return 1 + fi + fi + + if ! temp_dir="$(mktemp -d)"; then + echo "Unable to create temporary directory" + return 1 + fi + + fifo_file="$temp_dir/fifo" + output_file="$temp_dir/output" + + # If we have udev, let the initialization on udev + if ! [ -f /lib/udev/rules.d/60-tpm-udev.rules ]; then + chown tss: /dev/tpm0 + chmod 660 /dev/tpm0 + fi + + mkfifo "$fifo_file" + + # Start timeout to finish TCSD startup + sleep 10 & + sleep_pid=$! + + # The following loop ends when output side of FIFO closes (i.e. TCSD ends) + { while IFS= read -r LINE; do + echo "$LINE" + case "$LINE" in + *"TCSD up and running"*) + kill $sleep_pid 2>/dev/null + ;; + esac + done < $fifo_file && kill $sleep_pid; } >> "$output_file" 2>&1 & + + # TCSD in background mode logs into syslogd, so we would not have any logs + # available for debugging, so start TCSD in foreground mode, but as a + # background job. Unfortunatelly the redirected output to pipe is + # block-buffered (see `man 3 setbuf`), so in order to see any output we + # need to set it to line-buffered with stdbuf tool + stdbuf -oL tcsd -f >$fifo_file 2>&1 & + tcsd_pid=$! + + wait $sleep_pid 2>/dev/null + + if { ps -A 2>/dev/null || ps; } | awk -v pid="$tcsd_pid" '$1==pid {found=1} END {exit !found}'; then + ret=0 + echo $tcsd_pid > /run/tcsd.pid + else + ret=1 + [ -s "$output_file" ] && cat "$output_file" + fi + + rm -rf "$temp_dir" + + return $ret +} + +stop_tcsd() { + [ -s /run/tcsd.pid ] && { + pid=$(cat /run/tcsd.pid) + kill $pid >/dev/null 2>&1 || : + rm -f /run/tcsd.pid + } + + [ -s /tmp/tcsd.if ] && { + ip link set dev lo down || : + ip addr flush dev lo || : + rm -f /tmp/tcsd.if + } +} diff --git a/src/luks/meson.build b/src/luks/meson.build index 8a8394e0..818ffc0d 100644 --- a/src/luks/meson.build +++ b/src/luks/meson.build @@ -66,6 +66,8 @@ if libcryptsetup.found() and luksmeta.found() bins += join_paths(meson.current_source_dir(), 'clevis-luks-pass') mans += join_paths(meson.current_source_dir(), 'clevis-luks-pass.1') + + install_data('clevis-luks-tpm1-functions', install_dir: libexecdir) else warning('Will not install LUKS support due to missing dependencies!') endif From ca99564dac556240750d19ede78b961ed3961af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Wed, 30 Oct 2024 23:55:20 +0100 Subject: [PATCH 12/16] dracut: added TPM 1.2 support for Dracut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- src/luks/dracut/clevis-pin-tpm1/meson.build | 14 ++ .../dracut/clevis-pin-tpm1/module-setup.sh.in | 171 ++++++++++++++++++ src/luks/dracut/clevis/clevis-cleanup.in | 5 + .../dracut/clevis/clevis-password-unlocker.in | 24 +++ src/luks/dracut/meson.build | 1 + 5 files changed, 215 insertions(+) create mode 100644 src/luks/dracut/clevis-pin-tpm1/meson.build create mode 100755 src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in diff --git a/src/luks/dracut/clevis-pin-tpm1/meson.build b/src/luks/dracut/clevis-pin-tpm1/meson.build new file mode 100644 index 00000000..7f229f3f --- /dev/null +++ b/src/luks/dracut/clevis-pin-tpm1/meson.build @@ -0,0 +1,14 @@ +dracut = dependency('dracut', required: false) + +if dracut.found() + dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + '-pin-tpm1' + + configure_file( + input: 'module-setup.sh.in', + output: 'module-setup.sh', + install_dir: dracutdir, + configuration: data, + ) +else + warning('Will not install dracut module clevis-pin-tpm1 due to missing dependencies!') +endif diff --git a/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in b/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in new file mode 100755 index 00000000..66e0be15 --- /dev/null +++ b/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in @@ -0,0 +1,171 @@ +#!/bin/bash +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# 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 . +# + +require_file() { + local path="$1" + if ! [ -f "$path" ]; then + # shellcheck disable=SC2154 # $moddir is a dracut variable + local _module_name="${moddir##*/[0-9][0-9]}" + dinfo "dracut module '${_module_name}' will not be installed, because file '$path' could not be found!" + return 1 + fi + return 0 +} + +require_files() { + local _ret=0 + for path in "$@"; do + require_file "$path" || ((_ret++)) + done + return "$_ret" +} + +require_dir() { + local path="$1" + if ! [ -d "$path" ]; then + local _module_name="${moddir##*/[0-9][0-9]}" + dinfo "dracut module '${_module_name}' will not be installed, because directory '$path' could not be found!" + return 1 + fi + return 0 +} + +require_nonempty_dir() { + local path="$1" + require_dir "$path" || return 1 + # See https://superuser.com/a/667095 how to test empty dir + files=$(shopt -s nullglob; shopt -u dotglob; echo "$path/"*) + if ! [[ "$files" ]]; then + local _module_name="${moddir##*/[0-9][0-9]}" + dinfo "dracut module '${_module_name}' will not be installed, because directory '$path' is empty!" + return 1 + fi + return 0 +} + +check() { + local _module_name="${moddir##*/[0-9][0-9]}" + + require_binaries clevis-decrypt-tpm1 tpm_version tpm_unsealdata tcsd stdbuf || return 1 + if [[ $hostonly ]]; then + require_nonempty_dir /var/lib/tpm || return 1 + else + [ -f /usr/share/trousers/system.data.auth ] || \ + [ -f /var/lib/tpm/system.data.auth ] || \ + ddebug " ${_module_name}: no usable system.data.auth in /usr/share/trousers nor /var/lib/tpm found, using built-in one" + fi + if dracut_module_included "systemd"; then + # shellcheck disable=SC2154 # $systemdsystemunitdir is a dracut variable + require_files \ + "$systemdsystemunitdir"/tcsd.service \ + "$systemdsystemunitdir"/tcsd.service.d/clevis-tcsd.conf \ + || return 1 + fi + return 0 +} + +depends() { + echo clevis network + return 0 +} + +install() { + if dracut_module_included "systemd"; then + inst_multiple \ + "$systemdsystemunitdir/tcsd.service" \ + "$systemdsystemunitdir/tcsd.service.d/clevis-tcsd.conf" + # shellcheck disable=SC2154 # $initdir is a dracut variable + systemctl -q --root "$initdir" add-wants cryptsetup.target tcsd.service + else + inst_multiple \ + awk chmod chown mkfifo mktemp ip ps stdbuf \ + @libexecdir@/clevis-luks-tpm1-functions + if [ -f /usr/libexec/coreutils/libstdbuf.so ]; then + inst_multiple /usr/libexec/coreutils/libstdbuf.so* + else + inst_libdir_file 'coreutils/libstdbuf.so*' + fi + fi + + inst_multiple \ + clevis-decrypt-tpm1 \ + tcsd \ + tpm_version \ + tpm_unsealdata + + inst_rules 60-tpm-udev.rules + + if ! [[ $hostonly ]] || ! dracut_module_included "systemd"; then + # /etc/hosts is installed only in host-only mode with systemd, so + # we need to create our own in order to get tpm tools working. + # The localhost entry is required by tpm tools. + if [ ! -f "$initdir/etc/hosts" ]; then + echo "127.0.0.1 localhost" >> "$initdir/etc/hosts" + echo "::1 localhost ip6-localhost ip6-loopback" >> "$initdir/etc/hosts" + echo "ff02::1 ip6-allnodes" >> "$initdir/etc/hosts" + echo "ff02::2 ip6-allrouters" >> "$initdir/etc/hosts" + fi + fi + + if [[ $hostonly ]]; then + inst /etc/tcsd.conf + inst_multiple /var/lib/tpm/* + else + inst_dir /etc + touch "$initdir/etc/tcsd.conf" + if [ -f "/etc/tcsd.conf" ] && [[ $(stat -c "0%a" "/etc/tcsd.conf") = "0600" ]]; then + # Compatibility with tcsd version 0.3.14 + chmod 0600 "$initdir/etc/tcsd.conf" + chown tss:tss "$initdir/etc/tcsd.conf" + else + chmod 0640 "$initdir/etc/tcsd.conf" + chown root:tss "$initdir/etc/tcsd.conf" + fi + + inst_dir /var/lib/tpm + if [ -f /usr/share/trousers/system.data.auth ]; then + inst /usr/share/trousers/system.data.auth /var/lib/tpm/system.data + elif [ -f /var/lib/tpm/system.data.auth ]; then + inst /var/lib/tpm/system.data.auth /var/lib/tpm/system.data + else + jose b64 dec -i- >"$initdir/var/lib/tpm/system.data" <&1); then + if [ -n "$tcsd_output" ]; then + echo "Unable to start TCSD: $tcsd_output" | vwarn + else + warn "Unable to start TCSD" + fi + fi +} + mkdir -p /var/cache/clevis-disks chmod 0700 /var/cache/clevis-disks diff --git a/src/luks/dracut/meson.build b/src/luks/dracut/meson.build index 99282309..96a0d890 100644 --- a/src/luks/dracut/meson.build +++ b/src/luks/dracut/meson.build @@ -1,5 +1,6 @@ subdir('clevis') subdir('clevis-pin-tang') +subdir('clevis-pin-tpm1') subdir('clevis-pin-tpm2') subdir('clevis-pin-sss') subdir('clevis-pin-null') From f0cd2a3009083e3ce4643e1a2f03dc352032ea60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Tue, 1 Oct 2024 11:39:34 +0200 Subject: [PATCH 13/16] tpm1: allow testing tpm1 pin in CI build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- .github/workflows/build.yml | 2 +- .github/workflows/install-dependencies | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 792cc171..7ac48589 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: matrix: os: - fedora:latest - - quay.io/centos/centos:stream10-development + - quay.io/centos/centos:stream10 - quay.io/centos/centos:stream9 - debian:testing - debian:latest diff --git a/.github/workflows/install-dependencies b/.github/workflows/install-dependencies index 474e7803..9fd123fa 100755 --- a/.github/workflows/install-dependencies +++ b/.github/workflows/install-dependencies @@ -23,7 +23,8 @@ 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 opensc pcscd libsofthsm2-dev; do + libudisks2-dev libaudit-dev systemd opensc pcscd libsofthsm2-dev \ + swtpm-tools tpm-tools; do sleep 5 done ;; @@ -35,7 +36,7 @@ debian:*|ubuntu:*) dnf -y install --allowerasing systemd awk dnf -y --setopt=deltarpm=0 update dnf -y install dnf-utils jq socat cryptsetup keyutils cracklib-dicts lsof \ - opensc pcsc-lite softhsm + opensc pcsc-lite softhsm swtpm-tools tpm-tools trousers command -v dnf5 && dnf5 -y install dnf5-command\(builddep\) \ || dnf -y install dnf-command\(builddep\) dnf -y builddep clevis @@ -48,11 +49,12 @@ debian:*|ubuntu:*) yum config-manager -y --set-enabled crb || yum config-manager \ -y --set-enabled powertools || : yum -y install epel-release + yum -y install epel-next-release || : 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 opensc \ - pcsc-lite softhsm + pcsc-lite softhsm swtpm-tools tpm-tools trousers sed -i 's|>=1\.0\.2|>=1\.0\.1|' meson.build ;; esac From 2e4f477a716bead6d0704bde7dc3be4ff6eae0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Wed, 2 Oct 2024 22:56:13 +0200 Subject: [PATCH 14/16] tpm2: added TPM 2 software TPM tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- .github/workflows/install-dependencies | 2 +- src/pins/tpm2/meson.build | 20 +-- src/pins/tpm2/tests/meson.build | 39 +++++ src/pins/tpm2/tests/pin-tpm2-hw | 26 ++++ src/pins/tpm2/tests/pin-tpm2-sw | 27 ++++ .../tpm2/{pin-tpm2 => tests/pin-tpm2-tests} | 61 +------- .../tpm2/tests/tpm2-common-test-functions.in | 133 ++++++++++++++++++ 7 files changed, 229 insertions(+), 79 deletions(-) create mode 100644 src/pins/tpm2/tests/meson.build create mode 100755 src/pins/tpm2/tests/pin-tpm2-hw create mode 100755 src/pins/tpm2/tests/pin-tpm2-sw rename src/pins/tpm2/{pin-tpm2 => tests/pin-tpm2-tests} (74%) create mode 100644 src/pins/tpm2/tests/tpm2-common-test-functions.in diff --git a/.github/workflows/install-dependencies b/.github/workflows/install-dependencies index 9fd123fa..445013bd 100755 --- a/.github/workflows/install-dependencies +++ b/.github/workflows/install-dependencies @@ -24,7 +24,7 @@ debian:*|ubuntu:*) build-essential pkg-config libssl-dev libjansson-dev libjose-dev \ luksmeta libluksmeta-dev libpwquality-tools libglib2.0-dev \ libudisks2-dev libaudit-dev systemd opensc pcscd libsofthsm2-dev \ - swtpm-tools tpm-tools; do + swtpm-tools tpm-tools tpm2-tools; do sleep 5 done ;; diff --git a/src/pins/tpm2/meson.build b/src/pins/tpm2/meson.build index ec01dcf9..19077106 100644 --- a/src/pins/tpm2/meson.build +++ b/src/pins/tpm2/meson.build @@ -11,25 +11,7 @@ if all bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm2') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm2') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm2.1') + subdir('tests') else warning('Will not install tpm2 pin due to missing dependencies!') endif - -# Tests. -env = environment() -env.prepend('PATH', - join_paths(meson.source_root(), 'src'), - join_paths(meson.source_root(), 'src', 'luks'), - join_paths(meson.source_root(), 'src', 'luks', 'tests'), - join_paths(meson.source_root(), 'src', 'pins', 'sss'), - join_paths(meson.source_root(), 'src', 'pins', 'tang'), - join_paths(meson.source_root(), 'src', 'pins', 'tpm2'), - join_paths(meson.build_root(), 'src'), - join_paths(meson.build_root(), 'src', 'luks'), - join_paths(meson.build_root(), 'src', 'luks', 'tests'), - join_paths(meson.build_root(), 'src', 'pins', 'sss'), - join_paths(meson.build_root(), 'src', 'pins', 'tang'), - join_paths(meson.build_root(), 'src', 'pins', 'tpm2'), - separator: ':' -) -test('pin-tpm2', find_program('pin-tpm2'), env: env, timeout: 120) diff --git a/src/pins/tpm2/tests/meson.build b/src/pins/tpm2/tests/meson.build new file mode 100644 index 00000000..f3e21e1f --- /dev/null +++ b/src/pins/tpm2/tests/meson.build @@ -0,0 +1,39 @@ +# Tests. +env = environment() +env.prepend('PATH', + join_paths(meson.source_root(), 'src'), + join_paths(meson.source_root(), 'src', 'luks'), + join_paths(meson.source_root(), 'src', 'luks', 'tests'), + join_paths(meson.source_root(), 'src', 'pins', 'sss'), + join_paths(meson.source_root(), 'src', 'pins', 'tang'), + join_paths(meson.source_root(), 'src', 'pins', 'tpm2'), + join_paths(meson.source_root(), 'src', 'pins', 'tpm2', '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.build_root(), 'src', 'pins', 'sss'), + join_paths(meson.build_root(), 'src', 'pins', 'tang'), + join_paths(meson.build_root(), 'src', 'pins', 'tpm2'), + join_paths(meson.build_root(), 'src', 'pins', 'tpm2', 'tests'), + separator: ':' +) + +tpm2_data = configuration_data() +tpm2_data.merge_from(data) + +socat = find_program('socat', required: false) +swtpm = find_program('swtpm', '/usr/bin/swtpm', required: false) +swtpm_setup = find_program('swtpm_setup', '/usr/bin/swtpm_setup', required: false) + +tpm2_data.set('SOCAT_BIN', socat.found() ? socat.path() : '') +tpm2_data.set('SWTPM_BIN', swtpm.found() ? swtpm.path() : '') +tpm2_data.set('SWTPM_SETUP_BIN', swtpm_setup.found() ? swtpm_setup.path() : '') + +configure_file( + input: 'tpm2-common-test-functions.in', + output: 'tpm2-common-test-functions', + configuration: tpm2_data, +) + +test('pin-tpm2-hw', find_program('pin-tpm2-hw'), env: env, timeout: 120) +test('pin-tpm2-sw', find_program('pin-tpm2-sw'), env: env, timeout: 120) diff --git a/src/pins/tpm2/tests/pin-tpm2-hw b/src/pins/tpm2/tests/pin-tpm2-hw new file mode 100755 index 00000000..2c444d13 --- /dev/null +++ b/src/pins/tpm2/tests/pin-tpm2-hw @@ -0,0 +1,26 @@ +#!/bin/bash -x +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2019 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 . +# + +. tpm2-common-test-functions + +tpm2_hw_available || skip_test +tpm2_version || skip_test + +. pin-tpm2-tests diff --git a/src/pins/tpm2/tests/pin-tpm2-sw b/src/pins/tpm2/tests/pin-tpm2-sw new file mode 100755 index 00000000..8b64c136 --- /dev/null +++ b/src/pins/tpm2/tests/pin-tpm2-sw @@ -0,0 +1,27 @@ +#!/bin/bash -x +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2019 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 . +# + +. tpm2-common-test-functions + +tpm2_sw_check_preconditions +tpm2_version || skip_test +tpm2_start_emulation + +. pin-tpm2-tests diff --git a/src/pins/tpm2/pin-tpm2 b/src/pins/tpm2/tests/pin-tpm2-tests similarity index 74% rename from src/pins/tpm2/pin-tpm2 rename to src/pins/tpm2/tests/pin-tpm2-tests index 5ef0a6a3..a9154ca2 100755 --- a/src/pins/tpm2/pin-tpm2 +++ b/src/pins/tpm2/tests/pin-tpm2-tests @@ -20,43 +20,6 @@ TEST=$(basename "${0}") -# Code to return to mark test as skipped. -SKIP_RET_CODE=77 - -tpm2_available() { - # Old environment variables for tpm2-tools 3.0 - export TPM2TOOLS_TCTI_NAME=device - export TPM2TOOLS_DEVICE_FILE= - for dev in /dev/tpmrm?; do - [ -e "${dev}" ] || continue - TPM2TOOLS_DEVICE_FILE="${dev}" - break - done - - # New environment variable for tpm2-tools >= 3.1 - export TPM2TOOLS_TCTI="${TPM2TOOLS_TCTI_NAME}:${TPM2TOOLS_DEVICE_FILE}" - - if [ -z "${TPM2TOOLS_DEVICE_FILE}" ]; then - echo "A TPM2 device with the in-kernel resource manager is needed!" >&2 - return 1 - fi - - if ! [[ -r "${TPM2TOOLS_DEVICE_FILE}" \ - && -w "${TPM2TOOLS_DEVICE_FILE}" ]]; then - echo "The ${TPM2TOOLS_DEVICE_FILE} device must be readable and writable!" >&2 - return 1 - fi - - local _tpm2tools_info="$(tpm2_createprimary -v)" - local _match='version="(.)\.' - [[ ${_tpm2tools_info} =~ ${_match} ]] && TPM2TOOLS_VERSION="${BASH_REMATCH[1]}" - if [[ $TPM2TOOLS_VERSION -lt 3 ]] || [[ $TPM2TOOLS_VERSION -gt 5 ]]; then - echo "The tpm2 pin requires a tpm2-tools version between 3 and 5" >&2 - return 1 - fi - export TPM2TOOLS_VERSION -} - validate_pcrs() { local _pcr_bank="${1}" local _pcrs="${2}" @@ -77,11 +40,6 @@ validate_pcrs() { return 0 } -# Checking if we can run this test. -if ! tpm2_available; then - exit ${SKIP_RET_CODE} -fi - decode_jwe() { local jwe="${1}" @@ -160,32 +118,17 @@ else fi # Test with policies if we have the PIN rewrite available -if ! $(which clevis-pin-tpm2 >/dev/null 2>&1); +if ! command -v clevis-pin-tpm2 >/dev/null 2>&1; then echo "No PIN rewrite available" exit 0 fi -if ! $(which clevis-pin-tpm2-signtool >/dev/null 2>&1); +if ! command -v clevis-pin-tpm2-signtool >/dev/null 2>&1; then echo "No policy signtool available" exit 0 fi -function on_exit() { - popd - if [ ! -d "$TMP" ] || ! rm -rf "$TMP"; then - echo "Delete temporary files failed!" >&2 - echo "You need to clean up: $TMP" >&2 - exit 1 - fi -} -if ! TMP="$(mktemp -d)"; then - echo "Creating a temporary dir for TPM files failed!" >&2 - exit 1 -fi -trap 'on_exit' EXIT -pushd $TMP - clevis-pin-tpm2-signtool >policy_working.json << EOP --- - policy_ref: diff --git a/src/pins/tpm2/tests/tpm2-common-test-functions.in b/src/pins/tpm2/tests/tpm2-common-test-functions.in new file mode 100644 index 00000000..f5e5f498 --- /dev/null +++ b/src/pins/tpm2/tests/tpm2-common-test-functions.in @@ -0,0 +1,133 @@ +#!/bin/bash -x +# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: +# +# Copyright (c) 2019 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 . +# + +. tests-common-functions + +SOCAT_BIN="@SOCAT_BIN@" +SWTPM_BIN="@SWTPM_BIN@" +SWTPM_SETUP_BIN="@SWTPM_SETUP_BIN@" + +SWTPM_SOCKET_PID= + +function on_exit() { + popd || error "Unable to change directory" + if [ ! -d "$TESTDIR" ] || ! rm -rf "$TESTDIR"; then + echo "Delete temporary files failed!" >&2 + echo "You need to clean up: $TESTDIR" >&2 + exit 1 + fi + + # Cleanup sw emulation + if [ -n "$SWTPM_SOCKET_PID" ]; then + kill $SWTPM_SOCKET_PID >/dev/null 2>&1 + sleep .5 + # swtpm does not always terminate gracefully, so kill it + kill -9 $SWTPM_SOCKET_PID >/dev/null 2>&1 + fi +} +if ! TESTDIR="$(mktemp -d)"; then + echo "Creating a temporary dir for TPM files failed!" >&2 + exit 1 +fi +trap 'on_exit' EXIT +pushd "$TESTDIR" || error "Unable to change directory" + + +tpm2_hw_available() { + # Old environment variables for tpm2-tools 3.0 + export TPM2TOOLS_TCTI_NAME=device + export TPM2TOOLS_DEVICE_FILE= + for dev in /dev/tpmrm?; do + [ -e "${dev}" ] || continue + TPM2TOOLS_DEVICE_FILE="${dev}" + break + done + + # New environment variable for tpm2-tools >= 3.1 + export TPM2TOOLS_TCTI="${TPM2TOOLS_TCTI_NAME}:${TPM2TOOLS_DEVICE_FILE}" + + if [ -z "${TPM2TOOLS_DEVICE_FILE}" ]; then + echo "A TPM2 device with the in-kernel resource manager is needed!" >&2 + return 1 + fi + + if ! [[ -r "${TPM2TOOLS_DEVICE_FILE}" \ + && -w "${TPM2TOOLS_DEVICE_FILE}" ]]; then + echo "The ${TPM2TOOLS_DEVICE_FILE} device must be readable and writable!" >&2 + return 1 + fi + return 0 +} + +tpm2_version() { + local _tpm2tools_info + local _match='version="(.)\.' + _tpm2tools_info="$(tpm2_createprimary -v)" + [[ ${_tpm2tools_info} =~ ${_match} ]] && TPM2TOOLS_VERSION="${BASH_REMATCH[1]}" + if [[ $TPM2TOOLS_VERSION -lt 3 ]] || [[ $TPM2TOOLS_VERSION -gt 5 ]]; then + echo "The tpm2 pin requires a tpm2-tools version between 3 and 5" >&2 + return 1 + fi + export TPM2TOOLS_VERSION +} + +tpm2_sw_check_preconditions() { + [ -x "${SOCAT_BIN}" ] || skip_test "Skipping TPM2 test with software emulation, socat not found" + [ -x "${SWTPM_BIN}" ] || skip_test "Skipping TPM2 test with software emulation, swtpm not found" + [ -x "${SWTPM_SETUP_BIN}" ] || skip_test "Skipping TPM2 test with software emulation, swtpm_setup not found" + + if ! "${SWTPM_BIN}" socket --print-capabilities | jq -e '(.version | test("^0\\.[0-6](\\..*)?$")) or (.features | index("tpm-2.0"))' >/dev/null 2>&1; then + skip_test "Skipping TPM2 test with software emulation, no support for TPM 2.0 in swtpm" + fi +} + +tpm2_start_emulation() { + local socket_wait + local server_sock + local control_sock + + echo "Starting TPM 2 emulation" >&2 + + # Setup TPM 2 data + "${SWTPM_SETUP_BIN}" --tpm-state "$TESTDIR" --tpm2 --create-platform-cert --lock-nvram --display >&2 || error "Unable to setup TPM 2 emulation" + + # Start emulation over socket + server_sock="$TESTDIR"/swtpm.sock + control_sock="$TESTDIR"/swtpm.sock.ctrl + "${SWTPM_BIN}" socket --tpmstate dir="$TESTDIR" --tpm2 --ctrl type=unixio,path="$control_sock" --server type=unixio,path="$server_sock" --flags startup-clear >&2 & + SWTPM_SOCKET_PID=$! + + socket_wait=1 + while [ $socket_wait -le 100 ]; do + [ -S "$server_sock" ] && break + socket_wait=$((socket_wait + 1)) + sleep 0.1 + done + [ "$socket_wait" -gt 100 ] && error "Unable to start TPM 2 emulation" + + # Use swtpm in tpm2-tools + export TPM2TOOLS_TCTI="swtpm:path=$server_sock" + + # Test if socket communication is supported + if ! tpm2_getcap pcrs >/dev/null 2>&1; then + # Old libtss2 compatibility - use socat + export TPM2TOOLS_TCTI="cmd:\"$SOCAT_BIN\" - \"UNIX-CONNECT:$server_sock\"" + fi +} From ea3dcee818d3e5b2f5a5011eda3d3a1a09c2e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Old=C5=99ich=20Jedli=C4=8Dka?= Date: Fri, 8 Nov 2024 22:40:53 +0100 Subject: [PATCH 15/16] initramfs: fix running on root-only readable initrd filesystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oldřich Jedlička --- meson.build | 2 + src/initramfs-tools/hooks/clevis.in | 14 ++++-- .../scripts/local-top/clevis.in | 4 ++ ...unctions => clevis-luks-tpm1-functions.in} | 4 +- .../dracut/clevis-pin-tpm1/module-setup.sh.in | 10 ++-- .../dracut/clevis/clevis-password-unlocker.in | 2 +- src/luks/dracut/clevis/meson.build | 12 +++-- src/luks/meson.build | 23 ++++++++-- src/luks/systemd/meson.build | 26 ++++------- src/pins/tpm1/clevis-tpm1-tcsd-preload.c | 46 +++++++++++++++++++ src/pins/tpm1/meson.build | 7 +++ src/pins/tpm1/tests/meson.build | 1 + src/pins/tpm1/tests/tcsd-patch.c.in | 15 ------ .../tpm1/tests/tpm1-common-test-functions.in | 6 ++- 14 files changed, 116 insertions(+), 56 deletions(-) rename src/luks/{clevis-luks-tpm1-functions => clevis-luks-tpm1-functions.in} (93%) create mode 100644 src/pins/tpm1/clevis-tpm1-tcsd-preload.c diff --git a/meson.build b/meson.build index 7bf0bff4..b64653f3 100644 --- a/meson.build +++ b/meson.build @@ -6,11 +6,13 @@ project('clevis', 'c', license: 'GPL3+', libexecdir = join_paths(get_option('prefix'), get_option('libexecdir')) sysconfdir = join_paths(get_option('prefix'), get_option('sysconfdir')) bindir = join_paths(get_option('prefix'), get_option('bindir')) +libdir = join_paths(get_option('prefix'), get_option('libdir')) data = configuration_data() data.set('libexecdir', libexecdir) data.set('sysconfdir', sysconfdir) data.set('bindir', bindir) +data.set('libdir', libdir) add_project_arguments( '-Wall', diff --git a/src/initramfs-tools/hooks/clevis.in b/src/initramfs-tools/hooks/clevis.in index 8a7d76bb..9dcb2baa 100755 --- a/src/initramfs-tools/hooks/clevis.in +++ b/src/initramfs-tools/hooks/clevis.in @@ -99,14 +99,13 @@ fi if [ -x @bindir@/clevis-decrypt-tpm1 ]; then copy_exec @bindir@/clevis-decrypt-tpm1 || die 1 "@bindir@/clevis-decrypt-tpm1 not found" copy_exec @libexecdir@/clevis-luks-tpm1-functions || die 1 "@libexecdir@/clevis-luks-tpm1-functions not found" + copy_exec @libdir@/libclevis-tpm1-tcsd-preload.so || die 1 "@libdir@/libclevis-tpm1-tcsd-preload.so not found" tcsd_bin=$(find_binary "tcsd") # libgcc_s.so.* is no longer installed for gcc 2.34+ (no link to libpthread) libgcc_s=$(find_library "libgcc_s.so.[1-9]") tpm_version_bin=$(find_binary "tpm_version") tpm_unsealdata_bin=$(find_binary "tpm_unsealdata") - stdbuf_bin=$(find_binary "stdbuf") - libstdbuf_bin=$(find_library "coreutils/libstdbuf.so*") copy_exec "${tpm_version_bin}" || die 1 "Unable to copy ${tpm_version_bin}" copy_exec "${tpm_unsealdata_bin}" || die 1 "Unable to copy ${tpm_unsealdata_bin}" @@ -115,14 +114,19 @@ if [ -x @bindir@/clevis-decrypt-tpm1 ]; then copy_exec "${libgcc_s}" || die 1 "Unable to copy ${libgcc_s}" copy_file config /etc/tcsd.conf || dia 2 "Unable to copy /etc/tcsd.conf" - copy_exec "${stdbuf_bin}" || die 1 "Unable to copy ${stdbuf_bin}" - copy_exec "${libstdbuf_bin}" || die 1 "Unable to copy ${libstdbuf_bin}" - mkdir -p "${DESTDIR}/var/lib/tpm" || die 2 "Unable to create /var/lib/tpm" cp /var/lib/tpm/* "${DESTDIR}/var/lib/tpm/" || die 2 "Unable to copy /var/lib/tpm" chown -R tss:tss "${DESTDIR}/var/lib/tpm" || die 2 "Unable to change owner of /var/lib/tpm" chmod -R u=rwX,go= "${DESTDIR}/var/lib/tpm" || die 2 "Unable to change permissions of /var/lib/tpm" + if (( $(umask) & 0004 )); then + # Root-only readable initrd filesystem, we need to run as root + # shellcheck disable=SC2154 # $verbose is a dracut variable + [ "${verbose}" = "y" ] && echo "Forcing tcsd to run as root" + sed -i 's/^\([ ]*remote_ops\)/#\1/' "${DESTDIR}/etc/tcsd.conf" + echo "TCSD_NO_PRIVILEGE_DROP=1" >> "${DESTDIR}/conf/conf.d/clevis" + fi + mkdir -p "${DESTDIR}/lib/udev/rules.d" || die 2 "Unable to create /lib/udev/rules.d" # shellcheck disable=SC2043 for rule in 60-tpm-udev.rules; do diff --git a/src/initramfs-tools/scripts/local-top/clevis.in b/src/initramfs-tools/scripts/local-top/clevis.in index 30e978ab..2846fbaa 100755 --- a/src/initramfs-tools/scripts/local-top/clevis.in +++ b/src/initramfs-tools/scripts/local-top/clevis.in @@ -282,6 +282,10 @@ do_configure_tpm1() { wait_for_udev 10 + # shellcheck disable=SC2034 # setting default value + TCSD_NO_PRIVILEGE_DROP=0 + [ -f /conf/conf.d/clevis ] && . /conf/conf.d/clevis + if ! tcsd_output=$(start_tcsd 2>&1); then if [ -n "$tcsd_output" ]; then log_failure_msg "failed to start TCSD: $tcsd_output" diff --git a/src/luks/clevis-luks-tpm1-functions b/src/luks/clevis-luks-tpm1-functions.in similarity index 93% rename from src/luks/clevis-luks-tpm1-functions rename to src/luks/clevis-luks-tpm1-functions.in index 6656f7a0..969b7645 100755 --- a/src/luks/clevis-luks-tpm1-functions +++ b/src/luks/clevis-luks-tpm1-functions.in @@ -63,8 +63,8 @@ start_tcsd() { # available for debugging, so start TCSD in foreground mode, but as a # background job. Unfortunatelly the redirected output to pipe is # block-buffered (see `man 3 setbuf`), so in order to see any output we - # need to set it to line-buffered with stdbuf tool - stdbuf -oL tcsd -f >$fifo_file 2>&1 & + # need to set it to line-buffered with LD_PRELOAD library + TCSD_NO_PRIVILEGE_DROP=${TCSD_NO_PRIVILEGE_DROP:-0} LD_PRELOAD="@libdir@/libclevis-tpm1-tcsd-preload.so" tcsd -f >$fifo_file 2>&1 & tcsd_pid=$! wait $sleep_pid 2>/dev/null diff --git a/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in b/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in index 66e0be15..10334f60 100755 --- a/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in +++ b/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in @@ -64,7 +64,7 @@ require_nonempty_dir() { check() { local _module_name="${moddir##*/[0-9][0-9]}" - require_binaries clevis-decrypt-tpm1 tpm_version tpm_unsealdata tcsd stdbuf || return 1 + require_binaries clevis-decrypt-tpm1 tpm_version tpm_unsealdata tcsd || return 1 if [[ $hostonly ]]; then require_nonempty_dir /var/lib/tpm || return 1 else @@ -96,13 +96,9 @@ install() { systemctl -q --root "$initdir" add-wants cryptsetup.target tcsd.service else inst_multiple \ - awk chmod chown mkfifo mktemp ip ps stdbuf \ + awk chmod chown mkfifo mktemp ip ps \ + @libdir@/libclevis-tpm1-tcsd-preload.so \ @libexecdir@/clevis-luks-tpm1-functions - if [ -f /usr/libexec/coreutils/libstdbuf.so ]; then - inst_multiple /usr/libexec/coreutils/libstdbuf.so* - else - inst_libdir_file 'coreutils/libstdbuf.so*' - fi fi inst_multiple \ diff --git a/src/luks/dracut/clevis/clevis-password-unlocker.in b/src/luks/dracut/clevis/clevis-password-unlocker.in index 9076ea2e..c64895cf 100755 --- a/src/luks/dracut/clevis/clevis-password-unlocker.in +++ b/src/luks/dracut/clevis/clevis-password-unlocker.in @@ -168,7 +168,7 @@ do_configure_tpm1() { info "Starting TCSD daemon" - if ! tcsd_output=$(start_tcsd 2>&1); then + if ! tcsd_output=$(TCSD_NO_PRIVILEGE_DROP=0 start_tcsd 2>&1); then if [ -n "$tcsd_output" ]; then echo "Unable to start TCSD: $tcsd_output" | vwarn else diff --git a/src/luks/dracut/clevis/meson.build b/src/luks/dracut/clevis/meson.build index 4fd4486b..eed325f6 100644 --- a/src/luks/dracut/clevis/meson.build +++ b/src/luks/dracut/clevis/meson.build @@ -3,32 +3,36 @@ dracut = dependency('dracut', required: false) if dracut.found() dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + dracut_data = configuration_data() + dracut_data.merge_from(data) + dracut_data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) + configure_file( input: 'module-setup.sh.in', output: 'module-setup.sh', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) configure_file( input: 'clevis-cleanup.in', output: 'clevis-cleanup', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) configure_file( input: 'clevis-password-unlocker.in', output: 'clevis-password-unlocker', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) configure_file( input: 'clevis-password-unlocker-prepare.in', output: 'clevis-password-unlocker-prepare', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) install_data('clevis-cleanup-hook.sh', install_dir: dracutdir) diff --git a/src/luks/meson.build b/src/luks/meson.build index 818ffc0d..2a9bb7b0 100644 --- a/src/luks/meson.build +++ b/src/luks/meson.build @@ -28,14 +28,31 @@ clevis_luks_common_functions = configure_file( configuration: luksmeta_data ) +clevis_luks_tpm1_functions = configure_file( + input: 'clevis-luks-tpm1-functions.in', + output: 'clevis-luks-tpm1-functions', + configuration: data +) + clevis_luks_unbind = configure_file(input: 'clevis-luks-unbind.in', output: 'clevis-luks-unbind', configuration: luksmeta_data) +# SystemD dependencies checked here, used both in systemd and dracut subdirs +systemd = dependency('systemd', required: false) +systemdutildir = systemd.found() ? systemd.get_pkgconfig_variable('systemdutildir', default: '') : '' + +sd_reply_pass = find_program( + (systemdutildir != '') ? join_paths(systemdutildir, 'systemd-reply-password') : '', + join_paths(get_option('prefix'), get_option('libdir'), 'systemd', 'systemd-reply-password'), + join_paths(get_option('prefix'), 'lib', 'systemd', 'systemd-reply-password'), + join_paths('/', 'usr', get_option('libdir'), 'systemd', 'systemd-reply-password'), + join_paths('/', 'usr', 'lib', 'systemd', 'systemd-reply-password'), + required: false +) + if libcryptsetup.found() and luksmeta.found() subdir('systemd') - # systemd should come before dracut in order to set up - # variables like SYSTEMD_REPLY_PASS. subdir('dracut') subdir('udisks2') @@ -67,7 +84,7 @@ if libcryptsetup.found() and luksmeta.found() bins += join_paths(meson.current_source_dir(), 'clevis-luks-pass') mans += join_paths(meson.current_source_dir(), 'clevis-luks-pass.1') - install_data('clevis-luks-tpm1-functions', install_dir: libexecdir) + install_data(clevis_luks_tpm1_functions, install_dir: libexecdir) else warning('Will not install LUKS support due to missing dependencies!') endif diff --git a/src/luks/systemd/meson.build b/src/luks/systemd/meson.build index e1f9180e..64e678cd 100644 --- a/src/luks/systemd/meson.build +++ b/src/luks/systemd/meson.build @@ -1,17 +1,7 @@ -systemd = dependency('systemd', required: false) -systemdutildir = systemd.found() ? systemd.get_pkgconfig_variable('systemdutildir', default: '') : '' - -sd_reply_pass = find_program( - (systemdutildir != '') ? join_paths(systemdutildir, 'systemd-reply-password') : '', - join_paths(get_option('prefix'), get_option('libdir'), 'systemd', 'systemd-reply-password'), - join_paths(get_option('prefix'), 'lib', 'systemd', 'systemd-reply-password'), - join_paths('/', 'usr', get_option('libdir'), 'systemd', 'systemd-reply-password'), - join_paths('/', 'usr', 'lib', 'systemd', 'systemd-reply-password'), - required: false -) - if systemd.found() and sd_reply_pass.found() - data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) + systemd_data = configuration_data() + systemd_data.merge_from(data) + systemd_data.set('SYSTEMD_REPLY_PASS', sd_reply_pass.path()) unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') tcsdoverridedir = join_paths(unitdir, 'tcsd.service.d') @@ -20,31 +10,31 @@ if systemd.found() and sd_reply_pass.found() input: 'clevis-luks-askpass.service.in', output: 'clevis-luks-askpass.service', install_dir: unitdir, - configuration: data, + configuration: systemd_data, ) configure_file( input: 'clevis-luks-pkcs11-askpass.service.in', output: 'clevis-luks-pkcs11-askpass.service', install_dir: unitdir, - configuration: data, + configuration: systemd_data, ) configure_file( input: 'clevis-luks-askpass.in', output: 'clevis-luks-askpass', install_dir: libexecdir, - configuration: data + configuration: systemd_data ) configure_file( input: 'clevis-luks-pkcs11-askpass.in', output: 'clevis-luks-pkcs11-askpass', install_dir: libexecdir, - configuration: data + configuration: systemd_data ) configure_file( input: 'clevis-luks-pkcs11-askpin.in', output: 'clevis-luks-pkcs11-askpin', install_dir: libexecdir, - configuration: data + configuration: systemd_data ) install_data('clevis-luks-askpass.path', install_dir: unitdir) diff --git a/src/pins/tpm1/clevis-tpm1-tcsd-preload.c b/src/pins/tpm1/clevis-tpm1-tcsd-preload.c new file mode 100644 index 00000000..3f2b4da5 --- /dev/null +++ b/src/pins/tpm1/clevis-tpm1-tcsd-preload.c @@ -0,0 +1,46 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include + +#define TCSD_NO_PRIVILEGE_DROP_ENV "TCSD_NO_PRIVILEGE_DROP" + +static int no_privilege_drop(void) { + char *no_privilege_drop_env = getenv(TCSD_NO_PRIVILEGE_DROP_ENV); + return (no_privilege_drop_env != NULL + && no_privilege_drop_env[0] != '\0' + && no_privilege_drop_env[0] != '0'); +} + +int setuid(uid_t uid) { + static int (*real_setuid)(uid_t) = NULL; + if (no_privilege_drop()) { + return 0; + } else { + if (!real_setuid) { + real_setuid = dlsym(RTLD_NEXT, "setuid"); + } + return real_setuid(uid); + } +} + +int setgid(gid_t gid) { + static int (*real_setgid)(uid_t) = NULL; + if (no_privilege_drop()) { + return 0; + } else { + if (!real_setgid) { + real_setgid = dlsym(RTLD_NEXT, "setgid"); + } + return real_setgid(gid); + } + return 0; +} + +static void __attribute ((constructor)) +set_line_buffering (void) +{ + setvbuf(stdout, NULL, _IOLBF, 0); +} diff --git a/src/pins/tpm1/meson.build b/src/pins/tpm1/meson.build index 1c07bbb4..6f7cca0b 100644 --- a/src/pins/tpm1/meson.build +++ b/src/pins/tpm1/meson.build @@ -9,6 +9,13 @@ if all bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-tpm1') bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1') mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-tpm1.1') + + libdl_dep = dependency('dl', required: true) + libclevis_tpm1_tcsd_preload = shared_library('clevis-tpm1-tcsd-preload', 'clevis-tpm1-tcsd-preload.c', + dependencies: libdl_dep, + install_dir: libdir, + install: true) + subdir('tests') else warning('Will not install tpm1 pin due to missing dependencies!') diff --git a/src/pins/tpm1/tests/meson.build b/src/pins/tpm1/tests/meson.build index d7f24642..4f7ebbd9 100644 --- a/src/pins/tpm1/tests/meson.build +++ b/src/pins/tpm1/tests/meson.build @@ -30,6 +30,7 @@ tpm1_data.set('TPM_TAKEOWNERSHIP_BIN', tpm_takeownership.found() ? tpm_takeowner tpm1_data.set('TCSD_BIN', tcsd.found() ? tcsd.path() : '') tpm1_data.set('SWTPM_BIN', swtpm.found() ? swtpm.path() : '') tpm1_data.set('SWTPM_SETUP_BIN', swtpm_setup.found() ? swtpm_setup.path() : '') +tpm1_data.set('LIBCLEVIS_TPM1_TCSD_PRELOAD', libclevis_tpm1_tcsd_preload.path()) configure_file( input: 'tpm1-common-test-functions.in', diff --git a/src/pins/tpm1/tests/tcsd-patch.c.in b/src/pins/tpm1/tests/tcsd-patch.c.in index 99e85523..7921fbc2 100644 --- a/src/pins/tpm1/tests/tcsd-patch.c.in +++ b/src/pins/tpm1/tests/tcsd-patch.c.in @@ -74,18 +74,3 @@ int __xstat(int ver, const char *pathname, struct stat *statbuf) { return result; } #endif - -int setuid(uid_t uid) { - return 0; -} - -int setgid(gid_t gid) { - return 0; -} - -static void __attribute ((constructor)) -set_line_buffering (void) -{ - setvbuf(stdout, NULL, _IOLBF, 0); - fprintf(stderr, "set_line_buffering : done\n"); -} diff --git a/src/pins/tpm1/tests/tpm1-common-test-functions.in b/src/pins/tpm1/tests/tpm1-common-test-functions.in index 20b6ee4f..228abfb8 100644 --- a/src/pins/tpm1/tests/tpm1-common-test-functions.in +++ b/src/pins/tpm1/tests/tpm1-common-test-functions.in @@ -28,6 +28,7 @@ TPM_TAKEOWNERSHIP_BIN="@TPM_TAKEOWNERSHIP_BIN@" TCSD_BIN="@TCSD_BIN@" SWTPM_BIN="@SWTPM_BIN@" SWTPM_SETUP_BIN="@SWTPM_SETUP_BIN@" +LIBCLEVIS_TPM1_TCSD_PRELOAD="@LIBCLEVIS_TPM1_TCSD_PRELOAD@" SWTPM_SOCKET_PID= TCSD_PID= @@ -131,7 +132,10 @@ EOM chmod 0640 "$TESTDIR"/tcsd.conf fi - LD_PRELOAD="$TCSD_PATCH_LIB" TCSD_UN_SOCKET_DEVICE_PATH="$TESTDIR"/swtpm.sock "${TCSD_BIN}" -f -e -c "$TESTDIR"/tcsd.conf >&2 &2 Date: Wed, 4 Dec 2024 03:16:03 +0100 Subject: [PATCH 16/16] luks: check if keyutils are usable before running tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The command fails in Docker or otherwise limited environments, so skip the test when it is not usable. Signed-off-by: Oldřich Jedlička --- src/luks/tests/meson.build | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/luks/tests/meson.build b/src/luks/tests/meson.build index cd217396..928bf4d5 100644 --- a/src/luks/tests/meson.build +++ b/src/luks/tests/meson.build @@ -8,8 +8,14 @@ cryptsetup = find_program('cryptsetup', required: true) # Use keyctl to check an existing token id can be created from # kernel keyring password keyutils = find_program('keyctl', required: false) +keyutils_usable = false if keyutils.found() - message('keyutils installed') + keyutils_usable = run_command(keyutils, 'session', '-', '/bin/true', capture: false, check: false).returncode() == 0 + if keyutils_usable + message('keyutils installed') + else + warning('keyutils installed, but running fails (are you inside Docker?), unable to test existing token id binding') + endif else warning('keyutils not installed, unable to test existing token id binding') endif @@ -82,7 +88,7 @@ if luksmeta_data.get('OLD_CRYPTSETUP') == '0' test('unbind-unbound-slot-luks2', find_program('unbind-unbound-slot-luks2'), env: env) test('unbind-luks2', find_program('unbind-luks2'), env: env, timeout: 60) - if keyutils.found() and luksmeta_data.get('OLD_CRYPTSETUP_EXISTING_TOKEN_ID') == '0' + if keyutils.found() and keyutils_usable and luksmeta_data.get('OLD_CRYPTSETUP_EXISTING_TOKEN_ID') == '0' test('bind-luks2-ext-token', find_program('bind-luks2-ext-token'), env: env, timeout: 60) endif