diff options
Diffstat (limited to 'debian/initramfs')
-rw-r--r-- | debian/initramfs/conf-hook | 28 | ||||
-rw-r--r-- | debian/initramfs/conf-hooks.d/cryptsetup | 9 | ||||
-rw-r--r-- | debian/initramfs/cryptroot-unlock | 196 | ||||
-rw-r--r-- | debian/initramfs/hooks/cryptgnupg | 45 | ||||
-rw-r--r-- | debian/initramfs/hooks/cryptgnupg-sc | 77 | ||||
-rw-r--r-- | debian/initramfs/hooks/cryptkeyctl | 30 | ||||
-rw-r--r-- | debian/initramfs/hooks/cryptopensc | 62 | ||||
-rw-r--r-- | debian/initramfs/hooks/cryptpassdev | 38 | ||||
-rw-r--r-- | debian/initramfs/hooks/cryptroot | 329 | ||||
-rw-r--r-- | debian/initramfs/hooks/cryptroot-unlock | 30 | ||||
-rw-r--r-- | debian/initramfs/scripts/local-block/cryptroot | 21 | ||||
-rw-r--r-- | debian/initramfs/scripts/local-bottom/cryptgnupg-sc | 18 | ||||
-rw-r--r-- | debian/initramfs/scripts/local-bottom/cryptopensc | 32 | ||||
-rw-r--r-- | debian/initramfs/scripts/local-bottom/cryptroot | 22 | ||||
-rw-r--r-- | debian/initramfs/scripts/local-top/cryptopensc | 37 | ||||
-rw-r--r-- | debian/initramfs/scripts/local-top/cryptroot | 252 |
16 files changed, 1226 insertions, 0 deletions
diff --git a/debian/initramfs/conf-hook b/debian/initramfs/conf-hook new file mode 100644 index 0000000..81de87e --- /dev/null +++ b/debian/initramfs/conf-hook @@ -0,0 +1,28 @@ +# +# Configuration file for the cryptroot initramfs hook. +# + +# +# KEYFILE_PATTERN: ... +# +# The value of this variable is interpreted as a shell pattern. +# Matching key files from the crypttab(5) are included in the initramfs +# image. The associated devices can then be unlocked without manual +# intervention. (For instance if /etc/crypttab lists two key files +# /etc/keys/{root,swap}.key, you can set KEYFILE_PATTERN="/etc/keys/*.key" +# to add them to the initrd.) +# +# If KEYFILE_PATTERN if null or unset (default) then no key file is +# copied to the initramfs image. +# +# Note that the glob(7) is not expanded for crypttab(5) entries with a +# 'keyscript=' option. In that case, the field is not treated as a file +# name but given as argument to the keyscript. +# +# WARNING: If the initramfs image is to include private key material, +# you'll want to create it with a restrictive umask in order to keep +# non-privileged users at bay. For instance, set UMASK=0077 in +# /etc/initramfs-tools/initramfs.conf +# + +#KEYFILE_PATTERN= diff --git a/debian/initramfs/conf-hooks.d/cryptsetup b/debian/initramfs/conf-hooks.d/cryptsetup new file mode 100644 index 0000000..883c1ba --- /dev/null +++ b/debian/initramfs/conf-hooks.d/cryptsetup @@ -0,0 +1,9 @@ +# This will setup non-us keyboards in early userspace, +# necessary for punching in passphrases. +KEYMAP=y + +# force busybox on initramfs +BUSYBOX=y + +# and for systems using plymouth instead, use the new option +FRAMEBUFFER=y diff --git a/debian/initramfs/cryptroot-unlock b/debian/initramfs/cryptroot-unlock new file mode 100644 index 0000000..d31b6f4 --- /dev/null +++ b/debian/initramfs/cryptroot-unlock @@ -0,0 +1,196 @@ +#!/bin/busybox ash + +# Remotely unlock encrypted volumes. +# +# Copyright © 2015-2018 Guilhem Moulin <guilhem@debian.org> +# +# 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 2 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 <http://www.gnu.org/licenses/>. + +set -ue +PATH=/sbin:/bin + +TIMEOUT=10 +PASSFIFO=/lib/cryptsetup/passfifo +ASKPASS=/lib/cryptsetup/askpass +UNLOCK_ALL=n + +[ -f /lib/cryptsetup/functions ] || return 0 +. /lib/cryptsetup/functions +TABFILE="/cryptroot/crypttab" +unset -v IFS + +if [ ! -f "$TABFILE" ] || [ "$TABFILE" -ot "/proc/1" ]; then + # Too early, init-top/cryptroot hasn't finished yet + echo "Try again later" >&2 + exit 1 +fi + +# Print the list of PIDs the executed command of which is $exe. +pgrep_exe() { + local exe pid + exe="$(readlink -f -- "$1" 2>/dev/null)" && [ -f "$exe" ] || return 0 + ps -eo pid= | while read pid; do + [ "$(readlink -f "/proc/$pid/exe")" != "$exe" ] || printf '%d\n' "$pid" + done +} + +# Return 0 if $pid has a file descriptor pointing to $name, and 1 +# otherwise. +in_fds() { + local pid="$1" name fd + name="$(readlink -f -- "$2" 2>/dev/null)" && [ -e "$name" ] || return 1 + for fd in $(find "/proc/$pid/fd" -type l); do + [ "$(readlink -f "$fd")" != "$name" ] || return 0 + done + return 1 +} + +# Print the PID of the askpass process with a file descriptor opened to +# /lib/cryptsetup/passfifo. +get_askpass_pid() { + local pid + for pid in $(pgrep_exe "$ASKPASS"); do + if in_fds "$pid" "$PASSFIFO"; then + echo "$pid" + return 0 + fi + done + return 1 +} + +# Print the number of configured crypt devices that have not been unlocked yet. +count_locked_devices() { + local COUNT=0 + crypttab_foreach_entry count_locked_devices_callback + printf '%d\n' "$COUNT" +} +count_locked_devices_callback() { + dm_blkdevname "$CRYPTTAB_NAME" >/dev/null || COUNT=$(( $COUNT + 1 )) +} + +# Wait for askpass, then set $PID (resp. $BIRTH) to the PID (resp. +# birth date) of the cryptsetup process with same $CRYPTTAB_NAME. +wait_for_prompt() { + local pid timer num_locked_devices=-1 n + + # wait for the fifo + while :; do + n=$(count_locked_devices) + if [ $n -eq 0 ]; then + # all configured devices have been unlocked, we're done + exit 0 + elif [ $num_locked_devices -lt 0 ] || [ $n -lt $num_locked_devices ]; then + # reset $timer if a device was unlocked (for instance using + # a keyscript) while we were waiting + timer=$(( 10 * $TIMEOUT )) + fi + num_locked_devices=$n + + if pid=$(get_askpass_pid) && [ -p "$PASSFIFO" ]; then + break + fi + + usleep 100000 + timer=$(( $timer - 1 )) + if [ $timer -le 0 ]; then + echo "Error: Timeout reached while waiting for askpass." >&2 + exit 1 + fi + done + + # find the cryptsetup process with same $CRYPTTAB_NAME + local o v + for o in NAME TRIED OPTION_tries; do + if v="$(grep -z -m1 "^CRYPTTAB_$o=" "/proc/$pid/environ")"; then + eval "CRYPTTAB_$o"="\${v#CRYPTTAB_$o=}" + else + eval unset -v "CRYPTTAB_$o" + fi + done + if [ -z "${CRYPTTAB_NAME:+x}" ] || [ -z "${CRYPTTAB_TRIED:+x}" ]; then + return 1 + fi + if ( ! crypttab_find_entry --quiet "$CRYPTTAB_NAME" ); then + # use a subshell to avoid polluting our enironment + echo "Error: Refusing to process unknown device $CRYPTTAB_NAME" >&2 + exit 1 + fi + + for pid in $(pgrep_exe "/sbin/cryptsetup"); do + if grep -Fxqz "CRYPTTAB_NAME=$CRYPTTAB_NAME" "/proc/$pid/environ"; then + PID=$pid + BIRTH=$(stat -c"%Z" "/proc/$PID" 2>/dev/null) || break + return 0 + fi + done + + PID= + BIRTH= + return 1 +} + +# Wait until $PID no longer exists or has a birth date greater that +# $BIRTH (ie was reallocated). Then return with exit value 0 if +# /dev/mapper/$CRYPTTAB_NAME exists, and with exit value 1 if the +# maximum number of tries exceeded. Otherwise (if the unlocking +# failed), return with value 1. +wait_for_answer() { + local timer=$(( 10 * $TIMEOUT )) b + while [ -d "/proc/$PID" ] && b=$(stat -c"%Z" "/proc/$PID" 2>/dev/null) && [ $b -le $BIRTH ]; do + usleep 100000 + timer=$(( $timer - 1 )) + if [ $timer -le 0 ]; then + echo "Error: Timeout reached while waiting for PID $PID." >&2 + exit 1 + fi + done + + if dm_blkdevname "$CRYPTTAB_NAME" >/dev/null; then + echo "cryptsetup: $CRYPTTAB_NAME set up successfully" >&2 + [ "$UNLOCK_ALL" = y ] && return 0 || exit 0 + elif [ $(( ${CRYPTTAB_TRIED:-0} + 1 )) -ge ${CRYPTTAB_OPTION_tries:-3} ] && + [ ${CRYPTTAB_OPTION_tries:-3} -gt 0 ]; then + echo "cryptsetup: maximum number of tries exceeded for $CRYPTTAB_NAME" >&2 + exit 1 + else + echo "cryptsetup: cryptsetup failed, bad password or options?" >&2 + return 1 + fi +} + + +if [ -t 0 ] && [ -x "$ASKPASS" ]; then + # interactive mode on a TTY: keep trying until all configured devices have + # been unlocked or the maximum number of tries exceeded + UNLOCK_ALL=y + while :; do + # note: if the script is not killed before pivot_root it should + # exit on its own once $TIMEOUT is reached + if ! wait_for_prompt; then + usleep 100000 + continue + fi + read -rs -p "Please unlock disk $CRYPTTAB_NAME: "; echo + printf '%s' "$REPLY" >"$PASSFIFO" + wait_for_answer || true + done +else + # non-interactive mode: slurp the passphrase from stdin and exit + wait_for_prompt || exit 1 + echo "Please unlock disk $CRYPTTAB_NAME" + cat >"$PASSFIFO" + wait_for_answer || exit 1 +fi + +# vim: set filetype=sh : diff --git a/debian/initramfs/hooks/cryptgnupg b/debian/initramfs/hooks/cryptgnupg new file mode 100644 index 0000000..cffefdb --- /dev/null +++ b/debian/initramfs/hooks/cryptgnupg @@ -0,0 +1,45 @@ +#!/bin/sh + +set -e + +PREREQ="cryptroot" + +prereqs() +{ + echo "$PREREQ" +} + +case "$1" in + prereqs) + prereqs + exit 0 + ;; +esac + +. /usr/share/initramfs-tools/hook-functions +. /lib/cryptsetup/functions + +if [ ! -x "$DESTDIR/lib/cryptsetup/scripts/decrypt_gnupg" ] || [ ! -f "$TABFILE" ]; then + exit 0 +fi + +# Hooks for loading gnupg software and symmetrically encrypted key into +# the initramfs +copy_keys() { + crypttab_parse_options + if [ "${CRYPTTAB_OPTION_keyscript-}" = "/lib/cryptsetup/scripts/decrypt_gnupg" ]; then + if [ -f "$CRYPTTAB_KEY" ]; then + [ -f "$DESTDIR$CRYPTTAB_KEY" ] || copy_file keyfile "$CRYPTTAB_KEY" || RV=$? + else + cryptsetup_message "ERROR: Target $CRYPTTAB_NAME has a non-existing key file $CRYPTTAB_KEY" + RV=1 + fi + fi +} + +RV=0 +crypttab_foreach_entry copy_keys + +# Install gnupg software +copy_exec /usr/bin/gpg +exit $RV diff --git a/debian/initramfs/hooks/cryptgnupg-sc b/debian/initramfs/hooks/cryptgnupg-sc new file mode 100644 index 0000000..752474a --- /dev/null +++ b/debian/initramfs/hooks/cryptgnupg-sc @@ -0,0 +1,77 @@ +#!/bin/sh + +set -e + +PREREQ="cryptroot" + +prereqs() +{ + echo "$PREREQ" +} + +case "$1" in + prereqs) + prereqs + exit 0 + ;; +esac + +. /usr/share/initramfs-tools/hook-functions +. /lib/cryptsetup/functions + +if [ ! -x "$DESTDIR/lib/cryptsetup/scripts/decrypt_gnupg-sc" ] || [ ! -f "$TABFILE" ]; then + exit 0 +fi + +# Hooks for loading gnupg software and encrypted key into the initramfs +copy_keys() { + crypttab_parse_options + if [ "${CRYPTTAB_OPTION_keyscript-}" = "/lib/cryptsetup/scripts/decrypt_gnupg-sc" ]; then + if [ -f "$CRYPTTAB_KEY" ]; then + [ -f "$DESTDIR$CRYPTTAB_KEY" ] || copy_file keyfile "$CRYPTTAB_KEY" || RV=$? + else + cryptsetup_message "ERROR: Target $CRYPTTAB_NAME has a non-existing key file $CRYPTTAB_KEY" + RV=1 + fi + fi +} + +RV=0 +crypttab_foreach_entry copy_keys + +PUBRING="/etc/cryptsetup-initramfs/pubring.gpg" +if [ ! -f "$PUBRING" ]; then + cryptsetup_message "WARNING: $PUBRING: No such file" +else + [ -d "$DESTDIR/cryptroot/gnupghome" ] || mkdir -pm0700 "$DESTDIR/cryptroot/gnupghome" + # let gpg(1) create the keyring on the fly; we're not relying on its + # internals since it's the very same binary we're copying to the + # initramfs + /usr/bin/gpg --no-options --no-autostart --trust-model=always \ + --quiet --batch --no-tty --logger-file=/dev/null \ + --homedir="$DESTDIR/cryptroot/gnupghome" --import <"$PUBRING" + # make sure not to clutter the initramfs with backup keyrings + find "$DESTDIR/cryptroot" -name "*~" -type f -delete +fi + +copy_exec /usr/bin/gpg +copy_exec /usr/bin/gpg-agent +copy_exec /usr/lib/gnupg/scdaemon +copy_exec /usr/bin/gpgconf +copy_exec /usr/bin/gpg-connect-agent + +if [ ! -x "$DESTDIR/usr/bin/pinentry" ]; then + if [ -x "/usr/bin/pinentry-curses" ]; then + pinentry="/usr/bin/pinentry-curses" + elif [ -x "/usr/bin/pinentry-tty" ]; then + pinentry="/usr/bin/pinentry-tty" + else + cryptsetup_message "ERROR: missing required binary pinentry-curses or pinentry-tty" + RV=1 + fi + copy_exec "$pinentry" + ln -s "$pinentry" "$DESTDIR/usr/bin/pinentry" +fi +[ -f "$DESTDIR/lib/terminfo/l/linux" ] || copy_file terminfo /lib/terminfo/l/linux || RV=$? + +exit $RV diff --git a/debian/initramfs/hooks/cryptkeyctl b/debian/initramfs/hooks/cryptkeyctl new file mode 100644 index 0000000..0bac676 --- /dev/null +++ b/debian/initramfs/hooks/cryptkeyctl @@ -0,0 +1,30 @@ +#!/bin/sh + +set -e + +PREREQ="cryptroot" + +prereqs() +{ + echo "$PREREQ" +} + +case "$1" in + prereqs) + prereqs + exit 0 + ;; +esac + +. /usr/share/initramfs-tools/hook-functions + +# Hooks for loading keyctl software into the initramfs + +# Check whether cryptroot hook has installed decrypt_keyctl script +if [ ! -x "$DESTDIR/lib/cryptsetup/scripts/decrypt_keyctl" ]; then + exit 0 +fi + +copy_exec /bin/keyctl + +exit 0 diff --git a/debian/initramfs/hooks/cryptopensc b/debian/initramfs/hooks/cryptopensc new file mode 100644 index 0000000..e0c5167 --- /dev/null +++ b/debian/initramfs/hooks/cryptopensc @@ -0,0 +1,62 @@ +#!/bin/sh + +set -e + +PREREQ="cryptroot" + +prereqs() +{ + echo "$PREREQ" +} + +case "$1" in + prereqs) + prereqs + exit 0 + ;; +esac + +. /usr/share/initramfs-tools/hook-functions +. /lib/cryptsetup/functions + +if [ ! -x "$DESTDIR/lib/cryptsetup/scripts/decrypt_opensc" ] || [ ! -f "$TABFILE" ]; then + exit 0 +fi + +# Hooks for loading smartcard reading software into the initramfs +copy_keys() { + crypttab_parse_options + if [ "${CRYPTTAB_OPTION_keyscript-}" = "/lib/cryptsetup/scripts/decrypt_opensc" ]; then + if [ -f "$CRYPTTAB_KEY" ]; then + [ -f "$DESTDIR$CRYPTTAB_KEY" ] || copy_file keyfile "$CRYPTTAB_KEY" || RV=$? + else + cryptsetup_message "ERROR: Target $CRYPTTAB_NAME has a non-existing key file $CRYPTTAB_KEY" + RV=1 + fi + fi +} + +RV=0 +crypttab_foreach_entry copy_keys + +# Install directories needed by smartcard reading daemon, command, and +# key-script +mkdir -p -- "$DESTDIR/etc/opensc" "$DESTDIR/usr/lib/pcsc" "$DESTDIR/var/run" "$DESTDIR/tmp" + +# Install pcscd daemon, drivers, conf file +copy_exec /usr/sbin/pcscd + +cp -rt "$DESTDIR/usr/lib" /usr/lib/pcsc +cp -t "$DESTDIR/etc" /etc/reader.conf || true +cp -t "$DESTDIR/etc" /etc/libccid_Info.plist + +for so in $(ldconfig -p | sed -nr 's/^\s*(libusb-[0-9.-]+|libpcsclite)\.so\.[0-9]+\s.*=>\s*//p'); do + copy_exec "$so" +done + +# Install opensc commands and conf file +copy_exec /usr/bin/opensc-tool +copy_exec /usr/bin/pkcs15-crypt +cp -t "$DESTDIR/etc/opensc" /etc/opensc/opensc.conf + +exit $RV diff --git a/debian/initramfs/hooks/cryptpassdev b/debian/initramfs/hooks/cryptpassdev new file mode 100644 index 0000000..54492f0 --- /dev/null +++ b/debian/initramfs/hooks/cryptpassdev @@ -0,0 +1,38 @@ +#!/bin/sh + +set -e + +PREREQ="cryptroot" + +prereqs() +{ + echo "$PREREQ" +} + +case "$1" in + prereqs) + prereqs + exit 0 + ;; +esac + +. /usr/share/initramfs-tools/hook-functions + +# Hooks for adding filesystem modules to the initramfs when the passdev +# keyscript is used + +# Check whether the passdev script has been included +if [ ! -x "$DESTDIR/lib/cryptsetup/scripts/passdev" ]; then + exit 0 +fi + +# The filesystem type of the removable device is probed at boot-time, so +# we add a generous list of filesystems to include. This also helps with +# recovery situation as including e.g. the vfat module might help a user +# who needs to create a new cryptkey (using a backup of a keyfile) on +# a windows-machine for example. + +# This list needs to be kept in sync with the one defined in passdev.c +manual_add_modules ext4 ext3 ext2 vfat btrfs reiserfs xfs jfs ntfs iso9660 udf +exit 0 + diff --git a/debian/initramfs/hooks/cryptroot b/debian/initramfs/hooks/cryptroot new file mode 100644 index 0000000..2a86f21 --- /dev/null +++ b/debian/initramfs/hooks/cryptroot @@ -0,0 +1,329 @@ +#!/bin/sh + +PREREQ="" + +prereqs() +{ + echo "$PREREQ" +} + +case "$1" in + prereqs) + prereqs + exit 0 + ;; +esac + +. /usr/share/initramfs-tools/hook-functions +. /lib/cryptsetup/functions +TABFILE="/etc/crypttab" + + +# crypttab_find_and_print_entry($target) +# Find the crypttab(5) entry for the given (unmangled) $target and +# print it - preserving the mangling - to FD nr. 3; but only if the +# target has not already been processed during an earlier function +# call. (Processed target names are stored in +# $DESTDIR/cryptroot/targets.) +# Return 0 on success, 1 on error. +crypttab_find_and_print_entry() { + local target="$1" + local _CRYPTTAB_NAME _CRYPTTAB_SOURCE _CRYPTTAB_KEY _CRYPTTAB_OPTIONS + if ! grep -Fxqz -e "$target" -- "$DESTDIR/cryptroot/targets"; then + printf '%s\0' "$target" >>"$DESTDIR/cryptroot/targets" + crypttab_find_entry "$target" || return 1 + crypttab_parse_options --missing-path=warn || return 1 + crypttab_print_entry + fi +} + +# crypttab_print_entry() +# Print an unmangled crypttab(5) entry to FD nr. 3, using CRYPTTAB_* +# and _CRYPTTAB_* values. +# _CRYPTTAB_SOURCE is replaced with /dev/mapper/$sourcename for mapped +# sources, otherwise by UUID=<uuid> if possible (eg, for LUKS). If +# the entry uses the 'decrypt_derived' keyscript, the other +# crypttab(5) entries it depends on are (recursively) printed before +# hand. +# Various checks are performed on the key and crypttab options, but no +# parsing is done so it's the responsibility of the caller to call +# crypttab_parse_options(). +# Return 0 on success, 1 on error. +crypttab_print_entry() { + local DEV MAJ MIN sourcename uuid keyfile + if _resolve_device "$CRYPTTAB_SOURCE"; then + if [ "$(dmsetup info -c --noheadings -o devnos_used -- "$CRYPTTAB_NAME" 2>/dev/null)" != "$MAJ:$MIN" ]; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: Source mismatch" + elif sourcename="$(dmsetup info -c --noheadings -o mangled_name -j "$MAJ" -m "$MIN" 2>/dev/null)" && + [ -b "/dev/mapper/$sourcename" ]; then + # for mapped sources, use the dm name as the lvm + # local-block script doesn't work with UUIDs (#902943) + _CRYPTTAB_SOURCE="/dev/mapper/$sourcename" + elif uuid="$(_device_uuid "$DEV")"; then + # otherwise, use its UUID if possible (eg. for LUKS) + _CRYPTTAB_SOURCE="UUID=$uuid" + fi + # on failure _resolve_device() prints a warning and we try our + # luck with the unchanged _CRYPTTAB_SOURCE value + fi + + # if keyscript is set, the "key" is just an argument to the script + if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$CRYPTTAB_KEY" != "none" ]; then + crypttab_key_check || return 1 + case "$CRYPTTAB_KEY" in + $KEYFILE_PATTERN) + mkdir -pm0700 -- "$DESTDIR/cryptroot/keyfiles" + # $CRYPTTAB_NAME can't contain '/' (even after unmangling) + keyfile="/cryptroot/keyfiles/$CRYPTTAB_NAME.key" + if [ ! -f "$DESTDIR$keyfile" ] && ! copy_file keyfile "$CRYPTTAB_KEY" "$keyfile"; then + cryptsetup_message "WARNING: couldn't copy keyfile $CRYPTTAB_KEY" + fi + _CRYPTTAB_KEY="/cryptroot/keyfiles/$_CRYPTTAB_NAME.key" # preserve mangled name + ;; + *) + if [ "$usage" = rootfs ]; then + cryptsetup_message "WARNING: Skipping root target $CRYPTTAB_NAME: uses a key file" + return 1 + elif [ "$usage" = resume ]; then + cryptsetup_message "WARNING: Resume target $CRYPTTAB_NAME uses a key file" + fi + if [ -L "$CRYPTTAB_KEY" ] && keyfile="$(readlink -- "$CRYPTTAB_KEY")" && + [ "${keyfile#/}" != "$keyfile" ]; then + cryptsetup_message "WARNING: Skipping target $CRYPTTAB_NAME: key file is a symlink with absolute target" + return 1 + elif [ -f "$CRYPTTAB_KEY" ] && [ "$(stat -L -c"%m" -- "$CRYPTTAB_KEY" 2>/dev/null)" != "/" ]; then + cryptsetup_message "WARNING: Skipping target $CRYPTTAB_NAME: key file is not on the root FS" + return 1 + fi + if [ ! -e "$CRYPTTAB_KEY" ]; then + cryptsetup_message "WARNING: Target $CRYPTTAB_NAME has a non-existing key file $CRYPTTAB_KEY" + else + _CRYPTTAB_KEY="/FIXME-initramfs-rootmnt$_CRYPTTAB_KEY" # preserve mangled name + fi + esac + fi + + if [ -n "${CRYPTTAB_OPTION_keyscript+x}" ]; then + copy_exec "$CRYPTTAB_OPTION_keyscript" + fi + if [ "${CRYPTTAB_OPTION_keyscript-}" = "/lib/cryptsetup/scripts/decrypt_derived" ]; then + # (recursively) list first the device to derive the key from (so + # the boot scripts unlock it first); since _CRYPTTAB_* are local + # to crypttab_find_and_print_entry() the new value won't + # override the new ones + crypttab_find_and_print_entry "$CRYPTTAB_KEY" + fi + printf '%s %s %s %s\n' \ + "$_CRYPTTAB_NAME" "$_CRYPTTAB_SOURCE" "$_CRYPTTAB_KEY" "$_CRYPTTAB_OPTIONS" >&3 +} + +# get_resume_devno() +# Return the device ID(s) used for system suspend/hibernate. +get_resume_devno() { + local dev filename + + # uswsusp + for filename in /etc/uswsusp.conf /etc/suspend.conf; do + [ -e "$filename" ] || continue + dev="$(sed -nr '/^resume device\s*[:=]\s*/ {s///p;q}' "$filename")" + if [ -n "$dev" ] && [ "$dev" != "<path_to_resume_device_file>" ]; then + # trim quotes + dev="$(printf '%s' "$dev" | sed -re 's/^"(.*)"\s*$/\1/' -e "s/^'(.*)'\\s*$/\\1/")" + _print_devno "$(printf '%b' "$dev")" # unmangle + fi + done + + # regular swsusp + dev="$(sed -nr 's,^(.*\s)?resume=(\S+)(\s.*)?$,\2,p' /proc/cmdline)" + dev="$(printf '%b' "$dev")" # unmangle + _print_devno "$(printf '%b' "$dev")" # unmangle + + # initramfs-tools >=0.129 + dev="${RESUME:-auto}" + if [ "$dev" != none ]; then + if [ "$dev" = auto ]; then + # next line from /usr/share/initramfs-tools/hooks/resume + dev="$(grep ^/dev/ /proc/swaps | sort -rnk3 | head -n 1 | cut -d " " -f 1)" + fi + _print_devno "$(printf '%b' "$dev")" # unmangle + fi +} +_print_devno() { + local DEV MAJ MIN # locally scope the 3 variables _resolve_device() sets + if [ -n "$1" ] && _resolve_device "$1"; then + printf '%d:%d\n' "$MAJ" "$MIN" + fi +} + +# crypttab_print_initramfs_entry() +# Print a crypttab(5) entry - unless it was already processed - if it +# has the 'initramfs' option set. +crypttab_print_initramfs_entry() { + local usage= + if ! grep -Fxqz -e "$CRYPTTAB_NAME" -- "$DESTDIR/cryptroot/targets" && + crypttab_parse_options --quiet && + [ "${CRYPTTAB_OPTION_initramfs-no}" = "yes" ]; then + printf '%s\0' "$CRYPTTAB_NAME" >>"$DESTDIR/cryptroot/targets" + crypttab_print_entry + fi +} + +# generate_initrd_crypttab() +# Generate the crypttab(5) snippet that is relevant at initramfs +# stage. (Devices that aren't required at initramfs stage are +# ignored.) +generate_initrd_crypttab() { + local devnos usage IFS="$(printf '\t\n ')" + mkdir -- "$DESTDIR/cryptroot" + true >"$DESTDIR/cryptroot/targets" + + { + if devnos="$(get_mnt_devno /)"; then + usage=rootfs foreach_cryptdev crypttab_find_and_print_entry $devnos + else + cryptsetup_message "WARNING: Couldn't determine root device" + fi + + if devnos="$(get_resume_devno)"; then + usage=resume foreach_cryptdev crypttab_find_and_print_entry $devnos + fi + + if devnos="$(get_mnt_devno /usr)"; then + usage="" foreach_cryptdev crypttab_find_and_print_entry $devnos + fi + + # add crypttab entries with the 'initramfs' option set + crypttab_foreach_entry crypttab_print_initramfs_entry + } 3>"$DESTDIR/cryptroot/crypttab" + rm -f "$DESTDIR/cryptroot/targets" +} + +# populate_CRYPTO_MODULES() +# Find out which crypto modules are required for a crypttab(5) entry, +# and append them to the CRYPTO_MODULES variable. +populate_CRYPTO_MODULES() { + local cipher iv + + # cf. dmsetup(8) and https://gitlab.com/cryptsetup/cryptsetup/wikis/DMCrypt + cipher="$(dmsetup table --target crypt -- "$CRYPTTAB_NAME" | cut -d' ' -f4)" + if [ -z "$cipher" ]; then + cryptsetup_message "WARNING: Couldn't determine cipher modules to load for $CRYPTTAB_NAME" + elif [ "${cipher#capi:}" = "$cipher" ]; then + # direct specification "cipher[:keycount]-chainmode-ivmode[:ivopts]" + CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${cipher%%[-:]*}" # cipher + cipher="${cipher#"${cipher%%-*}-"}" # chainmode-ivmode[:ivopts]" + CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${cipher%-*}" # chainmode + iv="${cipher##*-}" # ivmode[:ivopts]" + CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${iv%%:*}" # ivmode + if [ "${iv#*:}" != "$iv" ]; then + CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${iv#*:}" # ivopts + fi + else + # kernel crypto API format "capi:cipher_api_spec-ivmode[:ivopts]", since linux 4.12 + cipher="${cipher#capi:}" + cryptsetup_message "WARNING: Couldn't determine cipher modules to load for $CRYPTTAB_NAME" \ + "(kernel crypto API format isn't supported yet)" + fi +} + +# add_modules($glob, $moduledir, [$moduledir ..]) +# Add modules matching under the given $moduledir(s), the name of +# which matching $glob. +# Return 0 if any module was found found, 1 if not. +add_modules() { + local glob="$1" found=n + shift + for mod in $(find -H "$@" -name "$glob.ko" -type f -printf '%f\n'); do + manual_add_modules "${mod%.ko}" + found=y + done + [ "$found" = y ] && return 0 || return 1 +} + +# add_crypto_modules($name, [$name ..]) +# Determine kernel module name and add to initramfs. +add_crypto_modules() { + local mod + for mod in "$@"; do + # We have several potential sources of modules (in order of preference): + # + # a) /lib/modules/$VERSION/kernel/arch/$ARCH/crypto/$mod-$specific.ko + # b) /lib/modules/$VERSION/kernel/crypto/$mod_generic.ko + # c) /lib/modules/$VERSION/kernel/crypto/$mod.ko + # + # and (currently ignored): + # + # d) /lib/modules/$VERSION/kernel/drivers/crypto/$specific-$mod.ko + add_modules "$mod-*" "$MODULESDIR"/kernel/arch/*/crypto || true + add_modules "${mod}_generic" "$MODULESDIR/kernel/crypto" \ + || add_modules "$mod" "$MODULESDIR/kernel/crypto" \ + || true + done +} + + +####################################################################### +# Begin real processing + +unset -v KEYFILE_PATTERN +KEYFILE_PATTERN= + +# Load the hook config +if [ -f "/etc/cryptsetup-initramfs/conf-hook" ]; then + . /etc/cryptsetup-initramfs/conf-hook +fi + +if [ -n "$KEYFILE_PATTERN" ]; then + case "${UMASK:-$(umask)}" in + 0[0-7]77) ;; + *) cryptsetup_message "WARNING: Permissive UMASK (${UMASK:-$(umask)})." \ + "Private key material within the initrd might be left unprotected." + ;; + esac +fi + +CRYPTO_MODULES= +if [ -r "$TABFILE" ]; then + generate_initrd_crypttab + TABFILE="$DESTDIR/cryptroot/crypttab" + crypttab_foreach_entry populate_CRYPTO_MODULES +fi + +# add required components +manual_add_modules dm_mod +manual_add_modules dm_crypt + +copy_exec /sbin/cryptsetup +copy_exec /sbin/dmsetup +copy_exec /lib/cryptsetup/askpass + +# We need sed. Either via busybox or as standalone binary. +if [ "$BUSYBOX" = n ] || [ ! -e "$BUSYBOXDIR/busybox" ]; then + copy_exec /bin/sed +fi + +# detect whether the host CPU has AES-NI support +if grep -Eq '^flags\s*:(.*\s)?aes(\s.*)?$' /proc/cpuinfo; then + CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }aesni" +else + # workaround for #883595/#901884 (xts depends on ecb) + CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }ecb" +fi + +# add userspace crypto module (only required for opening LUKS2 devices +# we add the module unconditionally as it's the default format) +CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }algif_skcipher" + +if [ "$MODULES" = most ]; then + for d in "$MODULESDIR"/kernel/arch/*/crypto; do + copy_modules_dir "${d#"$MODULESDIR/"}" + done + copy_modules_dir "kernel/crypto" +else + if [ "$MODULES" != "dep" ]; then + # with large initramfs, we always add a basic subset of modules + add_crypto_modules aes cbc chainiv cryptomgr krng sha256 xts + fi + add_crypto_modules $(printf '%s' "${CRYPTO_MODULES-}" | tr ' ' '\n' | sort -u) +fi +copy_file library /lib/cryptsetup/functions /lib/cryptsetup/functions diff --git a/debian/initramfs/hooks/cryptroot-unlock b/debian/initramfs/hooks/cryptroot-unlock new file mode 100644 index 0000000..b05a560 --- /dev/null +++ b/debian/initramfs/hooks/cryptroot-unlock @@ -0,0 +1,30 @@ +#!/bin/sh + +PREREQ="" + +prereqs() +{ + echo "$PREREQ" +} + +case "$1" in + prereqs) + prereqs + exit 0 + ;; +esac + +. /usr/share/initramfs-tools/hook-functions +if [ ! -f "$DESTDIR/bin/cryptroot-unlock" ] && + ! copy_file script /usr/share/cryptsetup/initramfs/bin/cryptroot-unlock /bin/cryptroot-unlock; then + echo "ERROR: Couldn't copy /bin/cryptroot-unlock" >&2 + exit 1 +fi + +if [ -f /etc/initramfs-tools/etc/motd ]; then + copy_file text /etc/initramfs-tools/etc/motd /etc/motd +else + cat >>"$DESTDIR/etc/motd" <<- EOF + To unlock root partition, and maybe others like swap, run \`cryptroot-unlock\`. + EOF +fi diff --git a/debian/initramfs/scripts/local-block/cryptroot b/debian/initramfs/scripts/local-block/cryptroot new file mode 100644 index 0000000..89c2b1c --- /dev/null +++ b/debian/initramfs/scripts/local-block/cryptroot @@ -0,0 +1,21 @@ +#!/bin/sh + +PREREQ="" + +prereqs() +{ + echo $PREREQ +} + +case $1 in +# get pre-requisites +prereqs) + prereqs + exit 0 + ;; +esac + +if [ -x /scripts/local-top/cryptroot ]; then + export CRYPTROOT_STAGE="local-block" + exec /scripts/local-top/cryptroot +fi diff --git a/debian/initramfs/scripts/local-bottom/cryptgnupg-sc b/debian/initramfs/scripts/local-bottom/cryptgnupg-sc new file mode 100644 index 0000000..47be70b --- /dev/null +++ b/debian/initramfs/scripts/local-bottom/cryptgnupg-sc @@ -0,0 +1,18 @@ +#!/bin/sh + +PREREQ="" + +prereqs() { + echo "$PREREQ" +} + +case $1 in + prereqs) + prereqs + exit 0 + ;; +esac + +if [ -x /usr/bin/gpgconf ] && [ -d "/cryptroot/gnupghome" ]; then + gpgconf --homedir="/cryptroot/gnupghome" --kill all +fi diff --git a/debian/initramfs/scripts/local-bottom/cryptopensc b/debian/initramfs/scripts/local-bottom/cryptopensc new file mode 100644 index 0000000..4de8f48 --- /dev/null +++ b/debian/initramfs/scripts/local-bottom/cryptopensc @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +PREREQ="" + +prereqs() +{ + echo "$PREREQ" +} + +case $1 in + prereqs) + prereqs + exit 0 + ;; +esac + +# Hook for stopping smartcard reading software + +if [ ! -x /usr/sbin/pcscd ]; then + exit 0 +fi + +. /scripts/functions + +if PID="$(cat /run/pcscd.pid)" 2>/dev/null && + [ "$(readlink -f "/proc/$PID/exe")" = "/usr/sbin/pcscd" ]; then + log_begin_msg "Stopping pcscd" + kill -TERM "$PID" + log_end_msg +fi diff --git a/debian/initramfs/scripts/local-bottom/cryptroot b/debian/initramfs/scripts/local-bottom/cryptroot new file mode 100644 index 0000000..945739f --- /dev/null +++ b/debian/initramfs/scripts/local-bottom/cryptroot @@ -0,0 +1,22 @@ +#!/bin/sh + +PREREQ="" + +prereqs() +{ + echo "$PREREQ" +} + +case $1 in +prereqs) + prereqs + exit 0 + ;; +esac + +# If we reached this stage, we do have a rootfs mounted +# so let's clean-up cryptroot setup mess... +[ -f /lib/cryptsetup/functions ] || return 0 +. /lib/cryptsetup/functions + +rm -f -- "$CRYPTROOT_COUNT_FILE" diff --git a/debian/initramfs/scripts/local-top/cryptopensc b/debian/initramfs/scripts/local-top/cryptopensc new file mode 100644 index 0000000..344acc6 --- /dev/null +++ b/debian/initramfs/scripts/local-top/cryptopensc @@ -0,0 +1,37 @@ +#!/bin/sh + +set -e + +PREREQ="" + +prereqs() +{ + echo "$PREREQ" +} + +case $1 in + prereqs) + prereqs + exit 0 + ;; +esac + +# Hook for starting smartcard reading software + +if [ ! -x /usr/sbin/pcscd ]; then + exit 0 +fi + +. /scripts/functions + +# Start pcscd daemon normally: +# start-stop-daemon --start --quiet \ +# --pidfile /run/pcscd.pid \ +# --exec /usr/sbin/pcscd +# Alternatively, start pcscd daemon in foreground so that it's pretty colored +# output may be seen on the console, useful for watching error messages since +# pcscd uses syslog which is not available (use --error or --critical to filter +# out debug message clutter): +# /usr/sbin/pcscd --error --foreground & +/usr/sbin/pcscd --foreground & +echo $! >/run/pcscd.pid diff --git a/debian/initramfs/scripts/local-top/cryptroot b/debian/initramfs/scripts/local-top/cryptroot new file mode 100644 index 0000000..1da5ad1 --- /dev/null +++ b/debian/initramfs/scripts/local-top/cryptroot @@ -0,0 +1,252 @@ +#!/bin/sh + +PREREQ="cryptroot-prepare" + +# +# Standard initramfs preamble +# +prereqs() +{ + # Make sure that cryptroot is run last in local-top + local req + for req in "${0%/*}"/*; do + script="${req##*/}" + if [ "$script" != "${0##*/}" ]; then + printf '%s\n' "$script" + fi + done +} + +case $1 in +prereqs) + prereqs + exit 0 + ;; +esac + +. /scripts/functions + +[ -f /lib/cryptsetup/functions ] || return 0 +. /lib/cryptsetup/functions + + +# wait_for_source() +# Wait for encrypted $CRYPTTAB_SOURCE . Set $CRYPTTAB_SOURCE +# to its normalized device name when it shows up; +# return 1 if timeout. +wait_for_source() { + wait_for_udev 10 + + if crypttab_resolve_source; then + # the device is here already, no need to loop + return 0 + fi + + # If the source device hasn't shown up yet, give it a little while + # to allow for asynchronous device discovery (e.g. USB). + # + # We also need to take into account RAID or other devices that may + # only be available on local-block stage. So, wait 5 seconds upfront, + # in local-top; if that fails, end execution relying on local-block + # invocations. Allow $ROOTDELAY/4 invocations with 1s sleep times (with + # a minimum of 20 invocations), and if after that we still fail, then it's + # really time to give-up. Variable $initrd_cnt tracks the re-invocations. + # + # Part of the lines below has been taken from initramfs-tools + # scripts/local's local_device_setup(), as suggested per + # https://launchpad.net/bugs/164044 . + + local slumber=5 + if [ "${CRYPTROOT_STAGE-}" = "local-block" ]; then + slumber=1 + fi + + cryptsetup_message "Waiting for encrypted source device $CRYPTTAB_SOURCE..." + + while [ $slumber -gt 0 ]; do + sleep 1 + + if [ -x /scripts/local-block/lvm2 ]; then + # activate any VG that might hold $CRYPTTAB_SOURCE + /scripts/local-block/lvm2 "$CRYPTTAB_SOURCE" + fi + + if crypttab_resolve_source; then + wait_for_udev 10 + return 0 + fi + + slumber=$(( $slumber - 1 )) + done + return 1 +} + +# setup_mapping() +# Set up a crypttab(5) mapping defined by $CRYPTTAB_NAME, +# $CRYPTTAB_SOURCE, $CRYPTTAB_KEY, $CRYPTTAB_OPTIONS. +setup_mapping() { + local dev initrd_cnt + + # We control here the number of re-invocations of this script from + # local-block - the heuristic is $ROOTDELAY/4, with a minimum of 20. + + if [ -f "$CRYPTROOT_COUNT_FILE" ]; then + initrd_cnt="$(cat <"$CRYPTROOT_COUNT_FILE")" + else + initrd_cnt="${ROOTDELAY:-180}" + initrd_cnt=$(( initrd_cnt/4 )) + if [ $initrd_cnt -lt 20 ]; then + initrd_cnt=20 + fi + echo "$initrd_cnt" >"$CRYPTROOT_COUNT_FILE" + fi + + # The same target can be specified multiple times + # e.g. root and resume lvs-on-lvm-on-crypto + if dm_blkdevname "$CRYPTTAB_NAME" >/dev/null; then + return 0 + fi + + crypttab_parse_options --export --missing-path=fail || return 1 + + if ! wait_for_source; then + if [ $initrd_cnt -eq 0 ]; then + # we've given up + if [ -n "$panic" ]; then + panic "ALERT! encrypted source device $CRYPTTAB_SOURCE does not exist, can't unlock $CRYPTTAB_NAME." + else + # let the user fix matters if they can + echo " ALERT! encrypted source device $CRYPTTAB_SOURCE does not exist, can't unlock $CRYPTTAB_NAME." + echo " Check cryptopts=source= bootarg: cat /proc/cmdline" + echo " or missing modules, devices: cat /proc/modules; ls /dev" + panic "Dropping to a shell." + fi + return 1 # can't continue because environment is lost + else + initrd_cnt=$(( initrd_cnt - 1 )) + echo "$initrd_cnt" >"$CRYPTROOT_COUNT_FILE" + return 0 # allow some attempts on local-block stage + fi + fi + + # our `cryptroot-unlock` script searches for cryptsetup processes + # with a given CRYPTTAB_NAME it their environment + export CRYPTTAB_NAME + + if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ]; then + # no keyscript: interactive unlocking, or key file + + if [ "${CRYPTTAB_KEY#/FIXME-initramfs-rootmnt/}" != "$CRYPTTAB_KEY" ]; then + # skip the mapping for now if the root FS is not mounted yet + sed -rn 's/^\s*[^#[:blank:]]\S*\s+(\S+)\s.*/\1/p' /proc/mounts | grep -Fxq -- "$rootmnt" || return 1 + # substitute the "/FIXME-initramfs-rootmnt/" prefix by the real root FS mountpoint otherwise + CRYPTTAB_KEY="$rootmnt/${CRYPTTAB_KEY#/FIXME-initramfs-rootmnt/}" + fi + + if [ "$CRYPTTAB_KEY" != "none" ]; then + if [ ! -e "$CRYPTTAB_KEY" ]; then + cryptsetup_message "ERROR: Skipping target $CRYPTTAB_NAME: non-existing key file $CRYPTTAB_KEY" + return 1 + fi + # try only once if we have a key file + CRYPTTAB_OPTION_tries=1 + fi + fi + + local count=0 maxtries="${CRYPTTAB_OPTION_tries:-3}" fstype vg rv + while [ $maxtries -le 0 ] || [ $count -lt $maxtries ]; do + if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$CRYPTTAB_KEY" != "none" ]; then + # unlock via keyfile + unlock_mapping "$CRYPTTAB_KEY" + else + # unlock interactively or via keyscript + run_keyscript "$CRYPTTAB_KEY" "$count" | unlock_mapping + fi + rv=$? + count=$(( $count + 1 )) + + if [ $rv -ne 0 ]; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: cryptsetup failed, bad password or options?" + sleep 1 + continue + elif ! dev="$(dm_blkdevname "$CRYPTTAB_NAME")"; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: unknown error setting up device mapping" + return 1 + fi + + if ! fstype="$(get_fstype "$dev")" || [ "$fstype" = "unknown" ]; then + if [ "$CRYPTTAB_TYPE" != "luks" ]; then + # bad password for plain dm-crypt device? or mkfs not run yet? + cryptsetup_message "ERROR: $CRYPTTAB_NAME: unknown fstype, bad password or options?" + wait_for_udev 10 + /sbin/cryptsetup remove -- "$CRYPTTAB_NAME" + sleep 1 + continue + fi + elif [ "$fstype" = lvm2 ]; then + if [ ! -x /sbin/lvm ]; then + cryptsetup_message "WARNING: $CRYPTTAB_NAME: lvm is not available" + return 1 + elif vg="$(lvm pvs --noheadings -o vg_name --config 'log{prefix=""}' -- "$dev")"; then + # activate the VG held by the PV we just unlocked + lvm lvchange -a y --sysinit -- "$vg" + fi + fi + + cryptsetup_message "$CRYPTTAB_NAME: set up successfully" + wait_for_udev 10 + return 0 + done + + cryptsetup_message "ERROR: $CRYPTTAB_NAME: maximum number of tries exceeded" + exit 1 +} + + +####################################################################### +# Begin real processing + +mkdir -p /cryptroot # might not exist yet if the main system has no crypttab(5) + +# Do we have any kernel boot arguments? +if ! grep -qE '^(.*\s)?cryptopts=' /proc/cmdline; then + # ensure $TABFILE exists and has a mtime greater than the boot time + # (existing $TABFILE is preserved) + touch -- "$TABFILE" +else + # let the read builtin unescape the '\' as GRUB substitutes '\' by '\\' in the cmdline + tr ' ' '\n' </proc/cmdline | sed -n 's/^cryptopts=//p' | while IFS= read cryptopts; do + # skip empty values (which can be used to disable the initramfs + # scripts for a particular boot, cf. #873840) + [ -n "$cryptopts" ] || continue + unset -v target source key options + + IFS="," + for x in $cryptopts; do + case "$x" in + target=*) target="${x#target=}";; + source=*) source="${x#source=}";; + key=*) key="${x#key=}";; + *) options="${options+$options,}$x";; + esac + done + + if [ -z "${source:+x}" ]; then + cryptsetup_message "ERROR: Missing source= value in kernel parameter cryptopts=$cryptopts" + else + # preserve mangling + printf '%s %s %s %s\n' "${target:-cryptroot}" "$source" "${key:-none}" "${options-}" + fi + done >"$TABFILE" +fi + +# Do we have any settings from the $TABFILE? +if [ -s "$TABFILE" ]; then + # Create locking directory before invoking cryptsetup(8) to avoid warnings + mkdir -pm0700 /run/cryptsetup + modprobe -q dm_crypt + + crypttab_foreach_entry setup_mapping +fi + +exit 0 |