diff --git a/src/initramfs-tools/hooks/clevis.in b/src/initramfs-tools/hooks/clevis.in index 3d4eb67f..e4aef627 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,53 @@ if [ -x @bindir@/clevis-decrypt-tpm2 ]; then manual_add_modules tpm_crb manual_add_modules tpm_tis fi +if [ -x @bindir@/clevis-decrypt-tpm1 ]; then + copy_exec @bindir@/clevis-decrypt-tpm1 || die 1 "@bindir@/clevis-decrypt-tpm1 not found" + copy_exec @libexecdir@/clevis-luks-tpm1-functions || die 1 "@libexecdir@/clevis-luks-tpm1-functions not found" + + tcsd_bin=$(find_binary "tcsd") + tpm_version_bin=$(find_binary "tpm_version") + tpm_unsealdata_bin=$(find_binary "tpm_unsealdata") + stdbuf_bin=$(find_binary "stdbuf") + libstdbuf_bin=$(find_library "coreutils/libstdbuf.so*") + + copy_exec "${tpm_version_bin}" || die 1 "Unable to copy ${tpm_version_bin}" + copy_exec "${tpm_unsealdata_bin}" || die 1 "Unable to copy ${tpm_unsealdata_bin}" + + copy_exec "${tcsd_bin}" || die 1 "Unable to copy ${tcsd_bin}" + copy_file config /etc/tcsd.conf || dia 2 "Unable to copy /etc/tcsd.conf" + + copy_exec "${stdbuf_bin}" || die 1 "Unable to copy ${stdbuf_bin}" + copy_exec "${libstdbuf_bin}" || die 1 "Unable to copy ${libstdbuf_bin}" + + mkdir -p "${DESTDIR}/var/lib/tpm" || die 2 "Unable to create /var/lib/tpm" + cp /var/lib/tpm/* "${DESTDIR}/var/lib/tpm/" || die 2 "Unable to copy /var/lib/tpm" + + 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 fff3d758..0483423a 100755 --- a/src/initramfs-tools/scripts/local-bottom/clevis.in +++ b/src/initramfs-tools/scripts/local-bottom/clevis.in @@ -33,10 +33,15 @@ esac [ -s /run/clevis.pid ] || exit 0 +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 | awk -v pid="$pid" '$2==pid { print $1 }') for kill_pid in $pid $child_pids; do - kill "$kill_pid" + kill "$kill_pid" done # Not really worried about downing extra interfaces: they will come up diff --git a/src/initramfs-tools/scripts/local-top/clevis.in b/src/initramfs-tools/scripts/local-top/clevis.in index 14872647..87089517 100755 --- a/src/initramfs-tools/scripts/local-top/clevis.in +++ b/src/initramfs-tools/scripts/local-top/clevis.in @@ -28,87 +28,73 @@ 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 -} - -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 +# Gets the luks device to be unlocked and used pins +get_pid_device_pins() { + local pid="$1" + local CRYPTTAB_SOURCE=$(cat /proc/${pid}/environ 2> /dev/null | \ + tr '\0' '\n' | grep '^CRYPTTAB_SOURCE=' | cut -d= -f2) - lml=$(luksmeta load -d "${CRYPTTAB_SOURCE}" -s "${slot}" -u "${UUID}") - [ $? -eq 0 ] || continue + # Wrong process, no CRYPTTAB_SOURCE, return error + [ -n "$CRYPTTAB_SOURCE" ] || return 1 - decrypted=$(echo -n "${lml}" | clevis decrypt 2>/dev/null) - [ $? -eq 0 ] || continue + [ -b "$CRYPTTAB_SOURCE" ] || return 0 - # Fail safe - [ "$decrypted" != "" ] || continue + local cache="/var/cache/clevis-disks/${CRYPTTAB_SOURCE//\//_}" + if [ ! -f "$cache" ]; then + local pins=$(clevis_luks_used_pins "$CRYPTTAB_SOURCE") + echo "${CRYPTTAB_SOURCE}:${pins}" > "$cache" + fi - echo -n "${decrypted}" >"$PASSFIFO" - return 0 - done + cat "$cache" + return 0 +} - return 1 +# Print colon-separated password-asking info like device, pins and fifo +# path for unlocking with password +get_askpass_info() { + local cryptkeyscript=$1 + local psinfo pf dev_pins + psinfo=$(ps -A) # Doing this so I don't end up matching myself + echo "$psinfo" | awk "/$cryptkeyscript/ { print \$1 }" | { + while read -r pid; do + if pf=$(get_pid_fifo_path "${pid}") && dev_pins=$(get_pid_device_pins "${pid}"); 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 + } } -luks2_decrypt() { +# Try to decrypt the password to fifo file +luks_decrypt() { local CRYPTTAB_SOURCE=$1 local PASSFIFO=$2 - cryptsetup luksDump "$CRYPTTAB_SOURCE" | sed -rn 's|^\s+([0-9]+): clevis|\1|p' | while read -r id; do - # jose jwe fmt -c outputs extra \n, so clean it up - cte=$(cryptsetup token export --token-id "$id" "$CRYPTTAB_SOURCE") - [ $? -eq 0 ] || continue - - josefmt=$(echo "${cte}" | jose fmt -j- -Og jwe -o-) - [ $? -eq 0 ] || continue - - josejwe=$(echo "${josefmt}" | jose jwe fmt -i- -c) - [ $? -eq 0 ] || continue - - jwe=$(echo "${josejwe}" | tr -d '\n') - [ $? -eq 0 ] || continue + local pt - decrypted=$(echo -n "${jwe}" | clevis decrypt 2>/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 +104,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 +118,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 + log_success_msg "clevis: TCSD up and running" + else + if [ -n "$tcsd_output" ]; then + log_failure_msg "clevis: Unable to start TCSD: $tcsd_output" + else + log_failure_msg "clevis: Unable to start TCSD" + fi + fi + + log_end_msg +} + +mkdir -p -m 0755 /var/cache +mkdir -p -m 0700 /var/cache/clevis-disks + clevisloop & echo $! >/run/clevis.pid diff --git a/src/luks/clevis-luks-tpm1-functions b/src/luks/clevis-luks-tpm1-functions new file mode 100755 index 00000000..f9c877c6 --- /dev/null +++ b/src/luks/clevis-luks-tpm1-functions @@ -0,0 +1,97 @@ +#!/bin/sh +# +# Copyright (c) 2024 Oldřich Jedlička +# +# Author: Oldřich Jedlička +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +start_tcsd() { + [ -s /run/tcsd.pid ] && return 0 + + if ! ip link show up dev lo | grep -qw UP; then + ip link set dev lo up && echo "lo" > /tmp/tcsd.if || : + if ! ip link show up dev lo | grep -qw UP; then + echo "Unable to set-up loopback network device" + return 1 + fi + fi + + if ! temp_dir="$(mktemp -d)"; then + echo "Unable to create temporary directory" + return 1 + fi + + fifo_file="$temp_dir/fifo" + output_file="$temp_dir/output" + + # If we have udev, let the initialization on udev + if ! [ -f /lib/udev/rules.d/60-tpm-udev.rules ]; then + chown tss: /dev/tpm0 + chmod 660 /dev/tpm0 + fi + + mkfifo "$fifo_file" + + # Start timeout to finish TCSD startup + sleep 10 & + sleep_pid=$! + + # The following loop ends when output side of FIFO closes (i.e. TCSD ends) + { while IFS= read -r LINE; do + echo "$LINE" + case "$LINE" in + *"TCSD up and running"*) + kill $sleep_pid 2>/dev/null + ;; + esac + done < $fifo_file && kill $sleep_pid; } >> "$output_file" 2>&1 & + + # TCSD in background mode logs into syslogd, so we would not have any logs + # available for debugging, so start TCSD in foreground mode, but as a + # background job. Unfortunatelly the redirected output to pipe is + # block-buffered (see `man 3 setbuf`), so in order to see any output we + # need to set it to line-buffered with stdbuf tool + stdbuf -oL tcsd -f >$fifo_file 2>&1 & + tcsd_pid=$! + + wait $sleep_pid 2>/dev/null + + if ps -A -o pid | awk -v pid="$tcsd_pid" '$1==pid {found=1} END {exit !found}'; then + ret=0 + echo $tcsd_pid > /run/tcsd.pid + else + ret=1 + [ -s "$output_file" ] && cat "$output_file" + fi + + rm -rf "$temp_dir" + + return $ret +} + +stop_tcsd() { + [ -s /run/tcsd.pid ] && { + pid=$(cat /run/tcsd.pid) + kill $pid >/dev/null 2>&1 || : + rm -f /run/tcsd.pid + } + + [ -s /tmp/tcsd.if ] && { + ip link set dev lo down || : + ip addr flush dev lo || : + rm -f /tmp/tcsd.if + } +} diff --git a/src/luks/meson.build b/src/luks/meson.build index 8a8394e0..818ffc0d 100644 --- a/src/luks/meson.build +++ b/src/luks/meson.build @@ -66,6 +66,8 @@ if libcryptsetup.found() and luksmeta.found() bins += join_paths(meson.current_source_dir(), 'clevis-luks-pass') mans += join_paths(meson.current_source_dir(), 'clevis-luks-pass.1') + + install_data('clevis-luks-tpm1-functions', install_dir: libexecdir) else warning('Will not install LUKS support due to missing dependencies!') endif