commit:     72839de16243fb410d587e18d76d3b637fa3f389
Author:     Maciej S. Szmigiero <mail <AT> maciej <DOT> szmigiero <DOT> name>
AuthorDate: Sun Nov 22 01:06:47 2020 +0000
Commit:     Robin H. Johnson <robbat2 <AT> gentoo <DOT> org>
CommitDate: Sun May 22 18:45:16 2022 +0000
URL:        https://gitweb.gentoo.org/proj/genkernel.git/commit/?id=72839de1

genkernel: add keyctl support for loading LUKS passphrase into a keyring

cryptsetup LUKS2 format comes with an ability to automatically unlock
multiple devices (root, swap, etc.) sharing the same passphrase, without
retyping it for each of them, by loading it into the user keyring.

This commit adds such (optional) genkernel support for loading LUKS
passphrase into the user keyring on boot.

In the default mode of operation the newly added key is (possibly) used
only to unlock root and swap devices and is removed soon after that.
By providing appropriate kernel command line parameter the key can be left
in the keyring instead (with an optional timeout) for unlocking other LUKS
devices post-initramfs time.

Because one of the most common use cases of this functionality will be
having an encrypted swap for doing suspend to disk (hibernation) let's also
make sure that we don't unlock the root device when doing so is unnecessary
(when we are resuming the system from hibernation).

Since the security of a FDE passphrase is of paramount importance in this
solution significant care has been taken not to leak it accidentally:
* The passphrase is read directly by keyctl to avoid storing it in the
shell,

* If the passphrase is used only to unlock root and swap devices (which is
the default mode of operation) the init script will check whether its
removal from keyring has actually succeeded and, if not, reboot the system
rather than continue while leaving it exposed,

* keyutils includes a patch (already upstreamed) to wipe the passphrase
from memory when no longer needed.

Signed-off-by: Maciej S. Szmigiero <mail <AT> maciej.szmigiero.name>

 defaults/initrd.scripts   | 52 ++++++++++++++++++++++++++++++++++++++++++++++-
 defaults/linuxrc          | 34 +++++++++++++++++++++++++++++--
 defaults/software.sh      |  7 +++++++
 doc/genkernel.8.txt       | 24 ++++++++++++++++++++++
 gen_cmdline.sh            |  6 ++++++
 gen_determineargs.sh      |  2 ++
 gen_initramfs.sh          | 29 ++++++++++++++++++++++++++
 genkernel.conf            |  3 +++
 gkbuilds/keyutils.gkbuild | 27 ++++++++++++++++++++++++
 9 files changed, 181 insertions(+), 3 deletions(-)

diff --git a/defaults/initrd.scripts b/defaults/initrd.scripts
index 4932783..eb556d6 100644
--- a/defaults/initrd.scripts
+++ b/defaults/initrd.scripts
@@ -2108,6 +2108,54 @@ openLUKS() {
        [ -d "${mntkey}" ] && run rmdir -p "${mntkey}" >/dev/null 2>&1
 }
 
