diff --git a/.gitignore b/.gitignore index 2e735dc..77e1515 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ SOURCES/cmsfs-1.1.8c.tar.gz -SOURCES/s390-tools-2.25.0.tar.gz +SOURCES/s390-tools-2.29.0-rust-vendor.tar.xz +SOURCES/s390-tools-2.29.0.tar.gz SOURCES/src_vipa-2.1.0.tar.gz diff --git a/.s390utils.metadata b/.s390utils.metadata index 3c07f8b..2d9d312 100644 --- a/.s390utils.metadata +++ b/.s390utils.metadata @@ -1,3 +1,4 @@ 9c9a4e89bddb2b4e6e09ef6fc7c2e6f2ad6316de SOURCES/cmsfs-1.1.8c.tar.gz -e8e0d3f651179fd14dc4a40d53a1e4ef6edaae7d SOURCES/s390-tools-2.25.0.tar.gz +1dcae3e55c2d4d945d0b5c61a12671468aa5f7ef SOURCES/s390-tools-2.29.0-rust-vendor.tar.xz +e10ffbde7f3fcf4438fdfdd83051ad68518e7be5 SOURCES/s390-tools-2.29.0.tar.gz 8ed8592a0a9370ce8422df9231ccb17f6cf49bed SOURCES/src_vipa-2.1.0.tar.gz diff --git a/SOURCES/s390-tools-zipl-blscfg-rpm-nvr-sort.patch b/SOURCES/s390-tools-zipl-blscfg-rpm-nvr-sort.patch index 4c55e19..366efba 100644 --- a/SOURCES/s390-tools-zipl-blscfg-rpm-nvr-sort.patch +++ b/SOURCES/s390-tools-zipl-blscfg-rpm-nvr-sort.patch @@ -1,4 +1,4 @@ -From a17c57bf2b7b6d64a509cb5fb02fe46849bc550c Mon Sep 17 00:00:00 2001 +From b2daaa34776ba6afec879e362378f6f7563590a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Hor=C3=A1k?= Date: Mon, 20 Jun 2022 17:43:05 +0200 Subject: [PATCH 1/2] Revert "zipl/src: Implement sorting bls entries by @@ -194,10 +194,10 @@ index 0cea1d4..9352f76 100644 return n; -- -2.38.1 +2.39.2 -From 7a51cfc15b870d90bffe1e24a1da922663ffe1d7 Mon Sep 17 00:00:00 2001 +From 692e70bcfc32a05e30146bd7077c41e0eaceff03 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Mon, 20 Jun 2022 17:46:59 +0200 Subject: [PATCH 2/2] blscfg: sort like rpm nvr, not like a single version @@ -213,7 +213,7 @@ Signed-off-by: Dan Horák 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/zipl/src/Makefile b/zipl/src/Makefile -index 64eabe4..7043005 100644 +index cab5655..7ec215d 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -9,6 +9,7 @@ ALL_LDFLAGS += -Wl,-z,noexecstack $(NO_PIE_LDFLAGS) @@ -223,7 +223,7 @@ index 64eabe4..7043005 100644 + -lrpmio -lrpm objects = misc.o error.o scan.o job.o boot.o bootmap.o fs-map.o disk.o \ - bootmap_header.o envblk.o install.o zipl.o $(rootdir)/zipl/boot/data.o + bootmap_header.o envblk.o install.o zipl.o diff --git a/zipl/src/scan.c b/zipl/src/scan.c index 9352f76..3327e2d 100644 --- a/zipl/src/scan.c @@ -344,5 +344,5 @@ index 9352f76..3327e2d 100644 static int scan_append_section_heading(struct scan_token* scan, int* index, char* name); -- -2.38.1 +2.39.2 diff --git a/SOURCES/s390utils-2.25.0-rhel.patch b/SOURCES/s390utils-2.25.0-rhel.patch deleted file mode 100644 index 8bc5883..0000000 --- a/SOURCES/s390utils-2.25.0-rhel.patch +++ /dev/null @@ -1,205 +0,0 @@ -From 48324b579e825a30110abac0369e9d544350ead1 Mon Sep 17 00:00:00 2001 -From: Steffen Eiden -Date: Wed, 14 Dec 2022 14:25:21 +0100 -Subject: [PATCH 1/4] zdump: replace atomic refcount with atomic int - -(Atomic) refcounting was introduced in glib2.58. -We want to support v2.56 as well, so replace it with int and the atomic -operations introduced in glib 2.4. - -Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/146 -Reviewed-by: Jan Hoeppner -Signed-off-by: Steffen Eiden ---- - zdump/pv_utils.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/zdump/pv_utils.c b/zdump/pv_utils.c -index 6d9f0bb..a39bfc5 100644 ---- a/zdump/pv_utils.c -+++ b/zdump/pv_utils.c -@@ -674,7 +674,7 @@ struct _storage_state_mmap { - size_t mapped_size; - pv_tweak_component_t *tweak_components; - size_t num_tweaks; -- gatomicrefcount ref_count; -+ int ref_count; - }; - - storage_state_mmap_t *storage_state_mmap_new(const int fd, const size_t file_size, const u64 offset, -@@ -733,14 +733,14 @@ storage_state_mmap_t *storage_state_mmap_new(const int fd, const size_t file_siz - ret->tweak_components = (pv_tweak_component_t *)(ptr + in_page_offset); - ret->num_tweaks = tweak_components_cnt; - ret->mapped_size = mmapped_size; -- g_atomic_ref_count_init(&ret->ref_count); -+ ret->ref_count = 1; - return g_steal_pointer(&ret); - } - - storage_state_mmap_t *storage_state_mmap_ref(storage_state_mmap_t *storage_state) - { - g_assert(storage_state); -- g_atomic_ref_count_inc(&storage_state->ref_count); -+ g_atomic_int_inc(&storage_state->ref_count); - return storage_state; - } - -@@ -748,7 +748,7 @@ void storage_state_mmap_unref(storage_state_mmap_t *storage_state) - { - if (!storage_state) - return; -- if (storage_state->ref_count && !g_atomic_ref_count_dec(&storage_state->ref_count)) -+ if (storage_state->ref_count && !g_atomic_int_dec_and_test(&storage_state->ref_count)) - return; - if (storage_state->first_page_ptr) { - int rc = munmap(storage_state->first_page_ptr, storage_state->mapped_size); --- -2.39.1 - - -From 4007220d35a9e33186fdfe3a4b5c22cd2eea9bb9 Mon Sep 17 00:00:00 2001 -From: Steffen Eiden -Date: Wed, 14 Dec 2022 13:53:29 +0100 -Subject: [PATCH 2/4] libpv: disallow glib features from after 2.56 - -Enforce that the first glib.h include is done via glib-helper.h for libpv -so that glib version checks are in place. - -Change zdump and pvattest such that they never include glibstuff before -libpv/glib-helper.h - -Reviewed-by: Jan Hoeppner -Signed-off-by: Steffen Eiden ---- - include/libpv/glib-helper.h | 6 ++++++ - pvattest/src/common.h | 2 +- - zdump/dfi_pv_elf.c | 1 - - 3 files changed, 7 insertions(+), 2 deletions(-) - -diff --git a/include/libpv/glib-helper.h b/include/libpv/glib-helper.h -index 5d3df50..9f90b28 100644 ---- a/include/libpv/glib-helper.h -+++ b/include/libpv/glib-helper.h -@@ -18,6 +18,12 @@ - #define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_56 - #endif - -+#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_56 -+ -+#ifdef __G_LIB_H__ -+#error "glib.h must be included via libpv/glib-helper.h" -+#endif -+ - #include - #include - #include -diff --git a/pvattest/src/common.h b/pvattest/src/common.h -index e2e3ef1..43d2ab9 100644 ---- a/pvattest/src/common.h -+++ b/pvattest/src/common.h -@@ -11,10 +11,10 @@ - /* Must be included before any other header */ - #include "config.h" - --#include - #include - - #include "libpv/glib-helper.h" -+#include - #include "libpv/macros.h" - #include "lib/zt_common.h" - -diff --git a/zdump/dfi_pv_elf.c b/zdump/dfi_pv_elf.c -index 8d1021e..9d33e8f 100644 ---- a/zdump/dfi_pv_elf.c -+++ b/zdump/dfi_pv_elf.c -@@ -21,7 +21,6 @@ - #include - #include - --#include - #include - #include - --- -2.39.1 - - -From fb01fb45bb6a9e62313e9cdb79ad5ed39471cfe7 Mon Sep 17 00:00:00 2001 -From: Marc Hartmayer -Date: Mon, 19 Dec 2022 09:51:51 +0000 -Subject: [PATCH 3/4] zgetdump/Makefile: don't use `.check_dep_zgetdump` as - linker input - -The `.check_dep_zgetdump` file is used to cache the result of the -dependency checks and should not be used as input for linking or -anything else. Let's add it as dependency for the objects file. This -shouldn't cause any problems since the Makefile rule for object files is -defined in `common.mak` as follows: - -%.o: %.c - $(CC) $(ALL_CPPFLAGS) $(ALL_CFLAGS) -c $< -o $@ - -Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/147 -Fixes: 8d8d5e9746a4 ("zdump: Fix Makefile dependencies") -Signed-off-by: Marc Hartmayer -Reviewed-by: Jan Hoeppner -Signed-off-by: Jan Hoeppner ---- - zdump/Makefile | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/zdump/Makefile b/zdump/Makefile -index ca8aadc..934dc47 100644 ---- a/zdump/Makefile -+++ b/zdump/Makefile -@@ -119,7 +119,9 @@ libs = $(rootdir)/libutil/libutil.a $(LIBPV) - - all: $(BUILD_TARGETS) - --zgetdump: .check_dep_zgetdump $(OBJECTS) $(libs) -+$(OBJECTS): .check_dep_zgetdump -+ -+zgetdump: $(OBJECTS) $(libs) - - skip-zgetdump: - echo " SKIP zgetdump due to unresolved dependencies" --- -2.39.1 - - -From 1ad208c17a0edc65e6abd47b68a7d9c206faf2a6 Mon Sep 17 00:00:00 2001 -From: Ingo Franzki -Date: Fri, 20 Jan 2023 11:04:18 +0100 -Subject: [PATCH 4/4] zkey: Support EP11 host library version 4 (#2165811) -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Try to load libep11.so.4 if available, but fallback to older -library versions if not. - -Reviewed-by: Jörg Schmidbauer -Signed-off-by: Ingo Franzki -Signed-off-by: Steffen Eiden -(cherry picked from commit 6222c384958729bc4b5bad61ad38967647cc3248) ---- - zkey/ep11.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/zkey/ep11.c b/zkey/ep11.c -index 58dc3c5..8359929 100644 ---- a/zkey/ep11.c -+++ b/zkey/ep11.c -@@ -35,7 +35,7 @@ - * Definitions for the EP11 library - */ - #define EP11_LIBRARY_NAME "libep11.so" --#define EP11_LIBRARY_VERSION 3 -+#define EP11_LIBRARY_VERSION 4 - #define EP11_WEB_PAGE "http://www.ibm.com/security/cryptocards" - - /** --- -2.39.1 - diff --git a/SOURCES/s390utils-2.29.0-rhel.patch b/SOURCES/s390utils-2.29.0-rhel.patch new file mode 100644 index 0000000..22f4d8f --- /dev/null +++ b/SOURCES/s390utils-2.29.0-rhel.patch @@ -0,0 +1,6998 @@ +From a32824922cb273703bacd44e6a29cbc33ae48cf5 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Fri, 21 Jul 2023 14:06:18 +0200 +Subject: [PATCH 01/22] zkey: Support EP11 AES keys with prepended header to + retain EP11 session (RHEL-11440) + +The pkey kernel module supports two key blob formats for EP11 AES keys. +The first one (PKEY_TYPE_EP11) contains a 16 bytes header that overlays +the first 32 bytes of the key blob which usually contain the ID of the +EP11 session to which the key is bound. For zkey/dm-crypt that session +ID used to be all zeros. The second blob format (PKEY_TYPE_EP11_AES) +prepends the 16 bytes header to the blob, an thus does not overlay the +blob. This format can be used for key blobs that are session-bound, i.e. +have a non-zero session ID in the first 32 bytes. + +Change zkey to generate EP11 keys using the new format (i.e. pkey type +PKEY_TYPE_EP11_AES), but existing key blobs using the old format can +still be used. + +Signed-off-by: Ingo Franzki +Reviewed-by: Joerg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit 1b044b8a40ab59e4f5ffe66e3ad81499b0beccce) +--- + zkey/ep11.c | 48 +++++++++----- + zkey/keystore.c | 4 +- + zkey/kmip/zkey-kmip.c | 76 ++++++++++++++++++---- + zkey/kms.c | 9 ++- + zkey/pkey.c | 141 +++++++++++++++++++++++++++++++++++++++-- + zkey/pkey.h | 45 +++++++++---- + zkey/zkey-cryptsetup.c | 15 ++++- + zkey/zkey.c | 8 ++- + 8 files changed, 295 insertions(+), 51 deletions(-) + +diff --git a/zkey/ep11.c b/zkey/ep11.c +index 83599297..df8b57df 100644 +--- a/zkey/ep11.c ++++ b/zkey/ep11.c +@@ -365,8 +365,9 @@ int select_ep11_apqn_by_mkvp(struct ep11_lib *ep11, u8 *mkvp, + * @param[in] target the target handle to use for the re-encipher operation + * @param[in] card the card that corresponds to the target handle + * @param[in] domain the domain that corresponds to the target handle +- * @param[in/out] ep11key the EP11 key token to reencipher. The re-enciphered +- * secure key will be returned in this buffer. ++ * @param[in/out] ep11key_blob the EP11 key token to reencipher. The ++ * re-enciphered secure key will be returned in this ++ * buffer. + * @param[in] ep11key_size the size of the secure key + * @param[in] verbose if true, verbose messages are printed + * +@@ -374,21 +375,29 @@ int select_ep11_apqn_by_mkvp(struct ep11_lib *ep11, u8 *mkvp, + */ + static int ep11_adm_reencrypt(struct ep11_lib *ep11, target_t target, + unsigned int card, unsigned int domain, +- struct ep11keytoken *ep11key, ++ u8 *ep11key_blob, + unsigned int ep11key_size, bool verbose) + { ++ struct ep11kblob_header *hdr = (struct ep11kblob_header *)ep11key_blob; ++ struct ep11keytoken *ep11key; + CK_BYTE resp[MAX_BLOBSIZE]; + CK_BYTE req[MAX_BLOBSIZE]; +- char ep11_token_header[sizeof(ep11key->head)]; ++ char ep11_token_header[sizeof(ep11key->head)] = { 0 }; + struct XCPadmresp lrb; + struct XCPadmresp rb; ++ bool with_header; + size_t resp_len; + size_t blob_len; + long req_len; + CK_RV rv; + int rc; + +- blob_len = ep11key->head.length; ++ with_header = is_ep11_aes_key_with_header(ep11key_blob, ep11key_size); ++ ep11key = (struct ep11keytoken *)(with_header ? ++ ep11key_blob + sizeof(struct ep11kblob_header) : ++ ep11key_blob); ++ blob_len = with_header ? hdr->len - sizeof(struct ep11kblob_header) : ++ ep11key->head.len; + if (blob_len > ep11key_size) { + pr_verbose(verbose, "Blob length larger than secure key size"); + return -EINVAL; +@@ -397,9 +406,14 @@ static int ep11_adm_reencrypt(struct ep11_lib *ep11, target_t target, + rb.domain = domain; + lrb.domain = domain; + +- /* The token header is an overlay over the (all zero) session field */ +- memcpy(ep11_token_header, ep11key, sizeof(ep11_token_header)); +- memset(ep11key->session, 0, sizeof(ep11key->session)); ++ if (!with_header) { ++ /* ++ * The token header is an overlay over the (all zero) session ++ * field ++ */ ++ memcpy(ep11_token_header, ep11key, sizeof(ep11_token_header)); ++ memset(ep11key->session, 0, sizeof(ep11key->session)); ++ } + + resp_len = sizeof(resp); + req_len = ep11->dll_xcpa_cmdblock(req, sizeof(req), XCP_ADM_REENCRYPT, +@@ -446,7 +460,8 @@ static int ep11_adm_reencrypt(struct ep11_lib *ep11, target_t target, + } + + memcpy(ep11key, lrb.payload, blob_len); +- memcpy(ep11key, ep11_token_header, sizeof(ep11_token_header)); ++ if (!with_header) ++ memcpy(ep11key, ep11_token_header, sizeof(ep11_token_header)); + + return 0; + } +@@ -469,7 +484,6 @@ int reencipher_ep11_key(struct ep11_lib *ep11, target_t target, + unsigned int card, unsigned int domain, u8 *secure_key, + unsigned int secure_key_size, bool verbose) + { +- struct ep11keytoken *ep11key = (struct ep11keytoken *)secure_key; + CK_IBM_DOMAIN_INFO dinf; + CK_ULONG dinf_len = sizeof(dinf); + CK_RV rv; +@@ -493,17 +507,21 @@ int reencipher_ep11_key(struct ep11_lib *ep11, target_t target, + return -ENODEV; + } + +- rc = ep11_adm_reencrypt(ep11, target, card, domain, ep11key, ++ rc = ep11_adm_reencrypt(ep11, target, card, domain, secure_key, + secure_key_size, verbose); + if (rc != 0) + return rc; + + if (is_xts_key(secure_key, secure_key_size)) { +- secure_key += EP11_KEY_SIZE; +- secure_key_size -= EP11_KEY_SIZE; +- ep11key = (struct ep11keytoken *)secure_key; ++ if (is_ep11_aes_key_with_header(secure_key, secure_key_size)) { ++ secure_key += EP11_AES_KEY_SIZE; ++ secure_key_size -= EP11_AES_KEY_SIZE; ++ } else { ++ secure_key += EP11_KEY_SIZE; ++ secure_key_size -= EP11_KEY_SIZE; ++ } + +- rc = ep11_adm_reencrypt(ep11, target, card, domain, ep11key, ++ rc = ep11_adm_reencrypt(ep11, target, card, domain, secure_key, + secure_key_size, verbose); + if (rc != 0) + return rc; +diff --git a/zkey/keystore.c b/zkey/keystore.c +index 4efa2e45..c0a7037b 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -3398,7 +3398,9 @@ static int _keystore_perform_reencipher(struct keystore *keystore, + "CURRENT master key", name); + if (!selected && + !is_ep11_aes_key(secure_key, +- secure_key_size)) ++ secure_key_size) && ++ !is_ep11_aes_key_with_header(secure_key, ++ secure_key_size)) + print_msg_for_cca_envvars( + "secure AES key"); + } +diff --git a/zkey/kmip/zkey-kmip.c b/zkey/kmip/zkey-kmip.c +index a00c5dd5..e7b7c739 100644 +--- a/zkey/kmip/zkey-kmip.c ++++ b/zkey/kmip/zkey-kmip.c +@@ -5278,9 +5278,11 @@ static int _ep11_unwrap_key_rsa(struct plugin_handle *ph, + m_UnwrapKey_t dll_m_UnwrapKey; + const unsigned char *key_blob; + struct ep11keytoken *ep11key; ++ struct ep11kblob_header *hdr; + CK_MECHANISM mech = { 0 }; + CK_BYTE csum[7] = { 0 }; + CK_BBOOL ck_true = true; ++ int pkey_fd, rc; + CK_RV rv; + + CK_ATTRIBUTE template[] = { +@@ -5306,7 +5308,8 @@ static int _ep11_unwrap_key_rsa(struct plugin_handle *ph, + pr_verbose(&ph->pd, "Wrap hashing algorithm: %d", + ph->profile->wrap_hashing_algo); + +- if (*unwrapped_key_len < sizeof(struct ep11keytoken)) { ++ if (*unwrapped_key_len < sizeof(struct ep11kblob_header) + ++ sizeof(struct ep11keytoken)) { + _set_error(ph, "Key buffer is too small"); + return -EINVAL; + } +@@ -5381,19 +5384,68 @@ static int _ep11_unwrap_key_rsa(struct plugin_handle *ph, + 256 * 256 * csum[csum_len - 3] + + 256 * 256 * 256 * csum[csum_len - 4]; + +- /* Setup the EP11 token header */ +- ep11key = (struct ep11keytoken *)unwrapped_key; +- memset(&ep11key->session, 0, sizeof(ep11key->session)); +- ep11key->head.type = TOKEN_TYPE_NON_CCA; +- ep11key->head.length = *unwrapped_key_len; +- ep11key->head.version = TOKEN_VERSION_EP11_AES; +- ep11key->head.keybitlen = bit_len; +- +- pr_verbose(&ph->pd, "unwrapped bit length: %u", +- ep11key->head.keybitlen); ++ /* Prepend and setup the EP11 token header */ ++ hdr = (struct ep11kblob_header *)unwrapped_key; ++ ep11key = (struct ep11keytoken *) ++ (unwrapped_key + sizeof(struct ep11kblob_header)); ++ memmove(ep11key, unwrapped_key, *unwrapped_key_len); ++ *unwrapped_key_len += sizeof(struct ep11kblob_header); ++ memset(hdr, 0, sizeof(struct ep11kblob_header)); ++ hdr->type = TOKEN_TYPE_NON_CCA; ++ hdr->hver = 0; ++ hdr->len = *unwrapped_key_len; ++ hdr->version = TOKEN_VERSION_EP11_AES_WITH_HEADER; ++ hdr->bitlen = bit_len; ++ ++ pr_verbose(&ph->pd, "unwrapped bit length: %u", hdr->bitlen); + + /* return full length, blob is already zero padded */ +- *unwrapped_key_len = sizeof(struct ep11keytoken); ++ *unwrapped_key_len = ++ sizeof(struct ep11kblob_header) + sizeof(struct ep11keytoken); ++ ++ /* ++ * Check if the pkey module supports keys of type ++ * TOKEN_VERSION_EP11_AES_WITH_HEADER, older kernels may not support ++ * such keys. If it does not support such keys, convert the key to ++ * TOKEN_VERSION_EP11_AES type, if its session field is all zero ++ * (i.e. the key is not session bound). ++ */ ++ pkey_fd = open_pkey_device(ph->pd.verbose); ++ if (pkey_fd < 0) { ++ _set_error(ph, "Failed to open pkey device"); ++ return -EIO; ++ } ++ ++ rc = validate_secure_key(pkey_fd, unwrapped_key, *unwrapped_key_len, ++ NULL, NULL, NULL, ph->pd.verbose); ++ close(pkey_fd); ++ if (rc == -EINVAL || rc == -ENODEV) { ++ pr_verbose(&ph->pd, "The pkey kernel module does not support " ++ "PKEY_TYPE_EP11_AES, fall back to PKEY_TYPE_EP11"); ++ ++ if (is_ep11_key_session_bound(unwrapped_key, ++ *unwrapped_key_len)) { ++ _set_error(ph, "The unwrapped key is session bound. " ++ "Kernel support is required for such keys"); ++ return -EIO; ++ } ++ ++ key_blob_len = hdr->len; ++ *unwrapped_key_len -= sizeof(struct ep11kblob_header); ++ memmove(unwrapped_key, ++ unwrapped_key + sizeof(struct ep11kblob_header), ++ *unwrapped_key_len); ++ ep11key = (struct ep11keytoken *)unwrapped_key; ++ memset(&ep11key->session, 0, sizeof(ep11key->session)); ++ ep11key->head.type = TOKEN_TYPE_NON_CCA; ++ ep11key->head.len = key_blob_len - ++ sizeof(struct ep11kblob_header); ++ ep11key->head.version = TOKEN_VERSION_EP11_AES; ++ ep11key->head.bitlen = bit_len; ++ } else if (rc != 0) { ++ _set_error(ph, "Failed to validate unwrapped key"); ++ return rc; ++ } + + return 0; + } +diff --git a/zkey/kms.c b/zkey/kms.c +index 9892a9e5..2e33b222 100644 +--- a/zkey/kms.c ++++ b/zkey/kms.c +@@ -2175,7 +2175,7 @@ int generate_kms_key(struct kms_info *kms_info, const char *name, + else if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) + key_size = AESCIPHER_KEY_SIZE; + else if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) +- key_size = EP11_KEY_SIZE; ++ key_size = EP11_AES_KEY_SIZE; + else + return -ENOTSUP; + +@@ -2248,6 +2248,9 @@ int generate_kms_key(struct kms_info *kms_info, const char *name, + if (verbose) + util_hexdump_grp(stderr, NULL, key_blob, 4, key_blob_size, 0); + ++ if (is_ep11_aes_key(key_blob, key_blob_size)) ++ key_size = EP11_KEY_SIZE; ++ + /* Save ID and label of 1st key */ + rc = properties_set(key_props, xts ? PROP_NAME_KMS_XTS_KEY1_ID : + PROP_NAME_KMS_KEY_ID, key1_id); +@@ -3132,6 +3135,8 @@ int import_kms_key(struct kms_info *kms_info, const char *key1_id, + key_size = AESCIPHER_KEY_SIZE; + else if (is_ep11_aes_key(key_blob, key_blob_size)) + key_size = EP11_KEY_SIZE; ++ else if (is_ep11_aes_key_with_header(key_blob, key_blob_size)) ++ key_size = EP11_AES_KEY_SIZE; + + if (key_size == 0 || key_blob_size > key_size) { + pr_verbose(verbose, "Key '%s' has an unknown or unsupported " +@@ -3366,6 +3371,8 @@ int refresh_kms_key(struct kms_info *kms_info, struct properties *key_props, + key_size = AESCIPHER_KEY_SIZE; + else if (is_ep11_aes_key(key_blob, key_blob_size)) + key_size = EP11_KEY_SIZE; ++ else if (is_ep11_aes_key_with_header(key_blob, key_blob_size)) ++ key_size = EP11_AES_KEY_SIZE; + + if (key_size == 0 || key_blob_size > key_size) { + pr_verbose(verbose, "Key '%s' has an unknown or unsupported " +diff --git a/zkey/pkey.c b/zkey/pkey.c +index e013e06a..25820887 100644 +--- a/zkey/pkey.c ++++ b/zkey/pkey.c +@@ -858,7 +858,7 @@ static enum pkey_key_type key_type_to_pkey_type(const char *key_type) + if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) + return PKEY_TYPE_CCA_CIPHER; + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) +- return PKEY_TYPE_EP11; ++ return PKEY_TYPE_EP11_AES; + + return 0; + } +@@ -879,6 +879,8 @@ static size_t key_size_for_type(enum pkey_key_type type) + return AESCIPHER_KEY_SIZE; + case PKEY_TYPE_EP11: + return EP11_KEY_SIZE; ++ case PKEY_TYPE_EP11_AES: ++ return EP11_AES_KEY_SIZE; + default: + return 0; + } +@@ -924,6 +926,7 @@ int generate_secure_key_random(int pkey_fd, const char *keyfile, + return -ENOTSUP; + } + ++retry: + genseck2.size = keybits_to_keysize(keybits); + if (genseck2.size == 0) { + warnx("Invalid value for '--keybits'/'-c': '%lu'", keybits); +@@ -957,10 +960,33 @@ int generate_secure_key_random(int pkey_fd, const char *keyfile, + genseck2.keylen = size; + + rc = pkey_genseck2(pkey_fd, &genseck2, verbose); ++ if (rc == -EINVAL && genseck2.type == PKEY_TYPE_EP11_AES) { ++ /* ++ * Older kernels may not support gensek2 with key type ++ * PKEY_TYPE_EP11_AES, retry with PKEY_TYPE_EP11. ++ */ ++ pr_verbose(verbose, ++ "ioctl PKEY_GENSECK2 does not support " ++ "PKEY_TYPE_EP11_AES, fall back to PKEY_TYPE_EP11"); ++ ++ genseck2.type = PKEY_TYPE_EP11; ++ free(genseck2.apqns); ++ genseck2.apqns = NULL; ++ genseck2.apqn_entries = 0; ++ free(secure_key); ++ goto retry; ++ } + if (rc != 0) { + warnx("Failed to generate a secure key: %s", strerror(-rc)); + goto out; + } ++ if (rc == 0 && genseck2.type == PKEY_TYPE_EP11) { ++ if (is_ep11_key_session_bound(secure_key, size)) { ++ warnx("The generated key is session bound. Kernel " ++ "support is required for such keys"); ++ goto out; ++ } ++ } + + if (xts) { + free(genseck2.apqns); +@@ -1062,6 +1088,7 @@ int generate_secure_key_clear(int pkey_fd, const char *keyfile, + return -ENOTSUP; + } + ++retry: + clr2seck2.size = keybits_to_keysize(HALF_KEYSIZE_FOR_XTS( + clear_key_size * 8, xts)); + if (clr2seck2.size == 0) { +@@ -1096,10 +1123,33 @@ int generate_secure_key_clear(int pkey_fd, const char *keyfile, + clr2seck2.keylen = size; + + rc = pkey_clr2seck2(pkey_fd, &clr2seck2, verbose); ++ if (rc == -EINVAL && clr2seck2.type == PKEY_TYPE_EP11_AES) { ++ /* ++ * Older kernels may not support clr2seck2 with key type ++ * PKEY_TYPE_EP11_AES, retry with PKEY_TYPE_EP11. ++ */ ++ pr_verbose(verbose, ++ "ioctl PKEY_CLR2SECK2 does not support " ++ "PKEY_TYPE_EP11_AES, fall back to PKEY_TYPE_EP11"); ++ ++ clr2seck2.type = PKEY_TYPE_EP11; ++ free(clr2seck2.apqns); ++ clr2seck2.apqns = NULL; ++ clr2seck2.apqn_entries = 0; ++ free(secure_key); ++ goto retry; ++ } + if (rc != 0) { + warnx("Failed to generate a secure key: %s", strerror(-rc)); + goto out; + } ++ if (rc == 0 && clr2seck2.type == PKEY_TYPE_EP11) { ++ if (is_ep11_key_session_bound(secure_key, size)) { ++ warnx("The generated key is session bound. Kernel " ++ "support is required for such keys"); ++ goto out; ++ } ++ } + + if (xts) { + free(clr2seck2.apqns); +@@ -1486,6 +1536,8 @@ int get_master_key_verification_pattern(const u8 *key, size_t key_size, + struct aesdatakeytoken *datakey = (struct aesdatakeytoken *)key; + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + struct ep11keytoken *ep11key = (struct ep11keytoken *)key; ++ struct ep11keytoken *ep11key2 = ++ (struct ep11keytoken *)(key + sizeof(struct ep11kblob_header)); + + util_assert(key != NULL, "Internal error: secure_key is NULL"); + util_assert(mkvp != NULL, "Internal error: mkvp is NULL"); +@@ -1497,6 +1549,8 @@ int get_master_key_verification_pattern(const u8 *key, size_t key_size, + memcpy(mkvp, &cipherkey->kvp, sizeof(cipherkey->kvp)); + else if (is_ep11_aes_key(key, key_size)) + memcpy(mkvp, &ep11key->wkvp, sizeof(ep11key->wkvp)); ++ else if (is_ep11_aes_key_with_header(key, key_size)) ++ memcpy(mkvp, &ep11key2->wkvp, sizeof(ep11key2->wkvp)); + else + return -EINVAL; + +@@ -1593,9 +1647,43 @@ bool is_ep11_aes_key(const u8 *key, size_t key_size) + + if (ep11key->head.type != TOKEN_TYPE_NON_CCA) + return false; ++ if (ep11key->head.hver != 0) ++ return false; + if (ep11key->head.version != TOKEN_VERSION_EP11_AES) + return false; +- if (ep11key->head.length > key_size) ++ if (ep11key->head.len > key_size) ++ return false; ++ ++ if (ep11key->version != 0x1234) ++ return false; ++ ++ return true; ++} ++ ++/** ++ * Check if the specified key is a EP11 AES key token with external header. ++ * ++ * @param[in] key the secure key token ++ * @param[in] key_size the size of the secure key ++ * ++ * @returns true if the key is an EP11 AES token with external header type ++ */ ++bool is_ep11_aes_key_with_header(const u8 *key, size_t key_size) ++{ ++ struct ep11kblob_header *header = (struct ep11kblob_header *)key; ++ struct ep11keytoken *ep11key = ++ (struct ep11keytoken *)(key + sizeof(struct ep11kblob_header)); ++ ++ if (key == NULL || key_size < EP11_AES_KEY_SIZE) ++ return false; ++ ++ if (header->type != TOKEN_TYPE_NON_CCA) ++ return false; ++ if (header->hver != 0) ++ return false; ++ if (header->version != TOKEN_VERSION_EP11_AES_WITH_HEADER) ++ return false; ++ if (header->len > key_size) + return false; + + if (ep11key->version != 0x1234) +@@ -1604,6 +1692,33 @@ bool is_ep11_aes_key(const u8 *key, size_t key_size) + return true; + } + ++/** ++ * Check if the specified EP11 AES key is session bound. ++ * ++ * @param[in] key the secure key token ++ * @param[in] key_size the size of the secure key ++ * ++ * @returns true if the key is an EP11 AES token type ++ */ ++bool is_ep11_key_session_bound(const u8 *key, size_t key_size) ++{ ++ struct ep11keytoken *ep11key; ++ ++ if (is_ep11_aes_key(key, key_size)) { ++ ep11key = (struct ep11keytoken *)key; ++ return memcmp(ep11key->session + sizeof(ep11key->head), ++ ZERO_SESSION, sizeof(ep11key->session) - ++ sizeof(ep11key->head)) != 0; ++ } else if (is_ep11_aes_key_with_header(key, key_size)) { ++ ep11key = (struct ep11keytoken *) ++ (key + sizeof(struct ep11kblob_header)); ++ return memcmp(ep11key->session, ZERO_SESSION, ++ sizeof(ep11key->session)) != 0; ++ } else { ++ return false; ++ } ++} ++ + /** + * Check if the specified key is an XTS type key + * +@@ -1629,6 +1744,11 @@ bool is_xts_key(const u8 *key, size_t key_size) + is_ep11_aes_key(key + EP11_KEY_SIZE, + key_size - EP11_KEY_SIZE)) + return true; ++ } else if (is_ep11_aes_key_with_header(key, key_size)) { ++ if (key_size == 2 * EP11_AES_KEY_SIZE && ++ is_ep11_aes_key_with_header(key + EP11_AES_KEY_SIZE, ++ key_size - EP11_AES_KEY_SIZE)) ++ return true; + } + + return false; +@@ -1650,6 +1770,7 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) + struct aesdatakeytoken *datakey = (struct aesdatakeytoken *)key; + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + struct ep11keytoken *ep11key = (struct ep11keytoken *)key; ++ struct ep11kblob_header *hdr = (struct ep11kblob_header *)key; + + util_assert(bitsize != NULL, "Internal error: bitsize is NULL"); + +@@ -1672,10 +1793,17 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) + *bitsize += cipherkey->pl - 384; + } + } else if (is_ep11_aes_key(key, key_size)) { +- *bitsize = ep11key->head.keybitlen; ++ *bitsize = ep11key->head.bitlen; + if (key_size == 2 * EP11_KEY_SIZE) { + ep11key = (struct ep11keytoken *)(key + EP11_KEY_SIZE); +- *bitsize += ep11key->head.keybitlen; ++ *bitsize += ep11key->head.bitlen; ++ } ++ } else if (is_ep11_aes_key_with_header(key, key_size)) { ++ *bitsize = hdr->bitlen; ++ if (key_size == 2 * EP11_AES_KEY_SIZE) { ++ hdr = (struct ep11kblob_header *) ++ (key + EP11_AES_KEY_SIZE); ++ *bitsize += hdr->bitlen; + } + } else { + return -EINVAL; +@@ -1700,6 +1828,8 @@ const char *get_key_type(const u8 *key, size_t key_size) + return KEY_TYPE_CCA_AESCIPHER; + if (is_ep11_aes_key(key, key_size)) + return KEY_TYPE_EP11_AES; ++ if (is_ep11_aes_key_with_header(key, key_size)) ++ return KEY_TYPE_EP11_AES; + return NULL; + } + +@@ -2016,7 +2146,8 @@ int reencipher_secure_key(struct ext_lib *lib, u8 *secure_key, + return rc; + } + +- if (is_ep11_aes_key(secure_key, secure_key_size)) { ++ if (is_ep11_aes_key(secure_key, secure_key_size) || ++ is_ep11_aes_key_with_header(secure_key, secure_key_size)) { + /* EP11 secure key: need the EP11 host library */ + if (lib->ep11->lib_ep11 == NULL) { + rc = load_ep11_library(lib->ep11, verbose); +diff --git a/zkey/pkey.h b/zkey/pkey.h +index 5a5bc3c5..3b57c5f0 100644 +--- a/zkey/pkey.h ++++ b/zkey/pkey.h +@@ -39,6 +39,8 @@ struct tokenheader { + #define TOKEN_VERSION_PROTECTED_KEY 0x01 + #define TOKEN_VERSION_CLEAR_KEY 0x02 + #define TOKEN_VERSION_EP11_AES 0x03 ++#define TOKEN_VERSION_EP11_AES_WITH_HEADER 0x06 ++#define TOKEN_VERSION_EP11_ECC_WITH_HEADER 0x07 + + struct aesdatakeytoken { + u8 type; /* TOKEN_TYPE_INTERNAL (0x01) for internal key token */ +@@ -89,17 +91,20 @@ struct aescipherkeytoken { + u8 varpart[80]; /* variable part */ + } __packed; + ++struct ep11kblob_header { ++ u8 type; /* always 0x00 */ ++ u8 hver; /* header version, currently needs to be 0x00 */ ++ u16 len; /* total length in bytes (including this header) */ ++ u8 version; /* PKEY_TYPE_EP11_AES or PKEY_TYPE_EP11_ECC */ ++ u8 res0; /* unused */ ++ u16 bitlen; /* clear key bit len, 0 for unknown */ ++ u8 res1[8]; /* unused */ ++} __packed; ++ + struct ep11keytoken { + union { + u8 session[32]; +- struct { +- u8 type; /* TOKEN_TYPE_NON_CCA (0x00) */ +- u8 res0; /* unused */ +- u16 length; /* length of token */ +- u8 version; /* TOKEN_VERSION_EP11_AES (0x03) */ +- u8 res1; /* unused */ +- u16 keybitlen; /* clear key bit len, 0 for unknown */ +- } head; ++ struct ep11kblob_header head; + }; + u8 wkvp[16]; /* wrapping key verification pattern */ + u64 attr; /* boolean key attributes */ +@@ -111,18 +116,29 @@ struct ep11keytoken { + u8 padding[64]; + } __packed; + ++#define ZERO_SESSION \ ++ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ++ + #define AESDATA_KEY_SIZE sizeof(struct aesdatakeytoken) + #define AESCIPHER_KEY_SIZE sizeof(struct aescipherkeytoken) + #define EP11_KEY_SIZE sizeof(struct ep11keytoken) ++#define EP11_AES_KEY_SIZE (sizeof(struct ep11kblob_header) + \ ++ sizeof(struct ep11keytoken)) + + /* MAX/MIN from zt_common.h produces warnings for variable length arrays */ + #define _MIN(a, b) ((a) < (b) ? (a) : (b)) + #define _MAX(a, b) ((a) > (b) ? (a) : (b)) + +-#define MAX_SECURE_KEY_SIZE _MAX(EP11_KEY_SIZE, \ +- _MAX(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE)) +-#define MIN_SECURE_KEY_SIZE _MIN(EP11_KEY_SIZE, \ +- _MIN(AESDATA_KEY_SIZE, AESCIPHER_KEY_SIZE)) ++#define MAX_SECURE_KEY_SIZE _MAX( \ ++ _MAX(EP11_KEY_SIZE, \ ++ EP11_AES_KEY_SIZE), \ ++ _MAX(AESDATA_KEY_SIZE, \ ++ AESCIPHER_KEY_SIZE)) ++#define MIN_SECURE_KEY_SIZE _MIN( \ ++ _MIN(EP11_KEY_SIZE, \ ++ EP11_AES_KEY_SIZE), \ ++ _MIN(AESDATA_KEY_SIZE, \ ++ AESCIPHER_KEY_SIZE)) + + struct pkey_seckey { + u8 seckey[AESDATA_KEY_SIZE]; /* the secure key blob */ +@@ -175,6 +191,9 @@ enum pkey_key_type { + PKEY_TYPE_CCA_DATA = (u32) 1, + PKEY_TYPE_CCA_CIPHER = (u32) 2, + PKEY_TYPE_EP11 = (u32) 3, ++ PKEY_TYPE_CCA_ECC = (u32) 0x1f, ++ PKEY_TYPE_EP11_AES = (u32) 6, ++ PKEY_TYPE_EP11_ECC = (u32) 7, + }; + + enum pkey_key_size { +@@ -321,6 +340,8 @@ int get_master_key_verification_pattern(const u8 *key, size_t key_size, + bool is_cca_aes_data_key(const u8 *key, size_t key_size); + bool is_cca_aes_cipher_key(const u8 *key, size_t key_size); + bool is_ep11_aes_key(const u8 *key, size_t key_size); ++bool is_ep11_aes_key_with_header(const u8 *key, size_t key_size); ++bool is_ep11_key_session_bound(const u8 *key, size_t key_size); + bool is_xts_key(const u8 *key, size_t key_size); + int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize); + const char *get_key_type(const u8 *key, size_t key_size); +diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c +index fae78c7f..8b55f7d1 100644 +--- a/zkey/zkey-cryptsetup.c ++++ b/zkey/zkey-cryptsetup.c +@@ -1673,7 +1673,10 @@ static int reencipher_prepare(int token) + warnx("Failed to re-encipher the secure volume " + "key for device '%s'\n", g.pos_arg); + if (!selected && +- !is_ep11_aes_key((u8 *)key, securekeysize)) ++ !is_ep11_aes_key((u8 *)key, ++ securekeysize) && ++ !is_ep11_aes_key_with_header((u8 *)key, ++ securekeysize)) + print_msg_for_cca_envvars( + "secure AES volume key"); + rc = -EINVAL; +@@ -1696,7 +1699,10 @@ static int reencipher_prepare(int token) + warnx("Failed to re-encipher the secure volume " + "key for device '%s'\n", g.pos_arg); + if (!selected && +- !is_ep11_aes_key((u8 *)key, securekeysize)) ++ !is_ep11_aes_key((u8 *)key, ++ securekeysize) && ++ !is_ep11_aes_key_with_header((u8 *)key, ++ securekeysize)) + print_msg_for_cca_envvars( + "secure AES volume key"); + rc = -EINVAL; +@@ -1836,7 +1842,10 @@ static int reencipher_complete(int token) + warnx("Failed to re-encipher the secure volume " + "key for device '%s'\n", g.pos_arg); + if (!selected && +- !is_ep11_aes_key((u8 *)key, securekeysize)) ++ !is_ep11_aes_key((u8 *)key, ++ securekeysize) && ++ !is_ep11_aes_key_with_header((u8 *)key, ++ securekeysize)) + print_msg_for_cca_envvars( + "secure AES volume key"); + rc = -EINVAL; +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 3000290f..843e5544 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -1968,7 +1968,9 @@ static int command_reencipher_file(void) + "master key has failed\n"); + if (!selected && + !is_ep11_aes_key(secure_key, +- secure_key_size)) ++ secure_key_size) && ++ !is_ep11_aes_key_with_header(secure_key, ++ secure_key_size)) + print_msg_for_cca_envvars( + "secure AES key"); + } +@@ -1993,7 +1995,9 @@ static int command_reencipher_file(void) + "master key has failed\n"); + if (!selected && + !is_ep11_aes_key(secure_key, +- secure_key_size)) ++ secure_key_size) && ++ !is_ep11_aes_key_with_header(secure_key, ++ secure_key_size)) + print_msg_for_cca_envvars( + "secure AES key"); + } +-- +2.53.0 + + +From df0819ca69dbef1f99321f51cd9c4d33c6374992 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Mon, 7 Aug 2023 16:56:54 +0200 +Subject: [PATCH 02/22] rust/Makefile: Fix use of Cargoflags for 'make clean' +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit ee669294650a2f96585357447fa5e93794e5b48f) +--- + rust/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/rust/Makefile b/rust/Makefile +index cf2fda70..420bafd0 100644 +--- a/rust/Makefile ++++ b/rust/Makefile +@@ -68,7 +68,7 @@ clean: + $(foreach target,$(CARGO_TARGETS),\ + $(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${ALL_CARGOFLAGS} ;) + $(foreach target,$(PV_TARGETS),\ +- $(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${CARGOFLAGS} ;) ++ $(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${ALL_CARGOFLAGS} ;) + $(RM) -- .check-dep-pvtools .detect-openssl.dep.c .check-cargo + + rust-test: .check-cargo .no-cross-compile +-- +2.53.0 + + +From b6ce8c7fc10c225c0b1d59af32edd323f5817ab7 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Mon, 7 Aug 2023 16:56:55 +0200 +Subject: [PATCH 03/22] rust/README.md: Fix some typos +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit d5f806390012b1fe0afb52eb9d78a883cc5b4cdf) +--- + rust/README.md | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/rust/README.md b/rust/README.md +index 2622bba2..61b0af80 100644 +--- a/rust/README.md ++++ b/rust/README.md +@@ -7,21 +7,21 @@ https://www.rust-lang.org/learn/get-started + ## Building rust code + ### s390-tools build system + If `cargo` is installed a simple `make` should do the job. Note that, +-compiling rust programs take significaltly longer than C code. To closely +-monitor the prgress use `make V=1` By default release builds are made. ++compiling rust programs take significantly longer than C code. To closely ++monitor the progress use `make V=1` By default release builds are made. + + With `make CARGOFLAGS=` one can pass additional flags to cargo. + With `make HAVE_CARGO=0` one can turn of any compilation that requires cargo. + With `make CARGO=<...>` one can set the cargo binary + + ### cargo +-If you need to run cargo directly, cd to each project you want to build and ++If you need to run cargo directly, `cd` to each project you want to build and + issue your cargo commands. Do **NOT** forget to specify `--release` if you are + building tools for a release. The s390-tools expect the environment variable + `S390_TOOLS_RELEASE` to be present at build time. This is the version string the + rust tools provide. + +-Tpp: You can use `make version` to get the version string. ++Tip: You can use `make version` to get the version string. + + ## Internal Libraries + * __utils__ _Library for rust tools that bundles common stuff for the 390-tools_ +@@ -43,7 +43,7 @@ Exiting tools may be rewritten in Rust. + + ### What (third-party) crates can be used for s390-tools? + A huge list of libraries are made available through Rusts' ecosystem and is one +-of many upsides. However, just like with Coding Style Guidlines, it is ++of many upsides. However, just like with Coding Style Guidelines, it is + important to limit the usage of those libraries so that within a project, + everyone is on the same page and that code written in Rust uses similar + approaches. It makes it easier for code review and maintainability in general. +@@ -98,8 +98,8 @@ use utils::release_string; + fn print_version() { + println!( + "{} version {}\nCopyright IBM Corp. 2023", +- env!("CARGO_PKG_NAME"), // collapes into the crates name +- release_string!() // this (very likely) collapes into a compile time constant ++ env!("CARGO_PKG_NAME"), // collapses into the crates name ++ release_string!() // this (very likely) collapses into a compile time constant + ); + } + ``` +-- +2.53.0 + + +From 883d28afea6ea18b1001ebf9e3d921d86be9c593 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Mon, 4 Sep 2023 14:18:50 +0200 +Subject: [PATCH 04/22] rust/**/*.rs: fix `cargo clippy` findings +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Automatically fixed by the command `cargo clippy --fix` and `cargo fmt`. + +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit 3849b2959414feae3e984d4cae0a0cfcb529006d) +--- + rust/pv/src/brcb.rs | 4 +- + rust/pv/src/crypto.rs | 2 +- + rust/pv/src/req.rs | 2 +- + rust/pv/src/uvsecret/guest_secret.rs | 2 +- + rust/pv/src/uvsecret/secret_list.rs | 2 +- + rust/pv/src/verify/helper.rs | 10 ++-- + rust/pv/src/verify/test.rs | 72 ++++++++++++---------------- + rust/pv/tests/add_secret_request.rs | 17 ++----- + 8 files changed, 47 insertions(+), 64 deletions(-) + +diff --git a/rust/pv/src/brcb.rs b/rust/pv/src/brcb.rs +index 4bf5f161..9ef58e2d 100644 +--- a/rust/pv/src/brcb.rs ++++ b/rust/pv/src/brcb.rs +@@ -205,7 +205,7 @@ mod tests { + #[test] + fn from_se_image_hdr() { + let bin_hdr = get_test_asset!("exp/secure_guest.hdr"); +- let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(bin_hdr.clone())).unwrap(); ++ let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap(); + assert_eq!(hdr_tags, EXP_HDR); + } + +@@ -220,7 +220,7 @@ mod tests { + )); + + // mess up magic +- let mut bin_hdr_copy = bin_hdr.clone(); ++ let mut bin_hdr_copy = *bin_hdr; + bin_hdr_copy.swap(0, 1); + assert!(matches!( + BootHdrTags::from_se_image(&mut Cursor::new(bin_hdr_copy)), +diff --git a/rust/pv/src/crypto.rs b/rust/pv/src/crypto.rs +index 910419e5..8132e9de 100644 +--- a/rust/pv/src/crypto.rs ++++ b/rust/pv/src/crypto.rs +@@ -315,7 +315,7 @@ mod tests { + ]; + + let res = encrypt_aes( +- &&SymKey::Aes256(aes_gcm_key.into()), ++ &SymKey::Aes256(aes_gcm_key.into()), + &aes_gcm_iv, + &aes_gcm_plain, + ) +diff --git a/rust/pv/src/req.rs b/rust/pv/src/req.rs +index cda448bf..b9acd732 100644 +--- a/rust/pv/src/req.rs ++++ b/rust/pv/src/req.rs +@@ -451,7 +451,7 @@ mod tests { + let ks = vec![ + Keyslot::new(host_key.clone()), + Keyslot::new(host_key.clone()), +- Keyslot::new(host_key.clone()), ++ Keyslot::new(host_key), + ]; + let mut aad = Vec::::new(); + ks.iter().for_each(|ks| aad.push(Aad::Ks(ks))); +diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs +index 8ad4fa55..8c444211 100644 +--- a/rust/pv/src/uvsecret/guest_secret.rs ++++ b/rust/pv/src/uvsecret/guest_secret.rs +@@ -105,7 +105,7 @@ mod test { + 0xef, 0xc7, 0x3c, 0x62, + ]; + let name = "association secret".to_string(); +- let secret = GuestSecret::association("association secret", secret_value.clone()).unwrap(); ++ let secret = GuestSecret::association("association secret", secret_value).unwrap(); + let exp = GuestSecret::Association { + name, + id: exp_id, +diff --git a/rust/pv/src/uvsecret/secret_list.rs b/rust/pv/src/uvsecret/secret_list.rs +index 724702a1..6943bd31 100644 +--- a/rust/pv/src/uvsecret/secret_list.rs ++++ b/rust/pv/src/uvsecret/secret_list.rs +@@ -117,7 +117,7 @@ impl Display for SecretEntry { + writeln!(f, "{} {}:", self.index, stype_str(self.stype.get()))?; + write!(f, " ")?; + for b in self.id { +- write!(f, "{:02x}", b)?; ++ write!(f, "{b:02x}")?; + } + Ok(()) + } +diff --git a/rust/pv/src/verify/helper.rs b/rust/pv/src/verify/helper.rs +index 89f92b87..a2f313b0 100644 +--- a/rust/pv/src/verify/helper.rs ++++ b/rust/pv/src/verify/helper.rs +@@ -484,20 +484,20 @@ mod test { + let ibm_wrong_subj = load_gen_cert("ibm_wrong_subject.crt"); + let no_sign_crt = load_gen_cert("inter_ca.crt"); + +- assert!(super::get_ibm_z_sign_key(&vec!(ibm_crt.clone())).is_ok()); ++ assert!(super::get_ibm_z_sign_key(&[ibm_crt.clone()]).is_ok()); + assert!(matches!( +- super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), ibm_crt.clone())), ++ super::get_ibm_z_sign_key(&[ibm_crt.clone(), ibm_crt.clone()]), + Err(Error::HkdVerify(ManyIbmSignKeys)) + )); + assert!(matches!( +- super::get_ibm_z_sign_key(&vec!(ibm_wrong_subj)), ++ super::get_ibm_z_sign_key(&[ibm_wrong_subj]), + Err(Error::HkdVerify(NoIbmSignKey)) + )); + assert!(matches!( +- super::get_ibm_z_sign_key(&vec!(no_sign_crt.clone())), ++ super::get_ibm_z_sign_key(&[no_sign_crt.clone()]), + Err(Error::HkdVerify(NoIbmSignKey)) + )); +- assert!(super::get_ibm_z_sign_key(&vec!(ibm_crt.clone(), no_sign_crt.clone())).is_ok(),); ++ assert!(super::get_ibm_z_sign_key(&[ibm_crt, no_sign_crt]).is_ok(),); + } + + #[test] +diff --git a/rust/pv/src/verify/test.rs b/rust/pv/src/verify/test.rs +index 8a6258da..1c0d2b56 100644 +--- a/rust/pv/src/verify/test.rs ++++ b/rust/pv/src/verify/test.rs +@@ -26,7 +26,7 @@ fn verify_sign_error(exp_raw: libc::c_int, obs: Error) { + } + fn verify_sign_error_slice(exp_raw: &[libc::c_int], obs: Error) { + if exp_raw +- .into_iter() ++ .iter() + .filter(|e| match &obs { + Error::HkdVerify(ty) => match ty { + IbmSignInvalid(err, _d) => &&err.as_raw() == e, +@@ -51,7 +51,7 @@ fn store_setup() { + let ibm_str = get_cert_asset_path_string("ibm.crt"); + let inter_str = get_cert_asset_path_string("inter.crt"); + +- let store = helper::store_setup(&None, &vec![], &vec![ibm_str, inter_str]); ++ let store = helper::store_setup(&None, &[], &[ibm_str, inter_str]); + assert!(store.is_ok()); + } + +@@ -63,7 +63,7 @@ fn verify_chain_online() { + + let mock_inter = mock_endpt("inter_ca.crl"); + +- let mut store = helper::store_setup(&Some(root_crt), &vec![], &vec![]).unwrap(); ++ let mut store = helper::store_setup(&Some(root_crt), &[], &[]).unwrap(); + download_crls_into_store(&mut store, slice::from_ref(&ibm_crt)).unwrap(); + let store = store.build(); + +@@ -71,8 +71,8 @@ fn verify_chain_online() { + + let mut sk = Stack::::new().unwrap(); + sk.push(inter_crt).unwrap(); +- verify_chain(&store, &sk, &vec![ibm_crt.clone()]).unwrap(); +- assert!(verify_chain(&store, &sk, &vec!(ibm_crt)).is_ok()); ++ verify_chain(&store, &sk, &[ibm_crt.clone()]).unwrap(); ++ assert!(verify_chain(&store, &sk, &[ibm_crt]).is_ok()); + } + + #[test] +@@ -82,13 +82,13 @@ fn verify_chain_offline() { + let inter_crt = load_gen_cert("inter_ca.crt"); + let root_crt = get_cert_asset_path_string("root_ca.chained.crt"); + +- let store = helper::store_setup(&Some(root_crt), &vec![inter_crl], &vec![]) ++ let store = helper::store_setup(&Some(root_crt), &[inter_crl], &[]) + .unwrap() + .build(); + + let mut sk = Stack::::new().unwrap(); + sk.push(inter_crt).unwrap(); +- assert!(verify_chain(&store, &sk, &vec![ibm_crt]).is_ok()); ++ assert!(verify_chain(&store, &sk, &[ibm_crt]).is_ok()); + } + + #[test] +@@ -107,8 +107,8 @@ fn verify_online() { + let inter_crl = get_cert_asset_path_string("inter_ca.crl"); + let ibm_crl = get_cert_asset_path_string("ibm.crl"); + let verifier = CertVerifier::new( +- &vec![ibm_crt, inter_crt], +- &vec![ibm_crl, inter_crl], ++ &[ibm_crt, inter_crt], ++ &[ibm_crl, inter_crl], + &Some(root_crt), + false, + ) +@@ -148,8 +148,8 @@ fn verify_offline() { + let hkd = load_gen_cert("host.crt"); + + let verifier = CertVerifier::new( +- &vec![ibm_crt, inter_crt], +- &vec![ibm_crl, inter_crl], ++ &[ibm_crt, inter_crt], ++ &[ibm_crl, inter_crl], + &Some(root_crt), + true, + ) +@@ -186,25 +186,20 @@ fn verifier_new() { + let ibm_rev_crt = get_cert_asset_path_string("ibm_rev.crt"); + + // To many signing keys +- let verifier = CertVerifier::new( +- &vec![ibm_crt.clone(), ibm_rev_crt.clone()], +- &vec![], +- &None, +- true, +- ); ++ let verifier = CertVerifier::new(&[ibm_crt.clone(), ibm_rev_crt.clone()], &[], &None, true); + assert!(matches!(verifier, Err(Error::HkdVerify(ManyIbmSignKeys)))); + + // no CRL for each X509 + let verifier = CertVerifier::new( +- &vec![inter_crt.clone(), ibm_crt.clone()], +- &vec![inter_crl.clone()], +- &Some(root_crt.clone()), ++ &[inter_crt.clone(), ibm_crt.clone()], ++ &[inter_crl.clone()], ++ &Some(root_crt), + false, + ); + verify_sign_error(3, verifier.unwrap_err()); + let verifier = CertVerifier::new( +- &vec![inter_crt.clone(), ibm_crt.clone()], +- &vec![], ++ &[inter_crt.clone(), ibm_crt.clone()], ++ &[], + &Some(root_chn_crt.clone()), + false, + ); +@@ -212,8 +207,8 @@ fn verifier_new() { + + // wrong intermediate (or ibm key) + let verifier = CertVerifier::new( +- &vec![inter_fake_crt, ibm_crt.clone()], +- &vec![inter_fake_crl], ++ &[inter_fake_crt, ibm_crt.clone()], ++ &[inter_fake_crl], + &Some(root_chn_crt.clone()), + true, + ); +@@ -222,8 +217,8 @@ fn verifier_new() { + + //wrong root ca + let verifier = CertVerifier::new( +- &vec![inter_crt.clone(), ibm_crt.clone()], +- &vec![inter_crl.clone()], ++ &[inter_crt.clone(), ibm_crt.clone()], ++ &[inter_crl.clone()], + &None, + true, + ); +@@ -231,33 +226,28 @@ fn verifier_new() { + + //correct signing key + intermediate cert + let _verifier = CertVerifier::new( +- &vec![inter_crt.clone(), ibm_crt.clone()], +- &vec![inter_crl.clone()], ++ &[inter_crt.clone(), ibm_crt.clone()], ++ &[inter_crl.clone()], + &Some(root_chn_crt.clone()), + false, + ) + .unwrap(); + + // no intermediate key +- let verifier = CertVerifier::new( +- &vec![ibm_crt.clone()], +- &vec![], +- &Some(root_chn_crt.clone()), +- false, +- ); ++ let verifier = CertVerifier::new(&[ibm_crt], &[], &Some(root_chn_crt.clone()), false); + verify_sign_error(20, verifier.unwrap_err()); + + //Ibm Sign outdated + let verifier = CertVerifier::new( +- &vec![inter_crt.clone(), ibm_early_crt.clone()], +- &vec![inter_crl.clone()], ++ &[inter_crt.clone(), ibm_early_crt], ++ &[inter_crl.clone()], + &Some(root_chn_crt.clone()), + false, + ); + assert!(matches!(verifier, Err(Error::HkdVerify(NoIbmSignKey)))); + let verifier = CertVerifier::new( +- &vec![inter_crt.clone(), ibm_late_crt.clone()], +- &vec![inter_crl.clone()], ++ &[inter_crt.clone(), ibm_late_crt], ++ &[inter_crl.clone()], + &Some(root_chn_crt.clone()), + false, + ); +@@ -265,9 +255,9 @@ fn verifier_new() { + + // revoked + let verifier = CertVerifier::new( +- &vec![inter_crt.clone(), ibm_rev_crt.clone()], +- &vec![inter_crl.clone()], +- &Some(root_chn_crt.clone()), ++ &[inter_crt, ibm_rev_crt], ++ &[inter_crl], ++ &Some(root_chn_crt), + false, + ); + verify_sign_error(23, verifier.unwrap_err()); +diff --git a/rust/pv/tests/add_secret_request.rs b/rust/pv/tests/add_secret_request.rs +index 2b3e861e..bf491c61 100644 +--- a/rust/pv/tests/add_secret_request.rs ++++ b/rust/pv/tests/add_secret_request.rs +@@ -18,7 +18,7 @@ use pv::{ + const TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]); + const CUID: ConfigUid = [0x42u8; 16]; + const ASSOC_SECRET: [u8; 32] = [0x11; 32]; +-const ASSOC_ID: &'static str = "add_secret_request"; ++const ASSOC_ID: &str = "add_secret_request"; + + fn create_asrcb( + guest_secret: GuestSecret, +@@ -38,7 +38,7 @@ fn create_asrcb( + }; + + asrcb.add_hostkey(hkd); +- Ok(asrcb.encrypt(ctx)?) ++ asrcb.encrypt(ctx) + } + + fn get_crypto() -> (PKey, ReqEncrCtx) { +@@ -63,17 +63,10 @@ where + { + let (host_key, ctx) = get_crypto(); + let cuid = match cuid { +- true => Some(CUID.into()), ++ true => Some(CUID), + false => None, + }; +- create_asrcb( +- guest_secret, +- ext_secret.into(), +- flags, +- cuid.into(), +- host_key, +- &ctx, +- ) ++ create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx) + } + + fn association() -> GuestSecret { +@@ -156,7 +149,7 @@ fn null_none_default_cuid_seven() { + let mut asrcb = + AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag()); + (0..7).for_each(|_| asrcb.add_hostkey(hkd.clone())); +- asrcb.set_cuid(CUID.into()); ++ asrcb.set_cuid(CUID); + let asrcb = asrcb.encrypt(&ctx).unwrap(); + + let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven"); +-- +2.53.0 + + +From 4c8072cebe9add441c42e62663d4089d14d32389 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 25 Oct 2023 15:26:14 +0200 +Subject: [PATCH 05/22] rust/pv: fix Invalid write of size 1 + +Fix a valgrind finding. Fix an invalid read/write of one byte after the +actual struct to clear. Not fixing this may result in a illegal write or +memory corruption of the program. Fortunately, for the actual only user, +pvsecret this is not the case. + +Fixes: c6f621d0 ("rust: Add library for pv tools") +Signed-off-by: Steffen Eiden +(cherry picked from commit 71b93d55effae6a74bbbbafe7057e1f97d692a74) +--- + rust/pv/src/secret.rs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/rust/pv/src/secret.rs b/rust/pv/src/secret.rs +index cdef9ef8..88287c8e 100644 +--- a/rust/pv/src/secret.rs ++++ b/rust/pv/src/secret.rs +@@ -34,8 +34,8 @@ impl Zeroize for Vec { + // * Vec allocated at least capacity elements continuously + // * dst points always to a valid location + unsafe { ++ std::ptr::write_volatile(dst, 0); + dst = dst.add(1); +- std::ptr::write_volatile(dst, 0) + } + } + std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst); +-- +2.53.0 + + +From 49eabe2d13ea3909f4c522fefaf8db998c7ab888 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 4 Oct 2023 10:59:34 +0200 +Subject: [PATCH 06/22] rust: Create workspace + +A workspaces simplifies the build and packaging process significantly. +All build artifacts and binaries are now built in a single location +(e.g., rust/target/release/*), and a unified dependency resolution is +used. Hence one Cargo.lock for all crates at rust/Cargo.lock. + +Closes: https://github.com/ibm-s390-linux/s390-tools/issues/156 +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +(cherry picked from commit 32b68a5fad652589c85eae098e799563b88864b5) +--- + rust/Cargo.toml | 15 ++++++++++ + rust/Makefile | 43 +++++++++++---------------- + rust/README.md | 20 +++++++++++-- + rust/pv/Cargo.toml | 8 ++--- + rust/pv/openssl_extensions/Cargo.toml | 4 +-- + rust/pvsecret/Cargo.toml | 8 ++--- + rust/utils/Cargo.toml | 4 +-- + 7 files changed, 59 insertions(+), 43 deletions(-) + create mode 100644 rust/Cargo.toml + +diff --git a/rust/Cargo.toml b/rust/Cargo.toml +new file mode 100644 +index 00000000..65a70a97 +--- /dev/null ++++ b/rust/Cargo.toml +@@ -0,0 +1,15 @@ ++[workspace] ++members = [ ++ "pv", ++ "pvsecret", ++ "utils", ++] ++resolver = "2" ++ ++[workspace.package] ++edition = "2021" ++license = "MIT" ++ ++[profile.release] ++lto = true ++panic = "abort" +diff --git a/rust/Makefile b/rust/Makefile +index 420bafd0..e4e98859 100644 +--- a/rust/Makefile ++++ b/rust/Makefile +@@ -15,16 +15,17 @@ ifneq (${HAVE_CARGO},0) + + BUILD_TARGETS = $(CARGO_TARGETS) + INSTALL_TARGETS := install-rust-tools install-man +- CARGO_TEST_TARGETS = $(addsuffix, _test, $(CARGO_TARGETS)) ++ CARGO_TEST_TARGETS = $(addsuffix .test, $(CARGO_TARGETS)) + + ifneq (${HAVE_OPENSSL},0) + ifneq (${HAVE_LIBCURL},0) + PV_TARGETS := pvsecret + +- PV_BUILD_TARGETS = $(PV_TARGETS) +- CARGO_TEST_TARGETS += $(addsuffix, _test, $(PV_TARGETS)) pv ++ PV_BUILD_TARGETS := $(PV_TARGETS) ++ CARGO_TEST_TARGETS += $(addsuffix .test,pv $(PV_TARGETS)) + endif #LIBCURL + endif #OPENSSL ++ TEST_TARGETS := $(addsuffix _build,$(CARGO_TEST_TARGETS)) + endif #CARGO + + BUILD_TARGETS += $(PV_BUILD_TARGETS) +@@ -39,18 +40,15 @@ endif + $(BUILD_TARGETS) rust-test: CC = $(CC_SILENT) + $(BUILD_TARGETS) rust-test: AR = $(AR_SILENT) + +-$(CARGO_TARGETS): .check-cargo .no-cross-compile +- $(CARGO_BUILD) --manifest-path=$@/Cargo.toml $(ALL_CARGOFLAGS) +-.PHONY: $(CARGO_TARGETS) ++$(PV_TARGETS): .check-dep-pvtools ++$(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile ++ $(CARGO_BUILD) --bin $@ $(ALL_CARGOFLAGS) ++.PHONY: $(PV_TARGETS) $(CARGO_TARGETS) + +- +-$(CARGO_TEST_TARGETS): .check-cargo .no-cross-compile +- $(CARGO_TEST) --manifest-path=$@/Cargo.toml --all-features $(CARGOFLAGS) +-.PHONY: $(CARGO_TEST_TARGETS) +- +-$(PV_TARGETS): .check-cargo .no-cross-compile .check-dep-pvtools +- $(CARGO_BUILD) --manifest-path=$@/Cargo.toml $(ALL_CARGOFLAGS) +-.PHONY: $(PV_TARGETS) ++$(TEST_TARGETS): ALL_CARGOFLAGS += --no-run ++$(CARGO_TEST_TARGETS) $(TEST_TARGETS): .check-cargo .no-cross-compile ++ $(CARGO_TEST) --package $(basename $@) --all-features $(ALL_CARGOFLAGS) ++.PHONY: $(TEST_TARGETS) $(CARGO_TEST_TARGETS) + + skip-build: + echo " SKIP rust-tools due to unresolved dependencies" +@@ -65,22 +63,17 @@ print-rust-targets: + echo $(BUILD_TARGETS) + + clean: +- $(foreach target,$(CARGO_TARGETS),\ +- $(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${ALL_CARGOFLAGS} ;) +- $(foreach target,$(PV_TARGETS),\ +- $(CARGO_CLEAN) --manifest-path=$(target)/Cargo.toml ${ALL_CARGOFLAGS} ;) ++ $(CARGO_CLEAN) ${ALL_CARGOFLAGS} + $(RM) -- .check-dep-pvtools .detect-openssl.dep.c .check-cargo + +-rust-test: .check-cargo .no-cross-compile +- $(foreach target,$(CARGO_TEST_TARGETS),\ +- $(CARGO_TEST) --manifest-path=$(target)/Cargo.toml --all-features ${ALL_CARGOFLAGS} ;) ++rust-test: $(CARGO_TEST_TARGETS) + + install-rust-tools: $(BUILD_TARGETS) + $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) + $(foreach target,$(CARGO_TARGETS),\ +- $(INSTALL) $(target)/target/release/$(target) $(DESTDIR)$(USRBINDIR);) ++ $(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);) + $(foreach target,$(PV_TARGETS),\ +- $(INSTALL) $(target)/target/release/$(target) $(DESTDIR)$(USRBINDIR);) ++ $(INSTALL) target/release/$(target) $(DESTDIR)$(USRBINDIR);) + + install-man: + $(foreach target,$(CARGO_TARGETS),\ +@@ -118,13 +111,13 @@ endif + + .check-dep-pvtools: .detect-openssl.dep.c + $(call check_dep, \ +- "$(BIN_PROGRAM)", \ ++ "Rust-pv", \ + $^, \ + "openssl-devel / libssl-dev version >= 1.1.1", \ + "HAVE_OPENSSL=0", \ + "-I.") + $(call check_dep, \ +- "$(BIN_PROGRAM)", \ ++ "Rust-pv", \ + "curl/curl.h", \ + "libcurl-devel", \ + "HAVE_LIBCURL=0") +diff --git a/rust/README.md b/rust/README.md +index 61b0af80..16603bf5 100644 +--- a/rust/README.md ++++ b/rust/README.md +@@ -81,11 +81,27 @@ is a start, but can change over time. + Dependencies used by the crates listed above can be used, too. + + ### Add new tool +-To add a new tool issue `cargo new ` in the `rust` directory. ++To add a new tool issue `cargo new $TOOLNAME` in the `rust` directory. + + Add the tool to the _s390-tools_ build system: + ```Makefile +-CARGO_TARGETS := TOOLNAME ++CARGO_TARGETS := $TOOLNAME ++``` ++Add the library to the _s390-tools_ test list: ++```Makefile ++CARGO_TEST_TARGETS := $LIBNAME ++``` ++ ++Add the tool/library to the cargo workspace: ++```toml ++[workspace] ++members = [ ++ "pv", ++ "pvsecret", ++ "$TOOLNAME", ++ "$LIBNAME" ++ "utils", ++] + ``` + + ### Versions +diff --git a/rust/pv/Cargo.toml b/rust/pv/Cargo.toml +index 689d6242..825386a4 100644 +--- a/rust/pv/Cargo.toml ++++ b/rust/pv/Cargo.toml +@@ -1,8 +1,8 @@ + [package] + name = "pv" + version = "0.9.0" +-edition = "2021" +-license = "MIT" ++edition.workspace = true ++license.workspace = true + + [dependencies] + libc = "0.2" +@@ -30,7 +30,3 @@ lazy_static = "1" + default = [] + request = ["dep:openssl", "dep:curl", "dep:openssl_extensions", "dep:serde", "dep:clap"] + uvsecret = ["dep:byteorder", "dep:serde"] +- +-[profile.release] +-lto = true +-panic = "abort" # release builds now do not clean up stack after panics. .1 Mb +diff --git a/rust/pv/openssl_extensions/Cargo.toml b/rust/pv/openssl_extensions/Cargo.toml +index 17d4c0e5..90907cee 100644 +--- a/rust/pv/openssl_extensions/Cargo.toml ++++ b/rust/pv/openssl_extensions/Cargo.toml +@@ -1,8 +1,8 @@ + [package] + name = "openssl_extensions" + version = "0.1.0" +-edition = "2021" +-license = "MIT" ++edition.workspace = true ++license.workspace = true + + [dependencies] + foreign-types = "0.3" +diff --git a/rust/pvsecret/Cargo.toml b/rust/pvsecret/Cargo.toml +index da4deab3..d1e75b13 100644 +--- a/rust/pvsecret/Cargo.toml ++++ b/rust/pvsecret/Cargo.toml +@@ -1,8 +1,8 @@ + [package] + name = "pvsecret" + version = "0.9.0" +-edition = "2021" +-license = "MIT" ++edition.workspace = true ++license.workspace = true + + [dependencies] + anyhow = { version = "1", features = ["std"] } +@@ -12,7 +12,3 @@ serde_yaml = "0.9" + + pv = { path = "../pv", features = ["uvsecret", "request"] } + utils = { path = "../utils" } +- +-[profile.release] +-lto = true +-panic = "abort" # release builds now do not clean up stack after panics. .1 Mb +diff --git a/rust/utils/Cargo.toml b/rust/utils/Cargo.toml +index 30bbbc83..215381be 100644 +--- a/rust/utils/Cargo.toml ++++ b/rust/utils/Cargo.toml +@@ -1,5 +1,5 @@ + [package] + name = "utils" + version = "0.1.0" +-edition = "2021" +-license = "MIT" ++edition.workspace = true ++license.workspace = true +-- +2.53.0 + + +From be47ce72f4ee7dc7ed2dafb9b89079b0c2b154fa Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 4 Oct 2023 11:08:20 +0200 +Subject: [PATCH 07/22] rust: Update dependency files + +With the last patch introducing the rust workspace the location of +Cargo.lock has changed. Therefore, remove all crate level lock-files and +add rust/Cargo.lock as the only lock-file. + +Steps to reproduce: +``` +cd rust +mv pvsecret/Cargo.lock . +cargo build +cargo update -p openssl +cargo update -p curl-sys +cargo update -p rustix + +``` + +While at it update some dependencies to get fixes for security issues. + +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +(cherry picked from commit d1b61c37fa78c95d0512b417aa21fbc66bddcd4b) +--- + rust/.gitignore | 3 - + rust/{pvsecret => }/Cargo.lock | 222 +++++++++++++++++++++++++++++++-- + 2 files changed, 210 insertions(+), 15 deletions(-) + rename rust/{pvsecret => }/Cargo.lock (77%) + +diff --git a/rust/.gitignore b/rust/.gitignore +index 4760f821..f9f3955f 100644 +--- a/rust/.gitignore ++++ b/rust/.gitignore +@@ -9,6 +9,3 @@ target/ + # Generated during make build can be removed at any point + .check-dep-pvtools + .check-cargo +- +-# Ignore lock files by default +-Cargo.lock +diff --git a/rust/pvsecret/Cargo.lock b/rust/Cargo.lock +similarity index 77% +rename from rust/pvsecret/Cargo.lock +rename to rust/Cargo.lock +index 1db32c29..f7d1cf0f 100644 +--- a/rust/pvsecret/Cargo.lock ++++ b/rust/Cargo.lock +@@ -2,6 +2,15 @@ + # It is not intended for manual editing. + version = 3 + ++[[package]] ++name = "aho-corasick" ++version = "1.1.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" ++dependencies = [ ++ "memchr", ++] ++ + [[package]] + name = "anstream" + version = "0.3.2" +@@ -57,6 +66,16 @@ version = "1.0.71" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + ++[[package]] ++name = "assert-json-diff" ++version = "2.0.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" ++dependencies = [ ++ "serde", ++ "serde_json", ++] ++ + [[package]] + name = "autocfg" + version = "1.1.0" +@@ -69,6 +88,12 @@ version = "1.3.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + ++[[package]] ++name = "bitflags" ++version = "2.4.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" ++ + [[package]] + name = "byteorder" + version = "1.4.3" +@@ -106,7 +131,7 @@ checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" + dependencies = [ + "anstream", + "anstyle", +- "bitflags", ++ "bitflags 1.3.2", + "clap_lex", + "strsim", + "terminal_size", +@@ -153,9 +178,9 @@ dependencies = [ + + [[package]] + name = "curl-sys" +-version = "0.4.63+curl-8.1.2" ++version = "0.4.68+curl-8.4.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" ++checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f" + dependencies = [ + "cc", + "libc", +@@ -163,7 +188,7 @@ dependencies = [ + "openssl-sys", + "pkg-config", + "vcpkg", +- "winapi", ++ "windows-sys 0.48.0", + ] + + [[package]] +@@ -202,6 +227,26 @@ version = "0.1.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + ++[[package]] ++name = "form_urlencoded" ++version = "1.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" ++dependencies = [ ++ "percent-encoding", ++] ++ ++[[package]] ++name = "getrandom" ++version = "0.2.10" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" ++dependencies = [ ++ "cfg-if", ++ "libc", ++ "wasi", ++] ++ + [[package]] + name = "hashbrown" + version = "0.12.3" +@@ -220,6 +265,12 @@ version = "0.3.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + ++[[package]] ++name = "httparse" ++version = "1.8.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" ++ + [[package]] + name = "indexmap" + version = "1.9.3" +@@ -259,6 +310,12 @@ version = "1.0.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + ++[[package]] ++name = "lazy_static" ++version = "1.4.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" ++ + [[package]] + name = "libc" + version = "0.2.146" +@@ -289,6 +346,29 @@ version = "0.4.19" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + ++[[package]] ++name = "memchr" ++version = "2.6.4" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" ++ ++[[package]] ++name = "mockito" ++version = "0.31.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "80f9fece9bd97ab74339fe19f4bcaf52b76dcc18e5364c7977c1838f76b38de9" ++dependencies = [ ++ "assert-json-diff", ++ "httparse", ++ "lazy_static", ++ "log", ++ "rand", ++ "regex", ++ "serde_json", ++ "serde_urlencoded", ++ "similar", ++] ++ + [[package]] + name = "once_cell" + version = "1.18.0" +@@ -297,11 +377,11 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + + [[package]] + name = "openssl" +-version = "0.10.54" ++version = "0.10.60" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" ++checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" + dependencies = [ +- "bitflags", ++ "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", +@@ -329,9 +409,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + + [[package]] + name = "openssl-sys" +-version = "0.9.88" ++version = "0.9.96" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" ++checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" + dependencies = [ + "cc", + "libc", +@@ -350,12 +430,24 @@ dependencies = [ + "openssl-sys", + ] + ++[[package]] ++name = "percent-encoding" ++version = "2.3.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" ++ + [[package]] + name = "pkg-config" + version = "0.3.27" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + ++[[package]] ++name = "ppv-lite86" ++version = "0.2.17" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" ++ + [[package]] + name = "proc-macro2" + version = "1.0.60" +@@ -373,11 +465,14 @@ dependencies = [ + "cfg-if", + "clap", + "curl", ++ "lazy_static", + "libc", + "log", ++ "mockito", + "openssl", + "openssl_extensions", + "serde", ++ "serde_test", + "thiserror", + "zerocopy", + ] +@@ -403,13 +498,72 @@ dependencies = [ + "proc-macro2", + ] + ++[[package]] ++name = "rand" ++version = "0.8.5" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" ++dependencies = [ ++ "libc", ++ "rand_chacha", ++ "rand_core", ++] ++ ++[[package]] ++name = "rand_chacha" ++version = "0.3.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" ++dependencies = [ ++ "ppv-lite86", ++ "rand_core", ++] ++ ++[[package]] ++name = "rand_core" ++version = "0.6.4" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" ++dependencies = [ ++ "getrandom", ++] ++ ++[[package]] ++name = "regex" ++version = "1.10.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" ++dependencies = [ ++ "aho-corasick", ++ "memchr", ++ "regex-automata", ++ "regex-syntax", ++] ++ ++[[package]] ++name = "regex-automata" ++version = "0.4.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" ++dependencies = [ ++ "aho-corasick", ++ "memchr", ++ "regex-syntax", ++] ++ ++[[package]] ++name = "regex-syntax" ++version = "0.8.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" ++ + [[package]] + name = "rustix" +-version = "0.37.20" ++version = "0.37.27" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" ++checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" + dependencies = [ +- "bitflags", ++ "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", +@@ -452,6 +606,38 @@ dependencies = [ + "syn 2.0.18", + ] + ++[[package]] ++name = "serde_json" ++version = "1.0.99" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" ++dependencies = [ ++ "itoa", ++ "ryu", ++ "serde", ++] ++ ++[[package]] ++name = "serde_test" ++version = "1.0.176" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" ++dependencies = [ ++ "serde", ++] ++ ++[[package]] ++name = "serde_urlencoded" ++version = "0.7.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" ++dependencies = [ ++ "form_urlencoded", ++ "itoa", ++ "ryu", ++ "serde", ++] ++ + [[package]] + name = "serde_yaml" + version = "0.9.21" +@@ -465,6 +651,12 @@ dependencies = [ + "unsafe-libyaml", + ] + ++[[package]] ++name = "similar" ++version = "2.3.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" ++ + [[package]] + name = "socket2" + version = "0.4.9" +@@ -561,6 +753,12 @@ version = "0.2.15" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + ++[[package]] ++name = "wasi" ++version = "0.11.0+wasi-snapshot-preview1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" ++ + [[package]] + name = "winapi" + version = "0.3.9" +-- +2.53.0 + + +From c25115c0d605c9c79efd8e17d4917a35603c0766 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 21 Nov 2023 13:27:21 +0100 +Subject: [PATCH 08/22] rust: Sanitize minimal dependencies + +The crate dependencies were a bit to slack. Due to the rust dependency +resolver's strategy of always selecting the latest version this never +lead to any issues. + +This has no impact on the workspaces Cargo.lock + +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +(cherry picked from commit 9019c6864a4d39f940994b3858cc331983c605ee) +--- + rust/pv/Cargo.toml | 16 ++++++++-------- + rust/pv/openssl_extensions/Cargo.toml | 10 +++++----- + rust/pvsecret/Cargo.toml | 4 ++-- + 3 files changed, 15 insertions(+), 15 deletions(-) + +diff --git a/rust/pv/Cargo.toml b/rust/pv/Cargo.toml +index 825386a4..22d5fd34 100644 +--- a/rust/pv/Cargo.toml ++++ b/rust/pv/Cargo.toml +@@ -5,26 +5,26 @@ edition.workspace = true + license.workspace = true + + [dependencies] +-libc = "0.2" +-log = { version = "0.4", features = ["std", "release_max_level_debug"] } +-thiserror = "1" ++libc = "0.2.49" ++log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } ++thiserror = "1.0.33" + zerocopy = "0.6" + cfg-if = "1.0.0" + + # dependencies for request feature + clap = { version ="4", features = ["derive", "wrap_help"], optional = true } +-curl = { version ="0.4", optional = true } +-openssl = {version = "0.10", optional = true } ++curl = { version ="0.4.7", optional = true } ++openssl = {version = "0.10.49", optional = true } + openssl_extensions = { path = "openssl_extensions", optional = true } +-serde = { version = "1", features = ["derive"], optional = true } ++serde = { version = "1.0.139", features = ["derive"], optional = true } + + # misc optional dependencies +-byteorder = {version = "1", optional = true } ++byteorder = {version = "1.3", optional = true } + + [dev-dependencies] + mockito = {version = "0.31", default-features = false } + serde_test = "1" +-lazy_static = "1" ++lazy_static = "1.1" + + [features] + default = [] +diff --git a/rust/pv/openssl_extensions/Cargo.toml b/rust/pv/openssl_extensions/Cargo.toml +index 90907cee..7bdeece6 100644 +--- a/rust/pv/openssl_extensions/Cargo.toml ++++ b/rust/pv/openssl_extensions/Cargo.toml +@@ -5,8 +5,8 @@ edition.workspace = true + license.workspace = true + + [dependencies] +-foreign-types = "0.3" +-libc = {version = "0.2", features = [ "extra_traits"] } +-log = { version = "0.4", features = ["std", "release_max_level_debug"] } +-openssl = "0.10" +-openssl-sys = "0.9" ++foreign-types = "0.3.1" ++libc = {version = "0.2.49", features = [ "extra_traits"] } ++log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } ++openssl = "0.10.49" ++openssl-sys = "0.9.85" +diff --git a/rust/pvsecret/Cargo.toml b/rust/pvsecret/Cargo.toml +index d1e75b13..e236c005 100644 +--- a/rust/pvsecret/Cargo.toml ++++ b/rust/pvsecret/Cargo.toml +@@ -5,9 +5,9 @@ edition.workspace = true + license.workspace = true + + [dependencies] +-anyhow = { version = "1", features = ["std"] } ++anyhow = { version = "1.0.70", features = ["std"] } + clap = { version ="4", features = ["derive", "wrap_help"]} +-log = { version = "0.4", features = ["std", "release_max_level_debug"] } ++log = { version = "0.4.6", features = ["std", "release_max_level_debug"] } + serde_yaml = "0.9" + + pv = { path = "../pv", features = ["uvsecret", "request"] } +-- +2.53.0 + + +From b6009c80b112ad85ca2aa649126b913af5af253c Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 29 Nov 2023 17:06:50 +0100 +Subject: [PATCH 09/22] rust: Use default panic behaviour + +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +(cherry picked from commit ae0cbf00b11bfe195c9e6e3b4168a3dccacde983) +--- + rust/Cargo.toml | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/rust/Cargo.toml b/rust/Cargo.toml +index 65a70a97..7ba1faa1 100644 +--- a/rust/Cargo.toml ++++ b/rust/Cargo.toml +@@ -12,4 +12,3 @@ license = "MIT" + + [profile.release] + lto = true +-panic = "abort" +-- +2.53.0 + + +From c4e48d060b7d92d7c6cd150728ecb55b301afa62 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Thu, 30 Nov 2023 16:02:16 +0100 +Subject: [PATCH 10/22] rust/pv: Update mockito to version 1 + +Signed-off-by: Steffen Eiden +(cherry picked from commit 21662d38e68b58bad033cdb1fca99987dd07cf78) +--- + rust/Cargo.lock | 468 ++++++++++++++++++++++++++++++++++++- + rust/pv/Cargo.toml | 2 +- + rust/pv/src/verify/test.rs | 4 +- + 3 files changed, 465 insertions(+), 9 deletions(-) + +diff --git a/rust/Cargo.lock b/rust/Cargo.lock +index f7d1cf0f..067be057 100644 +--- a/rust/Cargo.lock ++++ b/rust/Cargo.lock +@@ -2,6 +2,21 @@ + # It is not intended for manual editing. + version = 3 + ++[[package]] ++name = "addr2line" ++version = "0.21.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" ++dependencies = [ ++ "gimli", ++] ++ ++[[package]] ++name = "adler" ++version = "1.0.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" ++ + [[package]] + name = "aho-corasick" + version = "1.1.2" +@@ -82,6 +97,21 @@ version = "1.1.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + ++[[package]] ++name = "backtrace" ++version = "0.3.69" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" ++dependencies = [ ++ "addr2line", ++ "cc", ++ "cfg-if", ++ "libc", ++ "miniz_oxide", ++ "object", ++ "rustc-demangle", ++] ++ + [[package]] + name = "bitflags" + version = "1.3.2" +@@ -100,6 +130,12 @@ version = "1.4.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + ++[[package]] ++name = "bytes" ++version = "1.5.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" ++ + [[package]] + name = "cc" + version = "1.0.79" +@@ -172,7 +208,7 @@ dependencies = [ + "openssl-probe", + "openssl-sys", + "schannel", +- "socket2", ++ "socket2 0.4.9", + "winapi", + ] + +@@ -191,6 +227,12 @@ dependencies = [ + "windows-sys 0.48.0", + ] + ++[[package]] ++name = "equivalent" ++version = "1.0.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" ++ + [[package]] + name = "errno" + version = "0.3.1" +@@ -212,6 +254,12 @@ dependencies = [ + "libc", + ] + ++[[package]] ++name = "fnv" ++version = "1.0.7" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" ++ + [[package]] + name = "foreign-types" + version = "0.3.2" +@@ -236,6 +284,95 @@ dependencies = [ + "percent-encoding", + ] + ++[[package]] ++name = "futures" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" ++dependencies = [ ++ "futures-channel", ++ "futures-core", ++ "futures-executor", ++ "futures-io", ++ "futures-sink", ++ "futures-task", ++ "futures-util", ++] ++ ++[[package]] ++name = "futures-channel" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" ++dependencies = [ ++ "futures-core", ++ "futures-sink", ++] ++ ++[[package]] ++name = "futures-core" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" ++ ++[[package]] ++name = "futures-executor" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" ++dependencies = [ ++ "futures-core", ++ "futures-task", ++ "futures-util", ++] ++ ++[[package]] ++name = "futures-io" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" ++ ++[[package]] ++name = "futures-macro" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "syn 2.0.18", ++] ++ ++[[package]] ++name = "futures-sink" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" ++ ++[[package]] ++name = "futures-task" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" ++ ++[[package]] ++name = "futures-util" ++version = "0.3.29" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" ++dependencies = [ ++ "futures-channel", ++ "futures-core", ++ "futures-io", ++ "futures-macro", ++ "futures-sink", ++ "futures-task", ++ "memchr", ++ "pin-project-lite", ++ "pin-utils", ++ "slab", ++] ++ + [[package]] + name = "getrandom" + version = "0.2.10" +@@ -247,12 +384,43 @@ dependencies = [ + "wasi", + ] + ++[[package]] ++name = "gimli" ++version = "0.28.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" ++ ++[[package]] ++name = "h2" ++version = "0.3.22" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" ++dependencies = [ ++ "bytes", ++ "fnv", ++ "futures-core", ++ "futures-sink", ++ "futures-util", ++ "http", ++ "indexmap 2.1.0", ++ "slab", ++ "tokio", ++ "tokio-util", ++ "tracing", ++] ++ + [[package]] + name = "hashbrown" + version = "0.12.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + ++[[package]] ++name = "hashbrown" ++version = "0.14.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" ++ + [[package]] + name = "heck" + version = "0.4.1" +@@ -265,12 +433,64 @@ version = "0.3.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + ++[[package]] ++name = "http" ++version = "0.2.11" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" ++dependencies = [ ++ "bytes", ++ "fnv", ++ "itoa", ++] ++ ++[[package]] ++name = "http-body" ++version = "0.4.5" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" ++dependencies = [ ++ "bytes", ++ "http", ++ "pin-project-lite", ++] ++ + [[package]] + name = "httparse" + version = "1.8.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + ++[[package]] ++name = "httpdate" ++version = "1.0.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" ++ ++[[package]] ++name = "hyper" ++version = "0.14.27" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" ++dependencies = [ ++ "bytes", ++ "futures-channel", ++ "futures-core", ++ "futures-util", ++ "h2", ++ "http", ++ "http-body", ++ "httparse", ++ "httpdate", ++ "itoa", ++ "pin-project-lite", ++ "socket2 0.4.9", ++ "tokio", ++ "tower-service", ++ "tracing", ++ "want", ++] ++ + [[package]] + name = "indexmap" + version = "1.9.3" +@@ -278,7 +498,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" + dependencies = [ + "autocfg", +- "hashbrown", ++ "hashbrown 0.12.3", ++] ++ ++[[package]] ++name = "indexmap" ++version = "2.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" ++dependencies = [ ++ "equivalent", ++ "hashbrown 0.14.3", + ] + + [[package]] +@@ -340,6 +570,16 @@ version = "0.3.8" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + ++[[package]] ++name = "lock_api" ++version = "0.4.11" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" ++dependencies = [ ++ "autocfg", ++ "scopeguard", ++] ++ + [[package]] + name = "log" + version = "0.4.19" +@@ -352,21 +592,61 @@ version = "2.6.4" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + ++[[package]] ++name = "miniz_oxide" ++version = "0.7.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" ++dependencies = [ ++ "adler", ++] ++ ++[[package]] ++name = "mio" ++version = "0.8.8" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" ++dependencies = [ ++ "libc", ++ "wasi", ++ "windows-sys 0.48.0", ++] ++ + [[package]] + name = "mockito" +-version = "0.31.1" ++version = "1.2.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "80f9fece9bd97ab74339fe19f4bcaf52b76dcc18e5364c7977c1838f76b38de9" ++checksum = "f8d3038e23466858569c2d30a537f691fa0d53b51626630ae08262943e3bbb8b" + dependencies = [ + "assert-json-diff", +- "httparse", +- "lazy_static", ++ "futures", ++ "hyper", + "log", + "rand", + "regex", + "serde_json", + "serde_urlencoded", + "similar", ++ "tokio", ++] ++ ++[[package]] ++name = "num_cpus" ++version = "1.16.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" ++dependencies = [ ++ "hermit-abi", ++ "libc", ++] ++ ++[[package]] ++name = "object" ++version = "0.32.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" ++dependencies = [ ++ "memchr", + ] + + [[package]] +@@ -430,12 +710,47 @@ dependencies = [ + "openssl-sys", + ] + ++[[package]] ++name = "parking_lot" ++version = "0.12.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" ++dependencies = [ ++ "lock_api", ++ "parking_lot_core", ++] ++ ++[[package]] ++name = "parking_lot_core" ++version = "0.9.9" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" ++dependencies = [ ++ "cfg-if", ++ "libc", ++ "redox_syscall", ++ "smallvec", ++ "windows-targets", ++] ++ + [[package]] + name = "percent-encoding" + version = "2.3.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + ++[[package]] ++name = "pin-project-lite" ++version = "0.2.13" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" ++ ++[[package]] ++name = "pin-utils" ++version = "0.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" ++ + [[package]] + name = "pkg-config" + version = "0.3.27" +@@ -528,6 +843,15 @@ dependencies = [ + "getrandom", + ] + ++[[package]] ++name = "redox_syscall" ++version = "0.4.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" ++dependencies = [ ++ "bitflags 1.3.2", ++] ++ + [[package]] + name = "regex" + version = "1.10.2" +@@ -557,6 +881,12 @@ version = "0.8.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + ++[[package]] ++name = "rustc-demangle" ++version = "0.1.23" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" ++ + [[package]] + name = "rustix" + version = "0.37.27" +@@ -586,6 +916,12 @@ dependencies = [ + "windows-sys 0.42.0", + ] + ++[[package]] ++name = "scopeguard" ++version = "1.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" ++ + [[package]] + name = "serde" + version = "1.0.164" +@@ -644,19 +980,43 @@ version = "0.9.21" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" + dependencies = [ +- "indexmap", ++ "indexmap 1.9.3", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", + ] + ++[[package]] ++name = "signal-hook-registry" ++version = "1.4.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" ++dependencies = [ ++ "libc", ++] ++ + [[package]] + name = "similar" + version = "2.3.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" + ++[[package]] ++name = "slab" ++version = "0.4.9" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" ++dependencies = [ ++ "autocfg", ++] ++ ++[[package]] ++name = "smallvec" ++version = "1.11.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" ++ + [[package]] + name = "socket2" + version = "0.4.9" +@@ -667,6 +1027,16 @@ dependencies = [ + "winapi", + ] + ++[[package]] ++name = "socket2" ++version = "0.5.4" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" ++dependencies = [ ++ "libc", ++ "windows-sys 0.48.0", ++] ++ + [[package]] + name = "strsim" + version = "0.10.0" +@@ -725,6 +1095,81 @@ dependencies = [ + "syn 2.0.18", + ] + ++[[package]] ++name = "tokio" ++version = "1.33.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" ++dependencies = [ ++ "backtrace", ++ "bytes", ++ "libc", ++ "mio", ++ "num_cpus", ++ "parking_lot", ++ "pin-project-lite", ++ "signal-hook-registry", ++ "socket2 0.5.4", ++ "tokio-macros", ++ "windows-sys 0.48.0", ++] ++ ++[[package]] ++name = "tokio-macros" ++version = "2.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "syn 2.0.18", ++] ++ ++[[package]] ++name = "tokio-util" ++version = "0.7.10" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" ++dependencies = [ ++ "bytes", ++ "futures-core", ++ "futures-sink", ++ "pin-project-lite", ++ "tokio", ++ "tracing", ++] ++ ++[[package]] ++name = "tower-service" ++version = "0.3.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" ++ ++[[package]] ++name = "tracing" ++version = "0.1.40" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" ++dependencies = [ ++ "pin-project-lite", ++ "tracing-core", ++] ++ ++[[package]] ++name = "tracing-core" ++version = "0.1.32" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" ++dependencies = [ ++ "once_cell", ++] ++ ++[[package]] ++name = "try-lock" ++version = "0.2.4" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" ++ + [[package]] + name = "unicode-ident" + version = "1.0.9" +@@ -753,6 +1198,15 @@ version = "0.2.15" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + ++[[package]] ++name = "want" ++version = "0.3.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" ++dependencies = [ ++ "try-lock", ++] ++ + [[package]] + name = "wasi" + version = "0.11.0+wasi-snapshot-preview1" +diff --git a/rust/pv/Cargo.toml b/rust/pv/Cargo.toml +index 22d5fd34..ff845263 100644 +--- a/rust/pv/Cargo.toml ++++ b/rust/pv/Cargo.toml +@@ -22,7 +22,7 @@ serde = { version = "1.0.139", features = ["derive"], optional = true } + byteorder = {version = "1.3", optional = true } + + [dev-dependencies] +-mockito = {version = "0.31", default-features = false } ++mockito = {version = "1", default-features = false } + serde_test = "1" + lazy_static = "1.1" + +diff --git a/rust/pv/src/verify/test.rs b/rust/pv/src/verify/test.rs +index 1c0d2b56..5ca2e712 100644 +--- a/rust/pv/src/verify/test.rs ++++ b/rust/pv/src/verify/test.rs +@@ -14,7 +14,9 @@ use crate::test_utils::*; + pub fn mock_endpt(res: &str) -> mockito::Mock { + let res_path = get_cert_asset_path(res); + +- mockito::mock("GET", format!("/crl/{res}").as_str()) ++ let mut server = mockito::Server::new(); ++ server ++ .mock("GET", format!("/crl/{res}").as_str()) + .with_header("content-type", "application/pkix-crl") + .with_body_from_file(res_path) + .create() +-- +2.53.0 + + +From 66783f1901dcaca6f567ad13b05acc7dbe412ff0 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 20 Dec 2023 13:31:18 +0100 +Subject: [PATCH 11/22] rust/Makefile: Fix CC/AR variables for TEST_TARGETS +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 6fd02279da20acba882426496e3b87e556bdeabc) +--- + rust/Makefile | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/rust/Makefile b/rust/Makefile +index e4e98859..fa3cf04d 100644 +--- a/rust/Makefile ++++ b/rust/Makefile +@@ -37,8 +37,8 @@ endif + + # the cc crate uses these variables to compile c code. It does not open a shell + # to call the compiler, so no echo etc. allowed here, just a path to a program +-$(BUILD_TARGETS) rust-test: CC = $(CC_SILENT) +-$(BUILD_TARGETS) rust-test: AR = $(AR_SILENT) ++$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: CC = $(CC_SILENT) ++$(BUILD_TARGETS) $(TEST_TARGETS) rust-test: AR = $(AR_SILENT) + + $(PV_TARGETS): .check-dep-pvtools + $(PV_TARGETS) $(CARGO_TARGETS): .check-cargo .no-cross-compile +-- +2.53.0 + + +From d54a8aa4d7b77338fd5511d895eadbb074b6024a Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Fri, 15 Dec 2023 11:30:14 +0100 +Subject: [PATCH 12/22] rust/pv: Provide access for SecretList members +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Adds getter for SecretList and SecretEntry. +Adds enum to represent secret types. +Add Iterator functionality for SecretList. + +While at it, make the datatype of the capacity of the list transparent +for users. + +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 0764460eafdfd91f78bbed5ac818f19d60e14b70) +--- + rust/pv/src/lib.rs | 2 +- + rust/pv/src/uvsecret/secret_list.rs | 170 ++++++++++++++++++++++++---- + 2 files changed, 151 insertions(+), 21 deletions(-) + +diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs +index d72ac93e..ed891408 100644 +--- a/rust/pv/src/lib.rs ++++ b/rust/pv/src/lib.rs +@@ -70,7 +70,7 @@ pub mod uv { + }; + #[cfg(feature = "uvsecret")] + pub use crate::uvsecret::{ +- secret_list::SecretList, ++ secret_list::{ListableSecretType, SecretEntry, SecretList}, + uvc::{AddCmd, ListCmd, LockCmd}, + }; + } +diff --git a/rust/pv/src/uvsecret/secret_list.rs b/rust/pv/src/uvsecret/secret_list.rs +index 6943bd31..72a05b27 100644 +--- a/rust/pv/src/uvsecret/secret_list.rs ++++ b/rust/pv/src/uvsecret/secret_list.rs +@@ -2,13 +2,14 @@ + // + // Copyright IBM Corp. 2023 + +-use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result}; ++use crate::{assert_size, misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result}; + use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + use serde::{Serialize, Serializer}; +-use std::usize; + use std::{ + fmt::Display, + io::{Cursor, Read, Seek, Write}, ++ slice::Iter, ++ vec::IntoIter, + }; + use zerocopy::{AsBytes, FromBytes, U16, U32}; + +@@ -19,16 +20,79 @@ use super::ser_gsid; + /// Requires the `uvsecret` feature. + #[derive(Debug, PartialEq, Eq, Serialize)] + pub struct SecretList { +- total_num_secrets: u16, ++ total_num_secrets: usize, + secrets: Vec, + } + ++impl<'a> IntoIterator for &'a SecretList { ++ type Item = &'a SecretEntry; ++ type IntoIter = Iter<'a, SecretEntry>; ++ ++ fn into_iter(self) -> Self::IntoIter { ++ self.iter() ++ } ++} ++ ++impl IntoIterator for SecretList { ++ type Item = SecretEntry; ++ type IntoIter = IntoIter; ++ ++ fn into_iter(self) -> Self::IntoIter { ++ self.secrets.into_iter() ++ } ++} ++ ++impl FromIterator for SecretList { ++ fn from_iter>(iter: T) -> Self { ++ let secrets: Vec<_> = iter.into_iter().collect(); ++ let total_num_secrets = secrets.len() as u16; ++ Self::new(total_num_secrets, secrets) ++ } ++} ++ + impl SecretList { ++ #[doc(hidden)] ++ /// For testing purposes. ++ pub fn new(total_num_secrets: u16, secrets: Vec) -> Self { ++ Self { ++ total_num_secrets: total_num_secrets as usize, ++ secrets, ++ } ++ } ++ ++ /// Returns an iterator over the slice. ++ /// ++ /// The iterator yields all secret entries from start to end. ++ pub fn iter(&self) -> Iter<'_, SecretEntry> { ++ self.secrets.iter() ++ } ++ ++ /// Returns the length of this [`SecretList`]. ++ pub fn len(&self) -> usize { ++ self.secrets.len() ++ } ++ ++ /// Check for is_empty of this [`SecretList`]. ++ pub fn is_empty(&self) -> bool { ++ self.secrets.is_empty() ++ } ++ ++ /// Reports the number of secrets stored in UV ++ /// ++ /// This number may be not equal to the provided number of [`SecretEntry`] ++ pub fn total_num_secrets(&self) -> usize { ++ self.total_num_secrets ++ } ++ + /// Encodes the list in the same binary format the UV would do + pub fn encode(&self, w: &mut T) -> Result<()> { + let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?; + w.write_u16::(num_s)?; +- w.write_u16::(self.total_num_secrets)?; ++ w.write_u16::( ++ self.total_num_secrets ++ .try_into() ++ .map_err(|_| Error::ManySecrets)?, ++ )?; + w.write_all(&[0u8; 12])?; + for secret in &self.secrets { + w.write_all(secret.as_bytes())?; +@@ -39,10 +103,10 @@ impl SecretList { + /// Decodes the list from the binary format of the UV into this internal representation + pub fn decode(r: &mut R) -> std::io::Result { + let num_s = r.read_u16::()?; +- let total_num_secrets = r.read_u16::()?; ++ let total_num_secrets = r.read_u16::()? as usize; + let mut v: Vec = Vec::with_capacity(num_s as usize); + r.seek(std::io::SeekFrom::Current(12))?; //skip reserved bytes +- let mut buf = [0u8; SECRET_ENTRY_SIZE]; ++ let mut buf = [0u8; SecretEntry::STRUCT_SIZE]; + for _ in 0..num_s { + r.read_exact(&mut buf)?; + //cannot fail. buffer has the same size as the secret entry +@@ -84,9 +148,56 @@ fn ser_u16(v: &U16, ser: S) -> Result + ser.serialize_u16(v.get()) + } + ++/// Secret types that can appear in a [`SecretList`] ++#[non_exhaustive] ++#[derive(PartialEq, Eq)] ++pub enum ListableSecretType { ++ /// Association Secret ++ Association, ++ /// Invalid secret type, that should never appear in a list ++ /// ++ /// 0 is reserved ++ /// 1 is Null secret, with no id and not listable ++ Invalid(u16), ++ /// Unknown secret type ++ Unknown(u16), ++} ++impl ListableSecretType { ++ const ASSOCIATION: u16 = 0x0002; ++} ++ ++impl Display for ListableSecretType { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ match self { ++ Self::Association => write!(f, "Association"), ++ Self::Invalid(n) => write!(f, "Invalid({n})"), ++ Self::Unknown(n) => write!(f, "Unknown({n})"), ++ } ++ } ++} ++ ++impl From> for ListableSecretType { ++ fn from(value: U16) -> Self { ++ match value.get() { ++ 0x0000 => Self::Invalid(0), ++ 0x0001 => Self::Invalid(1), ++ Self::ASSOCIATION => ListableSecretType::Association, ++ n => Self::Unknown(n), ++ } ++ } ++} ++ ++impl From for U16 { ++ fn from(value: ListableSecretType) -> Self { ++ match value { ++ ListableSecretType::Association => ListableSecretType::ASSOCIATION, ++ ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n, ++ } ++ .into() ++ } ++} ++ + /// A secret in a [`SecretList`] +-/// +-/// Fields are in big endian + #[repr(C)] + #[derive(Debug, PartialEq, Eq, AsBytes, FromBytes, Serialize)] + pub struct SecretEntry { +@@ -101,20 +212,43 @@ pub struct SecretEntry { + #[serde(serialize_with = "ser_gsid")] + id: [u8; 32], + } +-const SECRET_ENTRY_SIZE: usize = 0x30; ++assert_size!(SecretEntry, SecretEntry::STRUCT_SIZE); ++ ++impl SecretEntry { ++ const STRUCT_SIZE: usize = 0x30; ++ ++ #[doc(hidden)] ++ /// For testing purposes. ++ pub fn new(index: u16, stype: ListableSecretType, id: [u8; 32], secret_len: u32) -> Self { ++ Self { ++ index: index.into(), ++ stype: stype.into(), ++ len: secret_len.into(), ++ res_8: 0, ++ id, ++ } ++ } + +-fn stype_str(stype: u16) -> String { +- match stype { +- // should never match (not incl in list), but here for completeness +- 1 => "Null".to_string(), +- 2 => "Association".to_string(), +- n => format!("Unknown {n}"), ++ /// Returns the index of this [`SecretEntry`]. ++ pub fn index(&self) -> u16 { ++ self.index.get() ++ } ++ ++ /// Returns the secret type of this [`SecretEntry`]. ++ pub fn stype(&self) -> ListableSecretType { ++ self.stype.into() ++ } ++ ++ /// Returns a reference to the id of this [`SecretEntry`]. ++ pub fn id(&self) -> &[u8] { ++ &self.id + } + } + + impl Display for SecretEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- writeln!(f, "{} {}:", self.index, stype_str(self.stype.get()))?; ++ let stype: ListableSecretType = self.stype.into(); ++ writeln!(f, "{} {}:", self.index, stype)?; + write!(f, " ")?; + for b in self.id { + write!(f, "{b:02x}")?; +@@ -128,10 +262,6 @@ mod test { + use super::*; + use std::io::{BufReader, BufWriter, Cursor}; + +- #[test] +- fn secret_entry_size() { +- assert_eq!(::std::mem::size_of::(), SECRET_ENTRY_SIZE); +- } + #[test] + fn dump_secret_entry() { + const EXP: &[u8] = &[ +-- +2.53.0 + + +From e75bbd754e5912d34c0aedfe35ccedd54ca850be Mon Sep 17 00:00:00 2001 +From: Harald Freudenberger +Date: Fri, 1 Dec 2023 12:10:20 +0100 +Subject: [PATCH 13/22] rust/pvapconfig: Introduce new tool pvapconfig +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +pvapconfig is a new tool for automatically configuring the APQNs +within an Secure Execution KVM guest with AP pass-through support. +Based on a given AP configuration it tries to find a matching +APQN and bind and associate it with the correct secret. + +Signed-off-by: Harald Freudenberger +Reviewed-by: Ingo Franzki +Reviewed-by: Holger Dengler +Reviewed-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 94a38ebc3aec9ee954f1ae9a4a0a09ea7f29f11c) +--- + rust/Cargo.lock | 16 + + rust/Cargo.toml | 1 + + rust/Makefile | 2 +- + rust/pvapconfig/Cargo.toml | 19 + + rust/pvapconfig/README.md | 9 + + rust/pvapconfig/man/pvapconfig.1 | 174 +++++++ + rust/pvapconfig/src/ap.rs | 817 +++++++++++++++++++++++++++++++ + rust/pvapconfig/src/cli.rs | 63 +++ + rust/pvapconfig/src/config.rs | 391 +++++++++++++++ + rust/pvapconfig/src/helper.rs | 272 ++++++++++ + rust/pvapconfig/src/main.rs | 668 +++++++++++++++++++++++++ + rust/pvapconfig/src/uv.rs | 105 ++++ + 12 files changed, 2536 insertions(+), 1 deletion(-) + create mode 100644 rust/pvapconfig/Cargo.toml + create mode 100644 rust/pvapconfig/README.md + create mode 100644 rust/pvapconfig/man/pvapconfig.1 + create mode 100644 rust/pvapconfig/src/ap.rs + create mode 100644 rust/pvapconfig/src/cli.rs + create mode 100644 rust/pvapconfig/src/config.rs + create mode 100644 rust/pvapconfig/src/helper.rs + create mode 100644 rust/pvapconfig/src/main.rs + create mode 100644 rust/pvapconfig/src/uv.rs + +diff --git a/rust/Cargo.lock b/rust/Cargo.lock +index 067be057..3a6c4238 100644 +--- a/rust/Cargo.lock ++++ b/rust/Cargo.lock +@@ -792,6 +792,22 @@ dependencies = [ + "zerocopy", + ] + ++[[package]] ++name = "pvapconfig" ++version = "0.9.0" ++dependencies = [ ++ "clap", ++ "lazy_static", ++ "openssl", ++ "openssl-sys", ++ "pv", ++ "rand", ++ "regex", ++ "serde", ++ "serde_yaml", ++ "utils", ++] ++ + [[package]] + name = "pvsecret" + version = "0.9.0" +diff --git a/rust/Cargo.toml b/rust/Cargo.toml +index 7ba1faa1..f358dbd9 100644 +--- a/rust/Cargo.toml ++++ b/rust/Cargo.toml +@@ -1,6 +1,7 @@ + [workspace] + members = [ + "pv", ++ "pvapconfig", + "pvsecret", + "utils", + ] +diff --git a/rust/Makefile b/rust/Makefile +index fa3cf04d..818694d0 100644 +--- a/rust/Makefile ++++ b/rust/Makefile +@@ -19,7 +19,7 @@ ifneq (${HAVE_CARGO},0) + + ifneq (${HAVE_OPENSSL},0) + ifneq (${HAVE_LIBCURL},0) +- PV_TARGETS := pvsecret ++ PV_TARGETS := pvsecret pvapconfig + + PV_BUILD_TARGETS := $(PV_TARGETS) + CARGO_TEST_TARGETS += $(addsuffix .test,pv $(PV_TARGETS)) +diff --git a/rust/pvapconfig/Cargo.toml b/rust/pvapconfig/Cargo.toml +new file mode 100644 +index 00000000..2a69468c +--- /dev/null ++++ b/rust/pvapconfig/Cargo.toml +@@ -0,0 +1,19 @@ ++[package] ++name = "pvapconfig" ++description = "A tool to configure the AP resources inside a SE guest based on UV secrets and an AP config file." ++authors = ["Harald Freudenberger "] ++version = "0.9.0" ++edition.workspace = true ++license.workspace = true ++ ++[dependencies] ++clap = { version ="4.1", features = ["derive", "wrap_help"]} ++lazy_static = "1.1" ++openssl = { version = "0.10" } ++openssl-sys = { version = "0.9" } ++pv = { path = "../pv", features = ["uvsecret", "request"] } ++rand = "0.8" ++regex = "1" ++serde = { version = "1.0", features = ["derive"] } ++serde_yaml = "0.9" ++utils = { path = "../utils" } +diff --git a/rust/pvapconfig/README.md b/rust/pvapconfig/README.md +new file mode 100644 +index 00000000..d30cb76d +--- /dev/null ++++ b/rust/pvapconfig/README.md +@@ -0,0 +1,9 @@ ++ ++# pvapconfig ++## Description ++**pvapconfig** is used to automatically set up the AP configuration ++within an IBM Secure Execution guest. +diff --git a/rust/pvapconfig/man/pvapconfig.1 b/rust/pvapconfig/man/pvapconfig.1 +new file mode 100644 +index 00000000..73ce8cea +--- /dev/null ++++ b/rust/pvapconfig/man/pvapconfig.1 +@@ -0,0 +1,174 @@ ++.\" pvapconfig.1 ++.\" ++.\" Copyright 2023 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++.\" use ++.\" groff -man -Tutf8 pvapconfig.1 ++.\" or ++.\" nroff -man pvapconfig.1 ++.\" to process this source ++.\" ++.TH PVAPCONFIG 1 "DEC 2023" "s390-tools" ++.SH NAME ++pvapconfig \- automatic configure APQNs within an SE KVM guest. ++.SH SYNOPSIS ++.TP 9 ++.B pvapconfig [OPTIONS] ++.SH DESCRIPTION ++pvapconfig is a tool for automatically configuring the APQNs within an ++Secure Execution KVM guest with AP pass-through support. Based on a ++given AP configuration it tries to find matching APQNs and binds and ++associates them with the given secret(s). ++ ++Here is a description of pvapconfig's process: ++.TP 3 ++1. Check AP bus: Support for AP bus needs to be available and the AP ++bus needs to support APSB. APSB is only available within an KVM SE ++guest with AP pass-through support. ++.TP 3 ++2. Check Ultravisor: UV support needs to be available and the UV needs ++to support the ++.I list secrets ++feature. ++.TP 3 ++3. Read in and validate the AP configuration file. By default if not ++overwritten by the --config option the AP configuration is read from ++.I /etc/pvapconfig.yaml ++and syntactically verified. See section CONFIGFILE for details about ++the syntax and semantic of the configuration file. ++.TP 3 ++4. Fetch the list of association secrets from the UV. Actually the ++index of the secret and the secret id for each entry is collected. The ++secret value is NOT fetched as it is NOT accessible but only usable ++within the UV firmware. ++.TP 3 ++5. Gather all APQNs available within this KVM SE guest. Collect ++information about each APQN like online states, crypto card serial ++numbers and master key verification patterns (MKVP). ++.TP 3 ++6. Go through all AP config entries. For each AP config entry try to ++find an APQN which is already configured (bound/associated) to ++satisfy this config entry. If such a match is found, the AP config ++entry is assumed to be fulfilled and marked as done. ++.TP 3 ++7. All remaining APQNs which do not already satisfy an AP config entry ++are now examined for their bind and association state and maybe reset ++to unbound state. ++.TP 3 ++8. Go through all AP config entries which are still not ++fulfilled. For each such AP config entry try to search for an APQN ++which would match to this entry and then prepare this APQN (bind, ++maybe associate). If successful, mark the AP config entry as done. ++.TP 3 ++9. Evaluation of the applied AP config entries. Applied means the AP ++config entry has been fulfilled either in step 6 or in step 8. With ++the strict option given ALL AP config entries need to apply otherwise ++an error message is printed and pvapconfig returns with exit failure. ++If the strict option is not given, it is enough to satisfy at least one ++AP config entry from the configuration and pvapconfig will return ++successfully. ++.SH OPTIONS ++.TP 8 ++.B -c, --config ++Use as the AP config file for pvapconfig. If pvapconfig ++is run without this option the default configuration file ++/etc/pvapconfig.yaml is used. ++.TP 8 ++.B -h, --help ++Print pvapconfig usage information and exit. ++.TP 8 ++.B -n, --dry-run ++Do not bind, unbind or associate APQNs but only process the ++configuration and the available APQNs and secrets and simulate the ++bind, unbind or associate action on the chosen APQN. Use it together ++with the verbose option to see which actions pvapconfig would do if ++unleashed. ++.TP 8 ++.B -s, --strict ++All AP config entries need to be satisfied to have pvapconfig ++terminate with success. Without this option one applied AP config ++entry is enough to meet the expectations. ++.TP 8 ++.B -v, --verbose ++Print out informational messages about what pvapconfig is actually ++doing. ++.TP 8 ++.B -V, --version ++Print version information and exit. ++.SH CONFIGFILE ++The pvapconfig yaml configuration file consists of a list of AP config ++entries. Each entry may hold this information: ++.TP 2 ++- mode: AP queue mode information, required, either "EP11" or "Accel". ++.TP 2 ++- mkvp: AP queue Master Key Verification Pattern (MKVP), required for ++EP11, hex string optional prepented with 0x. The MKVP hex string value ++may hold either 16 bytes (32 hex characters) or 32 bytes (64 hex ++characters) but only the leftmost 16 bytes hold MKVP information and ++thus the rest is ignored. ++.TP 2 ++- serialnr: Crypto Card Serial Number, string, optional for EP11, ++ignored for Accel. As this is a real ASCII string uppercase and ++lowercase character(s) count different. ++.TP 2 ++- mingen: Card Minimal Generation, string "CEX4", "CEX5", "CEX6", ++"CEX7" or "CEX8" for Accelerator, string "CEC8" for EP11, optional. If ++given specifies the minimal accepted Crypto card generation. ++.TP 2 ++- secretid: Secret id, hex string with optional 0x prepented, required ++for EP11, ignored for Accel. Details see the following text. ++.TP 2 ++- name: ASCII string, optional, but see details below. ++.TP 2 ++- description: Description of this AP config entry, string, ignored, ++ just for convenience for the reader or editor of the configuration. ++.PP ++The secret id uniquely identifies an association secret. However, it ++is a clumsy hex string of 64 characters which represent the readable ++sha256 value over the secret's name. So pvapconfig can use the name ++instead and calculate the secret id from the name. So the rule is: ++.TP 2 ++- If name and secretid is given, the secretid needs to match to the ++sha256 hash over the given name for this AP config entry. ++.TP 2 ++- If only name is given then the secretid is calculated with a sha256 ++ hash over the given name. ++.TP 2 ++- If only the secretid is given, there is nothing more to do but ++verify that the value is a hex string with 64 characters. ++.SH LOCKING ++Pvapconfig needs to have a consistent view of the AP resources ++during lifetime. There must not run multiple instances of pvapconfig ++or any manipulations of the AP resources in parallel. To prevent the ++execution of multiple pvapconfig instances the lock file ++/run/lock/pvapconfig.lock is established. A second instance of ++pvapconfig will detect this lock file and terminated with an error ++message. If for any reason this file still exists as a leftover from a ++previous pvapconfig crash for example, it needs to get removed by ++hand. The lock file contains the process id of the pvapconfig process ++which created this file. ++.SH RETURN VALUE ++.TP 8 ++.B 0 - Successful termination. ++At least one AP config entry has been applied or at least one APQN has ++been found in a state matching to one AP config entry. If strict ++option is given, ALL AP config entries have been applied. An AP config ++entry is applied either by configuring the APQN accordingly or an APQN ++has been found which already fulfills the constrains. ++.RE ++.TP 8 ++.B 1 - Failure. ++Either some kind of failure happened during processing the ++configuration or the configuration could not get applied ++successful. In all cases pvapconfig prints out a message to standard ++error with details about the failure. Also pvapconfig does NOT reset ++the APQNs to the state found at the startup when failing to apply the ++configuration. ++.SH NOTES ++For more information and details see the IBM documentation about ++Confidential Computing "Introducing IBM Secure Execution for Linux" ++available at https://www.ibm.com/docs/. ++.SH SEE ALSO ++\fBpvsecret\fR(1), \fBlszcrypt\fR(8), \fBchzcrypt\fR(8) +diff --git a/rust/pvapconfig/src/ap.rs b/rust/pvapconfig/src/ap.rs +new file mode 100644 +index 00000000..0b882359 +--- /dev/null ++++ b/rust/pvapconfig/src/ap.rs +@@ -0,0 +1,817 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2023 ++// ++//! AP support functions for pvapconfig ++// ++ ++use crate::helper::*; ++use regex::Regex; ++use std::fmt; ++use std::path::Path; ++use std::slice::Iter; ++use std::thread; ++use std::time; ++ ++const PATH_SYS_BUS_AP: &str = "/sys/bus/ap"; ++const PATH_SYS_BUS_AP_FEATURES: &str = "/sys/bus/ap/features"; ++const PATH_SYS_BUS_AP_BINDINGS: &str = "/sys/bus/ap/bindings"; ++const PATH_SYS_DEVICES_AP: &str = "/sys/devices/ap"; ++ ++const RE_CARD_DIR: &str = r"^card([[:xdigit:]]{2})$"; ++const RE_QUEUE_DIR: &str = r"^([[:xdigit:]]{2})\.([[:xdigit:]]{4})$"; ++const RE_CARD_TYPE: &str = r"^CEX([3-8])([ACP])$"; ++const RE_EP11_MKVP: &str = r"WK\s+CUR:\s+(\S+)\s+(\S+)"; ++const RE_CCA_AES_MKVP: &str = r"AES\s+CUR:\s+(\S+)\s+(\S+)"; ++const RE_CCA_APKA_MKVP: &str = r"APKA\s+CUR:\s+(\S+)\s+(\S+)"; ++ ++const SYS_BUS_AP_BINDINGS_POLL_MS: u64 = 500; ++ ++const SYS_BUS_AP_BIND_POLL_MS: u64 = 500; ++const SYS_BUS_AP_BIND_TIMEOUT_MS: u64 = 10000; ++ ++const SYS_BUS_AP_ASSOC_POLL_MS: u64 = 500; ++const SYS_BUS_AP_ASSOC_TIMEOUT_MS: u64 = 10000; ++ ++/// Check if AP bus support is available. ++/// Returns Result with Ok(()) or Err(failurestring). ++pub fn check_ap_bus_support() -> Result<(), String> { ++ if !Path::new(PATH_SYS_BUS_AP).is_dir() { ++ return Err(format!( ++ "AP bus support missing (path {PATH_SYS_BUS_AP} is invalid)." ++ )); ++ } ++ Ok(()) ++} ++ ++/// Check if AP bus supports APSB. ++/// ++/// When APSB support is available returns Result ++/// with Ok(()) or otherwise Err(failurestring). ++pub fn ap_bus_has_apsb_support() -> Result<(), String> { ++ if !Path::new(PATH_SYS_BUS_AP_FEATURES).is_file() { ++ return Err(format!( ++ "AP bus features support missing (file {PATH_SYS_BUS_AP_FEATURES} does not exist)." ++ )); ++ } ++ let features = sysfs_read_string(PATH_SYS_BUS_AP_FEATURES).map_err(|err| { ++ format!("Failure reading AP bus features from {PATH_SYS_BUS_AP_FEATURES} ({err:?}).") ++ })?; ++ match features.find("APSB") { ++ Some(_) => Ok(()), ++ None => Err("Missing AP bus feature APSB (SE AP pass-through not enabled ?).".to_string()), ++ } ++} ++ ++/// Wait for AP bus set up all it's devices. ++/// ++/// This function loops until the AP bus reports that ++/// - all AP queue devices have been constructed ++/// - and all AP device have been bound to a device driver. ++/// This may take some time and even loop forever if there ++/// is something wrong with the kernel modules setup. ++/// Returns true when AP bus bindings are complete, ++/// otherwise false and a message is printed. ++/// When AP bus binding complete is not immediately reached ++/// and this function needs to loop, about every 5 seconds ++/// a message is printed "Waiting for ...". ++pub fn wait_for_ap_bus_bindings_complete() -> bool { ++ let mut counter = 0; ++ loop { ++ match sysfs_read_string(PATH_SYS_BUS_AP_BINDINGS) { ++ Ok(s) => { ++ if s.contains("complete") { ++ return true; ++ } ++ } ++ Err(err) => { ++ eprintln!( ++ "Failure reading AP bus bindings from {} ({:?}).", ++ PATH_SYS_BUS_AP_BINDINGS, err ++ ); ++ return false; ++ } ++ } ++ thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BINDINGS_POLL_MS)); ++ counter += 1; ++ if counter % 10 == 0 { ++ println!("Waiting for AP bus bindings complete."); ++ } ++ } ++} ++ ++#[derive(Debug, Clone, PartialEq, Eq)] ++pub enum ApqnMode { ++ Accel, ++ Ep11, ++ Cca, ++} ++ ++#[derive(Debug, Clone)] ++pub struct ApqnInfoAccel { ++ // empty ++} ++ ++#[derive(Debug, Clone)] ++pub struct ApqnInfoEp11 { ++ pub serialnr: String, ++ pub mkvp: String, // may be an empty string if no WK set ++} ++ ++#[derive(Debug, Clone)] ++pub struct ApqnInfoCca { ++ pub serialnr: String, ++ pub mkvp_aes: String, // may be an empty string if no MK set ++ pub mkvp_apka: String, // may be an empty string if no MK set ++} ++ ++#[derive(Debug, Clone)] ++pub enum ApqnInfo { ++ Accel(ApqnInfoAccel), ++ Ep11(ApqnInfoEp11), ++ Cca(ApqnInfoCca), ++} ++ ++impl ApqnInfo { ++ fn accel_info(_carddir: &str, _queuedir: &str) -> Result { ++ Ok(ApqnInfo::Accel(ApqnInfoAccel {})) ++ } ++ ++ fn cca_info(carddir: &str, queuedir: &str) -> Result { ++ let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) { ++ Ok(r) => r, ++ Err(err) => { ++ return Err(format!( ++ "Failure reading serialnr from {carddir}/serialnr: {:?}.", ++ err ++ )) ++ } ++ }; ++ let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) { ++ Ok(r) => r, ++ Err(err) => { ++ return Err(format!( ++ "Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.", ++ err ++ )) ++ } ++ }; ++ let mut aes_mkvp = String::new(); ++ let re_cca_aes_mkvp = Regex::new(RE_CCA_AES_MKVP).unwrap(); ++ if !re_cca_aes_mkvp.is_match(&mkvps) { ++ return Err(format!( ++ "APQN {} failure parsing mkvps string '{}'.", ++ queuedir, mkvps ++ )); ++ } else { ++ let caps = re_cca_aes_mkvp.captures(&mkvps).unwrap(); ++ let valid = caps.get(1).unwrap().as_str().to_lowercase(); ++ if valid != "valid" { ++ eprintln!( ++ "Warning: APQN {} has no valid AES master key set.", ++ queuedir ++ ); ++ } else { ++ aes_mkvp = caps.get(2).unwrap().as_str().to_lowercase(); ++ if aes_mkvp.starts_with("0x") { ++ aes_mkvp = String::from(&aes_mkvp[2..]); ++ } ++ } ++ } ++ let mut apka_mkvp = String::new(); ++ let re_cca_apka_mkvp = Regex::new(RE_CCA_APKA_MKVP).unwrap(); ++ if !re_cca_apka_mkvp.is_match(&mkvps) { ++ return Err(format!( ++ "APQN {} failure parsing mkvps string '{}'.", ++ queuedir, mkvps ++ )); ++ } else { ++ let caps = re_cca_apka_mkvp.captures(&mkvps).unwrap(); ++ let valid = caps.get(1).unwrap().as_str().to_lowercase(); ++ if valid != "valid" { ++ eprintln!( ++ "Warning: APQN {} has no valid APKA master key set.", ++ queuedir ++ ); ++ } else { ++ apka_mkvp = caps.get(2).unwrap().as_str().to_lowercase(); ++ if apka_mkvp.starts_with("0x") { ++ apka_mkvp = String::from(&apka_mkvp[2..]); ++ } ++ } ++ } ++ Ok(ApqnInfo::Cca(ApqnInfoCca { ++ serialnr, ++ mkvp_aes: aes_mkvp, ++ mkvp_apka: apka_mkvp, ++ })) ++ } ++ ++ fn ep11_info(carddir: &str, queuedir: &str) -> Result { ++ let serialnr = match sysfs_read_string(&format!("{carddir}/serialnr")) { ++ Ok(r) => r, ++ Err(err) => { ++ return Err(format!( ++ "Failure reading serialnr from {carddir}/serialnr: {:?}.", ++ err ++ )) ++ } ++ }; ++ let mkvps = match sysfs_read_string(&format!("{carddir}/{queuedir}/mkvps")) { ++ Ok(r) => r, ++ Err(err) => { ++ return Err(format!( ++ "Failure reading mkvps from {carddir}/{queuedir}/mkvps: {:?}.", ++ err ++ )) ++ } ++ }; ++ let mut mkvp = String::new(); ++ let re_ep11_mkvp = Regex::new(RE_EP11_MKVP).unwrap(); ++ if !re_ep11_mkvp.is_match(&mkvps) { ++ return Err(format!( ++ "APQN {} failure parsing mkvps string '{}'.", ++ queuedir, mkvps ++ )); ++ } else { ++ let caps = re_ep11_mkvp.captures(&mkvps).unwrap(); ++ let valid = caps.get(1).unwrap().as_str().to_lowercase(); ++ if valid != "valid" { ++ eprintln!("Warning: APQN {} has no valid wrapping key set.", queuedir); ++ } else { ++ mkvp = caps.get(2).unwrap().as_str().to_lowercase(); ++ if mkvp.starts_with("0x") { ++ mkvp = String::from(&mkvp[2..]); ++ } ++ if mkvp.len() > 32 { ++ mkvp = String::from(&mkvp[..32]) ++ } ++ } ++ } ++ Ok(ApqnInfo::Ep11(ApqnInfoEp11 { serialnr, mkvp })) ++ } ++ ++ fn info(mode: &ApqnMode, carddir: &str, queuedir: &str) -> Result { ++ match mode { ++ ApqnMode::Accel => ApqnInfo::accel_info(carddir, queuedir), ++ ApqnMode::Cca => ApqnInfo::cca_info(carddir, queuedir), ++ ApqnMode::Ep11 => ApqnInfo::ep11_info(carddir, queuedir), ++ } ++ } ++} ++ ++#[derive(Debug, Clone)] ++pub struct Apqn { ++ pub name: String, ++ pub card: u32, ++ pub domain: u32, ++ pub gen: u32, ++ pub mode: ApqnMode, ++ pub info: Option, ++} ++ ++impl fmt::Display for Apqn { ++ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ++ write!(f, "({},{})", self.card, self.domain) ++ } ++} ++ ++impl Apqn { ++ pub fn bind_state(&self) -> Result { ++ get_apqn_bind_state(self.card, self.domain) ++ } ++ ++ pub fn set_bind_state(&self, state: BindState) -> Result<(), String> { ++ set_apqn_bind_state(self.card, self.domain, state) ++ } ++ ++ pub fn associate_state(&self) -> Result { ++ get_apqn_associate_state(self.card, self.domain) ++ } ++ ++ pub fn set_associate_state(&self, state: AssocState) -> Result<(), String> { ++ set_apqn_associate_state(self.card, self.domain, state) ++ } ++} ++ ++/// Wrapper object around Vector of Apqns ++pub struct ApqnList(Vec); ++ ++impl ApqnList { ++ #[cfg(test)] // only used in test code ++ pub fn from_apqn_vec(apqns: Vec) -> ApqnList { ++ ApqnList(apqns) ++ } ++ ++ #[cfg(test)] // only used in test code ++ pub fn to_apqn_vec(&self) -> Vec { ++ self.0.clone() ++ } ++ ++ pub fn iter(&self) -> Iter<'_, Apqn> { ++ self.0.iter() ++ } ++ ++ pub fn len(&self) -> usize { ++ self.0.len() ++ } ++ ++ pub fn is_empty(&self) -> bool { ++ self.0.is_empty() ++ } ++ ++ /// Scan AP bus devices in sysfs and construct the Apqnlist. ++ /// ++ /// The list is a vector of struct Apqn for each APQN found in sysfs ++ /// which is online and the card type matches to the regular expression ++ /// RE_CARD_TYPE. ++ /// On success a vector of struct Apqn is returned. This list may be ++ /// empty if there are no APQNs available or do not match to the conditions. ++ /// On failure None is returned. ++ /// Fatal errors which should never happened like unable to compile a ++ /// static regular expression will result in calling panic. ++ /// # Panics ++ /// Panics if the compilation of a static regular expression fails. ++ pub fn gather_apqns() -> Option { ++ let mut apqns: Vec = Vec::new(); ++ let re_card_type = Regex::new(RE_CARD_TYPE).unwrap(); ++ let re_queue_dir = Regex::new(RE_QUEUE_DIR).unwrap(); ++ let card_dirs = ++ match sysfs_get_list_of_subdirs_matching_regex(PATH_SYS_DEVICES_AP, RE_CARD_DIR) { ++ Ok(r) => r, ++ Err(err) => { ++ eprintln!( ++ "Failure reading AP devices {} ({:?}).", ++ PATH_SYS_DEVICES_AP, err ++ ); ++ return None; ++ } ++ }; ++ for dir in card_dirs { ++ let path = format!("{PATH_SYS_DEVICES_AP}/{dir}"); ++ let card_type = match sysfs_read_string(&format!("{path}/type")) { ++ Ok(r) => r, ++ Err(err) => { ++ eprintln!("Failure reading card type from {} ({:?}).", path, err); ++ return None; ++ } ++ }; ++ if !re_card_type.is_match(&card_type) { ++ eprintln!("Failure parsing card type string '{}'.", card_type); ++ return None; ++ } ++ let caps = re_card_type.captures(&card_type).unwrap(); ++ let gen = caps.get(1).unwrap().as_str().parse::().unwrap(); ++ let mode = match caps.get(2).unwrap().as_str().parse::().unwrap() { ++ 'A' => ApqnMode::Accel, ++ 'C' => ApqnMode::Cca, ++ 'P' => ApqnMode::Ep11, ++ _ => panic!("Code inconsistence between regex RE_CARD_TYPE and evaluation code."), ++ }; ++ if pv::misc::pv_guest_bit_set() { ++ // the UV blocks requests to CCA cards within SE guest with ++ // AP pass-through support. However, filter out CCA cards as these ++ // cards cause hangs during information gathering. ++ if mode == ApqnMode::Cca { ++ continue; ++ } ++ } ++ let queue_dirs = match sysfs_get_list_of_subdirs_matching_regex(&path, RE_QUEUE_DIR) { ++ Ok(r) => r, ++ Err(err) => { ++ eprintln!( ++ "Failure reading AP queue directories in {} ({:?}).", ++ path, err ++ ); ++ return None; ++ } ++ }; ++ for queue_dir in queue_dirs { ++ let _online = match sysfs_read_i32(&format!("{path}/{queue_dir}/online")) { ++ Ok(1) => true, ++ _ => continue, ++ }; ++ let caps = re_queue_dir.captures(&queue_dir).unwrap(); ++ let cardstr = caps.get(1).unwrap().as_str(); ++ let card = u32::from_str_radix(cardstr, 16).unwrap(); ++ let domstr = caps.get(2).unwrap().as_str(); ++ let dom = u32::from_str_radix(domstr, 16).unwrap(); ++ // For the mpvk and serialnr to fetch from the APQN within a SE ++ // guest the APQN needs to be bound to the guest. So if the APQN ++ // is not bound, temporarily bind it here until the info has ++ // been retrieved. ++ let mut tempbound = false; ++ if pv::misc::pv_guest_bit_set() { ++ let cbs = match get_apqn_bind_state(card, dom) { ++ Ok(bs) => bs, ++ Err(err) => { ++ eprintln!( ++ "Error: Failure reading APQN ({},{}) bind state: {}", ++ card, dom, err ++ ); ++ BindState::NotSupported ++ } ++ }; ++ if cbs == BindState::Unbound { ++ let r = set_apqn_bind_state(card, dom, BindState::Bound); ++ if r.is_err() { ++ eprintln!( ++ "Warning: Failure to temp. bind APQN ({},{}): {}", ++ card, ++ dom, ++ r.unwrap_err() ++ ); ++ continue; ++ } else { ++ tempbound = true; ++ } ++ }; ++ }; ++ let info = match ApqnInfo::info(&mode, &path, &queue_dir) { ++ Err(err) => { ++ // print the error but continue with info set to None ++ eprintln!( ++ "Warning: Failure to gather info for APQN ({},{}): {}", ++ card, dom, err ++ ); ++ None ++ } ++ Ok(i) => Some(i), ++ }; ++ if tempbound { ++ let r = set_apqn_bind_state(card, dom, BindState::Unbound); ++ if r.is_err() { ++ eprintln!( ++ "Warning: Failure to unbind temp. bound APQN ({},{}): {}", ++ card, ++ dom, ++ r.unwrap_err() ++ ); ++ } ++ }; ++ apqns.push(Apqn { ++ name: queue_dir.clone(), ++ card, ++ domain: dom, ++ gen, ++ mode: mode.clone(), ++ info, ++ }); ++ } ++ } ++ Some(ApqnList(apqns)) ++ } ++ ++ /// Sort this Apqnlist by card generation: ++ /// newest generation first, older generations last. ++ pub fn sort_by_gen(&mut self) { ++ self.0.sort_unstable_by(|a, b| b.gen.cmp(&a.gen)); ++ } ++ ++ /// Check MK restriction ++ /// ++ /// Within one card there must not exist 2 APQNs with same ++ /// MK setup. This rule only applies to EP11 cards. ++ /// Returns true if this check passed, ++ /// otherwise false and a message is printed. ++ pub fn check_mk_restriction(&self) -> bool { ++ for a1 in self.0.iter() { ++ for a2 in self.0.iter() { ++ if a1.card == a2.card ++ && a1.domain < a2.domain ++ && a1.mode == ApqnMode::Ep11 ++ && a1.info.is_some() ++ && a2.info.is_some() ++ { ++ let i1 = match a1.info.as_ref().unwrap() { ++ ApqnInfo::Ep11(i) => i, ++ _ => continue, ++ }; ++ let i2 = match a2.info.as_ref().unwrap() { ++ ApqnInfo::Ep11(i) => i, ++ _ => continue, ++ }; ++ if i1.mkvp.is_empty() || i2.mkvp.is_empty() { ++ continue; ++ } ++ if i1.mkvp == i2.mkvp { ++ eprintln!("APQN {} and APQN {} have same MPVK", a1, a2); ++ return false; ++ } ++ } ++ } ++ } ++ true ++ } ++} ++ ++#[derive(PartialEq, Eq)] ++pub enum BindState { ++ Bound, ++ Unbound, ++ NotSupported, ++} ++ ++/// Query bind state for this APQN. ++/// ++/// Returns a BindState enum as defined above or on failure ++/// an error string. Does NOT print any error messages. ++pub fn get_apqn_bind_state(card: u32, dom: u32) -> Result { ++ let path = format!( ++ "{}/card{:02x}/{:02x}.{:04x}/se_bind", ++ PATH_SYS_DEVICES_AP, card, card, dom ++ ); ++ match sysfs_read_string(&path) { ++ Err(err) => Err(format!( ++ "Failure reading se_bind attribute for APQN({},{}): {:?}.", ++ card, dom, err ++ )), ++ Ok(str) => match str.as_str() { ++ "bound" => Ok(BindState::Bound), ++ "unbound" => Ok(BindState::Unbound), ++ "-" => Ok(BindState::NotSupported), ++ _ => Err(format!("Unknown bind state '{str}'.")), ++ }, ++ } ++} ++ ++/// Bind or unbind an APQN. ++/// ++/// The action is determined by the BindState given in. ++/// But of course only Bound and Unbound is supported - otherwise ++/// this function panics! ++/// The function actively loops over the bind state until ++/// the requested bind state is reached or a timeout has ++/// occurred (SYS_BUS_AP_BIND_TIMEOUT_MS). ++/// On success () is returned, on failure an error string ++/// is returned. Does NOT print any error messages. ++/// # Panics ++/// Panics if a desired bind state other than Bund or Unbound is given. ++pub fn set_apqn_bind_state(card: u32, dom: u32, state: BindState) -> Result<(), String> { ++ let path = format!( ++ "{}/card{:02x}/{:02x}.{:04x}/se_bind", ++ PATH_SYS_DEVICES_AP, card, card, dom ++ ); ++ let r = match state { ++ BindState::Bound => sysfs_write_i32(&path, 1), ++ BindState::Unbound => sysfs_write_i32(&path, 0), ++ _ => panic!("set_apqn_bind_state called with invalid BindState."), ++ }; ++ if r.is_err() { ++ return Err(format!( ++ "Failure writing se_bind attribute for APQN({},{}): {:?}.", ++ card, ++ dom, ++ r.unwrap_err() ++ )); ++ } ++ let mut ms: u64 = 0; ++ loop { ++ thread::sleep(time::Duration::from_millis(SYS_BUS_AP_BIND_POLL_MS)); ++ ms += SYS_BUS_AP_BIND_POLL_MS; ++ if ms >= SYS_BUS_AP_BIND_TIMEOUT_MS { ++ break Err(format!( ++ "Timeout setting APQN({},{}) bind state.", ++ card, dom ++ )); ++ } ++ let newstate = match get_apqn_bind_state(card, dom) { ++ Err(err) => return Err(err), ++ Ok(s) => s, ++ }; ++ if newstate == state { ++ return Ok(()); ++ } ++ } ++} ++ ++#[derive(PartialEq, Eq)] ++pub enum AssocState { ++ Associated(u16), ++ AssociationPending, ++ Unassociated, ++ NotSupported, ++} ++ ++/// Query association state for this APQN. ++/// ++/// Returns an AssocState enum as defined above or on failure ++/// an error string. Does NOT print any error messages. ++pub fn get_apqn_associate_state(card: u32, dom: u32) -> Result { ++ let path = format!( ++ "{}/card{:02x}/{:02x}.{:04x}/se_associate", ++ PATH_SYS_DEVICES_AP, card, card, dom ++ ); ++ match sysfs_read_string(&path) { ++ Err(err) => Err(format!( ++ "Failure reading se_associate attribute for APQN({},{}: {:?}", ++ card, dom, err ++ )), ++ Ok(str) => { ++ if let Some(prefix) = str.strip_prefix("associated ") { ++ let value = &prefix.parse::(); ++ match value { ++ Ok(v) => Ok(AssocState::Associated(*v)), ++ Err(_) => Err(format!("Invalid association index in '{str}'.")), ++ } ++ } else { ++ match str.as_str() { ++ "association pending" => Ok(AssocState::AssociationPending), ++ "unassociated" => Ok(AssocState::Unassociated), ++ "-" => Ok(AssocState::NotSupported), ++ _ => Err(format!("Unknown association state '{str}'.")), ++ } ++ } ++ } ++ } ++} ++ ++fn set_apqn_associate_state_associate(card: u32, dom: u32, idx: u16) -> Result<(), String> { ++ let path = format!( ++ "{}/card{:02x}/{:02x}.{:04x}/se_associate", ++ PATH_SYS_DEVICES_AP, card, card, dom ++ ); ++ let r = sysfs_write_i32(&path, idx as i32); ++ if r.is_err() { ++ return Err(format!( ++ "Failure writing se_associate attribute for APQN({},{}): {:?}.", ++ card, ++ dom, ++ r.unwrap_err() ++ )); ++ } ++ let mut ms: u64 = 0; ++ loop { ++ thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS)); ++ ms += SYS_BUS_AP_ASSOC_POLL_MS; ++ if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS { ++ break Err(format!( ++ "Timeout setting APQN({},{}) association idx {} state.", ++ card, dom, idx ++ )); ++ } ++ let newstate = match get_apqn_associate_state(card, dom) { ++ Err(err) => return Err(err), ++ Ok(s) => s, ++ }; ++ if let AssocState::Associated(i) = newstate { ++ if idx == i { ++ return Ok(()); ++ } else { ++ return Err(format!( ++ "Failure: APQN({},{}) is associated with {} but it should be {}.", ++ card, dom, i, idx ++ )); ++ } ++ } ++ } ++} ++ ++fn set_apqn_associate_state_unbind(card: u32, dom: u32) -> Result<(), String> { ++ let bindpath = format!( ++ "{}/card{:02x}/{:02x}.{:04x}/se_bind", ++ PATH_SYS_DEVICES_AP, card, card, dom ++ ); ++ let r = sysfs_write_i32(&bindpath, 0); ++ if r.is_err() { ++ return Err(format!( ++ "Failure writing se_bind attribute for APQN({},{}): {:?}.", ++ card, ++ dom, ++ r.unwrap_err() ++ )); ++ } ++ let mut ms: u64 = 0; ++ loop { ++ thread::sleep(time::Duration::from_millis(SYS_BUS_AP_ASSOC_POLL_MS)); ++ ms += SYS_BUS_AP_ASSOC_POLL_MS; ++ if ms >= SYS_BUS_AP_ASSOC_TIMEOUT_MS { ++ break Err(format!( ++ "Timeout setting APQN({},{}) association unbind state.", ++ card, dom ++ )); ++ } ++ let newstate = match get_apqn_associate_state(card, dom) { ++ Err(err) => return Err(err), ++ Ok(s) => s, ++ }; ++ if newstate == AssocState::Unassociated { ++ return Ok(()); ++ } ++ } ++} ++ ++/// Associate or Unassociate an APQN. ++/// ++/// The action is determined by the AssocState given in. ++/// But of course only Associated and Unassociated is supported ++/// otherwise this function panics! ++/// The function actively loops over the association state until ++/// the requested state is reached or a timeout has ++/// occurred (SYS_BUS_AP_ASSOC_TIMEOUT_MS). ++/// The unassociate is in fact a unbind. So the code triggers ++/// an unbind and then loops over the sysfs se_associate until ++/// "unassociated" is reached. ++/// On success () is returned, on failure an error string ++/// is returned. Does NOT print any error messages. ++/// # Panics ++/// Panics if a desired bind state other than Associated or ++/// Unassociated is given. ++pub fn set_apqn_associate_state(card: u32, dom: u32, state: AssocState) -> Result<(), String> { ++ match state { ++ AssocState::Associated(idx) => set_apqn_associate_state_associate(card, dom, idx), ++ AssocState::Unassociated => set_apqn_associate_state_unbind(card, dom), ++ _ => panic!("set_apqn_associate_state called with invalid AssocState."), ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ ++ use super::*; ++ ++ // These tests assume, there is an AP bus available ++ // Also for each APQN which is online, it is assumed ++ // to have a valid master key set up (for Ep11 and CCA). ++ ++ #[test] ++ fn test_check_ap_bus_support() { ++ if Path::new(PATH_SYS_BUS_AP).is_dir() { ++ assert!(check_ap_bus_support().is_ok()); ++ } else { ++ assert!(check_ap_bus_support().is_err()); ++ } ++ } ++ #[test] ++ fn test_check_ap_bus_apsb_support() { ++ if Path::new(PATH_SYS_BUS_AP).is_dir() { ++ // if we are inside a secure execution guest the ++ // apsb check should succeed. Outside an SE guest ++ // the check should fail. ++ if pv::misc::pv_guest_bit_set() { ++ assert!(ap_bus_has_apsb_support().is_ok()); ++ } else { ++ assert!(ap_bus_has_apsb_support().is_err()); ++ } ++ } else { ++ assert!(ap_bus_has_apsb_support().is_err()); ++ } ++ } ++ #[test] ++ fn test_wait_for_ap_bus_bindings_complete() { ++ let r = wait_for_ap_bus_bindings_complete(); ++ if Path::new(PATH_SYS_BUS_AP).is_dir() { ++ assert!(r); ++ } else { ++ assert!(!r); ++ } ++ } ++ #[test] ++ fn test_gather_apqns() { ++ let r = ApqnList::gather_apqns(); ++ if Path::new(PATH_SYS_BUS_AP).is_dir() { ++ assert!(r.is_some()); ++ // fail if no entries found ++ let l = r.unwrap(); ++ let v = l.to_apqn_vec(); ++ for a in v { ++ match a.mode { ++ ApqnMode::Accel => { ++ // fail if no ApqnInfo is attached ++ assert!(a.info.is_some()); ++ } ++ ApqnMode::Ep11 => { ++ // fail if no ApqnInfo is attached ++ assert!(a.info.is_some()); ++ let info = a.info.unwrap(); ++ let i = match &info { ++ ApqnInfo::Ep11(i) => i, ++ _ => panic!("ApqnInfo attached onto Ep11 APQN is NOT ApqnInfoEp11 ?!?"), ++ }; ++ // fail if no serialnr ++ assert!(i.serialnr.len() > 0); ++ // mkvp is either empty (no WK set) or has exact 32 characters ++ assert!(i.mkvp.is_empty() || i.mkvp.len() == 32); ++ } ++ ApqnMode::Cca => { ++ // fail if no ApqnInfo is attached ++ assert!(a.info.is_some()); ++ let info = a.info.unwrap(); ++ let i = match &info { ++ ApqnInfo::Cca(i) => i, ++ _ => panic!("ApqnInfo attached onto Cca APQN is NOT ApqnInfoCca ?!?"), ++ }; ++ // fail if no serialnr ++ assert!(i.serialnr.len() > 0); ++ // aes mkvp is either empty (no MK set) or exact 16 characters ++ assert!(i.mkvp_aes.is_empty() || i.mkvp_aes.len() == 16); ++ // apka mkvp is either empty (no MK set) or exact 16 characters ++ assert!(i.mkvp_apka.is_empty() || i.mkvp_apka.len() == 16); ++ } ++ } ++ } ++ } else { ++ assert!(r.is_none()); ++ } ++ } ++} +diff --git a/rust/pvapconfig/src/cli.rs b/rust/pvapconfig/src/cli.rs +new file mode 100644 +index 00000000..e7fd2833 +--- /dev/null ++++ b/rust/pvapconfig/src/cli.rs +@@ -0,0 +1,63 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2023 ++// ++//! Command line interface for pvapconfig ++// ++ ++use clap::Parser; ++use lazy_static::lazy_static; ++ ++/// The default pvapconfig config file ++pub const PATH_DEFAULT_CONFIG_FILE: &str = "/etc/pvapconfig.yaml"; ++ ++#[derive(Parser, Clone)] ++pub struct Cli { ++ /// Provide a custom config file (overwrites default /etc/pvapconfig.yaml). ++ #[arg(short, long, value_name = "FILE")] ++ pub config: Option, ++ ++ /// Dry run: display the actions but don't actually perform them on the APQNs. ++ #[arg(short = 'n', long = "dry-run")] ++ pub dryrun: bool, ++ ++ /// Enforce strict match: All config entries need to be fullfilled. ++ /// ++ /// By default it is enough to successfully apply at least one config entry. ++ /// With the strict flag enabled, all config entries within a config file ++ /// need to be applied successful. ++ #[arg(long = "strict")] ++ pub strict: bool, ++ ++ /// Provide more detailed output. ++ #[arg(short, long)] ++ pub verbose: bool, ++ ++ /// Print version information and exit. ++ #[arg(short = 'V', long)] ++ pub version: bool, ++} ++ ++lazy_static! { ++ pub static ref ARGS: Cli = Cli::parse(); ++} ++ ++impl Cli { ++ /// verbose returns true if the verbose command line option ++ /// was given, otherwise false is returned. ++ pub fn verbose(&self) -> bool { ++ self.verbose ++ } ++ ++ /// dryrun returns true if the dry-run command line option ++ /// was given, otherwise false is returned. ++ pub fn dryrun(&self) -> bool { ++ self.dryrun ++ } ++ ++ /// strict returns true if the strict flag was given, otherwise ++ /// false is returned. ++ pub fn strict(&self) -> bool { ++ self.strict ++ } ++} +diff --git a/rust/pvapconfig/src/config.rs b/rust/pvapconfig/src/config.rs +new file mode 100644 +index 00000000..28d2ac75 +--- /dev/null ++++ b/rust/pvapconfig/src/config.rs +@@ -0,0 +1,391 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2023 ++// ++//! Functions around handling the pvapconfig configuration file ++// ++ ++use openssl::sha::sha256; ++use regex::Regex; ++use serde::{Deserialize, Serialize}; ++use serde_yaml::{self}; ++use std::fs::File; ++use std::slice::Iter; ++ ++pub const STR_MODE_EP11: &str = "ep11"; ++pub const STR_MODE_ACCEL: &str = "accel"; ++ ++const RE_EP11_MKVP_32: &str = r"^(0x)?([[:xdigit:]]{32})$"; ++const RE_EP11_MKVP_64: &str = r"^(0x)?([[:xdigit:]]{64})$"; ++const RE_SERIALNR: &str = r"^(\S{16})$"; ++const RE_EP11_GEN: &str = r"^cex(8)$"; ++const RE_ACCEL_GEN: &str = r"^cex([4-8])$"; ++const RE_SECRETID: &str = r"^(0x)?([[:xdigit:]]{64})$"; ++ ++#[derive(Debug, Serialize, Deserialize, Default, Clone)] ++#[serde(default, deny_unknown_fields)] ++pub struct ApConfigEntry { ++ pub name: String, // name and description are unmodified from the config file ++ pub description: String, // accel after validation ep11 after validation ++ pub mode: String, // "accel" "ep11" ++ pub mkvp: String, // empty 32 hex lowercase characters ++ pub serialnr: String, // empty empty or 16 non-whitespace characters ++ pub mingen: String, // empty or "cex4"..."cex8" empty or "cex8" ++ pub secretid: String, // empty 64 hex lowercase characters ++} ++ ++impl ApConfigEntry { ++ fn validate_secretid(&mut self) -> Result<(), String> { ++ // either secret id or name may be given ++ if self.secretid.is_empty() && self.name.is_empty() { ++ return Err("Neither secretid nor name given.".to_string()); ++ } ++ // if name is given, calculate sha256 digest for this name ++ // test for the hash calculated here can be done with openssl: ++ // echo -n "Hello" >in.bin; openssl dgst -sha256 -binary -out out.bin in.bin; hexdump -C out.bin ++ if self.name.is_empty() { ++ return Ok(()); ++ } ++ let hash = sha256(self.name.as_bytes()); ++ let hashstr = crate::helper::u8_to_hexstring(&hash); ++ // if there is a secretid given, this must match to the hash ++ if !self.secretid.is_empty() { ++ if self.secretid != hashstr { ++ return Err("Mismatch between sha256(name) and secretid.".to_string()); ++ } ++ } else { ++ self.secretid = hashstr; ++ } ++ Ok(()) ++ } ++ ++ /// # Panics ++ /// Panics if the compilation of a static regular expression fails. ++ fn validate_ep11_entry(&mut self) -> Result<(), String> { ++ // mkvp is required ++ let mut mkvp = self.mkvp.trim().to_lowercase(); ++ if mkvp.is_empty() { ++ return Err("Mkvp value missing.".to_string()); ++ } ++ // either 64 hex or 32 hex ++ if Regex::new(RE_EP11_MKVP_64).unwrap().is_match(&mkvp) { ++ // need to cut away the last 32 hex characters ++ mkvp = String::from(&mkvp[..mkvp.len() - 32]) ++ } else if Regex::new(RE_EP11_MKVP_32).unwrap().is_match(&mkvp) { ++ // nothing to do here ++ } else { ++ return Err(format!("Mkvp value '{}' is not valid.", &self.mkvp)); ++ } ++ self.mkvp = match mkvp.strip_prefix("0x") { ++ Some(rest) => String::from(rest), ++ None => mkvp, ++ }; ++ // serialnr is optional ++ let serialnr = self.serialnr.trim().to_string(); ++ if !serialnr.is_empty() && !Regex::new(RE_SERIALNR).unwrap().is_match(&serialnr) { ++ return Err(format!("Serialnr value '{}' is not valid.", &self.serialnr)); ++ } ++ self.serialnr = serialnr; ++ // mingen is optional, but if given only CEX8 is valid ++ let mingen = self.mingen.trim().to_lowercase(); ++ if !mingen.is_empty() && !Regex::new(RE_EP11_GEN).unwrap().is_match(&mingen) { ++ return Err(format!("Mingen value '{}' is not valid.", &self.mingen)); ++ } ++ self.mingen = mingen; ++ // secretid or name is required ++ let secretid = self.secretid.trim().to_lowercase(); ++ if !secretid.is_empty() && !Regex::new(RE_SECRETID).unwrap().is_match(&secretid) { ++ return Err(format!("Secretid value '{}' is not valid.", &self.secretid)); ++ } ++ self.secretid = match secretid.strip_prefix("0x") { ++ Some(rest) => String::from(rest), ++ None => secretid, ++ }; ++ // name is optional, ignored here ++ // description is optional, ignored here ++ // but the secretid needs some more validation ++ self.validate_secretid() ++ } ++ ++ /// # Panics ++ /// Panics if the compilation of a static regular expression fails. ++ fn validate_accel_entry(&mut self) -> Result<(), String> { ++ // mkvp is ignored ++ self.mkvp.clear(); ++ // serialnr is ignored ++ self.serialnr.clear(); ++ // mingen is optional, but if given must match to CEX4..CEX8 ++ let mingen = self.mingen.trim().to_lowercase(); ++ if !mingen.is_empty() && !Regex::new(RE_ACCEL_GEN).unwrap().is_match(&mingen) { ++ return Err(format!("Mingen value '{}' is not valid.", &self.mingen)); ++ } ++ self.mingen = mingen; ++ // secretid is ignored ++ self.secretid.clear(); ++ // name is optional, ignored here ++ // description is optional, ignored here ++ Ok(()) ++ } ++ ++ fn validate(&mut self) -> Result<(), String> { ++ // trim name ++ self.name = self.name.trim().to_string(); ++ // mode is always required ++ let mode = self.mode.trim().to_lowercase(); ++ match mode.as_str() { ++ STR_MODE_EP11 => { ++ self.mode = mode; ++ self.validate_ep11_entry()?; ++ } ++ STR_MODE_ACCEL => { ++ self.mode = mode; ++ self.validate_accel_entry()?; ++ } ++ _ => return Err(format!("Unknown or invalid mode '{}'.", mode)), ++ } ++ Ok(()) ++ } ++} ++ ++/// Wrapper object around Vector of ApConfigEntry ++pub struct ApConfigList(Vec); ++ ++impl ApConfigList { ++ #[cfg(test)] // only used in test code ++ pub fn from_apconfigentry_vec(apconfigs: Vec) -> ApConfigList { ++ ApConfigList(apconfigs) ++ } ++ ++ pub fn iter(&self) -> Iter<'_, ApConfigEntry> { ++ self.0.iter() ++ } ++ ++ pub fn len(&self) -> usize { ++ self.0.len() ++ } ++ ++ pub fn is_empty(&self) -> bool { ++ self.0.is_empty() ++ } ++ ++ fn read_yaml_file(fname: &str) -> Result, String> { ++ let file = match File::open(fname) { ++ Ok(f) => f, ++ Err(err) => { ++ return Err(format!( ++ "Failure to open AP config file {}: {:?}", ++ fname, err ++ )) ++ } ++ }; ++ match serde_yaml::from_reader(file) { ++ Ok(cfg) => Ok(cfg), ++ Err(err) => Err(format!( ++ "Failure parsing AP config file {}: {:?}", ++ fname, err ++ )), ++ } ++ } ++ ++ fn validate(config: &mut [ApConfigEntry]) -> Result<(), String> { ++ for (i, entry) in config.iter_mut().enumerate() { ++ let ename = if !entry.name.trim().is_empty() { ++ format!("AP config entry {} '{}'", i, entry.name.trim()) ++ } else { ++ format!("AP config entry {}", i) ++ }; ++ if let Err(err) = &entry.validate() { ++ return Err(format!("{}: {}", ename, err)); ++ } ++ } ++ Ok(()) ++ } ++ ++ /// Read in and validate the yaml configuration from a file. ++ /// Returns a Result with Ok(ApConfigList) on success ++ /// or an Err(errorstring) on failure. ++ pub fn read_and_validate_yaml_file(fname: &str) -> Result { ++ let mut apconfig = ApConfigList::read_yaml_file(fname)?; ++ ApConfigList::validate(&mut apconfig)?; ++ Ok(ApConfigList(apconfig)) ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ ++ use super::*; ++ use std::env; ++ use std::fs; ++ use std::io::Write; ++ ++ const GOOD_CONFIGS: [&str; 8] = [ ++ "# good test 1 ++- name: my Accelerator ++ mode: AcCel ++ mingen: Cex7\n", ++ "# good test 2 ++- name: my Accelerator 2 ++ description: Accelerator entry with description ++ mode: Accel\n", ++ "# good test 3 ++- name: my EP11 APQN 1 ++ mode: Ep11 ++ mkvp: 0xDB3C3B3C3F097DD55EC7EB0E7FDBCB93 ++ serialnr: 93AADFK719460083 ++ secretid: 0xBC9d46c052BC3574454C5715757274629a283767ed237922cfb8651c0e77320A\n", ++ "# good test 4 ++- name: my EP11 APQN 2 ++ mode: EP11 ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93 ++ serialnr: 93aaDHzu42082261 ++ secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n", ++ "# good test 5 ++- name: my EP11 APQN 3 ++ mode: EP11 ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93db3c3b3c3f097dd55ec7eb0e7fdbcb93 ++ serialnr: 93aaDHzu42082261 ++ secretid: 0xd146c9ae77cdff25fa87a5b3487587dc29a4e391b315c98570e8fa2e2ec91454\n", ++ "# no name but secretid given ++- mode: EP11 ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93 ++ secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n", ++ "# no secretid but name given ++- mode: EP11 ++ name: My-EP11-AP-config ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93\n", ++ "# secretid and name given ++- mode: EP11 ++ name: My-EP11-AP-config ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93 ++ secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n", ++ ]; ++ ++ const BAD_CONFIGS: [&str; 12] = [ ++ "# mode missing ++- name: bad test 1 ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93 ++ secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n", ++ "# invalid mode ++- name: bad test 2 ++ mode: CCA ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93 ++ secretid: 0x0767668dd22f23fa675c4641e04bb4e991f443be4df13ce3896b8eeca59fcc10\n", ++ "# Accelerator with wrong CEX3 ++- name: bad test 3 ++ mode: Accel ++ mingen: Cex3\n", ++ "# Accelerator with wrong CEX9 ++- name: bad test 4 ++ mode: Accel ++ mingen: CEX9\n", ++ "# EP11 with mkvp missing ++- name: bad test 5 ++ mode: EP11 ++ serialnr: 93AADHZU42082261\n", ++ "# EP11 with non hex mkvp ++- name: bad test 6 ++ mode: EP11 ++ mkvp: 0xabcdefghijklmnopqqponmlkjihgfedcba ++ serialnr: 93AADHZU42082261\n", ++ "# EP11 with mkvp too big ++- name: bad test 7 ++ mode: EP11 ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb93aa ++ serialnr: 93AADHZU42082261\n", ++ "# EP11 with mkvp too small ++- name: bad test 8 ++ mode: EP11 ++ mkvp: 0xdb3c3b3c3f097dd55ec7eb0e7fdbcb ++ serialnr: 93AADHZU42082261\n", ++ "# EP11 with invalid CEXx ++- name: bad test 9 ++ mode: EP11 ++ mingen: CEX7 ++ mkvp: 0x00112233445566778899aabbccddeeff ++ serialnr: 93AADHZU42082261\n", ++ "# EP11 with invalid Serialnr ++- name: bad test 10 ++ mode: EP11 ++ mkvp: 0x00112233445566778899aabbccddeeff ++ serialnr: 93AADHZU4208226\n", ++ "# EP11 with invalid Serialnr ++- name: bad test 11 ++ mode: EP11 ++ mkvp: 0x00112233445566778899aabbccddeeff ++ serialnr: 93AAD ZU42082261\n", ++ "# EP11 with sha256(name) != secretid ++- name: bad test 12 ++ mode: EP11 ++ mkvp: 0x00112233445566778899aabbccddeeff ++ serialnr: AABBCCDDEEFFGGHH ++ secretid: 0x2ca853f959fc5ce5f1888cb48dae39514a27bb66520ac85f6073a7f678d262c0\n", ++ ]; ++ ++ const BAD_DESERIALIZE: [&str; 2] = [ ++ "/*\ntotal nonsense\n */\n", ++ "# wrong/unknown field ++- name: de-serialize failure 1 ++ type: EP11\n", ++ ]; ++ ++ fn write_yaml_config_to_temp_file(content: &str) -> Result { ++ let dir = env::temp_dir(); ++ let rnd = rand::random::(); ++ let fname = format!("{}/config-test-{}.yaml", dir.to_str().unwrap(), rnd); ++ let mut f = match File::create(&fname) { ++ Ok(f) => f, ++ Err(_) => return Err(format!("Failure creating temp file '{fname}'.")), ++ }; ++ match f.write_all(content.as_bytes()) { ++ Ok(_) => Ok(fname), ++ Err(_) => { ++ fs::remove_file(&fname).ok(); ++ Err(format!("Failure writing to temp file '{fname}'.")) ++ } ++ } ++ } ++ ++ #[test] ++ fn test_good_yaml() { ++ for yaml in GOOD_CONFIGS { ++ let f = write_yaml_config_to_temp_file(yaml).unwrap(); ++ let config = ApConfigList::read_and_validate_yaml_file(&f).unwrap(); ++ assert!(config.len() > 0); ++ fs::remove_file(&f).ok(); ++ } ++ } ++ #[test] ++ fn test_bad_yaml() { ++ for yaml in BAD_CONFIGS { ++ let f = write_yaml_config_to_temp_file(yaml).unwrap(); ++ let r = ApConfigList::read_and_validate_yaml_file(&f); ++ assert!(r.is_err()); ++ fs::remove_file(&f).ok(); ++ } ++ } ++ #[test] ++ fn test_invalid_deserizalize() { ++ for yaml in BAD_DESERIALIZE { ++ let f = write_yaml_config_to_temp_file(yaml).unwrap(); ++ let r = ApConfigList::read_and_validate_yaml_file(&f); ++ assert!(r.is_err()); ++ fs::remove_file(&f).ok(); ++ } ++ } ++ #[test] ++ fn test_sha256() { ++ assert!( ++ crate::helper::u8_to_hexstring(&sha256("Hello".as_bytes())) ++ == "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969" ++ ); ++ assert!( ++ crate::helper::u8_to_hexstring(&sha256("SECRET1".as_bytes())) ++ == "03153249db7ce46b0330ffb1a760b59710531af08ec4d7f8424a6870fae49360" ++ ); ++ assert!( ++ crate::helper::u8_to_hexstring(&sha256("SECRET2".as_bytes())) ++ == "258499e710e0bd3bb878d6bac7e478b30f3f3e72566989f638c4143d14f6c0b6" ++ ); ++ } ++} +diff --git a/rust/pvapconfig/src/helper.rs b/rust/pvapconfig/src/helper.rs +new file mode 100644 +index 00000000..c4670cef +--- /dev/null ++++ b/rust/pvapconfig/src/helper.rs +@@ -0,0 +1,272 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2023 ++// ++//! Collection of helper functions for pvapconfig ++// ++ ++use regex::Regex; ++use std::error::Error; ++use std::fs; ++use std::fs::{File, OpenOptions}; ++use std::io::{Read, Write}; ++use std::path::PathBuf; ++ ++pub const PATH_PVAPCONFIG_LOCK: &str = "/run/lock/pvapconfig.lock"; ++ ++/// Convert u8 slice to (lowercase) hex string ++pub fn u8_to_hexstring(slice: &[u8]) -> String { ++ let s = String::with_capacity(2 * slice.len()); ++ slice.iter().fold(s, |acc, e| acc + &format!("{e:02x}")) ++} ++ ++/// Convert hexstring to u8 vector ++/// The hexstring may contain whitespaces which are ignored. ++/// If there are other characters in there or if the number ++/// of hex characters is uneven panic() is called. ++/// # Panics ++/// Panics if the given string contains characters other than ++/// hex digits and whitespace. Panics if the number of hex digits ++/// is not even. ++#[cfg(test)] // currently only used in test code ++pub fn hexstring_to_u8(hex: &str) -> Vec { ++ let mut s = String::new(); ++ for c in hex.chars() { ++ if c.is_ascii_hexdigit() { ++ s.push(c); ++ } else if c.is_whitespace() { ++ // ignore ++ } else { ++ panic!("Invalid character '{c}'"); ++ } ++ } ++ if s.len() % 2 == 1 { ++ panic!("Uneven # of hex characters in '{s}'"); ++ } ++ let mut hex_bytes = s.as_bytes().iter().map_while(|b| match b { ++ b'0'..=b'9' => Some(b - b'0'), ++ b'a'..=b'f' => Some(b - b'a' + 10), ++ b'A'..=b'F' => Some(b - b'A' + 10), ++ _ => None, ++ }); ++ let mut bytes = Vec::with_capacity(s.len()); ++ while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) { ++ bytes.push(h << 4 | l) ++ } ++ bytes ++} ++ ++/// Read sysfs file into string ++pub fn sysfs_read_string(fname: &str) -> Result> { ++ let mut file = File::open(fname)?; ++ let mut content = String::new(); ++ file.read_to_string(&mut content)?; ++ let trimmed_content = String::from(content.trim()); ++ Ok(trimmed_content) ++} ++ ++/// Write string into sysfs file ++pub fn sysfs_write_string(fname: &str, value: &str) -> Result<(), Box> { ++ let mut file = OpenOptions::new().write(true).open(fname)?; ++ file.write_all(value.as_bytes())?; ++ Ok(()) ++} ++ ++/// Read sysfs file content and parse as i32 value ++pub fn sysfs_read_i32(fname: &str) -> Result> { ++ let content = sysfs_read_string(fname)?; ++ Ok(content.parse::()?) ++} ++ ++/// Write an i32 value into a sysfs file ++pub fn sysfs_write_i32(fname: &str, value: i32) -> Result<(), Box> { ++ return sysfs_write_string(fname, &value.to_string()); ++} ++ ++/// For a given (sysfs) directory construct a list of all subdirs ++/// and give it back as a vector of strings. If there is no subdir, ++/// the vector is empty. ++pub fn sysfs_get_list_of_subdirs(dname: &str) -> Result, Box> { ++ let mut v: Vec = Vec::new(); ++ let entries = fs::read_dir(dname)?; ++ for entry in entries.flatten() { ++ let file_type = match entry.file_type() { ++ Ok(ft) => ft, ++ _ => continue, ++ }; ++ if !file_type.is_dir() { ++ continue; ++ } ++ let fname = match entry.file_name().into_string() { ++ Ok(s) => s, ++ _ => continue, ++ }; ++ v.push(fname); ++ } ++ Ok(v) ++} ++ ++/// For a given (sysfs) directory construct a list of all subdirs which ++/// match to the given regular expression and give the list back as a ++/// vector of strings. If there is no subdir, the vector is empty. ++pub fn sysfs_get_list_of_subdirs_matching_regex( ++ dname: &str, ++ regex: &str, ++) -> Result, Box> { ++ let mut v: Vec = Vec::new(); ++ let re = Regex::new(regex)?; ++ let entries = sysfs_get_list_of_subdirs(dname)?; ++ for entry in entries { ++ if re.is_match(&entry) { ++ v.push(entry); ++ } ++ } ++ Ok(v) ++} ++ ++/// LockFile for inter-process locking ++/// ++/// Simple class for process locking for pvapconfig. ++/// The lock concept is simple: The existence of a file is used as the ++/// locking indicator. If the file exists something is locked, if it does ++/// not exist something is not locked. In the lock file the PID of the ++/// process created the file ("owning this file") is written in. ++/// With the ProcessLock object leaving scope the associated lock file ++/// is automatically deleted. It is assumed that the creation of a file ++/// is an atomic operation - that's true for most filesystems but may ++/// cause problems with network based filesystems. ++/// Example: ++/// ``` ++/// let lock = LockFile::lock("/var/lock/process.lock"); ++/// assert!(lock.is_ok()); ++/// let lock2 = LockFile::lock("/var/lock/process.lock"); ++/// assert!(lock2.is_err()); ++/// drop(lock); ++/// let lock3 = LockFile::lock("/var/lock/process.lock"); ++/// assert!(lock3.is_ok()); ++/// ``` ++#[derive(Debug)] ++pub struct LockFile { ++ lockfile: PathBuf, ++} ++ ++impl LockFile { ++ /// Try to establish the lock file. ++ /// Upon success the given file is fresh created and has the pid of this ++ /// process written in. The function returns a new LockFile object ++ /// which has implemented the Drop Trait. So with this object going out ++ /// of scope the lock file is deleted. If establishing the lock file ++ /// fails for any reason (for example the file already exists), the ++ /// function fails with returning an Error string. This function does ++ /// NOT panic if establishing the lock file fails for any reason. If ++ /// the lock file could be esablished but writing in the PID fails, a ++ /// warning is printed but the function continues with returning a ++ /// LockFile object. ++ pub fn try_lock(fname: &str) -> Result { ++ let lockfile = PathBuf::from(fname); ++ let mut file = match OpenOptions::new() ++ .write(true) ++ .create_new(true) ++ .open(&lockfile) ++ { ++ Err(err) => { ++ return Err(format!( ++ "Failure trying to create lock file {fname}: {err:?}." ++ )) ++ } ++ Ok(f) => f, ++ }; ++ let _ = file ++ .write(format!("{}", std::process::id()).as_bytes()) ++ .map_err(|err| { ++ println!("Warning: could not write PID into lockfile {fname}: {err:?}.") ++ }); ++ Ok(LockFile { lockfile }) ++ } ++} ++ ++impl Drop for LockFile { ++ fn drop(&mut self) { ++ let _ = fs::remove_file(&self.lockfile).map_err(|err| { ++ println!( ++ "Warning: could not remove lockfile {}: {err:?}.", ++ self.lockfile.display() ++ ) ++ }); ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ ++ use super::*; ++ ++ // Only very simple tests ++ ++ const TEST_BYTES: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]; ++ const TEST_HEXSTR: &str = "0123456789abcdef"; ++ ++ #[test] ++ fn test_u8_to_hexstring() { ++ let str = u8_to_hexstring(&TEST_BYTES); ++ assert!(str == TEST_HEXSTR); ++ } ++ #[test] ++ fn test_hexstring_to_u8() { ++ let bytes = hexstring_to_u8(TEST_HEXSTR); ++ assert!(bytes.as_slice() == TEST_BYTES); ++ } ++ #[test] ++ fn test_sysfs_read_string() { ++ let r = sysfs_read_string("/proc/cpuinfo"); ++ assert!(r.is_ok()); ++ } ++ #[test] ++ fn test_sysfs_read_i32() { ++ let r = sysfs_read_i32("/proc/sys/kernel/random/entropy_avail"); ++ assert!(r.is_ok()); ++ } ++ #[test] ++ fn test_sysfs_get_list_of_subdirs() { ++ let r = sysfs_get_list_of_subdirs("/proc/self"); ++ assert!(r.is_ok()); ++ let v = r.unwrap(); ++ assert!(!v.is_empty()); ++ } ++ #[test] ++ fn test_sysfs_get_list_of_subdirs_matching_regex() { ++ let r = sysfs_get_list_of_subdirs_matching_regex("/proc/self", "fd.*"); ++ assert!(r.is_ok()); ++ let v = r.unwrap(); ++ assert!(!v.is_empty()); ++ for e in v { ++ assert!(e.strip_prefix("fd").is_some()); ++ } ++ } ++ #[test] ++ fn test_sysfs_write_i32() { ++ const TEST_PATH: &str = "/tmp/test_sysfs_write_i32"; ++ let mut file = File::create(TEST_PATH).unwrap(); ++ let _ = file.write_all(b"XYZ"); ++ drop(file); ++ let r = sysfs_read_i32(TEST_PATH); ++ assert!(r.is_err()); ++ let r = sysfs_write_i32(TEST_PATH, 999); ++ assert!(r.is_ok()); ++ let r = sysfs_read_i32(TEST_PATH); ++ assert!(r.is_ok()); ++ let v = r.unwrap(); ++ assert!(v == 999); ++ let _ = fs::remove_file(TEST_PATH); ++ } ++ #[test] ++ fn test_lockfile() { ++ let r1 = LockFile::try_lock(PATH_PVAPCONFIG_LOCK); ++ assert!(r1.is_ok()); ++ let r2 = LockFile::try_lock(PATH_PVAPCONFIG_LOCK); ++ assert!(r2.is_err()); ++ drop(r1); ++ let r3 = LockFile::try_lock(PATH_PVAPCONFIG_LOCK); ++ assert!(r3.is_ok()); ++ } ++} +diff --git a/rust/pvapconfig/src/main.rs b/rust/pvapconfig/src/main.rs +new file mode 100644 +index 00000000..07899cd9 +--- /dev/null ++++ b/rust/pvapconfig/src/main.rs +@@ -0,0 +1,668 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2023 ++// ++//! pvapconfig - Tool to automatically set up the AP configuration ++//! within an IBM Secure Execution guest. ++// ++ ++mod ap; ++mod cli; ++mod config; ++mod helper; ++mod uv; ++ ++use ap::{Apqn, ApqnList}; ++use cli::ARGS; ++use config::{ApConfigEntry, ApConfigList}; ++use helper::{LockFile, PATH_PVAPCONFIG_LOCK}; ++use pv::uv::{ListableSecretType, SecretList}; ++use std::process::ExitCode; ++use utils::release_string; ++ ++/// Simple macro for ++/// if Cli::verbose() { ++/// print!(...); ++/// } ++macro_rules! info { ++ ($($arg:tt)*) => {{ ++ if ARGS.verbose() { ++ print!($($arg)*); ++ } ++ }}; ++} ++ ++/// Simple macro for the main function only ++/// Does a eprintln of the arguments and then ++/// return with exit failure. ++macro_rules! println_and_exit_failure { ++ ($($arg:tt)*) => {{ ++ eprintln!($($arg)*); ++ return ExitCode::FAILURE; ++ }}; ++} ++ ++/// Simple macro for the main function only ++/// Check if given object has is_err() true and ++/// then eprintln the unwrapped error and ++/// returns with exit failure. ++macro_rules! on_error_print_and_exit { ++ ($r:expr) => { ++ if $r.is_err() { ++ eprintln!("{}", $r.unwrap_err()); ++ return ExitCode::FAILURE; ++ } ++ }; ++} ++ ++fn main() -> ExitCode { ++ // handle version option ++ if cli::ARGS.version { ++ println!( ++ "{} version {}\nCopyright IBM Corp. 2023", ++ env!("CARGO_PKG_NAME"), ++ release_string!() ++ ); ++ return ExitCode::SUCCESS; ++ } ++ ++ // make sure only one pvapconfig instance is running ++ let r = LockFile::try_lock(PATH_PVAPCONFIG_LOCK); ++ on_error_print_and_exit!(r); ++ let _lockfile = r.unwrap(); ++ ++ // AP bus check ++ info!("Checking AP bus support and facilities...\n"); ++ let r = ap::check_ap_bus_support(); ++ on_error_print_and_exit!(r); ++ let r = ap::ap_bus_has_apsb_support(); ++ on_error_print_and_exit!(r); ++ info!("AP bus support and facilities are ok.\n"); ++ ++ // UV check ++ info!("Checking UV support and environment...\n"); ++ if !pv::misc::pv_guest_bit_set() { ++ println_and_exit_failure!("Failure: this is not a SE guest."); ++ } ++ let r = uv::has_list_secrets_facility(); ++ on_error_print_and_exit!(r); ++ info!("UV support and environment is ok.\n"); ++ ++ // read configuration ++ let configfile: &str = match &cli::ARGS.config { ++ Some(f) => f, ++ _ => cli::PATH_DEFAULT_CONFIG_FILE, ++ }; ++ info!( ++ "Reading AP configuration entries from file '{}'...\n", ++ configfile ++ ); ++ let apconfig: ApConfigList = match ApConfigList::read_and_validate_yaml_file(configfile) { ++ Ok(apcfg) => apcfg, ++ Err(err) => println_and_exit_failure!("{}", err), ++ }; ++ if apconfig.is_empty() { ++ println!( ++ "No AP configuration entries in config file '{}': Nothing to do.", ++ configfile ++ ); ++ return ExitCode::SUCCESS; ++ } ++ info!("Found {} AP configuration entries.\n", apconfig.len()); ++ ++ // get list of secrets from UV ++ info!("Fetching list of secrets from UV...\n"); ++ let secrets: SecretList = match uv::gather_secrets() { ++ Err(e) => println_and_exit_failure!("{}", e), ++ Ok(los) => los, ++ }; ++ info!("Fetched {} Secret entries from UV.\n", secrets.len()); ++ ++ // Warning if no UV secrets given but AP config entries require it ++ let non_accel_apc = apconfig ++ .iter() ++ .filter(|apc| apc.mode != config::STR_MODE_ACCEL) ++ .count(); ++ if non_accel_apc > 0 && secrets.is_empty() { ++ println!( ++ "Warning: No UV Secrets given but at least one AP config entry requires a Secret." ++ ); ++ } ++ ++ info!("Waiting for AP bus bindings complete...\n"); ++ if !ap::wait_for_ap_bus_bindings_complete() { ++ return ExitCode::FAILURE; ++ } ++ info!("Fetching list of available APQNs...\n"); ++ let mut apqns: ApqnList = match ApqnList::gather_apqns() { ++ Some(l) => l, ++ None => return ExitCode::FAILURE, ++ }; ++ if apqns.is_empty() { ++ info!("List of available APQNs is empty: So there's nothing to do.\n"); ++ return ExitCode::SUCCESS; ++ } ++ info!("Found {} APQNs.\n", apqns.len()); ++ // check MK restriction ++ if !apqns.check_mk_restriction() { ++ return ExitCode::FAILURE; ++ } ++ ++ // now the real work ++ info!("Applying AP configuration...\n"); ++ let n = match do_ap_config(&mut apqns, &secrets, &apconfig, false) { ++ Err(e) => println_and_exit_failure!("{}", e), ++ Ok(n) => n, ++ }; ++ ++ if n == 0 { ++ println_and_exit_failure!( ++ "None out of {} AP config entries could be applied.", ++ apconfig.len() ++ ); ++ } else if ARGS.strict() && n != apconfig.len() { ++ println_and_exit_failure!( ++ "Strict flag given and only {} out of {} AP config entries have been applied.", ++ n, ++ apconfig.len() ++ ); ++ } ++ ++ info!( ++ "Successfully applied {} out of {} AP config entries.\n", ++ n, ++ apconfig.len() ++ ); ++ ++ ExitCode::SUCCESS ++} ++ ++/// The real worker function ++/// ++/// This is the real algorithm which is trying to apply the ++/// AP configuration read from the config file to the existing ++/// APQNs with the info from the list of secrets from the UV. ++/// Returns the nr of AP config entries which are fullfilled ++/// after the function ended. ++/// apqns needs to be mutable as the function does a resort ++/// but content stays the same. ++fn do_ap_config( ++ apqns: &mut ApqnList, ++ secrets: &SecretList, ++ apconfig: &ApConfigList, ++ fntest: bool, ++) -> Result { ++ let mut resolved_entries = 0; ++ let mut apconfig_done = vec![false; apconfig.len()]; ++ let mut apqn_done = vec![false; apqns.len()]; ++ ++ // Preparation: Sort APQNs by generation. ++ // All the following steps iterate through the list ++ // of APQNs. So by sorting the APQNs starting with ++ // highest card generation down to the older card ++ // generations we prefer newer card generations over ++ // older card generations. ++ apqns.sort_by_gen(); ++ ++ // Step 1: ++ // Go through all AP config entries and try to find an APQN ++ // which already matches to this entry. If such an APQN is ++ // found mark the AP config entry as done, and mark the APQN ++ // as used so that entry and APQN will get skipped over in ++ // the next steps. ++ ++ for (ci, apc) in apconfig.iter().enumerate() { ++ let cistr = if !apc.name.is_empty() { ++ format!("#{} '{}'", ci + 1, apc.name) ++ } else { ++ format!("#{}", ci + 1) ++ }; ++ for (ai, apqn) in apqns.iter().enumerate() { ++ if apqn_done[ai] { ++ continue; ++ } ++ if !config_and_apqn_match(apc, apqn) { ++ continue; ++ } ++ if fntest { ++ continue; ++ } ++ match apqn.mode { ++ ap::ApqnMode::Accel => { ++ // check bind state of this APQN ++ let bind_state_ok = match apqn.bind_state() { ++ Err(err) => { ++ eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"); ++ false ++ } ++ Ok(ap::BindState::Bound) => true, ++ Ok(_) => false, ++ }; ++ if !bind_state_ok { ++ continue; ++ } ++ // This APQN matches to the current AP config entry and is already bound. ++ // So this AP config entry is satisfied: mark this config enty as done ++ // and mark this APQN as used. ++ info!("Accelerator APQN {apqn} already satisfies AP config entry {cistr}.\n"); ++ apconfig_done[ci] = true; ++ apqn_done[ai] = true; ++ resolved_entries += 1; ++ break; ++ } ++ ap::ApqnMode::Ep11 => { ++ // check association state of this APQN ++ let (assoc_state_ok, assoc_idx) = match apqn.associate_state() { ++ Err(err) => { ++ eprintln!( ++ "Warning: Failure reading APQN {apqn} associate state: {err}" ++ ); ++ (false, 0) ++ } ++ Ok(ap::AssocState::Associated(idx)) => (true, idx), ++ Ok(_) => (false, 0), ++ }; ++ if !assoc_state_ok { ++ continue; ++ } ++ // check association index ++ let r = secrets.iter().find(|&se| { ++ se.stype() == ListableSecretType::Association ++ && se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE ++ && se.index() == assoc_idx ++ && helper::u8_to_hexstring(se.id()) == apc.secretid ++ }); ++ if r.is_none() { ++ continue; ++ } ++ // This APQN matches to the current AP config entry and is already ++ // associated with the right secret id. So this AP config entry is ++ // satisfied: mark this config enty as done and mark this APQN as used. ++ info!("EP11 APQN {apqn} already satisfies AP config entry {cistr}.\n"); ++ apconfig_done[ci] = true; ++ apqn_done[ai] = true; ++ resolved_entries += 1; ++ break; ++ } ++ _ => { ++ // (currently) unknown/unsupported APQN mode ++ } ++ } ++ } ++ } ++ ++ // Step 2: ++ // All APQNs NOT marked as done are now examined for their bind ++ // and association state and maybe reset to "unbound". ++ ++ for (ai, apqn) in apqns.iter().enumerate() { ++ if apqn_done[ai] || fntest { ++ continue; ++ } ++ match apqn.bind_state() { ++ Err(err) => eprintln!("Warning: Failure reading APQN {apqn} bind state: {err}"), ++ Ok(ap::BindState::Bound) => { ++ info!("Unbind APQN {apqn} as this bind/associate does not match to any AP config entry.\n"); ++ if !ARGS.dryrun() { ++ if let Err(err) = apqn.set_bind_state(ap::BindState::Unbound) { ++ return Err(format!("Failure unbinding APQN {apqn}: {err}")); ++ } ++ } ++ } ++ Ok(_) => {} ++ }; ++ } ++ ++ // Step 3: ++ // Go through all remaining AP config entries and try to fullfill each ++ // by searching for an APQN which would match to this config entry and ++ // then prepare this APQN (bind, maybe associate). ++ for (ci, apc) in apconfig.iter().enumerate() { ++ let cistr = if !apc.name.is_empty() { ++ format!("#{} '{}'", ci + 1, apc.name) ++ } else { ++ format!("#{}", ci + 1) ++ }; ++ if apconfig_done[ci] { ++ continue; ++ } ++ for (ai, apqn) in apqns.iter().enumerate() { ++ if apqn_done[ai] { ++ continue; ++ } ++ if !config_and_apqn_match(apc, apqn) { ++ continue; ++ } ++ match apqn.mode { ++ ap::ApqnMode::Accel => { ++ // try to bind this accelerator APQN ++ if ARGS.verbose() || fntest { ++ println!("Bind APQN {apqn} to match to AP config entry {cistr}."); ++ } ++ if !(ARGS.dryrun() || fntest) { ++ if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) { ++ // bind failed, unbind/reset this apqn, return with failure ++ let _ = apqn.set_bind_state(ap::BindState::Unbound); ++ return Err(format!("Failure binding APQN {apqn}: {err}")); ++ } ++ } ++ apconfig_done[ci] = true; ++ apqn_done[ai] = true; ++ resolved_entries += 1; ++ break; ++ } ++ ap::ApqnMode::Ep11 => { ++ // EP11 needs bind and associate, but before doing this let's ++ // check out which secret index to use with the associate ++ let se = match secrets.iter().find(|&se| { ++ se.stype() == ListableSecretType::Association ++ && se.id().len() == uv::AP_ASSOC_SECRET_ID_SIZE ++ && helper::u8_to_hexstring(se.id()) == apc.secretid ++ }) { ++ None => { ++ eprintln!("Warning: Secret id '{}' from config entry {} not found in UV secrets list.", ++ apc.secretid, cistr); ++ break; ++ } ++ Some(se) => se, ++ }; ++ // try to bind ++ if ARGS.verbose() || fntest { ++ println!( ++ "Bind APQN {apqn} to match to AP config entry {cistr} (step 1/2)." ++ ); ++ } ++ if !(ARGS.dryrun() || fntest) { ++ if let Err(err) = apqn.set_bind_state(ap::BindState::Bound) { ++ // bind failed, unbind/reset this apqn, return with failure ++ let _ = apqn.set_bind_state(ap::BindState::Unbound); ++ return Err(format!("Failure binding APQN {}: {}", apqn, err)); ++ } ++ } ++ // try to associate ++ if ARGS.verbose() || fntest { ++ println!( ++ "Associate APQN {} with uv secrets index {} to match AP config entry {} (step 2/2).", ++ apqn, se.index(), cistr ++ ); ++ } ++ if !(ARGS.dryrun() || fntest) { ++ let apas = ap::AssocState::Associated(se.index()); ++ apqn.set_associate_state(apas) ++ .map_err(|err| format!("Failure associating APQN {apqn}: {err}"))?; ++ } ++ apconfig_done[ci] = true; ++ apqn_done[ai] = true; ++ resolved_entries += 1; ++ break; ++ } ++ _ => { ++ // (currently) unknown/unsupported APQN mode ++ } ++ } ++ } ++ } ++ ++ Ok(resolved_entries) ++} ++ ++/// # Panics ++/// Panics if mingen for an accelerator has not a number as the 4th character. ++/// Panics if mingen for an ep11 has not a number as the 4th character. ++/// Please note this can not happen, as mingen is already checked via RE ++/// during storing the value into mingen. ++fn config_and_apqn_match(apc: &ApConfigEntry, apqn: &Apqn) -> bool { ++ if apc.mode == config::STR_MODE_ACCEL && apqn.mode == ap::ApqnMode::Accel { ++ // config and apqn are accelerators ++ // maybe check mingen ++ if !apc.mingen.is_empty() { ++ let mingen = &apc.mingen[3..].parse::().unwrap(); ++ if mingen < &apqn.gen { ++ return false; ++ } ++ } ++ return true; ++ } else if apc.mode == config::STR_MODE_EP11 && apqn.mode == ap::ApqnMode::Ep11 { ++ // config and apqn are ep11 ++ let info = match &apqn.info { ++ Some(ap::ApqnInfo::Ep11(i)) => i, ++ _ => return false, ++ }; ++ // maybe check mingen ++ if !apc.mingen.is_empty() { ++ let mingen = &apc.mingen[3..].parse::().unwrap(); ++ if mingen < &apqn.gen { ++ return false; ++ } ++ } ++ // maybe check serialnr ++ if !apc.serialnr.is_empty() && apc.serialnr != info.serialnr { ++ return false; ++ } ++ // check mkvp, currently an ep11 config entry must state an mkvp value ++ // whereas an ep11 info from an APQN may have an empty mkvp value to ++ // indicate that there is no WK set on this APQN. ++ if apc.mkvp != info.mkvp { ++ return false; ++ } ++ return true; ++ } ++ false ++} ++ ++#[cfg(test)] ++mod tests { ++ ++ use super::*; ++ use helper::hexstring_to_u8; ++ use pv::uv::SecretEntry; ++ ++ // This is more or less only a test for the do_ap_config() function ++ // However, this is THE main functionality of the whole application. ++ ++ fn make_test_apqns() -> Vec { ++ let mut v = Vec::new(); ++ v.push(ap::Apqn { ++ name: String::from("10.0007"), ++ card: 16, ++ domain: 7, ++ gen: 8, ++ mode: ap::ApqnMode::Accel, ++ info: Option::Some(ap::ApqnInfo::Accel(ap::ApqnInfoAccel {})), ++ }); ++ v.push(ap::Apqn { ++ name: String::from("11.0008"), ++ card: 17, ++ domain: 8, ++ gen: 8, ++ mode: ap::ApqnMode::Ep11, ++ info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { ++ serialnr: String::from("93AADFK719460083"), ++ mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"), ++ })), ++ }); ++ v.push(ap::Apqn { ++ name: String::from("12.0009"), ++ card: 18, ++ domain: 9, ++ gen: 8, ++ mode: ap::ApqnMode::Ep11, ++ info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { ++ serialnr: String::from("93AADHZU42082261"), ++ mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"), ++ })), ++ }); ++ v.push(ap::Apqn { ++ name: String::from("12.000a"), ++ card: 18, ++ domain: 10, ++ gen: 8, ++ mode: ap::ApqnMode::Ep11, ++ info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { ++ serialnr: String::from("93AADHZU42082261"), ++ mkvp: String::from("383d2a9ab781f35343554c5b3d9337cd"), ++ })), ++ }); ++ v.push(ap::Apqn { ++ name: String::from("13.000d"), ++ card: 19, ++ domain: 13, ++ gen: 8, ++ mode: ap::ApqnMode::Ep11, ++ info: Option::Some(ap::ApqnInfo::Ep11(ap::ApqnInfoEp11 { ++ serialnr: String::from("87HU397G150TZGR"), ++ mkvp: String::new(), ++ })), ++ }); ++ v.push(ap::Apqn { ++ name: String::from("13.000f"), ++ card: 19, ++ domain: 15, ++ gen: 8, ++ mode: ap::ApqnMode::Ep11, ++ info: Option::None, ++ }); ++ return v; ++ } ++ ++ fn make_assoc_secretentry(idx: u16, hexidstr: &str) -> SecretEntry { ++ let id = hexstring_to_u8(hexidstr); ++ let idlen: u32 = id.len().try_into().unwrap(); ++ let idarray = <&[u8; 32]>::try_from(id.as_slice()).unwrap(); ++ SecretEntry::new(idx, ListableSecretType::Association, *idarray, idlen) ++ } ++ ++ fn make_test_secrets() -> Vec { ++ let mut v: Vec = Vec::new(); ++ v.push(make_assoc_secretentry( ++ 33, ++ "3333333333333333333333333333333333333333333333333333333333333333", ++ )); ++ v.push(make_assoc_secretentry( ++ 13, ++ "bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a", ++ )); ++ v.push(make_assoc_secretentry( ++ 44, ++ "4444444444444444444444444444444444444444444444444444444444444444", ++ )); ++ v.push(make_assoc_secretentry( ++ 15, ++ "06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9", ++ )); ++ v.push(make_assoc_secretentry( ++ 17, ++ "6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e", ++ )); ++ return v; ++ } ++ ++ fn make_test_apconfigs() -> Vec { ++ let mut v: Vec = Vec::new(); ++ v.push(config::ApConfigEntry { ++ name: String::from("test_1"), ++ description: String::from("test_1"), ++ mode: String::from("accel"), ++ mkvp: String::from(""), ++ serialnr: String::from(""), ++ mingen: String::from("cex8"), ++ secretid: String::from(""), ++ }); ++ v.push(config::ApConfigEntry { ++ name: String::from("test_2"), ++ description: String::from("test_2"), ++ mode: String::from("ep11"), ++ mkvp: String::from("db3c3b3c3f097dd55ec7eb0e7fdbcb93"), ++ serialnr: String::from("93AADFK719460083"), ++ mingen: String::from("cex8"), ++ secretid: String::from( ++ "bc9d46c052bc3574454c5715757274629a283767ed237922cfb8651c0e77320a", ++ ), ++ }); ++ v.push(config::ApConfigEntry { ++ name: String::from("test_3"), ++ description: String::from("test_3"), ++ mode: String::from("ep11"), ++ mkvp: String::from("4a27bb66520ac85f6073a7f678d262c0"), ++ serialnr: String::from(""), ++ mingen: String::from("cex8"), ++ secretid: String::from( ++ "06cdbbac76a595b481110d108154bc05ebbf900a0f16e36a24045998934fb1e9", ++ ), ++ }); ++ v.push(config::ApConfigEntry { ++ name: String::from("test_4"), ++ description: String::from("test_4"), ++ mode: String::from("ep11"), ++ mkvp: String::from("8be1eaf5c44e2fa8b18804551b604b1b"), ++ serialnr: String::from(""), ++ mingen: String::from("cex8"), ++ secretid: String::from( ++ "6831af07f8c8e7309a3ace9f3b5554d34e3eaa4a27a08fdee469e367c3fa3e9e", ++ ), ++ }); ++ return v; ++ } ++ ++ #[test] ++ fn test_do_ap_config_invocation_1() { ++ let test_apqns = make_test_apqns(); ++ let mut apqns: Vec = Vec::new(); ++ apqns.push(test_apqns[0].clone()); ++ let secrets: Vec = Vec::new(); ++ let secretlist = SecretList::new(secrets.len() as u16, secrets); ++ let test_apconfigs = make_test_apconfigs(); ++ let mut apconfig: Vec = Vec::new(); ++ apconfig.push(test_apconfigs[0].clone()); ++ let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig); ++ let mut apqnlist = ApqnList::from_apqn_vec(apqns); ++ let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true); ++ assert!(r.is_ok()); ++ let n = r.unwrap(); ++ assert!(n == 1); ++ } ++ ++ #[test] ++ fn test_do_ap_config_invocation_2() { ++ let test_apqns = make_test_apqns(); ++ let mut apqns: Vec = Vec::new(); ++ apqns.push(test_apqns[1].clone()); ++ let mut secrets = make_test_secrets(); ++ while secrets.len() > 2 { ++ secrets.pop(); ++ } ++ let secretlist = SecretList::new(secrets.len() as u16, secrets); ++ let test_apconfigs = make_test_apconfigs(); ++ let mut apconfig: Vec = Vec::new(); ++ apconfig.push(test_apconfigs[1].clone()); ++ let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig); ++ let mut apqnlist = ApqnList::from_apqn_vec(apqns); ++ let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true); ++ assert!(r.is_ok()); ++ let n = r.unwrap(); ++ assert!(n == 1); ++ } ++ ++ #[test] ++ fn test_do_ap_config_invocation_3() { ++ let test_apqns = make_test_apqns(); ++ let mut apqns: Vec = Vec::new(); ++ for a in test_apqns.iter() { ++ apqns.push(a.clone()); ++ } ++ apqns.reverse(); ++ let secrets = make_test_secrets(); ++ let secretlist = SecretList::new(secrets.len() as u16, secrets); ++ let test_apconfigs = make_test_apconfigs(); ++ let mut apconfig: Vec = Vec::new(); ++ for c in test_apconfigs.iter() { ++ apconfig.push(c.clone()); ++ } ++ let apcfglist = ApConfigList::from_apconfigentry_vec(apconfig); ++ let mut apqnlist = ApqnList::from_apqn_vec(apqns); ++ let r = do_ap_config(&mut apqnlist, &secretlist, &apcfglist, true); ++ assert!(r.is_ok()); ++ let n = r.unwrap(); ++ assert!(n == 3, "n = {} != 3", n); ++ } ++} +diff --git a/rust/pvapconfig/src/uv.rs b/rust/pvapconfig/src/uv.rs +new file mode 100644 +index 00000000..2f98bd5e +--- /dev/null ++++ b/rust/pvapconfig/src/uv.rs +@@ -0,0 +1,105 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2023 ++// ++//! UV related functions for pvapconfig ++// ++ ++use pv::uv::{ListCmd, SecretList, UvDevice, UvcSuccess}; ++use regex::Regex; ++use std::path::Path; ++ ++/// The byte size of association secret of type 2 in struct SecretEntry ++pub const AP_ASSOC_SECRET_ID_SIZE: usize = 32; ++ ++const PATH_SYS_FW_UV_FACILITIES: &str = "/sys/firmware/uv/query/facilities"; ++ ++const RE_UV_FACILITIES: &str = r"^(0x)?([[:xdigit:]]+)"; ++const RE_UV_FAC_BIT_LIST_SECRETS: u32 = 30; ++ ++/// Check UV facilities to offer the 'list secrets' call. ++/// Returns a Result with Ok(()) if the 'list secrets' feature ++/// is available, otherwise an Err(reasonstring) is returned where ++/// the string denotes a hint which can be displayed. ++/// # Panics ++/// Panics if the compilation of a static regular expression fails. ++/// Panics if RE_UV_FACILITIES does not match. ++pub fn has_list_secrets_facility() -> Result<(), String> { ++ if !Path::new(PATH_SYS_FW_UV_FACILITIES).is_file() { ++ return Err(format!( ++ "UV facilities sysfs attribute not found (file {} does not exist).", ++ PATH_SYS_FW_UV_FACILITIES ++ )); ++ } ++ let facstr = match crate::helper::sysfs_read_string(PATH_SYS_FW_UV_FACILITIES) { ++ Ok(s) => s, ++ Err(err) => { ++ return Err(format!( ++ "Failure reading UV facilities from {PATH_SYS_FW_UV_FACILITIES} ({:?}).", ++ err ++ )) ++ } ++ }; ++ let re_uv_facilities = Regex::new(RE_UV_FACILITIES).unwrap(); ++ if !re_uv_facilities.is_match(&facstr) { ++ Err(format!("Failure parsing UV facilities entry '{facstr}'.")) ++ } else { ++ let caps = re_uv_facilities.captures(&facstr).unwrap(); ++ let fachex = caps.get(2).unwrap().as_str(); ++ let i: usize = RE_UV_FAC_BIT_LIST_SECRETS as usize / 4; ++ if i >= fachex.len() { ++ return Err(format!("Failure parsing UV facilities entry '{fachex}'.")); ++ } ++ let nibble = u32::from_str_radix(&fachex[i..i + 1], 16).unwrap(); ++ const THEBIT: u32 = 1 << (3 - (RE_UV_FAC_BIT_LIST_SECRETS % 4)); ++ if nibble & THEBIT == 0 { ++ return Err("The 'list secret' feature is missing on this UV.".to_string()); ++ } ++ Ok(()) ++ } ++} ++ ++/// Fetch the list of secrets from the UV. ++/// Returns Err(errorstring) on error or ++/// Ok(SecretList) on success. ++/// The list may be empty if the UV doesn't have any secrets stored. ++pub fn gather_secrets() -> Result { ++ let uv = match UvDevice::open() { ++ Err(e) => return Err(format!("Failed to open UV device: {:?}.", e)), ++ Ok(u) => u, ++ }; ++ let mut cmd = ListCmd::default(); ++ match uv.send_cmd(&mut cmd).map_err(|e| format!("{e:?}"))? { ++ UvcSuccess::RC_SUCCESS => (), ++ UvcSuccess::RC_MORE_DATA => println!("Warning: There is more data available than expected"), ++ }; ++ cmd.try_into().map_err(|e| format!("{e:?}")) ++} ++ ++#[cfg(test)] ++mod tests { ++ ++ use super::*; ++ ++ // As the name says: check for list secrets feature bit in UV facilities. ++ #[test] ++ fn test_has_list_secrets_facility() { ++ let r = has_list_secrets_facility(); ++ if pv::misc::pv_guest_bit_set() { ++ assert!(r.is_ok()); ++ } else { ++ assert!(r.is_err()); ++ } ++ } ++ ++ // Simple invocation of the list_secrets function. Should not fail ++ #[test] ++ fn test_list_secrets() { ++ let r = gather_secrets(); ++ if pv::misc::pv_guest_bit_set() { ++ assert!(r.is_ok()); ++ } else { ++ assert!(r.is_err()); ++ } ++ } ++} +-- +2.53.0 + + +From 6b69de3c519971a88c5953075586b322e1efdc3e Mon Sep 17 00:00:00 2001 +From: Joern Siglen +Date: Wed, 25 Oct 2023 15:01:11 +0200 +Subject: [PATCH 14/22] dbginfo.sh: enhance ethtool collection for ROCE + (RHEL-24110) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +collect module-info for new ROCE cards via ethtool + +Suggested-by: Niklas Schnelle +Reviewed-by: Niklas Schnelle +Signed-off-by: Joern Siglen +Signed-off-by: Jan Höppner +(cherry picked from commit d9034b01f1ddc69850a554d0077db34573560569) +--- + scripts/dbginfo.sh | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/scripts/dbginfo.sh b/scripts/dbginfo.sh +index 9226a8bb..f70cf6ee 100755 +--- a/scripts/dbginfo.sh ++++ b/scripts/dbginfo.sh +@@ -954,6 +954,8 @@ collect_ethtool() { + "${OUTPUT_FILE_ETHTOOL}" + call_run_command "ethtool -T ${network_device}" \ + "${OUTPUT_FILE_ETHTOOL}" ++ call_run_command "ethtool --module-info ${network_device}" \ ++ "${OUTPUT_FILE_ETHTOOL}" + done + else + pr_skip "ethtool: no devices" +-- +2.53.0 + + +From 90943f11e0feef6bc6cde3bf0b80ad0a21c55d72 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Dan=20Hor=C3=A1k?= +Date: Wed, 10 Apr 2024 11:27:58 +0200 +Subject: [PATCH 15/22] rust/pv: Support `Armonk` in IBM signing key subject + (RHEL-30398) + +New IBM signing keys will have Armonk as locality in the subject. +Ensure that CRLs with Poughkeepsie as issuer locality are still +discovered if they are signed with the signing keys private key. +Also, drop the check for issuer/subject comparison and only rely on +validity period and cryptographic signatures. + +Reviewed-by: Christoph Schlameuss +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden + +(backported from commit 1a3d0b74f7819f5e087e6ecbf3ec879a05a88bbc) +--- + rust/pv/src/verify.rs | 58 ++++++++++++++++++++++------ + rust/pv/src/verify/helper.rs | 74 +++++++++++++----------------------- + rust/pv/src/verify/test.rs | 12 ------ + 3 files changed, 73 insertions(+), 71 deletions(-) + +diff --git a/rust/pv/src/verify.rs b/rust/pv/src/verify.rs +index 54fe4357..3482f8c8 100644 +--- a/rust/pv/src/verify.rs ++++ b/rust/pv/src/verify.rs +@@ -3,10 +3,11 @@ + // Copyright IBM Corp. 2023 + + use core::slice; +-use log::debug; ++use log::{debug, trace}; ++use openssl::error::ErrorStack; + use openssl::stack::Stack; + use openssl::x509::store::X509Store; +-use openssl::x509::{CrlStatus, X509Ref, X509StoreContext, X509}; ++use openssl::x509::{CrlStatus, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, X509}; + use openssl_extensions::crl::StackableX509Crl; + use openssl_extensions::crl::X509StoreContextExtension; + +@@ -76,8 +77,8 @@ impl HkdVerifier for CertVerifier { + if verified_crls.is_empty() { + bail_hkd_verify!(NoCrl); + } +- for crl in &verified_crls { +- match crl.get_by_cert(&hkd.to_owned()) { ++ for crl in verified_crls { ++ match crl.get_by_serial(hkd.serial_number()) { + CrlStatus::NotRevoked => (), + _ => bail_hkd_verify!(HdkRevoked), + } +@@ -88,21 +89,54 @@ impl HkdVerifier for CertVerifier { + } + + impl CertVerifier { ++ fn quirk_crls( ++ ctx: &mut X509StoreContextRef, ++ subject: &X509NameRef, ++ ) -> Result, ErrorStack> { ++ match ctx.crls(subject) { ++ Ok(ret) if !ret.is_empty() => return Ok(ret), ++ _ => (), ++ } ++ ++ // Armonk/Poughkeepsie fixup ++ trace!("quirk_crls: Try Locality"); ++ if let Some(locality_subject) = helper::armonk_locality_fixup(subject) { ++ match ctx.crls(&locality_subject) { ++ Ok(ret) if !ret.is_empty() => return Ok(ret), ++ _ => (), ++ } ++ ++ // reorder ++ trace!("quirk_crls: Try Locality+Reorder"); ++ if let Ok(locality_ordered_subject) = helper::reorder_x509_names(&locality_subject) { ++ match ctx.crls(&locality_ordered_subject) { ++ Ok(ret) if !ret.is_empty() => return Ok(ret), ++ _ => (), ++ } ++ } ++ } ++ ++ // reorder unchanged loaciliy subject ++ trace!("quirk_crls: Try Reorder"); ++ if let Ok(ordered_subject) = helper::reorder_x509_names(subject) { ++ match ctx.crls(&ordered_subject) { ++ Ok(ret) if !ret.is_empty() => return Ok(ret), ++ _ => (), ++ } ++ } ++ // nothing found, return empty stack ++ Stack::new() ++ } ++ + ///Download the CLRs that a HKD refers to. + pub fn hkd_crls(&self, hkd: &X509Ref) -> Result> { + let mut ctx = X509StoreContext::new()?; + // Unfortunately we cannot use a dedicated function here and have to use a closure (E0434) + // Otherwise, we cannot refer to self ++ // Search for local CRLs + let mut crls = ctx.init_opt(&self.store, None, None, |ctx| { + let subject = self.ibm_z_sign_key.subject_name(); +- match ctx.crls(subject) { +- Ok(crls) => Ok(crls), +- _ => { +- // reorder the name and try again +- let broken_subj = helper::reorder_x509_names(subject)?; +- ctx.crls(&broken_subj).or_else(helper::stack_err_hlp) +- } +- } ++ Self::quirk_crls(ctx, subject) + })?; + + if !self.offline { +diff --git a/rust/pv/src/verify/helper.rs b/rust/pv/src/verify/helper.rs +index a2f313b0..732baef5 100644 +--- a/rust/pv/src/verify/helper.rs ++++ b/rust/pv/src/verify/helper.rs +@@ -14,7 +14,7 @@ use openssl::{ + error::ErrorStack, + nid::Nid, + ssl::SslFiletype, +- stack::{Stack, Stackable}, ++ stack::Stack, + x509::{ + store::{File, X509Lookup, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}, + verify::{X509VerifyFlags, X509VerifyParam}, +@@ -27,6 +27,7 @@ use openssl_extensions::{ + crl::X509StoreExtension, + }; + use std::cmp::Ordering; ++use std::str::from_utf8; + use std::time::Duration; + use std::usize; + +@@ -42,7 +43,6 @@ const SECURITY_CHAIN_MAX_LEN: c_int = 2; + /// verifies that the HKD + /// * has enough security bits + /// * is inside its validity period +-/// * issuer name is the subject name of the [`sign_key`] + /// * the Authority Key ID matches the Signing Key ID of the [`sign_key`] + pub fn verify_hkd_options(hkd: &X509Ref, sign_key: &X509Ref) -> Result<()> { + let hk_pkey = hkd.public_key()?; +@@ -56,9 +56,6 @@ pub fn verify_hkd_options(hkd: &X509Ref, sign_key: &X509Ref) -> Result<()> { + // verify that the hkd is still valid + check_validity_period(hkd.not_before(), hkd.not_after())?; + +- // check if hkd.issuer_name == issuer.subject +- check_x509_name_equal(sign_key.subject_name(), hkd.issuer_name())?; +- + // verify that the AKID of the hkd matches the SKID of the issuer + if let Some(akid) = hkd.akid() { + if akid.check(sign_key) != AkidCheckResult::OK { +@@ -78,9 +75,6 @@ pub fn verify_crl(crl: &X509CrlRef, issuer: &X509Ref) -> Option<()> { + return None; + } + } +- +- check_x509_name_equal(crl.issuer_name(), issuer.subject_name()).ok()?; +- + match crl.verify(issuer.public_key().ok()?.as_ref()).ok()? { + true => Some(()), + false => None, +@@ -210,7 +204,8 @@ pub fn download_crls_into_store(store: &mut X509StoreBuilderRef, crts: &[X509]) + //Asn1StringRef::as_slice aka ASN1_STRING_get0_data gives a string without \0 delimiter + const IBM_Z_COMMON_NAME: &[u8; 43usize] = b"International Business Machines Corporation"; + const IBM_Z_COUNTRY_NAME: &[u8; 2usize] = b"US"; +-const IBM_Z_LOCALITY_NAME: &[u8; 12usize] = b"Poughkeepsie"; ++const IBM_Z_LOCALITY_NAME_POUGHKEEPSIE: &[u8; 12usize] = b"Poughkeepsie"; ++const IBM_Z_LOCALITY_NAME_ARMONK: &[u8; 6usize] = b"Armonk"; + const IBM_Z_ORGANIZATIONAL_UNIT_NAME_SUFFIX: &str = "Key Signing Service"; + const IBM_Z_ORGANIZATION_NAME: &[u8; 43usize] = b"International Business Machines Corporation"; + const IBM_Z_STATE: &[u8; 8usize] = b"New York"; +@@ -229,7 +224,8 @@ fn is_ibm_signing_cert(cert: &X509) -> bool { + if subj.entries().count() != IMB_Z_ENTRY_COUNT + || !name_data_eq(subj, Nid::COUNTRYNAME, IBM_Z_COUNTRY_NAME) + || !name_data_eq(subj, Nid::STATEORPROVINCENAME, IBM_Z_STATE) +- || !name_data_eq(subj, Nid::LOCALITYNAME, IBM_Z_LOCALITY_NAME) ++ || !(name_data_eq(subj, Nid::LOCALITYNAME, IBM_Z_LOCALITY_NAME_POUGHKEEPSIE) ++ || name_data_eq(subj, Nid::LOCALITYNAME, IBM_Z_LOCALITY_NAME_ARMONK)) + || !name_data_eq(subj, Nid::ORGANIZATIONNAME, IBM_Z_ORGANIZATION_NAME) + || !name_data_eq(subj, Nid::COMMONNAME, IBM_Z_COMMON_NAME) + { +@@ -370,23 +366,6 @@ fn check_validity_period(not_before: &Asn1TimeRef, not_after: &Asn1TimeRef) -> R + } + } + +-fn check_x509_name_equal(lhs: &X509NameRef, rhs: &X509NameRef) -> Result<()> { +- if lhs.entries().count() != rhs.entries().count() { +- bail_hkd_verify!(IssuerMismatch); +- } +- +- for l in lhs.entries() { +- let ldata = l.data().as_slice(); +- +- // search for the matching value in the rhs names +- // found none? -> names are not equal +- if !rhs.entries().any(|r| memeq(ldata, r.data().as_slice())) { +- bail_hkd_verify!(IssuerMismatch); +- } +- } +- Ok(()) +-} +- + const NIDS_CORRECT_ORDER: [Nid; 6] = [ + Nid::COUNTRYNAME, + Nid::ORGANIZATIONNAME, +@@ -409,13 +388,28 @@ pub fn reorder_x509_names(subject: &X509NameRef) -> std::result::Result( +- e: ErrorStack, +-) -> std::result::Result, openssl::error::ErrorStack> { +- match e.errors().len() { +- 0 => Stack::::new(), +- _ => Err(e), ++/** ++* Workaround for potential locality mismatches between CRLs and Certs ++* # Return ++* fixed subject or none if locality was not Armonk or any OpenSSL error ++*/ ++pub fn armonk_locality_fixup(subject: &X509NameRef) -> Option { ++ if !name_data_eq(subject, Nid::LOCALITYNAME, IBM_Z_LOCALITY_NAME_ARMONK) { ++ return None; + } ++ ++ let mut ret = X509Name::builder().ok()?; ++ for entry in subject.entries() { ++ match entry.object().nid() { ++ nid @ Nid::LOCALITYNAME => ret ++ .append_entry_by_nid(nid, from_utf8(IBM_Z_LOCALITY_NAME_POUGHKEEPSIE).ok()?) ++ .ok()?, ++ _ => { ++ ret.append_entry(entry).ok()?; ++ } ++ } ++ } ++ Some(ret.build()) + } + + #[cfg(test)] +@@ -453,20 +447,6 @@ mod test { + )); + } + +- #[test] +- fn x509_name_equal() { +- let sign_crt = load_gen_cert("ibm.crt"); +- let hkd = load_gen_cert("host.crt"); +- let other = load_gen_cert("inter_ca.crt"); +- +- assert!(super::check_x509_name_equal(sign_crt.subject_name(), hkd.issuer_name()).is_ok(),); +- +- assert!(matches!( +- super::check_x509_name_equal(other.subject_name(), hkd.subject_name()), +- Err(Error::HkdVerify(IssuerMismatch)) +- )); +- } +- + #[test] + fn is_ibm_z_sign_key() { + let ibm_crt = load_gen_cert("ibm.crt"); +diff --git a/rust/pv/src/verify/test.rs b/rust/pv/src/verify/test.rs +index 5ca2e712..e4c60c57 100644 +--- a/rust/pv/src/verify/test.rs ++++ b/rust/pv/src/verify/test.rs +@@ -99,7 +99,6 @@ fn verify_online() { + let inter_crt = get_cert_asset_path_string("inter_ca.crt"); + let ibm_crt = get_cert_asset_path_string("ibm.crt"); + let hkd_revoked = load_gen_cert("host_rev.crt"); +- let hkd_inv = load_gen_cert("host_invalid_signing_key.crt"); + let hkd_exp = load_gen_cert("host_crt_expired.crt"); + let hkd = load_gen_cert("host.crt"); + +@@ -126,11 +125,6 @@ fn verify_online() { + Err(Error::HkdVerify(HdkRevoked)) + )); + +- assert!(matches!( +- verifier.verify(&hkd_inv), +- Err(Error::HkdVerify(IssuerMismatch)) +- )); +- + assert!(matches!( + verifier.verify(&hkd_exp), + Err(Error::HkdVerify(AfterValidity)) +@@ -145,7 +139,6 @@ fn verify_offline() { + let ibm_crt = get_cert_asset_path_string("ibm.crt"); + let ibm_crl = get_cert_asset_path_string("ibm.crl"); + let hkd_revoked = load_gen_cert("host_rev.crt"); +- let hkd_inv = load_gen_cert("host_invalid_signing_key.crt"); + let hkd_exp = load_gen_cert("host_crt_expired.crt"); + let hkd = load_gen_cert("host.crt"); + +@@ -163,11 +156,6 @@ fn verify_offline() { + Err(Error::HkdVerify(HdkRevoked)) + )); + +- assert!(matches!( +- verifier.verify(&hkd_inv), +- Err(Error::HkdVerify(IssuerMismatch)) +- )); +- + assert!(matches!( + verifier.verify(&hkd_exp), + Err(Error::HkdVerify(AfterValidity)) +-- +2.53.0 + + +From e1423607a66ee37f8ae581fbf5fa013f5ab80ae8 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Thu, 14 Mar 2024 16:05:09 +0000 +Subject: [PATCH 16/22] genprotimg: support `Armonk` in IBM signing key subject + (RHEL-30398) + +New IBM signing certificates will have 'Armonk' as locality in the +subject. Make sure that certificate revocations lists (CRL) with +'Poughkeepsie' as issuer locality are still considered as valid as long +as they are signed with the IBM signing keys private key. In addition, +drop the check for 'issuer(HKD) == subject(HKSK)' as it doesn't improve +security. While at it, remove now unused functions and fix a memory leak +of @akid in `check_crl_issuer`. + +Reviewed-by: Christoph Schlameuss +Signed-off-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +(cherry picked from commit d14e7593cc6380911ca42b09e11c53477ae13d5c) +--- + genprotimg/src/include/pv_crypto_def.h | 3 +- + genprotimg/src/utils/crypto.c | 210 ++++++++++++------------- + genprotimg/src/utils/crypto.h | 1 + + 3 files changed, 104 insertions(+), 110 deletions(-) + +diff --git a/genprotimg/src/include/pv_crypto_def.h b/genprotimg/src/include/pv_crypto_def.h +index 3635433c..49710dc1 100644 +--- a/genprotimg/src/include/pv_crypto_def.h ++++ b/genprotimg/src/include/pv_crypto_def.h +@@ -17,7 +17,8 @@ + /* 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_LOCALITY_NAME_POUGHKEEPSIE "Poughkeepsie" ++#define PV_IBM_Z_SUBJECT_LOCALITY_NAME_ARMONK "Armonk" + #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" +diff --git a/genprotimg/src/utils/crypto.c b/genprotimg/src/utils/crypto.c +index e3bbf1b2..86565b99 100644 +--- a/genprotimg/src/utils/crypto.c ++++ b/genprotimg/src/utils/crypto.c +@@ -664,62 +664,9 @@ static gboolean x509_name_data_by_nid_equal(X509_NAME *name, gint nid, + 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 = ++ * Poughkeepsie or Armonk, O = International Business Machines Corporation, CN = + * International Business Machines Corporation' and the organization unit (OUT) + * must end with the suffix ' Key Signing Service'. + */ +@@ -743,8 +690,10 @@ static gboolean has_ibm_signing_subject(X509 *cert) + PV_IBM_Z_SUBJECT_STATE)) + return FALSE; + +- if (!x509_name_data_by_nid_equal(subject, NID_localityName, +- PV_IBM_Z_SUBJECT_LOCALITY_NAME)) ++ if (!(x509_name_data_by_nid_equal(subject, NID_localityName, ++ PV_IBM_Z_SUBJECT_LOCALITY_NAME_POUGHKEEPSIE) || ++ x509_name_data_by_nid_equal(subject, NID_localityName, ++ PV_IBM_Z_SUBJECT_LOCALITY_NAME_ARMONK))) + return FALSE; + + if (!x509_name_data_by_nid_equal(subject, NID_organizationName, +@@ -806,6 +755,39 @@ static X509_NAME *x509_name_reorder_attributes(const X509_NAME *name, const gint + return g_steal_pointer(&ret); + } + ++/** Replace locality 'Armonk' with 'Pougkeepsie'. If Armonk was not set return ++ * `NULL`. ++ */ ++static X509_NAME *x509_armonk_locality_fixup(const X509_NAME *name) ++{ ++ g_autoptr(X509_NAME) ret = NULL; ++ int pos; ++ ++ /* Check if ``L=Armonk`` */ ++ if (!x509_name_data_by_nid_equal((X509_NAME *)name, NID_localityName, ++ PV_IBM_Z_SUBJECT_LOCALITY_NAME_ARMONK)) ++ return NULL; ++ ++ ret = X509_NAME_dup(name); ++ if (!ret) ++ g_abort(); ++ ++ pos = X509_NAME_get_index_by_NID(ret, NID_localityName, -1); ++ if (pos == -1) ++ return NULL; ++ ++ X509_NAME_ENTRY_free(X509_NAME_delete_entry(ret, pos)); ++ ++ /* Create a new name entry at the same position as before */ ++ if (X509_NAME_add_entry_by_NID( ++ ret, NID_localityName, MBSTRING_UTF8, ++ (const unsigned char *)&PV_IBM_Z_SUBJECT_LOCALITY_NAME_POUGHKEEPSIE, ++ sizeof(PV_IBM_Z_SUBJECT_LOCALITY_NAME_POUGHKEEPSIE) - 1, pos, 0) != 1) ++ return NULL; ++ ++ return g_steal_pointer(&ret); ++} ++ + /* 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). +@@ -828,24 +810,10 @@ X509_NAME *c2b_name(const X509_NAME *name) + return X509_NAME_dup((X509_NAME *)name); + } + +-/* Verify that: subject(issuer) == issuer(crl) and SKID(issuer) == AKID(crl) */ ++/* Verify that SKID(issuer) == AKID(crl) if available */ + 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; +- } ++ g_autoptr(AUTHORITY_KEYID) akid = NULL; + + /* If AKID(@crl) is specified it must match with SKID(@issuer) */ + akid = X509_CRL_get_ext_d2i(crl, NID_authority_key_identifier, NULL, NULL); +@@ -881,7 +849,6 @@ gint check_crl_valid_for_cert(X509_CRL *crl, X509 *cert, + return -1; + } + +- /* check that the @crl issuer matches with the subject name of @cert*/ + if (check_crl_issuer(crl, cert, err) < 0) + return -1; + +@@ -910,6 +877,60 @@ gint check_crl_valid_for_cert(X509_CRL *crl, X509 *cert, + return 0; + } + ++/* This function contains work-arounds for some known subject(CRT)<->issuer(CRL) ++ * issues. ++ */ ++static STACK_OF_X509_CRL *quirk_X509_STORE_ctx_get1_crls(X509_STORE_CTX *ctx, ++ const X509_NAME *subject, GError **err) ++{ ++ g_autoptr(X509_NAME) fixed_subject = NULL; ++ g_autoptr(STACK_OF_X509_CRL) ret = NULL; ++ ++ ret = Pv_X509_STORE_CTX_get1_crls(ctx, subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ ++ /* Workaround to fix the mismatch between issuer name of the * IBM ++ * signing CRLs and the IBM signing key subject name. Locality name has ++ * changed from Poughkeepsie to Armonk. ++ */ ++ fixed_subject = x509_armonk_locality_fixup(subject); ++ /* Was the locality replaced? */ ++ if (fixed_subject) { ++ X509_NAME *tmp; ++ ++ sk_X509_CRL_free(ret); ++ ret = Pv_X509_STORE_CTX_get1_crls(ctx, fixed_subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ ++ /* Workaround to fix the ordering mismatch between issuer name ++ * of the IBM signing CRLs and the IBM signing key subject name. ++ */ ++ tmp = fixed_subject; ++ fixed_subject = c2b_name(fixed_subject); ++ X509_NAME_free(tmp); ++ sk_X509_CRL_free(ret); ++ ret = Pv_X509_STORE_CTX_get1_crls(ctx, fixed_subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ X509_NAME_free(fixed_subject); ++ fixed_subject = NULL; ++ } ++ ++ /* Workaround to fix the ordering mismatch between issuer name of the ++ * IBM signing CRLs and the IBM signing key subject name. ++ */ ++ fixed_subject = c2b_name(subject); ++ sk_X509_CRL_free(ret); ++ ret = Pv_X509_STORE_CTX_get1_crls(ctx, fixed_subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ ++ g_set_error(err, PV_CRYPTO_ERROR, PV_CRYPTO_ERROR_NO_CRL, _("no CRL found")); ++ return NULL; ++} ++ + /* Given a certificate @cert try to find valid revocation lists in @ctx. If no + * valid CRL was found NULL is returned. + */ +@@ -927,20 +948,9 @@ STACK_OF_X509_CRL *store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, + return NULL; + } + +- 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; +- } +- } ++ ret = quirk_X509_STORE_ctx_get1_crls(ctx, subject, err); ++ if (!ret) ++ return NULL; + + /* Filter out non-valid CRLs for @cert */ + for (gint i = 0; i < sk_X509_CRL_num(ret); i++) { +@@ -1328,32 +1338,14 @@ gint check_chain_parameters(const STACK_OF_X509 *chain, + + /* 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 ++ * 1. Check whether the akid(cert) (if available) matches the issuer skid ++ * 2. Check that the cert algrithm matches the subject algorithm ++ * 3. 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; +- } ++ g_autoptr(AUTHORITY_KEYID) akid = NULL; + + akid = X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); + if (akid && X509_check_akid(issuer, akid) != X509_V_OK) { +diff --git a/genprotimg/src/utils/crypto.h b/genprotimg/src/utils/crypto.h +index fdf66de2..e45e57df 100644 +--- a/genprotimg/src/utils/crypto.h ++++ b/genprotimg/src/utils/crypto.h +@@ -75,6 +75,7 @@ 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(AUTHORITY_KEYID, AUTHORITY_KEYID_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) +-- +2.53.0 + + +From 1605e9c0033e245f8a6690e2ce95a27e383722df Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 12 Mar 2024 10:14:43 +0100 +Subject: [PATCH 17/22] libpv: Support `Armonk` in IBM signing key subject + (RHEL-30398) + +New IBM signing keys will have Armonk as locality in the subject. +Ensure that CRLs with Poughkeepsie as issuer locality are still +discovered if they are signed with the signing keys private key. +Also, drop the check for issuer/subject comparison and only rely on +validity period and cryptographic signatures. + +Reviewed-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +(cherry picked from commit d7c95265cdb6217b0203efa5893c3a27838af63c) +--- + include/libpv/cert.h | 3 +- + libpv/cert.c | 148 +++++++++++++++++++++++++++++-------------- + 2 files changed, 102 insertions(+), 49 deletions(-) + +diff --git a/include/libpv/cert.h b/include/libpv/cert.h +index bceb3c65..aebe33ba 100644 +--- a/include/libpv/cert.h ++++ b/include/libpv/cert.h +@@ -16,7 +16,8 @@ + + #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_LOCALITY_NAME_POUGHKEEPSIE "Poughkeepsie" ++#define PV_IBM_Z_SUBJECT_LOCALITY_NAME_ARMONK "Armonk" + #define PV_IBM_Z_SUBJECT_ORGANIZATIONAL_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" +diff --git a/libpv/cert.c b/libpv/cert.c +index c8bb8cc3..f4774fcb 100644 +--- a/libpv/cert.c ++++ b/libpv/cert.c +@@ -857,7 +857,7 @@ static gboolean x509_name_data_by_nid_equal(X509_NAME *name, int nid, const char + + /* 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 = ++ * Poughkeepsie or Armonk, O = International Business Machines Corporation, CN = + * International Business Machines Corporation' and the organization unit (OUT) + * must end with the suffix ' Key Signing Service'. + */ +@@ -879,7 +879,10 @@ static gboolean has_ibm_signing_subject(X509 *cert) + 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)) ++ if (!(x509_name_data_by_nid_equal(subject, NID_localityName, ++ PV_IBM_Z_SUBJECT_LOCALITY_NAME_POUGHKEEPSIE) || ++ x509_name_data_by_nid_equal(subject, NID_localityName, ++ PV_IBM_Z_SUBJECT_LOCALITY_NAME_ARMONK))) + return FALSE; + + if (!x509_name_data_by_nid_equal(subject, NID_organizationName, +@@ -1085,10 +1088,9 @@ static int check_signature_algo_match(const EVP_PKEY *pkey, const X509 *subject, + + /* 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 ++ * 1. Check whether the akid(cert) (if available) matches the issuer skid ++ * 2. Check that the cert algrithm matches the subject algorithm ++ * 3. Verify the signature of certificate @cert is using the public key of + * @issuer. + */ + static int check_host_key_issued(X509 *cert, X509 *issuer, GError **error) +@@ -1097,19 +1099,6 @@ static int check_host_key_issued(X509 *cert, X509 *issuer, GError **error) + const X509_NAME *cert_issuer = X509_get_issuer_name(cert); + g_autoptr(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 = pv_X509_NAME_oneline(issuer_subject); +- g_autofree char *cert_issuer_str = pv_X509_NAME_oneline(cert_issuer); +- +- g_set_error(error, PV_CERT_ERROR, PV_CERT_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(error, PV_CERT_ERROR, PV_CERT_ERROR_SKID_AKID_MISMATCH, +@@ -1286,21 +1275,10 @@ int pv_verify_cert(X509_STORE_CTX *ctx, X509 *cert, GError **error) + return 0; + } + +-/* Verify that: subject(issuer) == issuer(crl) and SKID(issuer) == AKID(crl) */ ++/* Verify that SKID(issuer) == AKID(crl) */ + static int check_crl_issuer(X509_CRL *crl, X509 *issuer, GError **error) + { +- 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 = pv_X509_NAME_oneline(issuer_subject); +- g_autofree char *crl_issuer_str = pv_X509_NAME_oneline(crl_issuer); +- +- g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_CRL_SUBJECT_ISSUER_MISMATCH, +- _("issuer mismatch:\n%s\n%s"), issuer_subject_str, crl_issuer_str); +- return -1; +- } ++ g_autoptr(AUTHORITY_KEYID) akid = NULL; + + /* If AKID(@crl) is specified it must match with SKID(@issuer) */ + akid = X509_CRL_get_ext_d2i(crl, NID_authority_key_identifier, NULL, NULL); +@@ -1325,7 +1303,6 @@ int pv_verify_crl(X509_CRL *crl, X509 *cert, int verify_flags, GError **error) + return -1; + } + +- /* check that the @crl issuer matches with the subject name of @cert*/ + if (check_crl_issuer(crl, cert, error) < 0) + return -1; + +@@ -1393,6 +1370,93 @@ int pv_check_chain_parameters(const STACK_OF_X509 *chain, GError **error) + return 0; + } + ++/** Replace locality 'Armonk' with 'Pougkeepsie'. If Armonk was not set return ++ * `NULL`. ++ */ ++static X509_NAME *x509_armonk_locality_fixup(const X509_NAME *name) ++{ ++ g_autoptr(X509_NAME) ret = NULL; ++ int pos; ++ ++ /* Check if ``L=Armonk`` */ ++ if (!x509_name_data_by_nid_equal((X509_NAME *)name, NID_localityName, ++ PV_IBM_Z_SUBJECT_LOCALITY_NAME_ARMONK)) ++ return NULL; ++ ++ ret = X509_NAME_dup(name); ++ if (!ret) ++ g_abort(); ++ ++ pos = X509_NAME_get_index_by_NID(ret, NID_localityName, -1); ++ if (pos == -1) ++ return NULL; ++ ++ X509_NAME_ENTRY_free(X509_NAME_delete_entry(ret, pos)); ++ ++ /* Create a new name entry at the same position as before */ ++ if (X509_NAME_add_entry_by_NID( ++ ret, NID_localityName, MBSTRING_UTF8, ++ (const unsigned char *)&PV_IBM_Z_SUBJECT_LOCALITY_NAME_POUGHKEEPSIE, ++ sizeof(PV_IBM_Z_SUBJECT_LOCALITY_NAME_POUGHKEEPSIE) - 1, pos, 0) != 1) ++ return NULL; ++ ++ return g_steal_pointer(&ret); ++} ++ ++/* This function contains work-arounds for some known subject(CRT)<->issuer(CRL) ++ * issues. ++ */ ++static STACK_OF_X509_CRL *quirk_X509_STORE_ctx_get1_crls(X509_STORE_CTX *ctx, ++ const X509_NAME *subject, GError **err) ++{ ++ g_autoptr(X509_NAME) fixed_subject = NULL; ++ g_autoptr(STACK_OF_X509_CRL) ret = NULL; ++ ++ ret = pv_X509_STORE_CTX_get1_crls(ctx, subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ ++ /* Workaround to fix the mismatch between issuer name of the * IBM ++ * signing CRLs and the IBM signing key subject name. Locality name has ++ * changed from Poughkeepsie to Armonk. ++ */ ++ fixed_subject = x509_armonk_locality_fixup(subject); ++ /* Was the locality replaced? */ ++ if (fixed_subject) { ++ X509_NAME *tmp; ++ ++ sk_X509_CRL_free(ret); ++ ret = pv_X509_STORE_CTX_get1_crls(ctx, fixed_subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ ++ /* Workaround to fix the ordering mismatch between issuer name ++ * of the IBM signing CRLs and the IBM signing key subject name. ++ */ ++ tmp = fixed_subject; ++ fixed_subject = pv_c2b_name(fixed_subject); ++ X509_NAME_free(tmp); ++ sk_X509_CRL_free(ret); ++ ret = pv_X509_STORE_CTX_get1_crls(ctx, fixed_subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ X509_NAME_free(fixed_subject); ++ fixed_subject = NULL; ++ } ++ ++ /* Workaround to fix the ordering mismatch between issuer name of the ++ * IBM signing CRLs and the IBM signing key subject name. ++ */ ++ fixed_subject = pv_c2b_name(subject); ++ sk_X509_CRL_free(ret); ++ ret = pv_X509_STORE_CTX_get1_crls(ctx, fixed_subject); ++ if (ret && sk_X509_CRL_num(ret) > 0) ++ return g_steal_pointer(&ret); ++ ++ g_set_error(err, PV_CERT_ERROR, PV_CERT_ERROR_NO_CRL, _("no CRL found")); ++ return NULL; ++} ++ + /* Given a certificate @cert try to find valid revocation lists in @ctx. If no + * valid CRL was found NULL is returned. + */ +@@ -1412,21 +1476,9 @@ STACK_OF_X509_CRL *pv_store_ctx_find_valid_crls(X509_STORE_CTX *ctx, X509 *cert, + return NULL; + } + +- ret = pv_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 = pv_c2b_name(subject); +- +- ret = pv_X509_STORE_CTX_get1_crls(ctx, broken_subject); +- if (!ret) { +- g_set_error(error, PV_CERT_ERROR, PV_CERT_ERROR_NO_CRL, _("no CRL found")); +- g_info("ERROR: %s", (*error)->message); +- return NULL; +- } +- } +- ++ ret = quirk_X509_STORE_ctx_get1_crls(ctx, subject, error); ++ if (!ret) ++ return NULL; + /* Filter out non-valid CRLs for @cert */ + for (int i = 0; i < sk_X509_CRL_num(ret); i++) { + X509_CRL *crl = sk_X509_CRL_value(ret, i); +-- +2.53.0 + + +From 3bd5cce64692d4b630b313cf465a55595971bed4 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 20 Mar 2024 15:36:52 +0100 +Subject: [PATCH 18/22] pvattest: Fix root-ca parsing (RHEL-30398) + +The parser setup falsely set the argument type as filename array, but +code expected a single filename. Fixed by setting up the parser +correctly to expect a single file name. + +Fixes: 3ab06d77fb1b ("pvattest: Create, perform, and verify attestation measurements") +Reviewed-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +(cherry picked from commit 2b5e7b049123aff094c7de79ba57a5df09471b2e) +--- + pvattest/src/argparse.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/pvattest/src/argparse.c b/pvattest/src/argparse.c +index fe5662f9..5924ddc8 100644 +--- a/pvattest/src/argparse.c ++++ b/pvattest/src/argparse.c +@@ -192,13 +192,13 @@ static gboolean hex_str_toull(const char *nptr, uint64_t *dst, GError **error) + } + + /* NOTE REQUIRED */ +-#define _entry_root_ca(__arg_data, __indent) \ +- { \ +- .long_name = "root-ca", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ +- .arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = __arg_data, \ +- .description = "Use FILE as the trusted root CA instead the\n" __indent \ +- "root CAs that are installed on the system (optional).\n", \ +- .arg_description = "FILE", \ ++#define _entry_root_ca(__arg_data, __indent) \ ++ { \ ++ .long_name = "root-ca", .short_name = 0, .flags = G_OPTION_FLAG_NONE, \ ++ .arg = G_OPTION_ARG_FILENAME, .arg_data = __arg_data, \ ++ .description = "Use FILE as the trusted root CA instead the\n" __indent \ ++ "root CAs that are installed on the system (optional).\n", \ ++ .arg_description = "FILE", \ + } + + /* NOTE REQUIRED */ +-- +2.53.0 + + +From a1b83b44def87915acaf0d9668bdf96824d0e781 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 3 Apr 2025 11:26:47 +0200 +Subject: [PATCH 19/22] zkey: Adjust CCA host library version detection for + newer CCA versions (RHEL-105487) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Newer CCA versions might report the version string with CSUACFV or CSUACFQ +with keyword STATCCA using a different indicator character after the +version information. Ignore the indication character and the remaining +data entirely. Only the version information as such is of interest. + +Signed-off-by: Ingo Franzki +Signed-off-by: Jan Höppner +(cherry picked from commit 33fc534a81b7c163936064c96f1fb902255f7ff1) +--- + zkey/cca.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/zkey/cca.c b/zkey/cca.c +index 71c08ad1..9d79c4b3 100644 +--- a/zkey/cca.c ++++ b/zkey/cca.c +@@ -91,7 +91,6 @@ static int get_cca_version(struct cca_lib *cca, bool verbose) + long return_code, reason_code; + long version_data_length; + long exit_data_len = 0; +- char date[20]; + + util_assert(cca != NULL, "Internal error: cca is NULL"); + +@@ -111,8 +110,8 @@ static int get_cca_version(struct cca_lib *cca, bool verbose) + version_data[sizeof(version_data) - 1] = '\0'; + pr_verbose(verbose, "CCA Version string: %s", version_data); + +- if (sscanf((char *)version_data, "%u.%u.%uz%s", &cca->version.ver, +- &cca->version.rel, &cca->version.mod, date) != 4) { ++ if (sscanf((char *)version_data, "%u.%u.%u", &cca->version.ver, ++ &cca->version.rel, &cca->version.mod) != 3) { + warnx("CCA library version is invalid: %s", version_data); + return -EINVAL; + } +@@ -536,7 +535,7 @@ static int get_cca_adapter_version(struct cca_lib *cca, + + pr_verbose(verbose, "CCA firmware version string: %s", version_data); + +- if (sscanf((char *)version_data, "%u.%u.%uz", &version->ver, ++ if (sscanf((char *)version_data, "%u.%u.%u", &version->ver, + &version->rel, &version->mod) != 3) { + warnx("CCA formware version is invalid: %s", version_data); + return -EINVAL; +-- +2.53.0 + + +From 8b98ad7e7e273b2327ad50bdf31935d4a439a618 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 11 Sep 2025 08:37:38 +0200 +Subject: [PATCH 20/22] libekmfweb: Fix generation of certificate or CSR with + RSA-PSS instead of RSA (RHEL-114130) + +Currently a certificate or certificate signing request generated by the +zkey EKMFWeb library erroneously always uses RSA-PSS as signing algorithm, +although EKMFWeb does not support RSA-PSS certificates in all versions. + +This bug was introduced with the rework to use libseckey for secure +key crypto operations. + +Fixes: 26c34a49b1e4 ("libekmfweb: Make use of the new libseckey") +Signed-off-by: Ingo Franzki +Reviewed-by: Finn Callies +Signed-off-by: Steffen Eiden +(cherry picked from commit e4dcf084c5a54f8030da39707c5fa0fbb7ae9681) +--- + libekmfweb/ekmfweb.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/libekmfweb/ekmfweb.c b/libekmfweb/ekmfweb.c +index ee25fc08..3a822e78 100644 +--- a/libekmfweb/ekmfweb.c ++++ b/libekmfweb/ekmfweb.c +@@ -5099,14 +5099,15 @@ int ekmf_generate_csr(const struct ekmf_config *config, + } + } + +- +- _ekmf_copy_pss_params(rsa_pss_params, &pss_params); ++ if (rsa_pss_params != NULL) ++ _ekmf_copy_pss_params(rsa_pss_params, &pss_params); + + rc = SK_OPENSSL_generate_csr(key_blob, key_blob_size, + subject_rdns, num_subject_rdns, + subject_utf8, cert, + extensions, num_extensions, +- digest_nid, &pss_params, &req, ++ digest_nid, rsa_pss_params != NULL ? ++ &pss_params : NULL, &req, + &ext_lib_info.ext_lib, verbose); + if (rc != 0) { + pr_verbose(verbose, "SK_OPENSSL_generate_csr failed " +@@ -5245,14 +5246,16 @@ int ekmf_generate_ss_cert(const struct ekmf_config *config, + } + } + +- _ekmf_copy_pss_params(rsa_pss_params, &pss_params); ++ if (rsa_pss_params != NULL) ++ _ekmf_copy_pss_params(rsa_pss_params, &pss_params); + + rc = SK_OPENSSL_generate_ss_cert(key_blob, key_blob_size, + subject_rdns, num_subject_rdns, + subject_utf8, rcert, + extensions, num_extensions, + validity_days, digest_nid, +- &pss_params, &cert, ++ rsa_pss_params != NULL ? ++ &pss_params : NULL, &cert, + &ext_lib_info.ext_lib, verbose); + if (rc != 0) { + pr_verbose(verbose, "SK_OPENSSL_generate_ss_cert failed " +-- +2.53.0 + + +From ad8dcda916c7c7ec4294fff11e1858e3cbd9ec2f Mon Sep 17 00:00:00 2001 +From: Eduard Shishkin +Date: Mon, 19 Feb 2024 17:25:04 +0100 +Subject: [PATCH 21/22] dasdfmt: Change mode default (RHEL-121638) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When formatting an ESE (thin-provisioned) ECKD DASD, dasdfmt(8) +defaults to the quick-format mode instead of full-format for normal +DASDs. This results in a significant performance impact during first +sequential write to each track, which may be unexpected for users. + +To address this, change the default for dasdfmt to always use +full-format mode. Customers that require thin provisioning(*) still +override the default by specifying quick format explicitly using the +"-M" option. + +Get rid of the related fallbacks; In case of unsuccessful space +release always fail. The customers that still require quick format can +proceed by specifying "--no-discard" option. + +(*) Thin provisioning: while providing a large amount of logical + space, zero amount of actual space is provisioned and then + allocated on an on-demand basis. + +Signed-off-by: Eduard Shishkin +Reviewed-by: Jan Höppner +Signed-off-by: Jan Höppner +(cherry picked from commit 998b61e5f3c9285cc84759a4347102d2ee6a0ff6) +--- + dasdfmt/dasdfmt.8 | 18 +++++++++--------- + dasdfmt/dasdfmt.c | 25 ++++++------------------- + 2 files changed, 15 insertions(+), 28 deletions(-) + +diff --git a/dasdfmt/dasdfmt.8 b/dasdfmt/dasdfmt.8 +index bef5c097..b531e155 100644 +--- a/dasdfmt/dasdfmt.8 ++++ b/dasdfmt/dasdfmt.8 +@@ -118,16 +118,16 @@ Specify the \fImode\fR to be used to format the device. Valid modes are: + .IP full + Format the entire disk with the specified blocksize. (default) + .IP quick +-Format the first two tracks and write label and partition information. Use this +-option only if you are sure that the target DASD already contains a regular +-format with the specified blocksize. A blocksize can optionally be specified +-using \fB-b\fR (\fB--blocksize\fR). ++Format the first two tracks and write label and partition information. ++.br ++Use this option for DASD ESE volumes to take the benefits of thin provisioning. ++In this case, a full space release precedes the formatting step. If this space ++release fails, then the formatting also fails. Specify the \fB--no-discard\fR ++option to omit the space release. + .br +-For thin-provisioned DASD ESE volumes, quick is the default mode. A full space +-release then precedes the formatting step. If this space release fails, dasdfmt +-falls back to a full-format mode. Formatting stops if the space release fails +-and quick mode was specified explicitly using \fB-M\fR. Specify the +-\fB--no-discard\fR option to omit the space release. ++For non-ESE volumes use this option only if you are sure that the target DASD ++already contains a regular format with the specified blocksize. A blocksize can ++optionally be specified using \fB-b\fR (\fB--blocksize\fR). + + .IP expand + Format all unformatted tracks at the end of the target DASD. This mode assumes +diff --git a/dasdfmt/dasdfmt.c b/dasdfmt/dasdfmt.c +index 2065e12e..a49e41c0 100644 +--- a/dasdfmt/dasdfmt.c ++++ b/dasdfmt/dasdfmt.c +@@ -1230,7 +1230,7 @@ static void dasdfmt_find_start(unsigned int cylinders, unsigned int heads, + format_params->start_unit = first; + } + +-static int dasdfmt_release_space(void) ++static void dasdfmt_release_space(void) + { + format_data_t r = { + .start_unit = 0, +@@ -1240,21 +1240,12 @@ static int dasdfmt_release_space(void) + int err = 0; + + if (!g.ese || g.no_discard) +- return 0; ++ return; + + printf("Releasing space for the entire device...\n"); + err = dasd_release_space(g.dev_node, &r); +- /* +- * Warn or Error on failing RAS depending on QUICK mode set explicitly or automatically +- */ +- if (err && !g.mode_specified) { +- warnx("Could not release space. Falling back to full format."); +- return 1; +- } else if (err && g.mode_specified) { ++ if (err) + error("Could not release space: %s", strerror(err)); +- } +- +- return 0; + } + + static void dasdfmt_prepare_and_format(unsigned int cylinders, unsigned int heads, +@@ -1454,12 +1445,8 @@ static void do_format_dasd(volume_label_t *vlabel, format_data_t *p, + dasdfmt_prepare_and_format(cylinders, heads, p); + break; + case QUICK: +- if (dasdfmt_release_space()) { +- p->stop_unit = (cylinders * heads) - 1; +- dasdfmt_prepare_and_format(cylinders, heads, p); +- } else { +- dasdfmt_quick_format(cylinders, heads, p); +- } ++ dasdfmt_release_space(); ++ dasdfmt_quick_format(cylinders, heads, p); + break; + case EXPAND: + dasdfmt_expand_format(cylinders, heads, p); +@@ -1491,7 +1478,7 @@ static void eval_format_mode(void) + } + + if (!g.mode_specified) +- mode = g.ese ? QUICK : FULL; ++ mode = FULL; + } + + /* +-- +2.53.0 + + +From e75eaad672b9352f1ceb0e90e525b7c988ed55ea Mon Sep 17 00:00:00 2001 +From: Aswin Karuvally +Date: Sun, 1 Feb 2026 18:39:38 +0530 +Subject: [PATCH 22/22] hsci: Automatically set appropriate MTU for HSCI + (RHEL-153548) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +At present, the hsci tool creates HiperSockets Converged Interface +(HSCI) with MTU of 1500 bytes regardless of the values of MTU for the +external and HiperSockets interface from which it is formed. This can +degrade performance or worse, affect network connectivity through the +converged interface. + +Update the tool to automatically set MTU of HSCI as the lower of the +MTU values of external and HiperSockets interface. + +Reviewed-by: Alexandra Winter +Signed-off-by: Aswin Karuvally +Signed-off-by: Jan Höppner +(cherry picked from commit 11f2ccb9570569a57ac0ef0148efcd9b35d7a6b6) +--- + hsci/hsci | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/hsci/hsci b/hsci/hsci +index 5540e82f..8f884c9a 100755 +--- a/hsci/hsci ++++ b/hsci/hsci +@@ -365,6 +365,13 @@ function add_hsci { + fi + fi + ++ #### Set MTU for the veth pair ++ ndev_mtu="$(cat /sys/class/net/${ndev}/mtu)" ++ hsdev_mtu="$(cat /sys/class/net/${hsdev}/mtu)" ++ hsci_mtu=$(( ndev_mtu < hsdev_mtu ? ndev_mtu : hsdev_mtu )) ++ ip link set dev $hscibp mtu $hsci_mtu ++ ip link set dev $hsci mtu $hsci_mtu ++ + #### Set veth pair to UP + ip link set dev $hscibp up >/dev/null 2>&1 + if [ $? -ne 0 ]; then +-- +2.53.0 + diff --git a/SPECS/s390utils.spec b/SPECS/s390utils.spec index fab6afe..6286587 100644 --- a/SPECS/s390utils.spec +++ b/SPECS/s390utils.spec @@ -3,23 +3,32 @@ # secure boot support is for RHEL only %if 0%{?rhel} >= 8 -%global signzipl 1 +%bcond_without signzipl +%else +%bcond_with signzipl %endif %if 0%{?fedora} -%global with_pandoc 1 +%bcond_without pandoc +%else +%bcond_with pandoc %endif +%bcond_without rust Name: s390utils Summary: Utilities and daemons for IBM z Systems -Version: 2.25.0 -Release: 2%{?dist}.alma +Version: 2.29.0 +Release: 3%{?dist}.5.alma.1 Epoch: 2 License: MIT -ExclusiveArch: s390 s390x #URL: http://www.ibm.com/developerworks/linux/linux390/s390-tools.html URL: https://github.com/ibm-s390-tools/s390-tools Source0: https://github.com/ibm-s390-tools/s390-tools/archive/v%{version}.tar.gz#/s390-tools-%{version}.tar.gz +# To create the vendor tarball: +# tar xf s390-tools-%%{version}.tar.gz ; pushd s390-tools-%%{version}/rust/pvsecret ; \ +# rm -f Cargo.lock && cargo vendor && \ +# tar Jvcf ../../../s390-tools-%%{version}-rust-vendor.tar.xz vendor/ ; popd +Source1: s390-tools-%{version}-rust-vendor.tar.xz Source4: http://www.linuxvm.org/Patches/S390/cmsfs-%{cmsfsver}.tar.gz Source5: zfcpconf.sh # http://www.ibm.com/developerworks/linux/linux390/src_vipa-%%{vipaver}.html @@ -38,7 +47,7 @@ Source24: 20-zipl-kernel.install Source25: 52-zipl-rescue.install Source26: 91-zipl.install -%if 0%{?signzipl} +%if %{with signzipl} %define pesign_name redhatsecureboot302 %endif @@ -47,7 +56,7 @@ Patch0: s390-tools-zipl-invert-script-options.patch Patch1: s390-tools-zipl-blscfg-rpm-nvr-sort.patch # upstream fixes/updates -Patch100: s390utils-%%{version}-rhel.patch +Patch100: s390utils-%{version}-rhel.patch Patch1000: cmsfs-1.1.8-warnings.patch Patch1001: cmsfs-1.1.8-kernel26.patch @@ -56,6 +65,13 @@ Patch1003: cmsfs-1.1.8-args.patch Patch2000: src_vipa-2.1.0-deprecate.patch +# https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval +ExcludeArch: %{ix86} + +%ifarch s390x +# +# s390x/native package structure +# Requires: s390utils-core = %{epoch}:%{version}-%{release} Requires: s390utils-base = %{epoch}:%{version}-%{release} Requires: s390utils-osasnmpd = %{epoch}:%{version}-%{release} @@ -64,8 +80,46 @@ Requires: s390utils-mon_statd = %{epoch}:%{version}-%{release} Requires: s390utils-iucvterm = %{epoch}:%{version}-%{release} Requires: s390utils-ziomon = %{epoch}:%{version}-%{release} Requires: s390utils-cmsfs = %{epoch}:%{version}-%{release} +%else +# +# multiarch package structure +# +Requires: s390utils-se-data = %{epoch}:%{version}-%{release} +%endif BuildRequires: gcc-c++ +BuildRequires: glib2-devel +%if %{with rust} +%if 0%{?rhel} +BuildRequires: libcurl-devel +BuildRequires: openssl-devel +BuildRequires: rust-toolset +%else +BuildRequires: crate(anstream) +BuildRequires: crate(anstyle-query) +BuildRequires: crate(anyhow) +BuildRequires: crate(byteorder) +BuildRequires: crate(cfg-if) +BuildRequires: crate(clap) +BuildRequires: crate(clap_complete) +BuildRequires: crate(clap_derive) +BuildRequires: crate(colorchoice) +BuildRequires: crate(curl) +BuildRequires: crate(is-terminal) +BuildRequires: crate(libc) +BuildRequires: crate(log) +BuildRequires: crate(openssl) +BuildRequires: crate(openssl-probe) +BuildRequires: crate(serde) +BuildRequires: crate(serde_derive) +BuildRequires: crate(serde_yaml) +BuildRequires: crate(strsim) +BuildRequires: crate(terminal_size) +BuildRequires: crate(thiserror) +BuildRequires: crate(zerocopy) +BuildRequires: rust-packaging +%endif +%endif %description This is a meta package for installing the default s390-tools sub packages. @@ -83,7 +137,7 @@ be used together with the zSeries (s390) Linux kernel and device drivers. %patch1 -p1 -b .blscfg-rpm-nvr-sort # upstream fixes/updates -%%patch100 -p1 +%patch100 -p1 # # cmsfs @@ -113,6 +167,17 @@ popd # remove --strip from install find . -name Makefile | xargs sed -i 's/$(INSTALL) -s/$(INSTALL)/g' +%if %{with rust} +%if 0%{?rhel} +pushd rust +%cargo_prep -V 1 +popd +%else +%cargo_prep +%endif +rm -rf ./rust/Cargo.lock +%endif + pushd cmsfs-%{cmsfsver} # cmdfs: fix encoding iconv -f ISO8859-1 -t UTF-8 -o README.new README @@ -127,8 +192,12 @@ popd %build make \ CFLAGS="%{build_cflags}" CXXFLAGS="%{build_cxxflags}" LDFLAGS="%{build_ldflags}" \ + RUSTFLAGS="%{build_rustflags}" \ +%if %{without rust} + HAVE_CARGO=0 \ +%endif HAVE_DRACUT=1 \ -%if 0%{?with_pandoc} +%if %{with pandoc} ENABLE_DOC=1 \ %endif NO_PIE_LDFLAGS="" \ @@ -136,6 +205,10 @@ make \ DISTRELEASE=%{release} \ V=1 +%ifarch s390x +# +# s390x/native specific %%build section +# pushd cmsfs-%{cmsfsver} ./configure make CC="gcc %{build_cflags} -fno-strict-aliasing %{build_ldflags}" @@ -144,12 +217,18 @@ popd pushd src_vipa-%{vipaver} make CC_FLAGS="%{build_cflags} -fPIC" LD_FLAGS="%{build_ldflags} -shared" LIBDIR=%{_libdir} popd +%endif %install make install \ + CFLAGS="%{build_cflags}" CXXFLAGS="%{build_cxxflags}" LDFLAGS="%{build_ldflags}" \ + RUSTFLAGS="%{build_rustflags}" \ +%if %{without rust} + HAVE_CARGO=0 \ +%endif HAVE_DRACUT=1 \ -%if 0%{?with_pandoc} +%if %{with pandoc} ENABLE_DOC=1 \ %endif DESTDIR=%{buildroot} \ @@ -158,8 +237,12 @@ make install \ DISTRELEASE=%{release} \ V=1 +%ifarch s390x +# +# s390x/native specific %%install section +# # sign the stage3 bootloader -%if 0%{?signzipl} +%if %{with signzipl} if [ -x /usr/bin/rpm-sign ]; then pushd %{buildroot}/lib/s390-tools/ rpm-sign --key "%{pesign_name}" --lkmsign stage3.bin --output stage3.signed @@ -229,11 +312,63 @@ install -p -m 644 %{SOURCE17} %{buildroot}%{_udevrulesdir}/81-ccw.rules # zipl.conf to be ghosted touch %{buildroot}%{_sysconfdir}/zipl.conf +%endif - +%ifarch s390x +# +# s390x/native main %%files section +# %files %doc README.md +%else +# +# multiarch %%files section +# + +%files +%doc README.md +%license LICENSE +%{_bindir}/genprotimg +%if %{with rust} +%{_bindir}/pvapconfig +%endif +%{_bindir}/pvattest +%{_bindir}/pvextract-hdr +%if %{with rust} +%{_bindir}/pvsecret +%endif +%{_mandir}/man1/genprotimg.1* +%if %{with rust} +%{_mandir}/man1/pvapconfig.1* +%endif +%{_mandir}/man1/pvattest.1* +%{_mandir}/man1/pvattest-create.1* +%{_mandir}/man1/pvattest-perform.1* +%{_mandir}/man1/pvattest-verify.1* +%if %{with rust} +%{_mandir}/man1/pvsecret-add.1* +%{_mandir}/man1/pvsecret-create-association.1* +%{_mandir}/man1/pvsecret-create-meta.1* +%{_mandir}/man1/pvsecret-create.1* +%{_mandir}/man1/pvsecret-list.1* +%{_mandir}/man1/pvsecret-lock.1* +%{_mandir}/man1/pvsecret-version.1* +%{_mandir}/man1/pvsecret.1* +%endif +%dir %{_datadir}/s390-tools +%{_datadir}/s390-tools/genprotimg/ + +# +# enf of multi-arch section +# +%endif + +%ifarch s390x +# +# s390x specific sub-packages +# +# # ************************* s390-tools core package ************************* # %package core @@ -289,6 +424,7 @@ systemctl --no-reload preset device_cio_free.service >/dev/null 2>&1 || : %{_unitdir}/cpi.service %config(noreplace) %{_sysconfdir}/sysconfig/cpi /usr/lib/dracut/modules.d/95zdev/ +/usr/lib/dracut/modules.d/95zdev-kdump/ %{_mandir}/man5/zipl.conf.5* %{_mandir}/man8/chreipl.8* %{_mandir}/man8/chzdev.8* @@ -343,6 +479,7 @@ Requires: ethtool Requires: tar Requires: file Requires: s390utils-core = %{epoch}:%{version}-%{release} +Requires: s390utils-se-data = %{epoch}:%{version}-%{release} %{?systemd_requires} BuildRequires: perl-generators BuildRequires: ncurses-devel @@ -350,7 +487,6 @@ BuildRequires: glibc-static BuildRequires: cryptsetup-devel >= 2.0.3 BuildRequires: json-c-devel BuildRequires: rpm-devel -BuildRequires: glib2-devel BuildRequires: libxml2-devel @@ -416,11 +552,6 @@ s390 base tools. This collection provides the following utilities: * tunedasd: Adjust tunable parameters on DASD devices. - * vmconvert: - Convert system dumps created by the z/VM VMDUMP command into dumps with - LKCD format. These LKCD dumps can then be analyzed with the dump analysis - tool lcrash. - * vmcp: Allows Linux users to send commands to the z/VM control program (CP). The normal usage is to invoke vmcp with the command you want to @@ -552,9 +683,14 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_bindir}/dump2tar %{_bindir}/genprotimg %{_bindir}/mk-s390image +%if %{with rust} +%{_bindir}/pvapconfig +%endif %{_bindir}/pvattest %{_bindir}/pvextract-hdr -%{_bindir}/vmconvert +%if %{with rust} +%{_bindir}/pvsecret +%endif %{_bindir}/zkey %{_bindir}/zkey-cryptsetup %{_unitdir}/dumpconf.service @@ -579,11 +715,23 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_libdir}/zkey/zkey-kmip.so %{_mandir}/man1/dump2tar.1* %{_mandir}/man1/genprotimg.1* +%if %{with rust} +%{_mandir}/man1/pvapconfig.1* +%endif %{_mandir}/man1/pvattest.1* %{_mandir}/man1/pvattest-create.1* %{_mandir}/man1/pvattest-perform.1* %{_mandir}/man1/pvattest-verify.1* -%{_mandir}/man1/vmconvert.1* +%if %{with rust} +%{_mandir}/man1/pvsecret-add.1* +%{_mandir}/man1/pvsecret-create-association.1* +%{_mandir}/man1/pvsecret-create-meta.1* +%{_mandir}/man1/pvsecret-create.1* +%{_mandir}/man1/pvsecret-list.1* +%{_mandir}/man1/pvsecret-lock.1* +%{_mandir}/man1/pvsecret-version.1* +%{_mandir}/man1/pvsecret.1* +%endif %{_mandir}/man1/zkey.1* %{_mandir}/man1/zkey-cryptsetup.1* %{_mandir}/man1/zkey-ekmfweb.1* @@ -634,7 +782,6 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_mandir}/man8/znetconf.8* %{_mandir}/man8/zpcictl.8* %dir %{_datadir}/s390-tools -%{_datadir}/s390-tools/genprotimg/ %{_datadir}/s390-tools/netboot/ %dir %attr(0770,root,zkeyadm) %{_sysconfdir}/zkey %dir %attr(0770,root,zkeyadm) %{_sysconfdir}/zkey/kmip @@ -651,6 +798,18 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_libdir}/src_vipa.so %{_mandir}/man8/src_vipa.8* +%package se-data +License: MIT +Summary: Data for Secure Execution +BuildArch: noarch + +%description se-data +%{summary}. + +%files se-data +%dir %{_datadir}/s390-tools +%{_datadir}/s390-tools/genprotimg/ + # # *********************** s390-tools osasnmpd package *********************** # @@ -955,7 +1114,7 @@ Summary: Use multipath information for re-IPL path failover BuildRequires: make BuildRequires: bash BuildRequires: coreutils -%if 0%{?with_pandoc} +%if %{with pandoc} BuildRequires: pandoc %endif BuildRequires: gawk @@ -976,7 +1135,7 @@ reconfigures the FCP re-IPL settings to use an operational path. %files chreipl-fcp-mpath %doc chreipl-fcp-mpath/README.md -%if 0%{?with_pandoc} +%if %{with pandoc} %doc chreipl-fcp-mpath/README.html %endif %dir %{_prefix}/lib/chreipl-fcp-mpath/ @@ -1008,11 +1167,77 @@ User-space development files for the s390/s390x architecture. %{_libdir}/libekmfweb.so %{_libdir}/libkmipclient.so +# +# end of s390x specific sub-packages +# +%endif + %changelog -* Wed May 17 2023 Eduard Abdullin - 2:2.25.0-2.alma +* Mon Aug 17 2026 Eduard Abdullin - 2:2.29.0-3.5.alma.1 - Fix detection 5* kernel +* Wed Apr 22 2026 Dan Horák - 2:2.29.0-3.5 +- hsci: Automatically set appropriate MTU for HSCI (RHEL-153548) +- Resolves: RHEL-153548 + +* Mon Nov 03 2025 Dan Horák - 2:2.29.0-3.4 +- dasdfmt: Change mode default (RHEL-121638) +- Resolves: RHEL-121638 + +* Mon Sep 15 2025 Dan Horák - 2:2.29.0-3.3 +- libekmfweb: Fix generation of certificate or CSR with RSA-PSS instead of RSA (RHEL-114130) +- Resolves: RHEL-114130 + +* Thu Aug 07 2025 Dan Horák - 2:2.29.0-3.2 +- zkey: Fix CCA host version detection for newer CCA versions (RHEL-105487) +- Resolves: RHEL-105487 + +* Wed Apr 10 2024 Dan Horák - 2:2.29.0-3.1 +- SE-tooling: New IBM host-key subject locality (RHEL-30398) +- dbginfo.sh: missing data of new ROCE cards (RHEL-24110) +- Resolves: RHEL-30398 RHEL-24110 + +* Mon Jan 29 2024 Dan Horák - 2:2.29.0-3 +- add s390utils-se-data as a noarch subpackage with Secure Execution data files +- Resolves: RHEL-10568 + +* Fri Jan 19 2024 Dan Horák - 2:2.29.0-2 +- SE: Secure guest tool to bind and associate APQNs (RHEL-10575) +- Resolves: RHEL-10575 + +* Mon Dec 04 2023 Dan Horák - 2:2.29.0-1 +- rebased to 2.29.0 (RHEL-11409) +- KVM: Support AP Bindings in SE Header (RHEL-10573) +- KVM: Userspace Tool for IBK Request Generation and Insertion (RHEL-10579) +- zkey: support for key type PKEY_TYPE_EP11_AES (RHEL-11441) +- vmur: fix handling of option -t (RHEL-11479) +- dbginfo.sh: global original Input Field Separator (IFS) (RHEL-16526) +- enable multi-arch build (RHEL-10568) +- Resolves: RHEL-11409 RHEL-10573 RHEL-10579 RHEL-11441 RHEL-11479 RHEL-16526 RHEL-10568 + +* Mon Aug 07 2023 Dan Horák - 2:2.27.0-3 +- zdev/dracut: fix kdump build to integrate with site support (#2229178) +- Resolves: #2229178 + +* Fri Jul 21 2023 Dan Horák - 2:2.27.0-2 +- Secure Execution APQN binding and IBK association (#2110510) +- Resolves: #2110510 + +* Tue Jun 06 2023 Dan Horák - 2:2.27.0-1.1 +- rebuild +- Related: #2159698 + +* Mon Jun 05 2023 Dan Horák - 2:2.27.0-1 +- rebased to 2.27.0 (#2159698) +- lszcrypt fails when querying a specific domain (#2177613) +- DASD autoquiesce support (#2196510) +- zcrypt DD: AP command filtering (#2170362) +- vmconvert and zgetdump consolidation (#2173925) +- Support for List-Directed dump from ECKD DASD (#2159694) +- Support for List-Directed IPL and re-IPL from ECKD DASD (#2159691) +- Resolves: #2159698 #2177613 #2196510 #2170362 #2173925 #2159694 #2159691 + * Fri Feb 03 2023 Dan Horák - 2:2.25.0-2 - zkey: Support EP11 host library version 4 (#2165811) - Resolves: #2165811