#!/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 # # 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 . # case $1 in prereqs) exit 0 ;; esac # Return fifo path or nothing if not found get_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}" fi fi done } # 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 lml=$(luksmeta load -d "${CRYPTTAB_SOURCE}" -s "${slot}" -u "${UUID}") [ $? -eq 0 ] || continue decrypted=$(echo -n "${lml}" | clevis decrypt 2>/dev/null) [ $? -eq 0 ] || continue # Fail safe [ "$decrypted" != "" ] || continue echo -n "${decrypted}" >"$PASSFIFO" return 0 done return 1 } luks2_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 decrypted=$(echo -n "${jwe}" | clevis decrypt 2>/dev/null) [ $? -eq 0 ] || continue # Fail safe [ "$decrypted" != "" ] || continue echo -n "${decrypted}" >"$PASSFIFO" return 0 done return 1 } has_tang_pin() { local dev="$1" clevis luks list -d "${dev}" | grep -q tang } # 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() { # Set the path how we want it (Probably not all needed) PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin" 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-) 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 netcfg_attempted=1 do_configure_networking 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 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 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 # This is a copy of 'all_netbootable_devices/all_non_enslaved_devices' for # platforms that might not provide it. clevis_all_netbootable_devices() { for device in /sys/class/net/*; do if [ ! -e "$device/flags" ]; then continue fi loop=$(($(cat "$device/flags") & 0x8 && 1 || 0)) bc=$(($(cat "$device/flags") & 0x2 && 1 || 0)) ptp=$(($(cat "$device/flags") & 0x10 && 1 || 0)) # Skip any device that is a loopback if [ $loop = 1 ]; then continue fi # Skip any device that isn't a broadcast # or point-to-point. if [ $bc = 0 ] && [ $ptp = 0 ]; then continue fi # Skip any enslaved device (has "master" link # attribute on it) device=$(basename "$device") ip -o link show "$device" | grep -q -w master && continue if [ -z "$DEVICE" ]; then DEVICE="$device" else DEVICE="$DEVICE $device" fi done echo "$DEVICE" } get_specified_device() { local dev="$(echo $IP | cut -d: -f6)" [ -z "$dev" ] || echo $dev } # Workaround configure_networking() not waiting long enough for an interface # to appear. This code can be removed once that has been fixed in all the # distro releases we care about. wait_for_device() { local device="$(get_specified_device)" local ret=0 if [ -n "$device" ]; then log_begin_msg "clevis: Waiting for interface ${device} to become available" local netdev_wait=0 while [ $netdev_wait -lt 10 ]; do if [ -e "/sys/class/net/${device}" ]; then break fi netdev_wait=$((netdev_wait + 1)) sleep 1 done if [ ! -e "/sys/class/net/${device}" ]; then log_failure_msg "clevis: Interface ${device} did not appear in time" ret=1 fi log_end_msg fi wait_for_udev 10 return $ret } do_configure_networking() { # Make sure networking is set up: if booting via nfs, it already is if [ "$boot" != nfs ] && wait_for_device; then clevis_net_cnt=$(clevis_all_netbootable_devices | tr ' ' '\n' | wc -l) if [ -z "$IP" ] && [ "$clevis_net_cnt" -gt 1 ]; then echo "" echo "clevis: Warning: multiple network interfaces available but no ip= parameter provided." fi configure_networking fi } clevisloop & echo $! >/run/clevis.pid