From b67ac1d584e00c8c36c885a2fd36709a16809d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 7 Dec 2020 14:26:26 +0100 Subject: [PATCH 1/9] zdev/lsdasd: Add FC Endpoint Security information (#1723844) Summary: zdev/lsdasd: Add FC Endpoint Security information Description: Provide the status of the FC Endpoint Security information via the long output of lsdasd for online Base and Alias devices. Upstream-ID: d619b492e997cef0ddba8073f2a3073c7c2ecc5c Upstream-ID: 79e4798061a89152b967a0761f88f59742722b06 --- zconf/lsdasd | 7 +++++-- zdev/src/dasd.c | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/zconf/lsdasd b/zconf/lsdasd index 792efc0..795b33b 100755 --- a/zconf/lsdasd +++ b/zconf/lsdasd @@ -387,6 +387,7 @@ function extended() read EXTSZ 2> /dev/null < $DEVPATH/extent_pool/extent_size read CAPACITY 2> /dev/null < $DEVPATH/capacity/logical_capacity read ALLOCATED 2> /dev/null < $DEVPATH/capacity/space_allocated + read FC_SEC 2> /dev/null < $DEVPATH/fc_security # convert to hexadecimal values PIM=0x$PIM @@ -521,7 +522,7 @@ function extended() elif [[ "$ALIAS" == 1 ]]; then if [[ "$BASEONLY" == "false" ]]; then ACTIVE="alias" - printf "%s:%s:%s# status:\t\t\t\t%s# type: \t\t\t\t%s# use_diag:\t\t\t\t%s# readonly:\t\t\t\t%s# eer_enabled:\t\t\t\t%s# erplog:\t\t\t\t%s# hpf:\t\t\t\t\t%s # uid: \t\t\t\t%s# paths_installed: \t\t\t%s %s %s %s %s %s %s %s# paths_in_use: \t\t\t%s %s %s %s %s %s %s %s# paths_non_preferred: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_cabling: \t\t%s %s %s %s %s %s %s %s# paths_cuir_quiesced: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_hpf_characteristics: \t%s %s %s %s %s %s %s %s# paths_error_threshold_exceeded: \t%s %s %s %s %s %s %s %s#\n" \ + printf "%s:%s:%s# status:\t\t\t\t%s# type: \t\t\t\t%s# use_diag:\t\t\t\t%s# readonly:\t\t\t\t%s# eer_enabled:\t\t\t\t%s# erplog:\t\t\t\t%s# hpf:\t\t\t\t\t%s # uid: \t\t\t\t%s# fc_security: \t\t\t\t%s# paths_installed: \t\t\t%s %s %s %s %s %s %s %s# paths_in_use: \t\t\t%s %s %s %s %s %s %s %s# paths_non_preferred: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_cabling: \t\t%s %s %s %s %s %s %s %s# paths_cuir_quiesced: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_hpf_characteristics: \t%s %s %s %s %s %s %s %s# paths_error_threshold_exceeded: \t%s %s %s %s %s %s %s %s#\n" \ "$SORTKEYLEN" "$SORTKEY" \ "$BUSID" \ "$ACTIVE" \ @@ -532,6 +533,7 @@ function extended() "$ERP" \ "$HPF" \ "$DEV_UID" \ + "$FC_SEC" \ "${INSTALLED_PATHS[@]}" \ "${USED_PATHS[@]}" \ "${NP_PATHS[@]}" \ @@ -563,7 +565,7 @@ function extended() DISCIPLINE="${DISCIPLINE} (ESE)" fi - printf "%s:%s:%s/%s/%s%s%s# status:\t\t\t\t%s# type: \t\t\t\t%s# blksz:\t\t\t\t%s# size: \t\t\t\t%s# blocks:\t\t\t\t%s# extent_size:\t\t\t\t%s# logical_capacity:\t\t\t%s# space_allocated:\t\t\t%s# use_diag:\t\t\t\t%s# readonly:\t\t\t\t%s# eer_enabled:\t\t\t\t%s# erplog:\t\t\t\t%s# hpf:\t\t\t\t\t%s# uid: \t\t\t\t%s# paths_installed: \t\t\t%s %s %s %s %s %s %s %s# paths_in_use: \t\t\t%s %s %s %s %s %s %s %s# paths_non_preferred: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_cabling: \t\t%s %s %s %s %s %s %s %s# paths_cuir_quiesced: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_hpf_characteristics: \t%s %s %s %s %s %s %s %s# paths_error_threshold_exceeded: \t%s %s %s %s %s %s %s %s#\n" \ + printf "%s:%s:%s/%s/%s%s%s# status:\t\t\t\t%s# type: \t\t\t\t%s# blksz:\t\t\t\t%s# size: \t\t\t\t%s# blocks:\t\t\t\t%s# extent_size:\t\t\t\t%s# logical_capacity:\t\t\t%s# space_allocated:\t\t\t%s# use_diag:\t\t\t\t%s# readonly:\t\t\t\t%s# eer_enabled:\t\t\t\t%s# erplog:\t\t\t\t%s# hpf:\t\t\t\t\t%s# uid: \t\t\t\t%s# fc_security: \t\t\t\t%s# paths_installed: \t\t\t%s %s %s %s %s %s %s %s# paths_in_use: \t\t\t%s %s %s %s %s %s %s %s# paths_non_preferred: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_cabling: \t\t%s %s %s %s %s %s %s %s# paths_cuir_quiesced: \t\t\t%s %s %s %s %s %s %s %s# paths_invalid_hpf_characteristics: \t%s %s %s %s %s %s %s %s# paths_error_threshold_exceeded: \t%s %s %s %s %s %s %s %s#\n" \ "$SORTKEYLEN" "$SORTKEY" \ "$BUSID" \ "$BLOCKNAME" \ @@ -584,6 +586,7 @@ function extended() "$ERP" \ "$HPF" \ "$DEV_UID" \ + "$FC_SEC" \ "${INSTALLED_PATHS[@]}" \ "${USED_PATHS[@]}" \ "${NP_PATHS[@]}" \ diff --git a/zdev/src/dasd.c b/zdev/src/dasd.c index 645e8b0..63b49e3 100644 --- a/zdev/src/dasd.c +++ b/zdev/src/dasd.c @@ -313,6 +313,22 @@ static struct attrib dasd_attr_safe_offline = { .writeonly = 1, }; +static struct attrib dasd_attr_fc_security = { + .name = "fc_security", + .title = "Show FC Endpoint Security state of DASD device", + .desc = + "This read-only attribute shows the Fibre Channel Endpoint Security\n" + "status of the connection to the DASD device:\n" + " Unsupported : The DASD device does not support Fibre Channel\n" + " Endpoint Security\n" + " Inconsistent : The operational channel paths of the DASD device\n" + " report inconsistent Fibre Channel Endpoint\n" + " Security status\n" + " Authentication: The connection has been authenticated\n" + " Encryption : The connection is encrypted\n", + .readonly = 1, +}; + /* * DASD subtype methods. */ @@ -617,6 +633,7 @@ struct subtype dasd_subtype_eckd = { &dasd_attr_reservation_policy, &dasd_attr_last_known_reservation_state, &dasd_attr_safe_offline, + &dasd_attr_fc_security, &internal_attr_early, ), .unknown_dev_attribs = 1, -- 2.26.2 From 5f1a4aa22f2c294cd29d1e896c2629969c840dde Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Mon, 2 Nov 2020 14:30:58 +0100 Subject: [PATCH 2/9] genprotimg: abort if one of the recursive targets is failing (#1845925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Abort compilation as soon as one of the recursive targets is failing. Fixes: 65b9fc442c1a ("genprotimg: introduce new tool for the creation of PV images") Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner (cherry picked from commit 6db7fbe0187042f44a63a5c7dbeb9f116909d02e) --- genprotimg/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genprotimg/Makefile b/genprotimg/Makefile index 127bde2..4e81a63 100644 --- a/genprotimg/Makefile +++ b/genprotimg/Makefile @@ -21,7 +21,7 @@ clean: clean-recursive $(RECURSIVE_TARGETS): @target=`echo $@ |sed s/-recursive//`; \ for d in $(SUBDIRS); do \ - $(MAKE) -C $$d $$target; \ + $(MAKE) -C $$d $$target || exit 1; \ done .PHONY: all install clean $(RECURSIVE_TARGETS) -- 2.26.2 From 9bbcbb300168b1e246b5b02605e359e5bbe8dceb Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Mon, 2 Nov 2020 15:55:46 +0100 Subject: [PATCH 3/9] genprotimg: fix two memory leaks (#1845925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ==1005844== HEAP SUMMARY: ==1005844== in use at exit: 18,907 bytes in 14 blocks ==1005844== total heap usage: 82 allocs, 68 frees, 32,529 bytes allocated ==1005844== ==1005844== 136 (104 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record 12 of 14 ==1005844== at 0x483885A: calloc (vg_replace_malloc.c:760) ==1005844== by 0x48C950D: g_malloc0 (gmem.c:132) ==1005844== by 0x100EC41: pv_args_new (pv_args.c:364) ==1005844== by 0x100587F: main (genprotimg.c:122) ==1005844== ==1005844== LEAK SUMMARY: ==1005844== definitely lost: 104 bytes in 1 blocks ==1005844== indirectly lost: 32 bytes in 1 blocks ==1005844== possibly lost: 0 bytes in 0 blocks ==1005844== still reachable: 18,771 bytes in 12 blocks ==1005844== suppressed: 0 bytes in 0 blocks ==1005844== Reachable blocks (those to which a pointer was found) are not shown. ==1005844== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==1005844== ==1005844== For lists of detected and suppressed errors, rerun with: -s ==1005844== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner (cherry picked from commit db6f272607842a6279fee589fb101f3a1f6148f3) --- genprotimg/src/genprotimg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/genprotimg/src/genprotimg.c b/genprotimg/src/genprotimg.c index 0d82550..c6de381 100644 --- a/genprotimg/src/genprotimg.c +++ b/genprotimg/src/genprotimg.c @@ -177,5 +177,7 @@ error: rmdir_recursive(tmp_dir, NULL); remove_signal_handler(signals, G_N_ELEMENTS(signals)); g_free(tmp_dir); + g_clear_pointer(&img, pv_img_free); + g_clear_pointer(&args, pv_args_free); exit(ret); } -- 2.26.2 From 977d553fd81dbc5924f9b783231e1bacfe73ccd2 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Wed, 11 Nov 2020 22:30:12 +0100 Subject: [PATCH 4/9] genprotimg: require argument for 'ramdisk' and 'parmfile' options (#1845925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A argument is required for the optional options 'ramdisk' and 'parmfile'. Fixes: 65b9fc442c1a ("genprotimg: introduce new tool for the creation of PV images") Reviewed-by: Bjoern Walk Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner (cherry picked from commit 895a88b2f8d775e45ab1251f0b4bb275efd44a64) --- genprotimg/src/pv/pv_args.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/genprotimg/src/pv/pv_args.c b/genprotimg/src/pv/pv_args.c index 9fb7298..4bb78b5 100644 --- a/genprotimg/src/pv/pv_args.c +++ b/genprotimg/src/pv/pv_args.c @@ -227,7 +227,7 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], .arg_description = _("IMAGE") }, { .long_name = "ramdisk", .short_name = 'r', - .flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, + .flags = G_OPTION_FLAG_FILENAME, .arg = G_OPTION_ARG_CALLBACK, .arg_data = cb_add_component, .description = _("Use RAMDISK as the initial RAM disk\n" INDENT @@ -235,7 +235,7 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], .arg_description = _("RAMDISK") }, { .long_name = "parmfile", .short_name = 'p', - .flags = G_OPTION_FLAG_OPTIONAL_ARG | G_OPTION_FLAG_FILENAME, + .flags = G_OPTION_FLAG_FILENAME, .arg = G_OPTION_ARG_CALLBACK, .arg_data = cb_add_component, .description = _("Use the kernel parameters stored in PARMFILE\n" INDENT -- 2.26.2 From c17cdcf28fabb373884f51c08b93502999910035 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Mon, 2 Nov 2020 15:19:06 +0100 Subject: [PATCH 5/9] genprotimg: add host-key document verification support (#1845925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add host-key document verification support to genprotimg. This ensures that a host-key document is genuine and provided by IBM. For this the user must provide the IBM Z signing key, the intermediate CA certificate (signed by the root CA used) so a chain of trust starting from the host-key document and ending in the root CA can be established. By default, genprotimg tries to download all revocation lists needed by looking up in the corresponding certificate on how CRL information can be obtained (see https://tools.ietf.org/html/rfc5280#section-4.2.1.13 for details). Acked-by: Patrick Steuer Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner (cherry picked from commit 074de1e14ed785c18f55ecf9762ac3f5de3465b4) --- README.md | 2 +- genprotimg/man/genprotimg.8 | 49 +- genprotimg/src/Makefile | 17 +- genprotimg/src/genprotimg.c | 17 + genprotimg/src/include/pv_crypto_def.h | 18 + genprotimg/src/pv/pv_args.c | 57 +- genprotimg/src/pv/pv_args.h | 6 + genprotimg/src/pv/pv_error.h | 27 + genprotimg/src/pv/pv_image.c | 203 +++- genprotimg/src/utils/crypto.c | 1379 +++++++++++++++++++++++- genprotimg/src/utils/crypto.h | 75 +- genprotimg/src/utils/curl.c | 121 +++ genprotimg/src/utils/curl.h | 25 + 13 files changed, 1927 insertions(+), 69 deletions(-) create mode 100644 genprotimg/src/utils/curl.c create mode 100644 genprotimg/src/utils/curl.h diff --git a/README.md b/README.md index aa2188a..6fb6831 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ build options: | cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup | | json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb | | glib2 | `HAVE_GLIB2` | genprotimg | -| libcurl | `HAVE_LIBCURL` | libekmfweb | +| libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb | This table lists additional build or install options: diff --git a/genprotimg/man/genprotimg.8 b/genprotimg/man/genprotimg.8 index 597106e..c08336b 100644 --- a/genprotimg/man/genprotimg.8 +++ b/genprotimg/man/genprotimg.8 @@ -2,7 +2,7 @@ .\" 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" +.TH GENPROTIMG 8 "November 2020" "s390-tools" .SH NAME genprotimg \- Create a protected virtualization image @@ -10,6 +10,7 @@ genprotimg \- Create a protected virtualization image .SY .B genprotimg \fB\-k\fR \fIHOST_KEY_DOCUMENT\fR... +\fB\-C\fR \fICERTIFICATE\fR... \fB\-i\fR \fIVMLINUZ\fR [\fB\-r\fR \fIRAMDISK\fR] [\fB\-p\fR \fIPARMFILE\fR] @@ -21,15 +22,19 @@ genprotimg \- Create a protected virtualization image .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 +image, a host-key document, certificates for the host-key document +verification, and an output file name. Optionally, specify an initial +RAM filesystem, and a file containing the kernel parameters. If the +command should be run offline, use the \fB\-\-offline\fR option and +specify the certificate revocation lists (CRLs) by using the +\fB\-\-crl\fR option. 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. +experimental options. For all certificates, CRLs, and host-key +documents, both the PEM and DER input formats are supported. 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. @@ -53,6 +58,12 @@ 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\-C\fR, \fB\-\-cert\fR=\fI\,FILE\/\fR +Specifies the certificate that is used to establish a chain of trust +for the verification of the host-key documents. Specify this option +twice to specify the IBM Z signing key and the intermediate CA +certificate (signed by the root CA). Required. +.TP \fB\-o\fR, \fB\-\-output\fR=\fI\,OUTPUT_FILE\/\fR Specifies the output file. Required. .TP @@ -65,6 +76,20 @@ Specifies the RAM disk image. Optional. \fB\-p\fR, \fB\-\-parmfile\fR=\fI\,PARMFILE\/\fR Specifies the kernel command line stored in \fI\,PARMFILE\/\fR. Optional. .TP +\fB\-\-crl\fR=\fI\,FILE\/\fR +Specifies the revocation list that is used to check whether a +certificate of the chain of trust is revoked. Specify this option +multiple times to use multiple CRLs. Optional. +.TP +\fB\-\-offline\fR +Specifies offline mode, in which no attempt is made to download +CRLs. Optional. +.TP +\fB\-\-root\-ca\fR=\fI\,FILE\/\fR +Specifies the root CA certificate for the verification. If omitted, +the DigiCert root CA certificate installed on the system is used. Use +this only if you trust the specified certificate. 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. @@ -77,11 +102,13 @@ Prints version information, then exits. 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: +\fI\,parmfile\/\fR, the intermediate CA in \fI\,DigiCertCA.crt\/\fR, +the IBM Z signing key in \fI\,ibm-z-host-key-signing.crt\/\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 +\& genprotimg \-i \fI\,vmlinuz\/\fR \-r \fI\,initramfs\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt \-o \fI\,/boot/vmlinuz.pv\/\fR .EE .Ve .PP diff --git a/genprotimg/src/Makefile b/genprotimg/src/Makefile index 1adeac3..2da15da 100644 --- a/genprotimg/src/Makefile +++ b/genprotimg/src/Makefile @@ -23,16 +23,16 @@ WARNINGS := -Wall -Wextra -Wshadow \ $(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 \ + pv/pv_opt_item.c utils/curl.c \ $(NULL) $(bin_PROGRAM)_OBJS := $($(bin_PROGRAM)_SRCS:.c=.o) ALL_CFLAGS += -std=gnu11 -DPKGDATADIR=$(PKGDATADIR) \ - $(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) \ + $(GLIB2_CFLAGS) $(LIBCRYPTO_CFLAGS) $(LIBCURL_CFLAGS) \ $(WARNINGS) \ $(NULL) ALL_CPPFLAGS += $(INCLUDE_PARMS) -LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) +LDLIBS += $(GLIB2_LIBS) $(LIBCRYPTO_LIBS) $(LIBCURL_LIBS) ifneq ($(shell sh -c 'command -v pkg-config'),) @@ -40,21 +40,27 @@ 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) +LIBCURL_CFLAGS := $(shell pkg-config --silence-errors --cflags libcurl) +LIBCURL_LIBS := $(shell pkg-config --silence-errors --libs libcurl) 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 +LIBCURL_CFLAGS := +LIBCURL_LIBS := -lcurl endif BUILD_TARGETS := skip-$(bin_PROGRAM) INSTALL_TARGETS := skip-$(bin_PROGRAM) ifneq (${HAVE_OPENSSL},0) ifneq (${HAVE_GLIB2},0) +ifneq (${HAVE_LIBCURL},0) BUILD_TARGETS := $(bin_PROGRAM) INSTALL_TARGETS := install-$(bin_PROGRAM) endif endif +endif all: $(BUILD_TARGETS) @@ -98,4 +104,9 @@ $($(bin_PROGRAM)_OBJS): .check-dep-$(bin_PROGRAM) "openssl-devel / libssl-dev version >= 1.1.0", \ "HAVE_OPENSSL=0", \ "-I.") + $(call check_dep, \ + "$(bin_PROGRAM)", \ + "curl/curl.h", \ + "libcurl-devel", \ + "HAVE_LIBCURL=0") touch $@ diff --git a/genprotimg/src/genprotimg.c b/genprotimg/src/genprotimg.c index c6de381..2041fa2 100644 --- a/genprotimg/src/genprotimg.c +++ b/genprotimg/src/genprotimg.c @@ -18,6 +18,8 @@ #include "common.h" #include "pv/pv_args.h" #include "pv/pv_image.h" +#include "utils/crypto.h" +#include "utils/curl.h" enum { LOG_LEVEL_CRITICAL = 0, @@ -117,6 +119,8 @@ static void remove_signal_handler(const gint *signals, const gsize signals_n) signal(signals[i], SIG_DFL); } +static void __attribute__((constructor)) __init(void); +static void __attribute__((destructor)) __cleanup(void); gint main(gint argc, gchar *argv[]) { g_autoptr(PvArgs) args = pv_args_new(); @@ -181,3 +185,16 @@ error: g_clear_pointer(&args, pv_args_free); exit(ret); } + +static void __init(void) +{ + pv_crypto_init(); + if (curl_init() != 0) + g_abort(); +} + +static void __cleanup(void) +{ + curl_cleanup(); + pv_crypto_cleanup(); +} diff --git a/genprotimg/src/include/pv_crypto_def.h b/genprotimg/src/include/pv_crypto_def.h index ddb8652..53984a3 100644 --- a/genprotimg/src/include/pv_crypto_def.h +++ b/genprotimg/src/include/pv_crypto_def.h @@ -14,6 +14,24 @@ #include "lib/zt_common.h" +/* IBM signing key subject */ +#define PV_IBM_Z_SUBJECT_COMMON_NAME "International Business Machines Corporation" +#define PV_IBM_Z_SUBJECT_COUNTRY_NAME "US" +#define PV_IBM_Z_SUBJECT_LOCALITY_NAME "Poughkeepsie" +#define PV_IBM_Z_SUBJECT_ORGANIZATIONONAL_UNIT_NAME_SUFFIX "Key Signing Service" +#define PV_IBM_Z_SUBJECT_ORGANIZATION_NAME "International Business Machines Corporation" +#define PV_IBM_Z_SUBJECT_STATE "New York" +#define PV_IMB_Z_SUBJECT_ENTRY_COUNT 6 + +/* Minimum security level for the keys/certificates used to establish a chain of + * trust (see https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_auth_level.html + * for details). + */ +#define PV_CERTS_SECURITY_LEVEL 2 + +/* SKID for DigiCert Assured ID Root CA */ +#define DIGICERT_ASSURED_ID_ROOT_CA_SKID "45EBA2AFF492CB82312D518BA7A7219DF36DC80F" + union ecdh_pub_key { struct { uint8_t x[80]; diff --git a/genprotimg/src/pv/pv_args.c b/genprotimg/src/pv/pv_args.c index 4bb78b5..3ba1f94 100644 --- a/genprotimg/src/pv/pv_args.c +++ b/genprotimg/src/pv/pv_args.c @@ -18,7 +18,9 @@ static gchar summary[] = "Use genprotimg to create a protected virtualization kernel image file,\n" - "which can be loaded using zipl or QEMU."; + "which can be loaded using zipl or QEMU. For all certificates, revocation\n" + "lists, and host-key documents, both the PEM and DER input formats are\n" + "supported."; static gint pv_arg_compare(gconstpointer arg_1, gconstpointer arg_2) { @@ -97,9 +99,14 @@ static gint pv_args_validate_options(PvArgs *args, GError **err) 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.")); + if (!args->no_verify && + (!args->untrusted_cert_paths || + g_strv_length(args->untrusted_cert_paths) == 0)) { + g_set_error( + err, PV_PARSE_ERROR, PR_PARSE_ERROR_MISSING_ARGUMENT, + _("Either specify the IBM Z signing key and (DigiCert) intermediate CA certificate\n" + "by using the '--cert' option, or use the '--no-verify' flag to disable the\n" + "host-key document verification completely (at your own risk).")); return -1; } @@ -141,6 +148,8 @@ static gboolean cb_set_string_option(const gchar *option, const gchar *value, { gchar **args_option = NULL; + if (g_str_equal(option, "--root-ca")) + args_option = &args->root_ca_path; if (g_str_equal(option, "-o") || g_str_equal(option, "--output")) args_option = &args->output_path; if (g_str_equal(option, "--x-comp-key")) @@ -211,6 +220,18 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], _("FILE specifies a host-key document. At least\n" INDENT "one is required."), .arg_description = _("FILE") }, + { .long_name = "cert", + .short_name = 'C', + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_FILENAME_ARRAY, + .arg_data = &args->untrusted_cert_paths, + .description = _( + "FILE contains a certificate that is used to\n" INDENT + "establish a chain of trust for the verification\n" INDENT + "of the host-key documents. The IBM Z signing\n" INDENT + "key and intermediate CA certificate (signed\n" INDENT + "by the root CA) are required."), + .arg_description = _("FILE") }, { .long_name = "output", .short_name = 'o', .flags = G_OPTION_FLAG_FILENAME, @@ -241,6 +262,31 @@ gint pv_args_parse_options(PvArgs *args, gint *argc, gchar **argv[], .description = _("Use the kernel parameters stored in PARMFILE\n" INDENT "(optional)."), .arg_description = _("PARMFILE") }, + { .long_name = "crl", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_FILENAME_ARRAY, + .arg_data = &args->crl_paths, + .description = _( + "FILE contains a certificate revocation list\n" INDENT + "(optional)."), + .arg_description = _("FILE") }, + { .long_name = "offline", + .short_name = 0, + .flags = G_OPTION_FLAG_NONE, + .arg = G_OPTION_ARG_NONE, + .arg_data = &args->offline, + .description = _("Don't download CRLs (optional)."), + .arg_description = NULL }, + { .long_name = "root-ca", + .short_name = 0, + .flags = G_OPTION_FLAG_FILENAME, + .arg = G_OPTION_ARG_CALLBACK, + .arg_data = cb_set_string_option, + .description = _( + "Set FILE as the trusted root CA and don't use the\n" INDENT + "root CAs that are installed on the system (optional)."), + .arg_description = _("FILE") }, { .long_name = "no-verify", .short_name = 0, .flags = G_OPTION_FLAG_NONE, @@ -378,6 +424,9 @@ void pv_args_free(PvArgs *args) g_free(args->cust_root_key_path); g_free(args->cust_comm_key_path); g_free(args->gcm_iv_path); + g_free(args->root_ca_path); + g_strfreev(args->crl_paths); + g_strfreev(args->untrusted_cert_paths); g_strfreev(args->host_keys); g_free(args->xts_key_path); g_slist_free_full(args->comps, (GDestroyNotify)pv_arg_free); diff --git a/genprotimg/src/pv/pv_args.h b/genprotimg/src/pv/pv_args.h index f17e7b8..8939232 100644 --- a/genprotimg/src/pv/pv_args.h +++ b/genprotimg/src/pv/pv_args.h @@ -25,6 +25,7 @@ void pv_arg_free(PvArg *arg); typedef struct { gint log_level; gint no_verify; + gboolean offline; gchar *pcf; gchar *scf; gchar *psw_addr; /* PSW address which will be used for the start of @@ -34,6 +35,11 @@ typedef struct { gchar *cust_comm_key_path; gchar *gcm_iv_path; gchar **host_keys; + gchar *root_ca_path; /* Trusted root CA used for the verification of the + * chain of trust (if specified). + */ + gchar **untrusted_cert_paths; + gchar **crl_paths; gchar *xts_key_path; GSList *comps; gchar *output_path; diff --git a/genprotimg/src/pv/pv_error.h b/genprotimg/src/pv/pv_error.h index 1dd24fc..abe47ae 100644 --- a/genprotimg/src/pv/pv_error.h +++ b/genprotimg/src/pv/pv_error.h @@ -28,6 +28,8 @@ typedef enum { PV_ERROR_IPIB_SIZE, PV_ERROR_PV_HDR_SIZE, PV_ERROR_INTERNAL, + PV_ERROR_CURL_INIT_FAILED, + PV_ERROR_DOWNLOAD_FAILED, } PvErrors; typedef enum { @@ -57,6 +59,31 @@ typedef enum { PV_CRYPTO_ERROR_RANDOMIZATION, PV_CRYPTO_ERROR_INVALID_PARM, PV_CRYPTO_ERROR_INVALID_KEY_SIZE, + PV_CRYPTO_ERROR_INVALID_VALIDITY_PERIOD, + PV_CRYPTO_ERROR_EXPIRED, + PV_CRYPTO_ERROR_NOT_VALID_YET, + PV_CRYPTO_ERROR_LOAD_CRL, + PV_CRYPTO_ERROR_NO_PUBLIC_KEY, + PV_CRYPTO_ERROR_INVALID_SIGNATURE_ALGORITHM, + PV_CRYPTO_ERROR_SIGNATURE_ALGORITHM_MISMATCH, + PV_CRYPTO_ERROR_INVALID_URI, + PV_CRYPTO_ERROR_CRL_DOWNLOAD_FAILED, + PV_CRYPTO_ERROR_CERT_SIGNATURE_INVALID, + PV_CRYPTO_ERROR_CRL_SIGNATURE_INVALID, + PV_CRYPTO_ERROR_CERT_SUBJECT_ISSUER_MISMATCH, + PV_CRYPTO_ERROR_CRL_SUBJECT_ISSUER_MISMATCH, + PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, + PV_CRYPTO_ERROR_MALFORMED_CERTIFICATE, + PV_CRYPTO_ERROR_NO_CRL, + PV_CRYPTO_ERROR_LOAD_ROOT_CA, + PV_CRYPTO_ERROR_LOAD_DEFAULT_CA, + PV_CRYPTO_ERROR_MALFORMED_ROOT_CA, + PV_CRYPTO_ERROR_WRONG_CA_USED, + PV_CRYPTO_ERROR_SKID_AKID_MISMATCH, + PV_CRYPTO_ERROR_NO_ISSUER_IBM_Z_FOUND, + PV_CRYPTO_ERROR_FAILED_DOWNLOAD_CRL, + PV_CRYPTO_ERROR_NO_CRLDP, + PV_CRYPTO_ERROR_CERT_REVOKED, } PvCryptoErrors; #endif diff --git a/genprotimg/src/pv/pv_image.c b/genprotimg/src/pv/pv_image.c index 7ec5fe9..59eca5e 100644 --- a/genprotimg/src/pv/pv_image.c +++ b/genprotimg/src/pv/pv_image.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -138,22 +139,18 @@ 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, +static HostKeyList *pv_img_get_host_keys(GSList *host_keys_with_path, 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++) { + for (GSList *iterator = host_keys_with_path; iterator; + iterator = iterator->next) { + x509_with_path *cert_with_path = iterator->data; g_autoptr(EVP_PKEY) host_key = NULL; - const gchar *path = *iterator; - - g_assert(path); + X509 *cert = cert_with_path->cert; - host_key = read_ec_pubkey_cert(store, nid, path, err); + host_key = read_ec_pubkey_cert(cert, nid, err); if (!host_key) return NULL; @@ -253,10 +250,172 @@ static gint pv_img_set_control_flags(PvImage *img, const gchar *pcf_s, return 0; } +static gint pv_img_hostkey_verify(GSList *host_key_certs, + const gchar *root_ca_path, + const gchar *const *crl_paths, + const gchar *const *untrusted_cert_paths, + gboolean offline, GError **err) +{ + g_autoslist(x509_with_path) untrusted_certs_with_path = NULL; + g_autoptr(STACK_OF_X509) ibm_signing_certs = NULL; + g_autoptr(STACK_OF_X509) untrusted_certs = NULL; + g_autoslist(x509_pair) ibm_z_pairs = NULL; + g_autoptr(X509_STORE) trusted = NULL; + gint ibm_signing_certs_count; + + /* Load trusted root CAs of the system if and only if @root_ca_path is + * NULL, otherwise use the root CA specified by @root_ca_path. + */ + trusted = store_setup(root_ca_path, crl_paths, err); + if (!trusted) + goto error; + + if (!offline) { + g_autoptr(STACK_OF_X509_CRL) downloaded_ibm_signing_crls = NULL; + + /* Set up the download routine for the lookup of CRLs. */ + store_setup_crl_download(trusted); + + /* Try to download the CRLs of the IBM Z signing certificates + * specified in the host-key documents. Ignore download errors + * as it's still possible that a CRL is specified via command + * line. + */ + downloaded_ibm_signing_crls = try_load_crls_by_certs(host_key_certs); + + /* Add the downloaded CRLs to the store so they can be used for + * the verification later. + */ + for (int i = 0; i < sk_X509_CRL_num(downloaded_ibm_signing_crls); i++) { + X509_CRL *crl = sk_X509_CRL_value(downloaded_ibm_signing_crls, i); + + if (X509_STORE_add_crl(trusted, crl) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("failed to load CRL")); + goto error; + } + } + } + + /* Load all untrusted certificates (e.g. IBM Z signing key and + * DigiCert intermediate CA) that are required to establish a chain of + * trust starting from the host-key document up to the root CA (if not + * otherwise specified that's the DigiCert Assured ID Root CA). + */ + untrusted_certs_with_path = load_certificates(untrusted_cert_paths, err); + if (!untrusted_certs_with_path) + goto error; + + /* Convert to STACK_OF(X509) */ + untrusted_certs = get_x509_stack(untrusted_certs_with_path); + + /* Find all IBM Z signing keys and remove them from the chain as we + * have to verify that they're valid. The last step of the chain of + * trust verification must be done manually, as the IBM Z signing keys + * are not marked as (intermediate) CA and therefore the standard + * `X509_verify_cert` function of OpenSSL cannot be used to verify the + * actual host-key documents. + */ + ibm_signing_certs = delete_ibm_signing_certs(untrusted_certs); + ibm_signing_certs_count = sk_X509_num(ibm_signing_certs); + if (ibm_signing_certs_count < 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, + _("please specify at least one IBM Z signing key")); + goto error; + } else if (ibm_signing_certs_count > 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_IBM_Z_SIGNING_KEY, + _("please specify only one IBM Z signing key")); + goto error; + } + + if (store_set_verify_param(trusted, err) < 0) + goto error; + + /* Verify that the IBM Z signing keys are trustable. + * For this we must check: + * + * 1. Can a chain of trust be established ending in a root CA + * 2. Is the correct root CA ued? It has either to be the + * 'DigiCert Assured ID Root CA' or the root CA specified via + * command line. + */ + for (gint i = 0; i < sk_X509_num(ibm_signing_certs); ++i) { + X509 *ibm_signing_cert = sk_X509_value(ibm_signing_certs, i); + g_autoptr(STACK_OF_X509_CRL) ibm_signing_crls = NULL; + g_autoptr(X509_STORE_CTX) ctx = NULL; + x509_pair *pair = NULL; + + g_assert(ibm_signing_cert); + + /* Create the verification context and set the trusted + * and chain parameters. + */ + ctx = create_store_ctx(trusted, untrusted_certs, err); + if (!ctx) + goto error; + + /* Verify the IBM Z signing key */ + if (verify_cert(ibm_signing_cert, ctx, err) < 0) + goto error; + + /* Verify the build chain of trust chain. If the user passes a + * trusted root CA on the command line then the check for the + * Subject Key Identifier (SKID) is skipped, otherwise let's + * check if the SKID meets our expectation. + */ + if (!root_ca_path && + check_chain_parameters(X509_STORE_CTX_get0_chain(ctx), + get_digicert_assured_id_root_ca_skid(), + err) < 0) { + goto error; + } + + ibm_signing_crls = store_ctx_find_valid_crls(ctx, ibm_signing_cert, err); + if (!ibm_signing_crls) { + g_prefix_error(err, _("IBM Z signing key: ")); + goto error; + } + + /* Increment reference counter of @ibm_signing_cert as the + * certificate will now also be owned by @ibm_z_pairs. + */ + if (X509_up_ref(ibm_signing_cert) != 1) + g_abort(); + + pair = x509_pair_new(&ibm_signing_cert, &ibm_signing_crls); + ibm_z_pairs = g_slist_append(ibm_z_pairs, pair); + g_assert(!ibm_signing_cert); + g_assert(!ibm_signing_crls); + } + + /* Verify host-key documents by using the IBM Z signing + * certificates and the corresponding certificate revocation + * lists. + */ + for (GSList *iterator = host_key_certs; iterator; iterator = iterator->next) { + x509_with_path *host_key_with_path = iterator->data; + const gchar *host_key_path = host_key_with_path->path; + X509 *host_key = host_key_with_path->cert; + gint flags = X509_V_FLAG_CRL_CHECK; + + if (verify_host_key(host_key, ibm_z_pairs, flags, + PV_CERTS_SECURITY_LEVEL, err) < 0) { + g_prefix_error(err, "'%s': ", host_key_path); + goto error; + } + } + + return 0; +error: + g_prefix_error(err, _("Failed to verify host-key document: ")); + return -1; +} + /* 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_autoslist(x509_with_path) host_key_certs = NULL; g_assert(img->xts_cipher); g_assert(img->cust_comm_cipher); @@ -285,8 +444,25 @@ static gint pv_img_set_keys(PvImage *img, const PvArgs *args, GError **err) if (!img->cust_pub_priv_key) return -1; + /* Load all host-key documents specified on the command line */ + host_key_certs = load_certificates((const gchar **)args->host_keys, + err); + if (!host_key_certs) + return -1; + + if (!args->no_verify && + pv_img_hostkey_verify(host_key_certs, args->root_ca_path, + (const gchar * const *)args->crl_paths, + (const gchar * const *)args->untrusted_cert_paths, + args->offline, err) < 0) { + return -1; + } + + /* Loads the public keys stored in the host-key documents and verify + * that the correct elliptic curve is used. + */ img->host_pub_keys = - pv_img_get_host_keys(args->host_keys, store, img->nid, err); + pv_img_get_host_keys(host_key_certs, img->nid, err); if (!img->host_pub_keys) return -1; @@ -406,6 +582,9 @@ PvImage *pv_img_new(PvArgs *args, const gchar *stage3a_path, GError **err) if (args->no_verify) g_warning(_("host-key document verification is disabled. Your workload is not secured.")); + if (args->root_ca_path) + g_warning(_("A different root CA than the default DigiCert root CA is selected. Ensure that this root CA is trusted.")); + ret->comps = pv_img_comps_new(EVP_sha512(), EVP_sha512(), EVP_sha512(), err); if (!ret->comps) return NULL; diff --git a/genprotimg/src/utils/crypto.c b/genprotimg/src/utils/crypto.c index b0c4aa9..0f774eb 100644 --- a/genprotimg/src/utils/crypto.c +++ b/genprotimg/src/utils/crypto.c @@ -16,6 +16,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -25,8 +30,49 @@ #include "pv/pv_error.h" #include "buffer.h" +#include "curl.h" #include "crypto.h" +#define DEFINE_GSLIST_MAP(t2, t1) \ + typedef t1 *(*g_slist_map_func_##t2##_##t1)(const t2 *x, \ + GError **err); \ + G_GNUC_UNUSED static GSList *g_slist_map_##t2##_##t1(const GSList *list, \ + g_slist_map_func_##t2##_##t1 func, \ + GError **err) \ + { \ + g_autoslist(t1) ret = NULL; \ + for (const GSList *iterator = list; iterator; \ + iterator = iterator->next) { \ + const t2 *value = iterator->data; \ + t1 *new_value = NULL; \ + g_assert(value); \ + new_value = func(value, err); \ + if (!new_value) \ + return NULL; \ + ret = g_slist_append(ret, g_steal_pointer(&new_value)); \ + } \ + return g_steal_pointer(&ret); \ + } + +#define DEFINE_GSLIST_TO_STACK(t1) \ + G_GNUC_UNUSED static STACK_OF(t1) *g_slist_to_stack_of_##t1(GSList **list) \ + { \ + g_assert(list); \ + g_autoptr(STACK_OF_##t1) ret = sk_##t1##_new_null(); \ + if (!ret) \ + g_abort(); \ + for (GSList *iterator = *list; iterator; \ + iterator = iterator->next) { \ + if (sk_##t1##_push(ret, g_steal_pointer(&iterator->data)) == 0) \ + g_abort(); \ + } \ + g_clear_pointer(list, g_slist_free); \ + return g_steal_pointer(&ret); \ + } + +DEFINE_GSLIST_MAP(x509_with_path, X509) +DEFINE_GSLIST_TO_STACK(X509) + EVP_MD_CTX *digest_ctx_new(const EVP_MD *md, GError **err) { g_autoptr(EVP_MD_CTX) ctx = EVP_MD_CTX_new(); @@ -359,79 +405,1340 @@ static gboolean certificate_uses_correct_curve(EVP_PKEY *key, gint nid, return TRUE; } -static gboolean verify_certificate(X509_STORE *store, X509 *cert, GError **err) +/* Verify that the used public key algorithm matches the subject signature + * algorithm + */ +static int check_signature_algo_match(const EVP_PKEY *pkey, const X509 *subject, + GError **err) { - g_autoptr(X509_STORE_CTX) csc = X509_STORE_CTX_new(); - if (!csc) - g_abort(); + gint pkey_nid; - 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 (!pkey) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_PUBLIC_KEY, + _("no public key")); + return -1; } - 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; + if (OBJ_find_sigid_algs(X509_get_signature_nid(subject), NULL, + &pkey_nid) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_SIGNATURE_ALGORITHM, + _("unsupported signature algorithm")); + return -1; } - return TRUE; + if (EVP_PKEY_type(pkey_nid) != EVP_PKEY_base_id(pkey)) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_SIGNATURE_ALGORITHM_MISMATCH, + _("signature algorithm mismatch")); + return -1; + } + + return 0; +} + +static X509_CRL *load_crl_from_bio(BIO *bio) +{ + g_autoptr(X509_CRL) crl = PEM_read_bio_X509_CRL(bio, NULL, 0, NULL); + if (crl) + return g_steal_pointer(&crl); + ERR_clear_error(); + BIO_reset(bio); + + /* maybe the CRL is stored in DER format */ + crl = d2i_X509_CRL_bio(bio, NULL); + if (crl) + return g_steal_pointer(&crl); + return NULL; +} + +static X509_CRL *GByteArray_to_X509_CRL(const GByteArray *data) +{ + g_autoptr(X509_CRL) ret = NULL; + g_autoptr(BIO) bio = NULL; + + g_assert(data); + + if (data->len > INT_MAX) + return NULL; + + bio = BIO_new_mem_buf(data->data, (int)data->len); + if (!bio) + g_abort(); + + ret = load_crl_from_bio(bio); + if (!ret) + return NULL; + + return g_steal_pointer(&ret); +} + +static gint load_crl_from_web(const gchar *url, X509_CRL **crl, GError **err) +{ + g_autoptr(X509_CRL) tmp_crl = NULL; + g_autoptr(GByteArray) data = NULL; + g_assert(crl); + + data = curl_download(url, CRL_DOWNLOAD_TIMEOUT_MS, + CRL_DOWNLOAD_MAX_SIZE, err); + if (!data) { + g_prefix_error(err, _("unable to download CRL: ")); + return -1; + } + tmp_crl = GByteArray_to_X509_CRL(data); + if (!tmp_crl) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_CRL_DOWNLOAD_FAILED, + _("unable to load CRL from '%s'"), url); + return -1; + } + *crl = g_steal_pointer(&tmp_crl); + return 0; +} + +static BIO *bio_read_from_file(const char *path) +{ + g_autoptr(BIO) bio = BIO_new_file(path, "r"); + + if (!bio) + return NULL; + + return g_steal_pointer(&bio); } -static X509 *load_certificate(const gchar *path, GError **err) +/* This function reads in only the first certificate and ignores all other. This + * is only relevant for the PEM file format. For the host-key document and the + * root CA this behavior is expected. + */ +X509 *load_cert_from_file(const char *path, GError **err) { - g_autoptr(X509) ret = NULL; - g_autoptr(BIO) bio = BIO_new_file(path, "rb"); + g_autoptr(BIO) bio = bio_read_from_file(path); + g_autoptr(X509) cert = NULL; if (!bio) { g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_READ_CERTIFICATE, - _("Failed to read host-key document: '%s'"), path); + _("unable to read certificate: '%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); + cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (cert) + return g_steal_pointer(&cert); + ERR_clear_error(); + BIO_reset(bio); + + /* maybe the certificate is stored in DER format */ + cert = d2i_X509_bio(bio, NULL); + if (cert) + return g_steal_pointer(&cert); + + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("unable to load certificate: '%s'"), path); + return NULL; +} + +/* @crl_paths is allowed to be NULL */ +static int load_crls_to_store(X509_STORE *store, const gchar *const *crl_paths, + gboolean err_out_empty_crls, GError **err) +{ + for (const gchar *const *iterator = crl_paths; + iterator != NULL && *iterator != NULL; iterator++) { + const gchar *crl_path = *iterator; + X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + int count; + + g_assert(crl_path); + + if (!lookup) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("X509 store initialization failed")); + return -1; + } + + /* support *.pem files containing multiple CRLs */ + count = X509_load_crl_file(lookup, crl_path, X509_FILETYPE_PEM); + if (count > 0) + continue; + + count = X509_load_crl_file(lookup, crl_path, X509_FILETYPE_ASN1); + if (count == 1) + continue; + + if (err_out_empty_crls) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_LOAD_CRL, + _("unable to load CRL from: '%s'"), crl_path); + return -1; + } + } + + return 0; +} + +/* returns + * 0 when the certificate is valid, + * -1 when not yet valid, + * 1 when expired + */ +static int check_validity_period(const ASN1_TIME *not_before, const ASN1_TIME *not_after) +{ + if (X509_cmp_current_time(not_before) != -1) + return -1; + + if (X509_cmp_current_time(not_after) != 1) + return 1; + + return 0; +} + +static gint x509_name_entry_get_data0(X509_NAME_ENTRY *entry, const guchar **data, + gsize *data_len) +{ + const ASN1_STRING *asn1_str; + gint tmp_data_len; + + g_assert(data); + g_assert(data_len); + + asn1_str = X509_NAME_ENTRY_get_data(entry); + if (!asn1_str) + return -1; + + tmp_data_len = ASN1_STRING_length(asn1_str); + if (tmp_data_len < 0) + return -1; + + *data = ASN1_STRING_get0_data(asn1_str); + *data_len = (gsize)tmp_data_len; + return 0; +} + +/* The caller must not free *data! */ +static gint x509_name_get_data0_by_NID(X509_NAME *name, gint nid, + const guchar **data, gsize *data_len) +{ + + X509_NAME_ENTRY *entry = NULL; + gint lastpos = -1; + + lastpos = X509_NAME_get_index_by_NID(name, nid, lastpos); + if (lastpos == -1) + return -1; + + entry = X509_NAME_get_entry(name, lastpos); + if (!entry) + return -1; + + if (x509_name_entry_get_data0(entry, data, data_len) < 0) + return -1; + + return 0; +} + +/* @y must be a NULL-terminated string */ +static gboolean x509_name_data_by_nid_equal(X509_NAME *name, gint nid, + const gchar *y) +{ + const guchar *data = NULL; + gsize y_len = strlen(y); + gsize data_len; + + if (x509_name_get_data0_by_NID(name, nid, &data, &data_len) < 0) + return FALSE; + + if (data_len != y_len) + return FALSE; + + return memcmp(data, y, data_len) == 0; +} + +static gboolean own_X509_NAME_ENTRY_equal(const X509_NAME_ENTRY *x, + const X509_NAME_ENTRY *y) +{ + const ASN1_OBJECT *x_obj = X509_NAME_ENTRY_get_object(x); + const ASN1_STRING *x_data = X509_NAME_ENTRY_get_data(x); + const ASN1_OBJECT *y_obj = X509_NAME_ENTRY_get_object(y); + const ASN1_STRING *y_data = X509_NAME_ENTRY_get_data(y); + gint x_len = ASN1_STRING_length(x_data); + gint y_len = ASN1_STRING_length(y_data); + + if (x_len < 0 || x_len != y_len) + return FALSE; + + /* ASN1_STRING_cmp(x_data, y_data) == 0 doesn't work because it also + * compares the type, which is sometimes different. + */ + return OBJ_cmp(x_obj, y_obj) == 0 && + memcmp(ASN1_STRING_get0_data(x_data), + ASN1_STRING_get0_data(y_data), + (unsigned long)x_len) == 0; +} + +static gboolean own_X509_NAME_equal(const X509_NAME *x, const X509_NAME *y) +{ + gint x_count = X509_NAME_entry_count(x); + gint y_count = X509_NAME_entry_count(y); + + if (x != y && (!x || !y)) + return FALSE; + + if (x_count != y_count) + return FALSE; + + for (gint i = 0; i < x_count; i++) { + const X509_NAME_ENTRY *entry_i = X509_NAME_get_entry(x, i); + gboolean entry_found = FALSE; + + for (gint j = 0; j < y_count; j++) { + const X509_NAME_ENTRY *entry_j = + X509_NAME_get_entry(y, j); + + if (own_X509_NAME_ENTRY_equal(entry_i, entry_j)) { + entry_found = TRUE; + break; + } + } + + if (!entry_found) + return FALSE; + } + return TRUE; +} + +/* Checks whether the subject of @cert is a IBM signing key subject. For this we + * must check that the subject is equal to: 'C = US, ST = New York, L = + * Poughkeepsie, O = International Business Machines Corporation, CN = + * International Business Machines Corporation' and the organization unit (OUT) + * must end with the suffix ' Key Signing Service'. + */ +static gboolean has_ibm_signing_subject(X509 *cert) +{ + X509_NAME *subject = X509_get_subject_name(cert); + /* X509_NAME_entry_count is safe to be used with NULL */ + gint entry_count = X509_NAME_entry_count(subject); + g_autofree gchar *data_str = NULL; + const guchar *data; + gsize data_len; + + if (entry_count != PV_IMB_Z_SUBJECT_ENTRY_COUNT) + return FALSE; + + if (!x509_name_data_by_nid_equal(subject, NID_countryName, + PV_IBM_Z_SUBJECT_COUNTRY_NAME)) + return FALSE; + + if (!x509_name_data_by_nid_equal(subject, NID_stateOrProvinceName, + PV_IBM_Z_SUBJECT_STATE)) + return FALSE; + + if (!x509_name_data_by_nid_equal(subject, NID_localityName, + PV_IBM_Z_SUBJECT_LOCALITY_NAME)) + return FALSE; + + if (!x509_name_data_by_nid_equal(subject, NID_organizationName, + PV_IBM_Z_SUBJECT_ORGANIZATION_NAME)) + return FALSE; + + if (!x509_name_data_by_nid_equal(subject, NID_commonName, + PV_IBM_Z_SUBJECT_COMMON_NAME)) + return FALSE; + + if (x509_name_get_data0_by_NID(subject, NID_organizationalUnitName, + &data, &data_len) < 0) + return FALSE; + + /* Make sure that data_str is null-terminated as in general it cannot be + * assumed that @data is null-terminated. + */ + data_str = g_strndup((const gchar *)data, data_len); + if (!g_str_has_suffix(data_str, + PV_IBM_Z_SUBJECT_ORGANIZATIONONAL_UNIT_NAME_SUFFIX)) + return FALSE; + + return TRUE; +} + +static X509_NAME *x509_name_reorder_attributes(const X509_NAME *name, const gint nids[], + gsize nids_len) +{ + gint entry_count = X509_NAME_entry_count(name); + g_autoptr(X509_NAME) ret = NULL; + + if (entry_count < 0) + return NULL; + + if (nids_len != (gsize) entry_count) return NULL; + + ret = X509_NAME_new(); + if (!ret) + g_abort(); + + for (gsize i = 0; i < nids_len; i++) { + const X509_NAME_ENTRY *entry = NULL; + gint nid = nids[i]; + gint lastpos = -1; + + lastpos = X509_NAME_get_index_by_NID((X509_NAME *)name, nid, lastpos); + if (lastpos == -1) + return NULL; + + entry = X509_NAME_get_entry(name, lastpos); + if (!entry) + return NULL; + + if (X509_NAME_add_entry(ret, entry, -1, 0) != 1) + return NULL; } return g_steal_pointer(&ret); } -EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, - GError **err) +/* In RFC 5280 the attributes of a (subject/issuer) name is not mandatory + * ordered. The problem is that our certificates are not consistent in the order + * (see https://tools.ietf.org/html/rfc5280#section-4.1.2.4 for details). + * + * This function converts a correct X509_NAME into the broken one. The caller is + * responsible to free the returned value. + */ +X509_NAME *c2b_name(const X509_NAME *name) { - g_autoptr(EVP_PKEY) ret = NULL; - g_autoptr(X509) cert = NULL; + gint nids[] = { NID_countryName, NID_organizationName, NID_organizationalUnitName, + NID_localityName, NID_stateOrProvinceName, NID_commonName }; + g_autoptr(X509_NAME) broken_name = NULL; - cert = load_certificate(path, err); - if (!cert) + g_assert(name); + + /* Try to reorder the attributes */ + broken_name = x509_name_reorder_attributes(name, nids, G_N_ELEMENTS(nids)); + if (broken_name) + return g_steal_pointer(&broken_name); + return X509_NAME_dup((X509_NAME *)name); +} + +/* Verify that: subject(issuer) == issuer(crl) and SKID(issuer) == AKID(crl) */ +static gint check_crl_issuer(X509_CRL *crl, X509 *issuer, GError **err) +{ + const X509_NAME *crl_issuer = X509_CRL_get_issuer(crl); + const X509_NAME *issuer_subject = X509_get_subject_name(issuer); + AUTHORITY_KEYID *akid = NULL; + + if (!own_X509_NAME_equal(issuer_subject, crl_issuer)) { + g_autofree char *issuer_subject_str = X509_NAME_oneline(issuer_subject, + NULL, 0); + g_autofree char *crl_issuer_str = X509_NAME_oneline(crl_issuer, NULL, 0); + + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_CRL_SUBJECT_ISSUER_MISMATCH, + _("issuer mismatch:\n%s\n%s"), + issuer_subject_str, crl_issuer_str); + return -1; + } + + /* If AKID(@crl) is specified it must match with SKID(@issuer) */ + akid = X509_CRL_get_ext_d2i(crl, NID_authority_key_identifier, NULL, NULL); + if (akid && X509_check_akid(issuer, akid) != X509_V_OK) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_SKID_AKID_MISMATCH, + _("AKID mismatch")); + return -1; + } + + return 0; +} + +/* Verify whether a revocation list @crl is valid and is issued by @cert. For + * this multiple steps must be done: + * + * 1. verify issuer of the CRL matches with the suject name of @cert + * 2. verify the validity period of the CRL + * 3. verify the signature of the CRL + * + * Important: This function doesn't verify whether @cert is allowed to issue a + * CRL. Returns 0 if @crl is valid and issued by @cert, otherwise -1. + */ +gint check_crl_valid_for_cert(X509_CRL *crl, X509 *cert, + gint verify_flags, GError **err) +{ + EVP_PKEY *pkey = X509_get0_pubkey(cert); + + g_assert(crl); + + if (!pkey) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("failed to retrieve public key from the certificate")); + return -1; + } + + /* check that the @crl issuer matches with the subject name of @cert*/ + if (check_crl_issuer(crl, cert, err) < 0) + return -1; + + /* verify the validity period of the CRL */ + if (!(verify_flags & X509_V_FLAG_NO_CHECK_TIME)) { + const ASN1_TIME *last = X509_CRL_get0_lastUpdate(crl); + const ASN1_TIME *next = X509_CRL_get0_nextUpdate(crl); + + if (!last || !next || check_validity_period(last, next)) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_VALIDITY_PERIOD, + _("validity period is not valid")); + return -1; + } + } else { + verify_flags &= ~X509_V_FLAG_NO_CHECK_TIME; + } + + /* verify the signature */ + if (X509_CRL_verify(crl, pkey) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_CRL_SIGNATURE_INVALID, + _("signature is not valid")); + return -1; + } + g_assert(verify_flags == 0); + return 0; +} + +/* Given a certificate @cert try to find valid revocation lists in @ctx. If no + * valid CRL was found NULL is returned. + */ +STACK_OF_X509_CRL *store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, + GError **err) +{ + g_autoptr(STACK_OF_X509_CRL) ret = NULL; + const gint verify_flags = 0; + X509_NAME *subject = NULL; + + subject = X509_get_subject_name(cert); + if (!subject) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_MALFORMED_CERTIFICATE, + _("certificate is malformed")); return NULL; + } - if (store && !verify_certificate(store, cert, err)) { - g_prefix_error(err, - _("Failed to load host-key document: '%s': "), - path); + ret = X509_STORE_CTX_get1_crls(ctx, subject); + if (!ret) { + /* Workaround to fix the mismatch between issuer name of the + * IBM Z signing CRLs and the IBM Z signing key subject name. + */ + g_autoptr(X509_NAME) broken_subject = c2b_name(subject); + + ret = X509_STORE_CTX_get1_crls(ctx, broken_subject); + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_CRL, + _("no CRL found")); + return NULL; + } + } + + /* Filter out non-valid CRLs for @cert */ + for (gint i = 0; i < sk_X509_CRL_num(ret); i++) { + X509_CRL *crl = sk_X509_CRL_value(ret, i); + + g_assert(crl); + + /* If @crl is not valid remove it from the array and log a + * warning. + */ + if (check_crl_valid_for_cert(crl, cert, verify_flags, err) < 0) { + g_assert(err); + g_warning(_("CRL is not valid: %s"), (*err)->message); + g_clear_error(err); + + /* Remove this certificate from the list and change i-- as the + * array has changed - this is not beautfiul, but right now the + * easiest solution I came up with + */ + if (sk_X509_CRL_delete(ret, i--) != crl) + g_abort(); + + g_clear_pointer(&crl, X509_CRL_free); + } + } + + if (sk_X509_CRL_num(ret) < 1) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_CRL, + _("no valid CRL found")); return NULL; } + return g_steal_pointer(&ret); +} + +/* Return a list of all IBM Z signing key certificates in @certs and remove them + * from the chain. Return empty stack if no IBM Z signing key is found. + */ +STACK_OF_X509 *delete_ibm_signing_certs(STACK_OF_X509 *certs) +{ + g_autoptr(STACK_OF_X509) ret = sk_X509_new_null(); + + for (gint i = 0; i < sk_X509_num(certs); i++) { + X509 *cert = sk_X509_value(certs, i); + + g_assert(cert); + + if (!has_ibm_signing_subject(cert)) + continue; + + /* Remove this certificate from the list and change i-- as the + * array has changed - this is not beautfiul, but right now the + * easiest solution I came up with. + */ + if (sk_X509_delete(certs, i--) != cert) + g_abort(); + + if (sk_X509_push(ret, g_steal_pointer(&cert)) == 0) + g_abort(); + } + + return g_steal_pointer(&ret); +} + +X509_STORE *store_setup(const gchar *root_ca_path, const gchar * const *crl_paths, + GError **err) +{ + g_autoptr(X509_STORE) store = X509_STORE_new(); + + g_assert(store); + + /* if @root_ca_path != NULL use the specified root CA only, otherwise use the + * default root CAs found on the system + */ + if (root_ca_path) { + X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + int count; + + if (!lookup) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("X509 store initialization failed")); + return NULL; + } + + count = X509_load_cert_file(lookup, root_ca_path, X509_FILETYPE_PEM); + if (count > 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_LOAD_ROOT_CA, + _("multiple certificates in one PEM file is not supported: '%s'"), + root_ca_path); + return NULL; + } else if (count < 1) { + count = X509_load_cert_file(lookup, root_ca_path, + X509_FILETYPE_ASN1); + if (count != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_LOAD_ROOT_CA, + _("failed to load root certificate from '%s'"), + root_ca_path); + return NULL; + } + } + } else { + /* Load certificates into @store from the hardcoded OpenSSL + * default paths + */ + if (X509_STORE_set_default_paths(store) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_LOAD_DEFAULT_CA, + _("failed to load system root certificates")); + return NULL; + } + } + + /* Error out if a CRL file was provided that has not at least one CRL*/ + if (load_crls_to_store(store, crl_paths, TRUE, err) < 0) + return NULL; + + return g_steal_pointer(&store); +} + +int store_set_verify_param(X509_STORE *store, GError **err) +{ + g_autoptr(X509_VERIFY_PARAM) param = NULL; + unsigned long flags = X509_V_FLAG_CRL_CHECK | + X509_V_FLAG_CRL_CHECK_ALL | + X509_V_FLAG_TRUSTED_FIRST | + X509_V_FLAG_CHECK_SS_SIGNATURE | + X509_V_FLAG_X509_STRICT | + X509_V_FLAG_POLICY_CHECK; + + /* Create a X509_VERIFY_PARAM structure, which specifies which checks + * should be done by the certificate verification operation + */ + param = X509_VERIFY_PARAM_new(); + if (!param) + g_abort(); + + /* The maximum depth level of the chain of trust for the verification of + * the IBM Z signing key is 2, i.e. IBM Z signing key -> (DigiCert) + * intermediate CA -> (DigiCert) root CA + */ + X509_VERIFY_PARAM_set_depth(param, 2); + + /* Set minimum allowed security level to at least 112 bits. */ + X509_VERIFY_PARAM_set_auth_level(param, PV_CERTS_SECURITY_LEVEL); + + /* Set verification purpose to 'Any Purpose' and specify that the + * associated trust setting of the default purpose should be used. + */ + if (X509_VERIFY_PARAM_set_purpose(param, + X509_PURPOSE_ANY | X509_TRUST_DEFAULT) != 1) + goto error; + + /* Each certificate from the chain of trust must be checked against a + * CRL to see if it has been revoked. In addition, use trusted + * certificates first mode, check signature of the last certificate, + * strict mode, and verify the policies. + */ + if (X509_VERIFY_PARAM_set_flags(param, flags) != 1) + goto error; + + if (X509_STORE_set1_param(store, param) != 1) + goto error; + + return 0; + +error: + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("X509 store initialization failed")); + return -1; +} + +/* @cert_paths must contain at least one element, otherwise an error is + * reported. + */ +GSList *load_certificates(const gchar *const *cert_paths, GError **err) +{ + g_autoslist(x509_with_path) ret = NULL; + + for (const gchar *const *iterator = cert_paths; + iterator != NULL && *iterator != NULL; iterator++) { + const gchar *cert_path = *iterator; + g_autoptr(X509) cert = NULL; + + g_assert(cert_path); + + cert = load_cert_from_file(cert_path, err); + if (!cert) + return NULL; + + ret = g_slist_append(ret, x509_with_path_new(cert, cert_path)); + } + if (!ret) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_READ_CERTIFICATE, + _("no certificates specified")); + return NULL; + } + + return g_steal_pointer(&ret); +} + +static X509 *get_cert(const x509_with_path *cert_with_path, G_GNUC_UNUSED GError **err) +{ + g_autoptr(X509) cert = NULL; + + g_assert(cert_with_path && cert_with_path->cert); + + cert = cert_with_path->cert; + if (X509_up_ref(cert) != 1) + g_abort(); + return g_steal_pointer(&cert); +} + +STACK_OF_X509 *get_x509_stack(const GSList *x509_with_path_list) +{ + g_autoslist(X509) certs = NULL; + g_autoptr(GError) err = NULL; + + certs = g_slist_map_x509_with_path_X509(x509_with_path_list, + get_cert, &err); + g_assert_null(err); + return g_slist_to_stack_of_X509(&certs); +} + +x509_with_path *x509_with_path_new(X509 *cert, const gchar *path) +{ + g_autoptr(x509_with_path) ret = g_new(x509_with_path, 1); + + g_assert(cert && path); + + if (X509_up_ref(cert) != 1) + g_abort(); + ret->cert = cert; + ret->path = g_strdup(path); + return g_steal_pointer(&ret); +} + +void x509_with_path_free(x509_with_path *cert) +{ + if (!cert) + return; + + X509_free(cert->cert); + g_free((gchar *)cert->path); + g_free(cert); +} + +x509_pair *x509_pair_new(X509 **cert, STACK_OF_X509_CRL **crls) +{ + g_autoptr(x509_pair) ret = g_new0(x509_pair, 1); + + g_assert(cert); + g_assert(crls); + + ret->cert = g_steal_pointer(cert); + ret->crls = g_steal_pointer(crls); + return g_steal_pointer(&ret); +} + +void x509_pair_free(x509_pair *pair) +{ + if (!pair) + return; + + sk_X509_CRL_pop_free(pair->crls, X509_CRL_free); + X509_free(pair->cert); + g_free(pair); +} + +X509_STORE_CTX *create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, + GError **err) +{ + g_autoptr(X509_STORE_CTX) ctx = X509_STORE_CTX_new(); + + if (!ctx || !X509_STORE_CTX_init(ctx, trusted, NULL, chain)) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("X509 store initialization failed: %s"), + X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx))); + return NULL; + } + + return g_steal_pointer(&ctx); +} + +gint verify_cert(X509 *cert, X509_STORE_CTX *ctx, GError **err) +{ + gint rc; + + X509_STORE_CTX_set_cert(ctx, cert); + rc = X509_verify_cert(ctx); + if (rc != 1) { + X509 *tmp_cert = NULL; + + tmp_cert = X509_STORE_CTX_get_current_cert(ctx); + if (tmp_cert) { + g_autofree char *subj_name = X509_NAME_oneline( + X509_get_subject_name(tmp_cert), NULL, 0); + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("failed to verify certificate '%s': %s"), + subj_name, + X509_verify_cert_error_string( + X509_STORE_CTX_get_error(ctx))); + } else { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("failed to verify certificate: %s"), + X509_verify_cert_error_string( + X509_STORE_CTX_get_error(ctx))); + } + + return -1; + } + + return 0; +} + +static int security_level_to_bits(int level) +{ + static int security_bits[] = { 0, 80, 112, 128, 192, 256 }; + + g_assert(level > 0 && level < (int)G_N_ELEMENTS(security_bits)); + + return security_bits[level]; +} + +static ASN1_OCTET_STRING *digicert_assured_id_root_ca; + +const ASN1_OCTET_STRING *get_digicert_assured_id_root_ca_skid(void) +{ + pv_crypto_init(); + return digicert_assured_id_root_ca; +} + +/* Used for the caching of the downloaded CRLs */ +static GHashTable *cached_crls; + +void pv_crypto_init(void) +{ + if (digicert_assured_id_root_ca) + return; + + cached_crls = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify)X509_CRL_free); + digicert_assured_id_root_ca = s2i_ASN1_OCTET_STRING( + NULL, NULL, DIGICERT_ASSURED_ID_ROOT_CA_SKID); +} + +void pv_crypto_cleanup(void) +{ + if (!digicert_assured_id_root_ca) + return; + g_clear_pointer(&cached_crls, g_hash_table_destroy); + g_clear_pointer(&digicert_assured_id_root_ca, ASN1_OCTET_STRING_free); +} + +gint check_chain_parameters(const STACK_OF_X509 *chain, + const ASN1_OCTET_STRING *skid, GError **err) +{ + const ASN1_OCTET_STRING *ca_skid = NULL; + gint len = sk_X509_num(chain); + X509 *ca = NULL; + + g_assert(skid); + /* at least one root and one leaf certificate must be defined */ + g_assert(len >= 2); + + /* get the root certificate of the chain of trust */ + ca = sk_X509_value(chain, len - 1); + if (!ca) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("no root certificate found")); + return -1; + } + + ca_skid = X509_get0_subject_key_id(ca); + if (!ca_skid) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_MALFORMED_ROOT_CA, + _("malformed root certificate")); + return -1; + } + + if (ASN1_STRING_cmp(ca_skid, skid) != 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_WRONG_CA_USED, + _("expecting DigiCert root CA to be used")); + return -1; + } + + return 0; +} + +/* It's almost the same as X509_check_issed from OpenSSL does except that we + * don't check the key usage of the potential issuer. This means we check: + * 1. issuer_name(cert) == subject_name(issuer) + * 2. Check whether the akid(cert) (if available) matches the issuer skid + * 3. Check that the cert algrithm matches the subject algorithm + * 4. Verify the signature of certificate @cert is using the public key of + * @issuer. + */ +static gint check_host_key_issued(X509 *cert, X509 *issuer, GError **err) +{ + const X509_NAME *issuer_subject = X509_get_subject_name(issuer); + const X509_NAME *cert_issuer = X509_get_issuer_name(cert); + AUTHORITY_KEYID *akid = NULL; + + /* We cannot use X509_NAME_cmp() because it considers the order of the + * X509_NAME_Entries. + */ + if (!own_X509_NAME_equal(issuer_subject, cert_issuer)) { + g_autofree char *issuer_subject_str = + X509_NAME_oneline(issuer_subject, NULL, 0); + g_autofree char *cert_issuer_str = + X509_NAME_oneline(cert_issuer, NULL, 0); + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_CERT_SUBJECT_ISSUER_MISMATCH, + _("Subject issuer mismatch:\n'%s'\n'%s'"), + issuer_subject_str, cert_issuer_str); + return -1; + } + + akid = X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); + if (akid && X509_check_akid(issuer, akid) != X509_V_OK) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_SKID_AKID_MISMATCH, + _("AKID mismatch")); + return -1; + } + + if (check_signature_algo_match(X509_get0_pubkey(issuer), cert, err) < 0) + return -1; + + if (X509_verify(cert, X509_get0_pubkey(issuer)) != 1) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_CERT_SIGNATURE_INVALID, + _("Signature verification failed")); + return -1; + } + + return 0; +} + +static gboolean is_cert_revoked(X509 *cert, X509_CRL *crl) +{ + X509_REVOKED *revoked = NULL; + gint rc; + + if (!cert || !crl) + g_abort(); + + rc = X509_CRL_get0_by_serial(crl, &revoked, + (ASN1_INTEGER *)X509_get0_serialNumber(cert)); + if (rc == 0) + return FALSE; + + if (revoked) + return TRUE; + + return FALSE; +} + +/* Get the first http[s] URL from a DIST_POINT */ +static const char *get_first_dp_url(DIST_POINT *dp) +{ + GENERAL_NAMES *general_names; + + g_assert(dp); + + if (!dp->distpoint || dp->distpoint->type != 0) + return NULL; + + general_names = dp->distpoint->name.fullname; + for (gint i = 0; i < sk_GENERAL_NAME_num(general_names); i++) { + GENERAL_NAME *name = sk_GENERAL_NAME_value(general_names, i); + g_autofree const gchar *uri_str = NULL; + ASN1_STRING *uri_asn1; + const gchar *uri_data; + gint uri_data_len; + gint type; + + uri_asn1 = GENERAL_NAME_get0_value(name, &type); + if (type != GEN_URI) + continue; + uri_data_len = ASN1_STRING_length(uri_asn1); + if (uri_data_len < 0) + continue; + uri_data = (const gchar *)ASN1_STRING_get0_data(uri_asn1); + /* Make sure that uri_str is null-terminated as in general it + * cannot be assumed that @uri_data is null-terminated. + */ + uri_str = g_strndup(uri_data, + (gsize)uri_data_len); + if (g_str_has_prefix(uri_str, "http://")) + return uri_data; + if (g_str_has_prefix(uri_str, "https://")) + return uri_data; + } + return NULL; +} + +static gboolean insert_crl(X509_NAME *name, X509_CRL *crl) +{ + g_autofree gchar *key = NULL; + + g_assert(name); + + key = X509_NAME_oneline(name, NULL, 0); + if (!key) + g_abort(); + if (X509_CRL_up_ref(crl) != 1) + g_abort(); + return g_hash_table_insert(cached_crls, g_steal_pointer(&key), crl); +} + +/* Caller is responsible for free'ing */ +static X509_CRL *lookup_crl(X509_NAME *name) +{ + g_autoptr(X509_CRL) crl = NULL; + g_autofree gchar *key = NULL; + + g_assert(name); + + key = X509_NAME_oneline(name, NULL, 0); + if (!key) + g_abort(); + crl = g_hash_table_lookup(cached_crls, key); + if (crl) { + if (X509_CRL_up_ref(crl) != 1) + g_abort(); + return g_steal_pointer(&crl); + } + return NULL; +} + +/* Returns empty stack if no CRL downloaded. */ +static STACK_OF_X509_CRL *crls_download_cb(X509_STORE_CTX *ctx, X509_NAME *nm) +{ + g_autoptr(STACK_OF_X509_CRL) crls = NULL; + g_autoptr(X509_CRL) crl = NULL; + /* must not be free'd */ + X509 *cert = NULL; + + crls = sk_X509_CRL_new_null(); + if (!crls) + g_abort(); + cert = X509_STORE_CTX_get_current_cert(ctx); + if (!cert) + g_steal_pointer(&crls); + g_assert(X509_NAME_cmp(X509_get_issuer_name(cert), nm) == 0); + crl = lookup_crl(nm); + if (!crl) { + /* ignore error */ + crl = load_crl_by_cert(cert, NULL); + if (!crl) + return g_steal_pointer(&crls); + g_assert_true(insert_crl(nm, crl)); + } + if (sk_X509_CRL_push(crls, g_steal_pointer(&crl)) == 0) + g_abort(); + return g_steal_pointer(&crls); +} + +void STACK_OF_DIST_POINT_free(STACK_OF_DIST_POINT *stack) +{ + if (!stack) + return; + + sk_DIST_POINT_pop_free(stack, DIST_POINT_free); +} + +void STACK_OF_X509_free(STACK_OF_X509 *stack) +{ + if (!stack) + return; + + sk_X509_pop_free(stack, X509_free); +} + +void STACK_OF_X509_CRL_free(STACK_OF_X509_CRL *stack) +{ + if (!stack) + return; + + sk_X509_CRL_pop_free(stack, X509_CRL_free); +} + +/* Downloaded CRLs have a higher precedence than the CRLs specified on the + * command line. + */ +static STACK_OF_X509_CRL *crls_cb(X509_STORE_CTX *ctx, X509_NAME *nm) +{ + g_autoptr(STACK_OF_X509_CRL) crls = crls_download_cb(ctx, nm); + + if (sk_X509_CRL_num(crls) > 0) + return g_steal_pointer(&crls); + return X509_STORE_CTX_get1_crls(ctx, nm); +} + +/* Set up CRL lookup with download support */ +void store_setup_crl_download(X509_STORE *st) +{ + X509_STORE_set_lookup_crls(st, crls_cb); +} + +/* Download a CRL using the URI specified in the distribution @crldp */ +static X509_CRL *load_crl_by_dist_point(DIST_POINT *crldp, GError **err) +{ + const gchar *uri = get_first_dp_url(crldp); + g_autoptr(X509_CRL) crl = NULL; + + if (!uri) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("no valid URL specified in distribution point")); + return NULL; + } + + if (load_crl_from_web(uri, &crl, err) < 0) + return NULL; + + return g_steal_pointer(&crl); +} + +/* This function returns the first X509_CRL found from the CRL distribution + * points specified in @cert. This function could be optimized by filtering + * duplicate certificates and/or filtering duplicated URIs. + */ +X509_CRL *load_crl_by_cert(X509 *cert, GError **err) +{ + g_autoptr(STACK_OF_DIST_POINT) crldps = NULL; + g_autoptr(X509_CRL) ret = NULL; + + g_assert(cert); + + crldps = X509_get_ext_d2i(cert, NID_crl_distribution_points, NULL, NULL); + if (!crldps || sk_DIST_POINT_num(crldps) == 0) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_CRLDP, + _("no distribution point found")); + return NULL; + } + + for (int i = 0; i < sk_DIST_POINT_num(crldps); i++) { + DIST_POINT *crldp = sk_DIST_POINT_value(crldps, i); + + g_assert(crldp); + + /* ignore error */ + ret = load_crl_by_dist_point(crldp, NULL); + if (ret) + return g_steal_pointer(&ret); + } + + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_FAILED_DOWNLOAD_CRL, + _("failed to download CRL")); + return NULL; +} + +STACK_OF_X509_CRL *try_load_crls_by_certs(GSList *certs_with_path) +{ + g_autoptr(STACK_OF_X509_CRL) ret = sk_X509_CRL_new_null(); + if (!ret) + g_abort(); + + for (GSList *iterator = certs_with_path; iterator; + iterator = iterator->next) { + x509_with_path *cert_with_path = iterator->data; + X509 *cert = cert_with_path->cert; + g_autoptr(X509_CRL) crl = NULL; + + g_assert(cert); + + /* ignore error */ + crl = load_crl_by_cert(cert, NULL); + if (!crl) + continue; + + if (sk_X509_CRL_push(ret, g_steal_pointer(&crl)) == 0) + g_abort(); + } + + return g_steal_pointer(&ret); +} + +/* Assumptions are that the issuer_crt and issuer_crl is a trusted IBM Z + * signing certificate/revocation list. This function verifies a host-key + * document. To do so multiple steps are required: + * + * 1. issuer(host_key) == subject(issuer_crt) + * 2. Signature verification + * 3. @host_key must not be expired + * 4. @host_key must not be revoked + */ +gint verify_host_key(X509 *host_key, GSList *issuer_pairs, + gint verify_flags, int level, GError **err) +{ + g_assert(host_key); + + const gint exp_security_bits = security_level_to_bits(level); + EVP_PKEY *pkey = X509_get0_pubkey(host_key); + gboolean successfully_checked = FALSE; + gint pkey_security_bits; + + if (!pkey) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_INTERNAL, + _("failed to retrieve public key")); + return -1; + } + + /* check key level, if necessary */ + pkey_security_bits = EVP_PKEY_security_bits(pkey); + if (exp_security_bits > 0 && pkey_security_bits < exp_security_bits) { + g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_VERIFICATION, + _("not enough bits of security (%d, %d expected)"), + pkey_security_bits, exp_security_bits); + return -1; + } + + if (!(verify_flags & X509_V_FLAG_NO_CHECK_TIME)) { + const ASN1_TIME *last = X509_get_notBefore(host_key); + const ASN1_TIME *next = X509_get_notAfter(host_key); + + if (!last || !next || check_validity_period(last, next)) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INVALID_VALIDITY_PERIOD, + _("validity period is not valid")); + return -1; + } + } else { + verify_flags &= ~X509_V_FLAG_NO_CHECK_TIME; + } + + /* Verify that the host_key was issued by a certificate and that it + * wasn't revoked. + */ + for (GSList *iterator = issuer_pairs; iterator; + iterator = iterator->next) { + const x509_pair *pair = iterator->data; + STACK_OF_X509_CRL *issuer_crls = NULL; + X509 *issuer_cert = NULL; + + g_assert(pair); + + issuer_cert = pair->cert; + issuer_crls = pair->crls; + + g_assert(issuer_cert); + + /* Verify that the issuer(host_key) == subject(issuer_cert) and + * that the signature is valid + */ + if (check_host_key_issued(host_key, issuer_cert, NULL) < 0) + continue; + + /* Check against CRL */ + if (verify_flags & X509_V_FLAG_CRL_CHECK) { + gboolean crl_checked = FALSE; + + verify_flags &= ~X509_V_FLAG_CRL_CHECK; + for (gint i = 0; i < sk_X509_CRL_num(issuer_crls); i++) { + X509_CRL *issuer_crl = + sk_X509_CRL_value(issuer_crls, i); + + g_assert(issuer_crl); + + if (is_cert_revoked(host_key, issuer_crl)) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_CERT_REVOKED, + _("certificate revoked")); + return -1; + } + + crl_checked = TRUE; + } + + if (!crl_checked) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_INTERNAL, + _("no valid CRL found")); + return -1; + } + successfully_checked = TRUE; + break; + } + } + + if (!successfully_checked) { + g_set_error(err, PV_CRYPTO_ERROR, + PV_CRYPTO_ERROR_NO_ISSUER_IBM_Z_FOUND, + _("no IBM Z signing key that issued this host-key document found")); + return -1; + } + + /* were some unsupported flags specified? */ + g_assert(verify_flags == 0); + return 0; +} + +EVP_PKEY *read_ec_pubkey_cert(X509 *cert, gint nid, + GError **err) +{ + g_autoptr(EVP_PKEY) ret = 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); + _("Failed to get public key from host-key document")); return NULL; } if (!certificate_uses_correct_curve(ret, nid, err)) { g_prefix_error(err, - _("Failed to load host-key document: '%s': "), - path); + _("Host-key document doesn\'t use correct EC curve")); return NULL; } diff --git a/genprotimg/src/utils/crypto.h b/genprotimg/src/utils/crypto.h index 34418ed..286cf45 100644 --- a/genprotimg/src/utils/crypto.h +++ b/genprotimg/src/utils/crypto.h @@ -11,14 +11,18 @@ #define PV_UTILS_CRYPTO_H #include +#include #include #include #include #include #include +#include #include +#include #include #include +#include #include #include "common.h" @@ -33,6 +37,9 @@ #define AES_256_XTS_TWEAK_SIZE 16 #define AES_256_XTS_KEY_SIZE 64 +#define CRL_DOWNLOAD_TIMEOUT_MS 3000 +#define CRL_DOWNLOAD_MAX_SIZE (1024 * 1024) /* in bytes */ + enum PvCryptoMode { PV_ENCRYPT, PV_DECRYPT, @@ -40,7 +47,34 @@ enum PvCryptoMode { typedef GSList HostKeyList; +/* play nice with g_autoptr */ +typedef STACK_OF(DIST_POINT) STACK_OF_DIST_POINT; +typedef STACK_OF(X509) STACK_OF_X509; +typedef STACK_OF(X509_CRL) STACK_OF_X509_CRL; + +void STACK_OF_DIST_POINT_free(STACK_OF_DIST_POINT *stack); +void STACK_OF_X509_free(STACK_OF_X509 *stack); +void STACK_OF_X509_CRL_free(STACK_OF_X509_CRL *stack); + +typedef struct { + X509 *cert; + const gchar *path; +} x509_with_path; + +x509_with_path *x509_with_path_new(X509 *cert, const gchar *path); +void x509_with_path_free(x509_with_path *cert); + +typedef struct { + X509 *cert; + STACK_OF_X509_CRL *crls; +} x509_pair; + +x509_pair *x509_pair_new(X509 **cert, STACK_OF_X509_CRL **crls); +void x509_pair_free(x509_pair *pair); + /* Register auto cleanup functions */ +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(ASN1_INTEGER, ASN1_INTEGER_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(ASN1_OCTET_STRING, ASN1_OCTET_STRING_free) 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) @@ -51,10 +85,18 @@ 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(STACK_OF_DIST_POINT, STACK_OF_DIST_POINT_free); +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509, STACK_OF_X509_free); +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(STACK_OF_X509_CRL, STACK_OF_X509_CRL_free); WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509, X509_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_CRL, X509_CRL_free) WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_LOOKUP, X509_LOOKUP_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_NAME, X509_NAME_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(x509_pair, x509_pair_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) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(X509_VERIFY_PARAM, X509_VERIFY_PARAM_free) +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(x509_with_path, x509_with_path_free) union cmp_index { struct { @@ -79,8 +121,37 @@ struct cipher_parms { const Buffer *iv_or_tweak; }; -EVP_PKEY *read_ec_pubkey_cert(X509_STORE *store, gint nid, const gchar *path, - GError **err); +int check_crl_valid_for_cert(X509_CRL *crl, X509 *cert, + gint verify_flags, GError **err); +void pv_crypto_init(void); +void pv_crypto_cleanup(void); +const ASN1_OCTET_STRING *get_digicert_assured_id_root_ca_skid(void); +gint verify_host_key(X509 *host_key, GSList *issuer_pairs, + gint verify_flags, int level, GError **err); +X509 *load_cert_from_file(const char *path, GError **err); +X509_CRL *load_crl_from_file(const gchar *path, GError **err); +GSList *load_certificates(const gchar *const *cert_paths, GError **err); +STACK_OF_X509 *get_x509_stack(const GSList *x509_with_path_list); +X509_STORE *store_setup(const gchar *root_ca_path, + const gchar * const *crl_paths, + GError **err); +int store_set_verify_param(X509_STORE *store, GError **err); +X509_CRL *load_crl_by_cert(X509 *cert, GError **err); +STACK_OF_X509_CRL *try_load_crls_by_certs(GSList *certs_with_path); +gint check_chain_parameters(const STACK_OF_X509 *chain, + const ASN1_OCTET_STRING *skid, GError **err); +X509_NAME *c2b_name(const X509_NAME *name); + +STACK_OF_X509 *delete_ibm_signing_certs(STACK_OF_X509 *certs); +STACK_OF_X509_CRL *store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, + GError **err); +X509_STORE_CTX *create_store_ctx(X509_STORE *trusted, STACK_OF_X509 *chain, + GError **err); +gint verify_cert(X509 *cert, X509_STORE_CTX *ctx, GError **err); +X509_CRL *get_first_valid_crl(X509_STORE_CTX *ctx, X509 *cert, GError **err); +void store_setup_crl_download(X509_STORE *st); +EVP_PKEY *read_ec_pubkey_cert(X509 *cert, gint nid, 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); diff --git a/genprotimg/src/utils/curl.c b/genprotimg/src/utils/curl.c new file mode 100644 index 0000000..a8ef3f6 --- /dev/null +++ b/genprotimg/src/utils/curl.c @@ -0,0 +1,121 @@ +/* + * Libcurl utils + * + * Copyright IBM Corp. 2020 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include + +#include "lib/zt_common.h" +#include "pv/pv_error.h" + +#include "curl.h" + +struct UserData { + GByteArray *buffer; + guint max_size; +}; + +static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + g_assert(userdata); + struct UserData *data = (struct UserData *)userdata; + GByteArray *buffer = data->buffer; + guint64 actual_size; + size_t err; + + g_assert(buffer); + + if (!g_uint64_checked_mul(&actual_size, size, nmemb)) + g_abort(); + + /* Signal an error condition by returning a amount that differs + * from the amount passed to the callback. This results in a + * CURLE_WRITE_ERROR. + */ + err = actual_size + 1; + + if (actual_size > G_MAXUINT) + return err; + + data->buffer = g_byte_array_append(buffer, (guchar *)ptr, (guint)actual_size); + if (data->buffer->len > data->max_size) + return err; + + return actual_size; +} + +gint curl_init(void) +{ + if (curl_global_init(CURL_GLOBAL_ALL) != 0) + return -1; + return 0; +} + +void curl_cleanup(void) +{ + curl_global_cleanup(); +} + +GByteArray *curl_download(const gchar *url, long timeout_ms, guint max_size, + GError **err) +{ + g_autoptr(GByteArray) ret = NULL; + g_autoptr(CURL) handle = NULL; + g_autofree gchar *agent = NULL; + struct UserData userdata; + CURLcode rc; + + /* set up curl session */ + handle = curl_easy_init(); + if (!handle) + g_abort(); + + /* follow redirection */ + rc = curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1l); + if (rc != CURLE_OK) + goto curl_err; + rc = curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, timeout_ms); + if (rc != CURLE_OK) + goto curl_err; + rc = curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1l); + if (rc != CURLE_OK) + goto curl_err; + agent = g_strdup_printf("%s/%s", tool_name, RELEASE_STRING); + rc = curl_easy_setopt(handle, CURLOPT_USERAGENT, agent); + if (rc != CURLE_OK) + goto curl_err; + rc = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback); + if (rc != CURLE_OK) + goto curl_err; + ret = g_byte_array_new(); + userdata.buffer = ret; + userdata.max_size = max_size; + rc = curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void *)&userdata); + if (rc != CURLE_OK) + goto curl_err; + rc = curl_easy_setopt(handle, CURLOPT_URL, url); + if (rc != CURLE_OK) + goto curl_err; + + rc = curl_easy_perform(handle); + if (rc != CURLE_OK) { + g_set_error(err, PV_ERROR, PV_ERROR_DOWNLOAD_FAILED, + _("download failed: %s"), curl_easy_strerror(rc)); + return NULL; + } + + return g_steal_pointer(&ret); +curl_err: + g_set_error(err, PV_ERROR, + PV_ERROR_CURL_INIT_FAILED, + _("cURL initialization failed: %s"), + curl_easy_strerror(rc)); + return NULL; +} diff --git a/genprotimg/src/utils/curl.h b/genprotimg/src/utils/curl.h new file mode 100644 index 0000000..4ec1c11 --- /dev/null +++ b/genprotimg/src/utils/curl.h @@ -0,0 +1,25 @@ +/* + * Libcurl 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_LIBCURL_H +#define PV_UTILS_LIBCURL_H + +#include +#include + +#include "common.h" + +WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURL, curl_easy_cleanup) + +GByteArray *curl_download(const gchar *url, long timeout_ms, guint max_size, + GError **err); +gint curl_init(void); +void curl_cleanup(void); + +#endif /* PV_UTILS_LIBCURL_H */ -- 2.26.2 From 4964155a921e38d971bc857dbe95ce60aaffef45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 7 Dec 2020 14:34:51 +0100 Subject: [PATCH 6/9] s390-tools: add hsci tool (#1847434) Summary: s390-tools: add hsci tool Description: hsci is used to control and show HSCI (HiperSockets Converged Interfaces) settings. A HiperSockets interface and an external network interface are converged to an HSCI interface. Upstream-ID: 0566a492aec764c5405a94185fd6117fcd520249 --- Makefile | 2 +- README.md | 3 + hsci/Makefile | 16 ++ hsci/hsci | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++ hsci/hsci.8 | 100 ++++++++++++ 5 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 hsci/Makefile create mode 100644 hsci/hsci create mode 100644 hsci/hsci.8 diff --git a/Makefile b/Makefile index cc277b8..cfbbd95 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \ 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 \ - genprotimg lsstp + genprotimg lsstp hsci SUB_DIRS = $(LIB_DIRS) $(TOOL_DIRS) diff --git a/README.md b/README.md index 6fb6831..5a2153c 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,9 @@ Package contents Management Foundation - Web Edition, and is used to manage keys in an enterprise. + * hsci: + Manage HiperSockets Converged Interfaces (HSCI). + For more information refer to the following publications: * "Device Drivers, Features, and Commands" chapter "Useful Linux commands" diff --git a/hsci/Makefile b/hsci/Makefile new file mode 100644 index 0000000..6f7474c --- /dev/null +++ b/hsci/Makefile @@ -0,0 +1,16 @@ +include ../common.mak + +all: + +install: hsci + $(SED) -e 's/%S390_TOOLS_VERSION%/$(S390_TOOLS_RELEASE)/' \ + < hsci >$(DESTDIR)$(BINDIR)/hsci; \ + chown $(OWNER).$(GROUP) $(DESTDIR)$(BINDIR)/hsci; \ + chmod 755 $(DESTDIR)$(BINDIR)/hsci; \ + $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man8 + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 644 hsci.8 \ + $(DESTDIR)$(MANDIR)/man8 + +clean: + +.PHONY: all install clean diff --git a/hsci/hsci b/hsci/hsci new file mode 100644 index 0000000..9a56aa0 --- /dev/null +++ b/hsci/hsci @@ -0,0 +1,441 @@ +#!/bin/bash +# +# hsci - Tool to manage HiperSockets Converged Interfaces (HSCI) +# +# 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. +# + +hsdev="" +ndev="" +hsci="" +hsdev_mac="" +hsif_pnetid="" +netif_pnetid="" +hsci_pnetid="" + +function usage { +cat <<-EOD +Usage: hsci COMMAND [OPTION] + +This tool is designed to control and show HSCI (HiperSockets Converged +Interfaces) settings. A HiperSockets interface and an external network + +COMMANDS + add HIPERSOCKETS_DEV NET_DEV Adds an HSCI interface + del HSCI_NAME Deletes an HSCI interface + show Lists the configured HSCI interfaces + +OPTIONS: + -v, --version Prints the version number of the hsci tool and exits + -h, --help Displays the help information for the command +EOD +} + +function prereqs_check { + if ! [ -x "$(command -v ip)" ]; then + echo "Error: No iproute2 installed on this system" >&2 + return 1 + fi +} + +function check_pnetids { + # get PNETID of the HS + local hsif_pnetids="" + local netif_pnetids="" + + if [ -e /sys/class/net/$hsdev/device/util_string ]; then + hsif_pnetids="$(cat /sys/class/net/$hsdev/device/util_string | tr -d '\000' | iconv -f IBM-1047 -t ASCII)" + else + if [ -e /sys/class/net/$hsdev/device/chpid ]; then + chpid="$(cat /sys/class/net/$hsdev/device/chpid | tr [:upper:] [:lower:])" + hsif_pnetids="$(cat /sys/devices/css0/chp0.$chpid/util_string | tr -d '\000' | iconv -f IBM-1047 -t ASCII)" + fi + fi + if [ "$hsif_pnetids" != "" ]; then + port_hsif="$(cat /sys/class/net/$hsdev/dev_port)" + (( idx=16*$port_hsif+1 )) + (( end=$idx+15 )) + hsif_pnetid="$(echo "$hsif_pnetids" | cut -c $idx-$end | tr -d ' ')" + fi + + # get PNETID of the NET_DEV + if [ -e /sys/class/net/$ndev/device/util_string ]; then + netif_pnetids="$(cat /sys/class/net/$ndev/device/util_string | tr -d '\000' | iconv -f IBM-1047 -t ASCII)" + else + if [ -e /sys/class/net/$ndev/device/chpid ]; then + chpid="$(cat /sys/class/net/$ndev/device/chpid | tr [:upper:] [:lower:])" + netif_pnetids="$(cat /sys/devices/css0/chp0.$chpid/util_string | tr -d '\000' | iconv -f IBM-1047 -t ASCII)" + fi + fi + if [ "$netif_pnetids" != "" ]; then + port_netif="$(cat /sys/class/net/$ndev/dev_port)" + (( idx=16*$port_netif+1 )) + (( end=$idx+15 )) + netif_pnetid="$(echo "$netif_pnetids" | cut -c $idx-$end | tr -d ' ')" + fi + + #Check PNETIDs + if [ "$hsif_pnetid" != "" ] && [ "$netif_pnetid" != "" ] && [ "$netif_pnetid" != "$hsif_pnetid" ]; then + echo "Error: $hsdev and $ndev have different PNETIDs! They are $hsif_pnetid and $netif_pnetid respectively" >&2 + return 1 + fi + + if [ "$hsif_pnetid" != "" ] && [ "$netif_pnetid" != "" ] && [ "$netif_pnetid" == "$hsif_pnetid" ]; then + hsci_pnetid=$hsif_pnetid + fi +} + +function verify_precon { + echo "Verifying net dev $ndev and HiperSockets dev $hsdev" + + if [ ! -e /sys/class/net/$hsdev ]; then + echo "Error: $hsdev does not exist" >&2 + return 1 + fi + if [ "$(cat /sys/class/net/$hsdev/device/card_type)" != "HiperSockets" ]; then + echo "Error: $hsdev is not a HiperSockets device" >&2 + return 1 + fi + if [ "$(cat /sys/class/net/$hsdev/device/layer2)" != "1" ]; then + echo "Error: $hsdev is not in layer 2 mode" >&2 + return 1 + fi + if [ ! -e /sys/class/net/$hsdev/device/vnicc/bridge_invisible ]; then + echo "Error: Missing vnic-characteristics support" >&2 + return 1 + fi + if [ "$(cat /sys/class/net/$hsdev/device/vnicc/bridge_invisible)" == "n/a" ]; then + echo "Error: $hsdev does not support vnicc" >&2 + return 1 + fi + if [ $(ip link show $hsdev | grep UP | wc -l) -eq 0 ]; then + echo "Error: $hsdev is not in state UP" >&2 + return 1 + fi + if [ $(bridge -d link show dev $hsdev self | grep learning_sync | wc -l) -eq 0 ]; then + echo "Error: $hsdev does not support attribute learning_sync" >&2 + return 1 + fi + if [ $(ip link show $hsdev | grep master | wc -l) -ne 0 ]; then + echo "Error: $hsdev is already a bridge port" >&2 + return 1 + fi + + #Pre-verify net_dev + if [ ! -e /sys/class/net/$ndev ]; then + echo "Error: $ndev does not exist" >&2 + return 1 + fi + if [ "$(cat /sys/class/net/$ndev/device/card_type)" == "HiperSockets" ]; then + echo "Error: $ndev is also a HiperSockets device" >&2 + return 1 + fi + if [ $(ip link show $ndev | grep UP | wc -l) -eq 0 ]; then + echo "Error: $ndev is not in state UP" >&2 + return 1 + fi + if [ $(ip link show $ndev | grep master | wc -l) -ne 0 ]; then + echo "Error: $ndev is already a bridge port" >&2 + return 1 + fi + + #Check PNETIDs + check_pnetids + if [ $? -ne 0 ]; then + return $? + fi + + return 0 +} + +function clean_up { + bridge link set dev $hsdev learning_sync off self >/dev/null 2>&1 + echo 0 > /sys/class/net/$hsdev/device/vnicc/bridge_invisible >/dev/null 2>&1 + bridge fdb del $hsdev_mac dev $ndev >/dev/null 2>&1 + ip link del $hsci >/dev/null 2>&1 +} + +############################################################################## +## add a new HSCI interface +############################################################################## +function add_hsci { + + if [ $# != 2 ]; then + echo "hsci: Invalid parameters" >&2 + echo "Use 'hsci --help' for more information" >&2 + return 1 + fi + hsdev=$1 + ndev=$2 + + #### Verify preconditions + verify_precon + if [ $? -ne 0 ]; then + return $? + fi + + hsci_postfix="$(readlink /sys/class/net/$hsdev/device/cdev0 | tail -c5)" + hsci=hsci$hsci_postfix + + echo "Adding $hsci with a HiperSockets dev $hsdev and an external dev $ndev" + + #### Create bridge + ip link add name $hsci type bridge stp_state 0 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Could not create a bridge" >&2 + return 1 + fi + + #### Prepare hsdev + # Set VNICC of hsdev to invisible + #(mandatory for co-existence with HS-OSA bridges!) + echo 1 > /sys/class/net/$hsdev/device/vnicc/bridge_invisible + + #### Create bridge ports + ip link set dev $ndev master $hsci >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Could not set master for $ndev" >&2 + clean_up + return 1 + fi + ip link set dev $hsdev master $hsci >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Could not set master for $hsdev" >&2 + clean_up + return 1 + fi + + # no forwarding between ndev and hsdev -> isolated on + # ndev is default for outgoing unknown targets -> flood on + # no need to learn external LAN targets into fdb -> learning off + bridge link set dev $ndev isolated on learning off flood on mcast_flood on >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to set bridge attributes on $ndev" >&2 + clean_up + return 1 + fi + + # no forwarding between ndev and hsdev -> isolated on + # fdb will be populated by dev-to-bridge-notification, no need to learn + # -> learning off + # only send to hsdev, if listed in fdb -> flood off + # don't send MC/BC on hsdev -> mcast_flood off + bridge link set dev $hsdev isolated on learning off flood off mcast_flood off >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to set bridge attributes on $hsdev" >&2 + clean_up + return 1 + fi + + # NOTE: Although not required, BCs will be sent out on hsdev. + # NOTE: We need to receive BCs on hsdev, as z/OS HSCI does ARP requests on HS. + + hsdev_mac="$(cat /sys/class/net/$hsdev/address)" + echo "Set $hsdev MAC $hsdev_mac on $ndev and $hsci" + + # set HS MAC on OSA as secondary MAC + bridge fdb add $hsdev_mac dev $ndev >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to set HS MAC on OSA as secondary MAC" >&2 + clean_up + return 1 + fi + + # set HS MAC (common MAC) on HSCI as primary MAC + ip link set address $hsdev_mac dev $hsci >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to set HiperSockets MAC (common MAC) on HSCI as primary MAC" >&2 + clean_up + return 1 + fi + + # use hsdev MTU + if [ -e /sys/class/net/$hsdev/mtu ]; then + hs_mtu="$(cat /sys/class/net/$hsdev/mtu)" + ip link set dev $hsci mtu $hs_mtu >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to set MTU for $hsci " >&2 + clean_up + return 1 + fi + fi + ip link set dev $hsci up >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to set $hsci up" >&2 + clean_up + return 1 + fi + + # Turn on device for bridge notification + bridge link set dev $hsdev learning_sync on self >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to turn on device for bridge notification" >&2 + clean_up + return 1 + fi + echo "Successfully added HSCI interface $hsci" + return 0 +} + +############################################################################## +## Delete HSCI +############################################################################## + +function del_hsci { + if [ $# != 1 ]; then + echo "hsci: invalid parameters" >&2 + echo "Use 'hsci --help' for more information" >&2 + return 1 + fi + hsci=$1 + if [ $(ip link show dev $hsci | wc -l) -eq 0 ]; then + echo "Error: $hsci does not exit" >&2 + return 1 + fi + if [ $(ip link show | grep "master $hsci" | wc -l) -eq 0 ]; then + echo "Error: $hsci is not an active HSCI interface" >&2 + return 1 + fi + + bports="$(ip link show | grep "master $hsci" | awk '{print $2}')" + for bport in $bports; do + if [ $(bridge -d link show dev $bport | grep "learning_sync on" | wc -l) -ne 0 ]; then + hsdev=${bport%:} + else + ndev=${bport%:} + fi + done + if [ "$hsdev" == "" ]; then + echo "Error: $hsci has no active HiperSockets port" >&2 + return 1 + fi + echo "Deleting HSCI interface $hsci with the HiperSockets $hsdev and the external $ndev" + + bridge link set dev $hsdev learning_sync off self >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to turn off learning_sync on $hsdev" >&2 + return 1 + fi + echo 0 > /sys/class/net/$hsdev/device/vnicc/bridge_invisible + + hsdev_mac="$(cat /sys/class/net/$hsdev/address)" + echo "Deleting $hsev MAC $hsdev_mac on $ndev" + bridge fdb del $hsdev_mac dev $ndev >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to delete $hsev MAC $hsdev_mac on $ndev" >&2 + return 1 + fi + + ip link del $hsci >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Error: Failed to delete $hsci" >&2 + return 1 + fi + echo "Successfully deleted device $hsci" + + return 0 +} + +############################################################################## +## Show HSCI +############################################################################## + +function list_active { + hsdev=$1 + local ext="" + + hsci="$(ip link show dev $hsdev | awk '{for(x=1;x&2 + echo "Use 'hsci --help' for more information" >&2 + return 1 + fi + header=0 + + for hs_net_dev in $(ls -1 /sys/class/net/); do + list_one $hs_net_dev + done + + return 0 +} + +#============================================================================== + +function print_version() +{ + echo "hsci utility: version %S390_TOOLS_VERSION%" + echo "Copyright IBM Corp. 2020" +} + +############################################################################## +##### Main +############################################################################## +prereqs_check + +args="$(getopt -u -o hv -l help,version -- $*)" +[ $? -ne 0 ] && exit 2 +set -- $args +while true; do + case $1 in + -v | --version) + print_version + exit 0 + ;; + -h | --help) + usage + exit 0 + ;; + --) + ;; + add) shift + add_hsci "$@" + exit $? + ;; + del) shift + del_hsci "$@" + exit $? + ;; + show) shift + show_hsci "$@" + exit $? + ;; + *) echo "hsci: Please specify a valid command or option" >&2 + echo "Use 'hsci --help' for more information" >&2 + exit 1 + esac + shift +done + diff --git a/hsci/hsci.8 b/hsci/hsci.8 new file mode 100644 index 0000000..fc17053 --- /dev/null +++ b/hsci/hsci.8 @@ -0,0 +1,100 @@ +.\" Copyright IBM Corp. 2020 + +.TH HSCI 8 "November 2020" "s390-tools" "Linux Programmer's Manual" + + +.SH NAME +.B hsci +\- control and show HSCI settings. + + +.SH SYNOPSIS +.B hsci add +.I HSDEV +.I NETDEV +.br +.B hsci del +.I HSCINAME +.br +.B hsci show +.br +.B hsci [\-hv] + +.SH DESCRIPTION +.BI hsci +is used to control and show HSCI (HiperSockets Converged Interfaces) settings. A HiperSockets interface and an external network interface are converged into an HSCI interface. + +.SH COMMANDS +.TP +.B add \fIHSDEV\fR \fINETDEV\fR +.RS .4i +.PP +Adds an HSCI interface +.PP +.I HSDEV +is the interface name of the HiperSockets device to be converged into the HSCI interface. +.PP +.I NETDEV +is the interface name of the external network device to be converged into the HSCI interface. +.RE + +.TP +.B del \fIHSCINAME\fR +.RS .4i +.PP +Deletes an HSCI interface +.PP +.I HSCINAME +is the name of the HSCI interface for the HiperSockets device and the external network device. +.RE + +.TP +.B show +.RS .4i +.PP +Lists the configured HSCI interfaces. +.RE + +.SH OPTIONS +.TP +.BR \-v ", " \-\-version +Prints the version number of hsci and exits. +.TP +.BR \-h ", " \-\-help +Displays the help information for the command. + +.SH EXIT CODES +.TP +.BR "0" +The hsci command ran successfully. + +.TP +.BR "1" +An error occurred. + +.SH EXAMPLE +.BR "hsci show" +.TP +.RB +Lists the configured HSCI interfaces: +.RS 1.2i + +HSCI PNET_ID HiperSockets External +.br +----------------------------------------- +.br +hsci8410 NET1 enc8410 encb040 + +.RE + +.SH SEE ALSO +.nf +ip(8), bridge(8) +.fi + +.SH AUTHOR +.nf +Written by Alexandra Winter + Wenjia Zhang +.fi + -- 2.26.2 From 0b71c216d158a21c9905dad773a22c456d80b32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 7 Dec 2020 14:36:11 +0100 Subject: [PATCH 7/9] zcryptstats: Fix handling of partial results with many domains (#1901962) Description: zcryptstats: Fix handling of partial results with many domains Symptom: Running zcryptstats when many domains are available per cryto card does not produce any output, and is hanging in a loop. Problem: When many domains per card are available, then the results of the SCDMD CHSC call may not fit into the output area, and a partial result is returned. The further results must be retrieved with another CHSC call. Fix the code to pass the correct next-domain to the subsequent CHSC call of a partial response. Otherwise the same set of domains 1 to n are retrieved again, resulting in an infinite loop, because this will always produce a partial result. Solution: Fix the code to pass the correct next-domain to the subsequent CHSC call of a partial response. Reproduction: Run zcryptstats on a system with many domains per card. Upstream-ID: cf2311f1f1de17435b49ba8c8697be91705ba031 --- zconf/zcrypt/zcryptstats.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/zconf/zcrypt/zcryptstats.c b/zconf/zcrypt/zcryptstats.c index 3bb2078..2456e5d 100644 --- a/zconf/zcrypt/zcryptstats.c +++ b/zconf/zcrypt/zcryptstats.c @@ -1178,8 +1178,14 @@ static int get_apqn_measurement_data(uint8_t card) scdmd_area.request.header.code = 0x102d; scdmd_area.request.header.length = sizeof(struct chsc_scdmd_request); - scdmd_area.request.first_drid.ap_index = card; - scdmd_area.request.first_drid.domain_index = g.min_domain; + if (scdmd_area.response.p) { + scdmd_area.request.first_drid = + scdmd_area.response.crid; + } else { + scdmd_area.request.first_drid.ap_index = card; + scdmd_area.request.first_drid.domain_index = + g.min_domain; + } scdmd_area.request.last_drid.ap_index = card; scdmd_area.request.last_drid.domain_index = g.max_domain; scdmd_area.request.s = 1; @@ -1217,10 +1223,6 @@ static int get_apqn_measurement_data(uint8_t card) rc = process_apqn_measurement_data(&scdmd_area); if (rc != 0) break; - - if (scdmd_area.response.p) - scdmd_area.request.first_drid = - scdmd_area.response.crid; } while (scdmd_area.response.p); return rc; -- 2.26.2 From 42d4f61d02b268f6a4e04a18188905ffbd95c9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 7 Dec 2020 14:37:26 +0100 Subject: [PATCH 8/9] dasdfmt: Fix bad file descriptor error when running on symlinks (#1901963) Description: dasdfmt: Fix bad file descriptor error when running on symlinks Symptom: When calling dasdfmt on device node symlinks like /dev/disk/by-id/ccw-0X9300, dasdfmt fails with "dasdfmt: the ioctl to get the blocksize of the device failed (Bad file descriptor)" Problem: This is because before the actual formatting process starts, the disk will be disabled calling the BIODASDDISABLE ioctl, resulting in the removal of the symlink. Trying to open this file later in the process to retrieve e.g. blocksize information results in the mentioned error, as the file doesn't exist any longer. Solution: In order to fix this without modifying the behaviour of libdasd, introduce the two global variables dev_node and dev_path. dev_path is the original device path entered by the user. dev_node on the other hand is the reliable device node under /dev/block/ using the major and minor numbers and is determined in get_device_name(). The dev_path is used for message output only and the dev_node variable is used for the actual disk operations. Reproduction: Simply call dasdfmt on a device node symlink in /dev/disk/ such as /dev/disk/by-id/ccw-0X9300 for example. Upstream-ID: 148d3f9b64da599adf453baf65e7a8596e2e7d97 Upstream-ID: cad450fdf9a9dd2562eec27157e4fd133a98813e --- dasdfmt/dasdfmt.c | 2 +- dasdfmt/dasdfmt.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dasdfmt/dasdfmt.c b/dasdfmt/dasdfmt.c index a424f3c..5665f64 100644 --- a/dasdfmt/dasdfmt.c +++ b/dasdfmt/dasdfmt.c @@ -1637,7 +1637,7 @@ int main(int argc, char *argv[]) /* End of options string - start of devices list */ break; default: - error("Try '%s --help' for more information."); + error("Try '%s --help' for more information.", prog_name); } if (rc == -1) diff --git a/dasdfmt/dasdfmt.h b/dasdfmt/dasdfmt.h index 9d35eed..4d6fbb5 100644 --- a/dasdfmt/dasdfmt.h +++ b/dasdfmt/dasdfmt.h @@ -41,6 +41,10 @@ static const char mode_str[3][10] = { "Full", "Quick", "Expand" }; +/* Report error, free memory, and exit */ +static void error(const char *format, ...) + __attribute__((__noreturn__, __format__(__printf__, 1, 2))); + #define DASD_PARTN_BITS 2 #define PARTN_MASK ((1 << DASD_PARTN_BITS) - 1) -- 2.26.2 From 2e86043c334aa5a48878eda656e292fb408dd912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 7 Dec 2020 14:38:35 +0100 Subject: [PATCH 9/9] zkey: Fix KMS plugin configuration to store APQNs correctly. (#1901968) Description: zkey: Fix KMS plugin configuration to store APQNs correctly. Symptom: When a KMS plugin is configured with APQNs, the set of APQNs is stored per card type, i.e. the set of CCA APQNs and the set of EP11 APQNs is stored separately in the KMS plugin configuration file. Unfortunately, the names of the configuration properties are swapped, so that CCA APQNs are stored as EP11 APQNs, and vice versa. This does not cause any malfunction as of today, however if this is fixed later, while a KMS plugin configuration already exists, then the KMS plugin will fail to work once the fix is applied. A KMS plugin reconfiguration would then be needed to make the plugin work again. Problem: The KMS configuration property names to store the CCA and EP11 APQNs are incorrect, i.e. swapped. Solution: Correct the KMS configuration property names. Reproduction: Configure a KMS plugin with APQNs and check the KMS config file. Upstream-ID: 07d181e29b484108bce5ea07c1561ffb62a1b56e --- zkey/kms.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zkey/kms.c b/zkey/kms.c index b2ce165..85e3ff2 100644 --- a/zkey/kms.c +++ b/zkey/kms.c @@ -46,8 +46,8 @@ #define KMS_CONFIG_PROP_KMS "kms" #define KMS_CONFIG_PROP_KMS_CONFIG "config" #define KMS_CONFIG_PROP_APQNS "apqns" -#define KMS_CONFIG_PROP_CCA_APQNS "ep11_apqns" -#define KMS_CONFIG_PROP_EP11_APQNS "cca_apqns" +#define KMS_CONFIG_PROP_CCA_APQNS "cca_apqns" +#define KMS_CONFIG_PROP_EP11_APQNS "ep11_apqns" #define KMS_CONFIG_LOCAL "local" #define KMS_KEY_PROP_NAME "zkey-name" -- 2.26.2