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 987230d4..42f7e7db 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 tpm2-tools; do sleep 5 done ;; @@ -33,7 +34,7 @@ debian:*|ubuntu:*) dnf -y clean all 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 @@ -45,12 +46,18 @@ debian:*|ubuntu:*) yum install -y yum-utils yum config-manager -y --set-enabled crb || yum config-manager \ -y --set-enabled powertools || : - yum -y install epel-release + yum -y install epel-release 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 + # EPEL-specific packages, EPEL 10 does not have tpm-tools + case "${DISTRO}" in + *centos:stream9) yum -y install tpm-tools trousers + ;; + *) ;; + esac sed -i 's|>=1\.0\.2|>=1\.0\.1|' meson.build ;; esac diff --git a/README.md b/README.md index dd3ed691..3bb7b112 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/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 3d4eb67f..516191d5 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,62 @@ 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" + 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") + + 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" + + 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 + 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 4798f203..a9a0c6fd 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,15 @@ esac [ -s /run/clevis.pid ] || exit 0 +. @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) -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/initramfs-tools/scripts/local-top/clevis.in b/src/initramfs-tools/scripts/local-top/clevis.in index 14872647..2846fbaa 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-common-functions.in b/src/luks/clevis-luks-common-functions.in index 2b393160..d78f1f94 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}")" @@ -199,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. @@ -252,8 +260,10 @@ clevis_luks_process_sss_pin() { local jwe="${1}" local threshold="${2}" + local sss_null local sss_pkcs11 local sss_tang + local sss_tpm1 local sss_tpm2 local sss local pin_cfg @@ -267,12 +277,18 @@ 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}" ;; tang) sss_tang="${sss_tang},${cfg}" ;; + tpm1) + sss_tpm1="${sss_tpm1},${cfg}" + ;; tpm2) sss_tpm2="${sss_tpm2},${cfg}" ;; @@ -283,10 +299,18 @@ 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 + 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 @@ -323,6 +347,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. @@ -1097,3 +1207,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/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/clevis-luks-tpm1-functions.in b/src/luks/clevis-luks-tpm1-functions.in new file mode 100755 index 00000000..969b7645 --- /dev/null +++ b/src/luks/clevis-luks-tpm1-functions.in @@ -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 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 + + 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/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/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..10334f60 --- /dev/null +++ b/src/luks/dracut/clevis-pin-tpm1/module-setup.sh.in @@ -0,0 +1,167 @@ +#!/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 || 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 \ + @libdir@/libclevis-tpm1-tcsd-preload.so \ + @libexecdir@/clevis-luks-tpm1-functions + 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" < +# 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..e62fa555 --- /dev/null +++ b/src/luks/dracut/clevis/clevis-cleanup.in @@ -0,0 +1,35 @@ +#!/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 + +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 + +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..c64895cf --- /dev/null +++ b/src/luks/dracut/clevis/clevis-password-unlocker.in @@ -0,0 +1,184 @@ +#!/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="" + local tpm1cfg_attempted=0 + + 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 +} + +do_configure_tpm1() { + local tcsd_output= + + [ -x @bindir@/clevis-decrypt-tpm1 ] && [ -f @libexecdir@/clevis-luks-tpm1-functions ] || return + + . @libexecdir@/clevis-luks-tpm1-functions + + info "Starting TCSD daemon" + + 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 + warn "Unable to start TCSD" + fi + fi +} + +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..eed325f6 100644 --- a/src/luks/dracut/clevis/meson.build +++ b/src/luks/dracut/clevis/meson.build @@ -3,21 +3,41 @@ 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: dracut_data, + ) + + configure_file( + input: 'clevis-password-unlocker.in', + output: 'clevis-password-unlocker', + install_dir: dracutdir, + configuration: dracut_data, ) configure_file( - input: 'clevis-hook.sh.in', - output: 'clevis-hook.sh', + input: 'clevis-password-unlocker-prepare.in', + output: 'clevis-password-unlocker-prepare', install_dir: dracutdir, - configuration: data, + configuration: dracut_data, ) - install_data('clevis-luks-unlocker', install_dir: libexecdir) + 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 55f32818..593d87a4 100755 --- a/src/luks/dracut/clevis/module-setup.sh.in +++ b/src/luks/dracut/clevis/module-setup.sh.in @@ -19,9 +19,15 @@ # depends() { - local __depends=crypt + local __depends="crypt bash" if dracut_module_included "systemd"; then - __depends=$(printf '%s systemd-cryptsetup' "${__depends}") + # Dracut v103 introduced a separate systemd-cryptsetup module + systemd_cryptsetup_dir=$(dracut_module_path "systemd-cryptsetup") + if [ -d "$systemd_cryptsetup_dir" ]; then + __depends=$(printf '%s systemd-cryptsetup' "${__depends}") + else + __depends=$(printf '%s systemd' "${__depends}") + fi fi echo "${__depends}" return 255 @@ -30,18 +36,20 @@ depends() { install() { if dracut_module_included "systemd"; then inst_multiple \ - $systemdsystemunitdir/clevis-luks-askpass.service \ - $systemdsystemunitdir/clevis-luks-askpass.path \ - $systemdsystemunitdir/cryptsetup.target \ + "$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 @@ -49,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 \ 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') diff --git a/src/luks/meson.build b/src/luks/meson.build index 8a8394e0..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') @@ -66,6 +83,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 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..97874486 100644 --- a/src/luks/systemd/clevis-luks-askpass.service.in +++ b/src/luks/systemd/clevis-luks-askpass.service.in @@ -2,6 +2,10 @@ 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 [Service] Type=simple 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..64e678cd 100644 --- a/src/luks/systemd/meson.build +++ b/src/luks/systemd/meson.build @@ -1,53 +1,45 @@ -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') configure_file( 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) 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 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-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 6c49dd90..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}" @@ -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, @@ -79,7 +80,19 @@ 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 + +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 7509faac..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}" @@ -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, @@ -79,7 +80,19 @@ 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 + +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 36c26893..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}" @@ -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, @@ -70,8 +71,20 @@ 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 + +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 c39245b3..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}" @@ -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, @@ -70,8 +71,20 @@ 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 + +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 24c187ba..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}" @@ -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. @@ -58,7 +59,19 @@ 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 + +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 0be6cfcd..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}" @@ -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. @@ -58,7 +59,19 @@ 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 + +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/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 9e51fb6c..928bf4d5 100644 --- a/src/luks/tests/meson.build +++ b/src/luks/tests/meson.build @@ -8,14 +8,20 @@ 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 -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 ) @@ -26,6 +32,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 +41,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: ':' ) @@ -54,6 +62,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 @@ -79,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 @@ -87,6 +96,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) 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/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/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/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/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/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 new file mode 100644 index 00000000..6f7cca0b --- /dev/null +++ b/src/pins/tpm1/meson.build @@ -0,0 +1,22 @@ +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') + 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!') +endif diff --git a/src/pins/tpm1/tests/meson.build b/src/pins/tpm1/tests/meson.build new file mode 100644 index 00000000..cf832188 --- /dev/null +++ b/src/pins/tpm1/tests/meson.build @@ -0,0 +1,63 @@ +# 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) +swtpm_bios = find_program('swtpm_bios', '/usr/bin/swtpm_bios', 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() : '') +tpm1_data.set('SWTPM_BIOS_BIN', swtpm_bios.found() ? swtpm_bios.path() : '') +tpm1_data.set('LIBCLEVIS_TPM1_TCSD_PRELOAD', libclevis_tpm1_tcsd_preload.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..7921fbc2 --- /dev/null +++ b/src/pins/tpm1/tests/tcsd-patch.c.in @@ -0,0 +1,76 @@ +#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 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..219e48e4 --- /dev/null +++ b/src/pins/tpm1/tests/tpm1-common-test-functions.in @@ -0,0 +1,155 @@ +#!/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_BIOS_BIN="@SWTPM_BIOS_BIN@" +LIBCLEVIS_TPM1_TCSD_PRELOAD="@LIBCLEVIS_TPM1_TCSD_PRELOAD@" + +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" + [ -x "${SWTPM_BIOS_BIN}" ] || skip_test "Skipping TPM1 test with software emulation, swtpm_bios 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 not-need-init >&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 BIOS checks + "${SWTPM_BIOS_BIN}" --unix "$TESTDIR"/swtpm.sock || error "Unable to prepare 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 + + TCSD_NO_PRIVILEGE_DROP=1 \ + LD_PRELOAD="$LIBCLEVIS_TPM1_TCSD_PRELOAD $TCSD_PATCH_LIB" \ + TCSD_UN_SOCKET_DEVICE_PATH="$TESTDIR"/swtpm.sock \ + "${TCSD_BIN}" -f -e -c "$TESTDIR"/tcsd.conf >&2 +# +# 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..c05f9033 --- /dev/null +++ b/src/pins/tpm2/tests/tpm2-common-test-functions.in @@ -0,0 +1,135 @@ +#!/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_BIOS_BIN="@SWTPM_BIOS_BIN@" + +SWTPM_SOCKET_PID= +SOCAT_SERVER_PID= +SOCAT_CONTROL_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 + [ -n "$SOCAT_SERVER_PID" ] && kill $SOCAT_SERVER_PID >/dev/null 2>&1 + [ -n "$SOCAT_CONTROL_PID" ] && kill $SOCAT_CONTROL_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" + + +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 "${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" + [ -x "${SWTPM_BIOS_BIN}" ] || skip_test "Skipping TPM2 test with software emulation, swtpm_bios 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-ek-cert --create-platform-cert --lock-nvram --display >&2 || error "Unable to setup TPM 2 emulation" + + # Start emulation over socket + server_sock="$TESTDIR"/swtpm.server.sock + control_sock="$TESTDIR"/swtpm.ctrl.sock + "${SWTPM_BIN}" socket --tpmstate dir="$TESTDIR" --tpm2 --ctrl type=unixio,path="$control_sock" --server type=unixio,path="$server_sock" --flags not-need-init >&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" + + # Run BIOS checks + "${SWTPM_BIOS_BIN}" --tpm2 --unix "$server_sock" || error "Unable to prepare TPM 2 emulation" + + # Use swtpm in tpm2-tools + export TPM2TOOLS_TCTI="cmd:\"$SOCAT_BIN\" - \"UNIX-CONNECT:$server_sock\"" +} 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 +}