diff --git a/.gitignore b/.gitignore index 60549f6..63d61f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -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 diff --git a/.s390utils.metadata b/.s390utils.metadata index dba08ad..c873ba0 100644 --- a/.s390utils.metadata +++ b/.s390utils.metadata @@ -1 +1,2 @@ -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 diff --git a/SOURCES/s390-tools-zipl-blscfg-rpm-nvr-sort.patch b/SOURCES/s390-tools-zipl-blscfg-rpm-nvr-sort.patch index 3960de1..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.37.3 +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.37.3 +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 94453de..0000000 --- a/SOURCES/s390utils-2.25.0-rhel.patch +++ /dev/null @@ -1,35 +0,0 @@ -From a9fed51fbf159a98fcd4a9dddf4fef243bb433af Mon Sep 17 00:00:00 2001 -From: Ingo Franzki -Date: Fri, 20 Jan 2023 11:04:18 +0100 -Subject: [PATCH] zkey: Support EP11 host library version 4 (#2165812) -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..cee3cc7 --- /dev/null +++ b/SOURCES/s390utils-2.29.0-rhel.patch @@ -0,0 +1,5719 @@ +From a32824922cb273703bacd44e6a29cbc33ae48cf5 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Fri, 21 Jul 2023 14:06:18 +0200 +Subject: [PATCH 01/13] 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 8359929..df8b57d 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 4efa2e4..c0a7037 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 a00c5dd..e7b7c73 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 9892a9e..2e33b22 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 e013e06..2582088 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 5a5bc3c..3b57c5f 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 fae78c7..8b55f7d 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 3000290..843e554 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.43.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/13] 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 cf2fda7..420bafd 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.43.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/13] 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 2622bba..61b0af8 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.43.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/13] 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 4bf5f16..9ef58e2 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 910419e..8132e9d 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 cda448b..b9acd73 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 8ad4fa5..8c44421 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 724702a..6943bd3 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 89f92b8..a2f313b 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 8a6258d..1c0d2b5 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 2b3e861..bf491c6 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.43.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/13] 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 cdef9ef..88287c8 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.43.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/13] 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 0000000..65a70a9 +--- /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 420bafd..e4e9885 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 61b0af8..16603bf 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 689d624..825386a 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 17d4c0e..90907ce 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 da4deab..d1e75b1 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 30bbbc8..215381b 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.43.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/13] 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 4760f82..f9f3955 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 1db32c2..f7d1cf0 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.43.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/13] 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 825386a..22d5fd3 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 90907ce..7bdeece 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 d1e75b1..e236c00 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.43.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/13] 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 65a70a9..7ba1faa 100644 +--- a/rust/Cargo.toml ++++ b/rust/Cargo.toml +@@ -12,4 +12,3 @@ license = "MIT" + + [profile.release] + lto = true +-panic = "abort" +-- +2.43.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/13] 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 f7d1cf0..067be05 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 22d5fd3..ff84526 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 1c0d2b5..5ca2e71 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.43.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/13] 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 e4e9885..fa3cf04 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.43.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/13] 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 d72ac93..ed89140 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 6943bd3..72a05b2 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.43.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/13] 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 067be05..3a6c423 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 7ba1faa..f358dbd 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 fa3cf04..818694d 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 0000000..2a69468 +--- /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 0000000..d30cb76 +--- /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 0000000..73ce8ce +--- /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 0000000..0b88235 +--- /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 0000000..e7fd283 +--- /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 0000000..28d2ac7 +--- /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 0000000..c4670ce +--- /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 0000000..07899cd +--- /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 0000000..2f98bd5 +--- /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.43.0 + diff --git a/SPECS/s390utils.spec b/SPECS/s390utils.spec index 37b7894..b004d71 100644 --- a/SPECS/s390utils.spec +++ b/SPECS/s390utils.spec @@ -1,21 +1,30 @@ # 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: 4%{?dist} +Version: 2.29.0 +Release: 3%{?dist} Epoch: 2 License: MIT -ExclusiveArch: s390 s390x URL: https://github.com/ibm-s390-linux/s390-tools Source0: https://github.com/ibm-s390-linux/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 Source5: https://fedorapeople.org/cgit/sharkcz/public_git/utils.git/tree/zfcpconf.sh Source7: https://fedorapeople.org/cgit/sharkcz/public_git/utils.git/tree/zfcp.udev Source12: https://fedorapeople.org/cgit/sharkcz/public_git/utils.git/tree/dasd.udev @@ -29,7 +38,7 @@ Source23: 20-zipl-kernel.install Source24: 52-zipl-rescue.install Source25: 91-zipl.install -%if 0%{?signzipl} +%if %{with signzipl} %define pesign_name redhatsecureboot302 %endif @@ -38,8 +47,15 @@ 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 +# 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} @@ -47,9 +63,47 @@ Requires: s390utils-cpuplugd = %{epoch}:%{version}-%{release} Requires: s390utils-mon_statd = %{epoch}:%{version}-%{release} Requires: s390utils-iucvterm = %{epoch}:%{version}-%{release} Requires: s390utils-ziomon = %{epoch}:%{version}-%{release} +%else +# +# multiarch package structure +# +Requires: s390utils-se-data = %{epoch}:%{version}-%{release} +%endif BuildRequires: make 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. @@ -69,18 +123,29 @@ be used together with the zSeries (s390) Linux kernel and device drivers. # upstream fixes/updates %patch100 -p1 -# drop -Werror from genprotimg to allow building with GCC 12 -sed -i.bak -e 's/-Werror//g' genprotimg/src/Makefile genprotimg/boot/Makefile - # 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 + %build make \ CFLAGS="%{build_cflags}" CXXFLAGS="%{build_cxxflags}" LDFLAGS="%{build_ldflags}" \ +%if %{without rust} + HAVE_CARGO=0 \ +%endif HAVE_DRACUT=1 \ -%if 0%{?with_pandoc} +%if %{with pandoc} ENABLE_DOC=1 \ %endif NO_PIE_LDFLAGS="" \ @@ -91,8 +156,11 @@ make \ %install make install \ +%if %{without rust} + HAVE_CARGO=0 \ +%endif HAVE_DRACUT=1 \ -%if 0%{?with_pandoc} +%if %{with pandoc} ENABLE_DOC=1 \ %endif DESTDIR=%{buildroot} \ @@ -101,8 +169,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 @@ -160,11 +232,62 @@ 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 ************************* # @@ -215,6 +338,7 @@ This package provides minimal set of tools needed to system to boot. %{_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* @@ -267,6 +391,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 @@ -274,7 +399,6 @@ BuildRequires: glibc-static BuildRequires: cryptsetup-devel >= 2.0.3 BuildRequires: json-c-devel BuildRequires: rpm-devel -BuildRequires: glib2-devel BuildRequires: libxml2-devel @@ -340,11 +464,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 @@ -476,9 +595,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 @@ -503,11 +627,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* @@ -557,7 +693,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 @@ -569,6 +704,18 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm # Additional Fedora/RHEL specific stuff /boot/tape0 +%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 *********************** # @@ -852,7 +999,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 @@ -873,7 +1020,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/ @@ -905,8 +1052,54 @@ User-space development files for the s390/s390x architecture. %{_libdir}/libekmfweb.so %{_libdir}/libkmipclient.so +# +# end of s390x specific sub-packages +# +%endif + %changelog +* 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-10567 + +* Fri Jan 19 2024 Dan Horák - 2:2.29.0-2 +- SE: Secure guest tool to bind and associate APQNs (RHEL-10574) +- Resolves: RHEL-10574 + +* Fri Nov 24 2023 Dan Horák - 2:2.29.0-1 +- rebased to 2.29.0 (RHEL-11408) +- KVM: Support AP Bindings in SE Header (RHEL-10572) +- KVM: Userspace Tool for IBK Request Generation and Insertion (RHEL-10578) +- zkey: support for key type PKEY_TYPE_EP11_AES (RHEL-11440) +- vmur: fix handling of option -t (RHEL-11480) +- dbginfo.sh: global original Input Field Separator (IFS) (RHEL-16527) +- enable multi-arch build (RHEL-10567) +- Resolves: RHEL-11408 RHEL-10572 RHEL-10578 RHEL-11440 RHEL-11480 RHEL-16527 RHEL-10567 + +* Mon Aug 7 2023 Dan Horák - 2:2.27.0-4 +- zdev/dracut: fix kdump build to integrate with site support (#2229177) +- Resolves: #2229177 + +* Thu Jul 20 2023 Dan Horák - 2:2.27.0-3 +- Secure Execution APQN binding and IBK association (#2110521) +- Resolves: #2110521 + +* Mon Jul 17 2023 Dan Horák - 2:2.27.0-2 +- zdev: cleanup patches to fix warnings (#2223304) +- zdev: add missing label in the udev-rules (#2222900) +- Resolves: #2223304 #2222900 + +* Wed May 31 2023 Dan Horák - 2:2.27.0-1 +- rebased to 2.27.0 (#2160062) +- lszcrypt fails when querying a specific domain (#2177612) +- DASD autoquiesce support (#2196517) +- zcrypt DD: AP command filtering (#2170360) +- vmconvert and zgetdump consolidation (#2173924) +- Support for List-Directed dump from ECKD DASD (#2160052) +- Support for List-Directed IPL and re-IPL from ECKD DASD (#2160040) +- Resolves: #2160062 #2177612 #2196517 #2170360 #2173924 #2160052 #2160040 + * Thu Feb 02 2023 Dan Horák - 2:2.25.0-4 - zkey: Support EP11 host library version 4 (#2165812) - Resolves: #2165812