Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:15.5:Update
s390-tools.17667
s390-tools-sles15sp2-48-genprotimg-introduce-ne...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File s390-tools-sles15sp2-48-genprotimg-introduce-new-tool-for-the-creation-of-PV.patch of Package s390-tools.17667
Subject: [PATCH] [FEAT VS1804] genprotimg: introduce new tool for the creation of PV images From: Marc Hartmayer <mhartmay@linux.ibm.com> Summary: genprotimg: Introduce new tool for the creation of PV images Description: genprotimg takes a kernel, host-key documents, optionally an initrd, optionally a file with the kernel command line, and it generates a single, loadable image file. The image consists of a concatenation of a plain text boot loader, the encrypted components for kernel, initrd, and cmdline, and the integrity-protected PV header, containing metadata necessary for running the guest in PV mode. It's possible to use this image file as a kernel for zIPL or for a direct kernel boot using QEMU. Upstream-ID: 65b9fc442c1a4ff24583171e714e5fdb1e92c8fd Problem-ID: VS1804 Upstream-Description: genprotimg: introduce new tool for the creation of PV images Protected VMs (PVM) are KVM VMs, where KVM can't access the VM's state like guest memory and guest registers anymore. Instead the PVMs are mostly managed by a new entity called Ultravisor (UV), which provides an API, so KVM and the PV can request management actions. PVMs are encrypted at rest and protected from hypervisor access while running. They switch from a normal operation into protected mode, so we can still use the standard boot process to load an encrypted image and then move it into protected mode. This commit adds the tool 'genprotimg'. It takes a kernel, key files, optionally an initrd, optionally a file with the kernel command line, and it generates a single, loadable image file. The image consists of a concatenation of a plain text boot loader, the encrypted components for kernel, initrd, and cmdline, and the integrity-protected PV header, containing metadata necessary for running the guest in PV mode. It's possible to use this image file as a kernel for zipl or for a direct kernel boot using QEMU. Reviewed-by: Bjoern Walk <bwalk@linux.ibm.com> Acked-by: Patrick Steuer <patrick.steuer@de.ibm.com> Reviewed-by: Claudio Imbrenda <imbrenda@linux.ibm.com> Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com> --- Makefile | 4 README.md | 6 genprotimg/.gitignore | 5 genprotimg/Makefile | 26 + genprotimg/README.md | 101 ++++ genprotimg/man/Makefile | 12 genprotimg/man/genprotimg.8 | 97 +++ genprotimg/src/Makefile | 101 ++++ genprotimg/src/common.h | 39 + genprotimg/src/genprotimg.c | 181 +++++++ genprotimg/src/include/pv_crypto_def.h | 25 + genprotimg/src/include/pv_hdr_def.h | 84 +++ genprotimg/src/pv/pv_args.c | 405 ++++++++++++++++ genprotimg/src/pv/pv_args.h | 53 ++ genprotimg/src/pv/pv_comp.c | 446 +++++++++++++++++ genprotimg/src/pv/pv_comp.h | 78 +++ genprotimg/src/pv/pv_comps.c | 252 ++++++++++ genprotimg/src/pv/pv_comps.h | 42 + genprotimg/src/pv/pv_error.c | 37 + genprotimg/src/pv/pv_error.h | 62 ++ genprotimg/src/pv/pv_hdr.c | 293 +++++++++++ genprotimg/src/pv/pv_hdr.h | 36 + genprotimg/src/pv/pv_image.c | 820 +++++++++++++++++++++++++++++++++ genprotimg/src/pv/pv_image.h | 68 ++ genprotimg/src/pv/pv_ipib.c | 128 +++++ genprotimg/src/pv/pv_ipib.h | 27 + genprotimg/src/pv/pv_opt_item.c | 26 + genprotimg/src/pv/pv_opt_item.h | 20 genprotimg/src/pv/pv_stage3.c | 164 ++++++ genprotimg/src/pv/pv_stage3.h | 30 + genprotimg/src/utils/align.h | 24 genprotimg/src/utils/buffer.c | 69 ++ genprotimg/src/utils/buffer.h | 31 + genprotimg/src/utils/crypto.c | 798 ++++++++++++++++++++++++++++++++ genprotimg/src/utils/crypto.h | 104 ++++ genprotimg/src/utils/file_utils.c | 234 +++++++++ genprotimg/src/utils/file_utils.h | 34 + include/boot/ipl.h | 5 38 files changed, 4965 insertions(+), 2 deletions(-) --- a/Makefile +++ b/Makefile @@ -8,7 +8,9 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt das tape390 osasnmpd qetharp ip_watcher qethconf scripts zconf \ vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \ ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \ - systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl + systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \ + genprotimg + SUB_DIRS = $(LIB_DIRS) $(TOOL_DIRS) all: $(TOOL_DIRS) --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ Package contents * dasdinfo: Display unique DASD ID, either UID or volser. + * genprotimg: + Create a protected virtualization image. + * udev rules: - 59-dasd.rules: rules for unique DASD device nodes created in /dev/disk/. - 57-osasnmpd.rules: udev rules for osasnmpd. @@ -264,9 +267,10 @@ build options: | pfm | `HAVE_PFM` | cpacfstats | | net-snmp | `HAVE_SNMP` | osasnmpd | | glibc-static | `HAVE_LIBC_STATIC` | zfcpdump | -| openssl | `HAVE_OPENSSL` | zkey | +| openssl | `HAVE_OPENSSL` | genprotimg,zkey | | cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup | | json-c | `HAVE_JSONC` | zkey-cryptsetup | +| glib2 | `HAVE_GLIB2` | genprotimg | This table lists additional build or install options: --- /dev/null +++ b/genprotimg/.gitignore @@ -0,0 +1,5 @@ +tags +compile_commands.json +src/.check-dep-genprotimg +src/.detect-openssl.dep.c +src/genprotimg --- /dev/null +++ b/genprotimg/Makefile @@ -0,0 +1,26 @@ +# Common definitions +include ../common.mak + +.DEFAULT_GOAL := all + +PKGDATADIR := "$(DESTDIR)$(TOOLS_DATADIR)/genprotimg" +TESTS := +SUBDIRS := boot src man +RECURSIVE_TARGETS := all-recursive install-recursive clean-recursive + +all: all-recursive + +install: all install-recursive + $(INSTALL) -d -m 755 "$(PKGDATADIR)" + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 boot/stage3a.bin "$(PKGDATADIR)" + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 boot/stage3b_reloc.bin "$(PKGDATADIR)" + +clean: clean-recursive + +$(RECURSIVE_TARGETS): + @target=`echo $@ |sed s/-recursive//`; \ + for d in $(SUBDIRS); do \ + $(MAKE) -C $$d $$target; \ + done + +.PHONY: all install clean $(RECURSIVE_TARGETS) --- /dev/null +++ b/genprotimg/README.md @@ -0,0 +1,101 @@ +# genprotimg + +`genprotimg` takes a kernel, key files, optionally an initrd image, +optionally a file containing the kernel command line parameters, and +generates a single, bootable image file. The generated image file +consists of a concatenation of a plain text boot loader, the encrypted +components for kernel, initrd, kernel command line, and the +integrity-protected PV header, containing the metadata necessary for +running the guest in protected mode. See [Memory Layout](#memory-layout) +for details about the internal structure of the created image. + +It is possible to use the generated image as a kernel for zipl or for +a direct kernel boot using QEMU. + +## Getting started + +If all dependencies are met a simple `make` call in the source tree +should be enough for building `genprotimg`. + +## Details + +The main idea of `genprotimg` is: + +1. read in all keys, IVs, and other information needed for the + encryption of the components and the generation of the PV header +2. add stub stage3a (so we can calculate the memory addresses) +3. add components: prepare the components (alignment and encryption) + and add them to the memory layout +4. build and add stage3b: generate the stage3b and add it to the memory layout +5. generate the PV header: generate the hashes (pld, ald, and tld) of + the components and create the PV header and IPIB +6. parameterize the stub stage3a: uses the IPIB and PV header +7. write the final image to the specified output path + +### Boot Loader + +The boot loader consists of two parts: + +1. stage3a boot loader (cleartext), this loader is responsible for the + transition into the protected mode by doing diag308 subcode 8 and + 10 calls. +2. stage3b boot loader (encrypted), this loader is very similar to the + normal zipl stage3 boot loader. It will be loaded by the Ultravisor + after the successful transition into protected mode. Like the zipl + stage3 boot loader it moves the kernel and patches in the values + for initrd and parmline. + +The loaders have the following constraints: + +1. It must be possible to place stage3a and stage3b at a location + greater than 0x10000 because the zipl stage3 loader zeroes out + everything at addresses lower than 0x10000 of the image. +2. As the stage3 loader of zipl assumes that the passed kernel image + looks like a normal kernel image, the zipl stage3 loader modifies the + content at the memory area 0x10400 - 0x10800, therefore we leave this + area unused in our stage3a loader. +3. The default entry address used by the zipl stage3 loader is 0x10000 + so we add a simple branch to 0x11000 at 0x10000 so the zipl stage3 + loader can modify the area 0x10400 - 0x10800 without affecting the + stage3a loader. + +#### Detail about stage3b + +The stage3b.bin is linked at address 0x9000, therefore it will not +work at another address. The relocation support for the stage3b +loader, so that it can be placed at addresses != 0x9000, is added in +the loader with the name stage3b_reloc.bin. By default, if we're +talking about stage3b we refer to stage3b_reloc.bin. + +### Memory Layout + +The memory layout of the bootable file looks like: + ++-----------------------+-----------+------------------------+ +|Start |End |Use | ++=======================+===========+========================+ +|0 |0x7 |Short PSW, starting | +| | |instruction at 0x11000 | ++-----------------------+-----------+------------------------+ +|0x10000 |0x10012 |Branch to 0x11000 | ++-----------------------+-----------+------------------------+ +|0x10013 |0x10fff |Left intentionally | +| | |unused | ++-----------------------+-----------+------------------------+ +|0x11000 |0x12fff |Stage3a | ++-----------------------+-----------+------------------------+ +|0x13000 |0x13fff |IPIB used as argument | +| | |for the diag308 call | ++-----------------------+-----------+------------------------+ +|0x14000 |0x1[45]fff |UV header used for the | +| | |diag308 call (size can | +| | |be either 1 or 2 pages) | ++-----------------------+-----------+------------------------+ +|NEXT_PAGE_ALIGNED_ADDR | |Encrypted Kernel | ++-----------------------+-----------+------------------------+ +|NEXT_PAGE_ALIGNED_ADDR | |Encrypted Cmdline | ++-----------------------+-----------+------------------------+ +|NEXT_PAGE_ALIGNED_ADDR | |Encrypted Initrd | ++-----------------------+-----------+------------------------+ +|NEXT_PAGE_ALIGNED_ADDR | |Encrypted Stage3b_reloc | ++-----------------------+-----------+------------------------+ --- /dev/null +++ b/genprotimg/man/Makefile @@ -0,0 +1,12 @@ +# Common definitions +include ../../common.mak + +all: + +install: + $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 + $(INSTALL) -m 644 -c genprotimg.8 $(DESTDIR)$(MANDIR)/man8 + +clean: + +.PHONY: all install clean --- /dev/null +++ b/genprotimg/man/genprotimg.8 @@ -0,0 +1,97 @@ +.\" Copyright 2020 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" +.TH GENPROTIMG 8 "March 2020" "s390-tools" +.SH NAME +genprotimg \- Create a protected virtualization image + +.SH SYNOPSIS +.SY +.B genprotimg +\fB\-k\fR \fIHOST_KEY_DOCUMENT\fR... +\fB\-i\fR \fIVMLINUZ\fR +[\fB\-r\fR \fIRAMDISK\fR] +[\fB\-p\fR \fIPARMFILE\fR] +\fB\-o\fR \fIOUTFILE\fR +[\fIOPTION\fR]... +.YS + +.SH DESCRIPTION +.PP +Use \fBgenprotimg\fR to generate a single bootable image file with +encrypted and integrity-protected parts. The command requires a kernel +image, a host-key document, and an output file name. Optionally, +specify an initial RAM filesystem, and a file containing the kernel +parameters. Should special circumstances require it, you can +optionally specify your own keys for the encryption by using the +experimental options. In the resulting image file, a plain text boot +loader, the encrypted components for kernel, initial RAM disk, kernel +parameters, and the encrypted and integrity-protected header are +concatenated. The header contains metadata necessary for running the +guest in protected mode. +.PP +Use this image file as a kernel image for zipl or for a direct kernel +boot using QEMU. + +.SH OPTIONS +.TP +\fB\-h\fR, \fB\-\-help\fR +Prints usage information, then exits. +.TP +\fB\-\-help-experimental\fR +Prints experimental usage information, then exits. +.TP +\fB\-\-help-all\fR +Prints all usage information, then exits. +.TP +\fB\-V\fR, \fB\-\-verbose\fR +Provides more detailed output. +.TP +\fB\-k\fR, \fB\-\-host-key-document\fR=\fI\,HOST_KEY_DOCUMENT\/\fR +Specifies a host-key document. At least one is required. Specify this +option multiple times to enable the image to run on more than one +host. +.TP +\fB\-o\fR, \fB\-\-output\fR=\fI\,OUTPUT_FILE\/\fR +Specifies the output file. Required. +.TP +\fB\-i\fR, \fB\-\-image\fR=\fI\,VMLINUZ\/\fR +Specifies the Linux kernel image file. Required. +.TP +\fB\-r\fR, \fB\-\-ramdisk\fR=\fI\,RAMDISK\/\fR +Specifies the RAM disk image. Optional. +.TP +\fB\-p\fR, \fB\-\-parmfile\fR=\fI\,PARMFILE\/\fR +Specifies the kernel command line stored in \fI\,PARMFILE\/\fR. Optional. +.TP +\fB\-\-no-verify\fR +Do not require the host-key documents to be valid. For testing +purposes, do not use for a production image. Optional. +.TP +\fB\-v\fR, \fB\-\-version\fR +Prints version information, then exits. + +.SH EXAMPLE +.PP +Generate a protected virtualization image in +\fI\,/boot/vmlinuz.pv\/\fR, using the kernel file \fI\,vmlinuz\/\fR, +the initrd in \fI\,initramfs\/\fR, the kernel parameters contained in +\fI\,parmfile\/\fR, and the host-key document in \fI\,host_key.crt\/\fR: +.PP +.Vb 1 +.EX +\& genprotimg \-i \fI\,vmlinuz\/\fR \-r \fI\,initramfs\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-o \fI\,/boot/vmlinuz.pv\/\fR +.EE +.Ve +.PP + +.SH NOTES +.IP "1." 4 +An ELF file cannot be used as a Linux kernel image. +.IP "2." 4 +Remember to re-run \fBzipl\fR after updating a protected +virtualization image. + +.SH SEE ALSO +\&\fBzipl\fR\|(5), \fBqemu\fR\|(1) --- /dev/null +++ b/genprotimg/src/Makefile @@ -0,0 +1,101 @@ +# Common definitions +include ../../common.mak + +bin_PROGRAM = genprotimg + +PKGDATADIR ?= "$(DESTDIR)$(TOOLS_DATADIR)/genprotimg" +SRC_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) +TOP_SRCDIR := $(SRC_DIR)/../ +ROOT_DIR = $(TOP_SRC_DIR)/../../ +ZIPL_DIR = $(ROOT_DIR)/zipl +LOADER_DIR = $(TOP_SRCDIR)/boot + +INCLUDE_PATHS = "$(SRC_DIR)" "$(TOP_SRCDIR)" "$(ROOTDIR)/include" +INCLUDE_PARMS = $(addprefix -I,$(INCLUDE_PATHS)) + +WARNINGS := -Wall -Wextra -Wshadow \ + -Wcast-align -Wwrite-strings -Wmissing-prototypes \ + -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline \ + -Wno-long-long -Wuninitialized -Wconversion -Wstrict-prototypes \ + -Wpointer-arith -Werror \ + $(NULL) + +$(bin_PROGRAM)_SRCS := $(bin_PROGRAM).c pv/pv_stage3.c pv/pv_image.c \ + pv/pv_comp.c pv/pv_hdr.c pv/pv_ipib.c utils/crypto.c utils/file_utils.c \ + pv/pv_args.c utils/buffer.c pv/pv_comps.c pv/pv_error.c \ + pv/pv_opt_item.c \ + $(NULL) +$(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o) + +ALL_CFLAGS += -std=gnu11 -DPKGDATADIR=$(PKGDATADIR) \ + $(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) \ + $(WARNINGS) \ + $(NULL) +ALL_CPPFLAGS += $(INCLUDE_PARMS) +LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) + + +ifneq ($(shell sh -c 'command -v pkg-config'),) +GLIB2_CFLAGS := $(shell pkg-config --silence-errors --cflags glib-2.0) +GLIB2_LIBS := $(shell pkg-config --silence-errors --libs glib-2.0) +LIBCRYPTO_CFLAGS := $(shell pkg-config --silence-errors --cflags libcrypto) +LIBCRYPTO_LIBS := $(shell pkg-config --silence-errors --libs libcrypto) +else +GLIB2_CFLAGS := -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include +GLIB2_LIBS := -lglib-2.0 +LIBCRYPTO_CFLAGS := +LIBCRYPTO_LIBS := -lcrypto +endif + +BUILD_TARGETS := skip-$(bin_PROGRAM) +INSTALL_TARGETS := skip-$(bin_PROGRAM) +ifneq (${HAVE_OPENSSL},0) +ifneq (${HAVE_GLIB2},0) +BUILD_TARGETS := $(bin_PROGRAM) +INSTALL_TARGETS := install-$(bin_PROGRAM) +endif +endif + +all: $(BUILD_TARGETS) + +install: $(INSTALL_TARGETS) + +$(bin_PROGRAM): $($(bin_PROGRAM)_OBJS) + +skip-$(bin_PROGRAM): + echo " SKIP $(bin_PROGRAM) due to unresolved dependencies" + +install-$(bin_PROGRAM): $(bin_PROGRAM) + $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) + $(INSTALL) -c $^ $(DESTDIR)$(USRBINDIR) + +clean: + $(RM) -f $($(bin_PROGRAM)_OBJS) $(bin_PROGRAM) .check-dep-$(bin_PROGRAM) .detect-openssl.dep.c + +.PHONY: all install clean skip-$(bin_PROGRAM) install-$(bin_PROGRAM) + +$($(bin_PROGRAM)_OBJS): .check-dep-$(bin_PROGRAM) + +.detect-openssl.dep.c: + echo "#include <openssl/evp.h>" > $@ + echo "#if OPENSSL_VERSION_NUMBER < 0x10100000L" >> $@ + echo " #error openssl version 1.1.0 is required" >> $@ + echo "#endif" >> $@ + echo "static void __attribute__((unused)) test(void) {" >> $@ + echo " EVP_MD_CTX *ctx = EVP_MD_CTX_new();" >> $@ + echo " EVP_MD_CTX_free(ctx);" >> $@ + echo "}" >> $@ + +.check-dep-$(bin_PROGRAM): .detect-openssl.dep.c + $(call check_dep, \ + "$(bin_PROGRAM)", \ + "glib.h", \ + "glib2-devel / libglib2.0-dev", \ + "HAVE_GLIB2=0") + $(call check_dep, \ + "$(bin_PROGRAM)", \ + $^, \ + "openssl-devel / libssl-dev version >= 1.1.0", \ + "HAVE_OPENSSL=0", \ + "-I.") + touch $@ --- /dev/null +++ b/genprotimg/src/common.h @@ -0,0 +1,39 @@ +#ifndef COMMON_H +#define COMMON_H + +#define GETTEXT_PACKAGE "genprotimg" +#include <glib.h> +#include <glib/gi18n-lib.h> + +#include "boot/linux_layout.h" +#include "lib/zt_common.h" + +static const gchar tool_name[] = "genprotimg"; +static const gchar copyright_notice[] = "Copyright IBM Corp. 2020"; + +/* default values */ +#define GENPROTIMG_STAGE3A_PATH (STRINGIFY(PKGDATADIR) "/stage3a.bin") +#define GENPROTIMG_STAGE3B_PATH (STRINGIFY(PKGDATADIR) "/stage3b_reloc.bin") + +#define PSW_SHORT_ADDR_MASK 0x000000007FFFFFFFULL +#define PSW_MASK_BA 0x0000000080000000ULL +#define PSW_MASK_EA 0x0000000100000000ULL +#define PSW_MASK_BIT_12 0x0008000000000000ULL + +#define DEFAULT_INITIAL_PSW_ADDR IMAGE_ENTRY +#define DEFAULT_INITIAL_PSW_MASK (PSW_MASK_EA | PSW_MASK_BA) + +#define DO_PRAGMA(x) _Pragma(#x) + +# ifdef __clang__ +# define WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(...) \ + DO_PRAGMA(clang diagnostic push) \ + DO_PRAGMA(clang diagnostic ignored "-Wunused-function") \ + G_DEFINE_AUTOPTR_CLEANUP_FUNC(__VA_ARGS__) \ + DO_PRAGMA(clang diagnostic pop) +# else +# define WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(...) \ + G_DEFINE_AUTOPTR_CLEANUP_FUNC(__VA_ARGS__) +# endif + +#endif --- /dev/null +++ b/genprotimg/src/genprotimg.c @@ -0,0 +1,181 @@ +/* + * genprotimg - build relocatable secure images + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <errno.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <glib/gtypes.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> + +#include "common.h" +#include "pv/pv_args.h" +#include "pv/pv_image.h" + +enum { + LOG_LEVEL_CRITICAL = 0, + LOG_LEVEL_INFO = 1, + LOG_LEVEL_DEBUG = 2, +}; + +static gint log_level = LOG_LEVEL_CRITICAL; +static gchar *tmp_dir; + +static void rmdir_recursive(gchar *dir_path, GError **err) +{ + const gchar *file = NULL; + g_autoptr(GDir) d = NULL; + + if (!dir_path) + return; + + d = g_dir_open(dir_path, 0, err); + if (!d) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to open directory '%s': %s"), dir_path, + g_strerror(errno)); + return; + } + + while ((file = g_dir_read_name(d)) != NULL) { + g_autofree gchar *file_path = + g_build_filename(dir_path, file, NULL); + /* ignore error */ + (void)g_unlink(file_path); + } + + if (g_rmdir(dir_path) != 0) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to remove directory '%s': %s"), dir_path, + g_strerror(errno)); + return; + } +} + +static void sig_term_handler(int signal G_GNUC_UNUSED) +{ + rmdir_recursive(tmp_dir, NULL); + exit(EXIT_FAILURE); +} + +static void log_handler_cb(const gchar *log_domain G_GNUC_UNUSED, + GLogLevelFlags level, const gchar *message, + gpointer user_data G_GNUC_UNUSED) +{ + const gchar *prefix = ""; + + /* filter out messages depending on debugging level */ + if ((level & G_LOG_LEVEL_DEBUG) && log_level < LOG_LEVEL_DEBUG) + return; + + if ((level & G_LOG_LEVEL_INFO) && log_level < LOG_LEVEL_INFO) + return; + + if (level & G_LOG_LEVEL_WARNING) + prefix = "WARNING: "; + + if (level & G_LOG_LEVEL_ERROR) + prefix = "ERROR: "; + + if (level & (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_ERROR)) + g_printerr("%s%s\n", prefix, message); + else + g_print("%s%s\n", prefix, message); +} + +static void setup_prgname(const gchar *name) +{ + g_set_prgname(name); + g_set_application_name(_(name)); +} + +static void setup_handler(const gint *signals, const gsize signals_n) +{ + /* set up logging handler */ + g_log_set_handler(NULL, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | + G_LOG_FLAG_RECURSION, + log_handler_cb, NULL); + + /* set signal handler */ + for (gsize i = 0; i < signals_n; i++) + signal(signals[i], sig_term_handler); +} + +static void remove_signal_handler(const gint *signals, const gsize signals_n) +{ + for (gsize i = 0; i < signals_n; i++) + signal(signals[i], SIG_DFL); +} + +gint main(gint argc, gchar *argv[]) +{ + g_autoptr(PvArgs) args = pv_args_new(); + gint signals[] = { SIGINT, SIGTERM }; + g_autoptr(PvImage) img = NULL; + gint ret = EXIT_FAILURE; + GError *err = NULL; + + setlocale(LC_CTYPE, ""); + setup_prgname(tool_name); + setup_handler(signals, G_N_ELEMENTS(signals)); + + if (pv_args_parse_options(args, &argc, &argv, &err) < 0) + goto error; + + /* set new log level */ + log_level = args->log_level; + + /* if the user has not specified a temporary directory let's + * create one + */ + if (!args->tmp_dir) { + tmp_dir = g_dir_make_tmp("genprotimg-XXXXXX", &err); + if (!tmp_dir) + goto error; + args->tmp_dir = g_strdup(tmp_dir); + } + + /* allocate and initialize ``pv_img`` data structure */ + img = pv_img_new(args, GENPROTIMG_STAGE3A_PATH, &err); + if (!img) + goto error; + + /* add user components: `args->comps` must be sorted by the + * component type => by memory address + */ + for (GSList *iterator = args->comps; iterator; iterator = iterator->next) { + const PvArg *arg = iterator->data; + + if (pv_img_add_component(img, arg, &err) < 0) + goto error; + } + + if (pv_img_finalize(img, GENPROTIMG_STAGE3B_PATH, &err) < 0) + goto error; + + if (pv_img_write(img, args->output_path, &err) < 0) + goto error; + + ret = EXIT_SUCCESS; + +error: + if (err) { + fputs(err->message, stderr); + fputc('\n', stderr); + g_clear_error(&err); + } + rmdir_recursive(tmp_dir, NULL); + remove_signal_handler(signals, G_N_ELEMENTS(signals)); + g_free(tmp_dir); + exit(ret); +} --- /dev/null +++ b/genprotimg/src/include/pv_crypto_def.h @@ -0,0 +1,25 @@ +/* + * PV cryptography related definitions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_CRYPTO_DEF_H +#define PV_CRYPTO_DEF_H + +#include <stdint.h> + +#include "lib/zt_common.h" + +union ecdh_pub_key { + struct { + uint8_t x[80]; + uint8_t y[80]; + }; + uint8_t data[160]; +} __packed; + +#endif --- /dev/null +++ b/genprotimg/src/include/pv_hdr_def.h @@ -0,0 +1,84 @@ +/* + * PV header definitions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_HDR_DEF_H +#define PV_HDR_DEF_H + +#include <openssl/sha.h> + +#include "boot/s390.h" +#include "lib/zt_common.h" +#include "utils/crypto.h" + +#include "pv_crypto_def.h" + +/* Magic number which is used to identify the file containing the PV + * header + */ +#define PV_MAGIC_NUMBER 0x49424d5365634578ULL +#define PV_VERSION_1 0x00000100U + +/* prevent Ultravisor decryption during unpack operation */ +#define PV_CFLAG_NO_DECRYPTION 0x10000000ULL + +/* maxima for the PV version 1 */ +#define PV_V1_IPIB_MAX_SIZE PAGE_SIZE +#define PV_V1_PV_HDR_MAX_SIZE (2 * PAGE_SIZE) + +typedef struct pv_hdr_key_slot { + uint8_t digest_key[SHA256_DIGEST_LENGTH]; + uint8_t wrapped_key[32]; + uint8_t tag[AES_256_GCM_TAG_SIZE]; +} __packed PvHdrKeySlot; + +typedef struct pv_hdr_opt_item { + uint32_t otype; + uint8_t ibk[32]; + uint8_t data[]; +} __packed PvHdrOptItem; + +/* integrity protected data (by GCM tag), but non-encrypted */ +struct pv_hdr_head { + uint64_t magic; + uint32_t version; + uint32_t phs; + uint8_t iv[AES_256_GCM_IV_SIZE]; + uint32_t res1; + uint64_t nks; + uint64_t sea; + uint64_t nep; + uint64_t pcf; + union ecdh_pub_key cust_pub_key; + uint8_t pld[SHA512_DIGEST_LENGTH]; + uint8_t ald[SHA512_DIGEST_LENGTH]; + uint8_t tld[SHA512_DIGEST_LENGTH]; +} __packed; + +/* Must not have any padding */ +struct pv_hdr_encrypted { + uint8_t cust_comm_key[32]; + uint8_t img_enc_key_1[AES_256_XTS_KEY_SIZE / 2]; + uint8_t img_enc_key_2[AES_256_XTS_KEY_SIZE / 2]; + struct psw_t psw; + uint64_t scf; + uint32_t noi; + uint32_t res2; +}; +STATIC_ASSERT(sizeof(struct pv_hdr_encrypted) == + 32 + 32 + 32 + sizeof(struct psw_t) + 8 + 4 + 4) + +typedef struct pv_hdr { + struct pv_hdr_head head; + struct pv_hdr_key_slot *slots; + struct pv_hdr_encrypted *encrypted; + struct pv_hdr_opt_item **optional_items; + uint8_t tag[AES_256_GCM_TAG_SIZE]; +} PvHdr; + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_args.c @@ -0,0 +1,405 @@ +/* + * PV arguments related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> +#include <glib/gprintf.h> + +#include "common.h" + +#include "pv_comp.h" +#include "pv_error.h" +#include "pv_args.h" + +static gchar summary[] = + "Use genprotimg to create a protected virtualization kernel image file,\n" + "which can be loaded using zipl or QEMU."; + +static gint pv_arg_compare(gconstpointer arg_1, gconstpointer arg_2) +{ + g_assert(arg_1); + g_assert(arg_2); + + PvComponentType a = ((PvArg *)arg_1)->type; + PvComponentType b = ((PvArg *)arg_2)->type; + + if (a < b) + return -1; + if (a == b) + return 0; + return 1; +} + +static gint pv_arg_has_type(gconstpointer arg, gconstpointer type) +{ + const PvArg *c = arg; + const PvComponentType *t = type; + + g_assert(arg); + + if (c->type == *t) + return 0; + if (c->type < *t) + return -1; + return 1; +} + +static gint pv_args_set_defaults(PvArgs *args, GError **err G_GNUC_UNUSED) +{ + if (!args->psw_addr) + args->psw_addr = + g_strdup_printf("0x%lx", DEFAULT_INITIAL_PSW_ADDR); + + return 0; +} + +static gint pv_args_validate_options(PvArgs *args, GError **err) +{ + PvComponentType KERNEL = PV_COMP_TYPE_KERNEL; + + if (args->unused_values->len > 0) { + g_autofree gchar *unused = NULL; + + for (gsize i = args->unused_values->len; i > 0; i--) { + g_autofree gchar *tmp = unused; + + unused = g_strjoin(" ", g_ptr_array_index(args->unused_values, i - 1), + tmp, + NULL); + } + + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_INVALID_ARGUMENT, + _("Unrecognized arguments: '%s'.\nUse 'genprotimg --help' for more information"), + unused); + return -1; + } + + if (!args->output_path) { + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Option '--output' is required.\nUse 'genprotimg --help' for more information")); + return -1; + } + + if (!g_slist_find_custom(args->comps, &KERNEL, pv_arg_has_type)) { + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Option '--image' is required.\nUse 'genprotimg --help' for more information")); + return -1; + } + + if (!args->host_keys || g_strv_length(args->host_keys) == 0) { + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Option '--host-key-document' is required.\nUse 'genprotimg --help' for more information")); + return -1; + } + + if (!args->no_verify) { + g_set_error(err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Use the option '--no-verify' as the verification support is not available yet.")); + return -1; + } + + return 0; +} + +static gboolean cb_add_component(const gchar *option, const gchar *value, + PvArgs *args, GError **err) +{ + PvArg *comp = NULL; + gint type = -1; + + if (g_str_equal(option, "-i") || g_str_equal(option, "--image")) + type = PV_COMP_TYPE_KERNEL; + if (g_str_equal(option, "-r") || g_str_equal(option, "--ramdisk")) + type = PV_COMP_TYPE_INITRD; + if (g_str_equal(option, "-p") || g_str_equal(option, "--parmfile")) + type = PV_COMP_TYPE_CMDLINE; + + if (type < 0) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Invalid option '%s': "), option); + return FALSE; + } + + if (g_slist_find_custom(args->comps, &type, pv_arg_has_type)) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Multiple values for option '%s'"), option); + return FALSE; + } + + comp = pv_arg_new((PvComponentType)type, value); + args->comps = g_slist_insert_sorted(args->comps, comp, pv_arg_compare); + return TRUE; +} + +static gboolean cb_set_string_option(const gchar *option, const gchar *value, + PvArgs *args, GError **err) +{ + gchar **args_option = NULL; + + if (g_str_equal(option, "-o") || g_str_equal(option, "--output")) + args_option = &args->output_path; + if (g_str_equal(option, "--x-comp-key")) + args_option = &args->xts_key_path; + if (g_str_equal(option, "--x-comm-key")) + args_option = &args->cust_comm_key_path; + if (g_str_equal(option, "--x-header-key")) + args_option = &args->cust_root_key_path; + if (g_str_equal(option, "--x-pcf")) + args_option = &args->pcf; + if (g_str_equal(option, "--x-psw")) + args_option = &args->psw_addr; + if (g_str_equal(option, "--x-scf")) + args_option = &args->scf; + + if (!args_option) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Invalid option '%s': "), option); + return FALSE; + } + + if (*args_option) { + g_set_error(err, PV_PARSE_ERROR, PV_PARSE_ERROR_SYNTAX, + _("Multiple values for option '%s'"), option); + return FALSE; + } + + *args_option = g_strdup(value); + return TRUE; +} + +static gboolean cb_set_log_level(const gchar *option G_GNUC_UNUSED, + const gchar *value G_GNUC_UNUSED, PvArgs *args, + GError **err G_GNUC_UNUSED) +{ + args->log_level++; + return TRUE; +} + +static gboolean cb_remaining_values(const gchar *option G_GNUC_UNUSED, + const gchar *value, PvArgs *args, + GError **err G_GNUC_UNUSED) +{ + g_ptr_array_add(args->unused_values, g_strdup(value)); + return TRUE; +} + +#define INDENT " " + +gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], + GError **err) +{ + g_autoptr(GOptionContext) context = NULL; + gboolean print_version = FALSE; + GOptionGroup *group, *x_group; + + g_autofree gchar *psw_desc = g_strdup_printf( + _("Load from the specified hexadecimal ADDRESS.\n" INDENT + "Optional; default: '0x%lx'."), + DEFAULT_INITIAL_PSW_ADDR); + GOptionEntry entries[] = { + { .long_name = "host-key-document", + .short_name = 'k', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_FILENAME_ARRAY, + .arg_data = &args->host_keys, + .description = + _("FILE specifies a host-key document. At least\n" INDENT + "one is required."), + .arg_description = _("FILE") }, + { .long_name = "output", + .short_name = 'o', + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _("Set FILE as the output file."), + .arg_description = _("FILE") }, + { .long_name = "image", + .short_name = 'i', + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_add_component, + .description = _("Use IMAGE as the Linux kernel image."), + .arg_description = _("IMAGE") }, + { .long_name = "ramdisk", + .short_name = 'r', + .flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_add_component, + .description = _("Use RAMDISK as the initial RAM disk\n" INDENT + "(optional)."), + .arg_description = _("RAMDISK") }, + { .long_name = "parmfile", + .short_name = 'p', + .flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_add_component, + .description = _("Use the kernel parameters stored in PARMFILE\n" INDENT + "(optional)."), + .arg_description = _("PARMFILE") }, + { .long_name = "no-verify", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &args->no_verify, + .description = _("Disable the host-key document verification\n" INDENT + "(optional)."), + .arg_description = NULL }, + { .long_name = "verbose", + .short_name = 'V', + .flags = G_OPTION_FLAG_NO_ARG, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_log_level, + .description = _("Provide more detailed output (optional)."), + .arg_description = NULL }, + { .long_name = "version", + .short_name = 'v', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &print_version, + .description = _("Print the version and exit."), + .arg_description = NULL }, + { .long_name = G_OPTION_REMAINING, + .short_name = 0, + .flags = 0, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_remaining_values, + .description = NULL, + .arg_description = NULL }, + { 0 }, + }; + + GOptionEntry x_entries[] = { + { .long_name = "x-comm-key", + .short_name = 0, + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _( + "Use FILE as the customer communication key.\n" INDENT + "Optional; default: auto-generated."), + .arg_description = _("FILE") }, + { .long_name = "x-comp-key", + .short_name = 0, + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _( + "Use FILE as the AES 256-bit XTS key\n" INDENT + "that is used for the component encryption.\n" INDENT + "Optional; default: auto-generated."), + .arg_description = _("FILE") }, + { .long_name = "x-header-key", + .short_name = 0, + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _( + "Use FILE as the AES 256-bit GCM header key\n" INDENT + "that protects the PV header.\n" INDENT + "Optional; default: auto-generated."), + .arg_description = _("FILE") }, + { .long_name = "x-pcf", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = + _("Specify the plaintext control flags\n" INDENT + "as a hexadecimal value.\n" INDENT + "Optional; default: '0x0'."), + .arg_description = _("VALUE") }, + { .long_name = "x-psw", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = psw_desc, + .arg_description = _("ADDRESS") }, + { .long_name = "x-scf", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _("Specify the secret control flags\n" INDENT + "as a hexadecimal value.\n" INDENT + "Optional; default: '0x0'."), + .arg_description = _("VALUE") }, + { 0 }, + }; + + context = g_option_context_new( + _("- Create a protected virtualization image")); + g_option_context_set_summary(context, _(summary)); + group = g_option_group_new(GETTEXT_PACKAGE, _("Application Options:"), + _("Show help options"), args, NULL); + g_option_group_add_entries(group, entries); + g_option_context_set_main_group(context, group); + + x_group = g_option_group_new("experimental", _("Experimental Options:"), + _("Show experimental options"), args, NULL); + g_option_group_add_entries(x_group, x_entries); + g_option_context_add_group(context, x_group); + if (!g_option_context_parse(context, argc, argv, err)) + return -1; + + if (print_version) { + g_printf(_("%s version %s\n"), tool_name, RELEASE_STRING); + g_printf("%s\n", copyright_notice); + exit(EXIT_SUCCESS); + } + + if (pv_args_set_defaults(args, err) < 0) + return -1; + + return pv_args_validate_options(args, err); +} + +PvArgs *pv_args_new(void) +{ + g_autoptr(PvArgs) args = g_new0(PvArgs, 1); + + args->unused_values = g_ptr_array_new_with_free_func(g_free); + return g_steal_pointer(&args); +} + +void pv_args_free(PvArgs *args) +{ + if (!args) + return; + + g_free(args->pcf); + g_free(args->scf); + g_free(args->psw_addr); + g_free(args->cust_root_key_path); + g_free(args->cust_comm_key_path); + g_free(args->gcm_iv_path); + g_strfreev(args->host_keys); + g_free(args->xts_key_path); + g_slist_free_full(args->comps, (GDestroyNotify)pv_arg_free); + g_ptr_array_free(args->unused_values, TRUE); + g_free(args->output_path); + g_free(args->tmp_dir); + g_free(args); +} + +void pv_arg_free(PvArg *arg) +{ + if (!arg) + return; + + g_free(arg->path); + g_free(arg); +} +PvArg *pv_arg_new(PvComponentType type, const gchar *path) +{ + g_autoptr(PvArg) ret = g_new0(struct pv_arg, 1); + + ret->type = type; + ret->path = g_strdup(path); + return g_steal_pointer(&ret); +} --- /dev/null +++ b/genprotimg/src/pv/pv_args.h @@ -0,0 +1,53 @@ +/* + * PV arguments related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_ARGS_H +#define PV_ARGS_H + +#include <glib.h> + +#include "pv_comp.h" + +typedef struct pv_arg { + PvComponentType type; + gchar *path; +} PvArg; + +PvArg *pv_arg_new(PvComponentType type, const gchar *path); +void pv_arg_free(PvArg *arg); + +typedef struct { + gint log_level; + gint no_verify; + gchar *pcf; + gchar *scf; + gchar *psw_addr; /* PSW address which will be used for the start of + * the actual component (e.g. Linux kernel) + */ + gchar *cust_root_key_path; + gchar *cust_comm_key_path; + gchar *gcm_iv_path; + gchar **host_keys; + gchar *xts_key_path; + GSList *comps; + gchar *output_path; + gchar *tmp_dir; + GPtrArray *unused_values; +} PvArgs; + +PvArgs *pv_args_new(void); +void pv_args_free(PvArgs *args); + +gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], + GError **err); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvArg, pv_arg_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvArgs, pv_args_free) + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_comp.c @@ -0,0 +1,446 @@ +/* + * PV component related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> +#include <glib/gtypes.h> +#include <openssl/bn.h> +#include <openssl/evp.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "boot/s390.h" +#include "common.h" +#include "utils/align.h" +#include "utils/buffer.h" +#include "utils/crypto.h" +#include "utils/file_utils.h" + +#include "pv_comp.h" +#include "pv_error.h" + +static void comp_file_free(CompFile *comp) +{ + if (!comp) + return; + + g_free(comp->path); + g_free(comp); +} + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(CompFile, comp_file_free) + +static PvComponent *pv_component_new(PvComponentType type, gsize size, + PvComponentDataType d_type, void **data, + GError **err) +{ + g_autoptr(PvComponent) ret = g_new0(PvComponent, 1); + + g_assert(type >= 0 && type <= UINT16_MAX); + + ret->type = (int)type; + ret->d_type = (int)d_type; + ret->data = g_steal_pointer(data); + ret->orig_size = size; + + if (generate_tweak(&ret->tweak, (uint16_t)type, err) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +PvComponent *pv_component_new_file(PvComponentType type, const gchar *path, + GError **err) +{ + g_autoptr(CompFile) file = g_new0(CompFile, 1); + gsize size; + gint rc; + + g_assert(path != NULL); + + rc = file_size(path, &size, err); + if (rc < 0) + return NULL; + + file->path = g_strdup(path); + file->size = size; + return pv_component_new(type, size, DATA_FILE, (void **)&file, err); +} + +PvComponent *pv_component_new_buf(PvComponentType type, const Buffer *buf, + GError **err) +{ + g_assert(buf); + + g_autoptr(Buffer) dup_buf = buffer_dup(buf, FALSE); + return pv_component_new(type, buf->size, DATA_BUFFER, (void **)&dup_buf, + err); +} + +void pv_component_free(PvComponent *component) +{ + if (!component) + return; + + switch ((PvComponentDataType)component->d_type) { + case DATA_BUFFER: + buffer_clear(&component->buf); + break; + case DATA_FILE: + comp_file_free(component->file); + break; + } + + g_free(component); +} + +gint pv_component_type(const PvComponent *component) +{ + return component->type; +} + +const gchar *pv_component_name(const PvComponent *component) +{ + gint type = pv_component_type(component); + + switch ((PvComponentType)type) { + case PV_COMP_TYPE_KERNEL: + return "kernel"; + case PV_COMP_TYPE_INITRD: + return "ramdisk"; + case PV_COMP_TYPE_CMDLINE: + return "parmline"; + case PV_COMP_TYPE_STAGE3B: + return "stage3b"; + } + + g_assert_not_reached(); +} + +uint64_t pv_component_size(const PvComponent *component) +{ + switch ((PvComponentDataType)component->d_type) { + case DATA_BUFFER: + return component->buf->size; + case DATA_FILE: + return component->file->size; + } + + g_assert_not_reached(); +} + +uint64_t pv_component_get_src_addr(const PvComponent *component) +{ + return component->src_addr; +} + +uint64_t pv_component_get_orig_size(const PvComponent *component) +{ + return component->orig_size; +} + +uint64_t pv_component_get_tweak_prefix(const PvComponent *component) +{ + return GUINT64_FROM_BE(component->tweak.cmp_idx.data); +} + +gboolean pv_component_is_stage3b(const PvComponent *component) +{ + return pv_component_type(component) == PV_COMP_TYPE_STAGE3B; +} + +gint pv_component_align_and_encrypt(PvComponent *component, const gchar *tmp_path, + void *opaque, GError **err) +{ + struct cipher_parms *parms = opaque; + + switch ((PvComponentDataType)component->d_type) { + case DATA_BUFFER: { + g_autoptr(Buffer) enc_buf = NULL; + + if (!(IS_PAGE_ALIGNED(pv_component_size(component)))) { + g_autoptr(Buffer) new = NULL; + + /* create a page aligned copy */ + new = buffer_dup(component->buf, TRUE); + buffer_clear(&component->buf); + component->buf = g_steal_pointer(&new); + } + enc_buf = encrypt_buf(parms, component->buf, err); + if (!enc_buf) + return -1; + + buffer_clear(&component->buf); + component->buf = g_steal_pointer(&enc_buf); + return 0; + } + case DATA_FILE: { + const gchar *comp_name = pv_component_name(component); + gchar *path_in = component->file->path; + g_autofree gchar *path_out = NULL; + gsize orig_size; + gsize prep_size; + + g_assert(path_in); + + path_out = g_build_filename(tmp_path, comp_name, NULL); + if (encrypt_file(parms, path_in, path_out, &orig_size, + &prep_size, err) < 0) + return -1; + + if (component->orig_size != orig_size) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("File has changed during the preparation '%s'"), + path_out); + return -1; + } + + g_free(component->file->path); + component->file->size = prep_size; + component->file->path = g_steal_pointer(&path_out); + return 0; + } + } + + g_assert_not_reached(); +} + +/* Page align the size of the component */ +gint pv_component_align(PvComponent *component, const gchar *tmp_path, + void *opaque G_GNUC_UNUSED, GError **err) +{ + if (IS_PAGE_ALIGNED(pv_component_size(component))) + return 0; + + switch (component->d_type) { + case DATA_BUFFER: { + g_autoptr(Buffer) buf = NULL; + + buf = buffer_dup(component->buf, TRUE); + buffer_clear(&component->buf); + component->buf = g_steal_pointer(&buf); + return 0; + } break; + case DATA_FILE: { + const gchar *comp_name = pv_component_name(component); + g_autofree gchar *path_out = + g_build_filename(tmp_path, comp_name, NULL); + gchar *path_in = component->file->path; + gsize size_out; + + if (pad_file_right(path_out, path_in, &size_out, PAGE_SIZE, + err) < 0) + return -1; + + g_free(component->file->path); + component->file->path = g_steal_pointer(&path_out); + component->file->size = size_out; + return 0; + } break; + } + + g_assert_not_reached(); +} + +/* Convert uint64_t address to byte array */ +static void uint64_to_uint8_buf(uint8_t dst[8], uint64_t addr) +{ + uint8_t *p = (uint8_t *)&addr; + + g_assert(dst); + + for (gint i = 0; i < 8; i++) { + /* cppcheck-suppress objectIndex */ + dst[i] = p[i]; + } +} + +int64_t pv_component_update_ald(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err) +{ + uint64_t addr = pv_component_get_src_addr(comp); + uint64_t size = pv_component_size(comp); + uint64_t cur = addr; + int64_t nep = 0; + + g_assert(IS_PAGE_ALIGNED(size) && size != 0); + + do { + uint64_t cur_be = GUINT64_TO_BE(cur); + uint8_t addr_buf[8]; + + uint64_to_uint8_buf(addr_buf, cur_be); + + if (EVP_DigestUpdate(ctx, addr_buf, sizeof(addr_buf)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return -1; + } + + cur += PAGE_SIZE; + nep++; + } while (cur < addr + size); + + return nep; +} + +int64_t pv_component_update_pld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err) +{ + uint64_t size = pv_component_size(comp); + int64_t nep = 0; + + g_assert(IS_PAGE_ALIGNED(size) && size != 0); + + switch (comp->d_type) { + case DATA_BUFFER: { + const Buffer *buf = comp->buf; + + g_assert(buf->size <= INT64_MAX); + g_assert(buf->size == size); + + if (EVP_DigestUpdate(ctx, buf->data, buf->size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return -1; + } + + nep = (int64_t)(buf->size / PAGE_SIZE); + break; + } + case DATA_FILE: { + const gchar *in_path = comp->file->path; + guchar in_buf[PAGE_SIZE]; + gsize num_bytes_read_total = 0; + gsize num_bytes_read = 0; + FILE *f_in; + + f_in = file_open(in_path, "rb", err); + if (!f_in) + return -1; + + do { + /* Read data in blocks. Update the digest + * context each read. + */ + if (file_read(f_in, in_buf, sizeof(*in_buf), + sizeof(in_buf), &num_bytes_read, + err) < 0) { + fclose(f_in); + return -1; + } + num_bytes_read_total += num_bytes_read; + + if (EVP_DigestUpdate(ctx, in_buf, sizeof(in_buf)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + fclose(f_in); + return -1; + } + + nep++; + } while (num_bytes_read_total < pv_component_size(comp) && + num_bytes_read != 0); + + if (num_bytes_read_total != pv_component_size(comp)) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("'%s' has changed during the preparation"), + in_path); + fclose(f_in); + return -1; + } + fclose(f_in); + break; + } + default: + g_assert_not_reached(); + } + + return nep; +} + +int64_t pv_component_update_tld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err) +{ + uint64_t size = pv_component_size(comp); + const union tweak *tweak = &comp->tweak; + g_autoptr(BIGNUM) tweak_num = NULL; + int64_t nep = 0; + + g_assert(IS_PAGE_ALIGNED(size) && size != 0); + + tweak_num = BN_bin2bn(tweak->data, sizeof(tweak->data), NULL); + if (!tweak_num) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_bin2bn failed")); + } + + for (uint64_t cur = 0; cur < size; cur += PAGE_SIZE) { + guchar tmp[sizeof(tweak->data)] = { 0 }; + + g_assert(BN_num_bytes(tweak_num) >= 0); + g_assert(sizeof(tmp) - (guint)BN_num_bytes(tweak_num) > 0); + + if (BN_bn2binpad(tweak_num, tmp, sizeof(tmp)) < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_bn2binpad failed")); + } + + if (EVP_DigestUpdate(ctx, tmp, sizeof(tmp)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return -1; + } + + /* calculate new tweak value */ + if (BN_add_word(tweak_num, PAGE_SIZE) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_add_word failed")); + } + + nep++; + } + + return nep; +} + +gint pv_component_write(const PvComponent *component, FILE *f, GError **err) +{ + uint64_t offset = pv_component_get_src_addr(component); + + g_assert(f); + + switch (component->d_type) { + case DATA_BUFFER: { + const Buffer *buf = component->buf; + + if (seek_and_write_buffer(f, buf, offset, err) < 0) + return -1; + + return 0; + } + case DATA_FILE: { + const CompFile *file = component->file; + + if (seek_and_write_file(f, file, offset, err) < 0) + return -1; + + return 0; + } + } + + g_assert_not_reached(); +} --- /dev/null +++ b/genprotimg/src/pv/pv_comp.h @@ -0,0 +1,78 @@ +/* + * PV component related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_COMP_H +#define PV_COMP_H + +#include <glib.h> +#include <openssl/evp.h> +#include <stdint.h> + +#include "utils/crypto.h" + +/* The order of this enum also implicitly defines the order of the + * components within the PV image! + */ +typedef enum { + PV_COMP_TYPE_KERNEL = 0, + PV_COMP_TYPE_CMDLINE = 1, + PV_COMP_TYPE_INITRD = 2, + PV_COMP_TYPE_STAGE3B = 3, +} PvComponentType; + +typedef enum { + DATA_FILE = 0, + DATA_BUFFER, +} PvComponentDataType; + +typedef struct comp_file { + gchar *path; + gsize size; +} CompFile; + +typedef struct { + gint type; /* PvComponentType */ + gint d_type; /* PvComponentDataType */ + union { + struct comp_file *file; + Buffer *buf; + void *data; + }; + uint64_t src_addr; + uint64_t orig_size; + union tweak tweak; /* used for the AES XTS encryption */ +} PvComponent; + +PvComponent *pv_component_new_file(PvComponentType type, const gchar *path, + GError **err); +PvComponent *pv_component_new_buf(PvComponentType type, const Buffer *buf, + GError **err); +void pv_component_free(PvComponent *component); +gint pv_component_type(const PvComponent *component); +const gchar *pv_component_name(const PvComponent *component); +uint64_t pv_component_size(const PvComponent *component); +uint64_t pv_component_get_src_addr(const PvComponent *component); +uint64_t pv_component_get_orig_size(const PvComponent *component); +uint64_t pv_component_get_tweak_prefix(const PvComponent *component); +gboolean pv_component_is_stage3b(const PvComponent *component); +gint pv_component_align_and_encrypt(PvComponent *component, const gchar *tmp_path, + void *opaque, GError **err); +gint pv_component_align(PvComponent *component, const gchar *tmp_path, + void *opaque G_GNUC_UNUSED, GError **err); +int64_t pv_component_update_pld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err); +int64_t pv_component_update_ald(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err); +int64_t pv_component_update_tld(const PvComponent *comp, EVP_MD_CTX *ctx, + GError **err); +gint pv_component_write(const PvComponent *component, FILE *f, GError **err); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvComponent, pv_component_free) + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_comps.c @@ -0,0 +1,252 @@ +/* + * PV components related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> +#include <glib/gtypes.h> +#include <openssl/evp.h> +#include <stdint.h> + +#include "boot/s390.h" +#include "boot/stage3b.h" +#include "common.h" +#include "utils/align.h" +#include "utils/crypto.h" + +#include "pv_comp.h" +#include "pv_comps.h" +#include "pv_error.h" +#include "pv_stage3.h" + +struct _pv_img_comps { + gboolean finalized; + uint64_t next_src; + uint64_t nep; + EVP_MD_CTX *ald; /* context used for the hash of the addresses */ + EVP_MD_CTX *pld; /* context used for the hash of the pages content */ + EVP_MD_CTX *tld; /* context used for the hash of the tweaks */ + GSList *comps; /* elements sorted by component type */ +}; + +void pv_img_comps_free(PvImgComps *comps) +{ + if (!comps) + return; + + EVP_MD_CTX_free(comps->ald); + EVP_MD_CTX_free(comps->pld); + EVP_MD_CTX_free(comps->tld); + g_slist_free_full(comps->comps, (GDestroyNotify)pv_component_free); + g_free(comps); +} + +PvImgComps *pv_img_comps_new(const EVP_MD *ald_md, const EVP_MD *pld_md, + const EVP_MD *tld_md, GError **err) +{ + g_autoptr(PvImgComps) ret = g_new0(PvImgComps, 1); + + ret->ald = digest_ctx_new(ald_md, err); + if (!ret->ald) + return NULL; + + ret->pld = digest_ctx_new(pld_md, err); + if (!ret->pld) + return NULL; + + ret->tld = digest_ctx_new(tld_md, err); + if (!ret->tld) + return NULL; + + return g_steal_pointer(&ret); +} + +guint pv_img_comps_length(const PvImgComps *comps) +{ + return g_slist_length(comps->comps); +} + +/* Update hashes and nep */ +/* Returns 0 in case of success and -1 in case of a failure */ +static gint pv_img_comps_hash_comp(PvImgComps *comps, const PvComponent *comp, + GError **err) +{ + int64_t nep_1 = 0; + int64_t nep_2 = 0; + int64_t nep_3 = 0; + + /* update pld */ + nep_1 = pv_component_update_pld(comp, comps->pld, err); + if (nep_1 < 0) + return -1; + + /* update ald */ + nep_2 = pv_component_update_ald(comp, comps->ald, err); + if (nep_2 < 0) + return -1; + + /* update tld */ + nep_3 = pv_component_update_tld(comp, comps->tld, err); + if (nep_3 < 0) + return -1; + + g_assert(nep_1 == nep_2); + g_assert(nep_2 == nep_3); + + /* update comps->nep */ + g_assert_true(g_uint64_checked_add(&comps->nep, comps->nep, + (uint64_t)nep_1)); + return 0; +} + +gint pv_img_comps_add_component(PvImgComps *comps, PvComponent **comp, + GError **err) +{ + g_assert(comp); + g_assert(*comp); + g_assert(comps); + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + + uint64_t src_addr = comps->next_src; + uint64_t src_size = pv_component_size(*comp) + ? PAGE_ALIGN(pv_component_size(*comp)) + : PAGE_SIZE; + + if (comps->finalized) { + g_set_error(err, PV_COMPONENT_ERROR, PV_COMPONENT_ERROR_FINALIZED, + _("Failed to add component, image is already finalized")); + return -1; + } + + /* set the address of the component in the memory layout */ + (*comp)->src_addr = src_addr; + + g_info("%12s:\t0x%012lx (%12ld / %12ld Bytes)", + pv_component_name(*comp), pv_component_get_src_addr(*comp), + pv_component_size(*comp), pv_component_get_orig_size(*comp)); + + /* append the component and pass the responsibility of @comp + * to @comps + */ + comps->comps = g_slist_append(comps->comps, g_steal_pointer(comp)); + comps->next_src += src_size; + + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + g_assert(!*comp); + return 0; +} + +struct stage3b_args *pv_img_comps_get_stage3b_args(const PvImgComps *comps, + struct psw_t *psw) +{ + g_autofree struct stage3b_args *ret = g_new0(struct stage3b_args, 1); + + for (GSList *iterator = comps->comps; iterator; iterator = iterator->next) { + const PvComponent *img_comp = iterator->data; + uint64_t src_addr, dst_size; + + g_assert(img_comp); + + src_addr = pv_component_get_src_addr(img_comp); + dst_size = pv_component_get_orig_size(img_comp); + + g_assert(dst_size <= pv_component_size(img_comp)); + + switch ((PvComponentType)pv_component_type(img_comp)) { + case PV_COMP_TYPE_KERNEL: + memblob_init(&ret->kernel, src_addr, dst_size); + break; + case PV_COMP_TYPE_CMDLINE: + memblob_init(&ret->cmdline, src_addr, dst_size); + break; + case PV_COMP_TYPE_INITRD: + memblob_init(&ret->initrd, src_addr, dst_size); + break; + case PV_COMP_TYPE_STAGE3B: + /* nothing needs to be done since it is the + * stage3b itself + */ + break; + default: + g_assert_not_reached(); + break; + } + } + + /* for `stage3b_args` big-endian format must be used */ + ret->psw.mask = GUINT64_TO_BE(psw->mask); + ret->psw.addr = GUINT64_TO_BE(psw->addr); + return g_steal_pointer(&ret); +} + +gint pv_img_comps_set_offset(PvImgComps *comps, gsize offset, GError **err) +{ + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + + if (!IS_PAGE_ALIGNED(offset)) { + g_set_error(err, PV_IMAGE_ERROR, PV_IMAGE_ERROR_OFFSET, + _("Offset must be page aligned")); + return -1; + } + + if (pv_img_comps_length(comps) > 0) { + g_set_error(err, PV_IMAGE_ERROR, PV_IMAGE_ERROR_OFFSET, + _("Offset cannot be changed after a component was added")); + return -1; + } + + comps->next_src += offset; + + g_assert(IS_PAGE_ALIGNED(comps->next_src)); + return 0; +} + +GSList *pv_img_comps_get_comps(const PvImgComps *comps) +{ + return comps->comps; +} + +gint pv_img_comps_finalize(PvImgComps *comps, Buffer **pld_digest, + Buffer **ald_digest, Buffer **tld_digest, + uint64_t *nep, GError **err) +{ + g_autoptr(Buffer) tmp_pld_digest = NULL; + g_autoptr(Buffer) tmp_ald_digest = NULL; + g_autoptr(Buffer) tmp_tld_digest = NULL; + + comps->finalized = TRUE; + for (GSList *iterator = comps->comps; iterator; iterator = iterator->next) { + const PvComponent *comp = iterator->data; + + /* update hashes and nep */ + if (pv_img_comps_hash_comp(comps, comp, err) < 0) + return -1; + } + + tmp_pld_digest = digest_ctx_finalize(comps->pld, err); + if (!tmp_pld_digest) + return -1; + + tmp_ald_digest = digest_ctx_finalize(comps->ald, err); + if (!tmp_ald_digest) + return -1; + + tmp_tld_digest = digest_ctx_finalize(comps->tld, err); + if (!tmp_tld_digest) + return -1; + + *pld_digest = g_steal_pointer(&tmp_pld_digest); + *ald_digest = g_steal_pointer(&tmp_ald_digest); + *tld_digest = g_steal_pointer(&tmp_tld_digest); + *nep = comps->nep; + return 0; +} + +PvComponent *pv_img_comps_get_nth_comp(PvImgComps *comps, guint n) +{ + return g_slist_nth_data(comps->comps, n); +} --- /dev/null +++ b/genprotimg/src/pv/pv_comps.h @@ -0,0 +1,42 @@ +/* + * PV components related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_COMPS_H +#define PV_COMPS_H + +#include <glib.h> +#include <openssl/evp.h> +#include <stdint.h> + +#include "boot/s390.h" +#include "boot/stage3b.h" +#include "utils/buffer.h" + +#include "pv_comp.h" + +typedef struct _pv_img_comps PvImgComps; + +PvImgComps *pv_img_comps_new(const EVP_MD *ald_md, const EVP_MD *pld_md, + const EVP_MD *tld_md, GError **err); +guint pv_img_comps_length(const PvImgComps *comps); +GSList *pv_img_comps_get_comps(const PvImgComps *comps); +struct stage3b_args *pv_img_comps_get_stage3b_args(const PvImgComps *comps, + struct psw_t *psw); +gint pv_img_comps_add_component(PvImgComps *comps, PvComponent **comp, + GError **err); +PvComponent *pv_img_comps_get_nth_comp(PvImgComps *comps, guint n); +gint pv_img_comps_set_offset(PvImgComps *comps, gsize offset, GError **err); +gint pv_img_comps_finalize(PvImgComps *comps, Buffer **pld_digest, + Buffer **ald_digest, Buffer **tld_digest, + uint64_t *nep, GError **err); +void pv_img_comps_free(PvImgComps *comps); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvImgComps, pv_img_comps_free) + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_error.c @@ -0,0 +1,37 @@ +/* + * PV error related functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> + +#include "pv_error.h" + +GQuark pv_error_quark(void) +{ + return g_quark_from_static_string("pv-error-quark"); +} + +GQuark pv_crypto_error_quark(void) +{ + return g_quark_from_static_string("pv-crypto-error-quark"); +} + +GQuark pv_component_error_quark(void) +{ + return g_quark_from_static_string("pv-component-error-quark"); +} + +GQuark pv_image_error_quark(void) +{ + return g_quark_from_static_string("pv-image-error-quark"); +} + +GQuark pv_parse_error_quark(void) +{ + return g_quark_from_static_string("pv-parse-error-quark"); +} --- /dev/null +++ b/genprotimg/src/pv/pv_error.h @@ -0,0 +1,62 @@ +/* + * PV error related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_ERROR_H +#define PV_ERROR_H + +#include <glib.h> + +GQuark pv_error_quark(void); +GQuark pv_parse_error_quark(void); +GQuark pv_component_error_quark(void); +GQuark pv_crypto_error_quark(void); +GQuark pv_image_error_quark(void); + +#define PV_ERROR pv_error_quark() +#define PV_PARSE_ERROR pv_parse_error_quark() +#define PV_CRYPTO_ERROR pv_crypto_error_quark() +#define PV_COMPONENT_ERROR pv_component_error_quark() +#define PV_IMAGE_ERROR pv_image_error_quark() + +typedef enum { + PV_ERROR_IPIB_SIZE, + PV_ERROR_PV_HDR_SIZE, + PV_ERROR_INTERNAL, +} PvErrors; + +typedef enum { + PV_PARSE_ERROR_OK = 0, + PV_PARSE_ERROR_SYNTAX, + PR_PARSE_ERROR_INVALID_ARGUMENT, + PR_PARSE_ERROR_MISSING_ARGUMENT, +} PvParseErrors; + +typedef enum { + PV_COMPONENT_ERROR_UNALIGNED, + PV_COMPONENT_ERROR_FINALIZED, +} PvComponentErrors; + +typedef enum { + PV_IMAGE_ERROR_OFFSET, + PV_IMAGE_ERROR_FINALIZED, +} PvImageErrors; + +typedef enum { + PV_CRYPTO_ERROR_VERIFICATION, + PV_CRYPTO_ERROR_INIT, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + PV_CRYPTO_ERROR_INTERNAL, + PV_CRYPTO_ERROR_DERIVE, + PV_CRYPTO_ERROR_KEYGENERATION, + PV_CRYPTO_ERROR_RANDOMIZATION, + PV_CRYPTO_ERROR_INVALID_PARM, + PV_CRYPTO_ERROR_INVALID_KEY_SIZE, +} PvCryptoErrors; + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_hdr.c @@ -0,0 +1,293 @@ +/* + * PV header related functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> +#include <glib/gtypes.h> +#include <openssl/aes.h> +#include <openssl/evp.h> +#include <stdint.h> +#include <string.h> + +#include "boot/s390.h" +#include "include/pv_crypto_def.h" +#include "utils/buffer.h" +#include "utils/crypto.h" + +#include "pv_comp.h" +#include "pv_hdr.h" +#include "pv_image.h" + +void pv_hdr_free(PvHdr *hdr) +{ + if (!hdr) + return; + + g_free(hdr->optional_items); + g_free(hdr->encrypted); + g_free(hdr->slots); + g_free(hdr); +} + +uint32_t pv_hdr_size(const PvHdr *hdr) +{ + return GUINT32_FROM_BE(hdr->head.phs); +} + +gboolean pv_hdr_uses_encryption(const PvHdr *hdr) +{ + return !(GUINT64_FROM_BE(hdr->head.pcf) & PV_CFLAG_NO_DECRYPTION); +} + +uint64_t pv_hdr_enc_size(const PvHdr *hdr) +{ + return GUINT64_FROM_BE(hdr->head.sea); +} + +uint32_t pv_hdr_enc_size_casted(const PvHdr *hdr) +{ + uint64_t size = pv_hdr_enc_size(hdr); + + if (size > UINT32_MAX) + g_abort(); + + return (uint32_t)size; +} + +static guint pv_hdr_tag_size(const PvHdr *hdr) +{ + return sizeof(hdr->tag); +} + +uint32_t pv_hdr_aad_size(const PvHdr *hdr) +{ + return pv_hdr_size(hdr) - pv_hdr_enc_size_casted(hdr) - + pv_hdr_tag_size(hdr); +} + +uint64_t pv_hdr_get_nks(const PvHdr *hdr) +{ + return GUINT64_FROM_BE(hdr->head.nks); +} + +/* In-place modification of ``buf`` */ +static gint pv_hdr_encrypt(const PvHdr *hdr, const PvImage *img, Buffer *buf, + GError **err) +{ + uint32_t hdr_len = pv_hdr_size(hdr); + uint32_t aad_len = pv_hdr_aad_size(hdr); + guint tag_len = pv_hdr_tag_size(hdr); + uint32_t enc_len = pv_hdr_enc_size_casted(hdr); + const Buffer aad_part = { .data = buf->data, .size = aad_len }; + Buffer enc_part = { .data = (uint8_t *)buf->data + aad_len, + .size = enc_len }; + Buffer tag_part = { .data = (uint8_t *)buf->data + hdr_len - tag_len, + .size = tag_len }; + struct cipher_parms parms; + int64_t c_len; + + g_assert(aad_part.size + enc_part.size + tag_part.size == buf->size); + g_assert(img->cust_root_key->size <= INT_MAX); + g_assert(img->gcm_iv->size <= INT_MAX); + g_assert(EVP_CIPHER_key_length(img->gcm_cipher) == + (int)img->cust_root_key->size); + g_assert(EVP_CIPHER_iv_length(img->gcm_cipher) == (int)img->gcm_iv->size); + + parms.key = img->cust_root_key; + parms.iv_or_tweak = img->gcm_iv; + parms.cipher = img->gcm_cipher; + + /* in-place encryption */ + c_len = gcm_encrypt(&enc_part, &aad_part, &parms, &enc_part, &tag_part, err); + if (c_len < 0) + return -1; + + g_assert(c_len == enc_len); + return 0; +} + +/* Initializes the unencrypted, but integrity protected part of the PV + * header + */ +static gint pv_hdr_aad_init(PvHdr *hdr, const PvImage *img, GError **err) +{ + g_autofree union ecdh_pub_key *cust_pub_key = NULL; + struct pv_hdr_key_slot *hdr_slot = hdr->slots; + struct pv_hdr_head *head = &hdr->head; + g_autoptr(Buffer) pld = NULL; + g_autoptr(Buffer) ald = NULL; + g_autoptr(Buffer) tld = NULL; + uint64_t nep = 0; + + g_assert(sizeof(head->iv) == img->gcm_iv->size); + g_assert(sizeof(head->cust_pub_key) == sizeof(*cust_pub_key)); + + cust_pub_key = evp_pkey_to_ecdh_pub_key(img->cust_pub_priv_key, err); + if (!cust_pub_key) + return -1; + + head->magic = GUINT64_TO_BE(PV_MAGIC_NUMBER); + head->version = GUINT32_TO_BE(PV_VERSION_1); + /* ``phs`` is already set so we can skip it here */ + memcpy(head->iv, img->gcm_iv->data, sizeof(head->iv)); + /* ``nks`` is already set so we can skip it here */ + /* ``sea`` is already set so we can skip it here */ + head->pcf = GUINT64_TO_BE(img->pcf); + memcpy(head->cust_pub_key.data, cust_pub_key, + sizeof(head->cust_pub_key)); + + if (pv_img_calc_pld_ald_tld_nep(img, &pld, &ald, &tld, &nep, err) < 0) + return -1; + + g_assert(sizeof(head->pld) == pld->size); + g_assert(sizeof(head->ald) == ald->size); + g_assert(sizeof(head->tld) == tld->size); + + head->nep = GUINT64_TO_BE(nep); + memcpy(head->pld, pld->data, sizeof(head->pld)); + memcpy(head->ald, ald->data, sizeof(head->ald)); + memcpy(head->tld, tld->data, sizeof(head->tld)); + + /* set the key slots */ + for (GSList *iterator = img->key_slots; iterator; iterator = iterator->next) { + const PvHdrKeySlot *slot = iterator->data; + + g_assert(slot); + + /* the memory for the slots is pre-allocated so we + * have not to allocate and since PvHdrKeySlot is + * stored in the big-edian format we can simply use + * memcpy. + */ + memcpy(hdr_slot++, slot, sizeof(*slot)); + } + + return 0; +} + +/* Initializes the encrypted and also integrity protected part of the + * PV header + */ +static gint pv_hdr_enc_init(PvHdr *hdr, const PvImage *img, GError **err) +{ + struct pv_hdr_encrypted *enc = hdr->encrypted; + const PvComponent *stage3b; + struct psw_t psw; + + g_assert(sizeof(enc->img_enc_key_1) + sizeof(enc->img_enc_key_2) == + EVP_CIPHER_key_length(img->xts_cipher)); + g_assert(sizeof(enc->cust_comm_key) == img->cust_comm_key->size); + g_assert(img->xts_key->size == + (guint)EVP_CIPHER_key_length(img->xts_cipher)); + + stage3b = pv_img_get_stage3b_comp(img, err); + if (!stage3b) + return -1; + + memcpy(enc->cust_comm_key, img->cust_comm_key->data, + sizeof(enc->cust_comm_key)); + memcpy(enc->img_enc_key_1, img->xts_key->data, + sizeof(enc->img_enc_key_1)); + memcpy(enc->img_enc_key_2, + (uint8_t *)img->xts_key->data + sizeof(enc->img_enc_key_1), + sizeof(enc->img_enc_key_2)); + + /* Setup program check handler */ + psw.mask = GUINT64_TO_BE(DEFAULT_INITIAL_PSW_MASK); + psw.addr = GUINT64_TO_BE(pv_component_get_src_addr(stage3b)); + enc->psw = psw; + enc->scf = GUINT64_TO_BE(img->scf); + enc->noi = GUINT32_TO_BE(g_slist_length(img->optional_items)); + + /* set the optional items */ + for (GSList *iterator = img->optional_items; iterator; + iterator = iterator->next) { + const struct pv_hdr_opt_item *item = iterator->data; + + g_assert(item); + + /* not supported in the first version */ + g_assert_not_reached(); + } + + return 0; +} + +PvHdr *pv_hdr_new(const PvImage *img, GError **err) +{ + uint32_t noi = g_slist_length(img->optional_items); + uint32_t hdr_size = pv_img_get_pv_hdr_size(img); + gsize nks = g_slist_length(img->key_slots); + uint32_t sea = pv_img_get_enc_size(img); + g_autoptr(PvHdr) ret = NULL; + + g_assert(nks > 0); + /* must be a multiple of AES block size */ + g_assert(sea % AES_BLOCK_SIZE == 0); + g_assert(sea >= sizeof(struct pv_hdr_encrypted)); + + ret = g_new0(PvHdr, 1); + ret->slots = g_new0(struct pv_hdr_key_slot, nks); + ret->head.phs = GUINT32_TO_BE(hdr_size); + ret->head.nks = GUINT64_TO_BE(nks); + ret->head.sea = GUINT64_TO_BE(sea); + + ret->encrypted = g_new0(struct pv_hdr_encrypted, 1); + ret->optional_items = g_malloc0(sea - sizeof(struct pv_hdr_encrypted)); + ret->encrypted->noi = GUINT32_TO_BE(noi); + + if (pv_hdr_aad_init(ret, img, err) < 0) + return NULL; + + if (pv_hdr_enc_init(ret, img, err) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +static void pv_hdr_memcpy(const PvHdr *hdr, const Buffer *dst) +{ + uint64_t nks = pv_hdr_get_nks(hdr); + uint8_t *data; + + g_assert(dst->size == pv_hdr_size(hdr)); + g_assert(pv_hdr_enc_size_casted(hdr) >= sizeof(*hdr->encrypted)); + + data = memcpy(dst->data, &hdr->head, sizeof(hdr->head)); + data = memcpy(data + sizeof(hdr->head), hdr->slots, + sizeof(struct pv_hdr_key_slot) * nks); + data = memcpy(data + sizeof(struct pv_hdr_key_slot) * nks, + hdr->encrypted, sizeof(*hdr->encrypted)); + if (pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted) > 0) { + (void)memcpy(data + sizeof(*hdr->encrypted), + hdr->optional_items, + pv_hdr_enc_size_casted(hdr) - sizeof(*hdr->encrypted)); + } +} + +Buffer *pv_hdr_serialize(const PvHdr *hdr, const PvImage *img, + enum PvCryptoMode mode, GError **err) +{ + uint32_t hdr_size = pv_hdr_size(hdr); + g_autoptr(Buffer) ret = NULL; + + ret = buffer_alloc(hdr_size); + pv_hdr_memcpy(hdr, ret); + + if (mode == PV_ENCRYPT) { + /* The buffer @ret is modified in-place */ + if (pv_hdr_encrypt(hdr, img, ret, err) < 0) + return NULL; + } else { + /* Simply copy the tag */ + memcpy((uint8_t *)ret->data + hdr_size - pv_hdr_tag_size(hdr), + hdr->tag, pv_hdr_tag_size(hdr)); + } + + return g_steal_pointer(&ret); +} --- /dev/null +++ b/genprotimg/src/pv/pv_hdr.h @@ -0,0 +1,36 @@ +/* + * PV header related functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_HDR_H +#define PV_HDR_H + +#include <glib.h> +#include <stdint.h> + +#include "boot/s390.h" +#include "include/pv_hdr_def.h" +#include "utils/crypto.h" +#include "utils/buffer.h" + +#include "pv_image.h" + +PvHdr *pv_hdr_new(const PvImage *img, GError **err); +void pv_hdr_free(PvHdr *hdr); +G_GNUC_UNUSED gboolean pv_hdr_uses_encryption(const PvHdr *hdr); +Buffer *pv_hdr_serialize(const PvHdr *hdr, const PvImage *img, + enum PvCryptoMode mode, GError **err); +uint32_t pv_hdr_size(const PvHdr *hdr); +uint32_t pv_hdr_aad_size(const PvHdr *hdr); +uint64_t pv_hdr_enc_size(const PvHdr *hdr); +uint32_t pv_hdr_enc_size_casted(const PvHdr *hdr); +uint64_t pv_hdr_get_nks(const PvHdr *hdr); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvHdr, pv_hdr_free) + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_image.c @@ -0,0 +1,820 @@ +/* + * PV image related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <errno.h> +#include <glib.h> +#include <openssl/evp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "boot/stage3a.h" +#include "common.h" +#include "include/pv_crypto_def.h" +#include "include/pv_hdr_def.h" +#include "utils/align.h" +#include "utils/crypto.h" +#include "utils/file_utils.h" + +#include "pv_args.h" +#include "pv_comps.h" +#include "pv_error.h" +#include "pv_hdr.h" +#include "pv_image.h" +#include "pv_ipib.h" +#include "pv_opt_item.h" +#include "pv_stage3.h" + +const PvComponent *pv_img_get_stage3b_comp(const PvImage *img, GError **err) +{ + const PvComponent *comp; + + g_return_val_if_fail(pv_img_comps_length(img->comps) >= 1, NULL); + + comp = pv_img_comps_get_nth_comp(img->comps, + pv_img_comps_length(img->comps) - 1); + if (!pv_component_is_stage3b(comp)) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to get 'stage3b' component")); + return NULL; + } + return comp; +} + +typedef gint (*prepare_func)(PvComponent *obj, const gchar *tmp_path, + void *opaque, GError **err); + +static gint pv_img_prepare_component(const PvImage *img, PvComponent *comp, + GError **err) +{ + struct cipher_parms parms = { 0 }; + g_autoptr(Buffer) tweak = NULL; + prepare_func func = NULL; + void *opaque = NULL; + gint rc; + + if (img->pcf & PV_CFLAG_NO_DECRYPTION) { + /* we only need to align the components */ + func = pv_component_align; + opaque = NULL; + } else { + const EVP_CIPHER *cipher = img->xts_cipher; + + g_assert_cmpint((int)img->xts_key->size, ==, + EVP_CIPHER_key_length(cipher)); + g_assert_cmpint((int)PAGE_SIZE % EVP_CIPHER_block_size(cipher), + ==, 0); + g_assert_cmpint(sizeof(comp->tweak), ==, + EVP_CIPHER_iv_length(cipher)); + g_assert(img->xts_key->size <= UINT_MAX); + + tweak = buffer_alloc(sizeof(comp->tweak.data)); + memcpy(tweak->data, comp->tweak.data, tweak->size); + func = pv_component_align_and_encrypt; + parms.cipher = cipher; + parms.key = img->xts_key; + parms.iv_or_tweak = tweak; + + opaque = &parms; + } + + rc = (*func)(comp, img->tmp_dir, opaque, err); + if (rc < 0) + return -1; + + return 0; +} + +static Buffer *pv_img_read_key(const gchar *path, guint key_size, + GError **err) +{ + g_autoptr(Buffer) tmp_ret = NULL; + Buffer *ret = NULL; + gsize bytes_read; + FILE *f = NULL; + gsize size; + + if (file_size(path, &size, err) != 0) + return NULL; + + if (size - key_size != 0) { + g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, + _("Wrong file size '%s': read %zd, expected %u"), path, size, + key_size); + return NULL; + } + + f = file_open(path, "rb", err); + if (!f) + return NULL; + + tmp_ret = buffer_alloc(size); + if (file_read(f, tmp_ret->data, 1, tmp_ret->size, &bytes_read, err) < 0) + goto err; + + if (bytes_read - key_size != 0) { + g_set_error(err, PV_ERROR, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, + _("Wrong file size '%s': read %zd, expected %u"), + path, bytes_read, key_size); + goto err; + } + + ret = g_steal_pointer(&tmp_ret); +err: + if (f) + fclose(f); + return ret; +} + +static EVP_PKEY *pv_img_get_cust_pub_priv_key(gint nid, GError **err) +{ + return generate_ec_key(nid, err); +} + +static HostKeyList *pv_img_get_host_keys(gchar **host_cert_paths, + X509_STORE *store, gint nid, + GError **err) +{ + g_autoslist(EVP_PKEY) ret = NULL; + + g_assert(host_cert_paths); + + for (gchar **iterator = host_cert_paths; iterator != NULL && *iterator != NULL; + iterator++) { + g_autoptr(EVP_PKEY) host_key = NULL; + const gchar *path = *iterator; + + g_assert(path); + + host_key = read_ec_pubkey_cert(store, nid, path, err); + if (!host_key) + return NULL; + + ret = g_slist_append(ret, g_steal_pointer(&host_key)); + } + + return g_steal_pointer(&ret); +} + +static Buffer *pv_img_get_key(const EVP_CIPHER *cipher, const gchar *path, + GError **err) +{ + gint key_len = EVP_CIPHER_key_length(cipher); + + g_assert(key_len > 0); + + if (path) + return pv_img_read_key(path, (guint)key_len, err); + + return generate_aes_key((guint)key_len, err); +} + +static Buffer *pv_img_get_iv(const EVP_CIPHER *cipher, const gchar *path, + GError **err) +{ + gint iv_len = EVP_CIPHER_iv_length(cipher); + + g_assert(iv_len > 0); + + if (path) + return pv_img_read_key(path, (guint)iv_len, err); + + return generate_aes_iv((guint)iv_len, err); +} + +static int hex_str_toull(const gchar *nptr, uint64_t *dst, + GError **err) +{ + uint64_t value; + gchar *end; + + g_assert(dst); + + if (!g_str_is_ascii(nptr)) { + g_set_error(err, PV_ERROR, EINVAL, + _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), + nptr); + return -1; + } + + value = g_ascii_strtoull(nptr, &end, 16); + if ((value == G_MAXUINT64 && errno == ERANGE) || + (end && *end != '\0')) { + g_set_error(err, PV_ERROR, EINVAL, + _("Invalid value: '%s'. A hexadecimal value is required, for example '0xcfe'"), + nptr); + return -1; + } + *dst = value; + return 0; +} + +static gint pv_img_set_psw_addr(PvImage *img, const gchar *psw_addr_s, + GError **err) +{ + if (psw_addr_s) { + uint64_t psw_addr; + + if (hex_str_toull(psw_addr_s, &psw_addr, err) < 0) + return -1; + + img->initial_psw.addr = psw_addr; + } + + return 0; +} + +static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s, + const gchar *scf_s, GError **err) +{ + uint64_t flags; + + if (pcf_s) { + if (hex_str_toull(pcf_s, &flags, err) < 0) + return -1; + + img->pcf = flags; + } + + if (scf_s) { + if (hex_str_toull(scf_s, &flags, err) < 0) + return -1; + + img->scf = flags; + } + + return 0; +} + +/* read in the keys or auto-generate them */ +static gint pv_img_set_keys(PvImage *img, const PvArgs *args, GError **err) +{ + g_autoptr(X509_STORE) store = NULL; + + g_assert(img->xts_cipher); + g_assert(img->cust_comm_cipher); + g_assert(img->gcm_cipher); + g_assert(img->nid); + + img->xts_key = pv_img_get_key(img->xts_cipher, args->xts_key_path, err); + if (!img->xts_key) + return -1; + + img->cust_comm_key = pv_img_get_key(img->cust_comm_cipher, + args->cust_comm_key_path, err); + if (!img->cust_comm_key) + return -1; + + img->cust_root_key = + pv_img_get_key(img->gcm_cipher, args->cust_root_key_path, err); + if (!img->cust_root_key) + return -1; + + img->gcm_iv = pv_img_get_iv(img->gcm_cipher, args->gcm_iv_path, err); + if (!img->gcm_iv) + return -1; + + img->cust_pub_priv_key = pv_img_get_cust_pub_priv_key(img->nid, err); + if (!img->cust_pub_priv_key) + return -1; + + img->host_pub_keys = + pv_img_get_host_keys(args->host_keys, store, img->nid, err); + if (!img->host_pub_keys) + return -1; + + return 0; +} + +static void pv_img_add_host_slot(PvImage *img, PvHdrKeySlot *slot) +{ + img->key_slots = g_slist_append(img->key_slots, slot); +} + +static void pv_hdr_key_slot_free(PvHdrKeySlot *slot) +{ + if (!slot) + return; + + g_free(slot); +} + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvHdrKeySlot, pv_hdr_key_slot_free) + +static PvHdrKeySlot *pv_hdr_key_slot_new(const EVP_CIPHER *gcm_cipher, + const Buffer *cust_root_key, + EVP_PKEY *cust_key, EVP_PKEY *host_key, + GError **err) +{ + g_autoptr(PvHdrKeySlot) ret = g_new0(PvHdrKeySlot, 1); + g_autofree union ecdh_pub_key *pub = NULL; + g_autoptr(Buffer) exchange_key = NULL; + g_autoptr(Buffer) digest_key = NULL; + g_autoptr(Buffer) iv = NULL; + Buffer pub_buf; + /* No AAD data is used */ + Buffer aad = { .data = NULL, .size = 0 }; + /* Set the output buffers for the encrypted data and the + * generated GCM tag + */ + Buffer enc = { .data = ret->wrapped_key, .size = sizeof(ret->wrapped_key) }; + Buffer tag = { .data = ret->tag, .size = sizeof(ret->tag) }; + struct cipher_parms parms; + int64_t c_len = 0; + + g_assert(EVP_CIPHER_iv_length(gcm_cipher) >= 0); + + pub = evp_pkey_to_ecdh_pub_key(host_key, err); + if (!pub) + return NULL; + + pub_buf.data = pub->data; + pub_buf.size = sizeof(*pub); + digest_key = sha256_buffer(&pub_buf, err); + if (!digest_key) + return NULL; + + g_assert(digest_key->size == sizeof(ret->digest_key)); + /* set `digest_key` field */ + memcpy(ret->digest_key, digest_key->data, sizeof(ret->digest_key)); + + exchange_key = compute_exchange_key(cust_key, host_key, err); + if (!exchange_key) + return NULL; + + /* initialize cipher parameters */ + g_assert(exchange_key->size <= INT_MAX); + g_assert(exchange_key->size == (guint)EVP_CIPHER_key_length(gcm_cipher)); + + /* create zero IV */ + iv = buffer_alloc((guint)EVP_CIPHER_iv_length(gcm_cipher)); + parms.iv_or_tweak = iv; + parms.key = exchange_key; + parms.cipher = gcm_cipher; + + /* Encrypt the customer root key that is used for the encryption + * of the PV header + */ + c_len = gcm_encrypt(cust_root_key, &aad, &parms, &enc, &tag, err); + if (c_len < 0) + return NULL; + + g_assert(c_len == (int64_t)cust_root_key->size); + return g_steal_pointer(&ret); +} + +static gint pv_img_set_host_slots(PvImage *img, GError **err) +{ + for (GSList *iterator = img->host_pub_keys; iterator; iterator = iterator->next) { + EVP_PKEY *host_key = iterator->data; + + g_assert(host_key); + + PvHdrKeySlot *slot = pv_hdr_key_slot_new(img->gcm_cipher, + img->cust_root_key, + img->cust_pub_priv_key, + host_key, err); + if (!slot) + return -1; + + pv_img_add_host_slot(img, slot); + } + + return 0; +} + +static gint pv_img_set_comps_offset(PvImage *img, uint64_t offset, GError **err) +{ + return pv_img_comps_set_offset(img->comps, offset, err); +} + +PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err) +{ + g_autoptr(PvImage) ret = g_new0(PvImage, 1); + uint64_t offset; + + g_assert(args->tmp_dir); + g_assert(stage3a_path); + + if (args->no_verify) + g_warning(_("host-key document verification is disabled. Your workload is not secured.")); + + ret->comps = pv_img_comps_new(EVP_sha512(), EVP_sha512(), EVP_sha512(), err); + if (!ret->comps) + return NULL; + + ret->cust_comm_cipher = EVP_aes_256_gcm(); + ret->gcm_cipher = EVP_aes_256_gcm(); + ret->initial_psw.addr = DEFAULT_INITIAL_PSW_ADDR; + ret->initial_psw.mask = DEFAULT_INITIAL_PSW_MASK; + ret->nid = NID_secp521r1; + ret->tmp_dir = g_strdup(args->tmp_dir); + ret->xts_cipher = EVP_aes_256_xts(); + + /* set initial PSW that will be loaded by the stage3b */ + if (pv_img_set_psw_addr(ret, args->psw_addr, err) < 0) + return NULL; + + /* set the control flags: PCF and SCF */ + if (pv_img_set_control_flags(ret, args->pcf, args->scf, err) < 0) + return NULL; + + /* read in the keys */ + if (pv_img_set_keys(ret, args, err) < 0) + return NULL; + + if (pv_img_set_host_slots(ret, err) < 0) + return NULL; + + /* allocate enough memory for the stage3a args and load the + * stage3a template into memory and set the loader_psw + */ + if (pv_img_load_and_set_stage3a(ret, stage3a_path, err) < 0) + return NULL; + + offset = PAGE_ALIGN(STAGE3A_LOAD_ADDRESS + ret->stage3a->size); + + /* shift right all components by the size of stage3a loader */ + if (pv_img_set_comps_offset(ret, offset, err) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +void pv_img_free(PvImage *img) +{ + if (!img) + return; + + g_slist_free_full(img->optional_items, + (GDestroyNotify)pv_opt_item_free); + g_slist_free_full(img->key_slots, (GDestroyNotify)pv_hdr_key_slot_free); + g_slist_free_full(img->host_pub_keys, (GDestroyNotify)EVP_PKEY_free); + EVP_PKEY_free(img->cust_pub_priv_key); + buffer_clear(&img->stage3a); + pv_img_comps_free(img->comps); + g_free(img->tmp_dir); + buffer_free(img->xts_key); + buffer_free(img->cust_root_key); + buffer_free(img->gcm_iv); + buffer_free(img->cust_comm_key); + g_free(img); +} + +static gint pv_img_prepare_and_add_component(PvImage *img, PvComponent **comp, + GError **err) +{ + g_assert(comp); + g_assert(*comp); + + /* prepares the component: does the alignment and encryption + * if required + */ + if (pv_img_prepare_component(img, *comp, err) < 0) + return -1; + + /* calculates the memory layout and adds the component to its + * internal list + */ + if (pv_img_comps_add_component(img->comps, comp, err) < 0) + return -1; + + g_assert(!*comp); + return 0; +} + +gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err) +{ + g_autoptr(PvComponent) comp = NULL; + + comp = pv_component_new_file(arg->type, arg->path, err); + if (!comp) + return -1; + + if (pv_img_prepare_and_add_component(img, &comp, err) < 0) + return -1; + + g_assert(!comp); + return 0; +} + +gint pv_img_calc_pld_ald_tld_nep(const PvImage *img, Buffer **pld, Buffer **ald, + Buffer **tld, uint64_t *nep, GError **err) +{ + return pv_img_comps_finalize(img->comps, pld, ald, tld, nep, err); +} + +static gint pv_img_build_stage3b(PvImage *img, Buffer *stage3b, GError **err) +{ + g_autofree struct stage3b_args *args = NULL; + + args = pv_img_comps_get_stage3b_args(img->comps, &img->initial_psw); + if (!args) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Cannot generate stage3b arguments")); + return -1; + } + + build_stage3b(stage3b, args); + return 0; +} + +gint pv_img_add_stage3b_comp(PvImage *img, const gchar *path, GError **err) +{ + g_autoptr(PvComponent) comp = NULL; + g_autoptr(Buffer) stage3b = NULL; + + stage3b = stage3b_getblob(path, err); + if (!stage3b) + return -1; + + /* set the stage3b data */ + if (pv_img_build_stage3b(img, stage3b, err) < 0) + return -1; + + comp = pv_component_new_buf(PV_COMP_TYPE_STAGE3B, stage3b, err); + if (!comp) + return -1; + + if (pv_img_prepare_and_add_component(img, &comp, err) < 0) + return -1; + + g_assert(!comp); + return 0; +} + +static uint32_t pv_img_get_aad_size(const PvImage *img) +{ + uint32_t key_size, size = 0; + + g_assert(sizeof(struct pv_hdr_head) <= UINT32_MAX); + g_assert(sizeof(struct pv_hdr_key_slot) <= UINT32_MAX); + + g_assert_true(g_uint_checked_add(&size, size, + (uint32_t)sizeof(struct pv_hdr_head))); + g_assert_true(g_uint_checked_mul(&key_size, + (uint32_t)sizeof(struct pv_hdr_key_slot), + g_slist_length(img->key_slots))); + g_assert_true(g_uint_checked_add(&size, size, key_size)); + return size; +} + +static uint32_t pv_img_get_opt_items_size(const PvImage *img) +{ + uint32_t ret = 0; + + g_assert(img); + + for (GSList *iterator = img->optional_items; iterator; + iterator = iterator->next) { + const struct pv_hdr_opt_item *item = iterator->data; + + g_assert(item); + g_assert_true(g_uint_checked_add(&ret, ret, pv_opt_item_size(item))); + } + return ret; +} + +uint32_t pv_img_get_enc_size(const PvImage *img) +{ + uint32_t ret = 0; + + g_assert(sizeof(struct pv_hdr_encrypted) <= UINT32_MAX); + + g_assert_true(g_uint_checked_add( + &ret, ret, (uint32_t)sizeof(struct pv_hdr_encrypted))); + g_assert_true( + g_uint_checked_add(&ret, ret, pv_img_get_opt_items_size(img))); + return ret; +} + +static uint32_t pv_img_get_tag_size(const PvImage *img G_GNUC_UNUSED) +{ + g_assert(sizeof(((struct pv_hdr *)0)->tag) <= UINT32_MAX); + + return (uint32_t)sizeof(((struct pv_hdr *)0)->tag); +} + +uint32_t pv_img_get_pv_hdr_size(const PvImage *img) +{ + uint32_t size = 0; + + g_assert_true( + g_uint_checked_add(&size, size, pv_img_get_aad_size(img))); + g_assert_true( + g_uint_checked_add(&size, size, pv_img_get_enc_size(img))); + g_assert_true( + g_uint_checked_add(&size, size, pv_img_get_tag_size(img))); + return size; +} + +static gint get_stage3a_data_size(const PvImage *img, gsize *data_size, + GError **err) +{ + gsize ipib_size, hdr_size; + + g_assert(data_size); + g_assert(*data_size == 0); + + ipib_size = pv_ipib_get_size(pv_img_comps_length(img->comps)); + if (ipib_size > PV_V1_IPIB_MAX_SIZE) { + g_set_error(err, PV_ERROR, PV_ERROR_IPIB_SIZE, + _("IPIB size is too large: '%zu' > '%zu'"), + ipib_size, PV_V1_IPIB_MAX_SIZE); + return -1; + } + + hdr_size = pv_img_get_pv_hdr_size(img); + if (hdr_size > PV_V1_PV_HDR_MAX_SIZE) { + g_set_error(err, PV_ERROR, PV_ERROR_PV_HDR_SIZE, + _("PV header size is too large: '%zu' > '%zu'"), + hdr_size, PV_V1_PV_HDR_MAX_SIZE); + return -1; + } + + *data_size += PAGE_ALIGN(ipib_size); + *data_size += PAGE_ALIGN(hdr_size); + return 0; +} + +gint pv_img_load_and_set_stage3a(PvImage *img, const gchar *path, GError **err) +{ + g_autoptr(Buffer) stage3a = NULL; + gsize bin_size, data_size = 0; + + if (get_stage3a_data_size(img, &data_size, err) < 0) + return -1; + + stage3a = stage3a_getblob(path, &bin_size, data_size, err); + if (!stage3a) + return -1; + + img->stage3a_psw.addr = STAGE3A_ENTRY; + img->stage3a_psw.mask = DEFAULT_INITIAL_PSW_MASK; + + /* set addresses and size */ + img->stage3a = g_steal_pointer(&stage3a); + img->stage3a_bin_size = bin_size; + return 0; +} + +/* Creates the PV IPIB and sets the stage3a arguments */ +static gint pv_img_build_stage3a(Buffer *stage3a, gsize stage3a_bin_size, + GSList *comps, const Buffer *hdr, GError **err) +{ + g_autofree struct ipl_parameter_block *ipib = NULL; + + g_assert(stage3a); + g_assert(hdr); + + ipib = pv_ipib_new(comps, hdr, err); + if (!ipib) + return -1; + + if (build_stage3a(stage3a, stage3a_bin_size, hdr, ipib, err) < 0) + return -1; + + g_info("%12s:\t0x%012lx (%12ld / %12ld Bytes)", "stage3a", + STAGE3A_LOAD_ADDRESS, stage3a->size, stage3a->size); + return 0; +} + +/* Creates the actual PV header (serialized and AES-GCM encrypted) */ +static Buffer *pv_img_create_pv_hdr(PvImage *img, GError **err) +{ + g_autoptr(Buffer) hdr_buf = NULL; + g_autoptr(PvHdr) hdr = NULL; + + hdr = pv_hdr_new(img, err); + if (!hdr) + return NULL; + + hdr_buf = pv_hdr_serialize(hdr, img, PV_ENCRYPT, err); + if (!hdr_buf) + return NULL; + + return g_steal_pointer(&hdr_buf); +} + +/* No changes to the components are allowed after calling this + * function + */ +gint pv_img_finalize(PvImage *pv, const gchar *stage3b_path, GError **err) +{ + g_autoptr(Buffer) hdr = NULL; + + /* load stage3b template into memory and add it to the list of + * components. This must be done before calling + * `pv_img_load_and_set_stage3a`. + */ + if (pv_img_add_stage3b_comp(pv, stage3b_path, err) < 0) + return -1; + + /* create the PV header */ + hdr = pv_img_create_pv_hdr(pv, err); + if (!hdr) + return -1; + + /* generate stage3a. At this point in time the PV header and + * the stage3b must be generated and encrypted + */ + if (pv_img_build_stage3a(pv->stage3a, pv->stage3a_bin_size, + pv_img_comps_get_comps(pv->comps), hdr, err) < 0) + return -1; + + return 0; +} + +static gint convert_psw_to_short_psw(const struct psw_t *psw, uint64_t *dst, + GError **err) +{ + g_assert(psw); + g_assert(dst); + + uint64_t psw_addr = psw->addr; + uint64_t psw_mask = psw->mask; + + /* test if PSW mask can be converted */ + if (psw_mask & PSW_SHORT_ADDR_MASK) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to convert PSW to short PSW")); + return -1; + } + + /* test for bit 12 */ + if (psw_mask & PSW_MASK_BIT_12) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to convert PSW to short PSW")); + return -1; + } + + /* test if PSW addr can be converted */ + if (psw_addr & ~PSW_SHORT_ADDR_MASK) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Failed to convert PSW to short PSW")); + return -1; + } + + *dst = psw_mask; + /* set bit 12 to 1 */ + *dst |= PSW_MASK_BIT_12; + *dst |= psw_addr; + return 0; +} + +static gint write_short_psw(FILE *f, struct psw_t *psw, GError **err) +{ + uint64_t short_psw, short_psw_be; + + if (convert_psw_to_short_psw(psw, &short_psw, err) < 0) + return -1; + + short_psw_be = GUINT64_TO_BE(short_psw); + return file_write(f, &short_psw_be, 1, sizeof(short_psw_be), NULL, err); +} + +gint pv_img_write(PvImage *img, const gchar *path, GError **err) +{ + gint ret = -1; + FILE *f = file_open(path, "wb", err); + + if (!f) + return -1; + + if (write_short_psw(f, &img->stage3a_psw, err) < 0) { + g_prefix_error(err, _("Failed to write image '%s': "), path); + goto err; + } + + if (seek_and_write_buffer(f, img->stage3a, STAGE3A_LOAD_ADDRESS, err) < + 0) { + g_prefix_error(err, _("Failed to write image '%s': "), path); + goto err; + } + + /* list is sorted by component type => by address */ + for (GSList *iterator = pv_img_comps_get_comps(img->comps); iterator; + iterator = iterator->next) { + gint rc; + const PvComponent *comp = iterator->data; + + rc = pv_component_write(comp, f, err); + if (rc < 0) { + g_prefix_error(err, _("Failed to write image '%s': "), + path); + goto err; + } + } + + ret = 0; +err: + if (f) + fclose(f); + return ret; +} --- /dev/null +++ b/genprotimg/src/pv/pv_image.h @@ -0,0 +1,68 @@ +/* + * PV image related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_IMAGE_H +#define PV_IMAGE_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <openssl/evp.h> +#include <stdint.h> + +#include "boot/s390.h" +#include "utils/buffer.h" + +#include "pv_args.h" +#include "pv_comp.h" +#include "pv_comps.h" +#include "pv_stage3.h" + +typedef struct { + gchar *tmp_dir; /* directory used for temporary files */ + Buffer *stage3a; /* stage3a containing IPIB and PV header */ + gsize stage3a_bin_size; /* size of stage3a.bin */ + struct psw_t stage3a_psw; /* (short) PSW that is written to + * location 0 of the created image + */ + struct psw_t initial_psw; /* PSW loaded by stage3b */ + EVP_PKEY *cust_pub_priv_key; /* customer private/public key */ + GSList *host_pub_keys; /* public host keys */ + gint nid; /* Elliptic Curve used for the key derivation */ + /* keys and cipher used for the AES-GCM encryption */ + Buffer *cust_root_key; + Buffer *gcm_iv; + const EVP_CIPHER *gcm_cipher; + /* Information for the IPIB and PV header */ + uint64_t pcf; + uint64_t scf; + Buffer *cust_comm_key; + const EVP_CIPHER *cust_comm_cipher; + Buffer *xts_key; + const EVP_CIPHER *xts_cipher; + GSList *key_slots; + GSList *optional_items; + PvImgComps *comps; +} PvImage; + +PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err); +void pv_img_free(PvImage *img); +gint pv_img_add_component(PvImage *img, const PvArg *arg, GError **err); +gint pv_img_finalize(PvImage *img, const gchar *stage3b_path, GError **err); +gint pv_img_calc_pld_ald_tld_nep(const PvImage *img, Buffer **pld, Buffer **ald, + Buffer **tld, uint64_t *nep, GError **err); +gint pv_img_load_and_set_stage3a(PvImage *img, const gchar *path, GError **err); +const PvComponent *pv_img_get_stage3b_comp(const PvImage *img, GError **err); +gint pv_img_add_stage3b_comp(PvImage *img, const gchar *path, GError **err); +uint32_t pv_img_get_enc_size(const PvImage *img); +uint32_t pv_img_get_pv_hdr_size(const PvImage *img); +gint pv_img_write(PvImage *img, const gchar *path, GError **err); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(PvImage, pv_img_free) + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_ipib.c @@ -0,0 +1,128 @@ +/* + * PV IPIB related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> +#include <glib/gtypes.h> +#include <stdint.h> +#include <stdio.h> + +#include "boot/ipl.h" +#include "boot/s390.h" +#include "common.h" +#include "include/pv_hdr_def.h" +#include "lib/zt_common.h" +#include "utils/align.h" +#include "utils/buffer.h" + +#include "pv_comp.h" +#include "pv_error.h" +#include "pv_ipib.h" + +uint64_t pv_ipib_get_size(uint32_t num_comp) +{ + gsize ipib_size = sizeof(struct ipl_pl_hdr) + + sizeof(struct ipl_pb0_pv) + + num_comp * sizeof(struct ipl_pb0_pv_comp); + + /* the minimal size is one page */ + return MAX(ipib_size, PAGE_SIZE); +} + +static gint pv_ipib_init(IplParameterBlock *ipib, GSList *comps, + const Buffer *hdr) +{ + g_assert(sizeof(struct ipl_pl_hdr) <= UINT32_MAX); + g_assert(sizeof(struct ipl_pb0_pv_comp) <= UINT32_MAX); + g_assert(sizeof(struct ipl_pb0_pv) <= UINT32_MAX); + g_assert(ipib); + + guint comps_length = g_slist_length(comps); + uint32_t ipl_pl_hdr_size = (uint32_t)sizeof(struct ipl_pl_hdr); + struct ipl_pb0_pv *pv = &ipib->pv; + uint32_t ipib_comps_size; + uint32_t blk0_len; + uint32_t ipib_size; + gsize i; + + g_assert_true( + g_uint_checked_mul(&ipib_comps_size, comps_length, + (uint32_t)sizeof(struct ipl_pb0_pv_comp))); + g_assert_true(g_uint_checked_add(&blk0_len, (uint32_t)sizeof(*pv), + ipib_comps_size)); + g_assert(ipl_pl_hdr_size + blk0_len <= PAGE_SIZE); + + ipib_size = MAX(ipl_pl_hdr_size + blk0_len, (uint32_t)PAGE_SIZE); + g_assert(pv_ipib_get_size(comps_length) == ipib_size); + + pv->pbt = IPL_TYPE_PV; + pv->len = GUINT32_TO_BE(blk0_len); + pv->num_comp = GUINT32_TO_BE(comps_length); + /* both values will be overwritten during the IPL process by + * the stage3a loader + */ + pv->pv_hdr_addr = GUINT64_TO_BE(0x0); + pv->pv_hdr_size = GUINT64_TO_BE(hdr->size); + + ipib->hdr.len = GUINT32_TO_BE(ipib_size); + ipib->hdr.version = IPL_PARM_BLOCK_VERSION; + + i = 0; + for (GSList *iterator = comps; iterator; iterator = iterator->next, i++) { + const PvComponent *comp = iterator->data; + uint64_t comp_addr, comp_size; + + g_assert(comp); + + comp_addr = pv_component_get_src_addr(comp); + comp_size = pv_component_size(comp); + + g_assert(IS_PAGE_ALIGNED(comp_size)); + + pv->components[i].addr = GUINT64_TO_BE(comp_addr); + pv->components[i].len = GUINT64_TO_BE(comp_size); + pv->components[i].tweak_pref = + GUINT64_TO_BE(pv_component_get_tweak_prefix(comp)); + if (i > 0) { + /* tweak prefixes of the components must grow + * strictly monotonous + */ + g_assert(GUINT64_FROM_BE(pv->components[i].tweak_pref) > + GUINT64_FROM_BE(pv->components[i - 1].tweak_pref)); + } + } + + return 0; +} + +IplParameterBlock *pv_ipib_new(GSList *comps, const Buffer *hdr, GError **err) +{ + uint64_t ipib_size = pv_ipib_get_size(g_slist_length(comps)); + g_autoptr(IplParameterBlock) ret = NULL; + + if (ipib_size > PV_V1_IPIB_MAX_SIZE) { + g_set_error(err, PV_ERROR, PV_ERROR_IPIB_SIZE, + _("IPIB size is too large: %lu < %lu"), ipib_size, + PAGE_SIZE); + return NULL; + } + + ret = g_malloc0(ipib_size); + if (pv_ipib_init(ret, comps, hdr) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + +void pv_ipib_free(IplParameterBlock *ipib) +{ + if (!ipib) + return; + + g_free(ipib); +} --- /dev/null +++ b/genprotimg/src/pv/pv_ipib.h @@ -0,0 +1,27 @@ +/* + * PV IPIB related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_IPIB_H +#define PV_IPIB_H + +#include <glib.h> +#include <stdint.h> + +#include "boot/ipl.h" +#include "utils/buffer.h" + +typedef struct ipl_parameter_block IplParameterBlock; + +uint64_t pv_ipib_get_size(uint32_t num_comp); +IplParameterBlock *pv_ipib_new(GSList *comps, const Buffer *hdr, GError **err); +void pv_ipib_free(IplParameterBlock *ipib); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(IplParameterBlock, pv_ipib_free) + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_opt_item.c @@ -0,0 +1,26 @@ +/* + * PV optional item related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> + +#include "pv_opt_item.h" + +uint32_t pv_opt_item_size(const struct pv_hdr_opt_item *item G_GNUC_UNUSED) +{ + /* not implemented yet */ + g_assert_not_reached(); +} + +void pv_opt_item_free(struct pv_hdr_opt_item *item) +{ + if (!item) + return; + + g_free(item); +} --- /dev/null +++ b/genprotimg/src/pv/pv_opt_item.h @@ -0,0 +1,20 @@ +/* + * PV optional item related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_OPT_ITEM_H +#define PV_OPT_ITEM_H + +#include <stdint.h> + +#include "include/pv_hdr_def.h" + +uint32_t pv_opt_item_size(const struct pv_hdr_opt_item *item); +void pv_opt_item_free(struct pv_hdr_opt_item *item); + +#endif --- /dev/null +++ b/genprotimg/src/pv/pv_stage3.c @@ -0,0 +1,164 @@ +/* + * PV stage3 loader related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> +#include <stdint.h> +#include <string.h> + +#include "boot/ipl.h" +#include "boot/stage3a.h" +#include "boot/stage3b.h" +#include "common.h" +#include "utils/align.h" + +#include "pv_error.h" +#include "pv_stage3.h" + +#define STAGE3A_ARGS(data_ptr, loader_size) \ + ((struct stage3a_args *)((uint64_t)data_ptr + loader_size - \ + sizeof(struct stage3a_args))) + +static Buffer *loader_getblob(const gchar *filename, gsize *loader_size, + gsize args_size, gsize data_size, + gboolean data_aligned, GError **err) +{ + g_autoptr(GMappedFile) mapped_file = NULL; + g_autoptr(Buffer) ret = NULL; + gsize size, tmp_loader_size; + gchar *loader_data; + + g_assert(loader_size); + + mapped_file = g_mapped_file_new(filename, FALSE, err); + if (!mapped_file) + return NULL; + + loader_data = g_mapped_file_get_contents(mapped_file); + if (!loader_data) { + g_set_error(err, G_FILE_ERROR, G_FILE_ERROR_BADF, + _("File '%s' is empty"), filename); + return NULL; + } + tmp_loader_size = g_mapped_file_get_length(mapped_file); + + if (tmp_loader_size < args_size) { + g_set_error(err, G_FILE_ERROR, G_FILE_ERROR_BADF, + _("File size less than expected: %lu < %ln"), + tmp_loader_size, loader_size); + return NULL; + } + + /* For example, the PV header and IPIB data must be page + * aligned. + */ + size = (data_aligned ? PAGE_ALIGN(tmp_loader_size) : tmp_loader_size) + + data_size; + + ret = buffer_alloc(size); + + /* copy the loader "template" */ + memcpy(ret->data, loader_data, tmp_loader_size); + /* reset our dummy data (offsets and length) to zeros */ + memset((uint8_t *)ret->data + tmp_loader_size - args_size, 0, + args_size); + *loader_size = tmp_loader_size; + return g_steal_pointer(&ret); +} + +Buffer *stage3a_getblob(const gchar *filename, gsize *loader_size, + gsize data_size, GError **err) +{ + return loader_getblob(filename, loader_size, + sizeof(struct stage3a_args), data_size, TRUE, + err); +} + +/* For the memory layout see stage3a.lds */ +/* Set the right offsets and sizes in the stage3a template + add + * the IPIB block with the PV header + */ +static gint stage3a_set_data(Buffer *loader, gsize loader_size, + const Buffer *hdr, struct ipl_parameter_block *ipib, + GError **err) +{ + uint32_t ipib_size = GUINT32_FROM_BE(ipib->hdr.len); + gsize args_size = sizeof(struct stage3a_args); + uint32_t hdr_size = (uint32_t)hdr->size; + uint64_t args_addr, next_data_addr; + + if (hdr->size > UINT32_MAX) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("Invalid header size: %zu"), hdr->size); + return -1; + } + + /* we assume here that the loader ``stage3a`` is loaded page + * aligned in the guest + */ + args_addr = (uint64_t)loader->data + loader_size - args_size; + + /* therefore `next_data_addr` is also page aligned */ + next_data_addr = (uint64_t)loader->data + PAGE_ALIGN(loader_size); + + /* copy IPIB data */ + memcpy((void *)next_data_addr, ipib, ipib_size); + + /* set IPIB offset in relation to the stage3a arguments */ + STAGE3A_ARGS(loader->data, loader_size)->ipib_offs = + GUINT64_TO_BE(next_data_addr - args_addr); + + next_data_addr = next_data_addr + PAGE_ALIGN(ipib_size); + /* copy PV header */ + memcpy((void *)next_data_addr, hdr->data, hdr_size); + /* set PV header size and offset in relation to the stage3a + * arguments + */ + STAGE3A_ARGS(loader->data, loader_size)->hdr_offs = + GUINT64_TO_BE(next_data_addr - args_addr); + STAGE3A_ARGS(loader->data, loader_size)->hdr_size = GUINT64_TO_BE(hdr_size); + + return 0; +} + +gint build_stage3a(Buffer *loader, gsize loader_size, const Buffer *hdr, + struct ipl_parameter_block *ipib, GError **err) +{ + return stage3a_set_data(loader, loader_size, hdr, ipib, err); +} + +Buffer *stage3b_getblob(const gchar *filename, GError **err) +{ + g_autoptr(Buffer) ret = NULL; + gsize rb_size; + + ret = loader_getblob(filename, &rb_size, sizeof(struct stage3b_args), 0, + FALSE, err); + if (!ret) + return NULL; + + g_assert(ret->size == rb_size); + return g_steal_pointer(&ret); +} + +void build_stage3b(Buffer *stage3b, const struct stage3b_args *args) +{ + g_assert(stage3b->size > sizeof(*args)); + + /* at the end of the stage3b there are the stage3b args + * positioned + */ + memcpy((uint8_t *)stage3b->data + stage3b->size - sizeof(*args), args, + sizeof(*args)); +} + +void memblob_init(struct memblob *arg, uint64_t src, uint64_t size) +{ + arg->src = GUINT64_TO_BE(src); + arg->size = GUINT64_TO_BE(size); +} --- /dev/null +++ b/genprotimg/src/pv/pv_stage3.h @@ -0,0 +1,30 @@ +/* + * PV stage3 loader related definitions and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_STAGE3_H +#define PV_STAGE3_H + +#include <glib.h> +#include <glib/gtypes.h> +#include <stdint.h> + +#include "boot/ipl.h" +#include "boot/s390.h" +#include "boot/stage3b.h" +#include "utils/buffer.h" + +Buffer *stage3a_getblob(const gchar *filename, gsize *loader_size, + gsize data_size, GError **err); +gint build_stage3a(Buffer *dc, gsize dc_size, const Buffer *hdr, + struct ipl_parameter_block *ipib, GError **err); +Buffer *stage3b_getblob(const gchar *filename, GError **err); +void build_stage3b(Buffer *stage3b, const struct stage3b_args *args); +void memblob_init(struct memblob *arg, uint64_t src, uint64_t size); + +#endif --- /dev/null +++ b/genprotimg/src/utils/align.h @@ -0,0 +1,24 @@ +/* + * Alignment utils + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_UTILS_ALIGN_H +#define PV_UTILS_ALIGN_H + +#include "boot/s390.h" +#include "lib/zt_common.h" + +#define IS_ALIGNED(addr, size) (!(addr & (size - 1))) + +/* align addr to the next page boundary */ +#define PAGE_ALIGN(addr) ALIGN((unsigned long)addr, PAGE_SIZE) + +/* test whether an address is aligned to PAGE_SIZE or not */ +#define IS_PAGE_ALIGNED(addr) IS_ALIGNED((unsigned long)(addr), PAGE_SIZE) + +#endif --- /dev/null +++ b/genprotimg/src/utils/buffer.c @@ -0,0 +1,69 @@ +/* + * Buffer functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <errno.h> +#include <glib.h> +#include <stdio.h> +#include <string.h> + +#include "align.h" +#include "buffer.h" +#include "common.h" +#include "file_utils.h" + +Buffer *buffer_alloc(gsize size) +{ + Buffer *ret = g_new0(Buffer, 1); + + ret->data = g_malloc0(size); + ret->size = size; + return ret; +} + +Buffer *buffer_dup(const Buffer *buf, gboolean page_aligned) +{ + Buffer *ret; + gsize size; + + if (!buf) + return NULL; + + size = buf->size; + if (page_aligned) + size = PAGE_ALIGN(size); + + ret = buffer_alloc(size); + + /* content will be 0-right-padded */ + memcpy(ret->data, buf->data, buf->size); + return ret; +} + +gint buffer_write(const Buffer *buf, FILE *file, GError **err) +{ + return file_write(file, buf->data, buf->size, 1, NULL, err); +} + +void buffer_free(Buffer *buf) +{ + if (!buf) + return; + + g_free(buf->data); + g_free(buf); +} + +void buffer_clear(Buffer **buf) +{ + if (!buf || !*buf) + return; + + buffer_free(*buf); + *buf = NULL; +} --- /dev/null +++ b/genprotimg/src/utils/buffer.h @@ -0,0 +1,31 @@ +/* + * Buffer definition and functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_UTILS_BUFFER_H +#define PV_UTILS_BUFFER_H + +#include <glib.h> +#include <stdio.h> + +#include "common.h" + +typedef struct Buffer { + void *data; + gsize size; /* in bytes */ +} Buffer; + +Buffer *buffer_alloc(gsize size); +void buffer_free(Buffer *buf); +void buffer_clear(Buffer **buf); +gint buffer_write(const Buffer *buf, FILE *file, GError **err); +Buffer *buffer_dup(const Buffer *buf, gboolean page_aligned); + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(Buffer, buffer_free) + +#endif --- /dev/null +++ b/genprotimg/src/utils/crypto.c @@ -0,0 +1,798 @@ +/* + * General cryptography helper functions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <glib.h> +#include <glib/gtypes.h> +#include <limits.h> +#include <openssl/aes.h> +#include <openssl/bn.h> +#include <openssl/ec.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/rand.h> +#include <stdint.h> +#include <string.h> + +#include "boot/s390.h" +#include "common.h" +#include "include/pv_crypto_def.h" +#include "pv/pv_error.h" + +#include "buffer.h" +#include "crypto.h" + +EVP_MD_CTX *digest_ctx_new(const EVP_MD *md, GError **err) +{ + g_autoptr(EVP_MD_CTX) ctx = EVP_MD_CTX_new(); + + if (!ctx) + g_abort(); + + if (EVP_DigestInit_ex(ctx, md, NULL) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestInit_ex failed")); + return NULL; + } + + return g_steal_pointer(&ctx); +} + +Buffer *digest_ctx_finalize(EVP_MD_CTX *ctx, GError **err) +{ + gint md_size = EVP_MD_size(EVP_MD_CTX_md(ctx)); + g_autoptr(Buffer) ret = NULL; + guint digest_size; + + g_assert(md_size > 0); + + ret = buffer_alloc((guint)md_size); + if (EVP_DigestFinal_ex(ctx, ret->data, &digest_size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestFinal_ex failed")); + return NULL; + } + + g_assert(digest_size == (guint)md_size); + g_assert(digest_size == ret->size); + return g_steal_pointer(&ret); +} + +/* Returns the digest of @buf using the hash algorithm @md */ +static Buffer *digest_buffer(const EVP_MD *md, const Buffer *buf, GError **err) +{ + g_autoptr(EVP_MD_CTX) md_ctx = NULL; + g_autoptr(Buffer) ret = NULL; + g_assert(buf); + + md_ctx = digest_ctx_new(md, err); + if (!md_ctx) + return NULL; + + if (EVP_DigestUpdate(md_ctx, buf->data, buf->size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_DigestUpdate failed")); + return NULL; + } + + ret = digest_ctx_finalize(md_ctx, err); + if (!ret) + return NULL; + + return g_steal_pointer(&ret); +} + +/* Returns the SHA256 digest of @buf */ +Buffer *sha256_buffer(const Buffer *buf, GError **err) +{ + g_autoptr(Buffer) ret = NULL; + + ret = digest_buffer(EVP_sha256(), buf, err); + if (!ret) + return NULL; + + g_assert(ret->size == SHA256_DIGEST_LENGTH); + return g_steal_pointer(&ret); +} + +/* Convert a EVP_PKEY to the key format used in the PV header */ +union ecdh_pub_key *evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **err) +{ + g_autofree union ecdh_pub_key *ret = g_new0(union ecdh_pub_key, 1); + g_autoptr(BIGNUM) pub_x_big = NULL; + g_autoptr(BIGNUM) pub_y_big = NULL; + g_autoptr(EC_KEY) ec_key = NULL; + const EC_POINT *pub_key; + const EC_GROUP *grp; + + ec_key = EVP_PKEY_get1_EC_KEY(key); + if (!ec_key) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Key has the wrong type")); + return NULL; + } + + pub_key = EC_KEY_get0_public_key(ec_key); + if (!pub_key) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Failed to get public key")); + return NULL; + } + + grp = EC_KEY_get0_group(ec_key); + if (!grp) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Failed to get EC group")); + return NULL; + } + + pub_x_big = BN_new(); + if (!pub_x_big) + g_abort(); + + pub_y_big = BN_new(); + if (!pub_y_big) + g_abort(); + + if (EC_POINT_get_affine_coordinates_GFp(grp, pub_key, pub_x_big, + pub_y_big, NULL) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Cannot convert key to internal format")); + return NULL; + } + + if (BN_bn2binpad(pub_x_big, ret->x, sizeof(ret->x)) < 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Cannot convert key to internal format")); + return NULL; + } + + if (BN_bn2binpad(pub_y_big, ret->y, sizeof(ret->y)) < 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Cannot convert key to internal format")); + return NULL; + } + + return g_steal_pointer(&ret); +} + +static Buffer *derive_key(EVP_PKEY *cust, EVP_PKEY *host, GError **err) +{ + g_autoptr(EVP_PKEY_CTX) ctx = NULL; + g_autoptr(Buffer) ret = NULL; + gsize key_size; + + ctx = EVP_PKEY_CTX_new(cust, NULL); + if (!ctx) + g_abort(); + + if (EVP_PKEY_derive_init(ctx) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Key derivation failed")); + return NULL; + } + + if (EVP_PKEY_derive_set_peer(ctx, host) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Key derivation failed")); + return NULL; + } + + /* Determine buffer length */ + if (EVP_PKEY_derive(ctx, NULL, &key_size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE, + _("Key derivation failed")); + return NULL; + } + + ret = buffer_alloc(key_size); + if (EVP_PKEY_derive(ctx, ret->data, &key_size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_DERIVE, + _("Key derivation failed")); + return NULL; + } + + g_assert(ret->size == key_size); + return g_steal_pointer(&ret); +} + +Buffer *compute_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **err) +{ + g_autoptr(Buffer) raw = buffer_alloc(70); + g_autoptr(Buffer) ret = NULL; + g_autoptr(Buffer) key = NULL; + guchar *data; + + key = derive_key(cust, host, err); + if (!key) + return NULL; + + g_assert(key->size == 66); + g_assert(key->size < raw->size); + + /* ANSI X.9.63-2011: 66 bytes x with leading 7 bits and + * concatenate 32 bit int '1' + */ + memcpy(raw->data, key->data, key->size); + data = raw->data; + data[66] = 0x00; + data[67] = 0x00; + data[68] = 0x00; + data[69] = 0x01; + + ret = sha256_buffer(raw, err); + if (!ret) + return NULL; + + return g_steal_pointer(&ret); +} + +gint generate_tweak(union tweak *tweak, uint16_t i, GError **err) +{ + tweak->cmp_idx.idx = GUINT16_TO_BE(i); + if (RAND_bytes(tweak->cmp_idx.rand, sizeof(tweak->cmp_idx.rand)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_RANDOMIZATION, + _("Generating a tweak failed because the required amount of random data is not available")); + return -1; + } + + return 0; +} + +static Buffer *generate_rand_data(guint size, const gchar *err_msg, + GError **err) +{ + g_autoptr(Buffer) buf = buffer_alloc(size); + + g_assert(size <= INT_MAX); + + if (RAND_bytes(buf->data, (int)size) != 1) { + g_set_error_literal(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_RANDOMIZATION, + err_msg); + return NULL; + } + + return g_steal_pointer(&buf); +} + +Buffer *generate_aes_iv(guint size, GError **err) +{ + return generate_rand_data(size, + _("Generating a IV failed because the required amount of random data is not available"), + err); +} + +Buffer *generate_aes_key(guint size, GError **err) +{ + return generate_rand_data(size, + _("Generating a key failed because the required amount of random data is not available"), + err); +} + +EVP_PKEY *generate_ec_key(gint nid, GError **err) +{ + g_autoptr(EVP_PKEY_CTX) ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + g_autoptr(EVP_PKEY) ret = NULL; + + if (!ctx) + g_abort(); + + if (EVP_PKEY_keygen_init(ctx) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, + _("EC key could not be auto-generated")); + return NULL; + } + + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, + _("EC key could not be auto-generated")); + return NULL; + } + + if (EVP_PKEY_keygen(ctx, &ret) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_KEYGENERATION, + _("EC key could not be auto-generated")); + return NULL; + } + + return g_steal_pointer(&ret); +} + +static gboolean certificate_uses_correct_curve(EVP_PKEY *key, gint nid, + GError **err) +{ + g_autoptr(EC_KEY) ec = NULL; + gint rc; + + g_assert(key); + + if (EVP_PKEY_id(key) != EVP_PKEY_EC) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("No EC key found")); + return FALSE; + } + + ec = EVP_PKEY_get1_EC_KEY(key); + if (!ec) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("No EC key found")); + return FALSE; + } + + if (EC_KEY_check_key(ec) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("Invalid EC key")); + return FALSE; + } + + rc = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + if (rc != nid) { + /* maybe the NID is unset */ + if (rc == 0) { + g_autoptr(EC_GROUP) grp = EC_GROUP_new_by_curve_name(nid); + const EC_POINT *pub = EC_KEY_get0_public_key(ec); + g_autoptr(BN_CTX) ctx = BN_CTX_new(); + + if (EC_POINT_is_on_curve(grp, pub, ctx) != 1) { + g_set_error_literal(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_PARM, + _("Invalid EC curve")); + return FALSE; + } + } else { + /* NID was set but doesn't match with the expected NID + */ + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_PARM, + _("Wrong NID used: '%d'"), + EC_GROUP_get_curve_name(EC_KEY_get0_group(ec))); + return FALSE; + } + } + + return TRUE; +} + +static gboolean verify_certificate(X509_STORE *store, X509 *cert, GError **err) +{ + g_autoptr(X509_STORE_CTX) csc = X509_STORE_CTX_new(); + if (!csc) + g_abort(); + + if (X509_STORE_CTX_init(csc, store, cert, NULL) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INIT, + _("Failed to initialize X.509 store")); + return FALSE; + } + + if (X509_verify_cert(csc) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_VERIFICATION, + _("Failed to verify host-key document")); + return FALSE; + } + + return TRUE; +} + +static X509 *load_certificate(const gchar *path, GError **err) +{ + g_autoptr(X509) ret = NULL; + g_autoptr(BIO) bio = BIO_new_file(path, "rb"); + + if (!bio) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to read host-key document: '%s'"), path); + return NULL; + } + + ret = PEM_read_bio_X509(bio, NULL, 0, NULL); + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to load host-key document: '%s'"), path); + return NULL; + } + + return g_steal_pointer(&ret); +} + +EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, + GError **err) +{ + g_autoptr(EVP_PKEY) ret = NULL; + g_autoptr(X509) cert = NULL; + + cert = load_certificate(path, err); + if (!cert) + return NULL; + + if (store && !verify_certificate(store, cert, err)) { + g_prefix_error(err, + _("Failed to load host-key document: '%s': "), + path); + return NULL; + } + + ret = X509_get_pubkey(cert); + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INVALID_PARM, + _("Failed to get public key from host-key document: '%s'"), + path); + return NULL; + } + + if (!certificate_uses_correct_curve(ret, nid, err)) { + g_prefix_error(err, + _("Failed to load host-key document: '%s': "), + path); + return NULL; + } + + return g_steal_pointer(&ret); +} + +static gint __encrypt_decrypt_bio(const struct cipher_parms *parms, BIO *b_in, + BIO *b_out, gsize *size_in, gsize *size_out, + gboolean encrypt, GError **err) +{ + gint num_bytes_read, num_bytes_written; + g_autoptr(EVP_CIPHER_CTX) ctx = NULL; + g_autoptr(BIGNUM) tweak_num = NULL; + const EVP_CIPHER *cipher = parms->cipher; + gint cipher_block_size = EVP_CIPHER_block_size(cipher); + guchar in_buf[PAGE_SIZE], + out_buf[PAGE_SIZE + (guint)cipher_block_size]; + const Buffer *key = parms->key; + const Buffer *tweak = parms->iv_or_tweak; + g_autofree guchar *tmp_tweak = NULL; + gint out_len, tweak_size; + gsize tmp_size_in = 0, tmp_size_out = 0; + + g_assert(cipher_block_size > 0); + g_assert(key); + g_assert(tweak); + g_assert(tweak->size <= INT_MAX); + + /* copy the value for leaving the original value untouched */ + tmp_tweak = g_malloc0(tweak->size); + memcpy(tmp_tweak, tweak->data, tweak->size); + tweak_size = (int)tweak->size; + tweak_num = BN_bin2bn(tmp_tweak, tweak_size, NULL); + if (!tweak_num) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("BN_bin2bn failed")); + return -1; + } + + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + g_abort(); + + /* don't set the key or tweak right away as we want to check + * lengths before + */ + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + + /* Now we can set the key and tweak */ + if (EVP_CipherInit_ex(ctx, NULL, NULL, key->data, tmp_tweak, encrypt) != + 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + + do { + memset(in_buf, 0, sizeof(in_buf)); + /* Read in data in 4096 bytes blocks. Update the ciphering + * with each read. + */ + num_bytes_read = BIO_read(b_in, in_buf, (int)PAGE_SIZE); + if (num_bytes_read < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Failed to read")); + return -1; + } + tmp_size_in += (guint)num_bytes_read; + + /* in case we reached the end and it's not the special + * case of an empty component we can break here + */ + if (num_bytes_read == 0 && tmp_size_in != 0) + break; + + if (EVP_CipherUpdate(ctx, out_buf, &out_len, in_buf, + sizeof(in_buf)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherUpdate failed")); + return -1; + } + g_assert(out_len >= 0); + + num_bytes_written = BIO_write(b_out, out_buf, out_len); + if (num_bytes_written < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Failed to write")); + return -1; + } + g_assert(num_bytes_written == out_len); + + tmp_size_out += (guint)num_bytes_written; + + /* Set new tweak value. Please keep in mind that the + * tweaks are stored in big-endian form. Therefore we + * must use the correct OpenSSL functions + */ + if (BN_add_word(tweak_num, PAGE_SIZE) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_add_word failed")); + } + g_assert(BN_num_bytes(tweak_num) > 0); + g_assert(BN_num_bytes(tweak_num) <= tweak_size); + + if (BN_bn2binpad(tweak_num, tmp_tweak, tweak_size) < 0) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("BN_bn2binpad failed")); + }; + + /* set new tweak */ + if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, tmp_tweak, + encrypt) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + } while (num_bytes_read == PAGE_SIZE); + + /* Now cipher the final block and write it out to file */ + if (EVP_CipherFinal_ex(ctx, out_buf, &out_len) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherFinal_ex failed")); + return -1; + } + g_assert(out_len >= 0); + + num_bytes_written = BIO_write(b_out, out_buf, out_len); + if (num_bytes_written < 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Failed to write")); + return -1; + } + g_assert(out_len == num_bytes_written); + tmp_size_out += (guint)out_len; + + if (BIO_flush(b_out) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Failed to flush")); + return -1; + } + + *size_in = tmp_size_in; + *size_out = tmp_size_out; + return 0; +} + +static Buffer *__encrypt_decrypt_buffer(const struct cipher_parms *parms, + const Buffer *in, gboolean encrypt, + GError **err) +{ + g_autoptr(Buffer) ret = NULL; + g_autoptr(BIO) b_out = NULL; + g_autoptr(BIO) b_in = NULL; + gsize in_size, out_size; + gchar *data = NULL; + long data_size; + + g_assert(in->size <= INT_MAX); + + b_in = BIO_new_mem_buf(in->data, (int)in->size); + if (!b_in) + g_abort(); + + b_out = BIO_new(BIO_s_mem()); + if (!b_out) + g_abort(); + + if (__encrypt_decrypt_bio(parms, b_in, b_out, &in_size, &out_size, + encrypt, err) < 0) + return NULL; + + data_size = BIO_get_mem_data(b_out, &data); + if (data_size < 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("Could not read buffer")); + return NULL; + } + + ret = buffer_alloc((unsigned long)data_size); + memcpy(ret->data, data, ret->size); + return g_steal_pointer(&ret); +} + +Buffer *encrypt_buf(const struct cipher_parms *parms, const Buffer *in, + GError **err) +{ + return __encrypt_decrypt_buffer(parms, in, TRUE, err); +} + +Buffer *decrypt_buf(const struct cipher_parms *parms, const Buffer *in, + GError **err) +{ + return __encrypt_decrypt_buffer(parms, in, FALSE, err); +} + +static gint __encrypt_decrypt_file(const struct cipher_parms *parms, + const gchar *path_in, const gchar *path_out, + gsize *size_in, gsize *size_out, gboolean encrypt, + GError **err) +{ + g_autoptr(BIO) b_out = NULL; + g_autoptr(BIO) b_in = NULL; + + b_in = BIO_new_file(path_in, "rb"); + if (!b_in) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to read file '%s'"), path_in); + return -1; + } + + b_out = BIO_new_file(path_out, "wb"); + if (!b_out) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("Failed to write file '%s'"), path_out); + return -1; + } + + if (__encrypt_decrypt_bio(parms, b_in, b_out, size_in, size_out, + encrypt, err) < 0) + return -1; + + return 0; +} + +gint encrypt_file(const struct cipher_parms *parms, const gchar *path_in, + const gchar *path_out, gsize *in_size, gsize *out_size, + GError **err) +{ + return __encrypt_decrypt_file(parms, path_in, path_out, in_size, + out_size, TRUE, err); +} + +G_GNUC_UNUSED static gint decrypt_file(const struct cipher_parms *parms, + const gchar *path_in, const gchar *path_out, + gsize *in_size, gsize *out_size, + GError **err) +{ + return __encrypt_decrypt_file(parms, path_in, path_out, in_size, + out_size, FALSE, err); +} + +/* GCM mode uses (zero-)padding */ +static int64_t gcm_encrypt_decrypt(const Buffer *in, const Buffer *aad, + const struct cipher_parms *parms, + Buffer *out, Buffer *tag, + enum PvCryptoMode mode, GError **err) +{ + g_autoptr(EVP_CIPHER_CTX) ctx = NULL; + const EVP_CIPHER *cipher = parms->cipher; + const Buffer *iv = parms->iv_or_tweak; + gboolean encrypt = mode == PV_ENCRYPT; + const Buffer *key = parms->key; + int64_t ret = -1; + gint len = -1; + + g_assert(cipher); + g_assert(key); + g_assert(iv); + /* Checks for later casts */ + g_assert(aad->size <= INT_MAX); + g_assert(in->size <= INT_MAX); + g_assert(tag->size <= INT_MAX); + g_assert(iv->size <= INT_MAX); + g_assert(out->size == in->size); + + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + g_abort(); + + /* First, set the cipher algorithm so we can verify our key/IV lengths + */ + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CIPHER_CTX_new failed")); + return -1; + } + + /* Set IV length */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)iv->size, NULL) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CIPHER_CTX_ex failed")); + return -1; + } + + /* Initialise key and IV */ + if (EVP_CipherInit_ex(ctx, NULL, NULL, key->data, iv->data, encrypt) != + 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherInit_ex failed")); + return -1; + } + + if (aad->size > 0) { + /* Provide any AAD data */ + if (EVP_CipherUpdate(ctx, NULL, &len, aad->data, + (int)aad->size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherUpdate failed")); + return -1; + } + g_assert(len == (int)aad->size); + } + + /* Provide data to be en/decrypted */ + if (EVP_CipherUpdate(ctx, out->data, &len, in->data, (int)in->size) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherUpdate failed")); + return -1; + } + ret = len; + + if (!encrypt) { + /* Set expected tag value */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, + (int)tag->size, tag->data) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Setting the GCM tag failed")); + return -1; + } + } + + /* Finalize the en/decryption */ + if (EVP_CipherFinal_ex(ctx, (guchar *)out->data + len, &len) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("EVP_CipherFinal_ex failed")); + return -1; + } + ret += len; + + if (encrypt) { + /* Get the tag */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, + (int)tag->size, tag->data) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("Getting the GCM tag failed")); + return -1; + } + } + + g_assert(ret == (int)in->size); + return ret; +} + +int64_t gcm_encrypt(const Buffer *in, const Buffer *aad, + const struct cipher_parms *parms, Buffer *out, Buffer *tag, + GError **err) +{ + return gcm_encrypt_decrypt(in, aad, parms, out, tag, PV_ENCRYPT, err); +} --- /dev/null +++ b/genprotimg/src/utils/crypto.h @@ -0,0 +1,104 @@ +/* + * General cryptography helper functions and definitions + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_UTILS_CRYPTO_H +#define PV_UTILS_CRYPTO_H + +#include <glib.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/ec.h> +#include <openssl/ecdh.h> +#include <openssl/evp.h> +#include <openssl/rand.h> +#include <openssl/sha.h> +#include <openssl/x509.h> +#include <stdint.h> + +#include "common.h" +#include "include/pv_crypto_def.h" +#include "lib/zt_common.h" + +#include "buffer.h" + +#define AES_256_GCM_IV_SIZE 12 +#define AES_256_GCM_TAG_SIZE 16 + +#define AES_256_XTS_TWEAK_SIZE 16 +#define AES_256_XTS_KEY_SIZE 64 + +enum PvCryptoMode { + PV_ENCRYPT, + PV_DECRYPT, +}; + +typedef GSList HostKeyList; + +/* Register auto cleanup functions */ +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIGNUM, BN_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BIO, BIO_free_all) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(BN_CTX, BN_CTX_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_GROUP, EC_GROUP_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_KEY, EC_KEY_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EC_POINT, EC_POINT_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_CIPHER_CTX, EVP_CIPHER_CTX_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_MD_CTX, EVP_MD_CTX_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_PKEY, EVP_PKEY_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(EVP_PKEY_CTX, EVP_PKEY_CTX_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509, X509_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_LOOKUP, X509_LOOKUP_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE, X509_STORE_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_STORE_CTX, X509_STORE_CTX_free) + +union cmp_index { + struct { + uint16_t idx; + guchar rand[6]; + } __packed; + uint64_t data; +}; + +/* The tweak is always stored in big endian format */ +union tweak { + struct { + union cmp_index cmp_idx; + uint64_t page_idx; /* page index */ + } __packed; + uint8_t data[AES_256_XTS_TWEAK_SIZE]; +}; + +struct cipher_parms { + const EVP_CIPHER *cipher; + const Buffer *key; + const Buffer *iv_or_tweak; +}; + +EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, + GError **err); +Buffer *compute_exchange_key(EVP_PKEY *cust, EVP_PKEY *host, GError **err); +Buffer *generate_aes_key(guint size, GError **err); +Buffer *generate_aes_iv(guint size, GError **err); +EVP_PKEY *generate_ec_key(gint nid, GError **err); +gint generate_tweak(union tweak *tweak, uint16_t i, GError **err); +union ecdh_pub_key *evp_pkey_to_ecdh_pub_key(EVP_PKEY *key, GError **err); +EVP_MD_CTX *digest_ctx_new(const EVP_MD *md, GError **err); +Buffer *digest_ctx_finalize(EVP_MD_CTX *ctx, GError **err); +Buffer *sha256_buffer(const Buffer *buf, GError **err); +int64_t gcm_encrypt(const Buffer *in, const Buffer *aad, + const struct cipher_parms *parms, Buffer *out, + Buffer *tag, GError **err); +gint encrypt_file(const struct cipher_parms *parms, const gchar *in_path, + const gchar *path_out, gsize *in_size, gsize *out_size, + GError **err); +Buffer *encrypt_buf(const struct cipher_parms *parms, const Buffer *in, + GError **err); +G_GNUC_UNUSED Buffer *decrypt_buf(const struct cipher_parms *parms, + const Buffer *in, GError **err); + +#endif --- /dev/null +++ b/genprotimg/src/utils/file_utils.c @@ -0,0 +1,234 @@ +/* + * General file utils + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include <errno.h> +#include <fcntl.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "pv/pv_error.h" + +#include "align.h" +#include "buffer.h" +#include "common.h" +#include "file_utils.h" + +FILE *file_open(const gchar *filename, const gchar *mode, GError **err) +{ + FILE *f = fopen(filename, mode); + + if (!f) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to open file '%s': %s"), filename, + g_strerror(errno)); + return NULL; + } + + return f; +} + +gint file_size(const gchar *filename, gsize *size, GError **err) +{ + GStatBuf st_buf; + + g_assert(size); + + if (g_stat(filename, &st_buf) != 0) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to get file status '%s': %s"), filename, + g_strerror(errno)); + return -1; + } + + if (!S_ISREG(st_buf.st_mode)) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("File '%s' is not a regular file"), filename); + return -1; + } + + if (st_buf.st_size < 0) { + g_set_error(err, G_FILE_ERROR, PV_ERROR_INTERNAL, + _("Invalid file size for '%s': %zu"), filename, + st_buf.st_size); + return -1; + } + + *size = (gsize)st_buf.st_size; + return 0; +} + +/* Returns 0 on success, otherwise -1. Stores the total number of + * elements successfully read in @count_read + */ +gint file_read(FILE *in, void *ptr, gsize size, gsize count, + gsize *count_read, GError **err) +{ + gsize tmp_count_read; + + tmp_count_read = fread(ptr, size, count, in); + if (count_read) + *count_read = tmp_count_read; + + if (ferror(in)) { + g_set_error(err, G_FILE_ERROR, 0, _("Failed to read file")); + return -1; + } + + return 0; +} + +gint file_write(FILE *out, const void *ptr, gsize size, gsize count, + gsize *count_written, GError **err) +{ + gsize tmp_count_written; + + tmp_count_written = fwrite(ptr, size, count, out); + if (count_written) + *count_written = tmp_count_written; + + if (tmp_count_written != count || ferror(out)) { + g_set_error(err, G_FILE_ERROR, 0, _("Failed to write file")); + return -1; + } + + return 0; +} + +static gint file_seek(FILE *f, uint64_t offset, GError **err) +{ + gint rc; + + if (offset > LONG_MAX) { + g_set_error(err, PV_ERROR, 0, _("Offset is too large")); + return -1; + } + + rc = fseek(f, (long)offset, SEEK_SET); + if (rc != 0) { + g_set_error(err, G_FILE_ERROR, + (gint)g_file_error_from_errno(errno), + _("Failed to seek: '%s'"), g_strerror(errno)); + return -1; + } + + return 0; +} + +gint seek_and_write_file(FILE *o, const CompFile *ifile, uint64_t offset, + GError **err) +{ + gsize bytes_read, bytes_written; + gsize total_bytes_read = 0; + FILE *i = NULL; + gchar buf[4096]; + gint ret = -1; + + if (file_seek(o, offset, err) < 0) + return -1; + + i = file_open(ifile->path, "rb", err); + if (!i) + return -1; + + do { + if (file_read(i, buf, 1, sizeof(buf), &bytes_read, err) < 0) { + g_prefix_error(err, _("Failed to read file '%s': "), + ifile->path); + goto err; + } + + if (bytes_read == 0) + break; + + total_bytes_read += bytes_read; + + if (file_write(o, buf, bytes_read, 1, &bytes_written, err) < 0) + goto err; + } while (bytes_written != 0); + + if (ifile->size != total_bytes_read) { + g_set_error(err, PV_ERROR, PV_ERROR_INTERNAL, + _("'%s' has changed during the preparation"), + ifile->path); + goto err; + } + + ret = 0; +err: + fclose(i); + return ret; +} + +gint seek_and_write_buffer(FILE *o, const Buffer *buf, uint64_t offset, + GError **err) +{ + if (file_seek(o, offset, err) < 0) + return -1; + + if (buffer_write(buf, o, err) < 0) + return -1; + + return 0; +} + +gint pad_file_right(const gchar *path_out, const gchar *path_in, gsize *size_out, + guint padding, GError **err) +{ + FILE *f_in, *f_out = NULL; + guchar buf[padding]; + gsize num_bytes_written; + gsize num_bytes_read; + uint64_t size_in = 0; + gint ret = -1; + + *size_out = 0; + f_in = file_open(path_in, "rb", err); + if (!f_in) + goto err; + + f_out = file_open(path_out, "wb", err); + if (!f_out) + goto err; + + do { + memset(buf, 0, sizeof(buf)); + + if (file_read(f_in, buf, 1, sizeof(buf), &num_bytes_read, err) < 0) { + g_prefix_error(err, _("Failed to read file '%s': "), + path_in); + goto err; + } + + size_in += num_bytes_read; + + if (file_write(f_out, buf, 1, sizeof(buf), &num_bytes_written, err)) { + g_prefix_error(err, _("Failed to write file '%s': "), + path_out); + goto err; + } + + *size_out += num_bytes_written; + } while (num_bytes_read == padding); + + g_assert(num_bytes_written == ALIGN(num_bytes_read, padding)); + + ret = 0; +err: + if (f_out) + fclose(f_out); + if (f_in) + fclose(f_in); + return ret; +} --- /dev/null +++ b/genprotimg/src/utils/file_utils.h @@ -0,0 +1,34 @@ +/* + * General file utils + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef PV_FILE_UTILS_H +#define PV_FILE_UTILS_H + +#include <glib.h> +#include <stdint.h> +#include <stdio.h> + +#include "pv/pv_comp.h" + +#include "buffer.h" + +FILE *file_open(const gchar *filename, const gchar *mode, GError **err); +gint file_size(const gchar *filename, gsize *size, GError **err); +gint file_read(FILE *in, void *ptr, gsize size, gsize count, + gsize *count_read, GError **err); +gint file_write(FILE *out, const void *ptr, gsize size, gsize count, + gsize *count_written, GError **err); +gint pad_file_right(const gchar *path_out, const gchar *path_in, + gsize *size_out, guint padding, GError **err); +gint seek_and_write_buffer(FILE *out, const Buffer *buf, uint64_t offset, + GError **err); +gint seek_and_write_file(FILE *o, const CompFile *ifile, uint64_t offset, + GError **err); + +#endif --- a/include/boot/ipl.h +++ b/include/boot/ipl.h @@ -18,6 +18,11 @@ #define IPL_RB_COMPONENT_FLAG_SIGNED 0x80 #define IPL_RB_COMPONENT_FLAG_VERIFIED 0x40 +#define IPL_PARM_BLOCK_VERSION 0x1 + +/* IPL Types */ +#define IPL_TYPE_PV 0x5 + #ifndef __ASSEMBLER__
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor