3479 lines
109 KiB
Diff
3479 lines
109 KiB
Diff
|
From b67ac1d584e00c8c36c885a2fd36709a16809d86 Mon Sep 17 00:00:00 2001
|
||
|
From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan@danny.cz>
|
||
|
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 <mhartmay@linux.ibm.com>
|
||
|
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 <mhartmay@linux.ibm.com>
|
||
|
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
|
||
|
(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 <mhartmay@linux.ibm.com>
|
||
|
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 <mhartmay@linux.ibm.com>
|
||
|
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
|
||
|
(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 <mhartmay@linux.ibm.com>
|
||
|
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 <bwalk@linux.ibm.com>
|
||
|
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
|
||
|
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
|
||
|
(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 <mhartmay@linux.ibm.com>
|
||
|
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 <patrick.steuer@de.ibm.com>
|
||
|
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
|
||
|
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
|
||
|
(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 <errno.h>
|
||
|
#include <glib.h>
|
||
|
#include <openssl/evp.h>
|
||
|
+#include <openssl/x509.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
@@ -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 <openssl/evp.h>
|
||
|
#include <openssl/pem.h>
|
||
|
#include <openssl/rand.h>
|
||
|
+#include <openssl/x509.h>
|
||
|
+#include <openssl/x509v3.h>
|
||
|
+#include <openssl/x509_vfy.h>
|
||
|
+#include <openssl/err.h>
|
||
|
+#include <stdio.h>
|
||
|
#include <stdint.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
@@ -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 <glib.h>
|
||
|
+#include <openssl/asn1.h>
|
||
|
#include <openssl/bio.h>
|
||
|
#include <openssl/bn.h>
|
||
|
#include <openssl/ec.h>
|
||
|
#include <openssl/ecdh.h>
|
||
|
#include <openssl/evp.h>
|
||
|
+#include <openssl/ossl_typ.h>
|
||
|
#include <openssl/rand.h>
|
||
|
+#include <openssl/safestack.h>
|
||
|
#include <openssl/sha.h>
|
||
|
#include <openssl/x509.h>
|
||
|
+#include <openssl/x509v3.h>
|
||
|
#include <stdint.h>
|
||
|
|
||
|
#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 <stdio.h>
|
||
|
+#include <glib.h>
|
||
|
+#include <glib/gtypes.h>
|
||
|
+#include <curl/curl.h>
|
||
|
+
|
||
|
+#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 <glib.h>
|
||
|
+#include <curl/curl.h>
|
||
|
+
|
||
|
+#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?= <dan@danny.cz>
|
||
|
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<NF;x++) if($x~/master/) print $(x+1)}')"
|
||
|
+ ext="$(ip link show | grep "master $hsci" | grep --invert-match $hsdev | awk '{print $2}')"
|
||
|
+ # remove trailing ':'
|
||
|
+ ndev="${ext%:}"
|
||
|
+
|
||
|
+ check_pnetids
|
||
|
+
|
||
|
+ printf '%-8s %-16s %-15s %-15s\n' "$hsci" "$hsci_pnetid" "$hsdev" "$ndev"
|
||
|
+
|
||
|
+ return 0
|
||
|
+}
|
||
|
+
|
||
|
+function print_header {
|
||
|
+ if [ $header -eq 0 ]; then
|
||
|
+ echo "HSCI PNET_ID HiperSockets External "
|
||
|
+ echo "------------------------------------------------------------"
|
||
|
+ fi
|
||
|
+}
|
||
|
+
|
||
|
+function list_one {
|
||
|
+ local hsnetdev=$1
|
||
|
+
|
||
|
+ if [ $(bridge -d link show dev $hsnetdev | grep "learning_sync on" | wc -l) -ne 0 ]; then
|
||
|
+ print_header
|
||
|
+ list_active $hsnetdev
|
||
|
+ fi
|
||
|
+
|
||
|
+ return 0
|
||
|
+}
|
||
|
+
|
||
|
+function show_hsci {
|
||
|
+ if [ $# != 0 ]; then
|
||
|
+ echo "hsci: invalid parameters" >&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 <wintera@linux.ibm.com>
|
||
|
+ Wenjia Zhang <wenjia@linux.ibm.com>
|
||
|
+.fi
|
||
|
+
|
||
|
--
|
||
|
2.26.2
|
||
|
|
||
|
|
||
|
From 0b71c216d158a21c9905dad773a22c456d80b32d Mon Sep 17 00:00:00 2001
|
||
|
From: =?UTF-8?q?Dan=20Hor=C3=A1k?= <dan@danny.cz>
|
||
|
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?= <dan@danny.cz>
|
||
|
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?= <dan@danny.cz>
|
||
|
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
|
||
|
|