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