+keyctl_keyadd() {
+       if [ -n "${KEYCTL_KEYDESC}" ]
+       then
+               if [ ! -x /bin/keyctl ]
+               then
+                       bad_msg "keyctl program is missing. Was initramfs built 
without --keyctl parameter?"
+                       exit 1
+               fi
+
+               # not using read to avoid secrets being left in memory
+               stty -echo
+               echo -n "Please type the key '${KEYCTL_KEYDESC}' for the user 
keyring then press Ctrl-D twice: "
+               KEYCTL_KEYID=`keyctl padd user "${KEYCTL_KEYDESC}" @u`
+               echo
+               stty echo
+
+               if [ -n "${KEYCTL_KEYID}" -a -n "${KEYCTL_KEYTIMEOUT}" ]
+               then
+                       keyctl timeout "${KEYCTL_KEYID}" "${KEYCTL_KEYTIMEOUT}"
+               fi
+       fi
+}
+
+keyctl_keyremove() {
+       if [ -n "${KEYCTL_KEYID}" -a -z "${KEYCTL_KEYKEEP}" ]
+       then
+               if [ ! -x /bin/keyctl ]
+               then
+                       bad_msg "keyctl program is missing. Was initramfs built 
without --keyctl parameter?"
+                       exit 1
+               fi
+
+               keyctl revoke "${KEYCTL_KEYID}"
+               keyctl unlink "${KEYCTL_KEYID}" >/dev/null
+
+               # trust but verify
+               if keyctl show "${KEYCTL_KEYID}" >/dev/null 2>&1
+               then
+                       # better reboot than leave the user passphrase 
accidentally exposed
+                       bad_msg "unable to remove the newly added key from 
keyring, rebooting in 5 seconds for security"
+                       sleep 5
+                       reboot -f
+               fi
+
+               KEYCTL_KEYID=
+       fi
+}
+
 iface_name() {
        local ifname="${1}"
 
@@ -2437,7 +2485,7 @@ ipv6_tentative() {
        fi
 }
 
-start_LUKS() {
+start_LUKS_root() {
        # if key is set but neither ssh enabled or key device is given, find
        # the key device
 
@@ -2461,7 +2509,9 @@ start_LUKS() {
                        REAL_ROOT="/dev/mapper/root"
                fi
        fi
+}
 
+start_LUKS_swap() {
        if [ -n "${CRYPT_SWAP_KEY}" ]
        then
                # same for swap, but no need to sleep if root was unencrypted

diff --git a/defaults/linuxrc b/defaults/linuxrc
index 15fbf7c..5ee7804 100644
--- a/defaults/linuxrc
+++ b/defaults/linuxrc
@@ -272,6 +272,15 @@ do
                swap_keydev_fstype=*)
                        CRYPT_SWAP_KEYDEV_FSTYPE=${x#*=}
                ;;
+               keyctl_keydesc=*)
+                       KEYCTL_KEYDESC=${x#*=}
+               ;;
+               keyctl_keytimeout=*)
+                       KEYCTL_KEYTIMEOUT=${x#*=}
+               ;;
+               keyctl_keykeep)
+                       KEYCTL_KEYKEEP=1
+               ;;
                real_resume=*|resume=*)
                        REAL_RESUME=${x#*=}
                ;;
@@ -656,10 +665,23 @@ then
        start_sshd
 fi
 
+keyctl_keyadd
+
 # Initialize LUKS root device except for livecd's
 if [ "${CDROOT}" != '1' ]
 then
-       start_LUKS
+       if ( [ -n "${CRYPT_SWAP_KEY}" ] && [ -z "${CRYPT_SWAP_KEYDEV}" ] ) || \
+          ( [ -n "${CRYPT_SWAP_HEADER}" ] && [ -z "${CRYPT_SWAP_HEADERDEV}" ] )
+       then
+               # the swap key or header might be on the root fs so start it 
first in this case
+               start_LUKS_root
+               luks_root_started=1
+               start_LUKS_swap
+       else
+               # we don't need to start the root at all if we are resuming 
from suspend
+               start_LUKS_swap
+       fi
+
        if [ "${NORESUME}" != '1' ] && [ -n "${REAL_RESUME}" ]
        then
                case "${REAL_RESUME}" in
@@ -691,6 +713,11 @@ then
 
                do_resume
        fi
+
+       if [ -z "${luks_root_started}" ]
+       then
+               start_LUKS_root
+       fi
 fi
 
 run mkdir -p "${NEW_ROOT}"
@@ -1060,7 +1087,8 @@ then
                losetup /dev/loop0 "${CDROOT_PATH}/${LOOPEXT}${LOOP}"
                test_success 'Preparing loop filesystem'
 
-               start_LUKS
+               start_LUKS_root
+               start_LUKS_swap
 
                case ${LOOPTYPE} in
                        normal)
@@ -1302,6 +1330,8 @@ else
        fi
 fi # if [ "${CDROOT}" = '1' ]
 
+keyctl_keyremove
+
 # Re-run to ensure $NEWROOT/etc/initramfs.mounts was processed at least once
 process_initramfs_mounts
 

diff --git a/defaults/software.sh b/defaults/software.sh
index ec2bacb..0dce9e1 100644
--- a/defaults/software.sh
+++ b/defaults/software.sh
@@ -128,6 +128,13 @@ 
GKPKG_JSON_C_SRCTAR="${GKPKG_JSON_C_SRCTAR:-${DISTDIR}/json-c-${GKPKG_JSON_C_PV}
 GKPKG_JSON_C_SRCDIR="${GKPKG_JSON_C_SRCDIR:-json-c-${GKPKG_JSON_C_PV}}"
 
GKPKG_JSON_C_BINPKG="${GKPKG_JSON_C_BINPKG:-%%CACHE%%/json-c-${GKPKG_JSON_C_PV}-%%ARCH%%.tar.xz}"
 
+GKPKG_KEYUTILS_PN="keyutils"
+GKPKG_KEYUTILS_PV="${GKPKG_KEYUTILS_PV:-1.6.3}"
+GKPKG_KEYUTILS_DEPS=""
+GKPKG_KEYUTILS_SRCTAR="${GKPKG_KEYUTILS_SRCTAR:-${DISTDIR}/keyutils-${GKPKG_KEYUTILS_PV}.tar.gz}"
+GKPKG_KEYUTILS_SRCDIR="${GKPKG_KEYUTILS_SRCDIR:-keyutils-${GKPKG_KEYUTILS_PV}}"
+GKPKG_KEYUTILS_BINPKG="${GKPKG_KEYUTILS_BINPKG:-%%CACHE%%/keyutils-${GKPKG_KEYUTILS_PV}-%%ARCH%%.tar.xz}"
+
 GKPKG_KMOD_PN="kmod"
 GKPKG_KMOD_PV="${GKPKG_KMOD_PV:-VERSION_KMOD}"
 GKPKG_KMOD_DEPS="zlib xz zstd"

diff --git a/doc/genkernel.8.txt b/doc/genkernel.8.txt
index ba8fd0a..a5c0b92 100644
--- a/doc/genkernel.8.txt
+++ b/doc/genkernel.8.txt
@@ -468,6 +468,12 @@ system is able to load multiple initramfs.
     `gpg --symmetric -o /path/to/LUKS-key.gpg /path/to/LUKS-key` .
     After that, re-point the *root_key* argument to the new .gpg file.
 
+*--*[*no-*]*keyctl*::
+    Includes or excludes support for keyutils keyctl.
+    This way a LUKS passphrase can be loaded into a keyring at boot time
+    to unlock multiple devices (root, swap, etc.) without retyping it for each
+    one.
+
 *--*[*no-*]*b2sum*::
     Includes or excludes b2sum in the initramfs.
     When enabled, this will compile coreutils' b2sum for you.
@@ -737,6 +743,24 @@ recognized by the kernel itself.
 *swap_keydev_fstype*=<...>::
     Used filesystem for *swap_keydev*. See *rootfstype* for more details.
 
+*keyctl_keydesc*=<...>::
+    Load a passphrase into a keyring at boot time under the key name provided
+    as an argument to this option.
+    This way multiple devices (root, swap, etc.) can be unlocked without
+    retyping the passphrase for each one.
+    You'll need to add this key name as a keyring token to every LUKS device
+    that it is supposed to unlock - have a look at cryptsetup 'token add'
+    operation.
+
+*keyctl_keykeep*::
+    Don't remove the newly added key before starting the real init.
+    Useful if you want to utilize it to unlock LUKS devices post-initramfs.
+
+*keyctl_keytimeout*=<...>::
+    Enable a timeout (in seconds) for the newly added key.
+    This option normally only makes sense when used together with the
+    *keyctl_keykeep* option.
+
 *crypt_silent*::
     Set this to silent all the output related to the cryptographic
     software,  and in case your encrypted device isn't open with the

diff --git a/gen_cmdline.sh b/gen_cmdline.sh
index e53de69..0cba7d1 100755
--- a/gen_cmdline.sh
+++ b/gen_cmdline.sh
@@ -181,6 +181,8 @@ longusage() {
   echo "       --no-luks               Exclude LUKS support"
   echo "       --gpg                   Include GPG-armored LUKS key support"
   echo "       --no-gpg                Exclude GPG-armored LUKS key support"
+  echo "       --keyctl                Include keyctl support for loading LUKS 
passphrase into a keyring"
+  echo "       --no-keyctl             Exclude keyctl support for loading LUKS 
passphrase into a keyring"
   echo "       --b2sum                 Include b2sum"
   echo "       --no-b2sum              Exclude b2sum"
   echo "       --busybox               Include busybox"
@@ -837,6 +839,10 @@ parse_cmdline() {
                        CMD_GPG=$(parse_optbool "$*")
                        print_info 3 "CMD_GPG: ${CMD_GPG}"
                        ;;
+               --keyctl|--no-keyctl)
+                       CMD_KEYCTL=$(parse_optbool "$*")
+                       print_info 3 "CMD_KEYCTL: ${CMD_KEYCTL}"
+                       ;;
                --firmware|--no-firmware)
                        CMD_FIRMWARE=$(parse_optbool "$*")
                        print_info 3 "CMD_FIRMWARE: ${CMD_FIRMWARE}"

diff --git a/gen_determineargs.sh b/gen_determineargs.sh
index 07b2202..ef69414 100755
--- a/gen_determineargs.sh
+++ b/gen_determineargs.sh
@@ -433,6 +433,7 @@ determine_real_args() {
        set_config_with_override STRING REAL_ROOT                             
CMD_REAL_ROOT
        set_config_with_override BOOL   LUKS                                  
CMD_LUKS                                  "no"
        set_config_with_override BOOL   GPG                                   
CMD_GPG                                   "no"
+       set_config_with_override BOOL   KEYCTL                                
CMD_KEYCTL                                "no"
        set_config_with_override BOOL   MDADM                                 
CMD_MDADM                                 "no"
        set_config_with_override STRING MDADM_CONFIG                          
CMD_MDADM_CONFIG
        set_config_with_override BOOL   E2FSPROGS                             
CMD_E2FSPROGS                             "no"
@@ -1099,6 +1100,7 @@ determine_real_args() {
                        FEATURES_REQUIRING_BUSYBOX+=( KEYMAP )
                        FEATURES_REQUIRING_BUSYBOX+=( LVM )
                        FEATURES_REQUIRING_BUSYBOX+=( LUKS )
+                       FEATURES_REQUIRING_BUSYBOX+=( KEYCTL )
                        FEATURES_REQUIRING_BUSYBOX+=( MDADM )
                        FEATURES_REQUIRING_BUSYBOX+=( MULTIPATH )
                        FEATURES_REQUIRING_BUSYBOX+=( SPLASH )

diff --git a/gen_initramfs.sh b/gen_initramfs.sh
index 0b74f4c..227badd 100755
--- a/gen_initramfs.sh
+++ b/gen_initramfs.sh
@@ -484,6 +484,7 @@ append_base_layout() {
        isTrue "${ZFS}" && build_parameters+=( --zfs ) || build_parameters+=( 
--no-zfs )
        isTrue "${SPLASH}" && build_parameters+=( --splash ) || 
build_parameters+=( --no-splash )
        isTrue "${STRACE}" && build_parameters+=( --strace ) || 
build_parameters+=( --no-strace )
+       isTrue "${KEYCTL}" && build_parameters+=( --keyctl ) || 
build_parameters+=( --no-keyctl )
        isTrue "${GPG}" && build_parameters+=( --gpg ) || build_parameters+=( 
--no-gpg )
        isTrue "${LUKS}" && build_parameters+=( --luks ) || build_parameters+=( 
--no-luks )
        isTrue "${FIRMWARE}" && build_parameters+=( --firmware ) || 
build_parameters+=( --no-firmware )
@@ -901,6 +902,33 @@ append_iscsi() {
        fi
 }
 
+append_keyutils() {
+       local PN=keyutils
+       local TDIR="${TEMP}/initramfs-${PN}-temp"
+       if [ -d "${TDIR}" ]
+       then
+               rm -r "${TDIR}" || gen_die "Failed to clean out existing 
'${TDIR}'!"
+       fi
+
+       populate_binpkg ${PN}
+
+       mkdir "${TDIR}" || gen_die "Failed to create '${TDIR}'!"
+
+       unpack "$(get_gkpkg_binpkg "${PN}")" "${TDIR}"
+
+       cd "${TDIR}" || gen_die "Failed to chdir to '${TDIR}'!"
+
+       log_future_cpio_content
+       find . -print0 | "${CPIO_COMMAND}" ${CPIO_ARGS} --append -F 
"${CPIO_ARCHIVE}" \
+               || gen_die "Failed to append ${PN} to cpio!"
+
+       cd "${TEMP}" || die "Failed to chdir to '${TEMP}'!"
+       if isTrue "${CLEANUP}"
+       then
+               rm -rf "${TDIR}"
+       fi
+}
+
 append_lvm() {
        local PN=lvm
        local TDIR="${TEMP}/initramfs-${PN}-temp"
@@ -2050,6 +2078,7 @@ create_initramfs() {
        append_data 'e2fsprogs' "${E2FSPROGS}"
        append_data 'gpg' "${GPG}"
        append_data 'iscsi' "${ISCSI}"
+       append_data 'keyutils' "${KEYCTL}"
        append_data 'luks' "${LUKS}"
        append_data 'lvm' "${LVM}"
        append_data 'bcache' "${BCACHE}"

diff --git a/genkernel.conf b/genkernel.conf
index 7d10496..7045e18 100644
--- a/genkernel.conf
+++ b/genkernel.conf
@@ -80,6 +80,9 @@ NOCOLOR="false"
 # Add GnuPG support
 #GPG="no"
 
+# Add keyctl support for loading LUKS passphrase into a keyring
+#KEYCTL="no"
+
 # Add in early microcode support: this sets the kernel options for early 
microcode loading
 # Possible values: empty/"no", "all", "intel", "amd"
 #MICROCODE="all"

diff --git a/gkbuilds/keyutils.gkbuild b/gkbuilds/keyutils.gkbuild
new file mode 100644
index 0000000..dc232be
--- /dev/null
+++ b/gkbuilds/keyutils.gkbuild
@@ -0,0 +1,27 @@
+# Copyright 1999-2019 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+src_prepare() {
+       default
+
+       sed -i \
+               -e "1iRPATH = -static" \
+               -e 's:-Werror::' \
+               -e '/^NO_SOLIB/d' \
+               Makefile || die
+}
+
+src_compile() {
+       export NO_SOLIB=1
+
+       gkmake keyctl
+}
+
+src_install() {
+       mkdir "${D}"/bin || die "Failed to create '${D}/bin'!"
+       cp -a keyctl "${D}"/bin/ \
+               || die "Failed to copy '${S}/keyctl'!"
+
+       "${STRIP}" --strip-all "${D}"/bin/keyctl \
+               || die "Failed to strip keyctl!"
+}

Reply via email to