From 5ec0fbc8be6bee1e4e7d682b8f12bcea277e3e21 Mon Sep 17 00:00:00 2001 From: Christoph Biedl Date: Sun, 24 May 2020 00:52:08 +0200 Subject: [PATCH] Implement a pin to store the key on a block device. Closes: #185 The "medium" pin allows storing the jwk in a file on a given block device. --- src/pins/medium/clevis-decrypt-medium | 99 +++++++++++++++ src/pins/medium/clevis-encrypt-medium | 121 +++++++++++++++++++ src/pins/medium/clevis-encrypt-medium.1.adoc | 69 +++++++++++ src/pins/medium/dracut.module-setup.sh.in | 29 +++++ src/pins/medium/initramfs.in | 45 +++++++ src/pins/medium/meson.build | 53 ++++++++ src/pins/medium/pin-medium | 67 ++++++++++ src/pins/meson.build | 1 + 8 files changed, 484 insertions(+) create mode 100755 src/pins/medium/clevis-decrypt-medium create mode 100755 src/pins/medium/clevis-encrypt-medium create mode 100644 src/pins/medium/clevis-encrypt-medium.1.adoc create mode 100755 src/pins/medium/dracut.module-setup.sh.in create mode 100755 src/pins/medium/initramfs.in create mode 100644 src/pins/medium/meson.build create mode 100755 src/pins/medium/pin-medium diff --git a/src/pins/medium/clevis-decrypt-medium b/src/pins/medium/clevis-decrypt-medium new file mode 100755 index 00000000..a3065219 --- /dev/null +++ b/src/pins/medium/clevis-decrypt-medium @@ -0,0 +1,99 @@ +#!/bin/bash + +set -eu + +# Copyright (c) 2020 Christoph Biedl +# Author: Christoph Biedl +# +# 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 . +# + +on_exit() { + [ "${TMP:-}" ] || return 0 + [ -e "$TMP" ] || return 0 + if mountpoint -q "$TMP" ; then + if ! umount "$TMP" ; then + echo "Failed to umount $device" >&2 + echo "You need to clean up: $TMP" >&2 + return 1 + fi + fi + # --one-file-system is not available in busybox rm + rm -rf "$TMP" +} + +[ $# -eq 1 ] && [ "${1:-}" = "--summary" ] && exit 2 + +if [ -t 0 ] ; then + echo >&2 + echo 'Usage: clevis decrypt medium < JWE > PLAINTEXT' >&2 + echo >&2 + exit 1 +fi + +read -d . hdr64 +if ! hdr="$(jose fmt --quote="$hdr64" --string --b64load --object --output=-)" ; then + echo 'JWE header corrupt' >&2 + exit 1 +fi + +if [ "$(jose fmt --json="$hdr" --get clevis --get pin --unquote=-)" != 'medium' ] ; then + echo 'JWE pin mismatch!' >&2 + exit 1 +fi + +if ! device="$(jose fmt --json="$hdr" --get clevis --get medium --get device --unquote=-)" ; then + echo 'JWE missing 'clevis.medium.device' header parameter!' >&2 + exit 1 +fi +if ! file="$(jose fmt --json="$hdr" --get clevis --get medium --get file --unquote=-)" ; then + echo 'JWE missing 'clevis.medium.file' header parameter!' >&2 + exit 1 +fi +if ! fstype="$(jose fmt --json="$hdr" --get clevis --get medium --get fstype --unquote=-)" ; then + echo 'JWE missing 'clevis.medium.fstype' header parameter!' >&2 + exit 1 +fi +if ! options="$(jose fmt --json="$hdr" --get clevis --get medium --get options --unquote=-)" ; then + echo 'JWE missing 'clevis.medium.options' header parameter!' >&2 + exit 1 +fi + +if [ "$options" ] ; then + options="ro,$options" +else + options='ro' +fi + +TMP="$(mktemp -d)" +trap 'on_exit' EXIT + +if ! mount -t "$fstype" -o "$options" "$device" "$TMP" ; then + echo "Failed to mount $device" >&2 + exit 1 +fi +if [ ! -f "$TMP/$file" ] ; then + echo "The key file $file does not exist on $device" >&2 + exit 1 +fi +jwk="$(cat "$TMP/$file")" +if ! umount "$TMP" ; then + echo "Failed to umount $device" >&2 + exit 1 +fi + +# clean up tempdir +on_exit + +( printf '%s' "$jwk$hdr64." ; cat ) | exec jose jwe dec --key=- --input=- diff --git a/src/pins/medium/clevis-encrypt-medium b/src/pins/medium/clevis-encrypt-medium new file mode 100755 index 00000000..05ff9e32 --- /dev/null +++ b/src/pins/medium/clevis-encrypt-medium @@ -0,0 +1,121 @@ +#!/bin/sh + +set -eu + +# Copyright (c) 2020 Christoph Biedl +# Author: Christoph Biedl +# +# 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 . +# + +on_exit() { + [ "${TMP:-}" ] || return 0 + [ -e "$TMP" ] || return 0 + if mountpoint -q "$TMP" ; then + if ! umount "$TMP" ; then + echo "Failed to umount $device" >&2 + echo "You need to clean up: $TMP" >&2 + return 1 + fi + fi + rm --one-file-system -rf "$TMP" +} + +SUMMARY='Encrypts using a key file on a medium policy' + +if [ "${1:-}" = '--summary' ] ; then + echo "$SUMMARY" + exit 0 +fi + +if [ -t 0 ] ; then + exec >&2 + echo + echo 'Usage: clevis encrypt medium CONFIG < PLAINTEXT > JWE' + echo + echo "$SUMMARY" + echo + echo 'This command uses the following configuration properties:' + echo + echo ' device: The block device that holds the key file (REQUIRED)' + echo + echo ' file: The file name of the key file (REQUIRED)' + echo + echo ' fstype: The type of the file system (default: "auto")' + echo + echo ' options: Additional mount options (default: none)' + echo + echo ' reuse: Re-use an existing key (default: This is an error)' + echo + exit 2 +fi + +if ! cfg="$(jose fmt --json="${1:-}" --object --output=- 2>/dev/null)" ; then + echo 'Configuration is malformed!' >&2 + exit 1 +fi + +if ! device="$(jose fmt --json="$cfg" --object --get device --unquote=-)" ; then + echo 'Missing the required device property!' >&2 + exit 1 +fi +if ! file="$(jose fmt --json="$cfg" --object --get file --unquote=-)" ; then + echo 'Missing the required file property!' >&2 + exit 1 +fi + +fstype="$(jose fmt --json="$cfg" --object --get fstype --unquote=-)" || fstype='auto' +options="$(jose fmt --json="$cfg" --object --get options --unquote=-)" || options='' +reuse="$(jose fmt --json="$cfg" --object --get reuse --unquote=-)" || reuse='' + +jwk="$(jose jwk gen --input='{"alg":"A256GCM"}')" + +if [ "$options" ] ; then + mopts="rw,noatime,$options" +else + mopts='rw,noatime' +fi + +TMP="$(mktemp -d)" +trap 'on_exit' EXIT + +if ! mount -t "$fstype" -o "$mopts" "$device" "$TMP" ; then + echo "Failed to mount $device" >&2 + exit 1 +fi +if [ "$reuse" ] ; then + if [ ! -f "$TMP/$file" ] ; then + echo "The key file $file does not exist on $device" >&2 + exit 1 + fi +elif [ -e "$TMP/$file" ] ; then + echo "The key file $file already exists on $device" >&2 + exit 1 +fi +( umask 0377 ; printf '%s' "$jwk" >"$TMP/$file" ) +if ! umount "$TMP" ; then + echo "Failed to umount $device" >&2 + exit 1 +fi + +# clean up tempdir +on_exit + +jwe='{"protected":{"clevis":{"pin":"medium","medium":{}}}}' +jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get medium --quote "$device" --set device -UUUU --output=-)" +jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get medium --quote "$file" --set file -UUUU --output=-)" +jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get medium --quote "$fstype" --set fstype -UUUU --output=-)" +jwe="$(jose fmt --json="$jwe" --get protected --get clevis --get medium --quote "$options" --set options -UUUU --output=-)" + +( printf '%s' "$jwe$jwk" ; cat ) | exec jose jwe enc --input=- --key=- --detached=- --compact diff --git a/src/pins/medium/clevis-encrypt-medium.1.adoc b/src/pins/medium/clevis-encrypt-medium.1.adoc new file mode 100644 index 00000000..30bfc625 --- /dev/null +++ b/src/pins/medium/clevis-encrypt-medium.1.adoc @@ -0,0 +1,69 @@ +CLEVIS-ENCRYPT-MEDIUM(1) +======================== +:doctype: manpage + + +== NAME + +clevis-encrypt-medium - Encrypts using a key fle on a medium policy + +== SYNOPSIS + +*clevis encrypt medium* CONFIG < PT > JWE + +== OVERVIEW + +The *clevis encrypt medium* command encrypts using a key file on a +medium policy. Its only argument is the JSON configuration object. + +Encrypting data using the medium pin works like this: + + $ clevis encrypt medium '{"device":"LABEL=keydisk","file":"secret.key"}' < PT > JWE + +To decrypt the data, just pass it to the *clevis decrypt* command: + + $ clevis decrypt < JWE > PT + +== SETTING UP + +The medium must have been formatted before. There is no limitation to +a particular file system as long as it can be mounted with a single +mount command. Also, when unlocking in early userland, support must +already be available. + +Device name like `/dev/sda1` are volatile, it is safer to use +`UUID=...` or `LABEL=...` to specify the device. The blkid(8) program +will show usable values. + +When setting up, the `file` on that medium must not exist yet. See the +reuse option below to override that. + +When used unlocking in early userland, it's recommended to set the +fstype to the actual file system type. + +== CONFIG + +This command uses the following configuration properties: + +* *device* (string) : + The block device that holds the key file, in a form understood by mount(1) (REQUIRED) + +* *file* (string) : + The file name of the key file, relative to the mount point (REQUIRED) + +* *fstype* (string) : + The type of the filesystem in the mount invocation (default: "auto") + +* *options* (string) : + Additional mount options (default: none) + Note that the mount for writing in `clevis-encrypt-medium` is always + mounted "rw,noatime", while the mount for reading in + `clevis-decrypt-medium` is "ro". + +* *reuse* (string) : + If an non-empty string, re-use an existing key file. By default, an + already existing key is considered an error. + +== SEE ALSO + +link:clevis-decrypt.1.adoc[*clevis-decrypt*(1)] diff --git a/src/pins/medium/dracut.module-setup.sh.in b/src/pins/medium/dracut.module-setup.sh.in new file mode 100755 index 00000000..630a5fa2 --- /dev/null +++ b/src/pins/medium/dracut.module-setup.sh.in @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Copyright (c) 2020 Christoph Biedl +# Author: Christoph Biedl +# +# 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 . +# + +depends() { + echo clevis + return 0 +} + +install() { + inst_multiple \ + clevis-decrypt-medium \ + mountpoint +} diff --git a/src/pins/medium/initramfs.in b/src/pins/medium/initramfs.in new file mode 100755 index 00000000..859bb102 --- /dev/null +++ b/src/pins/medium/initramfs.in @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright (c) 2020 Christoph Biedl +# Author: Christoph Biedl +# +# 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 + +. @initramfstoolsdir@/hook-functions + +die() { + code="$1" + msg="$2" + echo " (ERROR): $msg" >&2 + exit $1 +} + +copy_exec @bindir@/clevis-decrypt-medium || die 1 "@bindir@/clevis-decrypt-medium not found" + +find_binary() { + bin_name="$1" + resolved=$(which ${bin_name}) + [ -z "$resolved" ] && die 1 "Unable to find ${bin_name}" + echo "$resolved" +} + +mountpoint_bin=$(find_binary "mountpoint") +copy_exec "${mountpoint_bin}" || die 2 "Unable to copy ${mountpoint_bin} to initrd image" diff --git a/src/pins/medium/meson.build b/src/pins/medium/meson.build new file mode 100644 index 00000000..a0dd0c2d --- /dev/null +++ b/src/pins/medium/meson.build @@ -0,0 +1,53 @@ + +losetup = find_program('losetup', required: false) +mkfs = find_program('mkfs.ext4', required: false) +mount = find_program('mount', required: false) +dracut = dependency('dracut', required: false) +initramfs_tools = find_program('update-initramfs', required: false) + +bins += join_paths(meson.current_source_dir(), 'clevis-decrypt-medium') +bins += join_paths(meson.current_source_dir(), 'clevis-encrypt-medium') +mans += join_paths(meson.current_source_dir(), 'clevis-encrypt-medium.1') + +if losetup.found() and mkfs.found() and mount.found() + env = environment() + env.append('PATH', + join_paths(meson.source_root(), 'src'), + meson.current_source_dir(), + '/usr/libexec', + libexecdir, + separator: ':' + ) + + test('pin-medium', find_program('./pin-medium'), env: env) +else + warning('Will not test medium pin due to missing programs') +endif + +if dracut.found() + dracutdir = dracut.get_pkgconfig_variable('dracutmodulesdir') + '/60' + meson.project_name() + '-pin-medium' + configure_file( + input: 'dracut.module-setup.sh.in', + output: 'module-setup.sh', + install_dir: dracutdir, + configuration: data, + ) +else + warning('Will not install dracut module clevis-pin-medium due to missing dependencies!') +endif + +if initramfs_tools.found() + initramfstools_dir = '/usr/share/initramfs-tools' + initramfs_hooks_dir = '/usr/share/initramfs-tools/hooks' + initramfs_data = configuration_data() + initramfs_data.merge_from(data) + initramfs_data.set('initramfstoolsdir', initramfstools_dir) + configure_file( + input: 'initramfs.in', + output: 'clevis-pin-medium', + install_dir: initramfs_hooks_dir, + configuration: initramfs_data, + ) +else + warning('Will not install initramfs module clevis-pin-medium due to missing dependencies!') +endif diff --git a/src/pins/medium/pin-medium b/src/pins/medium/pin-medium new file mode 100755 index 00000000..e0b7a69d --- /dev/null +++ b/src/pins/medium/pin-medium @@ -0,0 +1,67 @@ +#!/bin/sh + +set -ex + +# Copyright (c) 2020 Christoph Biedl +# Author: Christoph Biedl +# +# 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 . +# + +if [ $(id -u) != 0 ]; then + echo 'WARNING: You must be root to run this test; test skipped.' >&2 + exit 77 +fi + +on_exit() { + [ "${TMP:-}" ] || return 0 + if mountpoint -q "$MP" ; then + if ! umount "$MP" ; then + echo "Failed to umount $device" >&2 + echo "You need to clean up: $TMP" >&2 + return 1 + fi + fi + if [ "${device:-}" ] ; then + losetup -d "$device" + fi + rm --one-file-system -rf "$TMP" +} + +TMP="$(mktemp -d)" +trap 'on_exit' EXIT + +mp="$TMP/mount" +image="$TMP/image" + +fallocate --length=1M "$image" +device="$(losetup --find --show "$image")" +mkfs.ext4 -L key-medium -O ^has_journal "$device" + +cfg="$(printf '{"device":"%s","file":"keyfile"}' "$device")" +inp='hi' +enc="$(printf '%s' "$inp" | clevis encrypt medium "$cfg")" +dec="$(printf '%s' "$enc" | clevis decrypt)" +test "$dec" = "$inp" + +# Use UUID instead of device name +blkid="$TMP/blkid" +blkid -o export "$device" >"$blkid" +. "$blkid" + +cfg="$(printf '{"device":"%s","file":"keyfile2"}' "UUID=$UUID")" +inp='hi' +enc="$(printf '%s' "$inp" | clevis encrypt medium "$cfg")" +dec="$(printf '%s' "$enc" | clevis decrypt)" +test "$dec" = "$inp" diff --git a/src/pins/meson.build b/src/pins/meson.build index 12670ae8..401baa16 100644 --- a/src/pins/meson.build +++ b/src/pins/meson.build @@ -1,3 +1,4 @@ +subdir('medium') subdir('sss') subdir('tang') subdir('tpm2')