diff --git a/Makefile b/Makefile index 8a07a4db..f2a4680a 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,9 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \ 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) diff --git a/README.md b/README.md index 5d9c8876..977f3258 100644 --- 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: diff --git a/genprotimg/.gitignore b/genprotimg/.gitignore new file mode 100644 index 00000000..08318df8 --- /dev/null +++ b/genprotimg/.gitignore @@ -0,0 +1,5 @@ +tags +compile_commands.json +src/.check-dep-genprotimg +src/.detect-openssl.dep.c +src/genprotimg diff --git a/genprotimg/Makefile b/genprotimg/Makefile new file mode 100644 index 00000000..11e82783 --- /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) diff --git a/genprotimg/README.md b/genprotimg/README.md new file mode 100644 index 00000000..49521342 --- /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 | ++-----------------------+-----------+------------------------+ diff --git a/genprotimg/man/Makefile b/genprotimg/man/Makefile new file mode 100644 index 00000000..e60bab45 --- /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 diff --git a/genprotimg/man/genprotimg.8 b/genprotimg/man/genprotimg.8 new file mode 100644 index 00000000..597106e0 --- /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) diff --git a/genprotimg/src/Makefile b/genprotimg/src/Makefile new file mode 100644 index 00000000..1adeac35 --- /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 " > $@ + 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 $@ diff --git a/genprotimg/src/common.h b/genprotimg/src/common.h new file mode 100644 index 00000000..88c50e32 --- /dev/null +++ b/genprotimg/src/common.h @@ -0,0 +1,39 @@ +#ifndef COMMON_H +#define COMMON_H + +#define GETTEXT_PACKAGE "genprotimg" +#include +#include + +#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 diff --git a/genprotimg/src/genprotimg.c b/genprotimg/src/genprotimg.c new file mode 100644 index 00000000..0d825503 --- /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 +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/genprotimg/src/include/pv_crypto_def.h b/genprotimg/src/include/pv_crypto_def.h new file mode 100644 index 00000000..ddb8652d --- /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 + +#include "lib/zt_common.h" + +union ecdh_pub_key { + struct { + uint8_t x[80]; + uint8_t y[80]; + }; + uint8_t data[160]; +} __packed; + +#endif diff --git a/genprotimg/src/include/pv_hdr_def.h b/genprotimg/src/include/pv_hdr_def.h new file mode 100644 index 00000000..850ed267 --- /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 + +#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 diff --git a/genprotimg/src/pv/pv_args.c b/genprotimg/src/pv/pv_args.c new file mode 100644 index 00000000..9fb7298b --- /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 +#include + +#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); +} diff --git a/genprotimg/src/pv/pv_args.h b/genprotimg/src/pv/pv_args.h new file mode 100644 index 00000000..f17e7b85 --- /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 + +#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 diff --git a/genprotimg/src/pv/pv_comp.c b/genprotimg/src/pv/pv_comp.c new file mode 100644 index 00000000..1f64eea8 --- /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 +#include +#include +#include +#include +#include +#include + +#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(); +} diff --git a/genprotimg/src/pv/pv_comp.h b/genprotimg/src/pv/pv_comp.h new file mode 100644 index 00000000..aa1b5ae7 --- /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 +#include +#include + +#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 diff --git a/genprotimg/src/pv/pv_comps.c b/genprotimg/src/pv/pv_comps.c new file mode 100644 index 00000000..15d32f09 --- /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 +#include +#include +#include + +#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); +} diff --git a/genprotimg/src/pv/pv_comps.h b/genprotimg/src/pv/pv_comps.h new file mode 100644 index 00000000..d555e36f --- /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 +#include +#include + +#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 diff --git a/genprotimg/src/pv/pv_error.c b/genprotimg/src/pv/pv_error.c new file mode 100644 index 00000000..9d6f264c --- /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 + +#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"); +} diff --git a/genprotimg/src/pv/pv_error.h b/genprotimg/src/pv/pv_error.h new file mode 100644 index 00000000..1dd24fcb --- /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 + +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 diff --git a/genprotimg/src/pv/pv_hdr.c b/genprotimg/src/pv/pv_hdr.c new file mode 100644 index 00000000..45e721dd --- /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 +#include +#include +#include +#include +#include + +#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); +} diff --git a/genprotimg/src/pv/pv_hdr.h b/genprotimg/src/pv/pv_hdr.h new file mode 100644 index 00000000..8df7a6f1 --- /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 +#include + +#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 diff --git a/genprotimg/src/pv/pv_image.c b/genprotimg/src/pv/pv_image.c new file mode 100644 index 00000000..8e28df8f --- /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 +#include +#include +#include +#include +#include + +#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; +} diff --git a/genprotimg/src/pv/pv_image.h b/genprotimg/src/pv/pv_image.h new file mode 100644 index 00000000..7c624e24 --- /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 +#include +#include +#include + +#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 diff --git a/genprotimg/src/pv/pv_ipib.c b/genprotimg/src/pv/pv_ipib.c new file mode 100644 index 00000000..2517e548 --- /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 +#include +#include +#include + +#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); +} diff --git a/genprotimg/src/pv/pv_ipib.h b/genprotimg/src/pv/pv_ipib.h new file mode 100644 index 00000000..93317901 --- /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 +#include + +#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 diff --git a/genprotimg/src/pv/pv_opt_item.c b/genprotimg/src/pv/pv_opt_item.c new file mode 100644 index 00000000..39b9a8da --- /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 + +#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); +} diff --git a/genprotimg/src/pv/pv_opt_item.h b/genprotimg/src/pv/pv_opt_item.h new file mode 100644 index 00000000..34bb0a97 --- /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 + +#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 diff --git a/genprotimg/src/pv/pv_stage3.c b/genprotimg/src/pv/pv_stage3.c new file mode 100644 index 00000000..a1e5b164 --- /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 +#include +#include + +#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); +} diff --git a/genprotimg/src/pv/pv_stage3.h b/genprotimg/src/pv/pv_stage3.h new file mode 100644 index 00000000..baaf921b --- /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 +#include +#include + +#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 diff --git a/genprotimg/src/utils/align.h b/genprotimg/src/utils/align.h new file mode 100644 index 00000000..fa19e58f --- /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 diff --git a/genprotimg/src/utils/buffer.c b/genprotimg/src/utils/buffer.c new file mode 100644 index 00000000..35aed742 --- /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 +#include +#include +#include + +#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; +} diff --git a/genprotimg/src/utils/buffer.h b/genprotimg/src/utils/buffer.h new file mode 100644 index 00000000..7239d5c9 --- /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 +#include + +#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 diff --git a/genprotimg/src/utils/crypto.c b/genprotimg/src/utils/crypto.c new file mode 100644 index 00000000..b0c4aa92 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/genprotimg/src/utils/crypto.h b/genprotimg/src/utils/crypto.h new file mode 100644 index 00000000..34418ed3 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/genprotimg/src/utils/file_utils.c b/genprotimg/src/utils/file_utils.c new file mode 100644 index 00000000..1d6fc370 --- /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 +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/genprotimg/src/utils/file_utils.h b/genprotimg/src/utils/file_utils.h new file mode 100644 index 00000000..47df1148 --- /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 +#include +#include + +#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 diff --git a/include/boot/ipl.h b/include/boot/ipl.h index ab3f7c4f..6e6394b8 100644 --- 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__