#!/bin/bash -e # # 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 0 if $pid has a file descriptor pointing to $name, 1 otherwise in_fds() { local pid="$1" name="$2" fd for fd in /proc/$pid/fd/*; do if [ -e "$fd" ]; then [ "$(readlink -f "$fd")" != "$name" ] || return 0 fi done return 1 } # Print the PID of the askpass process with a file descriptor opened to # /lib/cryptsetup/passfifo if there is one. 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 if in_fds "$pid" "$PASSFIFO"; then echo "$pid" break fi done } luks1_decrypt() { luksmeta load "$@" \ | clevis decrypt local rc for rc in "${PIPESTATUS[@]}"; do [ $rc -eq 0 ] || return $rc done return 0 } luks2_jwe() { # jose jwe fmt -c outputs extra \n, so clean it up cryptsetup token export "$@" \ | jose fmt -j- -Og jwe -o- \ | jose jwe fmt -i- -c \ | tr -d '\n' local rc for rc in "${PIPESTATUS[@]}"; do [ $rc -eq 0 ] || return $rc done return 0 } # 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 -e # 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 PASSFIFO='/usr/lib/cryptsetup/passfifo' OLD_CRYPTTAB_SOURCE="" while true; do pid=$(get_askpass_pid) until [ "$pid" ] && [ -p "$PASSFIFO" ]; do sleep .1 pid=$(get_askpass_pid) done # Import CRYPTTAB_SOURCE from the askpass process. local "$(grep '^CRYPTTAB_SOURCE=' /proc/"$pid"/environ)" # Make sure that CRYPTTAB_SOURCE is actually a block device [ ! -b "$CRYPTTAB_SOURCE" ] && continue # Make the source has changed if needed [ "$CRYPTTAB_SOURCE" = "$OLD_CRYPTTAB_SOURCE" ] && continue OLD_CRYPTTAB_SOURCE="$CRYPTTAB_SOURCE" UUID=cb6e8904-81ff-40da-a84a-07ab9ab5715e if cryptsetup isLuks --type luks1 "$CRYPTTAB_SOURCE"; then # If the device is not initialized, sliently skip it. luksmeta test -d "$CRYPTTAB_SOURCE" || continue luksmeta show -d "$CRYPTTAB_SOURCE" | while read -r slot state uuid; do [ "$state" == "active" ] || continue [ "$uuid" == "$UUID" ] || continue if pt="$(luks1_decrypt -d "$CRYPTTAB_SOURCE" -s "$slot" -u "$UUID")"; then echo -n "$pt" > "$PASSFIFO" break fi done elif cryptsetup isLuks --type luks2 "$CRYPTTAB_SOURCE"; then cryptsetup luksDump "$CRYPTTAB_SOURCE" | sed -rn 's|^\s+([0-9]+): clevis|\1|p' | while read -r id; do jwe="$(luks2_jwe --token-id "$id" "$CRYPTTAB_SOURCE")" \ || continue if pt="$(echo -n "$jwe" | clevis decrypt)"; then echo -n "$pt" > "$PASSFIFO" break fi done 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 DEVICE="$DEVICE $device" done echo "$DEVICE" } # Check if network is up before trying to configure it. eth_check() { for device in $(clevis_all_netbootable_devices); do ip link set dev "$device" up sleep 1 ETH_HAS_CARRIER=$(cat /sys/class/net/"$device"/carrier) if [ "$ETH_HAS_CARRIER" = '1' ]; then return 0 fi done return 1 } if eth_check; then # Make sure networking is set up: if booting via nfs, it already is # Doesn't seem to work when added to clevisloop for some reason [ "$boot" = nfs ] || configure_networking fi clevisloop & echo $! > /run/clevis.pid