Skip to content

Commit

Permalink
Added TPM 1.2 support for initramfs-tools
Browse files Browse the repository at this point in the history
Signed-off-by: Oldřich Jedlička <[email protected]>
  • Loading branch information
oldium committed Jun 23, 2024
1 parent 6fb4cd9 commit eca718c
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 102 deletions.
65 changes: 64 additions & 1 deletion src/initramfs-tools/hooks/clevis.in
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")
Expand Down
7 changes: 6 additions & 1 deletion src/initramfs-tools/scripts/local-bottom/clevis.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
212 changes: 112 additions & 100 deletions src/initramfs-tools/scripts/local-top/clevis.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -118,71 +104,69 @@ 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
# This has to be escaped for awk
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 <<EOM
${askpass_info}
EOM
# Ask process is running, variables might be empty for non-clevis device
sleep_time=.5
else
# No valid process found
sleep_time=.1
fi
done

# Import CRYPTTAB_SOURCE from the askpass process.
local CRYPTTAB_SOURCE="$(cat /proc/${pid}/environ 2> /dev/null | \
tr '\0' '\n' | grep '^CRYPTTAB_SOURCE=' | cut -d= -f2)"
[ -n "$CRYPTTAB_SOURCE" ] || continue

# Make sure that CRYPTTAB_SOURCE is actually a block device
[ ! -b "$CRYPTTAB_SOURCE" ] && continue

sleep .1
# Make the source has changed if needed
[ "$CRYPTTAB_SOURCE" = "$OLD_CRYPTTAB_SOURCE" ] && continue
OLD_CRYPTTAB_SOURCE="$CRYPTTAB_SOURCE"

if [ $netcfg_attempted -eq 0 ] && has_tang_pin ${CRYPTTAB_SOURCE}; then
if [[ " $pins " == *" tang "* ]] && [ $netcfg_attempted -eq 0 ]; then
netcfg_attempted=1
do_configure_networking
fi
if [[ " $pins " == *" tpm1 "* ]] && [ $tpm1cfg_attempted -eq 0 ]; then
tpm1cfg_attempted=1
do_configure_tpm1
fi

if cryptsetup isLuks --type luks1 "$CRYPTTAB_SOURCE"; then
# If the device is not initialized, sliently skip it.
luksmeta test -d "$CRYPTTAB_SOURCE" || continue
if luks_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"

if luks1_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"
else
OLD_CRYPTTAB_SOURCE=""
sleep 5
fi
elif cryptsetup isLuks --type luks2 "$CRYPTTAB_SOURCE"; then
if luks2_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"
else
OLD_CRYPTTAB_SOURCE=""
sleep 5
fi
# Now that the current device has its password, let's sleep a
# bit. This gives cryptsetup time to actually decrypt the
# device and prompt for the next password if needed.
sleep .5
else
# Unable to unlock now, retry later
OLD_CRYPTTAB_SOURCE=""
sleep 5
fi
# Now that the current device has its password, let's sleep a
# bit. This gives cryptsetup time to actually decrypt the
# device and prompt for the next password if needed.
sleep .5
done
}

. /scripts/functions
. clevis-luks-common-functions

# This is a copy of 'all_netbootable_devices/all_non_enslaved_devices' for
# platforms that might not provide it.
Expand Down Expand Up @@ -283,5 +267,33 @@ do_configure_networking() {
fi
}

do_configure_tpm1() {
local tcsd_output=
local tcsd_result

[ -x @bindir@/clevis-decrypt-tpm1 ] && [ -f @libexecdir@/clevis-luks-tpm1-functions ] || return

. @libexecdir@/clevis-luks-tpm1-functions

log_begin_msg "clevis: Starting TCSD daemon"

wait_for_udev 10

if tcsd_output=$(start_tcsd 2>&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
Loading

0 comments on commit eca718c

Please sign in to comment.