From f6729411cee3efe0bae89d440faa108634d8bd90 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 8 Feb 2017 11:38:34 +0100 Subject: [PATCH 01/20] refkit-image.bbclass: fix swupd xattr workaround Setting DEPENDS_${PN} has no effect. DEPENDS must be set instead, to ensure that attr-native really is available. The name also was wrong. Signed-off-by: Patrick Ohly --- meta-refkit/classes/refkit-image.bbclass | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meta-refkit/classes/refkit-image.bbclass b/meta-refkit/classes/refkit-image.bbclass index 78be54db69..190dcc6ae6 100644 --- a/meta-refkit/classes/refkit-image.bbclass +++ b/meta-refkit/classes/refkit-image.bbclass @@ -105,8 +105,8 @@ IMAGE_INSTALL_append = "${@ ' efi-combo-trigger' if ${REFKIT_USE_DSK_IMAGES} and # Setting a label explicitly on the directory prevents it # from inheriting other undesired attributes like security.SMACK64TRANSMUTE # from upper folders (see xattr-images.bbclass for details). -DEPENDS_${PN}_append = " \ - ${@ bb.utils.contains('IMAGE_FEATURES', 'swupd', 'xattr-native', '', d)} \ +DEPENDS_append = " \ + ${@ bb.utils.contains('IMAGE_FEATURES', 'swupd', 'attr-native', '', d)} \ " fix_var_lib_swupd () { if ${@bb.utils.contains('IMAGE_FEATURES', 'smack', 'true', 'false', d)} && From ecae57186602f75b72e11de08c1335ae94970d12 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 1 Feb 2017 16:21:13 +0100 Subject: [PATCH 02/20] refkit image: flexible partition sizes The VFAT parition size specified in the DSK_IMAGE_LAYOUT no longer matched the actual size used for images, and 15MB indeed is barely large enough. Adding tools like cryptsetup to the initramfs causes it to exceed that limit. Instead of hard-coding a fixed size in the .wks.in file, now a per-image variable is inserted instead. The size of the rootfs can also be controlled, including not limiting it at all and thus adapting the image size to the size of the rootfs. This will be needed for the installer image which is larger than normal images. The default is the same as before (30MB for the VFAT partitions, 4GB total image size). Signed-off-by: Patrick Ohly --- meta-refkit/classes/image-dsk.bbclass | 4 ++-- meta-refkit/classes/refkit-image.bbclass | 4 ++++ meta-refkit/wic/refkit-directdisk.wks.in | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/meta-refkit/classes/image-dsk.bbclass b/meta-refkit/classes/image-dsk.bbclass index f3b7b8397f..d1366a5769 100644 --- a/meta-refkit/classes/image-dsk.bbclass +++ b/meta-refkit/classes/image-dsk.bbclass @@ -75,7 +75,7 @@ DSK_IMAGE_LAYOUT ??= ' \ "partition_01_primary_uefi_boot": { \ "name": "primary_uefi", \ "uuid": 0, \ - "size_mb": 15, \ + "size_mb": ${REFKIT_VFAT_MB}, \ "source": "${IMAGE_ROOTFS}/boot/", \ "filesystem": "vfat", \ "type": "${PARTITION_TYPE_EFI}" \ @@ -83,7 +83,7 @@ DSK_IMAGE_LAYOUT ??= ' \ "partition_02_secondary_uefi_boot": { \ "name": "secondary_uefi", \ "uuid": 0, \ - "size_mb": 15, \ + "size_mb": ${REFKIT_VFAT_MB}, \ "source": "${IMAGE_ROOTFS}/boot/", \ "filesystem": "vfat", \ "type": "${PARTITION_TYPE_EFI_BACKUP}" \ diff --git a/meta-refkit/classes/refkit-image.bbclass b/meta-refkit/classes/refkit-image.bbclass index 190dcc6ae6..f4140e6551 100644 --- a/meta-refkit/classes/refkit-image.bbclass +++ b/meta-refkit/classes/refkit-image.bbclass @@ -274,7 +274,11 @@ IMAGE_FSTYPES_remove = "live" # Activate "dsk" image type. IMAGE_CLASSES += "${@ 'image-dsk' if ${REFKIT_USE_DSK_IMAGES} else ''}" +# By default, the full image is going to use roughly 4GB, independent +# of the actual roofs size. WKS_FILE = "refkit-directdisk.wks.in" +REFKIT_VFAT_MB ??= "30" +REFKIT_IMAGE_SIZE ??= "--fixed-size 3700M" WIC_CREATE_EXTRA_ARGS += " -D" # Inherit after setting variables that get evaluated when importing diff --git a/meta-refkit/wic/refkit-directdisk.wks.in b/meta-refkit/wic/refkit-directdisk.wks.in index a3cc14324a..7bc7eddb8a 100644 --- a/meta-refkit/wic/refkit-directdisk.wks.in +++ b/meta-refkit/wic/refkit-directdisk.wks.in @@ -4,6 +4,6 @@ # EFI stub, kernel, kernel cmdline, and the initrd bootloader --ptable gpt -part --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --fixed-size 30M --label primary_uefi --part-type C12A7328-F81F-11D2-BA4B-00A0C93EC93B --align 1024 --use-uuid -part --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --fixed-size 30M --label secondary_uefi --part-type E3C9E316-0B5C-4DB8-817D-F92DF00215AE --align 1024 --use-uuid -part / --source rootfs --fixed-size 3700M --fstype=ext4 --label rootfs --align 1024 --uuid ${REMOVABLE_MEDIA_ROOTFS_PARTUUID_VALUE} +part --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --fixed-size ${REFKIT_VFAT_MB}M --label primary_uefi --part-type C12A7328-F81F-11D2-BA4B-00A0C93EC93B --align 1024 --use-uuid +part --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --fixed-size ${REFKIT_VFAT_MB}M --label secondary_uefi --part-type E3C9E316-0B5C-4DB8-817D-F92DF00215AF --align 1024 --use-uuid +part / --source rootfs ${REFKIT_IMAGE_SIZE} --fstype=ext4 --label rootfs --align 1024 --uuid ${REMOVABLE_MEDIA_ROOTFS_PARTUUID_VALUE} From 2c2a6d0317fc244bbdda2bf972df1ee1cedb7af8 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Fri, 27 Jan 2017 08:00:58 +0100 Subject: [PATCH 03/20] refkit: separate installer image The Ostro OS OX approach of embedding the installer into the initramfs had several limitations: - accidentally booting the image tainted the image and made it impossible to install from it - installer image and installed image had to have exactly the same content - not very user friendly, in particular no easy way to configure the installation This new approach takes an arbitrary base image (in our case, the same content as in refkit-image-common) and adds to it: - the "image-installer" installer script - several other image files The content of the script is highly configurable. The generic part is in image-installer.bbclass, the refkit specific part in refkit-installer-image.bb. One possible usage is to add a systemd unit which can be started directly after booting and that will do a fully automated installation, for use in a factory. The manual installation can be tested in runqemu with: $ mkdir -p my-machine $ truncate -s 4G my-machine/hard-disk $ runqemu serial nographic refkit-installer-image wic intel-corei7-64 'qemuparams=-drive if=virtio,file=my-machine/hard-disk,format=raw' ovmf Pick an image file [(RETURN) = (0) = refkit-image-common-intel-corei7-64.wic, (1) = refkit-image-computervision-intel-corei7-64.wic]: Boot device is /dev/vda3. Found the following additional disk(s): vdb Pick a target device [(RETURN) = (0) = vdb]: Installing from /usr/lib/image-installer/refkit-image-common-intel-corei7-64.wic to /dev/vdb. Proceed? Type yes to confirm and no to abort: yes Running: kpartx -sav /usr/lib/image-installer/refkit-image-common-intel-corei7-64.wic Running: mount /dev/mapper/loop0p3 /tmp/tmp.PnK7AL Running: sgdisk -o /dev/vdb The operation has completed successfully. Running: sgdisk -n 1:+0:+15M -c 1:primary_uefi -t 1:EF00 -u 1:c63df54b-122c-4515-9e30-db041407809a -- /dev/vdb Setting name! partNum is 0 REALLY setting name! The operation has completed successfully. Running: sgdisk -A 1:set:2 -- /dev/vdb The operation has completed successfully. Running: mkfs.fat /dev/vdb1 mkfs.fat 4.0 (2016-05-06) Running: mount -t vfat /dev/vdb1 /tmp/tmp.IViBan Running: cp -r /tmp/tmp.PnK7AL/boot/EFI_internal_storage /tmp/tmp.IViBan/EFI Running: sgdisk -n 2:+0:+15M -c 2:secondary_uefi -t 2:2700 -u 2:c4e695f4-b184-41a3-9f4f-2c2f4a48aacd -- /dev/vdb Setting name! partNum is 1 REALLY setting name! The operation has completed successfully. Running: mkfs.fat /dev/vdb2 mkfs.fat 4.0 (2016-05-06) Running: mount -t vfat /dev/vdb2 /tmp/tmp.qhjKVX Running: cp -r /tmp/tmp.PnK7AL/boot/EFI_internal_storage /tmp/tmp.qhjKVX/EFI Running: sgdisk -n 3:+0:-1s -c 3:rootfs -t 3:8300 -u 3:12345678-9abc-def0-0fed-cba987654320 -- /dev/vdb Setting name! partNum is 2 REALLY setting name! The operation has completed successfully. Running: mkfs.ext4 -q -v -F -U 12345678-9abc-def0-0fed-cba987654320 /dev/vdb3 /dev/vdb3 contains a ext4 file system last mounted on / on Fri Jan 27 07:31:59 2017 fs_types for mke2fs.conf resolution: 'ext4' Running: mount -t ext4 /dev/vdb3 /tmp/tmp.awaVqF Running: rsync -aAX /tmp/tmp.PnK7AL/ /tmp/tmp.awaVqF/ done $ ln my-machine/hard-disk tmp-glibc/deploy/images/intel-corei7-64/my-installed-image-intel-corei7-64.wic $ cp tmp-glibc/deploy/images/intel-corei7-64/refkit-installer-image-intel-corei7-64.qemuboot.conf tmp-glibc/deploy/images/intel-corei7-64/my-installed-image-intel-corei7-64.qemuboot.conf $ runqemu my-installed-image wic intel-corei7-64 ovmf Signed-off-by: Patrick Ohly --- meta-refkit/classes/image-installer.bbclass | 357 ++++++++++++++++++ .../include/refkit-supported-recipes.txt | 6 + .../images/refkit-installer-image.bb | 188 +++++++++ 3 files changed, 551 insertions(+) create mode 100644 meta-refkit/classes/image-installer.bbclass create mode 100644 meta-refkit/recipes-image/images/refkit-installer-image.bb diff --git a/meta-refkit/classes/image-installer.bbclass b/meta-refkit/classes/image-installer.bbclass new file mode 100644 index 0000000000..c7cd85ed50 --- /dev/null +++ b/meta-refkit/classes/image-installer.bbclass @@ -0,0 +1,357 @@ +# This class can be inherited by an arbitrary image recipe to install +# the "image-installer" command plus one or more image files into the +# image. +# +# Basically that turns the image using image-installer.bbclass into an +# "installer image" which can be booted from a removal media to +# install those other images permanently onto internal storage of a +# device. +# +# The "image-installer" is a shell script that gets assembled from +# several fragments provided by image-installer.bbclass, by the distro +# or by the image recipe. The expected usage is that distros using +# image-installer.bbclass will heavily customize the final script. +# +# Because the shell code is embedded in the recipe, regular bitbake +# variables can be injected easily and the shell syntax is checked +# already by the bitbake parser. The downside is that we are limited +# to shell code that the bitbake parser understands (no bash +# extensions, for example) and error reports about the shell syntax +# are a bit hard to read. + +# INSTALLER_SOURCE_IMAGES contains one or more entries of the format +# : which then get copied into +# the rootfs of this image under INSTALLER_IMAGE_DATADIR for use by +# the installer script. +# +# The actual installer code and the input format are tightly coupled, +# so installer code below is just a fake stub which needs to be replaced +# with something that supports both the source images and the target +# hardware. +INSTALLER_SOURCE_IMAGES ??= "" + +INSTALLER_IMAGE_DATADIR = "${libdir}/image-installer" +INSTALLER_BINARY = "${IMAGE_ROOTFS}${sbindir}/image-installer" + +# Sanity check INSTALLER_SOURCE_IMAGES once. +python () { + for entry in d.getVar('INSTALLER_SOURCE_IMAGES').split(): + components = entry.split(':') + if len(components) != 2: + bb.fatal('%s in INSTALLER_SOURCE_IMAGES must have the format :' % entry) +} + +python install_images () { + import os + import shutil + import subprocess + import stat + + deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE') + install_dir = oe.path.join(d.getVar('IMAGE_ROOTFS'), d.getVar('INSTALLER_IMAGE_DATADIR')) + machine = d.getVar('MACHINE') + for image, suffix in [x.split(':') for x in d.getVar('INSTALLER_SOURCE_IMAGES').split()]: + imagename = '%s-%s.%s' % (image, machine, suffix) + path = os.path.join(deploy_dir_image, imagename) + if not os.path.exists(path): + bb.fatal('Image %s for INSTALLER_SOURCE_IMAGES entry %s:%s not found.' % (path, image, suffix)) + subprocess.check_output(['install', '-d', install_dir]) + # Preserve the symbolic links, because that makes images available + # under a constant name while also keeping the information about the + # exact version of the image which got included. + dest = os.path.join(install_dir, imagename) + if os.path.islink(path): + link_dest = os.path.basename(os.readlink(path)) + os.symlink(link_dest, dest) + os.lchown(dest, 0, 0) + dest = os.path.join(os.path.dirname(dest), link_dest) + path = os.path.realpath(path) + # Hard-linking both saves space during the build *and* + # sparseness of the image file. The fallback doesn't, but shouldn't + # be needed most of the time. + try: + os.link(path, dest) + except IOError as ex: + bb.warn('Hard-linking from %s to %s failed. Falling back to full copy, which looses sparseness: %s' % + (path, dest, str(ex))) + shutil.copy2(path, dest) + # We run under pseudo, so chown/chmod does not really change the attributes + # of the original image file. + os.chown(dest, 0, 0) + mode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH + os.chmod(dest, mode) +} +do_rootfs[depends] += "${@ ' '.join([x.split(':')[0] + ':do_image_complete' for x in '${INSTALLER_SOURCE_IMAGES}'.split()]) }" +do_rootfs[postfuncs] += " install_images " + + +# The default installer can be used interactively or +# configured to run automatically by setting environment +# variables in advance. +# +# The flow is: +# - determine which image is to be installed +# - determine which disks are suitable as target, automatically +# filtering out the boot disk and everything that is read-only +# or completely unusable (removable media) +# - determine which target to install to +# - execute installation +# +# See below for the commands used by each step. +# +# The default installer code is free of bashisms and passed +# checks with checkbashisms.pl and checkshell. The "local" +# keyword (although supported by bash and dash) is avoided +# in favor of wrapping function bodies with local variables +# in a subshell. +INSTALLER_DEFAULT () { +#!/bin/sh -e + +${INSTALLER_LOGGING} +${INSTALLER_UTILS} +${INSTALLER_PICK_INPUT} +${INSTALLER_FIND_OUTPUT} +${INSTALLER_PICK_OUTPUT} +${INSTALLER_INSTALL} + +pick_input +find_output +pick_output +install_image +info "Installed $CHOSEN_INPUT on $CHOSEN_OUTPUT successfully." +} + +INSTALLER ??= "${INSTALLER_DEFAULT}" + +# Ensure that whatever runtime tools are needed by the installer +# script are actually in the image. +INSTALLER_RDEPENDS ??= " \ + util-linux \ +" +IMAGE_INSTALL_append = " ${INSTALLER_RDEPENDS}" + +# Installs whatever is contained in ${INSTALLER} as ${INSTALLER_BINARY}. +python install_installer () { + import stat + + destfile = d.getVar('INSTALLER_BINARY') + bb.utils.mkdirhier(os.path.dirname(destfile)) + with open(destfile, 'w') as f: + f.write(d.getVar('INSTALLER')) + os.chown(destfile, 0, 0) + mode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | \ + stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + os.chmod(destfile, mode) +} +do_rootfs[postfuncs] += " install_installer " + +# Utility code for reporting to the user of the script. +INSTALLER_LOGGING[shellcheck] = "sh" +INSTALLER_LOGGING () { + fatal () { + echo >&2 "ERROR: $*" + exit 1 + } + + error () { + echo >&2 "ERROR: $*" + } + + info () { + echo >/dev/tty "$*" + } +} + +# Cleanup handling, common code for selecting among different input and output +# options interactively, etc. +INSTALLER_UTILS[shellcheck] = "sh" +INSTALLER_UTILS () { + # Skip user interaction. Can be set also in the environment before + # calling the script. + FORCE=${FORCE:-no} + + # log and run a command + execute () { + info "Running: $*" + "$@" + } + + # cleanup commands which are to be run when exiting + CLEANUP_CMDS= + add_cleanup () { + CLEANUP_CMDS="$*; $CLEANUP_CMDS" + } + remove_cleanup () { + CLEANUP_CMDS=$(echo "$CLEANUP_CMDS" | sed -e "s|$*; ||") + } + cleanup () { + eval "$CLEANUP_CMDS" + } + trap cleanup EXIT + + pick_option () ( + ambiguous_msg="$1"; shift + prompt="$1"; shift + chosen="$1"; shift + num_options=$# + + # If something was set already, just use it. + if [ "$chosen" ]; then + echo "$chosen" + return + fi + + if [ "$FORCE" = "yes" ]; then + if [ "$num_options" -ne 1 ]; then + fatal "$ambiguous_msg, cannot proceed: $*" + fi + echo "$1" + else + while true; do + if [ "$num_options" -eq 0 ]; then + printf >/dev/tty "%s" "$prompt [no defaults]: " + read answer + if [ "$answer" ]; then + echo "$answer" + return + fi + else + printf >/dev/tty "%s" "$prompt [" + i=0 + for option in "$@"; do + if [ $i -eq 0 ]; then + printf >/dev/tty "%s" "(RETURN) = (0) = $option" + else + printf >/dev/tty "%s" ", ($i) = $option" + fi + i=$( expr $i + 1 ) + done + printf >/dev/tty "%s" "]: " + + get_option () ( + number=$1; shift + i=0 + for option in "$@"; do + if [ $i -eq "$number" ]; then + echo "$option" + return 0 + fi + i=$( expr $i + 1 ) + done + error "$answer is not a valid shortcut number, try again." + return 1 + ) + + read answer + if [ ! "$answer" ]; then + get_option 0 "$@" && return + elif echo "$answer" | grep -q -e '^[0-9]*$'; then + # Might be an invalid number, so do not return unconditionally! + get_option "$answer" "$@" && return + else + echo "$answer" + return + fi + fi + done + fi + ) + + confirm_install () ( + if [ "$FORCE" != "yes" ]; then + while true; do + printf "%s" 'Proceed? Type "yes" to confirm and "no" to abort: ' + read answer + case $answer in + yes) break;; + no) info "Aborting as requested."; return 1;; + esac + done + fi + ) +} + +INSTALLER_PICK_INPUT[shellcheck] = "sh" +INSTALLER_PICK_INPUT () { + # We know what's going to be available, therefore we don't need code which looks + # for images. + AVAILABLE_INPUT="${@ ' '.join(['%s-${MACHINE}.%s' % (image, suffix) for image, suffix in [x.split(':') for x in d.getVar('INSTALLER_SOURCE_IMAGES').split()]])}" + CHOSEN_INPUT="${CHOSEN_INPUT:-}" + + pick_input () { + # shellcheck disable=SC2086 + CHOSEN_INPUT=$(pick_option "ambiguous input images" "Pick an image file" "$CHOSEN_INPUT" $AVAILABLE_INPUT) || exit 1 + } +} + + +INSTALLER_FIND_OUTPUT[shellcheck] = "sh" +INSTALLER_FIND_OUTPUT () { + AVAILABLE_OUTPUT="" + + _find_output () ( + # Find block devices and filter out our boot device and read-only devices. + local root_device=$(findmnt / --output SOURCE --noheadings) + # Might have been a symlink. + root_device=$(readlink -f "$root_device") + info "Boot device is $root_device." + local exclude= + if [ "$root_device" ]; then + # lsblk knows about the device topology and orders accordingly + # when the NAME column is enabled. Here we are relying on the + # fact that dependent items appear below the disk they are + # rooted in. + local disk= + # shellcheck disable=SC2034 + exclude=$( lsblk --output NAME,KNAME,TYPE --ascii --noheadings | while read name kname type; do + if [ "$type" = "disk" ]; then + disk=$kname + fi + if [ "/dev/$kname" = "$root_device" ]; then + echo "$disk" + break + fi + done ) + fi + + # ignore: + # boot device, + # CD-ROM, + # devices which aren't readable (for example removable device with no media) + AVAILABLE_OUTPUT=$( lsblk --nodeps --output KNAME,TYPE --noheadings | while read kname type; do + if [ "$kname" != "$exclude" ] && + [ "$type" != "rom" ] && + dd "if=/dev/$kname" of=/dev/null bs=1 count=1 2>/dev/null; then + printf " %s" "$kname" + fi + done ) + info "Found the following additional disk(s):$AVAILABLE_OUTPUT" + echo "$AVAILABLE_OUTPUT" + ) + find_output () { + AVAILABLE_OUTPUT=$(_find_output) + } +} + +INSTALLER_PICK_OUTPUT[shellcheck] = "sh" +INSTALLER_PICK_OUTPUT () { + CHOSEN_OUTPUT=${CHOSEN_OUTPUT:-} + pick_output () { + # shellcheck disable=SC2086 + CHOSEN_OUTPUT=$(pick_option "ambiguous target devices" "Pick a target device" "$CHOSEN_OUTPUT" $AVAILABLE_OUTPUT) || exit 1 + } +} + +# This is a non-functional stub which needs to be replaced. +INSTALLER_EMPTY[shellcheck] = "sh" +INSTALLER_EMPTY () { + install_image () ( + input="${INSTALLER_IMAGE_DATADIR}/$CHOSEN_INPUT" + output="/dev/$CHOSEN_OUTPUT" + info "Installing from $input to $output." + + confirm_install || exit 1 + fatal "installation not implemented" + ) +} +INSTALLER_INSTALL ??= "${INSTALLER_EMPTY}" diff --git a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt index 23af53b26e..c8b62c744c 100644 --- a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt +++ b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt @@ -37,6 +37,7 @@ appfw-test-app@iotqa atop@openembedded-layer attr@core autoconf@core +autoconf-archive@openembedded-layer automake@core bad-groups-app@iotqa base-files@core @@ -67,6 +68,7 @@ dbus-glib@core dbus-test@core dbus@core diffutils@core +dosfstools@core dri2proto@core e2fsprogs@core efi-combo-trigger@refkit @@ -140,6 +142,7 @@ keymaps@core keyutils@security-framework kmod@core krb5@openembedded-layer +libaio@core libarchive@core libatomic-ops@core libcap@core @@ -191,6 +194,7 @@ linux-libc-headers@core linux-yocto@core lowpan-tools@networking-layer lttng-ust@core +lvm2@openembedded-layer m4@core make@core mesa@core @@ -198,6 +202,7 @@ mmap-smack-test@security-smack mpfr@core mraa-test@iotqa mraa@refkit +multipath-tools@openembedded-layer ncurses@core netbase@core nettle@core @@ -261,6 +266,7 @@ systemd@core taglib@core tbb@openembedded-layer tcp-smack-test@security-smack +thin-provisioning-tools@openembedded-layer tiff@core tpm-tools@security tremor@core diff --git a/meta-refkit/recipes-image/images/refkit-installer-image.bb b/meta-refkit/recipes-image/images/refkit-installer-image.bb new file mode 100644 index 0000000000..65912f09f4 --- /dev/null +++ b/meta-refkit/recipes-image/images/refkit-installer-image.bb @@ -0,0 +1,188 @@ +SUMMARY = "IoT Reference OS Kit image with embedded installer." +DESCRIPTION = "IoT Reference OS Kit image with embedded image-installer command for copying IoT Reference OS Kit onto internal storage of a device." + +# The supported format for refkit images is wic because that is already getting built; +# installing files from it is a bit harder than using the tar format, but doable. +# Below, we use the kpartx command to get access to the rootfs files. +INSTALLER_SOURCE_IMAGES ?= " \ + refkit-image-common:wic \ + refkit-image-computervision:wic \ + refkit-image-gateway:wic \ +" + +# Allow wic to resize the image as needed by overriding the default fixes size. +REFKIT_IMAGE_SIZE ?= "" + +# The refkit specific part is derived from the Ostro OS XT installer. +REFKIT_INSTALLER_UEFI_COMBO[shellcheck] = "sh" +REFKIT_INSTALLER_UEFI_COMBO () { + populate () { + output=$1 + gdisk_pnum=$2 + uuid=$3 + rootfs=$4 + output_mounted= + output_mountpoint= + + cleanup_populate () { + [ "$output_mounted" ] && umount "$output_mountpoint" + [ "$output_mountpoint" ] && rmdir "$output_mountpoint" + remove_cleanup cleanup_populate + } + add_cleanup cleanup_populate + + if ! output_mountpoint=$(mktemp -d); then + fatal "could not create mount point" + fi + + # Might be with or without p in the middle (sda1 vs mmcblk0p1). + partition= + for i in $output*$gdisk_pnum; do + if [ -e "$i" ]; then + if [ "$partition" ]; then + fatal "partition #$gdisk_pnum in $output not unique?!" + fi + partition=$i + else + fatal "$output*$gdisk_pnum not found in $output" + fi + done + if [ ! "$partition" ]; then + fatal "could not identify partition #$gdisk_pnum in $output" + fi + + if [ "$uuid" ]; then + # Assume that there's only one ext4 partition and it contains root fs (/) + if ! execute mkfs.ext4 -q -v -F -U "$uuid" "$partition"; then + fatal "formatting target rootfs partition $gdisk_pnum failed" + fi + if ! execute mount -t ext4 "$partition" "$output_mountpoint"; then + fatal "mounting target rootfs failed" + else + output_mounted=1 + fi + if ! execute rsync -aAX "$rootfs/" "$output_mountpoint/"; then + fatal "copying rootfs failed" + fi + else + if ! execute mkfs.fat "$partition"; then + fatal "formating vfat partition $gdisk_pnum failed" + fi + if ! execute mount -t vfat "$partition" "$output_mountpoint"; then + fatal "mounting target vfat partition $gdisk_pnum failed" + else + output_mounted=1 + fi + if ! execute cp -r "$rootfs/boot/EFI_internal_storage" "$output_mountpoint/EFI"; then + fatal "copying EFI files failed" + fi + fi + if ! sync; then + fatal "syncing data failed" + fi + cleanup_populate + } + + install_image () { + input="${INSTALLER_IMAGE_DATADIR}/$CHOSEN_INPUT" + output="/dev/$CHOSEN_OUTPUT" + info "Installing from $input to $output." + + confirm_install || return 1 + + input_mountpoint= + input_mounted= + + cleanup_install_image () { + [ "$input_mounted" ] && umount "$input_mountpoint" + [ "$input_mountpoint" ] && rmdir "$input_mountpoint" + [ "$input" ] && kpartx -d "$input" + remove_cleanup cleanup_install_image + } + add_cleanup cleanup_install_image + + # Assume that there's only one ext4 partition at the end and it + # contains the systems' rootfs. + loopdev=$(execute kpartx -sav "$input" | tail -1 | sed -e 's/^\(add map \)*\([^ ]*\).*/\2/') + if [ ! "$loopdev" ]; then + fatal "kpartx failed for $input" + fi + if ! input_mountpoint=$(mktemp -d); then + fatal "could not create mount point" + fi + if ! execute mount "/dev/mapper/$loopdev" "$input_mountpoint"; then + fatal "count not mount rootfs from /dev/mapper/$loopdev" + else + input_mounted=1 + fi + + # Clear all partition data on disk + if ! execute sgdisk -o "$output"; then + fatal "sgdisk $output has failed - damaged disk?" + fi + + # Read partition description from rootfs. + if ! . "$input_mountpoint/boot/emmc-partitions-data"; then + fatal "reading $input_mountpoint/boot/emmc-partitions-data failed" + fi + + # Create partitions. + pnum=0 + gdisk_pnum=1 + while [ "$pnum" -lt "$PART_COUNT" ]; do + eval size="\$PART_${pnum}_SIZE" + eval uuid="\$PART_${pnum}_UUID" + eval type_id="\$PART_${pnum}_TYPE" + eval lname="\$PART_${pnum}_NAME" + eval fs="\$PART_${pnum}_FS" + + if [ "$gdisk_pnum" -eq "$PART_COUNT" ]; then + # Make the last partition take the rest of the space + if ! execute sgdisk -n "$gdisk_pnum:+0:-1s" -c "$gdisk_pnum:$lname" \ + -t "$gdisk_pnum:$type_id" -u "${gdisk_pnum}:${uuid}" -- "$output"; then + fatal "creating rootfs partition failed" + fi + else + if ! execute sgdisk -n "$gdisk_pnum:+0:+${size}M" -c "$gdisk_pnum:$lname" \ + -t "$gdisk_pnum:$type_id" -u "${gdisk_pnum}:${uuid}" -- "$output"; then + fatal "creating vfat partition $gdisk_pnum failed" + fi + fi + + if [ "$gdisk_pnum" -eq 1 ]; then + # Set bootable flag on the first partition + if ! execute sgdisk -A "${gdisk_pnum}:set:2" -- "$output"; then + fatal "making first partition bootable failed" + fi + fi + + if [ "$lname" = "rootfs" ]; then + populate "$output" "$gdisk_pnum" "$uuid" "$input_mountpoint" + else + populate "$output" "$gdisk_pnum" "" "$input_mountpoint" + fi + + pnum=$(expr $pnum + 1) + gdisk_pnum=$(expr $gdisk_pnum + 1) + done + cleanup_install_image + } +} +INSTALLER_INSTALL ?= "${REFKIT_INSTALLER_UEFI_COMBO}" + +INSTALLER_RDEPENDS_append = " \ + dosfstools \ + e2fsprogs-mke2fs \ + gptfdisk \ + kpartx \ + rsync \ +" + +inherit image-installer + +REFKIT_INSTALLER_IMAGE_EXTRA_FEATURES ?= "${REFKIT_IMAGE_FEATURES_COMMON}" +REFKIT_INSTALLER_IMAGE_EXTRA_INSTALL ?= "${REFKIT_IMAGE_INSTALL_COMMON}" +REFKIT_IMAGE_EXTRA_FEATURES += "${REFKIT_INSTALLER_IMAGE_EXTRA_FEATURES}" +REFKIT_IMAGE_EXTRA_INSTALL += "${REFKIT_INSTALLER_IMAGE_EXTRA_INSTALL}" + +inherit refkit-image From b889c8815f03cc6fd4fe81cd9a931d9794ad1beb Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 1 Feb 2017 16:27:31 +0100 Subject: [PATCH 04/20] installer-image: optionally support LUKS When the "luks" distro feature is set, the refkit-installer-image sets up LUKS for the rootfs and the refkit-initramfs opens that before mounting the rootfs. Currently a proof-of-concept because a well-known password is needed. Depends on cryptsetup from meta-openembedded. Signed-off-by: Patrick Ohly --- .../include/refkit-supported-recipes.txt | 2 + .../files/initramfs-framework-refkit-luks | 52 +++++++++++++++++++ .../images/initramfs-framework-refkit-luks.bb | 24 +++++++++ .../recipes-image/images/refkit-initramfs.bb | 5 ++ .../images/refkit-installer-image.bb | 18 ++++++- 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks create mode 100644 meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb diff --git a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt index c8b62c744c..0d6e2e56eb 100644 --- a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt +++ b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt @@ -61,6 +61,7 @@ connman@core coreutils@core cracklib@core cryptodev-linux@core +cryptsetup@openembedded-layer curl@core dash@openembedded-layer db@core @@ -123,6 +124,7 @@ icu@core iftop@networking-layer ima-evm-utils@integrity initramfs-framework-ima@integrity +initramfs-framework-refkit-luks@refkit initramfs-framework@core initscripts@core intel-microcode@intel diff --git a/meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks b/meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks new file mode 100644 index 0000000000..36ac2b8cea --- /dev/null +++ b/meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks @@ -0,0 +1,52 @@ +#!/bin/sh + +luks_enabled() { + [ "$bootparam_root" ] +} + +LUKS_NAME=rootfs +LUKS_PASSWORD=refkit + +luks_run() { + C=0 + delay=${bootparam_rootdelay:-1} + timeout=${bootparam_roottimeout:-5} + + if [ "`echo ${bootparam_root} | cut -c1-5`" = "UUID=" ]; then + root_uuid=`echo $bootparam_root | cut -c6-` + bootparam_root="/dev/disk/by-uuid/$root_uuid" + fi + + if [ "`echo ${bootparam_root} | cut -c1-9`" = "PARTUUID=" ]; then + root_uuid=`echo $bootparam_root | cut -c10-` + bootparam_root="/dev/disk/by-partuuid/$root_uuid" + fi + + while true; do + if [ $(( $C * $delay )) -gt $timeout ]; then + fatal "LUKS root partition '$bootparam_root' not found." + fi + + if [ -e "$bootparam_root" ]; then + cryptsetup isLuks "$bootparam_root" 2>/dev/null + case $? in + 0) # LUKS volumne + if echo "$LUKS_PASSWORD" | cryptsetup open --type luks "$bootparam_root" "$LUKS_NAME" --key-file -; then + bootparam_root="/dev/mapper/$LUKS_NAME" + return + fi + ;; + 1) # not a LUKS volume + return + ;; + *) # something else + debug "Error accessing "$bootparam_root" via cryptsetup" + ;; + esac + fi + + debug "Sleeping for $delay second(s) to wait root to settle..." + sleep $delay + C=$(( $C + 1 )) + done +} diff --git a/meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb b/meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb new file mode 100644 index 0000000000..163b1516b3 --- /dev/null +++ b/meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb @@ -0,0 +1,24 @@ +# This recipe creates a module for the initramfs-framework in OE-core +# which opens the partition identified via the root +# kernel parameter as a LUKS container and changes bootparam_root so +# that the following init code uses the decrypted volume. +# +# Currently a proof-of-concept with a fixed password, do not use in +# production! + +SUMMARY = "LUKS module for the modular initramfs system" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420" +RDEPENDS_${PN} += "initramfs-framework-base" + +SRC_URI = " \ + file://initramfs-framework-refkit-luks \ +" + +do_install () { + install -d ${D}/init.d + install ${WORKDIR}/initramfs-framework-refkit-luks ${D}/init.d/80-luks +} + +FILES_${PN} = "/init.d" +RDEPENDS_${PN} = "cryptsetup" diff --git a/meta-refkit/recipes-image/images/refkit-initramfs.bb b/meta-refkit/recipes-image/images/refkit-initramfs.bb index 34e43a075c..6f229b3d01 100644 --- a/meta-refkit/recipes-image/images/refkit-initramfs.bb +++ b/meta-refkit/recipes-image/images/refkit-initramfs.bb @@ -30,11 +30,16 @@ IMAGE_FEATURES = "" # Instead we have additional image feature(s). IMAGE_FEATURES[validitems] += " \ ima \ + luks \ " IMAGE_FEATURES += " \ ${@bb.utils.contains('DISTRO_FEATURES', 'ima', 'ima', '', d)} \ " FEATURE_PACKAGES_ima = "initramfs-framework-ima" +IMAGE_FEATURES += " \ + ${@bb.utils.contains('DISTRO_FEATURES', 'luks', 'luks', '', d)} \ +" +FEATURE_PACKAGES_luks = "initramfs-framework-refkit-luks" IMAGE_LINGUAS = "" diff --git a/meta-refkit/recipes-image/images/refkit-installer-image.bb b/meta-refkit/recipes-image/images/refkit-installer-image.bb index 65912f09f4..9acfe3432d 100644 --- a/meta-refkit/recipes-image/images/refkit-installer-image.bb +++ b/meta-refkit/recipes-image/images/refkit-installer-image.bb @@ -23,10 +23,14 @@ REFKIT_INSTALLER_UEFI_COMBO () { rootfs=$4 output_mounted= output_mountpoint= + output_luks= + LUKS_NAME=rootfs + LUKS_PASSWORD=refkit cleanup_populate () { [ "$output_mounted" ] && umount "$output_mountpoint" [ "$output_mountpoint" ] && rmdir "$output_mountpoint" + [ "$output_luks" ] && cryptsetup close "$output_luks" remove_cleanup cleanup_populate } add_cleanup cleanup_populate @@ -48,10 +52,20 @@ REFKIT_INSTALLER_UEFI_COMBO () { fi done if [ ! "$partition" ]; then - fatal "could not identify partition #$gdisk_pnum in $output" + fatal "could not identify parition #$gdisk_pnum in $output" fi if [ "$uuid" ]; then + if ${@ bb.utils.contains('DISTRO_FEATURES', 'luks', 'true', 'false', d) }; then + if ! echo "$LUKS_PASSWORD" | execute cryptsetup luksFormat "$partition" --key-file -; then + fatal "formatting $partition as LUKS contained failed" + fi + if ! echo "$LUKS_PASSWORD" | execute cryptsetup open --type luks "$partition" "$LUKS_NAME" --key-file -; then + fatal "opening $partition as LUKS container failed" + fi + output_luks=$LUKS_NAME + partition=/dev/mapper/$LUKS_NAME + fi # Assume that there's only one ext4 partition and it contains root fs (/) if ! execute mkfs.ext4 -q -v -F -U "$uuid" "$partition"; then fatal "formatting target rootfs partition $gdisk_pnum failed" @@ -176,8 +190,10 @@ INSTALLER_RDEPENDS_append = " \ gptfdisk \ kpartx \ rsync \ + ${@ bb.utils.contains('DISTRO_FEATURES', 'luks', 'cryptsetup', '', d) } \ " + inherit image-installer REFKIT_INSTALLER_IMAGE_EXTRA_FEATURES ?= "${REFKIT_IMAGE_FEATURES_COMMON}" From bfbe14c3c20fbb999f5536ebce6f8ed568fbd5a5 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Mon, 6 Feb 2017 08:42:18 +0100 Subject: [PATCH 05/20] refkit image: whole-disk encryption with TPM NVRAM key The per-machine key is stored in TPM NVRAM, where it is only available to valid, authorized software once Secure Boot is enabled. This is a proof-of-concept. It relies heavily on working Secure Boot, which requires further work and solving some usability questions, like distinguishing between a device fresh from the factory and a locked down device. Bringing up TPM inside the initramfs depends on IPv4. init-ifupdown is used to provide the necessary config files. Signed-off-by: Patrick Ohly --- .../include/refkit-supported-recipes.txt | 1 + .../files/initramfs-framework-refkit-luks | 52 ------- .../images/initramfs-framework-refkit-luks.bb | 141 +++++++++++++++++- .../images/refkit-boot-settings.inc | 19 +++ .../images/refkit-installer-image.bb | 63 ++++++-- 5 files changed, 207 insertions(+), 69 deletions(-) delete mode 100644 meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks create mode 100644 meta-refkit/recipes-image/images/refkit-boot-settings.inc diff --git a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt index 0d6e2e56eb..dbfc215a72 100644 --- a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt +++ b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt @@ -123,6 +123,7 @@ i2c-quark-board@quark-bsp icu@core iftop@networking-layer ima-evm-utils@integrity +init-ifupdown@core initramfs-framework-ima@integrity initramfs-framework-refkit-luks@refkit initramfs-framework@core diff --git a/meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks b/meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks deleted file mode 100644 index 36ac2b8cea..0000000000 --- a/meta-refkit/recipes-image/images/files/initramfs-framework-refkit-luks +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/sh - -luks_enabled() { - [ "$bootparam_root" ] -} - -LUKS_NAME=rootfs -LUKS_PASSWORD=refkit - -luks_run() { - C=0 - delay=${bootparam_rootdelay:-1} - timeout=${bootparam_roottimeout:-5} - - if [ "`echo ${bootparam_root} | cut -c1-5`" = "UUID=" ]; then - root_uuid=`echo $bootparam_root | cut -c6-` - bootparam_root="/dev/disk/by-uuid/$root_uuid" - fi - - if [ "`echo ${bootparam_root} | cut -c1-9`" = "PARTUUID=" ]; then - root_uuid=`echo $bootparam_root | cut -c10-` - bootparam_root="/dev/disk/by-partuuid/$root_uuid" - fi - - while true; do - if [ $(( $C * $delay )) -gt $timeout ]; then - fatal "LUKS root partition '$bootparam_root' not found." - fi - - if [ -e "$bootparam_root" ]; then - cryptsetup isLuks "$bootparam_root" 2>/dev/null - case $? in - 0) # LUKS volumne - if echo "$LUKS_PASSWORD" | cryptsetup open --type luks "$bootparam_root" "$LUKS_NAME" --key-file -; then - bootparam_root="/dev/mapper/$LUKS_NAME" - return - fi - ;; - 1) # not a LUKS volume - return - ;; - *) # something else - debug "Error accessing "$bootparam_root" via cryptsetup" - ;; - esac - fi - - debug "Sleeping for $delay second(s) to wait root to settle..." - sleep $delay - C=$(( $C + 1 )) - done -} diff --git a/meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb b/meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb index 163b1516b3..14770498d6 100644 --- a/meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb +++ b/meta-refkit/recipes-image/images/initramfs-framework-refkit-luks.bb @@ -11,14 +11,141 @@ LICENSE = "MIT" LIC_FILES_CHKSUM = "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420" RDEPENDS_${PN} += "initramfs-framework-base" -SRC_URI = " \ - file://initramfs-framework-refkit-luks \ -" +require refkit-boot-settings.inc + +refkit_luks[shellcheck] = "sh" +refkit_luks () { + + luks_enabled() { + [ "$bootparam_root" ] + } + + luks_run () { + C=0 + delay=${bootparam_rootdelay:-1} + timeout=${bootparam_roottimeout:-5} + + if [ "$(echo "$bootparam_root" | cut -c1-5)" = "UUID=" ]; then + root_uuid=$(echo "$bootparam_root" | cut -c6-) + bootparam_root=/dev/disk/by-uuid/$root_uuid + fi + + if [ "$(echo "$bootparam_root" | cut -c1-9)" = "PARTUUID=" ]; then + root_uuid=$(echo "$bootparam_root" | cut -c10-) + bootparam_root=/dev/disk/by-partuuid/$root_uuid + fi + + while true; do + seconds=$( expr "$C" '*' "$delay" ) + # shellcheck disable=SC2035 + if [ "$seconds" -gt "$timeout" ]; then + fatal "LUKS root partition $bootparam_root not found." + fi + + # The same refkit-initramfs is used for live boot from USB stick + # and for locked-down booting from internal disk, therefore it + # has to support booting with and without encryption. + # + # TODO: However, when booting from an internal disk, it must + # enforce encryption, otherwise an attacker could downgrade + # the installation from encrypted LUKS to something unencrypted + # under his control. The data would be gone, but integrity + # protection would have failed. + # + # TODO: use two different initramfs variants and set up devices + # so that they only boot less secure installer images until + # installed, then reject them in the future. + if [ -e "$bootparam_root" ]; then + cryptsetup isLuks "$bootparam_root" 2>/dev/null + case $? in + 0) # LUKS volumne found + keyfile=$(mktemp) + keyfile_offset= + tcsd_pid= + luks_cleanup () { + dd if=/dev/zero of="$keyfile" count=1 bs="$(stat -c '%s' "$keyfile")" + rm "$keyfile" + if [ "$tcsd_pid" ]; then + kill "$tcsd_pid" + fi + ifdown lo + } + if ${@ bb.utils.contains('DISTRO_FEATURES', 'tpm1.2', 'true', 'false', d) }; then + # Bring up IPv4 (needed by tcsd and tpm-tools) and tcsd itself. + ifup lo + tcsd -f & + tcsd_pid=$! + while true; do + if ! kill -0 "$tcsd_pid"; then + luks_cleanup + fatal "tcsd terminated unexpectedly" + fi + # Once tcsd has sockets open, we can talk to it. + # shellcheck disable=SC2010 + if ls -l "/proc/$tcsd_pid/fd" | grep -q -w "socket"; then + break + fi + sleep 1 + done + + if ! tpm_nvread -i "${REFKIT_DISK_ENCRYPTION_NVRAM_INDEX}" -f "$keyfile" -z; then + luks_cleanup + fatal "Error reading NVRAM area with index ${REFKIT_DISK_ENCRYPTION_NVRAM_INDEX}" + fi + keyfile_offset="${REFKIT_DISK_ENCRYPTION_NVRAM_ID_LEN}" + od "$keyfile" + if [ "$(head -c "$keyfile_offset" "$keyfile")" != "${REFKIT_DISK_ENCRYPTION_NVRAM_ID}" ]; then + luks_cleanup + fatal "Unexpected content in NVRAM area" + fi + # Lock access until next reboot. + if ! tpm_nvread -i "${REFKIT_DISK_ENCRYPTION_NVRAM_INDEX}" -s 0 -z; then + luks_cleanup + fatal "Error locking NVRAM area with index ${REFKIT_DISK_ENCRYPTION_NVRAM_INDEX}" + fi + fi + if [ ! -s "$keyfile" ]; then + printf "%s" "${REFKIT_DISK_ENCRYPTION_PASSWORD}" >"$keyfile" + keyfile_offset=0 + fi + if cryptsetup open --type luks "$bootparam_root" "${REFKIT_DEVICE_MAPPER_ROOTFS_NAME}" --key-file "$keyfile" --keyfile-offset "$keyfile_offset"; then + bootparam_root="/dev/mapper/${REFKIT_DEVICE_MAPPER_ROOTFS_NAME}" + luks_cleanup + return + fi + luks_cleanup + ;; + 1) # not a LUKS volume + return + ;; + *) # something else + debug "Error accessing $bootparam_root via cryptsetup" + ;; + esac + fi + + debug "Sleeping for $delay second(s) to wait root to settle..." + sleep "$delay" + C=$( expr $C + 1 ) + done + } -do_install () { - install -d ${D}/init.d - install ${WORKDIR}/initramfs-framework-refkit-luks ${D}/init.d/80-luks } +python do_install () { + import os + + os.makedirs(os.path.join(d.getVar('D'), 'init.d')) + with open(os.path.join(d.getVar('D'), 'init.d', '80-luks'), 'w') as f: + f.write(d.getVar('refkit_luks')) +} + +# netbase is needed because it enables IPv6, and tpm-tools happens +# to communicate with trousers via IPv6. Probably could be reconfigured +# to use only IPv4. + FILES_${PN} = "/init.d" -RDEPENDS_${PN} = "cryptsetup" +RDEPENDS_${PN} = " \ + cryptsetup \ + ${@ bb.utils.contains('DISTRO_FEATURES', 'tpm1.2', 'trousers tpm-tools strace netbase init-ifupdown', '', d) } \ +" diff --git a/meta-refkit/recipes-image/images/refkit-boot-settings.inc b/meta-refkit/recipes-image/images/refkit-boot-settings.inc new file mode 100644 index 0000000000..1ec45fc4ad --- /dev/null +++ b/meta-refkit/recipes-image/images/refkit-boot-settings.inc @@ -0,0 +1,19 @@ +# refkit-initramfs and refkit-installer-image cooperate to implement +# whole-disk encryption. + +# Determines what name is used under /dev/mapper/ for the rootfs. +REFKIT_DEVICE_MAPPER_ROOTFS_NAME ??= "rootfs" + +# Insecure, well-known password as fallback when there is no TPM. +# TODO: ensure that this feature can't be used in production. +REFKIT_DISK_ENCRYPTION_PASSWORD ??= "refkit" + +# Index of the TPM NVRAM area used for random the per-machine disk encryption key. +# The area contains a short ID + version, followed by the key. +REFKIT_DISK_ENCRYPTION_NVRAM_INDEX ??= "1" +REFKIT_DISK_ENCRYPTION_NVRAM_ID ??= "REFKIT_0" +REFKIT_DISK_ENCRYPTION_NVRAM_ID_LEN = "${@ len('${REFKIT_DISK_ENCRYPTION_NVRAM_ID}') }" + +# Default size of the random disk encryption key (only used during installation, +# initramfs determines the size based on what is in the NVRAM area). +REFKIT_DISK_ENCRYPTION_KEY_SIZE ??= "32" diff --git a/meta-refkit/recipes-image/images/refkit-installer-image.bb b/meta-refkit/recipes-image/images/refkit-installer-image.bb index 9acfe3432d..1a4b2c7905 100644 --- a/meta-refkit/recipes-image/images/refkit-installer-image.bb +++ b/meta-refkit/recipes-image/images/refkit-installer-image.bb @@ -13,6 +13,8 @@ INSTALLER_SOURCE_IMAGES ?= " \ # Allow wic to resize the image as needed by overriding the default fixes size. REFKIT_IMAGE_SIZE ?= "" +require refkit-boot-settings.inc + # The refkit specific part is derived from the Ostro OS XT installer. REFKIT_INSTALLER_UEFI_COMBO[shellcheck] = "sh" REFKIT_INSTALLER_UEFI_COMBO () { @@ -24,18 +26,23 @@ REFKIT_INSTALLER_UEFI_COMBO () { output_mounted= output_mountpoint= output_luks= - LUKS_NAME=rootfs - LUKS_PASSWORD=refkit + LUKS_NAME=installerrootfs + LUKS_PASSWORD="${REFKIT_DISK_ENCRYPTION_PASSWORD}" + # Use something which is guaranteed to not be persistent. + keydir=$(TMPDIR=/dev/shm mktemp -dt keydir.XXXXXX) + keyfile="$keydir/keyfile" + keyfile_offset= cleanup_populate () { - [ "$output_mounted" ] && umount "$output_mountpoint" + [ "$keydir" ] && (dd if=/dev/zero of="$keyfile" count=1 bs="${REFKIT_DISK_ENCRYPTION_KEY_SIZE}"; rm -rf "$keydir" ) + [ "$output_mounted" ] && execute umount "$output_mountpoint" [ "$output_mountpoint" ] && rmdir "$output_mountpoint" - [ "$output_luks" ] && cryptsetup close "$output_luks" + [ "$output_luks" ] && execute cryptsetup close "$output_luks" remove_cleanup cleanup_populate } add_cleanup cleanup_populate - if ! output_mountpoint=$(mktemp -d); then + if ! output_mountpoint=$(mktemp -dt output-partition.XXXXXX); then fatal "could not create mount point" fi @@ -56,11 +63,46 @@ REFKIT_INSTALLER_UEFI_COMBO () { fi if [ "$uuid" ]; then + if ${@ bb.utils.contains('DISTRO_FEATURES', 'tpm1.2', 'true', 'false', d) }; then + # This uses the well-known (all zero) owner and SRK secrets, + # thus granting any process running on the device access to the + # TPM. + # TODO: lock down access to system processes? + if ! execute tpm_takeownership -y -z; then + fatal "taking ownership of TPM failed - needs to be reset?" + fi + # We store a random key in the TPM NVRAM where it is accessible + # to the initramfs. The initramfs will turn off read-access + # after it has retrieved the key, so nothing else that gets started + # later will have access to the key. + if ! execute tpm_nvdefine -i "${REFKIT_DISK_ENCRYPTION_NVRAM_INDEX}" -s "$( expr "${REFKIT_DISK_ENCRYPTION_NVRAM_ID_LEN}" + "${REFKIT_DISK_ENCRYPTION_KEY_SIZE}" )" -p 'AUTHREAD|AUTHWRITE|READ_STCLEAR' -y -z; then + fatal "creating NVRAM area failed" + fi + if ! (printf "%s" "${REFKIT_DISK_ENCRYPTION_NVRAM_ID}" && + dd if=/dev/urandom bs="${REFKIT_DISK_ENCRYPTION_KEY_SIZE}" count=1) >"$keyfile"; then + fatal "key creation failed" + fi + keyfile_offset="${REFKIT_DISK_ENCRYPTION_NVRAM_ID_LEN}" + if ! execute tpm_nvwrite -i "${REFKIT_DISK_ENCRYPTION_NVRAM_INDEX}" -z -f "$keyfile"; then + fatal "storing key in NVRAM failed" + fi + # Lock access until reboot. + if ! execute tpm_nvread -i "${REFKIT_DISK_ENCRYPTION_NVRAM_INDEX}" -z -s 0; then + fatal "locking key in NVRAM failed" + fi + fi + + # Unsafe fallback without TPM: well-known password. + # TODO: detect when this ends up getting used in production. + if [ ! -s "$keyfile" ]; then + printf "%s" "$LUKS_PASSWORD" >"$keyfile" + keyfile_offset=0 + fi if ${@ bb.utils.contains('DISTRO_FEATURES', 'luks', 'true', 'false', d) }; then - if ! echo "$LUKS_PASSWORD" | execute cryptsetup luksFormat "$partition" --key-file -; then + if ! execute cryptsetup luksFormat "$partition" --batch-mode --key-file "$keyfile" --keyfile-offset "$keyfile_offset"; then fatal "formatting $partition as LUKS contained failed" fi - if ! echo "$LUKS_PASSWORD" | execute cryptsetup open --type luks "$partition" "$LUKS_NAME" --key-file -; then + if ! execute cryptsetup open --type luks "$partition" "$LUKS_NAME" --key-file "$keyfile" --keyfile-offset "$keyfile_offset"; then fatal "opening $partition as LUKS container failed" fi output_luks=$LUKS_NAME @@ -108,9 +150,9 @@ REFKIT_INSTALLER_UEFI_COMBO () { input_mounted= cleanup_install_image () { - [ "$input_mounted" ] && umount "$input_mountpoint" + [ "$input_mounted" ] && execute umount "$input_mountpoint" [ "$input_mountpoint" ] && rmdir "$input_mountpoint" - [ "$input" ] && kpartx -d "$input" + [ "$input" ] && execute kpartx -d "$input" remove_cleanup cleanup_install_image } add_cleanup cleanup_install_image @@ -121,7 +163,7 @@ REFKIT_INSTALLER_UEFI_COMBO () { if [ ! "$loopdev" ]; then fatal "kpartx failed for $input" fi - if ! input_mountpoint=$(mktemp -d); then + if ! input_mountpoint=$(mktemp -dt input-rootfs.XXXXXX); then fatal "could not create mount point" fi if ! execute mount "/dev/mapper/$loopdev" "$input_mountpoint"; then @@ -191,6 +233,7 @@ INSTALLER_RDEPENDS_append = " \ kpartx \ rsync \ ${@ bb.utils.contains('DISTRO_FEATURES', 'luks', 'cryptsetup', '', d) } \ + ${@ bb.utils.contains('DISTRO_FEATURES', 'tpm1.2', 'trousers tpm-tools', '', d) } \ " From 52682c5713869b96b5b8d2c4e843274b073588b1 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 8 Feb 2017 11:29:39 +0100 Subject: [PATCH 06/20] refkit + linux-yocto/intel: temporarily bump SRCREV_meta The .scc files are merged upstream, but not all SRCREVs updates have found their way into OE-core (sent), meta-intel (not even sent) or refkit. Signed-off-by: Patrick Ohly --- meta-refkit/conf/distro/refkit.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meta-refkit/conf/distro/refkit.conf b/meta-refkit/conf/distro/refkit.conf index 4dc75e1f8e..96faf46334 100644 --- a/meta-refkit/conf/distro/refkit.conf +++ b/meta-refkit/conf/distro/refkit.conf @@ -303,6 +303,11 @@ PACKAGE_ARCH_pn-rhino = "${TUNE_PKGARCH}" require conf/distro/include/yocto-uninative.inc INHERIT += "uninative" +# temporary workaround: dm-verity.scc already submitted and merged, but not +# all SRCREVs updated +SRCREV_meta_pn-linux-yocto = "7e8ec462b6bc68cb0f3cb0064909e035db1c4038" +SRCREV_meta_pn-linux-intel = "7e8ec462b6bc68cb0f3cb0064909e035db1c4038" + # Disable running fsck at boot. System clock is typically wrong at early boot # stage due to lack of RTC backup battery. This causes unnecessary fixes being # made due to filesystem metadata time stamps being in future. From 7ea993b67799d07a28554570a4071e6eb66903b6 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 8 Feb 2017 11:29:39 +0100 Subject: [PATCH 07/20] refkit: update meta-openembedded The fixes for multipath-tools compilation are needed for the installer image. multipath-tools was blacklisted because of a compile problem on ARM. We can override that in the distro, a patch fixing that upstream was sent. * meta-openembedded 5ecbf9b...044e518 (105): > smbnetfs gnome-disk-utility contacts kexecboot initramfs-kexecboot-klibc-image system-config-keyboard system-setup-keyboard geany geany-plugins efivar efibootmgr gimp system-config-keyboard tumbler: Blacklist > recipes: blacklist recipes runtime depending on blacklisted recipes > recipes: blacklist recipes depending on blacklisted recipes > python-edbus exquisite elementary libeweather unionfs-fuse xfsprogs gmtk devilspie2 gnome-backgrounds gnome-desktop gnome-mime-data gtk-engines gtksourceview2 libgnomekbd libidl-native onboard libgpewidget ubi-utils-klibc kexec-tools-klibc gupnp-igd gupnp-tools dvb-apps gstreamer mpd crda netcat-openbsd wireshark gnokii libmbim mosh networkmanager-openvpn libtelepathy dbus-daemon-proxy libdbus-c++ php vala-dbus-binding-tool collectd libgxim pywbem gtkhtml2 fbida fontforge libsexy wayland-fits xstdcmap xf86-video-glamo font-adobe-100dpi font-adobe-utopia-100dpi font-bh-100dpi font-bh-lucidatypewriter-100dpi font-misc-misc crash a2jmidid libsdl2-mixer libsdl-mixer minidlna sylpheed libsdl2-ttf libsdl-ttf ode pidgin postgresql syslog-ng usb-modeswitch xdg-user-dirs gateone python3-cryptography-vectors python3-ndg-httpsclient python-cryptography-vectors python-pbr bundler netdata menulibre openzone xfce4-verve-plugin iperf terminus-font xf86-video-nouveau ipmiutil klibc-utils pmbw multipath-tools gparted, gnome-system-monitor, php, vala-dbus-binding-tool, gtkmathview, lmsensors, postgresql: Blacklist > gnome-disk-utility: add dbus-glib-native to DEPENDS > nostromo: add base-passwd to DEPENDS > sthttpd: add base-passwd to DEPENDS > xfce4-notifyd: add dbus-glib-native to DEPENDS > geany: fix build > abiword: fix build > gedit: fix build > gegl: fix build > libgnomeui: fix build > evolution-data-server: fix build > poppler: add qttools-native to qt5 dependency to fix build > libglade: fix build and cleanup > esound: fix build > audiofile: add alsa-lib to DEPENDS > exfat-utils: update SRC_URI > glibmm: add dependency on glib-2.0-native > libwebsockets: Add recipe for version 2.1.0 > libuv: Add recipe for version 1.10.2 > libev: Add recipe for version 4.24 > dconf: make gtk+3 dependency optional > dconf: fix bash completion in wrong package > dconf: depends on dbus > udisks: Make use of bitbake variables for sbin, lib locations. > nginx: update to version 1.11.9 > nginx: update to version 1.10.3 > apache: use the APACHE_MIRROR variable in the SRC_URI > python-py: update to version 1.4.32 > python-sqlalchemy: update to version 1.1.5 > python-werkzeug: update to version 0.11.15 > python-thrift: update to version 0.10.0 > python-pyalsaaudio: update to version 0.8.2 > gateone: correct the inherit to use setuptools > python-pbr: change the dependency to be a RDEPENDS and fix build issues > procmail: avoid bashism in do_install > cryptsetup: enable native compilation > lvm2: enable native compilation > fuse: support native compilation > lvm2: remove unbuildable 2.02.138 > linux-yocto-tiny-kexecboot: remove obsoleted recipe > initramfs-kexecboot-image: disable runtime dependency on run-postinsts > udisks: add missing hard dependency > lm_sensors: update SRC_URI > python-pycrypto: add app to meta-python > iperf: remove depricated package > ipmiutil: fix build issue > libatasmart: unify the complier and CFLAGS for host build > postgresgl: update to 9.4.10 and build fix > nginx: handle systemd service file > python-pytest: update version to 3.0.6 > python-lxml: update to version 3.7.2 > python-requests: update to version 2.13.0 > python-ndg-httpsclient: add to the DEPENDS rather than replace > python-cryptography-vectors: add to the DEPENDS rather than replace > python-pyudev: update to version 0.21.0 > python-pybind11: Update to version 2.0.1 > python-idna: update to version 2.2 > udisks2: add libxslt-native to DEPENDS for xsltproc to be in sysroot > m2crypto: depend on typing > typing: add version 3.5.3.0 > geoclue: add missing dependency on gobject-introspection-native > vboxguestdrivers: upgrade to 5.1.14 to fix compatibility with 4.9 kernel > gst-instruments: add recipe > python-pytz: update to 2016.10 > python-pyyaml: add dependency on python > python3-multidict: make repeated builds work even without sphinx > python-pyopenssl: Don't overwrite inherited dependencies > python-cffi: Don't overwrite inherited dependencies > libnet-ssleay-perl: fix build error > opencv: Configure freetype via PACKAGECONFIG > multipath-tools: update to 0.6.4 > multipath-tools: fix building of shared objects > remove some more True options to getVar calls > nano: upgrade to 2.7.4 > jack: add ARM NEON support for sample conversions > python-pyopenssl: Fix build for per recipe sysroot. > python-ndg-httpsclient: Fix build for per recipe sysroot. > python-cffi: Fix build for per-recipe sysroot. > python-click: update to version 6.7 > python-psutil: upgrade to version 5.0.1 > python-pyro4: update to version 4.53 > python-serpent: add new recipe for version 1.16 > python-selectors34: add new recipe for version 1.1 > libgphoto2: remove bash runtime dependency. > luajit: mips and mipsel are 32bit targets too > xfce: add dbus-glib-native to DEPENDS to fix do_configure > tremor: add (from oe-core) > python-netaddr: update to version 0.7.19 > python-flask-bootstrap: update to version 3.3.7.1 > python-ipaddress: update to version 1.0.18 > nginx: update to version 1.11.8 > onboard: initial add 1.3.0 > picocom: Fix build > fwts: upgrade to 17.01.00 release > rsyslog: use atomic builtins to avoid race issue > kodi-17: Fix build for qemuarm and update to latest > kodi-17: Recommend python-ctpes for addons to work > kodi-17: Upgrade 17.0 release > kodi-17: Fix build with musl > kodi-17: Add packageconfig for lcms support > kodi: Fix build with pic on x86_64 > kodi: Add krypton/17.0 recipe Signed-off-by: Patrick Ohly --- meta-openembedded | 2 +- meta-refkit/conf/distro/refkit.conf | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/meta-openembedded b/meta-openembedded index 5ecbf9bab4..044e518954 160000 --- a/meta-openembedded +++ b/meta-openembedded @@ -1 +1 @@ -Subproject commit 5ecbf9bab404af0de7d0f058d1620f40dae0d2d8 +Subproject commit 044e5189549de11b2a94efd29a6009a76162b8f1 diff --git a/meta-refkit/conf/distro/refkit.conf b/meta-refkit/conf/distro/refkit.conf index 96faf46334..43627d50c4 100644 --- a/meta-refkit/conf/distro/refkit.conf +++ b/meta-refkit/conf/distro/refkit.conf @@ -312,3 +312,8 @@ SRCREV_meta_pn-linux-intel = "7e8ec462b6bc68cb0f3cb0064909e035db1c4038" # stage due to lack of RTC backup battery. This causes unnecessary fixes being # made due to filesystem metadata time stamps being in future. APPEND_append = " fsck.mode=skip" + + +# Blacklisted in meta-openembedded because it fails on arm. +# We can override that for now. +PNBLACKLIST[multipath-tools] = "" From a994a514ed71b4e3183559344ed558dcfa5dc6da Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 8 Feb 2017 11:49:26 +0100 Subject: [PATCH 08/20] refkit image: allow adding extra partitions Adding additional lines to REFKIT_EXTRA_PARTITION makes it possible to create additional partitions. Signed-off-by: Patrick Ohly --- meta-refkit/classes/refkit-image.bbclass | 1 + meta-refkit/wic/refkit-directdisk.wks.in | 1 + 2 files changed, 2 insertions(+) diff --git a/meta-refkit/classes/refkit-image.bbclass b/meta-refkit/classes/refkit-image.bbclass index f4140e6551..31e3c8e308 100644 --- a/meta-refkit/classes/refkit-image.bbclass +++ b/meta-refkit/classes/refkit-image.bbclass @@ -279,6 +279,7 @@ IMAGE_CLASSES += "${@ 'image-dsk' if ${REFKIT_USE_DSK_IMAGES} else ''}" WKS_FILE = "refkit-directdisk.wks.in" REFKIT_VFAT_MB ??= "30" REFKIT_IMAGE_SIZE ??= "--fixed-size 3700M" +REFKIT_EXTRA_PARTITION ??= "" WIC_CREATE_EXTRA_ARGS += " -D" # Inherit after setting variables that get evaluated when importing diff --git a/meta-refkit/wic/refkit-directdisk.wks.in b/meta-refkit/wic/refkit-directdisk.wks.in index 7bc7eddb8a..17604928d1 100644 --- a/meta-refkit/wic/refkit-directdisk.wks.in +++ b/meta-refkit/wic/refkit-directdisk.wks.in @@ -7,3 +7,4 @@ bootloader --ptable gpt part --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --fixed-size ${REFKIT_VFAT_MB}M --label primary_uefi --part-type C12A7328-F81F-11D2-BA4B-00A0C93EC93B --align 1024 --use-uuid part --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --fixed-size ${REFKIT_VFAT_MB}M --label secondary_uefi --part-type E3C9E316-0B5C-4DB8-817D-F92DF00215AF --align 1024 --use-uuid part / --source rootfs ${REFKIT_IMAGE_SIZE} --fstype=ext4 --label rootfs --align 1024 --uuid ${REMOVABLE_MEDIA_ROOTFS_PARTUUID_VALUE} +${REFKIT_EXTRA_PARTITION} From e6f2be3bcdda8dee6352b6d49c5fc8e2255d6ce9 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 8 Feb 2017 14:19:01 +0100 Subject: [PATCH 09/20] refkit image: support dm-verity dm-verity is useful as an integrity protection mechanism for read-only partitions. In contrast to whole-disk encryption, it also works for removable media like USB sticks. Accidental (media errors) or intentional (offline attacks) modifications are detected at runtime directly when accessing blocks. This would be useful for the installer image (more reliable flashing), even when not worrying about offline attacks. However, the distro feature is currently disabled by default because it depends on pending patches in meta-oe which enable building the necessary tools also natively. A default signing key is provided as part of this change, but only used when the build is configured to be for development. When building a production image, the developer needs to create and configure his own secret key. OpenSSL is used for signinging and verification. GnuPG was considered. It would make passphrase handling for the private key a bit more flexible, but adds a huge chain of dependencies to the initramfs and thus was ruled out. It also would introduce GPLv3 into the initramfs. Signed-off-by: Patrick Ohly --- meta-refkit/classes/refkit-image.bbclass | 31 +++++ .../distro/include/refkit-development.inc | 7 ++ .../include/refkit-supported-recipes.txt | 1 + meta-refkit/files/dm-verity/private.pem | 54 +++++++++ .../initramfs-framework-refkit-dm-verity.bb | 109 +++++++++++++++++ .../recipes-image/images/refkit-initramfs.bb | 5 + .../images/refkit-installer-image.bb | 10 +- .../lib/wic/plugins/source/dm-verity.py | 111 ++++++++++++++++++ 8 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 meta-refkit/files/dm-verity/private.pem create mode 100644 meta-refkit/recipes-image/images/initramfs-framework-refkit-dm-verity.bb create mode 100644 meta-refkit/scripts/lib/wic/plugins/source/dm-verity.py diff --git a/meta-refkit/classes/refkit-image.bbclass b/meta-refkit/classes/refkit-image.bbclass index 31e3c8e308..393e517e09 100644 --- a/meta-refkit/classes/refkit-image.bbclass +++ b/meta-refkit/classes/refkit-image.bbclass @@ -60,6 +60,7 @@ IMAGE_FEATURES[validitems] += " \ ima \ smack \ swupd \ + dm-verity \ ${REFKIT_IMAGE_PKG_FEATURES} \ " @@ -117,6 +118,36 @@ fix_var_lib_swupd () { } ROOTFS_POSTPROCESS_COMMAND_append = " fix_var_lib_swupd;" +# When using dm-verity, the rootfs has to be read-only. +# An extra partition gets created by wic which holds the +# hash data for the rootfs partition, including a signed +# root hash. +# +# A suitable initramfs (like refkit-initramfs with the dm-verity +# image feature enabled) then validates the signed root +# hash and activates the rootfs. refkit-initramfs checks the +# "dmverity" boot parameter for that. If not present or +# the refkit-initramfs was built without dm-verity support, +# booting proceeds without integrity protection. +# +# TODO: WKS_FILE_DEPENDS currently unused (see https://bugzilla.yoctoproject.org/show_bug.cgi?id=11017), +# one has to set instead: +# DEPENDS_append_pn-wic-tools = " cryptsetup-native openssl-native" +WKS_FILE_DEPENDS_append = " \ + ${@ bb.utils.contains('IMAGE_FEATURES', 'dm-verity', 'cryptsetup-native openssl-native', '', d)} \ +" +REFKIT_DM_VERITY_PARTUUID = "12345678-9abc-def0-0fed-cba987654322" +REFKIT_DM_VERITY_PARTITION () { +part --source dm-verity --uuid ${REFKIT_DM_VERITY_PARTUUID} --label rootfs +} +REFKIT_EXTRA_PARTITION .= "${@ bb.utils.contains('IMAGE_FEATURES', 'dm-verity', d.getVar('REFKIT_DM_VERITY_PARTITION'), '', d) }" +APPEND_append = "${@ bb.utils.contains('IMAGE_FEATURES', 'dm-verity', ' dmverity=PARTUUID=${REFKIT_DM_VERITY_PARTUUID}', '', d) }" +WICVARS_append = "${@ bb.utils.contains('IMAGE_FEATURES', 'dm-verity', ' \ + REFKIT_DMVERITY_PRIVATE_KEY \ + REFKIT_DMVERITY_PASSWORD \ + ', '', d) } \ +" + # Make progress messages from do_swupd_update visible as normal command # line output, instead of just recording it to the logs. Useful # because that task can run for a long time without any output. diff --git a/meta-refkit/conf/distro/include/refkit-development.inc b/meta-refkit/conf/distro/include/refkit-development.inc index f65cf5cbb3..acefae9be3 100644 --- a/meta-refkit/conf/distro/include/refkit-development.inc +++ b/meta-refkit/conf/distro/include/refkit-development.inc @@ -1,6 +1,13 @@ # Use the pre-generated keys for IMA signing. IMA_EVM_KEY_DIR = "${IMA_EVM_BASE}/data/debug-keys" +# Use the pre-generated OpenSSL keys for dm-verity signing with +# well-known password "refkit" passed directly via the command line. +# Other openssl -passin variants (for example, file:) +# also work. +REFKIT_DMVERITY_PRIVATE_KEY = "${META_REFKIT_BASE}/files/dm-verity/private.pem" +REFKIT_DMVERITY_PASSWORD = "pass:refkit" + # Enable local root access. Always use _append, to # avoid getting this change overwritten by a # REFKIT_IMAGE_EXTRA_FEATURES = "foo" in local.conf. diff --git a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt index dbfc215a72..50233d6ac3 100644 --- a/meta-refkit/conf/distro/include/refkit-supported-recipes.txt +++ b/meta-refkit/conf/distro/include/refkit-supported-recipes.txt @@ -125,6 +125,7 @@ iftop@networking-layer ima-evm-utils@integrity init-ifupdown@core initramfs-framework-ima@integrity +initramfs-framework-refkit-dm-verity@refkit initramfs-framework-refkit-luks@refkit initramfs-framework@core initscripts@core diff --git a/meta-refkit/files/dm-verity/private.pem b/meta-refkit/files/dm-verity/private.pem new file mode 100644 index 0000000000..eded30c17a --- /dev/null +++ b/meta-refkit/files/dm-verity/private.pem @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,3934235B9433071CB80CC7F1B0A9F7ED + +q8m/XEyx4dq68zoMYg2momOFcpxOY633L8cUrhZhg7DQ3Kb/32LsRGmMMcV9fKf5 +ZuT+t8gXcMfjYSIkg7ToNI8LWepIaxjMyUGQpch9JskGhpK8O+/g8Q3phTz75Cd/ +ehDMxWng3cQ4uXducM667ZaRYv+p9f5whDXT2NCaV8adP55VtfTpi4f8B8f1hqxC +OaOwuda8XSz0BezI5DsrA0JxhUr+7Y389wZRvOM4ZgbKFPahUBGKAMTJWvdXBGLw +cIQGvI/7FcqDpEFOkr8mnxDnMoXKzRKOzI+UBzOQubNKuw0mHHrW9aZFfcyLWgcd +qn/LJivAfNTXWVC7IiKbcfLmPg/HODAshZIQyeETlKyXrPoJzYb1L/HIuhLZ+CgT +3O19xIlbVSMmsoGGwmbB4GwMzLwrpqUtOEwE9tC1+dsFkaE6qlIVRBOKWM1MSSix +FTmqh3QLrcrRn4sGuGlmVQAWRuhEk0sr1dJ0nRl6Czt3W4U0+eho8AIPGj9Y9FvR +sDUBX2vWOuR8GsjaL7tbGAQUZaXRS8Iv0zr+NXw4rg6thJNt/DEa74e1HvCc9WOG +tJRKdHSkVfiqMsS48RorhuH4hO6qJUsMpdQoRUaIDho23dCm6u+sgn7wpo4WGyLA +GojaGZfwTwMpjnB5ZRUGEVZFdLqO7okW61pc8j45OTL4cImQm37WkPVIvYDrOEZI +8L7//jrU6Xo8xe5v9fJb4O5I4Yn4AEfA3/tlh7JgpWB7JCYvRjkMYG8hN5vbAw6P +98XCvR7uq+sAgFfPXZmnXCWfvTpVgRMDCoVhyfBq/jRCGgfTuLKOGli3VXQhrszU +xnXW+yt+WFBSAqUnReNTrIRfzCWOUtYbjAjP2HNcrrnTw/ruZp/F6A9mCqi+CMeE +6v1pUhmFnsoFRgf9sTDr2qVdC/1I5I2NhTUei1/am61/K4gIEo+QMvH8NDXRX5hd +xUr2AMZtttoq6wRX9HA2GcNHFjgQgBUuMdeJBk4yf1bQ3KHFOnIp3H1HTrS3+/2/ +rebGvnApgrIVgW/2n+0uYW/EOE2IaGxI3TVcY/EABmekar8Mhj2MpgqQDVEV/sOs +rAfwXjgZGfYa+02D79PVTIWvrt++1M02eBv0miTfUnt48QKTWzNi2HxhnH5kpiM5 +I9vgt/LuBCVwGG6V4z8VWuVhz+F5dIYckThtSD7sh/aJm0uqBP7OwFtTVfuNh4qC +NXF6KlvZQ0OH+OvzKDl4mq/jvrxtoz7CUEE1i38gj6tvivBmpnRUR/zPd0T8NVDU +5hIDgzugbqvOECZ3KTRRMH4v3LR1TRSKWL42jGUBdu4v8gZldCCfsuCNLOPYqU/C +9LYGV8d5Lc+HRGv3VBYPd5iEeOGMlQ0AoZ3LYjLVGmsod+s+6hm8fX56r0lr9Xt7 +p7N3610aX54zNtgBlvmOFvb4/TirDKve22miFsIQ3tEMN2fvCCnUsD5mot56bLeD +Txde8EpXyfPqV+Xap9+ERQgOx2BMhV+nzNYDwdCGYpLhOlYa7rkSuMeyTiVq9XxB +bnpK20Fox2WbMwjwZCnfAUKjMhWrrnnJArwjsKuLmwEu+fMljX58V+4k10HRyNeN +FgAc25AUvpVccQZubCtb97AKzGfniF6hEDJMrN5LZ0uEZqzfbsUVOTNfXPD06iM6 +/CsnY5VjKFoGYE7hq7nWyOXQjQSjKNDc7N5NcA74jY12sypNnSuMJRlz9sRbERRV +sXhjImOZMy0EFKTCJ+M/S+p40itDtvDWpotFwEIg5jiSIWGVphTj4sGegeJI54Y4 +ktM5cHE84qqAM0esl4LvWghA81ICrFxwthgpOzQ5Kd7y0iLYMp/Eyt8NuqisHUj0 +pRZfx0D+6aun3lHcFSYYimVrVNFAel4yRd8ekSVw6sSt4FXN0ql2cqb/k7TGUT6h +3jR4mCOaPUGIyf79v/HhWwYarrUH4elhQ2oa26z/21GWuF1c7i76G7sFQPLZZslL +80/lDg2754NBhR4z+aYFb552t1Yqy9qPabJ3mJ6ZSz5CFFC6GdqbgxMI61CnOCRa +SjwebNVNhwaPdG8XVT0IGqS0AZ53/LBOoGhn4OXcHcl1Kh9cgSMKcTCYdWNTMzgn +EjG8GbkUz5QZoiKQqJJUwgkDew4ICUt9zSsow/H/56NKzW8Z6OuV4WnH80Whu2Io +y0QBzXY685IGzOmU1bF29KCZX0rxxnCWSZ4AxIA7rcopFIEqqPYUGcPNjVPZWKzY +FKgAyquqRLlljCh3YEwYuK38kKzqMAhsnBFi7rcu6ND0DhN3sNUlf3oow+akiEvi +6joiCTDUnU9+IuJdPh1+lomzaeb0HAejBGcLu8csNzKzwX6auApEv3brnSeFYGBj +jZlOCl9HF3uhs15qQsQpDa1uTWQeS7sTJUUiX3jG+/fRSB6w/AV8m/fz/VucVWfT +4Tso+X4bqR3p82nClRm2h6AO+3PiOoZph7hPDm16FdE5H+hNLFPSvIFwwKXp/XFe +Sq0YGGSZd1NEURVxriP7qaXgdUbCx5vDqQxOnVOXnwAA7FaIiKT4Y/nwlcf7QRed +iYzIBECHPPGnV+9sf8Fg2rqOc4F2+Bq74BloitaqjaqmcQ3MaU1SL3UuQyzFs0/B +ezN6b3NquEtZe6WIxmMFkpb1uBBPz3PWBRLwi/io4sTIgAkXNtubk++9CK8vTrWp +Tq/WXx9O0r2621IKvPnsKoE27VM9aFxfZHhVACR1EMltEhuQieC576LSLrPm5vZk ++CoAFr93FK2C4QkldGMbxqpN1NcuTBy/mTSgZgglfXifEBIehyPSgcA14K4iCUDz +Ie+aH0TrjXKuN7ROvkMIxpFM7Sns/Gn5ozMLA0d1a3B3RqL91DP4HpgCZ9qT8jtI +eHspmLPMI7BvVDBjWIyXlAwkOYMEH5IpqJFBl7+zHE+0mrhwKUmtlewUflaGuiqX +IxFbT3wbNeknn1f9ROi7GJVdHClGHPZImEYYV+v87ekZ1efa7zUhD7AZG51uTSbK +LzieKWtbmI5XBcB+kcc5sPyUT0hoW4PM/ZHqe1k8cGgdY7U1+WouylzZryXyxlbO +MSdz2O9EaL5Ah3W4yj7RlwQ1rfZoRFX6xXD6d7I+MTc9nytq8SHy7hdzD/wAFI9f +-----END RSA PRIVATE KEY----- diff --git a/meta-refkit/recipes-image/images/initramfs-framework-refkit-dm-verity.bb b/meta-refkit/recipes-image/images/initramfs-framework-refkit-dm-verity.bb new file mode 100644 index 0000000000..76fe2c9b69 --- /dev/null +++ b/meta-refkit/recipes-image/images/initramfs-framework-refkit-dm-verity.bb @@ -0,0 +1,109 @@ +# This recipe creates a module for the initramfs-framework in OE-core +# which opens the partition identified via the "root" +# kernel parameter using the dm-verity hashes stored in +# the partition identified via the "dmverity" kernel +# parameter and changes bootparam_root so +# that the following init code uses the read-only, +# integrity protected mapped partition. + +SUMMARY = "dm-verity module for the modular initramfs system" +LICENSE = "MIT" +DEPENDS = "openssl-native" +LIC_FILES_CHKSUM = "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420" + +require refkit-boot-settings.inc + +refkit_dmverity[shellcheck] = "sh" +refkit_dmverity () { + + dmverity_enabled() { + [ "$bootparam_dmverity" ] + } + + dmverity_run () { + C=0 + delay=${bootparam_rootdelay:-1} + timeout=${bootparam_roottimeout:-5} + + if [ "$(echo "$bootparam_root" | cut -c1-5)" = "UUID=" ]; then + root_uuid=$(echo "$bootparam_root" | cut -c6-) + bootparam_root=/dev/disk/by-uuid/$root_uuid + fi + + if [ "$(echo "$bootparam_root" | cut -c1-9)" = "PARTUUID=" ]; then + root_uuid=$(echo "$bootparam_root" | cut -c10-) + bootparam_root=/dev/disk/by-partuuid/$root_uuid + fi + + if [ "$(echo "$bootparam_dmverity" | cut -c1-5)" = "UUID=" ]; then + root_uuid=$(echo "$bootparam_dmverity" | cut -c6-) + bootparam_dmverity=/dev/disk/by-uuid/$root_uuid + fi + + if [ "$(echo "$bootparam_dmverity" | cut -c1-9)" = "PARTUUID=" ]; then + root_uuid=$(echo "$bootparam_dmverity" | cut -c10-) + bootparam_dmverity=/dev/disk/by-partuuid/$root_uuid + fi + + while true; do + seconds=$( expr "$C" '*' "$delay" ) + # shellcheck disable=SC2035 + if [ "$seconds" -gt "$timeout" ]; then + fatal "root partition $bootparam_root and/or dm-verity hash partition $bootparam_dmverity not found." + fi + + if [ -e "$bootparam_root" ] && [ -e "$bootparam_dmverity" ]; then + signature=$(grep -n -m 1 -e "^signature=" "$bootparam_dmverity") + if [ ! "$signature" ]; then + fatal "no signature found in dm-verity hash partition $bootparam_dmverity" + fi + header_lines="$( expr $(echo "$signature" | sed -e 's/:.*//') - 1 )" + header=$(mktemp) + if ! head "-$header_lines" "$bootparam_dmverity" >"$header"; then + fatal "failed to read header from $bootparam_dmverity" + fi + sigfile=$(mktemp) + echo "$signature" | sed -e 's/^[0-9]*:signature=//' | openssl base64 -d >"$sigfile" + result=$(openssl dgst -sha256 -verify /etc/dm-verity-pubkey.pem -signature "$sigfile" "$header") + if [ "$?" != 0 ] || [ "$result" != "Verified OK" ]; then + fatal "dm-verity header in $bootparam_dmverity did not pass OpenSSL signature verification" + fi + + eval $(grep -e ^roothash= -e ^headersize= "$header") + if ! veritysetup create "${REFKIT_DEVICE_MAPPER_ROOTFS_NAME}" "$bootparam_root" "$bootparam_dmverity" "$roothash" --hash-offset "$headersize"; then + fatal "veritysetup of rootfs $bootparam_root using dm-verity hash partition $bootparam_dmverity failed" + fi + bootparam_root="/dev/mapper/${REFKIT_DEVICE_MAPPER_ROOTFS_NAME}" + return + fi + + debug "Sleeping for $delay second(s) to wait root to settle..." + sleep "$delay" + C=$( expr $C + 1 ) + done + } + +} + +python do_install () { + import os + import subprocess + + os.makedirs(os.path.join(d.getVar('D'), 'init.d')) + with open(os.path.join(d.getVar('D'), 'init.d', '80-dmverity'), 'w') as f: + f.write(d.getVar('refkit_dmverity')) + + privkey = d.getVar('REFKIT_DMVERITY_PRIVATE_KEY') + password = d.getVar('REFKIT_DMVERITY_PASSWORD') + pubkey = os.path.join(d.getVar('D'), 'etc', 'dm-verity-pubkey.pem') + os.makedirs(os.path.dirname(pubkey)) + subprocess.check_output(['openssl', 'rsa', '-in', privkey, '-passin', password, '-pubout', '-out', pubkey], + stderr=subprocess.STDOUT) +} + +FILES_${PN} = "/init.d /etc" +RDEPENDS_${PN} += " \ + initramfs-framework-base \ + cryptsetup \ + openssl \ +" diff --git a/meta-refkit/recipes-image/images/refkit-initramfs.bb b/meta-refkit/recipes-image/images/refkit-initramfs.bb index 6f229b3d01..0a269987b6 100644 --- a/meta-refkit/recipes-image/images/refkit-initramfs.bb +++ b/meta-refkit/recipes-image/images/refkit-initramfs.bb @@ -31,6 +31,7 @@ IMAGE_FEATURES = "" IMAGE_FEATURES[validitems] += " \ ima \ luks \ + dm-verity \ " IMAGE_FEATURES += " \ ${@bb.utils.contains('DISTRO_FEATURES', 'ima', 'ima', '', d)} \ @@ -40,6 +41,10 @@ IMAGE_FEATURES += " \ ${@bb.utils.contains('DISTRO_FEATURES', 'luks', 'luks', '', d)} \ " FEATURE_PACKAGES_luks = "initramfs-framework-refkit-luks" +IMAGE_FEATURES += " \ + ${@bb.utils.contains('DISTRO_FEATURES', 'dm-verity', 'dm-verity', '', d)} \ +" +FEATURE_PACKAGES_dm-verity = "initramfs-framework-refkit-dm-verity" IMAGE_LINGUAS = "" diff --git a/meta-refkit/recipes-image/images/refkit-installer-image.bb b/meta-refkit/recipes-image/images/refkit-installer-image.bb index 1a4b2c7905..aff5cd9559 100644 --- a/meta-refkit/recipes-image/images/refkit-installer-image.bb +++ b/meta-refkit/recipes-image/images/refkit-installer-image.bb @@ -239,7 +239,15 @@ INSTALLER_RDEPENDS_append = " \ inherit image-installer -REFKIT_INSTALLER_IMAGE_EXTRA_FEATURES ?= "${REFKIT_IMAGE_FEATURES_COMMON}" +# When dm-verity support is enabled in the distro, the installer image +# by default uses a read-only partition with dm-verity used for integrity +# protection. This has the useful effect that corrupted data on a USB +# stick gets detected instead of silently writing a broken image to +# internal storage. +REFKIT_INSTALLER_IMAGE_EXTRA_FEATURES ?= " \ + ${@ bb.utils.contains('DISTRO_FEATURES', 'dm-verity', 'read-only-rootfs dm-verity', '', d) } \ + ${REFKIT_IMAGE_FEATURES_COMMON} \ +" REFKIT_INSTALLER_IMAGE_EXTRA_INSTALL ?= "${REFKIT_IMAGE_INSTALL_COMMON}" REFKIT_IMAGE_EXTRA_FEATURES += "${REFKIT_INSTALLER_IMAGE_EXTRA_FEATURES}" REFKIT_IMAGE_EXTRA_INSTALL += "${REFKIT_INSTALLER_IMAGE_EXTRA_INSTALL}" diff --git a/meta-refkit/scripts/lib/wic/plugins/source/dm-verity.py b/meta-refkit/scripts/lib/wic/plugins/source/dm-verity.py new file mode 100644 index 0000000000..9167d10f76 --- /dev/null +++ b/meta-refkit/scripts/lib/wic/plugins/source/dm-verity.py @@ -0,0 +1,111 @@ +# Copyright (c) 2017, Intel Corporation. +# All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# DESCRIPTION +# This source plugin can be used for a partition following sometime after +# the main rootfs in a wic file to generate a partition containing +# dm-verity hash data for the rootfs. +# +# AUTHORS +# Patrick Ohly +# + +import base64 +import glob +import os +import re +import shutil +import tempfile + +from wic import msger +from wic.pluginbase import SourcePlugin +from wic.utils.misc import (exec_cmd, exec_native_cmd, get_bitbake_var) + +class DMVerityPlugin(SourcePlugin): + """ + Creates dm-verity hash data for one rootfs partition, as identified by + the --label parameter. + """ + + name = 'dm-verity' + + @classmethod + def do_prepare_partition(cls, part, source_params, creator, cr_workdir, + oe_builddir, bootimg_dir, kernel_dir, + rootfs_dir, native_sysroot): + """ + Called to do the actual content population for a partition i.e. it + 'prepares' the partition to be incorporated into the image. + In this case, locate the temporary root partition and hash it. + """ + + # We rely on the --label parameter and the naming convention + # in partition.py prepare_rootfs() here to find the already + # prepared rootfs partition image. + pattern = '%s/rootfs_%s.*' % (cr_workdir, part.label) + rootfs = glob.glob(pattern) + if len(rootfs) != 1: + msger.error("%s shell pattern does not match exactly one rootfs image (missing --label parameter?): %s" % (pattern, rootfs)) + else: + rootfs = rootfs[0] + msger.debug("Calculating dm-verity hash for rootfs %s (native %s)." % (rootfs, native_sysroot)) + + hashimg = '%s/dm-verity_%s.img' % (cr_workdir, part.label) + # Reserve some fixed amount of space at the start of the hash image + # for our own data (in particular, the signed root hash). + # The content of that part is: + # roothash=<....> + # + # signature= + header_size = 4096 + ret, out = exec_native_cmd("veritysetup format '%s' '%s' --hash-offset=%d" % + (rootfs, hashimg, header_size), + native_sysroot) + m = re.search(r'^Root hash:\s*(\S+)$', out, re.MULTILINE) + if ret or not m: + msger.error('veritysetup failed: %s' % out) + else: + root_hash = m.group(1) + privkey = get_bitbake_var('REFKIT_DMVERITY_PRIVATE_KEY') + password = get_bitbake_var('REFKIT_DMVERITY_PASSWORD') + tmp = tempfile.mkdtemp(prefix='dm-verity-') + try: + data_filename = os.path.join(tmp, 'data') + header = ('roothash=%s\nheadersize=%d\n' % (root_hash, header_size)).encode('ascii') + with open(data_filename, 'wb') as data: + data.write(header) + # Must use a temporary file, exec_native_cmd() only supports UTF-8 output. + signature = os.path.join(tmp, 'sig') + ret, out = exec_native_cmd("openssl dgst -sha256 -passin '%s' -sign '%s' -out '%s' '%s'" % + (password, privkey, signature, data_filename), + native_sysroot) + if ret: + msger.error('openssl signing failed') + with open(signature, 'rb') as f: + header += b'signature=' + base64.standard_b64encode(f.read()) + b'\n' + if len(header) + 1 >= header_size: + msger.error('reserved space for dm-verity header too small') + with open(hashimg, 'rb+') as hash: + hash.write(header) + finally: + shutil.rmtree(tmp) + + data_bytes = os.stat(rootfs).st_size + hash_bytes = os.stat(hashimg).st_size + msger.debug("dm-verity data partition %d bytes, hash partition %d bytes, ratio %f." % + (data_bytes, hash_bytes, data_bytes / hash_bytes)) + part.size = data_bytes // 1024 + part.source_file = hashimg From 9c9ecd845fe797cda5a921dda83daa681c79a3bd Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Tue, 14 Feb 2017 15:42:16 +0100 Subject: [PATCH 10/20] refkit-ci.inc: enable installer image Enabling the build of the installer image also indirectly triggers building of the images included in it. Signed-off-by: Patrick Ohly --- meta-refkit/conf/distro/include/refkit-ci.inc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meta-refkit/conf/distro/include/refkit-ci.inc b/meta-refkit/conf/distro/include/refkit-ci.inc index d5652f7eed..646b210ee0 100644 --- a/meta-refkit/conf/distro/include/refkit-ci.inc +++ b/meta-refkit/conf/distro/include/refkit-ci.inc @@ -54,8 +54,10 @@ REFKIT_VM_IMAGE_TYPES = "wic.xz wic.zip wic.bmap wic.xz.sha256sum" # which must contain only alphanumeric symbols,'-' and '_'. # Any other symbols would be skipped in parser. # -# Following targets would be used to perform default build task: -REFKIT_CI_BUILD_TARGETS="refkit-image-minimal refkit-image-common refkit-image-computervision refkit-image-gateway" +# Following targets would be used to perform default build task. +# When adding new profile images, add them to refkit-installer-image.bb +# and they will get pulled into the build indirectly. +REFKIT_CI_BUILD_TARGETS="refkit-image-minimal refkit-installer-image" # Following targets would be executed with do_populate_sdk task REFKIT_CI_SDK_TARGETS="" # Following targets would be executed with do_populate_sdk_ext task. From 845edb692ef2251a4d0050cb040b553833c96a8f Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 15 Feb 2017 12:58:47 +0100 Subject: [PATCH 11/20] meta-security: add forked repository with swtpm patches The swtpm and trousers fixes are staged upstream, but not in master yet. In the meantime this submodule pulls from a fork based on the current master with those pending patches added. Signed-off-by: Patrick Ohly --- .gitmodules | 4 ++++ meta-refkit/conf/bblayers.conf.sample | 7 ++++--- meta-refkit/conf/layer.conf | 2 +- meta-security | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) create mode 160000 meta-security diff --git a/.gitmodules b/.gitmodules index 51292c526a..ad78228539 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,7 @@ [submodule "meta-intel-realsense"] path = meta-intel-realsense url = https://github.com/IntelRealSense/meta-intel-realsense.git +[submodule "meta-security"] + path = meta-security + url = https://github.com/pohly/meta-security.git + branch = swtpm diff --git a/meta-refkit/conf/bblayers.conf.sample b/meta-refkit/conf/bblayers.conf.sample index e4c01a1c98..8b4bf6317e 100644 --- a/meta-refkit/conf/bblayers.conf.sample +++ b/meta-refkit/conf/bblayers.conf.sample @@ -1,6 +1,6 @@ # LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf # changes incompatibly -LCONF_VERSION = "1" +LCONF_VERSION = "2" BBPATH = "${TOPDIR}" BBFILES ?= "" @@ -13,19 +13,20 @@ REFKIT_LAYERS = " \ ##OEROOT##/../meta-oic \ ##OEROOT##/../meta-iot-web \ ##OEROOT##/../meta-iotqa \ + ##OEROOT##/../meta-security \ ##OEROOT##/../meta-security-isafw \ ##OEROOT##/../meta-intel-realsense \ " # REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-efl" -# REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-filesystems" +REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-filesystems" REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-gnome" # REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-gpe" # REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-initramfs" # REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-multimedia" REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-networking" REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-oe" -# REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-perl" +REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-perl" REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-python" # REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-ruby" # REFKIT_LAYERS += "##OEROOT##/../meta-openembedded/meta-systemd" diff --git a/meta-refkit/conf/layer.conf b/meta-refkit/conf/layer.conf index 1f092d70a5..0563ce1f92 100644 --- a/meta-refkit/conf/layer.conf +++ b/meta-refkit/conf/layer.conf @@ -39,7 +39,7 @@ REFKIT_LOCALCONF_VERSION = "2" LOCALCONF_VERSION = "${REFKIT_LOCALCONF_VERSION}" # Same for LCONF_VERSION in bblayer.conf.sample. -REFKIT_LAYER_CONF_VERSION = "1" +REFKIT_LAYER_CONF_VERSION = "2" LAYER_CONF_VERSION = "${REFKIT_LAYER_CONF_VERSION}" # The default error messages use shell meta* wildcards to find the diff --git a/meta-security b/meta-security new file mode 160000 index 0000000000..28a45876c1 --- /dev/null +++ b/meta-security @@ -0,0 +1 @@ +Subproject commit 28a45876c170ac7913df2b4ad78577bbc0b0790f From b9e6c3d31fbf33d7176a22b1612e15c6fb4f6f19 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 15 Feb 2017 16:07:32 +0100 Subject: [PATCH 12/20] refkit-image.bbclass: bump size of VFAT partition 30MB are not enough when enabling dm-verity support and thus including cryptsetup and openssl in the initramfs. The size increase could be made conditional, but that seems overly complex. Signed-off-by: Patrick Ohly --- meta-refkit/classes/refkit-image.bbclass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-refkit/classes/refkit-image.bbclass b/meta-refkit/classes/refkit-image.bbclass index 393e517e09..6a43687f4e 100644 --- a/meta-refkit/classes/refkit-image.bbclass +++ b/meta-refkit/classes/refkit-image.bbclass @@ -308,7 +308,7 @@ IMAGE_CLASSES += "${@ 'image-dsk' if ${REFKIT_USE_DSK_IMAGES} else ''}" # By default, the full image is going to use roughly 4GB, independent # of the actual roofs size. WKS_FILE = "refkit-directdisk.wks.in" -REFKIT_VFAT_MB ??= "30" +REFKIT_VFAT_MB ??= "40" REFKIT_IMAGE_SIZE ??= "--fixed-size 3700M" REFKIT_EXTRA_PARTITION ??= "" WIC_CREATE_EXTRA_ARGS += " -D" From 1ca15a7922ed45bc23b39bb9d3ec7f4712c217b9 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 15 Feb 2017 16:10:54 +0100 Subject: [PATCH 13/20] refkit.conf: enable dm-verity and luks The result is that the installer image will be read-only with dm-verity as integrity protection. The main practical advantage is that bit flips on low-quality USB sticks will be detected reliably. Whole disk-encryption with LUKS is enabled for normal images, but not enforced and thus does not really protect against offline attacks. It gets activated for images installed by the installer image. Signed-off-by: Patrick Ohly --- meta-refkit/conf/distro/refkit.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/meta-refkit/conf/distro/refkit.conf b/meta-refkit/conf/distro/refkit.conf index 43627d50c4..a20cb55992 100644 --- a/meta-refkit/conf/distro/refkit.conf +++ b/meta-refkit/conf/distro/refkit.conf @@ -65,10 +65,13 @@ MAINTAINER = "Mikko Ylinen " TARGET_VENDOR = "-refkit" -REFKIT_DEFAULT_DISTRO_FEATURES = "systemd bluez5 pam" +REFKIT_DEFAULT_DISTRO_FEATURES = "systemd bluez5 pam luks tpm1.2 dm-verity" REFKIT_DEFAULT_EXTRA_RDEPENDS ??= "" REFKIT_DEFAULT_EXTRA_RRECOMMENDS ??= "" +# A temporary workaround for https://bugzilla.yoctoproject.org/show_bug.cgi?id=11017 +DEPENDS_append_pn-wic-tools = "${@ bb.utils.contains('DISTRO_FEATURES', 'dm-verity', ' cryptsetup-native openssl-native', '', d) }" + # Smack security is a distribution feature which can be enabled or # disabled as needed. To simplify recipes, there is also a smack # override. From e6c24851aed1b66b7c327e6b4f64394462101d98 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Thu, 16 Feb 2017 15:46:59 +0100 Subject: [PATCH 14/20] openembedded-core: use branch with UEFI support Patches pending upstream, but not merged yet. Enables "bitbake ovmf". Signed-off-by: Patrick Ohly --- .gitmodules | 2 +- meta-refkit/conf/layer.conf | 4 ++++ openembedded-core | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index ad78228539..6509f87c71 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "openembedded-core"] path = openembedded-core - url = http://git.openembedded.org/openembedded-core + url = https://github.com/pohly/openembedded-core.git branch = master [submodule "bitbake"] path = bitbake diff --git a/meta-refkit/conf/layer.conf b/meta-refkit/conf/layer.conf index 0563ce1f92..00f86b8e91 100644 --- a/meta-refkit/conf/layer.conf +++ b/meta-refkit/conf/layer.conf @@ -48,3 +48,7 @@ LAYER_CONF_VERSION = "${REFKIT_LAYER_CONF_VERSION}" # user confusion. SANITY_LOCALCONF_SAMPLE = "${META_REFKIT_BASE}/conf/local.conf.sample" SANITY_BBLAYERCONF_SAMPLE = "${META_REFKIT_BASE}/conf/bblayers.conf.sample" + +# acpica from meta-oe no longer works, we need to use the one from +# OE-core. Temporary workaround until meta-oe/acpica is gone. +BBMASK .= "meta-openembedded/meta-oe/recipes-extended/acpica/" diff --git a/openembedded-core b/openembedded-core index d1109378d7..59772e0cfe 160000 --- a/openembedded-core +++ b/openembedded-core @@ -1 +1 @@ -Subproject commit d1109378d730c5cf50240c4d1a468e3aef5208ea +Subproject commit 59772e0cfe15561693978e34b883f9a816de4c77 From ea3c21094081d2aabf50625ab695c18276b0f418 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Thu, 16 Feb 2017 15:49:38 +0100 Subject: [PATCH 15/20] refkit.conf: enable dm-verity and TPM in kernel --- meta-refkit/conf/distro/refkit.conf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/meta-refkit/conf/distro/refkit.conf b/meta-refkit/conf/distro/refkit.conf index a20cb55992..e551e8591d 100644 --- a/meta-refkit/conf/distro/refkit.conf +++ b/meta-refkit/conf/distro/refkit.conf @@ -69,6 +69,14 @@ REFKIT_DEFAULT_DISTRO_FEATURES = "systemd bluez5 pam luks tpm1.2 dm-verity" REFKIT_DEFAULT_EXTRA_RDEPENDS ??= "" REFKIT_DEFAULT_EXTRA_RRECOMMENDS ??= "" +# Depening on the distro features we need certain kernel features. The assumption +# here is that all kernels we use support KERNEL_FEATURES *and* have these +# features. +KERNEL_FEATURES_append = " \ + ${@ bb.utils.contains('DISTRO_FEATURES', 'dm-verity', ' features/device-mapper/dm-verity.scc', '', d) } \ + ${@ bb.utils.contains('DISTRO_FEATURES', 'tpm1.2', ' features/tpm/tpm.scc', '', d) } \ +" + # A temporary workaround for https://bugzilla.yoctoproject.org/show_bug.cgi?id=11017 DEPENDS_append_pn-wic-tools = "${@ bb.utils.contains('DISTRO_FEATURES', 'dm-verity', ' cryptsetup-native openssl-native', '', d) }" From 221b95857bf226f6383b2133924cd06d96781db3 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Thu, 16 Feb 2017 20:50:40 +0100 Subject: [PATCH 16/20] refkit-image.bbclass: temporarily disable ttyS0 removal The removal of ttyS0 from APPEND is annoying when running under qemu, because there's no output on the serial console. Signed-off-by: Patrick Ohly --- meta-refkit/classes/refkit-image.bbclass | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meta-refkit/classes/refkit-image.bbclass b/meta-refkit/classes/refkit-image.bbclass index 6a43687f4e..afa2ddd2dd 100644 --- a/meta-refkit/classes/refkit-image.bbclass +++ b/meta-refkit/classes/refkit-image.bbclass @@ -381,7 +381,9 @@ ima_evm_sign_rootfs_prepend () { APPEND_append = "${@bb.utils.contains('IMAGE_FEATURES', 'smack', '', ' security=none', d)}" # Use what RMC gives, not the defaults in meta-intel machine configs -APPEND_remove_intel-corei7-64 = "console=ttyS0,115200" +# TODO: only use this once qemu also gets a serial console in RMC. +# See https://bugzilla.yoctoproject.org/show_bug.cgi?id=11061 +# APPEND_remove_intel-corei7-64 = "console=ttyS0,115200" # In addition, when Smack is disabled in the image but enabled in the # distro, we strip all Smack xattrs from the rootfs. Otherwise we still From 12d987b94146525c5bd4cfb0e379caa9084ea54a Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Thu, 16 Feb 2017 20:58:04 +0100 Subject: [PATCH 17/20] refkit.conf: enable tcsd tcsd is not started automatically, but we need that for the installer script. Signed-off-by: Patrick Ohly --- meta-refkit/conf/distro/refkit.conf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/meta-refkit/conf/distro/refkit.conf b/meta-refkit/conf/distro/refkit.conf index e551e8591d..8e8c333f4a 100644 --- a/meta-refkit/conf/distro/refkit.conf +++ b/meta-refkit/conf/distro/refkit.conf @@ -77,6 +77,14 @@ KERNEL_FEATURES_append = " \ ${@ bb.utils.contains('DISTRO_FEATURES', 'tpm1.2', ' features/tpm/tpm.scc', '', d) } \ " +# The upstream recipe does not start tcsd automatically, but we +# want that because the installer image calls the TPM tools +# without starting tcsd first (it shouldn't have to!), and +# without tcsd already running, the tools just fail. A better +# solution would be socket-activation, but tcsd does not support +# that. Does not matter, tcsd is only installed when needed. +SYSTEMD_AUTO_ENABLE_forcevariable_pn-trousers = "enable" + # A temporary workaround for https://bugzilla.yoctoproject.org/show_bug.cgi?id=11017 DEPENDS_append_pn-wic-tools = "${@ bb.utils.contains('DISTRO_FEATURES', 'dm-verity', ' cryptsetup-native openssl-native', '', d) }" From 15943fc1cf477406eb6f959637ddb45b77215e31 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Fri, 17 Feb 2017 17:16:02 +0100 Subject: [PATCH 18/20] efi-combo-trigger: fix dependency issue Recipe specific sysroots revealed the missing glib-2.0 dependency. Signed-off-by: Patrick Ohly --- meta-refkit/recipes-swupd/efi-combo-trigger/efi-combo-trigger.bb | 1 + 1 file changed, 1 insertion(+) diff --git a/meta-refkit/recipes-swupd/efi-combo-trigger/efi-combo-trigger.bb b/meta-refkit/recipes-swupd/efi-combo-trigger/efi-combo-trigger.bb index a5fc57c694..4389f8ee48 100644 --- a/meta-refkit/recipes-swupd/efi-combo-trigger/efi-combo-trigger.bb +++ b/meta-refkit/recipes-swupd/efi-combo-trigger/efi-combo-trigger.bb @@ -1,6 +1,7 @@ DESCRIPTION = "swupd plugin for updating the kernel+initramfs combo in the EFI system partition" PV = "1.0" LICENSE = "MIT" +DEPENDS = "glib-2.0" SRC_URI = " \ file://efi_combo_updater.c \ From 9ebbf196843b097a0e98b6bded3930001de6686c Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Fri, 17 Feb 2017 17:20:01 +0100 Subject: [PATCH 19/20] refkit.conf: temporary workaround for libarchive-native dependency Patch sent to OE-core ("[PATCH] libarchive: fix bzip2 dependency for native build"). Signed-off-by: Patrick Ohly --- meta-refkit/conf/distro/refkit.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/meta-refkit/conf/distro/refkit.conf b/meta-refkit/conf/distro/refkit.conf index 8e8c333f4a..383a1e5a08 100644 --- a/meta-refkit/conf/distro/refkit.conf +++ b/meta-refkit/conf/distro/refkit.conf @@ -41,6 +41,10 @@ INHERIT += "refkit-update-alternatives" # stateless. PACKAGECONFIG_remove_pn-swupd-client = "stateless" +# temporary workaround: +# fix DEPENDS while patch is pending for OE-core +DEPENDS_append_pn-libarchive-native = " bzip2-replacement-native" + # TODO: # 1. https://github.com/ostroproject/ostro-os-xt/tree/master/meta-ostro-xt/recipes-swupd/swupd-client/swupd-client # has a more recent, shell-based version of the EFI combo updater. From 9c86bdd83f694e474ebf3545770c633acaae8b07 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Fri, 17 Feb 2017 17:24:32 +0100 Subject: [PATCH 20/20] image-installer: document the ELC demo setup This corresponds to the demo described in "Surviving in the Wilderness: Integrity Protection and System Update" https://openiotelcna2017.sched.com/event/9J5i Signed-off-by: Patrick Ohly --- doc/howtos/image-installer.rst | 70 +++++++++++++++++++ doc/howtos/image-installer/init-tpm | 6 ++ doc/howtos/image-installer/run-swtpm | 10 +++ doc/howtos/image-installer/runqemu-install | 6 ++ .../image-installer/runqemu-internal-disk | 4 ++ 5 files changed, 96 insertions(+) create mode 100644 doc/howtos/image-installer.rst create mode 100755 doc/howtos/image-installer/init-tpm create mode 100755 doc/howtos/image-installer/run-swtpm create mode 100755 doc/howtos/image-installer/runqemu-install create mode 100755 doc/howtos/image-installer/runqemu-internal-disk diff --git a/doc/howtos/image-installer.rst b/doc/howtos/image-installer.rst new file mode 100644 index 0000000000..b2d46456d1 --- /dev/null +++ b/doc/howtos/image-installer.rst @@ -0,0 +1,70 @@ +Building with swupd enabled +=========================== + +* git clone --recursive --branch installer-image https://github.com/pohly/intel-iot-refkit.git +* git clone https://git.yoctoproject.org/git/meta-swupd +* cd intel-iot-refkit +* . refkit-init-build-env +* bitbake-layers add-layer `pwd`/../../meta-swupd +* Add to local.conf: + + require conf/distro/include/refkit-development.inc + + REFKIT_IMAGE_COMMON_EXTRA_FEATURES_append = " swupd" + OS_VERSION = "1000" + SWUPD_VERSION_URL = "" + SWUPD_CONTENT_URL = "" + +* bitbake ovmf refkit-installer-image refkit-image-common swtpm-wrappers +* meta-swupd/scripts/swupd-http-server & +* Edit local.conf to build an incremental update: + + OS_VERSION = "1010" + SWUPD_VERSION_URL = "http://localhost:8000" + SWUPD_CONTENT_URL = "http://localhost:8000" + +* bitbake refkit-image-common # Do not rebuild the refkit-installer-image! + +Installing, rebooting, updating +=============================== + +Precondition: user must be able to run commands as root with sudo. + +* cd intel-iot-refkit +* . refkit-init-build-env +* export PATH=../doc/howtos/image-installer:$PATH +* meta-swupd/scripts/swupd-http-server & +* init-tpm # initializes content of a virtual TPM +* run-swtpm # run software TPM in background as root, creates /dev/vtpm0 (must be repeated after each runqemu run!) +* runqemu-install +* Once booting has finished: + * lsblk # dm-verity is active, shown twice and size is a bit odd (hash partition /dev/vda4 should be smaller) + * mount # rootfs is ro + * image-installer + * select refkit-image-common (swupd enabled!), confirm vdb, yes + * reboot +* run-swtpm +* cp tmp-glibc/deploy/images/intel-corei7-64/my-installed-image-intel-corei7-64.wic tmp-glibc/deploy/images/intel-corei7-64/my-installed-image-intel-corei7-64.wic.1000 # can be copied back to repeat the following steps without starting at the top +* runqemu-internal-disk +* Once booted: + * cat /etc/os-release + * lsblk # LUKS crypt active + * mount # rootfs is rw + * connmanctl services + * connmanctl config ethernet_525400123402_cable --ipv4 manual 192.168.7.2 # must match tap0 on host + * swupd verify --url http://192.168.7.1:8000 + * cryptsetup # command not available + * swupd update --url http://192.168.7.1:8000 # fast, incremental update + * cryptsetup status rootfs + * cat /etc/os-release + +Troubleshooting +=============== + +bitbake do_fetch_swupd_inputs fails: SWUPD_VERSION_URL and +SWUPD_CONTENT_URL must be empty for the first build, and non-empty in the +second build. meta-swupd/scripts/swupd-http-server must be running +during the second build. + +qemu can't open /dev/vtpm0: run-swtpm. Must be done after each runqemu invocation +because swtpm shuts down after use. diff --git a/doc/howtos/image-installer/init-tpm b/doc/howtos/image-installer/init-tpm new file mode 100755 index 0000000000..d0d7a3eb59 --- /dev/null +++ b/doc/howtos/image-installer/init-tpm @@ -0,0 +1,6 @@ +#!/bin/sh -ex + +IMAGE_DIR=tmp-glibc/deploy/images/intel-corei7-64 +rm -rf $IMAGE_DIR/my-tpm +mkdir $IMAGE_DIR/my-tpm +tmp-glibc/work/*/swtpm-wrappers/1.0-r0/swtpm_setup_oe.sh --tpm-state $IMAGE_DIR/my-tpm --createek diff --git a/doc/howtos/image-installer/run-swtpm b/doc/howtos/image-installer/run-swtpm new file mode 100755 index 0000000000..cba24884bf --- /dev/null +++ b/doc/howtos/image-installer/run-swtpm @@ -0,0 +1,10 @@ +#!/bin/sh -ex + +IMAGE_DIR=tmp-glibc/deploy/images/intel-corei7-64 +# LOGFILE=tmp-glibc/log/swtpm_cuse.log +# rm -f $LOGFILE +# touch $LOGFILE +# Beware, need absolute paths! +# --log file=$(realpath $LOGFILE),level=20 +sudo tmp-glibc/work/*/swtpm-wrappers/1.0-r0/swtpm_cuse_oe.sh -n vtpm0 --tpmstate dir=$(realpath $IMAGE_DIR/my-tpm) +sudo chown $(id -u) /dev/vtpm0 diff --git a/doc/howtos/image-installer/runqemu-install b/doc/howtos/image-installer/runqemu-install new file mode 100755 index 0000000000..24267c3e53 --- /dev/null +++ b/doc/howtos/image-installer/runqemu-install @@ -0,0 +1,6 @@ +#!/bin/sh -ex + +IMAGE_DIR=tmp-glibc/deploy/images/intel-corei7-64 +truncate -s 4G $IMAGE_DIR/my-installed-image-intel-corei7-64.wic +cp $IMAGE_DIR/refkit-installer-image-intel-corei7-64.qemuboot.conf $IMAGE_DIR/my-installed-image-intel-corei7-64.qemuboot.conf +runqemu serial nographic refkit-installer-image wic intel-corei7-64 "qemuparams=-drive if=virtio,file=$IMAGE_DIR/my-installed-image-intel-corei7-64.wic,format=raw -tpmdev cuse-tpm,id=tpm0,path=/dev/vtpm0 -device tpm-tis,tpmdev=tpm0" ovmf diff --git a/doc/howtos/image-installer/runqemu-internal-disk b/doc/howtos/image-installer/runqemu-internal-disk new file mode 100755 index 0000000000..81d4b9efb7 --- /dev/null +++ b/doc/howtos/image-installer/runqemu-internal-disk @@ -0,0 +1,4 @@ +#!/bin/sh -ex + +IMAGE_DIR=tmp-glibc/deploy/images/intel-corei7-64 +runqemu serial nographic my-installed-image wic intel-corei7-64 "qemuparams=-tpmdev cuse-tpm,id=tpm0,path=/dev/vtpm0 -device tpm-tis,tpmdev=tpm0" ovmf