diff --git a/s390utils-2.35.0-rhel.patch b/s390utils-2.35.0-rhel.patch deleted file mode 100644 index e69de29..0000000 diff --git a/s390utils-2.36.0-rhel.patch b/s390utils-2.36.0-rhel.patch new file mode 100644 index 0000000..af086c4 --- /dev/null +++ b/s390utils-2.36.0-rhel.patch @@ -0,0 +1,9730 @@ +From c0c76b5735daa9690be297335d21181a70eaaded Mon Sep 17 00:00:00 2001 +From: Eduard Shishkin +Date: Mon, 16 Dec 2024 13:55:52 +0100 +Subject: [PATCH 01/31] zipl/src: Fix incorrect installation of zipl_helper.md +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Dereferencing zipl.helper.md by 'make install' causes "duplicate +BUILD-IDs" conflict for distro partners. + +Install zipl_helper.device-mapper as a regular file and all other +helpers - as symlinks to the zipl_helper.device-mapper + +Fixes: 3296d85e351a ("zipl/src: add support of md-mirrors") +Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/178 +Signed-off-by: Eduard Shishkin +Acked-by: Jan Höppner +Signed-off-by: Jan Höppner +(cherry picked from commit 352e2fe5d3da1993cf65be86bdb743b34c95b0b0) +--- + zipl/src/Makefile | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/zipl/src/Makefile b/zipl/src/Makefile +index 7927974..39489e2 100644 +--- a/zipl/src/Makefile ++++ b/zipl/src/Makefile +@@ -38,9 +38,10 @@ install: all + $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) + $(INSTALL) -c zipl $(DESTDIR)$(BINDIR) + $(INSTALL) -c zipl-editenv $(DESTDIR)$(BINDIR) +- $(INSTALL) -m 755 $(zipl_helpers) $(chreipl_helpers) \ +- $(DESTDIR)$(TOOLS_LIBDIR) +- $(CP) --no-dereference $(chreipl_helpers) $(DESTDIR)$(TOOLS_LIBDIR) ++ $(INSTALL) -m 755 zipl_helper.device-mapper $(DESTDIR)$(TOOLS_LIBDIR) ++ ln -f -s zipl_helper.device-mapper $(DESTDIR)$(TOOLS_LIBDIR)/zipl_helper.md ++ ln -f -s zipl_helper.device-mapper $(DESTDIR)$(TOOLS_LIBDIR)/chreipl_helper.md ++ ln -f -s zipl_helper.device-mapper $(DESTDIR)$(TOOLS_LIBDIR)/chreipl_helper.device-mapper + + clean: + rm -f *.o $(zipl_helpers) $(chreipl_helpers) zipl zipl-editenv +-- +2.47.1 + + +From cd32b1c9ebc8d7efa955efd15ba7261e7b7fd083 Mon Sep 17 00:00:00 2001 +From: Niklas Schnelle +Date: Fri, 6 Dec 2024 15:28:08 +0100 +Subject: [PATCH 02/31] opticsmon: Fix runaway loop in on_link_change() + (RHEL-24153) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When on_link_change() gets called with a netdev that would be monitored +but hasn't entered zpci_list yet, reloads is 1 after the loops and +a reload occurs. Then the netdev is found in the list and reloads +becomes -1 which incorrectly triggers more reloads until underflow. +Fix this by returning once the device is found. Also just check for +reloads being larger than zero. + +Fixes: c34adb9cabee ("opticsmon: Introduce opticsmon tool") +Reviewed-by: Halil Pasic +Signed-off-by: Niklas Schnelle +Signed-off-by: Jan Höppner +(cherry picked from commit dff965465ca9d9c4edaf0f90eadd9a6de335b354) +--- + opticsmon/opticsmon.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/opticsmon/opticsmon.c b/opticsmon/opticsmon.c +index c2f355e..50dd8d7 100644 +--- a/opticsmon/opticsmon.c ++++ b/opticsmon/opticsmon.c +@@ -280,16 +280,15 @@ void on_link_change(struct zpci_netdev *netdev, void *arg) + if (!ctx->zpci_list || util_list_is_empty(ctx->zpci_list)) + zpci_list_reload(&ctx->zpci_list); + +-reload: ++find: + util_list_iterate(ctx->zpci_list, zdev) { + for (i = 0; i < zdev->num_netdevs; i++) { + if (!strcmp(zdev->netdevs[i].name, netdev->name)) { +- reloads--; + /* Skip data collection if operational state is + * unchanged + */ + if (zdev->netdevs[i].operstate == netdev->operstate) +- continue; ++ return; + /* Update operation state for VFs even though + * they are skipped just for a consistent view + */ +@@ -297,14 +296,15 @@ reload: + /* Only collect optics data for PFs */ + if (!zpci_is_vf(zdev)) + dump_adapter_data(ctx, zdev); ++ return; + } + } + } + /* Might be a new device, reload list of devices and retry */ +- if (reloads) { ++ if (reloads > 0) { + zpci_list_reload(&ctx->zpci_list); + reloads--; +- goto reload; ++ goto find; + } + } + +-- +2.47.1 + + +From 95f31c8471f9e6f353afca7da42bc3042472aa5d Mon Sep 17 00:00:00 2001 +From: Niklas Schnelle +Date: Mon, 9 Dec 2024 15:08:03 +0100 +Subject: [PATCH 03/31] libzpci: opticsmon: Refactor on_link_change() using new + zpci_find_by_netdev() (RHEL-24153) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Finding a PCI device given the name of a netdev seems generally useful +so pull this out into a new zpci_find_by_netdev() function in libzpci +and use this to simplify on_link_change() removing the need for +backwards goto. + +Reviewed-by: Halil Pasic +Reviewed-by: Jan Höppner +Signed-off-by: Niklas Schnelle +Signed-off-by: Jan Höppner +(cherry picked from commit cf5560a100b5552e2eeeaac9c60a88ae77233530) +--- + include/lib/pci_list.h | 3 +++ + libzpci/pci_list.c | 31 +++++++++++++++++++++++++++++++ + opticsmon/opticsmon.c | 27 +++++++++++---------------- + 3 files changed, 45 insertions(+), 16 deletions(-) + +diff --git a/include/lib/pci_list.h b/include/lib/pci_list.h +index 829ec24..5b2918b 100644 +--- a/include/lib/pci_list.h ++++ b/include/lib/pci_list.h +@@ -93,4 +93,7 @@ const char *zpci_pft_str(struct zpci_dev *zdev); + const char *zpci_operstate_str(operstate_t state); + operstate_t zpci_operstate_from_str(const char *oper_str); + ++struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name, ++ struct zpci_netdev **netdev); ++ + #endif /* LIB_ZPCI_PCI_LIST_H */ +diff --git a/libzpci/pci_list.c b/libzpci/pci_list.c +index 10f64e8..e0d56e4 100644 +--- a/libzpci/pci_list.c ++++ b/libzpci/pci_list.c +@@ -356,3 +356,34 @@ void zpci_free_dev_list(struct util_list *zpci_list) + } + util_list_free(zpci_list); + } ++ ++/** ++ * Find a PCI device given the name of a netdev ++ * ++ * This function allows finding a PCI device when only the name of one ++ * of its netdevs is known. ++ * ++ * @param[in] zpci_list The device list to search ++ * @param[in] netdev_name The name of the netdev ++ * @param[out] netdev Pointer to store the netdev or NULL if ++ * only the PCI device is needed ++ * ++ * @return The PCI device if one is found NULL otherwise ++ */ ++struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name, ++ struct zpci_netdev **netdev) ++{ ++ struct zpci_dev *zdev = NULL; ++ int i; ++ ++ util_list_iterate(zpci_list, zdev) { ++ for (i = 0; i < zdev->num_netdevs; i++) { ++ if (!strcmp(zdev->netdevs[i].name, netdev_name)) { ++ if (netdev) ++ *netdev = &zdev->netdevs[i]; ++ return zdev; ++ } ++ } ++ } ++ return NULL; ++} +diff --git a/opticsmon/opticsmon.c b/opticsmon/opticsmon.c +index 50dd8d7..7ecaa12 100644 +--- a/opticsmon/opticsmon.c ++++ b/opticsmon/opticsmon.c +@@ -274,38 +274,33 @@ static int oneshot_mode(struct opticsmon_ctx *ctx) + void on_link_change(struct zpci_netdev *netdev, void *arg) + { + struct opticsmon_ctx *ctx = arg; +- struct zpci_dev *zdev; +- int i, reloads = 1; +- +- if (!ctx->zpci_list || util_list_is_empty(ctx->zpci_list)) +- zpci_list_reload(&ctx->zpci_list); ++ struct zpci_netdev *found_netdev; ++ struct zpci_dev *zdev = NULL; ++ int reloads = 1; + +-find: +- util_list_iterate(ctx->zpci_list, zdev) { +- for (i = 0; i < zdev->num_netdevs; i++) { +- if (!strcmp(zdev->netdevs[i].name, netdev->name)) { ++ do { ++ if (ctx->zpci_list) { ++ zdev = zpci_find_by_netdev(ctx->zpci_list, netdev->name, &found_netdev); ++ if (zdev) { + /* Skip data collection if operational state is + * unchanged + */ +- if (zdev->netdevs[i].operstate == netdev->operstate) ++ if (found_netdev->operstate == netdev->operstate) + return; + /* Update operation state for VFs even though + * they are skipped just for a consistent view + */ +- zdev->netdevs[i].operstate = netdev->operstate; ++ found_netdev->operstate = netdev->operstate; + /* Only collect optics data for PFs */ + if (!zpci_is_vf(zdev)) + dump_adapter_data(ctx, zdev); + return; + } + } +- } +- /* Might be a new device, reload list of devices and retry */ +- if (reloads > 0) { ++ /* Could be uninitalized list or a new device, retry after reload */ + zpci_list_reload(&ctx->zpci_list); + reloads--; +- goto find; +- } ++ } while (reloads > 0); + } + + #define MAX_EVENTS 8 +-- +2.47.1 + + +From 95277a7eac2391196e39548221c551b79dcbd77a Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Wed, 11 Dec 2024 19:25:59 +0100 +Subject: [PATCH 04/31] rust/pvimg: Add '--(enable|disable)-image-encryption' + flags to 'pvimg create' (RHEL-70851) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +With runtime attestation it might be useful to have non-encrypted Secure +Execution images. This patch adds the support for this to the 'pvimg +create' and 'genprotimg' commands. + +Reviewed-by: Steffen Eiden +Acked-by: Hendrik Brueckner +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit cf51ac786095f2a1a17d04fea9ee73271438d247) +--- + rust/pvimg/man/genprotimg.1 | 26 +++++++++++++++++++++----- + rust/pvimg/man/pvimg-create.1 | 26 +++++++++++++++++++++----- + rust/pvimg/man/pvimg-info.1 | 10 +++++----- + rust/pvimg/man/pvimg-test.1 | 10 +++++----- + rust/pvimg/man/pvimg.1 | 10 +++++----- + rust/pvimg/src/cli.rs | 18 ++++++++++++++++++ + rust/pvimg/src/cmd/create.rs | 10 ++++++++++ + 7 files changed, 85 insertions(+), 25 deletions(-) + +diff --git a/rust/pvimg/man/genprotimg.1 b/rust/pvimg/man/genprotimg.1 +index 46a91aa..3f4949e 100644 +--- a/rust/pvimg/man/genprotimg.1 ++++ b/rust/pvimg/man/genprotimg.1 +@@ -3,11 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH genprotimg 1 "2024-12-05" "s390-tools" "Genprotimg Manual" ++.TH genprotimg 1 "2024-12-11" "s390-tools" "Genprotimg Manual" + .nh + .ad l + .SH NAME +-\fBgenprotimg\fP - Create an IBM Secure Execution image ++\fBgenprotimg\fP \- Create an IBM Secure Execution image + \fB + .SH SYNOPSIS + .nf +@@ -196,6 +196,22 @@ Disable the support for backup target keys (default). + .RE + .RE + .PP ++\-\-enable\-image\-encryption ++.RS 4 ++Enable encryption of the image components (default). The image components are: ++the kernel, ramdisk, and kernel command line. ++.RE ++.RE ++.PP ++\-\-disable\-image\-encryption ++.RS 4 ++Disable encryption of the image components. The image components are: the ++kernel, ramdisk, and kernel command line. Use only if the components used do not ++contain any confidential content (for example, secrets like non\-public ++cryptographic keys). ++.RE ++.RE ++.PP + \-v, \-\-verbose + .RS 4 + Provide more detailed output. +@@ -222,16 +238,16 @@ Print help (see a summary with \fB\-h\fR). + + .SH EXIT STATUS + .TP 8 +-.B 0 - Program finished successfully ++.B 0 \- Program finished successfully + The command was executed successfully. + .RE + .TP 8 +-.B 1 - Generic error ++.B 1 \- Generic error + Something went wrong during the operation. Refer to the error + message. + .RE + .TP 8 +-.B 2 - Usage error ++.B 2 \- Usage error + The command was used incorrectly, for example: unsupported command + line flag, or wrong number of arguments. + .RE +diff --git a/rust/pvimg/man/pvimg-create.1 b/rust/pvimg/man/pvimg-create.1 +index aba197f..dae1cf1 100644 +--- a/rust/pvimg/man/pvimg-create.1 ++++ b/rust/pvimg/man/pvimg-create.1 +@@ -3,11 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvimg-create 1 "2024-12-05" "s390-tools" "Pvimg Manual" ++.TH pvimg-create 1 "2024-12-11" "s390-tools" "Pvimg Manual" + .nh + .ad l + .SH NAME +-\fBpvimg create\fP - Create an IBM Secure Execution image ++\fBpvimg create\fP \- Create an IBM Secure Execution image + \fB + .SH SYNOPSIS + .nf +@@ -195,6 +195,22 @@ Disable the support for backup target keys (default). + .RE + .RE + .PP ++\-\-enable\-image\-encryption ++.RS 4 ++Enable encryption of the image components (default). The image components are: ++the kernel, ramdisk, and kernel command line. ++.RE ++.RE ++.PP ++\-\-disable\-image\-encryption ++.RS 4 ++Disable encryption of the image components. The image components are: the ++kernel, ramdisk, and kernel command line. Use only if the components used do not ++contain any confidential content (for example, secrets like non\-public ++cryptographic keys). ++.RE ++.RE ++.PP + \-h, \-\-help + .RS 4 + Print help (see a summary with \fB\-h\fR). +@@ -203,16 +219,16 @@ Print help (see a summary with \fB\-h\fR). + + .SH EXIT STATUS + .TP 8 +-.B 0 - Program finished successfully ++.B 0 \- Program finished successfully + The command was executed successfully. + .RE + .TP 8 +-.B 1 - Generic error ++.B 1 \- Generic error + Something went wrong during the operation. Refer to the error + message. + .RE + .TP 8 +-.B 2 - Usage error ++.B 2 \- Usage error + The command was used incorrectly, for example: unsupported command + line flag, or wrong number of arguments. + .RE +diff --git a/rust/pvimg/man/pvimg-info.1 b/rust/pvimg/man/pvimg-info.1 +index e88cbe4..d2726c3 100644 +--- a/rust/pvimg/man/pvimg-info.1 ++++ b/rust/pvimg/man/pvimg-info.1 +@@ -3,11 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvimg-info 1 "2024-12-05" "s390-tools" "Pvimg Manual" ++.TH pvimg-info 1 "2024-12-11" "s390-tools" "Pvimg Manual" + .nh + .ad l + .SH NAME +-\fBpvimg info\fP - Print information about the IBM Secure Execution image ++\fBpvimg info\fP \- Print information about the IBM Secure Execution image + \fB + .SH SYNOPSIS + .nf +@@ -51,16 +51,16 @@ Print help (see a summary with \fB\-h\fR). + + .SH EXIT STATUS + .TP 8 +-.B 0 - Program finished successfully ++.B 0 \- Program finished successfully + The command was executed successfully. + .RE + .TP 8 +-.B 1 - Generic error ++.B 1 \- Generic error + Something went wrong during the operation. Refer to the error + message. + .RE + .TP 8 +-.B 2 - Usage error ++.B 2 \- Usage error + The command was used incorrectly, for example: unsupported command + line flag, or wrong number of arguments. + .RE +diff --git a/rust/pvimg/man/pvimg-test.1 b/rust/pvimg/man/pvimg-test.1 +index 901c7ed..4fb7d73 100644 +--- a/rust/pvimg/man/pvimg-test.1 ++++ b/rust/pvimg/man/pvimg-test.1 +@@ -3,11 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvimg-test 1 "2024-12-05" "s390-tools" "Pvimg Manual" ++.TH pvimg-test 1 "2024-12-11" "s390-tools" "Pvimg Manual" + .nh + .ad l + .SH NAME +-\fBpvimg test\fP - Test different aspects of an existing IBM Secure Execution image ++\fBpvimg test\fP \- Test different aspects of an existing IBM Secure Execution image + \fB + .SH SYNOPSIS + .nf +@@ -54,16 +54,16 @@ Print help (see a summary with \fB\-h\fR). + + .SH EXIT STATUS + .TP 8 +-.B 0 - Program finished successfully ++.B 0 \- Program finished successfully + The command was executed successfully. + .RE + .TP 8 +-.B 1 - Generic error ++.B 1 \- Generic error + Something went wrong during the operation. Refer to the error + message. + .RE + .TP 8 +-.B 2 - Usage error ++.B 2 \- Usage error + The command was used incorrectly, for example: unsupported command + line flag, or wrong number of arguments. + .RE +diff --git a/rust/pvimg/man/pvimg.1 b/rust/pvimg/man/pvimg.1 +index 37c8e97..5676b61 100644 +--- a/rust/pvimg/man/pvimg.1 ++++ b/rust/pvimg/man/pvimg.1 +@@ -3,11 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvimg 1 "2024-12-05" "s390-tools" "Pvimg Manual" ++.TH pvimg 1 "2024-12-11" "s390-tools" "Pvimg Manual" + .nh + .ad l + .SH NAME +-\fBpvimg\fP - Create and inspect IBM Secure Execution images ++\fBpvimg\fP \- Create and inspect IBM Secure Execution images + \fB + .SH SYNOPSIS + .nf +@@ -69,16 +69,16 @@ Print help (see a summary with \fB\-h\fR). + + .SH EXIT STATUS + .TP 8 +-.B 0 - Program finished successfully ++.B 0 \- Program finished successfully + The command was executed successfully. + .RE + .TP 8 +-.B 1 - Generic error ++.B 1 \- Generic error + Something went wrong during the operation. Refer to the error + message. + .RE + .TP 8 +-.B 2 - Usage error ++.B 2 \- Usage error + The command was used incorrectly, for example: unsupported command + line flag, or wrong number of arguments. + .RE +diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs +index 2ca4e90..12f0b76 100644 +--- a/rust/pvimg/src/cli.rs ++++ b/rust/pvimg/src/cli.rs +@@ -140,6 +140,20 @@ pub struct CreateBootImageLegacyFlags { + /// Disable the support for backup target keys (default). + #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_backup_keys", group="header-flags")] + pub disable_backup_keys: Option, ++ ++ /// Enable encryption of the image components (default). ++ /// ++ /// The image components are: the kernel, ramdisk, and kernel command line. ++ #[arg(long, action = clap::ArgAction::SetTrue, group="header-flags")] ++ pub enable_image_encryption: Option, ++ ++ /// Disable encryption of the image components. ++ /// ++ /// The image components are: the kernel, ramdisk, and kernel command line. ++ /// Use only if the components used do not contain any confidential content ++ /// (for example, secrets like non-public cryptographic keys). ++ #[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_image_encryption", group="header-flags")] ++ pub disable_image_encryption: Option, + } + + #[non_exhaustive] +@@ -476,6 +490,8 @@ mod test { + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-pckmo", ["--enable-pckmo"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-pckmo-hmac", ["--enable-pckmo-hmac"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-backup-keys", ["--enable-backup-keys"])])), ++ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("disable-image-encryption", ["--disable-image-encryption"])])), ++ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-image-encryption", ["--enable-image-encryption"])])), + ]; + let invalid_create_args = [ + flat_map_collect(remove(mvcanv.clone(), "no-verify")), +@@ -501,6 +517,8 @@ mod test { + CliOption::new("x-pcf2", ["--x-pcf", "0x0"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-pckmo", ["--enable-pckmo"]), + CliOption::new("disable-pckmo", ["--disable-pckmo"])])), ++ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-image-encryption", ["--enable-image-encryption"]), ++ CliOption::new("disable-image-encryption", ["--disable-image-encryption"])])), + ]; + + let mut genprotimg_valid_args = vec![ +diff --git a/rust/pvimg/src/cmd/create.rs b/rust/pvimg/src/cmd/create.rs +index b696d79..475d352 100644 +--- a/rust/pvimg/src/cmd/create.rs ++++ b/rust/pvimg/src/cmd/create.rs +@@ -80,6 +80,12 @@ fn parse_flags( + lf.enable_backup_keys + .filter(|x| *x) + .and(Some(PcfV1::all_enabled([PcfV1::BackupTargetKeys]))), ++ lf.disable_image_encryption ++ .filter(|x| *x) ++ .and(Some(PcfV1::all_enabled([PcfV1::NoComponentEncryption]))), ++ lf.enable_image_encryption ++ .filter(|x| *x) ++ .and(Some(PcfV1::all_disabled([PcfV1::NoComponentEncryption]))), + ] + .into_iter() + .flatten() +@@ -135,6 +141,10 @@ pub fn create(opt: &CreateBootImageArgs) -> Result { + read_user_provided_keys(opt.comm_key.as_deref(), &opt.experimental_args)?; + let (plaintext_flags, secret_flags) = parse_flags(opt)?; + ++ if plaintext_flags.is_set(PcfV1::NoComponentEncryption) { ++ warn!("The components encryption is disabled, make sure that the components do not contain any confidential content."); ++ } ++ + let mut components = components(&opt.component_paths)?; + if opt.no_component_check { + warn!("The component check is turned off!"); +-- +2.47.1 + + +From 0bc601307846a7cdd667355dbae21c877d603e50 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Thu, 12 Dec 2024 20:19:55 +0100 +Subject: [PATCH 05/31] rust/pvimg/man: Document command line option aliases in + the manpages (RHEL-72022) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Document the visible command line option aliases in the manpages. + +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit b1fdbac5f95e8b03e703033ed23320611f1ed2a8) +--- + rust/pvimg/man/genprotimg.1 | 4 ++-- + rust/pvimg/man/pvimg-create.1 | 4 ++-- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/rust/pvimg/man/genprotimg.1 b/rust/pvimg/man/genprotimg.1 +index 3f4949e..f4b1aa1 100644 +--- a/rust/pvimg/man/genprotimg.1 ++++ b/rust/pvimg/man/genprotimg.1 +@@ -3,7 +3,7 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH genprotimg 1 "2024-12-11" "s390-tools" "Genprotimg Manual" ++.TH genprotimg 1 "2024-12-12" "s390-tools" "Genprotimg Manual" + .nh + .ad l + .SH NAME +@@ -37,7 +37,7 @@ QEMU. + + .SH OPTIONS + .PP +-\-i, \-\-kernel ++\-i, \-\-kernel, \-\-image + .RS 4 + Use the content of FILE as a raw binary Linux kernel. The Linux kernel must be a + raw binary s390x Linux kernel. The ELF format is not supported. +diff --git a/rust/pvimg/man/pvimg-create.1 b/rust/pvimg/man/pvimg-create.1 +index dae1cf1..6670704 100644 +--- a/rust/pvimg/man/pvimg-create.1 ++++ b/rust/pvimg/man/pvimg-create.1 +@@ -3,7 +3,7 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvimg-create 1 "2024-12-11" "s390-tools" "Pvimg Manual" ++.TH pvimg-create 1 "2024-12-12" "s390-tools" "Pvimg Manual" + .nh + .ad l + .SH NAME +@@ -36,7 +36,7 @@ QEMU. + + .SH OPTIONS + .PP +-\-i, \-\-kernel ++\-i, \-\-kernel, \-\-image + .RS 4 + Use the content of FILE as a raw binary Linux kernel. The Linux kernel must be a + raw binary s390x Linux kernel. The ELF format is not supported. +-- +2.47.1 + + +From 840452df23ebe54db82e8d0cf94352bddb758ed4 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Thu, 12 Dec 2024 20:19:56 +0100 +Subject: [PATCH 06/31] rust/pvimg: Add '--cck ' command line option and + make '--comm-key' an alias (RHEL-72022) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add '--cck ' as an command line option and make '--comm-key' an +alias of it. This makes the command line more similar to the other +Secure Execution related PV-tools (e.g. pvattest and pvsecret). + +Suggested-by: Reinhard Bündgen +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit 5b6d7a467dc342c9c25a0af72b2d5546798cdc94) +--- + rust/pvimg/man/genprotimg.1 | 11 +++++------ + rust/pvimg/man/pvimg-create.1 | 11 +++++------ + rust/pvimg/src/cli.rs | 14 ++++++++------ + rust/pvimg/src/cmd/create.rs | 3 +-- + 4 files changed, 19 insertions(+), 20 deletions(-) + +diff --git a/rust/pvimg/man/genprotimg.1 b/rust/pvimg/man/genprotimg.1 +index f4b1aa1..feba391 100644 +--- a/rust/pvimg/man/genprotimg.1 ++++ b/rust/pvimg/man/genprotimg.1 +@@ -3,7 +3,7 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH genprotimg 1 "2024-12-12" "s390-tools" "Genprotimg Manual" ++.TH genprotimg 1 "2024-12-17" "s390-tools" "Genprotimg Manual" + .nh + .ad l + .SH NAME +@@ -123,7 +123,7 @@ Overwrite an existing Secure Execution boot image. + .RE + .RE + .PP +-\-\-comm\-key ++\-\-cck, \-\-comm\-key + .RS 4 + Use the content of FILE as the customer\-communication key (CCK). The file must + contain exactly 32 bytes of data. +@@ -133,7 +133,7 @@ contain exactly 32 bytes of data. + \-\-enable\-dump + .RS 4 + Enable Secure Execution guest dump support. This option requires the +-\fB\-\-comm\-key\fR option. ++\fB\-\-cck\fR option. + .RE + .RE + .PP +@@ -146,8 +146,7 @@ Disable Secure Execution guest dump support (default). + \-\-enable\-cck\-extension\-secret + .RS 4 + Add\-secret requests must provide an extension secret that matches the +-CCK\-derived extension secret. This option requires the \fB\-\-comm\-key\fR +-option. ++CCK\-derived extension secret. This option requires the \fB\-\-cck\fR option. + .RE + .RE + .PP +@@ -268,7 +267,7 @@ Generate an IBM Secure Execution image: + + Generate an IBM Secure Execution image with Secure Execution guest dump support: + .PP +-.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-comm\-key \fI\,comm-key\fR ++.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm-key\fR + .SH NOTES + .IP "1." 4 + The \fBgenprotimg\fR(1) command is a symbolic link to the \fBpvimg-create\fR(1) command. +diff --git a/rust/pvimg/man/pvimg-create.1 b/rust/pvimg/man/pvimg-create.1 +index 6670704..d6b4c3a 100644 +--- a/rust/pvimg/man/pvimg-create.1 ++++ b/rust/pvimg/man/pvimg-create.1 +@@ -3,7 +3,7 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvimg-create 1 "2024-12-12" "s390-tools" "Pvimg Manual" ++.TH pvimg-create 1 "2024-12-17" "s390-tools" "Pvimg Manual" + .nh + .ad l + .SH NAME +@@ -122,7 +122,7 @@ Overwrite an existing Secure Execution boot image. + .RE + .RE + .PP +-\-\-comm\-key ++\-\-cck, \-\-comm\-key + .RS 4 + Use the content of FILE as the customer\-communication key (CCK). The file must + contain exactly 32 bytes of data. +@@ -132,7 +132,7 @@ contain exactly 32 bytes of data. + \-\-enable\-dump + .RS 4 + Enable Secure Execution guest dump support. This option requires the +-\fB\-\-comm\-key\fR option. ++\fB\-\-cck\fR option. + .RE + .RE + .PP +@@ -145,8 +145,7 @@ Disable Secure Execution guest dump support (default). + \-\-enable\-cck\-extension\-secret + .RS 4 + Add\-secret requests must provide an extension secret that matches the +-CCK\-derived extension secret. This option requires the \fB\-\-comm\-key\fR +-option. ++CCK\-derived extension secret. This option requires the \fB\-\-cck\fR option. + .RE + .RE + .PP +@@ -249,7 +248,7 @@ Generate an IBM Secure Execution image: + + Generate an IBM Secure Execution image with Secure Execution guest dump support: + .PP +-.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-comm\-key \fI\,comm-key\fR ++.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm-key\fR + .SH NOTES + .IP "1." 4 + The \fBgenprotimg\fR(1) command is a symbolic link to the \fBpvimg-create\fR(1) command. +diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs +index 12f0b76..e622e1d 100644 +--- a/rust/pvimg/src/cli.rs ++++ b/rust/pvimg/src/cli.rs +@@ -96,8 +96,8 @@ pub struct ComponentPaths { + #[command(group(ArgGroup::new("header-flags").multiple(true).conflicts_with_all(["x_pcf", "x_scf"])))] + pub struct CreateBootImageLegacyFlags { + /// Enable Secure Execution guest dump support. This option requires the +- /// '--comm-key' option. +- #[arg(long, action = clap::ArgAction::SetTrue, requires="comm_key", group="header-flags")] ++ /// '--cck' option. ++ #[arg(long, action = clap::ArgAction::SetTrue, requires="cck", group="header-flags")] + pub enable_dump: Option, + + /// Disable Secure Execution guest dump support (default). +@@ -105,9 +105,9 @@ pub struct CreateBootImageLegacyFlags { + pub disable_dump: Option, + + /// Add-secret requests must provide an extension secret that matches the +- /// CCK-derived extension secret. This option requires the '--comm-key' ++ /// CCK-derived extension secret. This option requires the '--cck' + /// option. +- #[arg(long, action = clap::ArgAction::SetTrue, requires="comm_key", group="header-flags")] ++ #[arg(long, action = clap::ArgAction::SetTrue, requires="cck", group="header-flags")] + pub enable_cck_extension_secret: Option, + + /// Add-secret requests don't have to provide the CCK-derived extension +@@ -328,8 +328,8 @@ pub struct CreateBootImageArgs { + /// Use the content of FILE as the customer-communication key (CCK). + /// + /// The file must contain exactly 32 bytes of data. +- #[arg(long, value_name = "FILE")] +- pub comm_key: Option, ++ #[arg(long, value_name = "FILE", visible_alias = "comm-key")] ++ pub cck: Option, + + #[clap(flatten)] + pub legacy_flags: CreateBootImageLegacyFlags, +@@ -481,6 +481,8 @@ mod test { + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("parmfile", ["--parmfile", "/dev/null"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]), + CliOption::new("comm-key", ["--comm-key", "/dev/null"])])), ++ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]), ++ CliOption::new("comm-key", ["--cck", "/dev/null"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]), + CliOption::new("comm-key", ["--comm-key", "/dev/null"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-pcf", ["--x-pcf", "0x0"]), +diff --git a/rust/pvimg/src/cmd/create.rs b/rust/pvimg/src/cmd/create.rs +index 475d352..cc754a1 100644 +--- a/rust/pvimg/src/cmd/create.rs ++++ b/rust/pvimg/src/cmd/create.rs +@@ -137,8 +137,7 @@ pub fn create(opt: &CreateBootImageArgs) -> Result { + let verified_host_keys = opt + .certificate_args + .get_verified_hkds("Secure Execution image")?; +- let user_provided_keys = +- read_user_provided_keys(opt.comm_key.as_deref(), &opt.experimental_args)?; ++ let user_provided_keys = read_user_provided_keys(opt.cck.as_deref(), &opt.experimental_args)?; + let (plaintext_flags, secret_flags) = parse_flags(opt)?; + + if plaintext_flags.is_set(PcfV1::NoComponentEncryption) { +-- +2.47.1 + + +From 2a9d164010c0eaa6098083062ac0cdcb9be84b78 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Wed, 8 Jan 2025 12:33:05 +0100 +Subject: [PATCH 07/31] rust/pvimg: Document the change from '--comm-key' to + '--cck' in the help message (RHEL-72022) + +This fixes problems when users search for '--comm-key' in the help +message. + +Fixes: 5b6d7a467dc3 ("rust/pvimg: Add '--cck ' command line option and make '--comm-key' an alias") +Reviewed-by: Steffen Eiden +Reviewed-by: Nico Boehr +Signed-off-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +(cherry picked from commit 7bc12d0202d5819442dd4c32755feb5eb19af70b) +--- + rust/pvimg/src/cli.rs | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs +index e622e1d..3e09a40 100644 +--- a/rust/pvimg/src/cli.rs ++++ b/rust/pvimg/src/cli.rs +@@ -327,7 +327,8 @@ pub struct CreateBootImageArgs { + + /// Use the content of FILE as the customer-communication key (CCK). + /// +- /// The file must contain exactly 32 bytes of data. ++ /// The file must contain exactly 32 bytes of data. This option used to be ++ /// called '--comm-key' in previous versions. + #[arg(long, value_name = "FILE", visible_alias = "comm-key")] + pub cck: Option, + +-- +2.47.1 + + +From fa2dcf81a6c002192f351040ff68f8d60370e93c Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Fri, 6 Dec 2024 20:45:36 +0100 +Subject: [PATCH 08/31] rust/pvimg: Fix possible 'range start index out of + range for slice' error (RHEL-71821) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fix possible 'range start index 16 out of range for slice of length 0' +error by adding a check of the slice data length. + +Fixes: f4cf4ae6ebb1 ("rust: Add a new tool called 'pvimg'") +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit 560b276f7e9938475af921c8ebd4cd05910dbf31) +--- + rust/pvimg/src/pv_utils/se_hdr/brb.rs | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs +index f7ae1bc..ac3a2e6 100644 +--- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs ++++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs +@@ -259,6 +259,10 @@ impl SeHdr { + return Err(Error::InvalidSeHdr); + } + ++ if sehs <= common_size { ++ return Err(Error::InvalidSeHdr); ++ } ++ + data.resize(sehs, 0); + reader.read_exact(&mut data[common_size..])?; + Self::try_from_data(&data) +@@ -366,3 +370,22 @@ impl AeadCipherTrait for SeHdrPlain { + self.data.aead_tag_size() + } + } ++ ++#[cfg(test)] ++mod tests { ++ use std::io::Cursor; ++ ++ use super::SeHdr; ++ use crate::error::Error; ++ ++ #[test] ++ fn test_sehdr_try_from_io() { ++ // Invalid SeHdr as `sehs` is set to 0 ++ assert!(matches!( ++ SeHdr::try_from_io(Cursor::new([ ++ 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 8 ++ ])), ++ Err(Error::InvalidSeHdr) ++ )); ++ } ++} +-- +2.47.1 + + +From 979b4bad1653a7b897a43e8fe7ee393de77fc4e4 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Tue, 17 Dec 2024 12:20:30 +0100 +Subject: [PATCH 09/31] pvimg: Add '--hdr-key' command line option to 'pvimg + create' (RHEL-71821) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add '--hdr-key ' as a command line option to the 'pvimg create' +command. This key can then be used later to decrypt the Secure Execution +header of a Secure Execution image, e.g. 'pvimg info --key +--format json '. While updating the manpages, add missing hyphen +escapes in the manpages. + +Reviewed-by: Steffen Eiden +Acked-by: Hendrik Brueckner +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit 3b8fdcc892a38498edf73dd16328532be155fb70) +--- + rust/pvimg/man/genprotimg.1 | 18 +++++++++++++----- + rust/pvimg/man/pvimg-create.1 | 18 +++++++++++++----- + rust/pvimg/man/pvimg-info.1 | 6 ++++-- + rust/pvimg/src/cli.rs | 19 ++++++++++++++----- + rust/pvimg/src/cmd/common.rs | 3 ++- + rust/pvimg/src/cmd/create.rs | 6 +++++- + 6 files changed, 51 insertions(+), 19 deletions(-) + +diff --git a/rust/pvimg/man/genprotimg.1 b/rust/pvimg/man/genprotimg.1 +index feba391..bc3375e 100644 +--- a/rust/pvimg/man/genprotimg.1 ++++ b/rust/pvimg/man/genprotimg.1 +@@ -130,6 +130,14 @@ contain exactly 32 bytes of data. + .RE + .RE + .PP ++\-\-hdr\-key ++.RS 4 ++Use the content of FILE as the Secure Execution header protection key. The file ++must contain exactly 32 bytes of data. If the option is not specified, the ++Secure Execution header protection key is a randomly generated key. ++.RE ++.RE ++.PP + \-\-enable\-dump + .RS 4 + Enable Secure Execution guest dump support. This option requires the +@@ -252,22 +260,22 @@ line flag, or wrong number of arguments. + .RE + .SH EXAMPLES + These are examples of how to generate an IBM Secure Execution image in +-\fI\,/boot/secure-linux\/\fR, using the kernel file \fI\,/boot/vmlinuz\/\fR, the ++\fI\,/boot/secure\-linux\/\fR, using the kernel file \fI\,/boot/vmlinuz\/\fR, the + initrd in \fI\,/boot/initrd.img\/\fR, the kernel parameters contained in + \fI\,parmfile\/\fR, the intermediate CA in \fI\,DigiCertCA.crt\/\fR, the IBM Z +-signing key in \fI\,ibm-z-host-key-signing.crt\/\fR, and the host-key document +-in \fI\,host_key.crt\/\fR. An AES-256 GCM key is stored in \fI\,comm-key\/\fR, ++signing key in \fI\,ibm\-z\-host\-key\-signing.crt\/\fR, and the host-key document ++in \fI\,host_key.crt\/\fR. An AES-256 GCM key is stored in \fI\,comm\-key\/\fR, + which is used when creating a Secure Execution image with guest dump support + enabled in the second example. + + Generate an IBM Secure Execution image: + + .PP +-.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR ++.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm\-z\-host\-key\-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure\-linux\/\fR + + Generate an IBM Secure Execution image with Secure Execution guest dump support: + .PP +-.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm-key\fR ++.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm\-z\-host\-key\-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure\-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm-key\fR + .SH NOTES + .IP "1." 4 + The \fBgenprotimg\fR(1) command is a symbolic link to the \fBpvimg-create\fR(1) command. +diff --git a/rust/pvimg/man/pvimg-create.1 b/rust/pvimg/man/pvimg-create.1 +index d6b4c3a..1a12e6e 100644 +--- a/rust/pvimg/man/pvimg-create.1 ++++ b/rust/pvimg/man/pvimg-create.1 +@@ -129,6 +129,14 @@ contain exactly 32 bytes of data. + .RE + .RE + .PP ++\-\-hdr\-key ++.RS 4 ++Use the content of FILE as the Secure Execution header protection key. The file ++must contain exactly 32 bytes of data. If the option is not specified, the ++Secure Execution header protection key is a randomly generated key. ++.RE ++.RE ++.PP + \-\-enable\-dump + .RS 4 + Enable Secure Execution guest dump support. This option requires the +@@ -233,22 +241,22 @@ line flag, or wrong number of arguments. + .RE + .SH EXAMPLES + These are examples of how to generate an IBM Secure Execution image in +-\fI\,/boot/secure-linux\/\fR, using the kernel file \fI\,/boot/vmlinuz\/\fR, the ++\fI\,/boot/secure\-linux\/\fR, using the kernel file \fI\,/boot/vmlinuz\/\fR, the + initrd in \fI\,/boot/initrd.img\/\fR, the kernel parameters contained in + \fI\,parmfile\/\fR, the intermediate CA in \fI\,DigiCertCA.crt\/\fR, the IBM Z +-signing key in \fI\,ibm-z-host-key-signing.crt\/\fR, and the host-key document +-in \fI\,host_key.crt\/\fR. An AES-256 GCM key is stored in \fI\,comm-key\/\fR, ++signing key in \fI\,ibm\-z\-host\-key\-signing.crt\/\fR, and the host-key document ++in \fI\,host_key.crt\/\fR. An AES-256 GCM key is stored in \fI\,comm\-key\/\fR, + which is used when creating a Secure Execution image with guest dump support + enabled in the second example. + + Generate an IBM Secure Execution image: + + .PP +-.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR ++.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm\-z\-host\-key\-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure\-linux\/\fR + + Generate an IBM Secure Execution image with Secure Execution guest dump support: + .PP +-.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm-key\fR ++.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm\-z\-host\-key\-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure\-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm\-key\fR + .SH NOTES + .IP "1." 4 + The \fBgenprotimg\fR(1) command is a symbolic link to the \fBpvimg-create\fR(1) command. +diff --git a/rust/pvimg/man/pvimg-info.1 b/rust/pvimg/man/pvimg-info.1 +index d2726c3..82b42da 100644 +--- a/rust/pvimg/man/pvimg-info.1 ++++ b/rust/pvimg/man/pvimg-info.1 +@@ -3,7 +3,7 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvimg-info 1 "2024-12-11" "s390-tools" "Pvimg Manual" ++.TH pvimg-info 1 "2024-12-17" "s390-tools" "Pvimg Manual" + .nh + .ad l + .SH NAME +@@ -39,7 +39,9 @@ Possible values: + .PP + \-\-key + .RS 4 +-Use the key in FILE to decrypt the Secure Execution header. ++Use the key in FILE to decrypt the Secure Execution header. It is the key that ++was specified with the command line option \fB\-\-hdr\-key\fR at the Secure ++Execution image creation. + .RE + .RE + .PP +diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs +index 3e09a40..5af3636 100644 +--- a/rust/pvimg/src/cli.rs ++++ b/rust/pvimg/src/cli.rs +@@ -192,6 +192,9 @@ pub struct InfoArgs { + pub format: OutputFormat, + + /// Use the key in FILE to decrypt the Secure Execution header. ++ /// ++ /// It is the key that was specified with the command line option ++ /// '--hdr-key' at the Secure Execution image creation. + #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)] + pub key: Option, + } +@@ -332,6 +335,14 @@ pub struct CreateBootImageArgs { + #[arg(long, value_name = "FILE", visible_alias = "comm-key")] + pub cck: Option, + ++ /// Use the content of FILE as the Secure Execution header protection key. ++ /// ++ /// The file must contain exactly 32 bytes of data. If the option is not ++ /// specified, the Secure Execution header protection key is a randomly ++ /// generated key. ++ #[arg(long, value_name = "FILE", alias = "x-header-key")] ++ pub hdr_key: Option, ++ + #[clap(flatten)] + pub legacy_flags: CreateBootImageLegacyFlags, + +@@ -353,11 +364,6 @@ pub struct CreateBootImageExperimentalArgs { + #[arg(long, value_name = "FILE", hide(true))] + pub x_comp_key: Option, + +- /// Manually set the Secure Execution header protection key (experimental option). +- // Hidden in user documentation. +- #[arg(long, value_name = "FILE", hide(true))] +- pub x_header_key: Option, +- + /// Manually set the PSW address used for the Secure Execution header (experimental option). + // Hidden in user documentation. + #[arg(long, value_name = "ADDRESS", hide(true))] +@@ -495,6 +501,8 @@ mod test { + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-backup-keys", ["--enable-backup-keys"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("disable-image-encryption", ["--disable-image-encryption"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-image-encryption", ["--enable-image-encryption"])])), ++ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-header-key", ["--x-header-key", "/dev/null"]),])), ++ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-header-key", ["--hdr-key", "/dev/null"]),])), + ]; + let invalid_create_args = [ + flat_map_collect(remove(mvcanv.clone(), "no-verify")), +@@ -522,6 +530,7 @@ mod test { + CliOption::new("disable-pckmo", ["--disable-pckmo"])])), + flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-image-encryption", ["--enable-image-encryption"]), + CliOption::new("disable-image-encryption", ["--disable-image-encryption"])])), ++ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-header-key", ["--hdr-key"]),])), + ]; + + let mut genprotimg_valid_args = vec![ +diff --git a/rust/pvimg/src/cmd/common.rs b/rust/pvimg/src/cmd/common.rs +index 6b46ced..8da99d6 100644 +--- a/rust/pvimg/src/cmd/common.rs ++++ b/rust/pvimg/src/cmd/common.rs +@@ -25,6 +25,7 @@ pub struct UserProvidedKeys { + /// Reads all user provided keys. + pub fn read_user_provided_keys( + cck_path: Option<&Path>, ++ hdr_key_path: Option<&Path>, + experimental_args: &CreateBootImageExperimentalArgs, + ) -> Result { + let components_key = { +@@ -43,7 +44,7 @@ pub fn read_user_provided_keys( + } + }; + let aead_key = { +- match &experimental_args.x_header_key { ++ match hdr_key_path { + Some(key_path) => { + info!( + "Use file '{}' as the Secure Execution header protection", +diff --git a/rust/pvimg/src/cmd/create.rs b/rust/pvimg/src/cmd/create.rs +index cc754a1..3e2ca65 100644 +--- a/rust/pvimg/src/cmd/create.rs ++++ b/rust/pvimg/src/cmd/create.rs +@@ -137,7 +137,11 @@ pub fn create(opt: &CreateBootImageArgs) -> Result { + let verified_host_keys = opt + .certificate_args + .get_verified_hkds("Secure Execution image")?; +- let user_provided_keys = read_user_provided_keys(opt.cck.as_deref(), &opt.experimental_args)?; ++ let user_provided_keys = read_user_provided_keys( ++ opt.cck.as_deref(), ++ opt.hdr_key.as_deref(), ++ &opt.experimental_args, ++ )?; + let (plaintext_flags, secret_flags) = parse_flags(opt)?; + + if plaintext_flags.is_set(PcfV1::NoComponentEncryption) { +-- +2.47.1 + + +From 9155c5e49a8fff05d479ddc81d8d25e819278803 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Wed, 18 Dec 2024 13:41:13 +0100 +Subject: [PATCH 10/31] rust/utils: mkdtemp: fix memory leak (RHEL-71821) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fix memory leak of @template_raw. The documentation of CString::into_raw +reads: + +"Consumes the CString and transfers ownership of the string to a C +caller. +... +Failure to call CString::from_raw will lead to a memory leak." [1] + +Let's fix the memory leak by always calling `CString::from_raw` and +therefore reclaim the ownership. + +[1] https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw + +Fixes: e56acf4f14b0 ("pv_core: add `TemporaryDirectory`") +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit 3f6572e901ddcc654021c4302cb2a99999acb87a) +--- + rust/utils/src/tmpfile.rs | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/rust/utils/src/tmpfile.rs b/rust/utils/src/tmpfile.rs +index 07acdba..883d558 100644 +--- a/rust/utils/src/tmpfile.rs ++++ b/rust/utils/src/tmpfile.rs +@@ -16,13 +16,14 @@ fn mkdtemp>(template: P) -> Result { + // SAFETY: template_raw is a valid CString because it was generated by + // the `CString::new`. + let ret = libc::mkdtemp(template_raw); ++ // SAFETY: `template_raw` is still a valid CString because it was ++ // generated by `CString::new` and modified by `libc::mkdtemp`. ++ let path_cstr = std::ffi::CString::from_raw(template_raw); + + if ret.is_null() { ++ drop(path_cstr); + Err(std::io::Error::last_os_error()) + } else { +- // SAFETY: `template_raw` is still a valid CString because it was +- // generated by `CString::new` and modified by `libc::mkdtemp`. +- let path_cstr = std::ffi::CString::from_raw(template_raw); + let path = OsStr::from_bytes(path_cstr.as_bytes()); + let path = std::path::PathBuf::from(path); + +-- +2.47.1 + + +From e412cecd6ad9be261b17cf9ad64b786c7da58e4f Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Tue, 17 Dec 2024 11:58:01 +0100 +Subject: [PATCH 11/31] rust/pvimg: Add upper estimates for the Secure + Execution header (RHEL-71821) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +A Secure Execution header V1 can be at maximum two pages large, optional +items are not supported, and the size of the encrypted part cannot be +larger than the total size of the Secure Execution header add this as +Deku assertions and additional conditions to the code. In addition, add +a check for the number of key slots. + +Fixes: f4cf4ae6ebb1 ("rust: Add a new tool called 'pvimg'") +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit 944581eaefe4c6887790f2b8ed39c9ee76146c55) +--- + rust/pvimg/src/pv_utils/error.rs | 3 + + rust/pvimg/src/pv_utils/se_hdr/brb.rs | 50 +++++++++++++--- + rust/pvimg/src/pv_utils/se_hdr/builder.rs | 10 +++- + rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs | 71 ++++++++++++++++++++--- + rust/pvimg/src/pv_utils/uvdata.rs | 18 ++++-- + 5 files changed, 130 insertions(+), 22 deletions(-) + +diff --git a/rust/pvimg/src/pv_utils/error.rs b/rust/pvimg/src/pv_utils/error.rs +index 2a17627..a12c4a2 100644 +--- a/rust/pvimg/src/pv_utils/error.rs ++++ b/rust/pvimg/src/pv_utils/error.rs +@@ -30,6 +30,9 @@ pub enum Error { + #[error("Invalid Secure Execution header")] + InvalidSeHdr, + ++ #[error("Secure Execution header size {given} is larger than the maximum of {maximum} bytes")] ++ InvalidSeHdrTooLarge { given: usize, maximum: usize }, ++ + #[error("Invalid component metadata.")] + InvalidComponentMetadata, + +diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs +index ac3a2e6..b8dadba 100644 +--- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs ++++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs +@@ -171,8 +171,8 @@ impl AeadCipherTrait for SeHdr { + } + + impl AeadDataTrait for SeHdr { +- fn aad(&self) -> Vec { +- [serialize_to_bytes(&self.common).unwrap(), self.data.aad()].concat() ++ fn aad(&self) -> Result> { ++ Ok([serialize_to_bytes(&self.common)?, self.data.aad()?].concat()) + } + + fn data(&self) -> Vec { +@@ -265,7 +265,7 @@ impl SeHdr { + + data.resize(sehs, 0); + reader.read_exact(&mut data[common_size..])?; +- Self::try_from_data(&data) ++ Self::try_from_data(&data).map_err(|_| Error::InvalidSeHdr) + } + } + +@@ -342,13 +342,13 @@ impl UvDataPlainTrait for SeHdrPlain { + } + + impl AeadPlainDataTrait for SeHdrPlain { +- fn aad(&self) -> Vec { +- let data_aad = self.data.aad(); ++ fn aad(&self) -> Result> { ++ let data_aad = self.data.aad()?; + +- [serialize_to_bytes(&self.common).unwrap(), data_aad].concat() ++ Ok([serialize_to_bytes(&self.common)?, data_aad].concat()) + } + +- fn data(&self) -> Confidential> { ++ fn data(&self) -> Result>> { + self.data.data() + } + +@@ -387,5 +387,41 @@ mod tests { + ])), + Err(Error::InvalidSeHdr) + )); ++ ++ // Invalid SeHdr as the `sehs` is too large. ++ assert!(matches!( ++ SeHdr::try_from_io(Cursor::new([ ++ 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 1, 0, 0, 0, 1, 255, 65, 65, 65, 65, 67, 0, ++ 65, 17, 65, 0, 65, 65, 65, 65, 65, 65, 91, 91, 180, 91, 91, 91, 91, 91, 91, 91, 91, ++ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 241, 241, ++ 241, 241, 241, 91, 91, 91, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, ++ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 80, ++ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, ++ 112, 112, 112, 112, 91, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, ++ 112, 112, 112, 112, 112, 112, 112, 0, 0, 0, 0, 101, 99, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 65, 65, 65, 65, 67, 0, 65, 17, 65, 0, 65, 65, 65, 65, ++ 65, 65, 91, 91, 180, 91, 91, 91, 91, 91, 91, 91, 91, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, 241, 241, 241, 241, 241, 91, 91, 91, 112, ++ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, ++ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 80, 112, 112, 112, 112, 112, 112, ++ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 91, 112, 112, ++ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 73, 66, 77, 83, 101, 99, 69, 120, ++ 0, 112, 112, 0, 1, 0, 0, 0, 0, 101, 99, 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 65, 65, 65, 65, 67, 0, 65, 17, 65, 0, 65, 65, 65, 65, 65, 65, 91, ++ 91, 180, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, ++ 91, 91, 112, 112, 112, 112, 112, 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 1, 0, 0, ++ 0, 0, 48, 53, 53, 53, 53, 53, 53, 53, 91, 91, 91, 241, 241, 46, 49, 49, 0, 49, 49, ++ 0, 0, 112, 112, 112, 91, 0, 0, 0, 0, 9, 0, 49, 50, 22, 241, 241, 241, 241, 241, ++ 241, 241, 241, 241, 241, 241, 91, 91, 91, 91, 91, 255, 251, 0, 0, 91, 91, 91, 91, ++ 91, 91, 91, 91, 91, 91, 91, 0, 0, 91, 0, 0, 10, 91, 91, 91, 65, 65, 65, 65 ++ ])), ++ Err(Error::InvalidSeHdr) ++ )); + } + } +diff --git a/rust/pvimg/src/pv_utils/se_hdr/builder.rs b/rust/pvimg/src/pv_utils/se_hdr/builder.rs +index ba6de89..93bcc7a 100644 +--- a/rust/pvimg/src/pv_utils/se_hdr/builder.rs ++++ b/rust/pvimg/src/pv_utils/se_hdr/builder.rs +@@ -230,8 +230,14 @@ mod tests { + + let decrypted = bin.decrypt(&prot_key).expect("BUG"); + assert_eq!(bin.common, decrypted.common); +- assert_eq!(bin.aad(), decrypted.aad()); +- assert_ne!(&bin.data(), decrypted.data().value()); ++ assert_eq!( ++ bin.aad().expect("should not fail"), ++ decrypted.aad().expect("should not fail") ++ ); ++ assert_ne!( ++ &bin.data(), ++ decrypted.data().expect("should not fail").value() ++ ); + let _decrypted_hdrv1: SeHdrDataV1 = decrypted.data.try_into().expect("BUG"); + } + +diff --git a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs +index a7f2f60..b179d50 100644 +--- a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs ++++ b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs +@@ -19,6 +19,7 @@ use serde::{Serialize, Serializer}; + use super::keys::phkh_v1; + use crate::{ + error::Error, ++ misc::PAGESIZE, + pv_utils::{ + error::Result, + se_hdr::{ +@@ -51,11 +52,14 @@ struct HdrSizesV1 { + #[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] + #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] + struct SeHdrAadV1 { ++ #[deku(assert = "*sehs <= SeHdrDataV1::MAX_SIZE.try_into().unwrap()")] + sehs: u32, + #[serde(serialize_with = "ser_hex")] + iv: [u8; SymKeyType::AES_256_GCM_IV_LEN], + res1: u32, ++ #[deku(assert = "*nks <= (*sehs).into()", update = "self.keyslots.len()")] + nks: u64, ++ #[deku(assert = "*sea <= (*sehs).into()")] + sea: u64, + nep: u64, + #[serde(serialize_with = "ser_lower_hex")] +@@ -118,6 +122,7 @@ pub struct SeHdrConfV1 { + psw: PSW, + #[serde(serialize_with = "ser_lower_hex")] + scf: u64, ++ #[deku(assert_eq = "0")] + noi: u32, + res2: u32, + #[deku(count = "noi")] +@@ -200,6 +205,7 @@ where + } + + impl SeHdrDataV1 { ++ const MAX_SIZE: usize = 2 * PAGESIZE; + const PCF_DEFAULT: u64 = 0x0; + const SCF_DEFAULT: u64 = 0x0; + +@@ -241,7 +247,14 @@ impl SeHdrDataV1 { + tag: SeHdrTagV1::default(), + }; + let hdr_size = ret.size()?; +- ret.aad.sehs = hdr_size.phs.try_into()?; ++ let phs = hdr_size.phs.try_into()?; ++ if phs > Self::MAX_SIZE { ++ return Err(Error::InvalidSeHdrTooLarge { ++ given: phs, ++ maximum: Self::MAX_SIZE, ++ }); ++ } ++ ret.aad.sehs = phs.try_into()?; + ret.aad.sea = hdr_size.sea; + Ok(ret) + } +@@ -494,8 +507,8 @@ impl KeyExchangeTrait for SeHdrBinV1 { + } + + impl AeadDataTrait for SeHdrBinV1 { +- fn aad(&self) -> Vec { +- serialize_to_bytes(&self.aad).unwrap() ++ fn aad(&self) -> Result> { ++ serialize_to_bytes(&self.aad) + } + + fn data(&self) -> Vec { +@@ -508,12 +521,12 @@ impl AeadDataTrait for SeHdrBinV1 { + } + + impl AeadPlainDataTrait for SeHdrDataV1 { +- fn aad(&self) -> Vec { +- serialize_to_bytes(&self.aad).unwrap() ++ fn aad(&self) -> Result> { ++ serialize_to_bytes(&self.aad) + } + +- fn data(&self) -> Confidential> { +- serialize_to_bytes(self.data.value()).unwrap().into() ++ fn data(&self) -> Result>> { ++ Ok(serialize_to_bytes(self.data.value())?.into()) + } + + fn tag(&self) -> Vec { +@@ -610,4 +623,48 @@ mod tests { + assert_eq!(psw, hdr_data_v1.data.value().psw); + assert_eq!(cck.value(), hdr_data_v1.data.value().cck.value()); + } ++ ++ #[test] ++ fn max_size_sehdr_test() { ++ const MAX_HOST_KEYS: usize = 95; ++ ++ let (_, host_key) = get_test_key_and_cert(); ++ let pub_key = host_key.public_key().unwrap(); ++ let host_keys_max: Vec<_> = (0..MAX_HOST_KEYS).map(|_| pub_key.clone()).collect(); ++ let too_many_host_keys: Vec<_> = (0..MAX_HOST_KEYS + 1).map(|_| pub_key.clone()).collect(); ++ let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]); ++ let meta = ComponentMetadataV1 { ++ ald: [0x1; SHA_512_HASH_LEN], ++ pld: [0x2; SHA_512_HASH_LEN], ++ tld: [0x3; SHA_512_HASH_LEN], ++ nep: 3, ++ key: xts_key, ++ }; ++ let psw = PSW { ++ addr: 1234, ++ mask: 5678, ++ }; ++ ++ let mut builder = SeHdrBuilder::new(SeHdrVersion::V1, psw.clone(), meta.clone()) ++ .expect("should not fail"); ++ builder ++ .add_hostkeys(&host_keys_max) ++ .expect("should not fail") ++ .with_components(meta.clone()) ++ .expect("should not fail"); ++ let bin = builder.build().expect("should not fail"); ++ assert_eq!(bin.common.version, SeHdrVersion::V1); ++ let hdr_v1: SeHdrBinV1 = bin.data.try_into().expect("should not fail"); ++ assert_eq!(hdr_v1.aad.sehs, 8160); ++ ++ let mut builder = SeHdrBuilder::new(SeHdrVersion::V1, psw.clone(), meta.clone()) ++ .expect("should not fail"); ++ ++ builder ++ .add_hostkeys(&too_many_host_keys) ++ .expect("should not fail") ++ .with_components(meta) ++ .expect("should not fail"); ++ assert!(matches!(builder.build(), Err(Error::InvalidSeHdr))); ++ } + } +diff --git a/rust/pvimg/src/pv_utils/uvdata.rs b/rust/pvimg/src/pv_utils/uvdata.rs +index b0ec355..c6ed956 100644 +--- a/rust/pvimg/src/pv_utils/uvdata.rs ++++ b/rust/pvimg/src/pv_utils/uvdata.rs +@@ -34,7 +34,7 @@ pub trait AeadCipherTrait { + #[enum_dispatch] + pub trait AeadDataTrait { + /// Returns the authenticated associated data. +- fn aad(&self) -> Vec; ++ fn aad(&self) -> Result>; + + /// Returns the encrypted data. + fn data(&self) -> Vec; +@@ -47,10 +47,10 @@ pub trait AeadDataTrait { + #[enum_dispatch] + pub trait AeadPlainDataTrait { + /// Returns the authenticated associated data. +- fn aad(&self) -> Vec; ++ fn aad(&self) -> Result>; + + /// Returns the unencrypted data. +- fn data(&self) -> Confidential>; ++ fn data(&self) -> Result>>; + + /// Returns the tag data. + fn tag(&self) -> Vec; +@@ -124,8 +124,14 @@ pub trait UvDataPlainTrait: + expected: self.aead_key_type().to_string(), + }); + } +- let aad = self.aad(); +- let unecrypted_data = self.data(); ++ let aad = self.aad().map_err(|err| match err { ++ Error::Deku(_) => Error::InvalidSeHdr, ++ err => err, ++ })?; ++ let unecrypted_data = self.data().map_err(|err| match err { ++ Error::Deku(_) => Error::InvalidSeHdr, ++ err => err, ++ })?; + let iv = self.iv(); + let result = encrypt_aead(key, iv, &aad, unecrypted_data.value())?; + Self::C::try_from_data(&result.into_buf()) +@@ -169,7 +175,7 @@ pub trait UvDataTrait: AeadDataTrait + AeadCipherTrait + KeyExchangeTrait + Clon + } + + let tag_size = self.aead_tag_size(); +- let aad = self.aad(); ++ let aad = self.aad()?; + let unecrypted_data = self.data(); + let iv = self.iv(); + let tag = self.tag(); +-- +2.47.1 + + +From 1617c8482e0846a3afb4af2772011e4621442f58 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Tue, 17 Dec 2024 18:13:31 +0100 +Subject: [PATCH 12/31] pvimg: info: Rename '--key' into '--hdr-key' and use + '--key' as an alias (RHEL-71821) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Rename '--key' into '--hdr-key' and use '--key' as an (non-visible) +alias for '--hdr-key' in order to keep the command line backwards +compatible. The chances of someone using '--key' are very low, as this +version has not yet been released by any OS distribution. + +This change makes the command line options for the different subcommands +more consistent and therefore easier to use. + +Suggested-by: Reinhard Bündgen +Acked-by: Hendrik Brueckner +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +(cherry picked from commit 6e48c5ebaa26c6bd2a1bc33ccf36ed8bd6946358) +--- + rust/pvimg/man/pvimg-info.1 | 2 +- + rust/pvimg/src/cli.rs | 22 +++++++++++++++++++--- + rust/pvimg/src/cmd/info.rs | 2 +- + 3 files changed, 21 insertions(+), 5 deletions(-) + +diff --git a/rust/pvimg/man/pvimg-info.1 b/rust/pvimg/man/pvimg-info.1 +index 82b42da..aaaa3ab 100644 +--- a/rust/pvimg/man/pvimg-info.1 ++++ b/rust/pvimg/man/pvimg-info.1 +@@ -37,7 +37,7 @@ Possible values: + .RE + .RE + .PP +-\-\-key ++\-\-hdr\-key + .RS 4 + Use the key in FILE to decrypt the Secure Execution header. It is the key that + was specified with the command line option \fB\-\-hdr\-key\fR at the Secure +diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs +index 5af3636..f5a8c30 100644 +--- a/rust/pvimg/src/cli.rs ++++ b/rust/pvimg/src/cli.rs +@@ -195,8 +195,8 @@ pub struct InfoArgs { + /// + /// It is the key that was specified with the command line option + /// '--hdr-key' at the Secure Execution image creation. +- #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)] +- pub key: Option, ++ #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, alias = "key")] ++ pub hdr_key: Option, + } + + #[derive(Args, Debug)] +@@ -722,6 +722,22 @@ mod test { + CliOption::new("image", ["/dev/null"]), + ], + )), ++ flat_map_collect(insert( ++ args.clone(), ++ vec![ ++ CliOption::new("hdr-key", ["--hdr-key", "/dev/null"]), ++ CliOption::new("format", ["--format=json"]), ++ CliOption::new("image", ["/dev/null"]), ++ ], ++ )), ++ flat_map_collect(insert( ++ args.clone(), ++ vec![ ++ CliOption::new("hdr-key", ["--key", "/dev/null"]), ++ CliOption::new("format", ["--format=json"]), ++ CliOption::new("image", ["/dev/null"]), ++ ], ++ )), + // separation between keyword and positional args works + flat_map_collect(insert( + args.clone(), +@@ -762,7 +778,7 @@ mod test { + + // Test for invalid combinations + // Input is missing +- let mut pvimg_invalid_args = vec![vec!["pvimg", "test"]]; ++ let mut pvimg_invalid_args = vec![vec!["pvimg", "info"]]; + + for create_args in &valid_test_args { + pvimg_valid_args.push( +diff --git a/rust/pvimg/src/cmd/info.rs b/rust/pvimg/src/cmd/info.rs +index 1ced054..2f593cf 100644 +--- a/rust/pvimg/src/cmd/info.rs ++++ b/rust/pvimg/src/cmd/info.rs +@@ -27,7 +27,7 @@ pub fn info(opt: &InfoArgs) -> Result { + + SeHdr::seek_sehdr(&mut input, None)?; + let hdr = SeHdr::try_from_io(input)?; +- if let Some(key_path) = &opt.key { ++ if let Some(key_path) = &opt.hdr_key { + let key = + SymKey::try_from_data(hdr.key_type(), read_file(key_path, "Reading key")?.into())?; + serde_json::to_writer_pretty(&mut output, &hdr.decrypt(&key)?)?; +-- +2.47.1 + + +From 78b388c1613724270ec34cef28b7be181f5e0db5 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Thu, 18 Jul 2024 10:55:45 +0200 +Subject: [PATCH 13/31] rust/pvsecret: Refactor writing secret (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Refactor the writing of secret-type dependent output files to ease +extensions. + +Reviewed-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 1e44ace41de3cbd744b22a8f9835473b091186e0) +--- + rust/pv/src/uvsecret/guest_secret.rs | 2 +- + rust/pvsecret/src/cmd/create.rs | 89 +++++++++++++++------------- + 2 files changed, 48 insertions(+), 43 deletions(-) + +diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs +index 509691f..4f1db31 100644 +--- a/rust/pv/src/uvsecret/guest_secret.rs ++++ b/rust/pv/src/uvsecret/guest_secret.rs +@@ -68,7 +68,7 @@ impl GuestSecret { + } + + /// Reference to the confidential data +- pub(crate) fn confidential(&self) -> &[u8] { ++ pub fn confidential(&self) -> &[u8] { + match &self { + Self::Null => &[], + Self::Association { secret, .. } => secret.value().as_slice(), +diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs +index 808b29e..9251c38 100644 +--- a/rust/pvsecret/src/cmd/create.rs ++++ b/rust/pvsecret/src/cmd/create.rs +@@ -62,7 +62,7 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> { + write_out(&opt.output, ser_asrbc, "add-secret request")?; + info!("Successfully wrote the request to '{}'", &opt.output); + +- write_secret(&opt.secret, &asrcb, &opt.output) ++ write_secret(&opt.secret, asrcb.guest_secret(), &opt.output) + } + + /// Read+parse the first key from the buffer. +@@ -206,54 +206,59 @@ fn read_cuid(asrcb: &mut AddSecretRequest, opt: &CreateSecretOpt) -> Result<()> + Ok(()) + } + ++// Write non confidential data (=name+id) to a yaml stdout ++fn write_yaml>( ++ name: &str, ++ guest_secret: &GuestSecret, ++ stdout: &bool, ++ outp_path: P, ++) -> Result<()> { ++ debug!("Non-confidential secret information: {guest_secret:x?}"); ++ ++ let secret_info = serde_yaml::to_string(guest_secret)?; ++ if stdout.to_owned() { ++ println!("{secret_info}"); ++ return Ok(()); ++ } ++ ++ let gen_name: String = name ++ .chars() ++ .map(|c| if c.is_whitespace() { '_' } else { c }) ++ .collect(); ++ let mut yaml_path = outp_path ++ .as_ref() ++ .parent() ++ .with_context(|| format!("Cannot open directory of {:?}", outp_path.as_ref()))? ++ .to_owned(); ++ yaml_path.push(gen_name); ++ yaml_path.set_extension("yaml"); ++ write_out(&yaml_path, secret_info, "secret information")?; ++ warn!( ++ "Successfully wrote secret info to '{}'", ++ yaml_path.display().to_string() ++ ); ++ Ok(()) ++} ++ + /// Write the generated secret (if any) to the specified output stream + fn write_secret>( + secret: &AddSecretType, +- asrcb: &AddSecretRequest, ++ guest_secret: &GuestSecret, + outp_path: P, + ) -> Result<()> { +- if let AddSecretType::Association { +- name, +- stdout, +- output_secret: secret_out, +- .. +- } = secret +- { +- let gen_name: String = name +- .chars() +- .map(|c| if c.is_whitespace() { '_' } else { c }) +- .collect(); +- let mut gen_path = outp_path +- .as_ref() +- .parent() +- .with_context(|| format!("Cannot open directory of {:?}", outp_path.as_ref()))? +- .to_owned(); +- gen_path.push(format!("{gen_name}.yaml")); +- +- // write non confidential data (=name+id) to a yaml +- let secret_info = serde_yaml::to_string(asrcb.guest_secret())?; +- if stdout.to_owned() { +- println!("{secret_info}"); +- } else { +- write_out(&gen_path, secret_info, "association secret info")?; +- debug!( +- "Non-confidential secret information: {:x?}", +- asrcb.guest_secret() +- ); +- warn!( +- "Successfully wrote association info to '{}'", +- gen_path.display() +- ); +- } +- +- if let Some(path) = secret_out { +- if let GuestSecret::Association { secret, .. } = asrcb.guest_secret() { +- write_out(path, secret.value(), "Association secret")? +- } else { +- unreachable!("The secret type has to be `association` at this point (bug)!") ++ match secret { ++ AddSecretType::Association { ++ name, ++ stdout, ++ output_secret, ++ .. ++ } => { ++ write_yaml(name, guest_secret, stdout, outp_path)?; ++ if let Some(path) = output_secret { ++ write_out(path, guest_secret.confidential(), "Association secret")? + } +- info!("Successfully wrote generated association secret to '{path}'"); + } ++ _ => (), + }; + Ok(()) + } +-- +2.47.1 + + +From a5d58d0e6fd5d90ef12956ee7354a3f43d17f2ea Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 5 Mar 2024 10:46:29 +0100 +Subject: [PATCH 14/31] rust/pv: Support for writing data in PEM format + (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Use existing OpenSSL functionalities to create PEM files containing +arbitrary data. + +Acked-by: Marc Hartmayer +Acked-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit d1636168b26cc842bc0766235c8a4f2da9663f20) +--- + rust/pv/src/error.rs | 3 + + rust/pv/src/lib.rs | 6 + + rust/pv/src/openssl_extensions/bio.rs | 85 +++++++ + rust/pv/src/openssl_extensions/mod.rs | 2 + + .../src/openssl_extensions/stackable_crl.rs | 41 +--- + rust/pv/src/pem_utils.rs | 222 ++++++++++++++++++ + 6 files changed, 321 insertions(+), 38 deletions(-) + create mode 100644 rust/pv/src/openssl_extensions/bio.rs + create mode 100644 rust/pv/src/pem_utils.rs + +diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs +index af85e93..3ba808f 100644 +--- a/rust/pv/src/error.rs ++++ b/rust/pv/src/error.rs +@@ -106,6 +106,9 @@ pub enum Error { + )] + AddDataMissing(&'static str), + ++ #[error("An ASCII string was expected, but non-ASCII characters were received.")] ++ NonAscii, ++ + // errors from other crates + #[error(transparent)] + PvCore(#[from] pv_core::Error), +diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs +index 7a33210..ec31b9a 100644 +--- a/rust/pv/src/lib.rs ++++ b/rust/pv/src/lib.rs +@@ -37,6 +37,7 @@ mod brcb; + mod crypto; + mod error; + mod openssl_extensions; ++mod pem_utils; + mod req; + mod utils; + mod uvattest; +@@ -71,6 +72,11 @@ pub mod attest { + }; + } + ++/// Definitions and functions to write objects in PEM format ++pub mod pem { ++ pub use crate::pem_utils::Pem; ++} ++ + /// Miscellaneous functions and definitions + pub mod misc { + pub use pv_core::misc::*; +diff --git a/rust/pv/src/openssl_extensions/bio.rs b/rust/pv/src/openssl_extensions/bio.rs +new file mode 100644 +index 0000000..73528ee +--- /dev/null ++++ b/rust/pv/src/openssl_extensions/bio.rs +@@ -0,0 +1,85 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2024 ++ ++use core::slice; ++use openssl::error::ErrorStack; ++use openssl_sys::BIO_new_mem_buf; ++use std::ffi::c_int; ++use std::{marker::PhantomData, ptr}; ++ ++pub struct BioMem(*mut openssl_sys::BIO); ++ ++impl Drop for BioMem { ++ fn drop(&mut self) { ++ // SAFETY: Pointer is valid. The pointer value is dropped after the free. ++ unsafe { ++ openssl_sys::BIO_free_all(self.0); ++ } ++ } ++} ++ ++impl BioMem { ++ pub fn new() -> Result { ++ openssl_sys::init(); ++ ++ // SAFETY: Returns a valid pointer or null. null-case is tested right after this. ++ let bio = unsafe { openssl_sys::BIO_new(openssl_sys::BIO_s_mem()) }; ++ match bio.is_null() { ++ true => Err(ErrorStack::get()), ++ false => Ok(Self(bio)), ++ } ++ } ++ ++ pub fn as_ptr(&self) -> *mut openssl_sys::BIO { ++ self.0 ++ } ++ ++ /// Copies the content of this slice into a Vec ++ pub fn to_vec(&self) -> Vec { ++ let buf; ++ // SAFTEY: BIO provides a continuous memory that can be used to build a slice. ++ unsafe { ++ let mut ptr = ptr::null_mut(); ++ let len = openssl_sys::BIO_get_mem_data(self.0, &mut ptr); ++ buf = slice::from_raw_parts(ptr as *const _ as *const _, len as usize) ++ } ++ buf.to_vec() ++ } ++} ++ ++pub struct BioMemSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>); ++impl Drop for BioMemSlice<'_> { ++ fn drop(&mut self) { ++ // SAFETY: Pointer is valid. The pointer value is dropped after the free. ++ unsafe { ++ openssl_sys::BIO_free_all(self.0); ++ } ++ } ++} ++ ++impl<'a> BioMemSlice<'a> { ++ pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { ++ openssl_sys::init(); ++ ++ // SAFETY: `buf` is a slice (i.e. pointer+size) pointing to a valid memory region. ++ // So the resulting bio is valid. Lifetime of the slice is connected by this Rust ++ // structure. ++ assert!(buf.len() <= c_int::MAX as usize); ++ let bio = unsafe { ++ { ++ let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int); ++ match r.is_null() { ++ true => Err(ErrorStack::get()), ++ false => Ok(r), ++ } ++ }? ++ }; ++ ++ Ok(BioMemSlice(bio, PhantomData)) ++ } ++ ++ pub fn as_ptr(&self) -> *mut openssl_sys::BIO { ++ self.0 ++ } ++} +diff --git a/rust/pv/src/openssl_extensions/mod.rs b/rust/pv/src/openssl_extensions/mod.rs +index fab2663..f6234e5 100644 +--- a/rust/pv/src/openssl_extensions/mod.rs ++++ b/rust/pv/src/openssl_extensions/mod.rs +@@ -6,8 +6,10 @@ + + /// Extensions to the rust-openssl crate + mod akid; ++mod bio; + mod crl; + mod stackable_crl; + + pub use akid::*; ++pub use bio::*; + pub use crl::*; +diff --git a/rust/pv/src/openssl_extensions/stackable_crl.rs b/rust/pv/src/openssl_extensions/stackable_crl.rs +index aef7cf8..12a9f9d 100644 +--- a/rust/pv/src/openssl_extensions/stackable_crl.rs ++++ b/rust/pv/src/openssl_extensions/stackable_crl.rs +@@ -2,16 +2,14 @@ + // + // Copyright IBM Corp. 2023 + +-use std::{marker::PhantomData, ptr}; +- ++use crate::openssl_extensions::bio::BioMemSlice; + use foreign_types::{ForeignType, ForeignTypeRef}; + use openssl::{ + error::ErrorStack, + stack::Stackable, + x509::{X509Crl, X509CrlRef}, + }; +-use openssl_sys::BIO_new_mem_buf; +-use std::ffi::c_int; ++use std::ptr; + + #[derive(Debug)] + pub struct StackableX509Crl(*mut openssl_sys::X509_CRL); +@@ -62,44 +60,11 @@ impl Stackable for StackableX509Crl { + type StackType = openssl_sys::stack_st_X509_CRL; + } + +-pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>); +-impl Drop for MemBioSlice<'_> { +- fn drop(&mut self) { +- unsafe { +- openssl_sys::BIO_free_all(self.0); +- } +- } +-} +- +-impl<'a> MemBioSlice<'a> { +- pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { +- openssl_sys::init(); +- +- assert!(buf.len() <= c_int::MAX as usize); +- let bio = unsafe { +- { +- let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int); +- if r.is_null() { +- Err(ErrorStack::get()) +- } else { +- Ok(r) +- } +- }? +- }; +- +- Ok(MemBioSlice(bio, PhantomData)) +- } +- +- pub fn as_ptr(&self) -> *mut openssl_sys::BIO { +- self.0 +- } +-} +- + impl StackableX509Crl { + pub fn stack_from_pem(pem: &[u8]) -> Result, ErrorStack> { + unsafe { + openssl_sys::init(); +- let bio = MemBioSlice::new(pem)?; ++ let bio = BioMemSlice::new(pem)?; + + let mut crls = vec![]; + loop { +diff --git a/rust/pv/src/pem_utils.rs b/rust/pv/src/pem_utils.rs +new file mode 100644 +index 0000000..e646251 +--- /dev/null ++++ b/rust/pv/src/pem_utils.rs +@@ -0,0 +1,222 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2024 ++ ++use crate::Result; ++use crate::{openssl_extensions::BioMem, Error}; ++use openssl::error::ErrorStack; ++use pv_core::request::Confidential; ++use std::{ ++ ffi::{c_char, CString}, ++ fmt::Display, ++}; ++ ++mod ffi { ++ use openssl_sys::BIO; ++ use std::ffi::{c_char, c_int, c_long, c_uchar}; ++ extern "C" { ++ pub fn PEM_write_bio( ++ bio: *mut BIO, ++ name: *const c_char, ++ header: *const c_char, ++ data: *const c_uchar, ++ len: c_long, ++ ) -> c_int; ++ } ++} ++ ++/// Thin wrapper around [`CString`] only containing ASCII chars. ++#[derive(Debug)] ++struct AsciiCString(CString); ++ ++impl AsciiCString { ++ /// Convert from string ++ /// ++ /// # Returns ++ /// Error if string is not ASCII or contains null chars ++ pub(crate) fn from_str(s: &str) -> Result { ++ match s.is_ascii() { ++ true => Ok(Self(CString::new(s).map_err(|_| Error::NonAscii)?)), ++ false => Err(Error::NonAscii), ++ } ++ } ++ ++ fn as_ptr(&self) -> *const c_char { ++ self.0.as_ptr() ++ } ++} ++ ++/// Helper struct to construct the PEM format ++#[derive(Debug)] ++struct InnerPem<'d> { ++ name: AsciiCString, ++ header: Option, ++ data: &'d [u8], ++} ++ ++impl<'d> InnerPem<'d> { ++ fn new(name: &str, header: Option, data: &'d [u8]) -> Result { ++ Ok(Self { ++ name: AsciiCString::from_str(name)?, ++ header: match header { ++ Some(h) => Some(AsciiCString::from_str(&h)?), ++ None => None, ++ }, ++ data, ++ }) ++ } ++ ++ /// Generate PEM representation of the data ++ fn to_pem(&self) -> Result> { ++ let bio = BioMem::new()?; ++ let hdr_ptr = match self.header { ++ // avoid moving variable -> use reference ++ Some(ref h) => h.as_ptr(), ++ None => std::ptr::null(), ++ }; ++ ++ // SAFETY: ++ // All pointers point to valid C strings or memory regions ++ let rc = unsafe { ++ ffi::PEM_write_bio( ++ bio.as_ptr(), ++ self.name.as_ptr(), ++ hdr_ptr, ++ self.data.as_ptr(), ++ self.data.len() as std::ffi::c_long, ++ ) ++ }; ++ ++ match rc { ++ 1 => Err(Error::InternalSsl("Could not write PEM", ErrorStack::get())), ++ _ => Ok(bio.to_vec()), ++ } ++ } ++} ++ ++/// Data in PEM format ++/// ++/// Displays into a printable PEM structure. ++/// Must be constructed from another structure in this library. ++/// ++/// ```rust,ignore ++/// let pem: Pem = ...; ++/// println!("PEM {pem}"); ++/// ``` ++/// ```PEM ++///-----BEGIN ----- ++///
++/// ++/// ++///-----END ----- ++ ++#[derive(Debug)] ++pub struct Pem { ++ pem: Confidential, ++} ++ ++#[allow(unused)] ++impl Pem { ++ /// Create a new PEM structure. ++ /// ++ /// # Errors ++ /// ++ /// This function will return an error if name or header contain non-ASCII chars, or OpenSSL ++ /// could not generate the PEM (very likely due to OOM). ++ pub(crate) fn new(name: &str, header: H, data: D) -> Result ++ where ++ D: AsRef<[u8]>, ++ H: Into>, ++ { ++ let mut header = header.into(); ++ let header = match header { ++ Some(h) if h.ends_with('\n') => Some(h), ++ Some(h) if h.is_empty() => None, ++ Some(mut h) => { ++ h.push('\n'); ++ Some(h) ++ } ++ None => None, ++ }; ++ ++ let inner_pem = InnerPem::new(name, header, data.as_ref())?; ++ ++ // Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or OpenSSL Errors ++ // Both error should be very unlikely ++ // OpenSSL should be able to create PEM if there is enough memory and produce a non-null ++ // terminated ASCII-string ++ // Unwrap succeeds it's all ASCII ++ // Std lib implements all the conversations without a copy ++ let pem = CString::new(inner_pem.to_pem()?) ++ .map_err(|_| Error::NonAscii)? ++ .into_string() ++ .unwrap() ++ .into(); ++ ++ Ok(Self { pem }) ++ } ++ ++ /// Converts the PEM-data into a byte vector. ++ /// ++ /// This consumes the `PEM`. ++ #[inline] ++ #[must_use = "`self` will be dropped if the result is not used"] ++ pub fn into_bytes(self) -> Confidential> { ++ self.pem.into_inner().into_bytes().into() ++ } ++} ++ ++impl Display for Pem { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ self.pem.value().fmt(f) ++ } ++} ++ ++#[cfg(test)] ++mod test { ++ use super::*; ++ ++ #[test] ++ fn no_data() { ++ const EXP: &str = ++ "-----BEGIN PEM test-----\ntest hdr value: 17\n\n-----END PEM test-----\n"; ++ let test_pem = Pem::new("PEM test", "test hdr value: 17".to_string(), []).unwrap(); ++ let pem_str = test_pem.to_string(); ++ assert_eq!(pem_str, EXP); ++ } ++ ++ #[test] ++ fn no_hdr() { ++ const EXP: &str = ++ "-----BEGIN PEM test-----\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n"; ++ let test_pem = Pem::new("PEM test", None, "very secret key").unwrap(); ++ let pem_str = test_pem.to_string(); ++ assert_eq!(pem_str, EXP); ++ } ++ ++ #[test] ++ fn some_data() { ++ const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n"; ++ let test_pem = Pem::new( ++ "PEM test", ++ "test hdr value: 17".to_string(), ++ "very secret key", ++ ) ++ .unwrap(); ++ let pem_str = test_pem.to_string(); ++ assert_eq!(pem_str, EXP); ++ } ++ ++ #[test] ++ fn data_linebreak() { ++ const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n"; ++ let test_pem = Pem::new( ++ "PEM test", ++ "test hdr value: 17\n".to_string(), ++ "very secret key", ++ ) ++ .unwrap(); ++ let pem_str = test_pem.to_string(); ++ assert_eq!(pem_str, EXP); ++ } ++} +-- +2.47.1 + + +From 0f2055ca030a868e43e6076cba5cc9cc1277241c Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 20 Feb 2024 14:50:47 +0100 +Subject: [PATCH 15/31] rust/pv_core: Update ffi.rs to linux/uvdevice.h v6.13 + (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +While at it, add a file global #[allow(dead_code)]. +The file is a rustified copy of linux/arch/s390/include/uapi/asm/uvdevice.h +and there might be things that are not needed here but are defined in that header. + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 69eb06f39e5134565babfe96c66a3786c0a571cf) +--- + rust/pv_core/src/uvdevice/ffi.rs | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/rust/pv_core/src/uvdevice/ffi.rs b/rust/pv_core/src/uvdevice/ffi.rs +index bbcc586..3d9998d 100644 +--- a/rust/pv_core/src/uvdevice/ffi.rs ++++ b/rust/pv_core/src/uvdevice/ffi.rs +@@ -2,6 +2,13 @@ + // + // Copyright IBM Corp. 2023 + ++// This file is a rustified copy of linux/arch/s390/include/uapi/asm/uvdevice.h ++// There might be things that are not needed here but nontheless defined in that header. ++// Those two files should be in sync -> there might be unused/dead code. ++// ++// The `UVIO_IOCTL_*` and `UVIO_SUPP_*` macros ++#![allow(dead_code)] ++ + use std::mem::size_of; + + use crate::{assert_size, static_assert}; +@@ -11,9 +18,8 @@ pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000; + pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000; + pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000; + pub const UVIO_ADD_SECRET_MAX_LEN: usize = 0x100000; +-#[allow(unused)] +-// here for completeness + pub const UVIO_LIST_SECRETS_LEN: usize = 0x1000; ++pub const UVIO_RETR_SECRET_MAX_LEN: usize = 0x2000; + + // equal to ascii 'u' + pub const UVIO_TYPE_UVC: u8 = 117u8; +@@ -23,6 +29,7 @@ pub const UVIO_IOCTL_ATT_NR: u8 = 1; + pub const UVIO_IOCTL_ADD_SECRET_NR: u8 = 2; + pub const UVIO_IOCTL_LIST_SECRETS_NR: u8 = 3; + pub const UVIO_IOCTL_LOCK_SECRETS_NR: u8 = 4; ++pub const UVIO_IOCTL_RETR_SECRET_NR: u8 = 5; + + /// Uvdevice IOCTL control block + /// Programs can use this struct to communicate with the uvdevice via IOCTLs +-- +2.47.1 + + +From ad6a20789e0b02bdfe1d7a685b897639fc298c2f Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 5 Mar 2024 11:56:57 +0100 +Subject: [PATCH 16/31] rust/pv_core: Retrieve Secret UVC (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Create the uvdevice-IOCTL functionality for the new Retrieve Secret UVC. + +Reviewed-by: Christoph Schlameuss +Acked-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 01cd81ecf5d1a7e1e504ae1b67692cf63cd4b51d) +--- + rust/pv_core/src/error.rs | 7 ++ + rust/pv_core/src/lib.rs | 2 +- + rust/pv_core/src/uvdevice/secret.rs | 97 +++++++++++++++++++++++- + rust/pv_core/src/uvdevice/secret_list.rs | 15 ++++ + 4 files changed, 118 insertions(+), 3 deletions(-) + +diff --git a/rust/pv_core/src/error.rs b/rust/pv_core/src/error.rs +index 20fca24..ba7b7e2 100644 +--- a/rust/pv_core/src/error.rs ++++ b/rust/pv_core/src/error.rs +@@ -4,6 +4,8 @@ + + use std::path::PathBuf; + ++use crate::uv::SecretId; ++ + /// Result type for this crate + pub type Result = std::result::Result; + +@@ -70,6 +72,11 @@ pub enum Error { + #[error("The attestation request does not specify a measurement size or measurement data.")] + BinArcbNoMeasurement, + ++ #[error( ++ "The secret with the ID {id} cannot be retrieved. The requested size is too large ({size})" ++ )] ++ InvalidRetrievableSecretType { id: SecretId, size: usize }, ++ + // errors from other crates + #[error(transparent)] + Io(#[from] std::io::Error), +diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs +index 349c0b2..5922211 100644 +--- a/rust/pv_core/src/lib.rs ++++ b/rust/pv_core/src/lib.rs +@@ -32,7 +32,7 @@ pub mod misc { + /// [`crate::uv::UvCmd`] + pub mod uv { + pub use crate::uvdevice::attest::AttestationCmd; +- pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd}; ++ pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd, RetrieveCmd}; + pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList}; + pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess}; + } +diff --git a/rust/pv_core/src/uvdevice/secret.rs b/rust/pv_core/src/uvdevice/secret.rs +index 6c22b6e..263f17d 100644 +--- a/rust/pv_core/src/uvdevice/secret.rs ++++ b/rust/pv_core/src/uvdevice/secret.rs +@@ -3,8 +3,15 @@ + // Copyright IBM Corp. 2023 + + use super::ffi; +-use crate::{request::MagicValue, uv::UvCmd, uvsecret::AddSecretMagic, Error, Result, PAGESIZE}; +-use std::io::Read; ++use crate::{ ++ request::{Confidential, MagicValue}, ++ uv::{SecretEntry, UvCmd}, ++ uvsecret::AddSecretMagic, ++ Error, Result, PAGESIZE, ++}; ++use log::debug; ++use std::{io::Read, mem::size_of_val}; ++use zerocopy::AsBytes; + + /// _List Secrets_ Ultravisor command. + /// +@@ -116,3 +123,89 @@ impl UvCmd for LockCmd { + } + } + } ++ ++/// Retrieve a secret value from UV store ++#[derive(Debug)] ++pub struct RetrieveCmd { ++ entry: SecretEntry, ++ key: Confidential>, ++} ++ ++impl RetrieveCmd { ++ /// Maximum size of a retrieved key (=2 pages) ++ pub const MAX_SIZE: usize = ffi::UVIO_RETR_SECRET_MAX_LEN; ++ ++ /// Create a retrieve-secret UVC from a [`SecretEntry`]. ++ /// ++ /// This uses the index of the secret entry for the UVC. ++ pub fn from_entry(entry: SecretEntry) -> Result { ++ entry.try_into() ++ } ++ ++ /// Transform a [`RetrieveCmd`] into a key-vector. ++ /// ++ /// Only makes sense to call after a successful UVC execution. ++ pub fn into_key(self) -> Confidential> { ++ self.key ++ } ++ ++ /// Get the secret entry ++ /// ++ /// Get the secret entry that is used as metadata to retrieve the secret ++ pub fn meta_data(&self) -> &SecretEntry { ++ &self.entry ++ } ++} ++ ++impl TryFrom for RetrieveCmd { ++ type Error = Error; ++ ++ fn try_from(entry: SecretEntry) -> Result { ++ let len = entry.secret_size() as usize; ++ ++ // Next to impossible if the secret entry is a valid response from UV ++ if len > Self::MAX_SIZE { ++ return Err(Error::InvalidRetrievableSecretType { ++ id: entry.secret_id().to_owned(), ++ size: len, ++ }); ++ } ++ ++ // Ensure that an u16 fits into the buffer. ++ let size = std::cmp::max(size_of_val(&entry.index()), len); ++ debug!("Create a buf with {} elements", size); ++ let mut buf = vec![0; size]; ++ // The IOCTL expects the secret index in the first two bytes of the buffer. They will be ++ // overwritten in the response ++ entry.index_be().write_to_prefix(&mut buf).unwrap(); ++ Ok(Self { ++ entry, ++ key: buf.into(), ++ }) ++ } ++} ++ ++impl UvCmd for RetrieveCmd { ++ const UV_IOCTL_NR: u8 = ffi::UVIO_IOCTL_RETR_SECRET_NR; ++ ++ fn rc_fmt(&self, rc: u16, _: u16) -> Option<&'static str> { ++ match rc { ++ // should not appear (TM), software creates request from a list item ++ 0x0009 => Some("the allocated buffer is to small to store the secret"), ++ // should not appear (TM), kernel allocates the memory ++ 0x0102 => { ++ Some("access exception recognized when accessing retrieved secret storage area") ++ } ++ // should not appear (TM), software creates request from a list item ++ 0x010f => Some("the Secret Store is empty"), ++ // should not appear (TM), software creates request from a list item ++ 0x0110 => Some("the Secret Store does not contain a secret with the specified index"), ++ 0x0111 => Some("the secret is not retrievable"), ++ _ => None, ++ } ++ } ++ ++ fn data(&mut self) -> Option<&mut [u8]> { ++ Some(self.key.value_mut()) ++ } ++} +diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs +index d20928b..0a8af50 100644 +--- a/rust/pv_core/src/uvdevice/secret_list.rs ++++ b/rust/pv_core/src/uvdevice/secret_list.rs +@@ -110,6 +110,11 @@ impl SecretEntry { + self.index.get() + } + ++ /// Returns the index of this [`SecretEntry`] in BE. ++ pub(crate) fn index_be(&self) -> &U16 { ++ &self.index ++ } ++ + /// Returns the secret type of this [`SecretEntry`]. + pub fn stype(&self) -> ListableSecretType { + self.stype.into() +@@ -127,6 +132,16 @@ impl SecretEntry { + pub fn id(&self) -> &[u8] { + self.id.as_ref() + } ++ ++ /// Get the id as [`SecretId`] reference ++ pub(crate) fn secret_id(&self) -> &SecretId { ++ &self.id ++ } ++ ++ /// Returns the secret size of this [`SecretEntry`]. ++ pub fn secret_size(&self) -> u32 { ++ self.len.get() ++ } + } + + impl Display for SecretEntry { +-- +2.47.1 + + +From cf2fe8bed95ca8b6513d02a85b83504a68a2584b Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 5 Mar 2024 12:16:44 +0100 +Subject: [PATCH 17/31] rust/pv_core: Support for listing Retrievable Secrets + (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add support for listing retrievable secrets in the List Secrets UVC. + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 4af137f4fad8638169ccf0ddcb6dc4b0fe8fb1c1) +--- + rust/pv_core/src/lib.rs | 2 + + rust/pv_core/src/uvdevice.rs | 1 + + rust/pv_core/src/uvdevice/retr_secret.rs | 399 +++++++++++++++++++++++ + rust/pv_core/src/uvdevice/secret_list.rs | 157 +++++++-- + 4 files changed, 536 insertions(+), 23 deletions(-) + create mode 100644 rust/pv_core/src/uvdevice/retr_secret.rs + +diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs +index 5922211..caebfce 100644 +--- a/rust/pv_core/src/lib.rs ++++ b/rust/pv_core/src/lib.rs +@@ -32,6 +32,8 @@ pub mod misc { + /// [`crate::uv::UvCmd`] + pub mod uv { + pub use crate::uvdevice::attest::AttestationCmd; ++ pub use crate::uvdevice::retr_secret::RetrievableSecret; ++ pub use crate::uvdevice::retr_secret::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes}; + pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd, RetrieveCmd}; + pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList}; + pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess}; +diff --git a/rust/pv_core/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs +index d417681..e984824 100644 +--- a/rust/pv_core/src/uvdevice.rs ++++ b/rust/pv_core/src/uvdevice.rs +@@ -25,6 +25,7 @@ mod info; + mod test; + pub(crate) use ffi::uv_ioctl; + pub mod attest; ++pub mod retr_secret; + pub mod secret; + pub mod secret_list; + +diff --git a/rust/pv_core/src/uvdevice/retr_secret.rs b/rust/pv_core/src/uvdevice/retr_secret.rs +new file mode 100644 +index 0000000..490152b +--- /dev/null ++++ b/rust/pv_core/src/uvdevice/retr_secret.rs +@@ -0,0 +1,399 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2024 ++ ++use crate::uv::{ListableSecretType, RetrieveCmd}; ++use serde::{Deserialize, Serialize, Serializer}; ++use std::fmt::Display; ++ ++/// Allowed sizes for AES keys ++#[non_exhaustive] ++#[derive(PartialEq, Eq, Debug)] ++pub enum AesSizes { ++ /// 128 bit key ++ Bits128, ++ /// 192 bit key ++ Bits192, ++ /// 256 bit key ++ Bits256, ++} ++ ++impl AesSizes { ++ /// Construct the key-size from the bit-size. ++ /// ++ /// Returns [`None`] if the bit-size is not supported. ++ pub fn from_bits(bits: u32) -> Option { ++ match bits { ++ 128 => Some(Self::Bits128), ++ 192 => Some(Self::Bits192), ++ 256 => Some(Self::Bits256), ++ _ => None, ++ } ++ } ++ ++ /// Returns the bit-size for the key-type ++ const fn bit_size(&self) -> u32 { ++ match self { ++ Self::Bits128 => 128, ++ Self::Bits192 => 192, ++ Self::Bits256 => 256, ++ } ++ } ++} ++ ++impl Display for AesSizes { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!(f, "{}", self.bit_size()) ++ } ++} ++ ++/// Allowed sizes for AES-XTS keys ++#[non_exhaustive] ++#[derive(PartialEq, Eq, Debug)] ++pub enum AesXtsSizes { ++ /// Two AES 128 bit keys ++ Bits128, ++ /// Two AES 256 bit keys ++ Bits256, ++} ++ ++impl AesXtsSizes { ++ /// Construct the key-size from the bit-size. ++ /// ++ /// It's a key containing two keys; bit-size is half the number of bits it has ++ /// Returns [`None`] if the bit-size is not supported. ++ pub fn from_bits(bits: u32) -> Option { ++ match bits { ++ 128 => Some(Self::Bits128), ++ 256 => Some(Self::Bits256), ++ _ => None, ++ } ++ } ++ ++ /// Returns the bit-size for the key-type ++ /// ++ /// It's a key containing two keys: bit-size is half the number of bits it has ++ const fn bit_size(&self) -> u32 { ++ match self { ++ Self::Bits128 => 128, ++ Self::Bits256 => 256, ++ } ++ } ++} ++ ++impl Display for AesXtsSizes { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!(f, "{}", self.bit_size()) ++ } ++} ++ ++/// Allowed sizes for HMAC-SHA keys ++#[non_exhaustive] ++#[derive(PartialEq, Eq, Debug)] ++pub enum HmacShaSizes { ++ /// SHA 256 bit ++ Sha256, ++ /// SHA 512 bit ++ Sha512, ++} ++ ++impl HmacShaSizes { ++ /// Construct the key-size from the sha-size. ++ /// ++ /// FW expects maximum resistance keys (double the SHA size). ++ /// The `sha_size` is half of the number of bits in the key ++ /// Returns [`None`] if the `sha_size` is not supported. ++ pub fn from_sha_size(sha_size: u32) -> Option { ++ match sha_size { ++ 256 => Some(Self::Sha256), ++ 512 => Some(Self::Sha512), ++ _ => None, ++ } ++ } ++ ++ /// Returns the sha-size for the key-type ++ /// ++ /// FW expects maximum resistance keys (double the SHA size). ++ /// The `sha_size` is half of the number of bits in the key ++ const fn sha_size(&self) -> u32 { ++ match self { ++ Self::Sha256 => 256, ++ Self::Sha512 => 512, ++ } ++ } ++} ++ ++impl Display for HmacShaSizes { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!(f, "{}", self.sha_size()) ++ } ++} ++ ++/// Allowed curves for EC private keys ++#[non_exhaustive] ++#[derive(PartialEq, Eq, Debug)] ++pub enum EcCurves { ++ /// secp256r1 or prime256v1 curve ++ Secp256R1, ++ /// secp384p1 curve ++ Secp384R1, ++ /// secp521r1 curve ++ Secp521R1, ++ /// ed25519 curve ++ Ed25519, ++ /// ed448 curve ++ Ed448, ++} ++ ++impl EcCurves { ++ const fn exp_size(&self) -> usize { ++ match self { ++ Self::Secp256R1 => 32, ++ Self::Secp384R1 => 48, ++ Self::Secp521R1 => 80, ++ Self::Ed25519 => 32, ++ Self::Ed448 => 64, ++ } ++ } ++ ++ /// Resizes the raw key to the expected size. ++ /// ++ /// See [`Vec::resize`] ++ pub fn resize_raw_key(&self, mut raw: Vec) -> Vec { ++ raw.resize(self.exp_size(), 0); ++ raw ++ } ++} ++ ++// The names have to stay constant, otherwise the PEM contains invalid types ++impl Display for EcCurves { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ match self { ++ Self::Secp256R1 => write!(f, "SECP256R1"), ++ Self::Secp384R1 => write!(f, "SECP384R1"), ++ Self::Secp521R1 => write!(f, "SECP521R1"), ++ Self::Ed25519 => write!(f, "ED25519"), ++ Self::Ed448 => write!(f, "ED448"), ++ } ++ } ++} ++ ++/// Retrievable Secret types ++#[non_exhaustive] ++#[derive(PartialEq, Eq, Debug)] ++pub enum RetrievableSecret { ++ /// Plain-text secret ++ PlainText, ++ /// Protected AES key ++ Aes(AesSizes), ++ /// Protected AES-XTS key ++ AesXts(AesXtsSizes), ++ /// Protected HMAC-SHA key ++ HmacSha(HmacShaSizes), ++ /// Protected EC-private key ++ Ec(EcCurves), ++} ++ ++// The names have to stay constant, otherwise the PEM contains invalid/unknown types ++impl Display for RetrievableSecret { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ // Alternate representation: Omit sizes/curves ++ if f.alternate() { ++ match self { ++ Self::PlainText => write!(f, "PLAINTEXT"), ++ Self::Aes(_) => write!(f, "AES-KEY"), ++ Self::AesXts(_) => write!(f, "AES-XTS-KEY"), ++ Self::HmacSha(_) => write!(f, "HMAC-SHA-KEY"), ++ Self::Ec(_) => write!(f, "EC-PRIVATE-KEY"), ++ } ++ } else { ++ match self { ++ Self::PlainText => write!(f, "PLAINTEXT"), ++ Self::Aes(s) => write!(f, "AES-{s}-KEY"), ++ Self::AesXts(s) => write!(f, "AES-XTS-{s}-KEY"), ++ Self::HmacSha(s) => write!(f, "HMAC-SHA-{s}-KEY"), ++ Self::Ec(c) => write!(f, "EC-{c}-PRIVATE-KEY"), ++ } ++ } ++ } ++} ++ ++impl RetrievableSecret { ++ /// Report expected input types ++ pub fn expected(&self) -> String { ++ match self { ++ Self::PlainText => format!("less than {}", RetrieveCmd::MAX_SIZE), ++ Self::Aes(_) => "128, 192, or 256".to_string(), ++ Self::AesXts(_) => "128 or 256".to_string(), ++ Self::HmacSha(_) => "256 or 512".to_string(), ++ Self::Ec(_) => "secp256r1, secp384r1, secp521r1, ed25519, or ed448".to_string(), ++ } ++ } ++} ++ ++impl From<&RetrievableSecret> for u16 { ++ fn from(value: &RetrievableSecret) -> Self { ++ match value { ++ RetrievableSecret::PlainText => ListableSecretType::PLAINTEXT, ++ RetrievableSecret::Aes(AesSizes::Bits128) => ListableSecretType::AES_128_KEY, ++ RetrievableSecret::Aes(AesSizes::Bits192) => ListableSecretType::AES_192_KEY, ++ RetrievableSecret::Aes(AesSizes::Bits256) => ListableSecretType::AES_256_KEY, ++ RetrievableSecret::AesXts(AesXtsSizes::Bits128) => ListableSecretType::AES_128_XTS_KEY, ++ RetrievableSecret::AesXts(AesXtsSizes::Bits256) => ListableSecretType::AES_256_XTS_KEY, ++ RetrievableSecret::HmacSha(HmacShaSizes::Sha256) => { ++ ListableSecretType::HMAC_SHA_256_KEY ++ } ++ RetrievableSecret::HmacSha(HmacShaSizes::Sha512) => { ++ ListableSecretType::HMAC_SHA_512_KEY ++ } ++ RetrievableSecret::Ec(EcCurves::Secp256R1) => ListableSecretType::ECDSA_P256_KEY, ++ RetrievableSecret::Ec(EcCurves::Secp384R1) => ListableSecretType::ECDSA_P384_KEY, ++ RetrievableSecret::Ec(EcCurves::Secp521R1) => ListableSecretType::ECDSA_P521_KEY, ++ RetrievableSecret::Ec(EcCurves::Ed25519) => ListableSecretType::ECDSA_ED25519_KEY, ++ RetrievableSecret::Ec(EcCurves::Ed448) => ListableSecretType::ECDSA_ED448_KEY, ++ } ++ } ++} ++ ++// serializes to: (String name) ++impl Serialize for RetrievableSecret { ++ fn serialize(&self, serializer: S) -> Result ++ where ++ S: Serializer, ++ { ++ let id: u16 = self.into(); ++ serializer.serialize_str(&format!("{id} ({self})")) ++ } ++} ++ ++/// deserializes from the secret type nb only ++impl<'de> Deserialize<'de> for RetrievableSecret { ++ fn deserialize(de: D) -> Result ++ where ++ D: serde::Deserializer<'de>, ++ { ++ struct RetrSecretVisitor; ++ impl<'de> serde::de::Visitor<'de> for RetrSecretVisitor { ++ type Value = RetrievableSecret; ++ ++ fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { ++ fmt.write_str( ++ "a retrievable secret type: ` (String name)` number in [3,10]|[17,21]", ++ ) ++ } ++ fn visit_str(self, s: &str) -> Result ++ where ++ E: serde::de::Error, ++ { ++ let (n, _) = s.split_once(' ').ok_or(serde::de::Error::invalid_value( ++ serde::de::Unexpected::Str(s), ++ &self, ++ ))?; ++ let id: u16 = n.parse().map_err(|_| { ++ serde::de::Error::invalid_value(serde::de::Unexpected::Str(n), &self) ++ })?; ++ let listable: ListableSecretType = id.into(); ++ match listable { ++ ListableSecretType::Retrievable(r) => Ok(r), ++ _ => Err(serde::de::Error::invalid_value( ++ serde::de::Unexpected::Unsigned(id.into()), ++ &self, ++ )), ++ } ++ } ++ } ++ de.deserialize_str(RetrSecretVisitor) ++ } ++} ++ ++#[cfg(test)] ++mod test { ++ use serde_test::{assert_tokens, Token}; ++ ++ use super::*; ++ ++ #[test] ++ fn retr_serde_plain() { ++ let retr = RetrievableSecret::PlainText; ++ assert_tokens(&retr, &[Token::Str("3 (PLAINTEXT)")]); ++ } ++ ++ #[test] ++ fn retr_serde_aes() { ++ let retr = RetrievableSecret::Aes(AesSizes::Bits192); ++ assert_tokens(&retr, &[Token::Str("5 (AES-192-KEY)")]); ++ } ++ ++ #[test] ++ fn retr_serde_aes_xts() { ++ let retr = RetrievableSecret::AesXts(AesXtsSizes::Bits128); ++ assert_tokens(&retr, &[Token::Str("7 (AES-XTS-128-KEY)")]); ++ } ++ ++ #[test] ++ fn retr_serde_hmac() { ++ let retr = RetrievableSecret::HmacSha(HmacShaSizes::Sha256); ++ assert_tokens(&retr, &[Token::Str("9 (HMAC-SHA-256-KEY)")]); ++ } ++ ++ #[test] ++ fn retr_serde_es() { ++ let retr = RetrievableSecret::Ec(EcCurves::Secp521R1); ++ assert_tokens(&retr, &[Token::Str("19 (EC-SECP521R1-PRIVATE-KEY)")]); ++ } ++ ++ // Ensure that the string representation of the retrievable types stay constant, or PEM will have ++ // different, incompatible types ++ #[test] ++ fn stable_type_names() { ++ assert_eq!("PLAINTEXT", RetrievableSecret::PlainText.to_string()); ++ assert_eq!( ++ "AES-128-KEY", ++ RetrievableSecret::Aes(AesSizes::Bits128).to_string() ++ ); ++ assert_eq!( ++ "AES-192-KEY", ++ RetrievableSecret::Aes(AesSizes::Bits192).to_string() ++ ); ++ assert_eq!( ++ "AES-256-KEY", ++ RetrievableSecret::Aes(AesSizes::Bits256).to_string() ++ ); ++ assert_eq!( ++ "AES-XTS-128-KEY", ++ RetrievableSecret::AesXts(AesXtsSizes::Bits128).to_string() ++ ); ++ assert_eq!( ++ "AES-XTS-256-KEY", ++ RetrievableSecret::AesXts(AesXtsSizes::Bits256).to_string() ++ ); ++ assert_eq!( ++ "HMAC-SHA-256-KEY", ++ RetrievableSecret::HmacSha(HmacShaSizes::Sha256).to_string() ++ ); ++ assert_eq!( ++ "HMAC-SHA-512-KEY", ++ RetrievableSecret::HmacSha(HmacShaSizes::Sha512).to_string() ++ ); ++ assert_eq!( ++ "EC-SECP256R1-PRIVATE-KEY", ++ RetrievableSecret::Ec(EcCurves::Secp256R1).to_string() ++ ); ++ assert_eq!( ++ "EC-SECP384R1-PRIVATE-KEY", ++ RetrievableSecret::Ec(EcCurves::Secp384R1).to_string() ++ ); ++ assert_eq!( ++ "EC-SECP521R1-PRIVATE-KEY", ++ RetrievableSecret::Ec(EcCurves::Secp521R1).to_string() ++ ); ++ assert_eq!( ++ "EC-ED25519-PRIVATE-KEY", ++ RetrievableSecret::Ec(EcCurves::Ed25519).to_string() ++ ); ++ assert_eq!( ++ "EC-ED448-PRIVATE-KEY", ++ RetrievableSecret::Ec(EcCurves::Ed448).to_string() ++ ); ++ } ++} +diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs +index 0a8af50..4e95501 100644 +--- a/rust/pv_core/src/uvdevice/secret_list.rs ++++ b/rust/pv_core/src/uvdevice/secret_list.rs +@@ -2,9 +2,14 @@ + // + // Copyright IBM Corp. 2024 + +-use crate::assert_size; +-use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result}; +-use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; ++use crate::{ ++ assert_size, ++ misc::to_u16, ++ uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret}, ++ uvdevice::UvCmd, ++ Error, Result, ++}; ++use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; + use serde::{Deserialize, Serialize, Serializer}; + use std::{ + fmt::Display, +@@ -18,7 +23,7 @@ use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32}; + /// + /// (de)serializes itself in/from a hex-string + #[repr(C)] +-#[derive(PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Debug, Clone)] ++#[derive(PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Debug, Clone, Default)] + pub struct SecretId([u8; Self::ID_SIZE]); + assert_size!(SecretId, SecretId::ID_SIZE); + +@@ -94,11 +99,11 @@ impl SecretEntry { + /// Create a new entry for a [`SecretList`]. + /// + /// The content of this entry will very likely not represent the status of the guest in the +- /// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged. ++ /// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encouraged. + pub fn new(index: u16, stype: ListableSecretType, id: SecretId, secret_len: u32) -> Self { + Self { + index: index.into(), +- stype: stype.into(), ++ stype: U16::new(stype.into()), + len: secret_len.into(), + res_8: 0, + id, +@@ -117,7 +122,7 @@ impl SecretEntry { + + /// Returns the secret type of this [`SecretEntry`]. + pub fn stype(&self) -> ListableSecretType { +- self.stype.into() ++ self.stype.get().into() + } + + /// Returns a reference to the id of this [`SecretEntry`]. +@@ -146,7 +151,7 @@ impl SecretEntry { + + impl Display for SecretEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- let stype: ListableSecretType = self.stype.into(); ++ let stype: ListableSecretType = self.stype.get().into(); + writeln!(f, "{} {}:", self.index, stype)?; + write!(f, " ")?; + for b in self.id.as_ref() { +@@ -298,51 +303,115 @@ fn ser_u16(v: &U16, ser: S) -> Result + pub enum ListableSecretType { + /// Association Secret + Association, ++ /// Retrievable key ++ Retrievable(RetrievableSecret), ++ + /// Invalid secret type, that should never appear in a list + /// + /// 0 is reserved +- /// 1 is Null secret, with no id and not listable ++ /// 1 is Null secret, with no id and not list-able + Invalid(u16), + /// Unknown secret type + Unknown(u16), + } + + impl ListableSecretType { +- /// UV type id for an association secret +- pub const ASSOCIATION: u16 = 0x0002; +- /// UV type id for a null secret +- pub const NULL: u16 = 0x0001; + const RESERVED_0: u16 = 0x0000; ++ /// UV secret-type id for a null secret ++ pub const NULL: u16 = 0x0001; ++ /// UV secret-type id for an association secret ++ pub const ASSOCIATION: u16 = 0x0002; ++ /// UV secret-type id for a plain text secret ++ pub const PLAINTEXT: u16 = 0x0003; ++ /// UV secret-type id for an aes-128-key secret ++ pub const AES_128_KEY: u16 = 0x0004; ++ /// UV secret-type id for an aes-192-key secret ++ pub const AES_192_KEY: u16 = 0x0005; ++ /// UV secret-type id for an aes-256-key secret ++ pub const AES_256_KEY: u16 = 0x0006; ++ /// UV secret-type id for an aes-xts-128-key secret ++ pub const AES_128_XTS_KEY: u16 = 0x0007; ++ /// UV secret-type id for an aes-xts-256-key secret ++ pub const AES_256_XTS_KEY: u16 = 0x0008; ++ /// UV secret-type id for an hmac-sha-256-key secret ++ pub const HMAC_SHA_256_KEY: u16 = 0x0009; ++ /// UV secret-type id for an hmac-sha-512-key secret ++ pub const HMAC_SHA_512_KEY: u16 = 0x000a; ++ // 0x000b - 0x0010 reserved ++ /// UV secret-type id for an ecdsa-p256-private-key secret ++ pub const ECDSA_P256_KEY: u16 = 0x0011; ++ /// UV secret-type id for an ecdsa-p384-private-key secret ++ pub const ECDSA_P384_KEY: u16 = 0x0012; ++ /// UV secret-type id for an ecdsa-p521-private-key secret ++ pub const ECDSA_P521_KEY: u16 = 0x0013; ++ /// UV secret-type id for an ed25519-private-key secret ++ pub const ECDSA_ED25519_KEY: u16 = 0x0014; ++ /// UV secret-type id for an ed448-private-key secret ++ pub const ECDSA_ED448_KEY: u16 = 0x0015; + } + + 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})"), ++ Self::Invalid(n) => write!(f, "Invalid(0x{n:04x})"), ++ Self::Unknown(n) => write!(f, "Unknown(0x{n:04x})"), ++ Self::Retrievable(r) => write!(f, "{r}"), + } + } + } + +-impl From> for ListableSecretType { +- fn from(value: U16) -> Self { +- match value.get() { ++impl From> for ListableSecretType { ++ fn from(value: U16) -> Self { ++ value.get().into() ++ } ++} ++ ++impl From for ListableSecretType { ++ fn from(value: u16) -> Self { ++ match value { + Self::RESERVED_0 => Self::Invalid(Self::RESERVED_0), + Self::NULL => Self::Invalid(Self::NULL), + Self::ASSOCIATION => Self::Association, ++ Self::PLAINTEXT => Self::Retrievable(RetrievableSecret::PlainText), ++ Self::AES_128_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits128)), ++ Self::AES_192_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits192)), ++ Self::AES_256_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits256)), ++ Self::AES_128_XTS_KEY => { ++ Self::Retrievable(RetrievableSecret::AesXts(AesXtsSizes::Bits128)) ++ } ++ Self::AES_256_XTS_KEY => { ++ Self::Retrievable(RetrievableSecret::AesXts(AesXtsSizes::Bits256)) ++ } ++ Self::HMAC_SHA_256_KEY => { ++ Self::Retrievable(RetrievableSecret::HmacSha(HmacShaSizes::Sha256)) ++ } ++ Self::HMAC_SHA_512_KEY => { ++ Self::Retrievable(RetrievableSecret::HmacSha(HmacShaSizes::Sha512)) ++ } ++ Self::ECDSA_P256_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp256R1)), ++ Self::ECDSA_P384_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp384R1)), ++ Self::ECDSA_P521_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp521R1)), ++ Self::ECDSA_ED25519_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed25519)), ++ Self::ECDSA_ED448_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed448)), + n => Self::Unknown(n), + } + } + } + +-impl From for U16 { ++impl From for U16 { ++ fn from(value: ListableSecretType) -> Self { ++ Self::new(value.into()) ++ } ++} ++ ++impl From for u16 { + fn from(value: ListableSecretType) -> Self { + match value { + ListableSecretType::Association => ListableSecretType::ASSOCIATION, + ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n, ++ ListableSecretType::Retrievable(r) => (&r).into(), + } +- .into() + } + } + +@@ -363,8 +432,8 @@ where + where + E: serde::de::Error, + { +- if s.len() != SecretId::ID_SIZE * 2 + 2 { +- return Err(serde::de::Error::invalid_length(s.len(), &self)); ++ if s.len() != SecretId::ID_SIZE * 2 + "0x".len() { ++ return Err(serde::de::Error::invalid_length(s.len() - 2, &self)); + } + let nb = s.strip_prefix("0x").ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self) +@@ -385,7 +454,6 @@ mod test { + + use super::*; + use std::io::{BufReader, BufWriter, Cursor}; +- + #[test] + fn dump_secret_entry() { + const EXP: &[u8] = &[ +@@ -516,4 +584,47 @@ mod test { + )], + ) + } ++ ++ #[test] ++ fn secret_list_ser() { ++ let list = SecretList { ++ total_num_secrets: 0x112, ++ secrets: vec![SecretEntry { ++ index: 1.into(), ++ stype: 2.into(), ++ len: 32.into(), ++ res_8: 0, ++ id: SecretId::from([0; 32]), ++ }], ++ }; ++ ++ assert_ser_tokens( ++ &list, ++ &[ ++ Token::Struct { ++ name: "SecretList", ++ len: 2, ++ }, ++ Token::String("total_num_secrets"), ++ Token::U64(0x112), ++ Token::String("secrets"), ++ Token::Seq { len: Some(1) }, ++ Token::Struct { ++ name: "SecretEntry", ++ len: (4), ++ }, ++ Token::String("index"), ++ Token::U16(1), ++ Token::String("stype"), ++ Token::U16(2), ++ Token::String("len"), ++ Token::U32(32), ++ Token::String("id"), ++ Token::String("0x0000000000000000000000000000000000000000000000000000000000000000"), ++ Token::StructEnd, ++ Token::SeqEnd, ++ Token::StructEnd, ++ ], ++ ) ++ } + } +-- +2.47.1 + + +From 67480b7219b711226352257bd2690448d9521c06 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 5 Mar 2024 12:19:22 +0100 +Subject: [PATCH 18/31] rust/pv: Retrievable secrets support (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Support retrievable secret for Add-Secret requests. + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit fd024387d710887bd2016658c44d4762a08c791c) +--- + rust/pv/src/crypto.rs | 3 +- + rust/pv/src/error.rs | 8 + + rust/pv/src/lib.rs | 8 +- + rust/pv/src/uvsecret.rs | 1 + + rust/pv/src/uvsecret/guest_secret.rs | 399 +++++++++++++++++++++++++-- + rust/pv/src/uvsecret/retr_secret.rs | 234 ++++++++++++++++ + 6 files changed, 631 insertions(+), 22 deletions(-) + create mode 100644 rust/pv/src/uvsecret/retr_secret.rs + +diff --git a/rust/pv/src/crypto.rs b/rust/pv/src/crypto.rs +index 8f11d2b..ebc85f7 100644 +--- a/rust/pv/src/crypto.rs ++++ b/rust/pv/src/crypto.rs +@@ -29,7 +29,6 @@ pub type Aes256XtsKey = Confidential<[u8; SymKeyType::AES_256_XTS_KEY_LEN]>; + + /// SHA-512 digest length (in bytes) + pub const SHA_512_HASH_LEN: usize = 64; +- + #[allow(dead_code)] + pub(crate) const SHA_256_HASH_LEN: u32 = 32; + #[allow(dead_code)] +@@ -60,6 +59,8 @@ impl SymKeyType { + pub const AES_256_XTS_KEY_LEN: usize = 64; + /// AES256-XTS tweak length (in bytes) + pub const AES_256_XTS_TWEAK_LEN: usize = 16; ++ /// AES256 GCM Block length ++ pub const AES_256_GCM_BLOCK_LEN: usize = 16; + + /// Returns the tag length of the [`SymKeyType`] if it is an AEAD key + pub const fn tag_len(&self) -> Option { +diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs +index 3ba808f..601b40f 100644 +--- a/rust/pv/src/error.rs ++++ b/rust/pv/src/error.rs +@@ -109,6 +109,14 @@ pub enum Error { + #[error("An ASCII string was expected, but non-ASCII characters were received.")] + NonAscii, + ++ #[error("Incorrect {what} for a {kind}. Is: {value}; expected: {exp}")] ++ RetrInvKey { ++ what: &'static str, ++ kind: String, ++ value: String, ++ exp: String, ++ }, ++ + // errors from other crates + #[error(transparent)] + PvCore(#[from] pv_core::Error), +diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs +index ec31b9a..4337566 100644 +--- a/rust/pv/src/lib.rs ++++ b/rust/pv/src/lib.rs +@@ -104,7 +104,12 @@ pub mod request { + + /// Reexports some useful OpenSSL symbols + pub mod openssl { +- pub use openssl::{error::ErrorStack, hash::DigestBytes, pkey, x509}; ++ pub use openssl::{error::ErrorStack, hash::DigestBytes, nid::Nid, pkey, x509}; ++ // rust-OpenSSL does not define these NIDs ++ #[allow(missing_docs)] ++ pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519); ++ #[allow(missing_docs)] ++ pub const NID_ED448: Nid = Nid::from_raw(openssl_sys::NID_ED448); + } + + pub use pv_core::request::*; +@@ -118,6 +123,7 @@ pub mod secret { + asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion}, + ext_secret::ExtSecret, + guest_secret::GuestSecret, ++ retr_secret::{IbmProtectedKey, RetrievedSecret}, + user_data::verify_asrcb_and_get_user_data, + }; + } +diff --git a/rust/pv/src/uvsecret.rs b/rust/pv/src/uvsecret.rs +index 343e4b0..c3b43bb 100644 +--- a/rust/pv/src/uvsecret.rs ++++ b/rust/pv/src/uvsecret.rs +@@ -10,4 +10,5 @@ + pub mod asrcb; + pub mod ext_secret; + pub mod guest_secret; ++pub mod retr_secret; + pub mod user_data; +diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs +index 4f1db31..3bad6d3 100644 +--- a/rust/pv/src/uvsecret/guest_secret.rs ++++ b/rust/pv/src/uvsecret/guest_secret.rs +@@ -4,20 +4,34 @@ + + #[allow(unused_imports)] // used for more convenient docstring + use super::asrcb::AddSecretRequest; +-use crate::assert_size; + use crate::{ +- crypto::{hash, random_array}, +- request::Confidential, +- Result, ++ assert_size, ++ crypto::{hash, random_array, SymKeyType}, ++ request::{ ++ openssl::{NID_ED25519, NID_ED448}, ++ Confidential, ++ }, ++ uv::{ ++ AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret, ++ RetrieveCmd, SecretId, ++ }, ++ Error, Result, + }; + use byteorder::BigEndian; +-use openssl::hash::MessageDigest; +-use pv_core::uv::{ListableSecretType, SecretId}; ++use openssl::{ ++ hash::MessageDigest, ++ nid::Nid, ++ pkey::{Id, PKey, Private}, ++}; ++use pv_core::static_assert; + use serde::{Deserialize, Serialize}; +-use std::{convert::TryInto, fmt::Display}; ++use std::fmt::Display; + use zerocopy::{AsBytes, U16, U32}; + + const ASSOC_SECRET_SIZE: usize = 32; ++/// Maximum size of a plain-text secret payload (8190) ++pub(crate) const MAX_SIZE_PLAIN_PAYLOAD: usize = RetrieveCmd::MAX_SIZE - 2; ++static_assert!(MAX_SIZE_PLAIN_PAYLOAD == 8190); + + /// A Secret to be added in [`AddSecretRequest`] + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +@@ -36,13 +50,60 @@ pub enum GuestSecret { + #[serde(skip)] + secret: Confidential<[u8; ASSOC_SECRET_SIZE]>, + }, ++ /// Retrievable key ++ /// ++ /// Create Retrievables using [`GuestSecret::retrievable`] ++ /// Secret size is always valid for the type/kind ++ Retrievable { ++ /// Retrievable secret type ++ kind: RetrievableSecret, ++ /// Name of the secret ++ name: String, ++ /// SHA256 hash of [`GuestSecret::RetrievableKey::name`] ++ id: SecretId, ++ /// Confidential actual retrievable secret (32 bytes) ++ #[serde(skip)] ++ secret: Confidential>, ++ }, ++} ++ ++macro_rules! retr_constructor { ++ ($(#[$err:meta])* | $(#[$kind:meta])* => $type: ty, $func: ident) => { ++ /// Create a new ++ $(#[$kind])* ++ /// [`GuestSecret::Retrievable`] secret. ++ /// ++ /// * `name` - Name of the secret. Will be hashed into a 32 byte id ++ /// * `secret` - the secret value ++ /// ++ /// # Errors ++ /// ++ $(#[$err])* ++ pub fn $func(name: &str, secret: $type) -> Result { ++ let (kind, secret) = $func(secret)?; ++ Ok(Self::Retrievable { ++ kind, ++ name: name.to_string(), ++ id: Self::name_to_id(name)?, ++ secret, ++ }) ++ } ++ }; + } + + impl GuestSecret { ++ fn name_to_id(name: &str) -> Result { ++ let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())? ++ .to_vec() ++ .try_into() ++ .unwrap(); ++ Ok(id.into()) ++ } ++ + /// Create a new [`GuestSecret::Association`]. + /// + /// * `name` - Name of the secret. Will be hashed into a 32 byte id +- /// * `secret` - Value of the secret. Ranom if [`Option::None`] ++ /// * `secret` - Value of the secret. Random if [`Option::None`] + /// + /// # Errors + /// +@@ -51,10 +112,6 @@ impl GuestSecret { + where + O: Into>, + { +- let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())? +- .to_vec() +- .try_into() +- .unwrap(); + let secret = match secret.into() { + Some(s) => s, + None => random_array()?, +@@ -62,16 +119,28 @@ impl GuestSecret { + + Ok(Self::Association { + name: name.to_string(), +- id: id.into(), ++ id: Self::name_to_id(name)?, + secret: secret.into(), + }) + } + ++ retr_constructor!(#[doc = r"This function will return an error if the secret is larger than 8 pages"] ++ | #[doc = r"plaintext"] => Confidential>, plaintext); ++ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"] ++ | #[doc = r"AES Key"] => Confidential>, aes); ++ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"] ++ | #[doc = r"AES-XTS Key"] => Confidential>, aes_xts); ++ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"] ++ | #[doc = r"HMAC-SHA Key"] => Confidential>, hmac_sha); ++ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the curve is invalid"] ++ | #[doc = r"EC PRIVATE Key"] => PKey, ec); ++ + /// Reference to the confidential data + pub fn confidential(&self) -> &[u8] { + match &self { + Self::Null => &[], + Self::Association { secret, .. } => secret.value().as_slice(), ++ Self::Retrievable { secret, .. } => secret.value(), + } + } + +@@ -79,7 +148,7 @@ impl GuestSecret { + pub(crate) fn auth(&self) -> SecretAuth { + match &self { + Self::Null => SecretAuth::Null, +- // Panic: every non null secret type is listable -> no panic ++ // Panic: every non null secret type is list-able -> no panic + listable => { + SecretAuth::Listable(ListableSecretHdr::from_guest_secret(listable).unwrap()) + } +@@ -92,6 +161,7 @@ impl GuestSecret { + // Null is not listable, but the ListableSecretType provides the type constant (1) + Self::Null => ListableSecretType::NULL, + Self::Association { .. } => ListableSecretType::ASSOCIATION, ++ Self::Retrievable { kind, .. } => kind.into(), + } + } + +@@ -100,6 +170,7 @@ impl GuestSecret { + match self { + Self::Null => 0, + Self::Association { secret, .. } => secret.value().len() as u32, ++ Self::Retrievable { secret, .. } => secret.value().len() as u32, + } + } + +@@ -107,18 +178,157 @@ impl GuestSecret { + fn id(&self) -> Option { + match self { + Self::Null => None, +- Self::Association { id, .. } => Some(id.to_owned()), ++ Self::Association { id, .. } | Self::Retrievable { id, .. } => Some(id.to_owned()), + } + } + } + ++type RetrKeyInfo = (RetrievableSecret, Confidential>); ++ ++fn extend_to_multiple(mut key: Vec, multiple: usize) -> Confidential> { ++ match key.len().checked_rem(multiple) { ++ Some(0) | None => key, ++ Some(m) => { ++ key.resize(key.len() + multiple - m, 0); ++ key ++ } ++ } ++ .into() ++} ++ ++/// Get a plain-text key ++/// ++/// ```none ++/// size U16 | payload (0-8190) bytes ++/// ``` ++fn plaintext(inp: Confidential>) -> Result { ++ let key_len = inp.value().len(); ++ if key_len > RetrieveCmd::MAX_SIZE { ++ return Err(Error::RetrInvKey { ++ what: "key size", ++ value: key_len.to_string(), ++ kind: RetrievableSecret::PlainText.to_string(), ++ exp: RetrievableSecret::PlainText.expected(), ++ }); ++ } ++ let mut key = Vec::with_capacity(2 + inp.value().len()); ++ let key_len: U16 = (key_len as u16).into(); ++ key.extend_from_slice(key_len.as_bytes()); ++ key.extend_from_slice(inp.value()); ++ let key = extend_to_multiple(key, SymKeyType::AES_256_GCM_BLOCK_LEN); ++ ++ Ok((RetrievableSecret::PlainText, key)) ++} ++ ++/// Get an AES-key ++fn aes(key: Confidential>) -> Result { ++ let key_len = key.value().len() as u32; ++ let bit_size = bitsize(key_len); ++ match AesSizes::from_bits(bit_size) { ++ Some(size) => Ok((RetrievableSecret::Aes(size), key)), ++ None => { ++ // Use some AES type to get exp sizes and name ++ let kind = RetrievableSecret::Aes(AesSizes::Bits128); ++ Err(Error::RetrInvKey { ++ what: "key size", ++ value: bit_size.to_string(), ++ kind: format!("{kind:#}"), ++ exp: kind.expected(), ++ }) ++ } ++ } ++} ++ ++/// Get an AES-XTS-key ++fn aes_xts(key: Confidential>) -> Result { ++ let key_len = key.value().len() as u32; ++ let bit_size = bitsize(key_len / 2); ++ match AesXtsSizes::from_bits(bit_size) { ++ Some(size) => Ok((RetrievableSecret::AesXts(size), key)), ++ None => { ++ // Use some AES-XTS type to get exp sizes and name ++ let kind = RetrievableSecret::AesXts(AesXtsSizes::Bits128); ++ Err(Error::RetrInvKey { ++ what: "key size", ++ value: bit_size.to_string(), ++ kind: format!("{kind:#}"), ++ exp: kind.expected(), ++ }) ++ } ++ } ++} ++ ++/// Get an HMAC-SHA-key ++fn hmac_sha(key: Confidential>) -> Result { ++ let key_len = key.value().len() as u32; ++ let size = bitsize(key_len / 2); ++ match HmacShaSizes::from_sha_size(size) { ++ Some(size) => Ok((RetrievableSecret::HmacSha(size), key)), ++ None => { ++ // Use some HMAC type to get exp sizes and name ++ let kind = RetrievableSecret::HmacSha(HmacShaSizes::Sha256); ++ Err(Error::RetrInvKey { ++ what: "key size", ++ value: size.to_string(), ++ kind: format!("{kind:#}"), ++ exp: kind.expected(), ++ }) ++ } ++ } ++} ++ ++/// Get an EC-private-key ++fn ec(key: PKey) -> Result { ++ let (key, nid) = match key.id() { ++ Id::EC => { ++ let ec_key = key.ec_key()?; ++ let key = ec_key.private_key().to_vec(); ++ let nid = ec_key.group().curve_name().unwrap_or(Nid::UNDEF); ++ (key, nid) ++ } ++ // ED keys are not handled via the EC struct in OpenSSL. ++ id @ (Id::ED25519 | Id::ED448) => { ++ let key = key.raw_private_key()?; ++ let nid = Nid::from_raw(id.as_raw()); ++ (key, nid) ++ } ++ _ => (vec![], Nid::UNDEF), ++ }; ++ ++ let kind = match nid { ++ Nid::X9_62_PRIME256V1 => EcCurves::Secp256R1, ++ Nid::SECP384R1 => EcCurves::Secp384R1, ++ Nid::SECP521R1 => EcCurves::Secp521R1, ++ NID_ED25519 => EcCurves::Ed25519, ++ NID_ED448 => EcCurves::Ed448, ++ nid => { ++ // Use some EC type to get exp sizes and name ++ let ec = RetrievableSecret::Ec(EcCurves::Secp521R1); ++ return Err(Error::RetrInvKey { ++ what: "curve or format", ++ kind: format!("{ec:#}"), ++ value: nid.long_name()?.to_string(), ++ exp: ec.expected(), ++ }); ++ } ++ }; ++ ++ let key = kind.resize_raw_key(key); ++ Ok((RetrievableSecret::Ec(kind), key.into())) ++} ++ ++#[inline(always)] ++const fn bitsize(bytesize: u32) -> u32 { ++ bytesize * 8 ++} ++ + impl Display for GuestSecret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Null => write!(f, "Meta"), + gs => { + let kind: U16 = gs.kind().into(); +- let st: ListableSecretType = kind.into(); ++ let st: ListableSecretType = kind.get().into(); + write!(f, "{st}") + } + } +@@ -153,20 +363,24 @@ assert_size!(ListableSecretHdr, 0x30); + + impl ListableSecretHdr { + fn from_guest_secret(gs: &GuestSecret) -> Option { +- let id = gs.id()?; + Some(Self { + res0: 0, + kind: gs.kind().into(), + secret_len: gs.secret_len().into(), + res8: 0, +- id, ++ id: gs.id()?, + }) + } + } + + #[cfg(test)] + mod test { ++ ++ use super::HmacShaSizes as HmacSizes; ++ use super::RetrievableSecret::*; + use super::*; ++ use openssl::ec::{EcGroup, EcKey}; ++ use pv_core::uv::AesSizes; + use serde_test::{assert_tokens, Token}; + + #[test] +@@ -187,8 +401,103 @@ mod test { + assert_eq!(secret, exp); + } + ++ macro_rules! retr_test { ++ ($name: ident, $func: ident, $size: expr, $exp_kind: expr) => { ++ #[test] ++ fn $name() { ++ let secret_value = vec![0x11; $size]; ++ let name = "test retr secret".to_string(); ++ let secret = GuestSecret::$func(&name, secret_value.clone().into()).unwrap(); ++ let exp_id = [ ++ 0x61, 0x2c, 0xd6, 0x3e, 0xa8, 0xf2, 0xc1, 0x15, 0xc1, 0xe, 0x15, 0xb8, 0x8a, ++ 0x90, 0x16, 0xc1, 0x55, 0xef, 0x9c, 0x7c, 0x2c, 0x8e, 0x56, 0xd0, 0x78, 0x4c, ++ 0x8a, 0x1d, 0xc9, 0x3a, 0x80, 0xba, ++ ]; ++ let exp = GuestSecret::Retrievable { ++ kind: $exp_kind, ++ name, ++ id: exp_id.into(), ++ secret: secret_value.into(), ++ }; ++ assert_eq!(exp, secret); ++ } ++ }; ++ } ++ ++ retr_test!(retr_aes_128, aes, 16, Aes(AesSizes::Bits128)); ++ retr_test!(retr_aes_192, aes, 24, Aes(AesSizes::Bits192)); ++ retr_test!(retr_aes_256, aes, 32, Aes(AesSizes::Bits256)); ++ retr_test!(retr_aes_xts_128, aes_xts, 32, AesXts(AesXtsSizes::Bits128)); ++ retr_test!(retr_aes_xts_256, aes_xts, 64, AesXts(AesXtsSizes::Bits256)); ++ retr_test!(retr_aes_hmac_256, hmac_sha, 64, HmacSha(HmacSizes::Sha256)); ++ retr_test!(retr_aes_hmac_512, hmac_sha, 128, HmacSha(HmacSizes::Sha512)); ++ ++ #[test] ++ fn plaintext_no_pad() { ++ let key = vec![0, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]; ++ let name = "PLAINTEXT_PAD".to_string(); ++ let secret = GuestSecret::plaintext(&name, key[2..].to_vec().into()).unwrap(); ++ let exp_id = [ ++ 15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1, ++ 94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74, ++ ]; ++ let exp = GuestSecret::Retrievable { ++ kind: PlainText, ++ name, ++ id: exp_id.into(), ++ secret: key.into(), ++ }; ++ ++ assert_eq!(secret, exp); ++ } ++ + #[test] +- fn ap_asc_parse() { ++ fn plaintext_pad() { ++ let key = vec![0, 10, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0]; ++ let name = "PLAINTEXT_PAD".to_string(); ++ let secret = GuestSecret::plaintext(&name, key[2..12].to_vec().into()).unwrap(); ++ let exp_id = [ ++ 15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1, ++ 94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74, ++ ]; ++ let exp = GuestSecret::Retrievable { ++ kind: PlainText, ++ name, ++ id: exp_id.into(), ++ secret: key.into(), ++ }; ++ ++ assert_eq!(secret, exp); ++ } ++ ++ #[track_caller] ++ fn test_ec(grp: Nid, exp_kind: EcCurves, exp_len: usize) { ++ let key = match grp { ++ NID_ED25519 => PKey::generate_ed25519().unwrap(), ++ NID_ED448 => PKey::generate_ed448().unwrap(), ++ nid => { ++ let group = EcGroup::from_curve_name(nid).unwrap(); ++ let key = EcKey::generate(&group).unwrap(); ++ PKey::from_ec_key(key).unwrap() ++ } ++ }; ++ let (kind, key) = ec(key).unwrap(); ++ ++ assert_eq!(kind, Ec(exp_kind)); ++ assert_eq!(key.value().len(), exp_len); ++ } ++ ++ #[test] ++ fn retr_ec() { ++ test_ec(Nid::X9_62_PRIME256V1, EcCurves::Secp256R1, 32); ++ test_ec(Nid::SECP384R1, EcCurves::Secp384R1, 48); ++ test_ec(Nid::SECP521R1, EcCurves::Secp521R1, 80); ++ test_ec(NID_ED25519, EcCurves::Ed25519, 32); ++ test_ec(NID_ED448, EcCurves::Ed448, 64); ++ } ++ ++ #[test] ++ fn asc_parse() { + let id = [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, + 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, +@@ -217,6 +526,39 @@ mod test { + ); + } + ++ #[test] ++ fn retrievable_parse() { ++ let id = [ ++ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, ++ 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, ++ 0x89, 0xab, 0xcd, 0xef, ++ ]; ++ let asc = GuestSecret::Retrievable { ++ kind: PlainText, ++ name: "test123".to_string(), ++ id: id.into(), ++ secret: vec![].into(), ++ }; ++ ++ assert_tokens( ++ &asc, ++ &[ ++ Token::StructVariant { ++ name: "GuestSecret", ++ variant: "Retrievable", ++ len: 3, ++ }, ++ Token::String("kind"), ++ Token::String("3 (PLAINTEXT)"), ++ Token::String("name"), ++ Token::String("test123"), ++ Token::String("id"), ++ Token::String("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), ++ Token::StructVariantEnd, ++ ], ++ ); ++ } ++ + #[test] + fn guest_secret_bin_null() { + let gs = GuestSecret::Null; +@@ -228,7 +570,7 @@ mod test { + } + + #[test] +- fn guest_secret_bin_ap() { ++ fn guest_secret_bin_asoc() { + let gs = GuestSecret::Association { + name: "test".to_string(), + id: [1; 32].into(), +@@ -241,4 +583,21 @@ mod test { + assert_eq!(exp, gs_bytes_auth.get()); + assert_eq!(&[2; 32], gs.confidential()); + } ++ ++ #[test] ++ fn guest_secret_bin_retr() { ++ let gs = GuestSecret::Retrievable { ++ kind: PlainText, ++ name: "test".to_string(), ++ id: [1; 32].into(), ++ secret: vec![2; 32].into(), ++ }; ++ let auth = gs.auth(); ++ let gs_bytes_auth = auth.get(); ++ let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0]; ++ exp.extend([1; 32]); ++ ++ assert_eq!(exp, gs_bytes_auth); ++ assert_eq!(&[2; 32], gs.confidential()); ++ } + } +diff --git a/rust/pv/src/uvsecret/retr_secret.rs b/rust/pv/src/uvsecret/retr_secret.rs +new file mode 100644 +index 0000000..5fad016 +--- /dev/null ++++ b/rust/pv/src/uvsecret/retr_secret.rs +@@ -0,0 +1,234 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2024 ++ ++use crate::{pem::Pem, uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD, Result}; ++ ++use byteorder::BigEndian; ++use log::warn; ++use pv_core::{ ++ request::Confidential, ++ uv::{ListableSecretType, RetrievableSecret, RetrieveCmd}, ++}; ++use zerocopy::{FromBytes, U16}; ++ ++/// An IBM Protected Key ++/// ++/// A protected key, writeable as pem. ++/// ++/// Will convert into PEM as: ++/// ```PEM ++///-----BEGIN IBM PROTECTED KEY----- ++///kind: ++/// ++/// ++///-----END IBM PROTECTED KEY----- ++/// ``` ++#[derive(Debug, PartialEq, Eq)] ++pub struct IbmProtectedKey { ++ kind: ListableSecretType, ++ key: Confidential>, ++} ++ ++impl IbmProtectedKey { ++ /// Get the binary representation of the key. ++ pub fn data(&self) -> &[u8] { ++ self.key.value() ++ } ++ ++ /// Converts a [`IbmProtectedKey`] into a vector. ++ pub fn into_bytes(self) -> Confidential> { ++ self.key ++ } ++ ++ /// Get the data in PEM format. ++ /// ++ /// # Errors ++ /// ++ /// This function will return an error if the PEM conversion failed (very unlikely). ++ pub fn to_pem(&self) -> Result { ++ Pem::new( ++ "IBM PROTECTED KEY", ++ format!("kind: {}", self.kind), ++ self.key.value(), ++ ) ++ } ++ ++ fn new(kind: ListableSecretType, key: K) -> Self ++ where ++ K: Into>>, ++ { ++ Self { ++ kind, ++ key: key.into(), ++ } ++ } ++} ++ ++impl From for RetrievedSecret { ++ fn from(value: RetrieveCmd) -> Self { ++ let kind = value.meta_data().stype(); ++ let key = value.into_key(); ++ ++ match kind { ++ ListableSecretType::Retrievable(RetrievableSecret::PlainText) => { ++ // Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is the ++ // minimum size ++ let len = U16::::read_from_prefix(key.value()) ++ .unwrap_or_default() ++ .get() as usize; ++ ++ // Test if the plain text secret has a size: ++ // 1. len <= 8190 ++ // 2. first two bytes are max 15 less than buffer-size+2 ++ // 3. bytes after len + 2 are zero ++ match len <= MAX_SIZE_PLAIN_PAYLOAD ++ && key.value().len() - (len + 2) < 15 ++ && key.value()[len + 2..].iter().all(|c| *c == 0) ++ { ++ false => Self::Plaintext(key), ++ true => Self::Plaintext(key.value()[2..len + 2].to_vec().into()), ++ } ++ } ++ kind => { ++ match kind { ++ ListableSecretType::Retrievable(_) => (), ++ _ => warn!("Retrieved an unretrievable Secret! Will continue; interpreting it as a protected key."), ++ } ++ Self::ProtectedKey(IbmProtectedKey::new(kind, key)) ++ } ++ } ++ } ++} ++ ++/// A retrieved Secret. ++#[derive(Debug, PartialEq, Eq)] ++pub enum RetrievedSecret { ++ /// A plaintext secret ++ Plaintext(Confidential>), ++ /// An [`IbmProtectedKey`] ++ ProtectedKey(IbmProtectedKey), ++} ++ ++impl RetrievedSecret { ++ /// Create a new IBM PROTECTED KEY object ++ pub fn from_cmd(cmd: RetrieveCmd) -> Self { ++ cmd.into() ++ } ++ ++ /// Get the binary representation of the key. ++ pub fn data(&self) -> &[u8] { ++ match self { ++ RetrievedSecret::Plaintext(p) => p.value(), ++ RetrievedSecret::ProtectedKey(p) => p.data(), ++ } ++ } ++ ++ /// Converts a [`IbmProtectedKey`] into a vector. ++ pub fn into_bytes(self) -> Confidential> { ++ match self { ++ RetrievedSecret::Plaintext(p) => p, ++ RetrievedSecret::ProtectedKey(p) => p.into_bytes(), ++ } ++ } ++ /// Get the data in PEM format. ++ /// ++ /// # Errors ++ /// ++ /// This function will return an error if the PEM conversion failed (very unlikely). ++ pub fn to_pem(&self) -> Result { ++ match self { ++ RetrievedSecret::Plaintext(p) => Pem::new("PLAINTEXT SECRET", None, p.value()), ++ RetrievedSecret::ProtectedKey(p) => p.to_pem(), ++ } ++ } ++} ++ ++#[cfg(test)] ++mod test { ++ use super::*; ++ use pv_core::uv::*; ++ ++ fn mk_retr(secret: &[u8]) -> RetrievedSecret { ++ let entry = SecretEntry::new( ++ 0, ++ ListableSecretType::Retrievable(RetrievableSecret::PlainText), ++ SecretId::default(), ++ secret.len() as u32, ++ ); ++ let mut cmd = RetrieveCmd::from_entry(entry).unwrap(); ++ cmd.data().unwrap().copy_from_slice(secret); ++ RetrievedSecret::from_cmd(cmd) ++ } ++ ++ #[test] ++ fn from_retr_cmd() { ++ let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0]; ++ let prot_key = mk_retr(&secret); ++ let exp = RetrievedSecret::Plaintext(secret[2..12].to_vec().into()); ++ assert_eq!(prot_key, exp); ++ } ++ ++ #[test] ++ fn from_retr_inv_size() { ++ let secret = vec![0x20; 32]; ++ let prot_key = mk_retr(&secret); ++ let exp = RetrievedSecret::Plaintext(secret.into()); ++ assert_eq!(prot_key, exp); ++ } ++ ++ #[test] ++ fn from_retr_inv_no_zero_after_end() { ++ let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 1, 0, 0, 0]; ++ let prot_key = mk_retr(&secret); ++ let exp = RetrievedSecret::Plaintext(secret.into()); ++ assert_eq!(prot_key, exp); ++ } ++ ++ #[test] ++ fn from_retr_inv_to_much_padding() { ++ let secret = vec![ ++ 0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, ++ ]; ++ let prot_key = mk_retr(&secret); ++ let exp = RetrievedSecret::Plaintext(secret.into()); ++ assert_eq!(prot_key, exp); ++ } ++ ++ #[test] ++ fn from_retr_0_size() { ++ let secret = vec![0x00; 32]; ++ let prot_key = mk_retr(&secret); ++ let exp = RetrievedSecret::Plaintext(secret.into()); ++ assert_eq!(prot_key, exp); ++ } ++ ++ #[test] ++ fn plain_text_pem() { ++ let exp = "\ ++ -----BEGIN PLAINTEXT SECRET-----\n\ ++ ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\ ++ -----END PLAINTEXT SECRET-----\n"; ++ let prot = RetrievedSecret::Plaintext(vec![17; 48].into()); ++ let pem = prot.to_pem().unwrap(); ++ let pem_str = pem.to_string(); ++ assert_eq!(pem_str, exp); ++ } ++ ++ #[test] ++ fn prot_key_pem() { ++ let exp = "\ ++ -----BEGIN IBM PROTECTED KEY-----\n\ ++ kind: AES-128-KEY\n\n\ ++ ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\ ++ -----END IBM PROTECTED KEY-----\n"; ++ let prot = IbmProtectedKey::new( ++ ListableSecretType::Retrievable(RetrievableSecret::Aes(AesSizes::Bits128)), ++ vec![17; 48], ++ ); ++ let pem = prot.to_pem().unwrap(); ++ let pem_str = pem.to_string(); ++ assert_eq!(pem_str, exp); ++ } ++} +-- +2.47.1 + + +From ce872d9b3104209d2a8bee32fcc1b1f2f2e0e2ad Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Fri, 13 Dec 2024 15:04:02 +0100 +Subject: [PATCH 19/31] rust/pvsecret: Improve CLI (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Improve the wording of the help/man text/ + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit a14f9d4edcc5db0d54e4fbe3ec3d98c7c270bf8e) +--- + rust/pvsecret/src/cli.rs | 26 +++++++++++++------------- + 1 file changed, 13 insertions(+), 13 deletions(-) + +diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs +index 6deaaeb..c4b9f2b 100644 +--- a/rust/pvsecret/src/cli.rs ++++ b/rust/pvsecret/src/cli.rs +@@ -37,8 +37,8 @@ pub struct CreateSecretOpt { + + /// Specifies the header of the guest image. + /// +- /// Can be an IBM Secure Execution image created by genprotimg or an extracted IBM Secure +- /// Execution header. The header must start at a page boundary. ++ /// Can be an IBM Secure Execution image created by 'pvimg/genprotimg' or an ++ /// extracted IBM Secure Execution header. + #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath)] + pub hdr: String, + +@@ -150,12 +150,12 @@ pub enum AddSecretType { + + /// Create an association secret. + /// +- /// Use an association secret to connect a trusted I/O device to a guest. The `pvapconfig` tool ++ /// Use an association secret to connect a trusted I/O device to a guest. The 'pvapconfig' tool + /// provides more information about association secrets. + Association { +- /// String to identify the new secret. ++ /// String that identifies the new secret. + /// +- /// The actual secret is set with --input-secret. The name is saved in `NAME.yaml` with ++ /// The actual secret is set with '--input-secret'. The name is saved in `NAME.yaml` with + /// white-spaces mapped to `_`. + name: String, + +@@ -166,15 +166,15 @@ pub enum AddSecretType { + stdout: bool, + + /// Path from which to read the plaintext secret. Uses a random secret if not specified. +- #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, conflicts_with("output_secret"))] ++ #[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath, conflicts_with("output_secret"))] + input_secret: Option, + +- /// Save the generated secret as plaintext in FILE. ++ /// Save the generated secret as plaintext in SECRET-FILE. + /// + /// The generated secret can be used to generate add-secret requests for a different guest +- /// with the same secret using --input-secret. Destroy the secret when it is not used ++ /// with the same secret using '--input-secret'. Destroy the secret when it is not used + /// anymore. +- #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)] ++ #[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath,)] + output_secret: Option, + }, + } +@@ -243,13 +243,13 @@ pub enum Command { + /// Create a new add-secret request. + /// + /// Create add-secret requests for IBM Secure Execution guests. Only create these requests in a +- /// trusted environment, such as your workstation. The `pvattest create` command creates a ++ /// trusted environment, such as your workstation. The 'pvattest create' command creates a + /// randomly generated key to protect the request. The generated requests can then be added on +- /// an IBM Secure Execution guest using `pvsecret add`. The guest can then use the secrets with ++ /// an IBM Secure Execution guest using 'pvsecret add'. The guest can then use the secrets with + /// the use case depending on the secret type. + Create(Box), + +- /// Perform an add-secret request (s390x only). ++ /// Submit an add-secret request to the Ultravisor (s390x only). + /// + /// Perform an add-secret request using a previously generated add-secret request. Only + /// available on s390x. +@@ -258,7 +258,7 @@ pub enum Command { + /// Lock the secret-store (s390x only). + /// + /// Lock the secret store (s390x only). After this command executed successfully, all +- /// add-secret requests will fail. Only available on s390x. ++ /// subsequent add-secret requests will fail. Only available on s390x. + Lock, + + /// List all ultravisor secrets (s390x only). +-- +2.47.1 + + +From 1c97c4569b0521896927547d0c9581d7808f3905 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Mon, 19 Feb 2024 15:15:16 +0100 +Subject: [PATCH 20/31] rust/pvsecret: Add support for retrievable secrets + (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Support for creating and retrieving retrievable secrets. + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 93da795520ca2f0a73cfbfc951a9b16437a1b95b) +--- + rust/pvsecret/src/cli.rs | 129 +++++++++++++++++++++++++++++++- + rust/pvsecret/src/cmd.rs | 6 +- + rust/pvsecret/src/cmd/create.rs | 30 +++++++- + rust/pvsecret/src/cmd/list.rs | 12 ++- + rust/pvsecret/src/cmd/retr.rs | 62 +++++++++++++++ + rust/pvsecret/src/main.rs | 1 + + 6 files changed, 230 insertions(+), 10 deletions(-) + create mode 100644 rust/pvsecret/src/cmd/retr.rs + +diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs +index c4b9f2b..4e74768 100644 +--- a/rust/pvsecret/src/cli.rs ++++ b/rust/pvsecret/src/cli.rs +@@ -1,7 +1,10 @@ + // SPDX-License-Identifier: MIT + // +-// Copyright IBM Corp. 2023 ++// Copyright IBM Corp. 2023, 2024 + ++use std::fmt::Display; ++ ++use clap::error::ErrorKind::ValueValidation; + use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint}; + use utils::{CertificateOptions, DeprecatedVerbosityOptions, STDOUT}; + +@@ -177,6 +180,72 @@ pub enum AddSecretType { + #[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath,)] + output_secret: Option, + }, ++ ++ /// Create a retrievable secret. ++ /// ++ /// A retrievable secret is stored in the per-guest storage of the Ultravisor. A SE-guest can ++ /// retrieve the secret at runtime and use it. All retrievable secrets, but the plaintext ++ /// secret, are retrieved as wrapped/protected key objects and only usable inside the current, ++ /// running SE-guest instance. ++ #[command(visible_alias = "retr")] ++ Retrievable { ++ /// String that identifies the new secret. ++ /// ++ /// The actual secret is set with '--secret'. The name is saved in `NAME.yaml` with ++ /// white-spaces mapped to `_`. ++ name: String, ++ ++ /// Print the hashed name to stdout. ++ /// ++ /// The hashed name is not written to `NAME.yaml` ++ #[arg(long)] ++ stdout: bool, ++ ++ /// Use SECRET-FILE as retrievable secret ++ #[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath)] ++ secret: String, ++ ++ /// Specify the secret type. ++ /// ++ /// Limitations to the input data apply depending on the secret type. ++ #[arg(long = "type", value_name = "TYPE")] ++ kind: RetrieveableSecretInpKind, ++ }, ++} ++ ++#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] ++pub enum RetrieveableSecretInpKind { ++ /// A plaintext secret. ++ /// Can be any file up to 8190 bytes long ++ Plain, ++ /// An AES key. ++ /// Must be a plain byte file 128, 192, or 256 bit long. ++ Aes, ++ /// An AES-XTS key. ++ /// Must be a plain byte file 512, or 1024 bit long. ++ AesXts, ++ /// A HMAC-SHA key. ++ /// Must be a plain byte file 512, or 1024 bit long. ++ HmacSha, ++ /// An elliptic curve private key. ++ /// Must be a PEM or DER file. ++ Ec, ++} ++ ++impl Display for RetrieveableSecretInpKind { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!( ++ f, ++ "{}", ++ match self { ++ Self::Plain => "PLAINTEXT", ++ Self::Aes => "AES KEY", ++ Self::AesXts => "AES-XTS KEY", ++ Self::HmacSha => "HMAC-SHA KEY", ++ Self::Ec => "EC PRIVATE KEY", ++ } ++ ) ++ } + } + + // all members s390x only +@@ -238,6 +307,56 @@ pub struct VerifyOpt { + pub output: String, + } + ++// all members s390x only ++#[derive(Args, Debug)] ++pub struct RetrSecretOptions { ++ /// Specify the secret ID to be retrieved. ++ /// ++ /// Input type depends on '--inform'. If `yaml` (default) is specified, it must be a yaml ++ /// created by the create subcommand of this tool. If `hex` is specified, it must be a hex ++ /// 32-byte unsigned big endian number string. Leading zeros are required. ++ #[cfg(target_arch = "s390x")] ++ #[arg(value_name = "ID", value_hint = ValueHint::FilePath)] ++ pub input: String, ++ ++ /// Specify the output path to place the secret value ++ #[cfg(target_arch = "s390x")] ++ #[arg(short, long, value_name = "FILE", default_value = STDOUT, value_hint = ValueHint::FilePath)] ++ pub output: String, ++ ++ /// Define input type for the Secret ID ++ #[cfg(target_arch = "s390x")] ++ #[arg(long, value_enum, default_value_t)] ++ pub inform: RetrInpFmt, ++ ++ /// Define the output format for the retrieved secret ++ #[cfg(target_arch = "s390x")] ++ #[arg(long, value_enum, default_value_t)] ++ pub outform: RetrOutFmt, ++} ++ ++#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)] ++pub enum RetrInpFmt { ++ /// Use a yaml file ++ #[default] ++ Yaml, ++ /// Use a hex string. ++ Hex, ++} ++ ++#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)] ++pub enum RetrOutFmt { ++ /// Write the secret as PEM. ++ /// ++ /// File starts with `-----BEGIN IBM PROTECTED KEY----` and `-----BEGIN ++ /// PLAINTEXT SECRET-----` for plaintext secrets it contains one header ++ /// line with the type information and the base64 protected key ++ #[default] ++ Pem, ++ /// Write the secret in binary. ++ Bin, ++} ++ + #[derive(Subcommand, Debug)] + pub enum Command { + /// Create a new add-secret request. +@@ -274,6 +393,10 @@ pub enum Command { + /// provided key. Outputs the arbitrary user-data. + Verify(VerifyOpt), + ++ /// Retrieve a secret from the UV secret store (s390x only). ++ #[command(visible_alias = "retr")] ++ Retrieve(RetrSecretOptions), ++ + /// Print version information and exit. + #[command(aliases(["--version"]), hide(true))] + Version, +@@ -294,13 +417,13 @@ pub fn validate_cli(cli: &CliOptions) -> Result<(), clap::Error> { + } + if secret_out == &Some(format!("{name}.yaml")) { + return Err(CliOptions::command().error( +- clap::error::ErrorKind::ValueValidation, ++ ValueValidation, + format!("Secret output file and the secret name '{name}.yaml' are the same."), + )); + } + if format!("{name}.yaml") == opt.output { + return Err(CliOptions::command().error( +- clap::error::ErrorKind::ValueValidation, ++ ValueValidation, + format!( + "output file and the secret name '{}' are the same.", + &opt.output +diff --git a/rust/pvsecret/src/cmd.rs b/rust/pvsecret/src/cmd.rs +index a826fb3..10d99a5 100644 +--- a/rust/pvsecret/src/cmd.rs ++++ b/rust/pvsecret/src/cmd.rs +@@ -16,6 +16,8 @@ mod add; + mod list; + #[cfg(target_arch = "s390x")] + mod lock; ++#[cfg(target_arch = "s390x")] ++mod retr; + + // Commands (directly) related to UVCs are only available on s389x + #[cfg(target_arch = "s390x")] +@@ -24,12 +26,13 @@ mod uv_cmd { + pub use add::add; + pub use list::list; + pub use lock::lock; ++ pub use retr::retr; + pub const UV_CMD_FN: &[&str] = &["+add", "+lock", "+list"]; + } + + #[cfg(not(target_arch = "s390x"))] + mod uv_cmd { +- use crate::cli::{AddSecretOpt, ListSecretOpt}; ++ use crate::cli::{AddSecretOpt, ListSecretOpt, RetrSecretOptions}; + use anyhow::{bail, Result}; + macro_rules! not_supp { + ($name: ident $( ,$opt: ty )?) => { +@@ -40,6 +43,7 @@ mod uv_cmd { + } + not_supp!(add, AddSecretOpt); + not_supp!(list, ListSecretOpt); ++ not_supp!(retr, RetrSecretOptions); + not_supp!(lock); + pub const UV_CMD_FN: &[&str] = &[]; + } +diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs +index 9251c38..73089a1 100644 +--- a/rust/pvsecret/src/cmd/create.rs ++++ b/rust/pvsecret/src/cmd/create.rs +@@ -4,7 +4,6 @@ + + use std::path::Path; + +-use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt}; + use anyhow::{anyhow, bail, Context, Error, Result}; + use log::{debug, info, trace, warn}; + use pv::{ +@@ -22,6 +21,8 @@ use pv::{ + use serde_yaml::Value; + use utils::get_writer_from_cli_file_arg; + ++use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt, RetrieveableSecretInpKind}; ++ + fn write_out(path: &P, data: D, ctx: &str) -> pv::Result<()> + where + P: AsRef, +@@ -32,6 +33,23 @@ where + Ok(()) + } + ++fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Result { ++ let secret_data = read_file(secret, &format!("retrievable {kind}"))?.into(); ++ ++ match kind { ++ RetrieveableSecretInpKind::Plain => GuestSecret::plaintext(name, secret_data), ++ RetrieveableSecretInpKind::Aes => GuestSecret::aes(name, secret_data), ++ RetrieveableSecretInpKind::AesXts => GuestSecret::aes_xts(name, secret_data), ++ RetrieveableSecretInpKind::HmacSha => GuestSecret::hmac_sha(name, secret_data), ++ RetrieveableSecretInpKind::Ec => GuestSecret::ec( ++ name, ++ read_private_key(secret_data.value()) ++ .with_context(|| format!("Cannot read {secret} as {kind} from PEM or DER"))?, ++ ), ++ } ++ .map_err(Error::from) ++} ++ + /// Prepare an add-secret request + pub fn create(opt: &CreateSecretOpt) -> Result<()> { + if pv_guest_bit_set() { +@@ -88,6 +106,9 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result { + input_secret: None, + .. + } => GuestSecret::association(name, None)?, ++ AddSecretType::Retrievable { ++ name, secret, kind, .. ++ } => retrievable(name, secret, kind)?, + }; + trace!("AddSecret: {secret:x?}"); + +@@ -136,7 +157,9 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result { + .as_ref() + .map(|p| read_file(p, "User-signing key")) + .transpose()? +- .map(|buf| read_private_key(&buf)) ++ .map(|buf| { ++ read_private_key(&buf).context("Cannot read {secret} as private key from PEM or DER") ++ }) + .transpose()?; + + if user_data.is_some() || user_key.is_some() { +@@ -258,6 +281,9 @@ fn write_secret>( + write_out(path, guest_secret.confidential(), "Association secret")? + } + } ++ AddSecretType::Retrievable { name, stdout, .. } => { ++ write_yaml(name, guest_secret, stdout, outp_path)? ++ } + _ => (), + }; + Ok(()) +diff --git a/rust/pvsecret/src/cmd/list.rs b/rust/pvsecret/src/cmd/list.rs +index f7e3a72..0bd9eca 100644 +--- a/rust/pvsecret/src/cmd/list.rs ++++ b/rust/pvsecret/src/cmd/list.rs +@@ -3,21 +3,25 @@ + // Copyright IBM Corp. 2023 + + use crate::cli::{ListSecretOpt, ListSecretOutputType}; +-use anyhow::{Context, Result}; ++use anyhow::{Context, Error, Result}; + use log::warn; + use pv::uv::{ListCmd, SecretList, UvDevice, UvcSuccess}; + use utils::{get_writer_from_cli_file_arg, STDOUT}; + + /// Do a List Secrets UVC +-pub fn list(opt: &ListSecretOpt) -> Result<()> { +- let uv = UvDevice::open()?; ++pub fn list_uvc(uv: &UvDevice) -> Result { + let mut cmd = ListCmd::default(); + match uv.send_cmd(&mut cmd)? { + UvcSuccess::RC_SUCCESS => (), + UvcSuccess::RC_MORE_DATA => warn!("There is more data available than expected"), + }; ++ cmd.try_into().map_err(Error::new) ++} + +- let secret_list: SecretList = cmd.try_into()?; ++/// Do a List Secrets UVC and output the list in the requested format ++pub fn list(opt: &ListSecretOpt) -> Result<()> { ++ let uv = UvDevice::open()?; ++ let secret_list = list_uvc(&uv)?; + let mut wr_out = get_writer_from_cli_file_arg(&opt.output)?; + + match &opt.format { +diff --git a/rust/pvsecret/src/cmd/retr.rs b/rust/pvsecret/src/cmd/retr.rs +new file mode 100644 +index 0000000..7f7704c +--- /dev/null ++++ b/rust/pvsecret/src/cmd/retr.rs +@@ -0,0 +1,62 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright IBM Corp. 2024 ++ ++use super::list::list_uvc; ++use crate::cli::{RetrInpFmt, RetrOutFmt, RetrSecretOptions}; ++use anyhow::{anyhow, bail, Context, Result}; ++use log::{debug, info}; ++use pv::{ ++ misc::open_file, ++ misc::write, ++ secret::{GuestSecret, RetrievedSecret}, ++ uv::{RetrieveCmd, SecretId, UvDevice}, ++}; ++use utils::get_writer_from_cli_file_arg; ++ ++fn retrieve(id: &SecretId) -> Result { ++ let uv = UvDevice::open()?; ++ let secrets = list_uvc(&uv)?; ++ let secret = secrets ++ .into_iter() ++ .find(|s| s.id() == id.as_ref()) ++ .ok_or(anyhow!( ++ "The UV secret-store has no secret with the ID {id}" ++ ))?; ++ ++ info!("Try to retrieve secret at index: {}", secret.index()); ++ debug!("Try to retrieve: {secret:?}"); ++ ++ let mut uv_cmd = RetrieveCmd::from_entry(secret)?; ++ uv.send_cmd(&mut uv_cmd)?; ++ ++ Ok(RetrievedSecret::from_cmd(uv_cmd)) ++} ++ ++pub fn retr(opt: &RetrSecretOptions) -> Result<()> { ++ let mut output = get_writer_from_cli_file_arg(&opt.output)?; ++ let id = match &opt.inform { ++ RetrInpFmt::Yaml => match serde_yaml::from_reader(&mut open_file(&opt.input)?)? { ++ GuestSecret::Retrievable { id, .. } => id, ++ gs => bail!("The file contains a {gs}-secret, which is not retrievable."), ++ }, ++ RetrInpFmt::Hex => { ++ serde_yaml::from_str(&opt.input).context("Cannot parse SecretId information")? ++ } ++ }; ++ ++ let retr_secret = ++ retrieve(&id).context("Could not retrieve the secret from the UV secret store.")?; ++ ++ let out_data = match opt.outform { ++ RetrOutFmt::Bin => retr_secret.into_bytes(), ++ RetrOutFmt::Pem => retr_secret.to_pem()?.into_bytes(), ++ }; ++ write( ++ &mut output, ++ out_data.value(), ++ &opt.output, ++ "IBM Protected Key", ++ )?; ++ Ok(()) ++} +diff --git a/rust/pvsecret/src/main.rs b/rust/pvsecret/src/main.rs +index 502a6ea..883a3ee 100644 +--- a/rust/pvsecret/src/main.rs ++++ b/rust/pvsecret/src/main.rs +@@ -45,6 +45,7 @@ fn main() -> ExitCode { + Command::Create(opt) => cmd::create(opt), + Command::Version => Ok(print_version!("2024", log_level; FEATURES.concat())), + Command::Verify(opt) => cmd::verify(opt), ++ Command::Retrieve(opt) => cmd::retr(opt), + }; + + match res { +-- +2.47.1 + + +From bb6654ed02453ccd5bb87ca9938f06214351fe22 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 12 Jun 2024 16:23:31 +0200 +Subject: [PATCH 21/31] rust/pv_core: Refactor secret list (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Improve the secret list implementation. Use structs+{As,From}Bytes +instead of arbitrary seeks and reads/writes to parse the secret list. + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 256289a30aa5d3f6a4d2631dea69d1dc47205150) +--- + rust/pv_core/src/uvdevice.rs | 10 ++ + rust/pv_core/src/uvdevice/secret_list.rs | 124 ++++++++++++++--------- + 2 files changed, 86 insertions(+), 48 deletions(-) + +diff --git a/rust/pv_core/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs +index e984824..e701366 100644 +--- a/rust/pv_core/src/uvdevice.rs ++++ b/rust/pv_core/src/uvdevice.rs +@@ -163,6 +163,16 @@ pub enum UvcSuccess { + RC_MORE_DATA = UvDevice::RC_MORE_DATA, + } + ++impl UvcSuccess { ++ /// Returns true if there is more data available ++ pub fn more_data(&self) -> bool { ++ match self { ++ Self::RC_SUCCESS => false, ++ Self::RC_MORE_DATA => true, ++ } ++ } ++} ++ + /// The `UvDevice` is a (virtual) device on s390 machines to send Ultravisor commands(UVCs) from + /// userspace. + /// +diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs +index 4e95501..d7c268c 100644 +--- a/rust/pv_core/src/uvdevice/secret_list.rs ++++ b/rust/pv_core/src/uvdevice/secret_list.rs +@@ -4,16 +4,16 @@ + + use crate::{ + assert_size, +- misc::to_u16, + uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret}, + uvdevice::UvCmd, + Error, Result, + }; +-use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; ++use byteorder::{BigEndian, ByteOrder}; + use serde::{Deserialize, Serialize, Serializer}; + use std::{ + fmt::Display, + io::{Cursor, Read, Seek, Write}, ++ mem::size_of, + slice::Iter, + vec::IntoIter, + }; +@@ -31,7 +31,7 @@ impl SecretId { + /// Size in bytes of the [`SecretId`] + pub const ID_SIZE: usize = 32; + +- /// Create a [`SecretId`] forom a buffer. ++ /// Create a [`SecretId`] from a buffer. + pub fn from(buf: [u8; Self::ID_SIZE]) -> Self { + buf.into() + } +@@ -120,7 +120,7 @@ impl SecretEntry { + &self.index + } + +- /// Returns the secret type of this [`SecretEntry`]. ++ /// Returns the secret type of this [`SecretEntry`] + pub fn stype(&self) -> ListableSecretType { + self.stype.get().into() + } +@@ -161,12 +161,45 @@ impl Display for SecretEntry { + } + } + ++#[repr(C)] ++#[derive(Debug, FromBytes, AsBytes, FromZeroes, Clone, PartialEq, Eq, Default, Serialize)] ++struct SecretListHdr { ++ #[serde(skip)] ++ num_secrets_stored: U16, ++ #[serde(serialize_with = "ser_u16")] ++ total_num_secrets: U16, ++ #[serde(skip)] ++ next_secret_idx: U16, ++ #[serde(skip)] ++ reserved_06: u16, ++ #[serde(skip)] ++ reserved_08: u64, ++} ++ ++impl SecretListHdr { ++ fn new(num_secrets_stored: u16, total_num_secrets: u16, next_secret_idx: u16) -> Self { ++ Self { ++ num_secrets_stored: num_secrets_stored.into(), ++ total_num_secrets: total_num_secrets.into(), ++ next_secret_idx: next_secret_idx.into(), ++ reserved_06: 0, ++ reserved_08: 0, ++ } ++ } ++} ++assert_size!(SecretListHdr, 16); ++ + /// List of secrets used to parse the [`crate::uv::ListCmd`] result. + /// +-/// The list should not hold more than 0xffffffff elements +-#[derive(Debug, PartialEq, Eq, Serialize)] ++/// The list should ONLY be created from an UV-Call result using either: ++/// - [`TryInto::try_into`] from [`ListCmd`] ++/// - [`SecretList::decode`] ++/// Any other ways can create invalid lists that do not represent the UV secret store. ++/// The list must not hold more than [`u32::MAX`] elements ++#[derive(Debug, PartialEq, Eq, Serialize, Default)] + pub struct SecretList { +- total_num_secrets: usize, ++ #[serde(flatten)] ++ hdr: SecretListHdr, + secrets: Vec, + } + +@@ -202,10 +235,14 @@ impl SecretList { + /// The content of this list will very likely not represent the status of the guest in the + /// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged. + pub fn new(total_num_secrets: u16, secrets: Vec) -> Self { +- Self { +- total_num_secrets: total_num_secrets as usize, ++ Self::new_with_hdr( ++ SecretListHdr::new(total_num_secrets, total_num_secrets, 0), + secrets, +- } ++ ) ++ } ++ ++ fn new_with_hdr(hdr: SecretListHdr, secrets: Vec) -> Self { ++ Self { hdr, secrets } + } + + /// Returns an iterator over the slice. +@@ -229,19 +266,12 @@ impl SecretList { + /// + /// This number may be not equal to the provided number of [`SecretEntry`] + pub fn total_num_secrets(&self) -> usize { +- self.total_num_secrets ++ self.hdr.total_num_secrets.get() as usize + } + + /// 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 +- .try_into() +- .map_err(|_| Error::ManySecrets)?, +- )?; +- w.write_all(&[0u8; 12])?; ++ w.write_all(self.hdr.as_bytes())?; + for secret in &self.secrets { + w.write_all(secret.as_bytes())?; + } +@@ -250,19 +280,20 @@ 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::()? 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; size_of::()]; ++ r.read_exact(&mut buf)?; ++ let hdr = SecretListHdr::ref_from(&buf).unwrap(); ++ + let mut buf = [0u8; SecretEntry::STRUCT_SIZE]; +- for _ in 0..num_s { ++ let mut v = Vec::with_capacity(hdr.num_secrets_stored.get() as usize); ++ for _ in 0..hdr.num_secrets_stored.get() { + r.read_exact(&mut buf)?; + // cannot fail. buffer has the same size as the secret entry + let secr = SecretEntry::read_from(buf.as_slice()).unwrap(); + v.push(secr); + } + Ok(Self { +- total_num_secrets, ++ hdr: hdr.clone(), + secrets: v, + }) + } +@@ -278,7 +309,7 @@ impl TryFrom for SecretList { + + impl Display for SecretList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?; ++ writeln!(f, "Total number of secrets: {}", self.total_num_secrets())?; + if !self.secrets.is_empty() { + writeln!(f)?; + } +@@ -481,8 +512,8 @@ mod test { + let buf = [ + 0x00u8, 0x01, // num secr stored + 0x01, 0x12, // total num secrets +- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +- 0x00, // reserved ++ 0x01, 0x01, // next valid idx ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + // secret + 0x00, 0x01, 0x00, 0x02, // idx + type + 0x00, 0x00, 0x00, 0x20, // len +@@ -493,16 +524,16 @@ mod test { + 0x00, 0x00, 0x00, 0x00, + ]; + +- let exp = SecretList { +- total_num_secrets: 0x112, +- secrets: vec![SecretEntry { ++ let exp = SecretList::new_with_hdr( ++ SecretListHdr::new(0x001, 0x112, 0x101), ++ vec![SecretEntry { + index: 1.into(), + stype: 2.into(), + len: 32.into(), + res_8: 0, + id: SecretId::from([0; 32]), + }], +- }; ++ ); + + let mut br = BufReader::new(Cursor::new(buf)); + let sl = SecretList::decode(&mut br).unwrap(); +@@ -514,8 +545,8 @@ mod test { + const EXP: &[u8] = &[ + 0x00, 0x01, // num secr stored + 0x01, 0x12, // total num secrets +- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +- 0x00, // reserved ++ 0x01, 0x01, // next valid idx ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + // secret + 0x00, 0x01, 0x00, 0x02, // idx + type + 0x00, 0x00, 0x00, 0x20, // len +@@ -526,16 +557,16 @@ mod test { + 0x00, 0x00, 0x00, 0x00, + ]; + +- let sl = SecretList { +- total_num_secrets: 0x112, +- secrets: vec![SecretEntry { ++ let sl = SecretList::new_with_hdr( ++ SecretListHdr::new(0x001, 0x112, 0x101), ++ vec![SecretEntry { + index: 1.into(), + stype: 2.into(), + len: 32.into(), + res_8: 0, + id: SecretId::from([0; 32]), + }], +- }; ++ ); + + let mut buf = [0u8; 0x40]; + { +@@ -587,26 +618,23 @@ mod test { + + #[test] + fn secret_list_ser() { +- let list = SecretList { +- total_num_secrets: 0x112, +- secrets: vec![SecretEntry { ++ let list = SecretList::new_with_hdr( ++ SecretListHdr::new(0x001, 0x112, 0x101), ++ vec![SecretEntry { + index: 1.into(), + stype: 2.into(), + len: 32.into(), + res_8: 0, + id: SecretId::from([0; 32]), + }], +- }; ++ ); + + assert_ser_tokens( + &list, + &[ +- Token::Struct { +- name: "SecretList", +- len: 2, +- }, ++ Token::Map { len: None }, + Token::String("total_num_secrets"), +- Token::U64(0x112), ++ Token::U16(0x112), + Token::String("secrets"), + Token::Seq { len: Some(1) }, + Token::Struct { +@@ -623,7 +651,7 @@ mod test { + Token::String("0x0000000000000000000000000000000000000000000000000000000000000000"), + Token::StructEnd, + Token::SeqEnd, +- Token::StructEnd, ++ Token::MapEnd, + ], + ) + } +-- +2.47.1 + + +From 6f5bd4d347f81b077b838ec907bd53d061680392 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Wed, 12 Jun 2024 16:35:15 +0200 +Subject: [PATCH 22/31] rust/pv*: Support longer secret lists (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Make use of the enhanced list secrets UAPI for the uvdevice in the latest kernel +version. This allows fetching secret lists with more than 85 entries via +reserving more userspace memory in the IOCTL argument. + +While at it, move the errno readout next to the ioctl-syscall. + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit 93216d916c479ee1292aa1d598ac9c0e7f585bd8) +--- + rust/pv_core/src/uvdevice.rs | 6 ++++-- + rust/pv_core/src/uvdevice/secret.rs | 11 +++++++++++ + rust/pvsecret/src/cmd/list.rs | 28 +++++++++++++++++++++------- + 3 files changed, 36 insertions(+), 9 deletions(-) + +diff --git a/rust/pv_core/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs +index e701366..689748a 100644 +--- a/rust/pv_core/src/uvdevice.rs ++++ b/rust/pv_core/src/uvdevice.rs +@@ -59,11 +59,13 @@ fn ioctl_raw(raw_fd: RawFd, cmd: c_ulong, cb: &mut IoctlCb) -> Result<()> { + rc = ioctl(raw_fd, cmd, cb.as_ptr_mut()); + } + ++ // NOTE io::Error handles all errnos ioctl uses ++ let errno = std::io::Error::last_os_error(); ++ + debug!("ioctl resulted with {cb:?}"); + match rc { + 0 => Ok(()), +- // NOTE io::Error handles all errnos ioctl uses +- _ => Err(std::io::Error::last_os_error().into()), ++ _ => Err(errno.into()), + } + } + +diff --git a/rust/pv_core/src/uvdevice/secret.rs b/rust/pv_core/src/uvdevice/secret.rs +index 263f17d..cb5b723 100644 +--- a/rust/pv_core/src/uvdevice/secret.rs ++++ b/rust/pv_core/src/uvdevice/secret.rs +@@ -24,6 +24,17 @@ impl ListCmd { + Self(vec![0; size]) + } + ++ /// Create a new list secrets command with `pages` capacity. ++ /// ++ /// * `pages` - number pf pages to allocate for this IOCTL ++ /// ++ /// # Panic ++ /// This function will trigger a panic if the allocation size is larger than [`usize::MAX`]. ++ /// Very likely an OOM situation occurs way before this! ++ pub fn with_pages(pages: usize) -> Self { ++ Self::with_size(pages * PAGESIZE) ++ } ++ + /// Create a new list secrets command with a one page capacity + pub fn new() -> Self { + Self::with_size(PAGESIZE) +diff --git a/rust/pvsecret/src/cmd/list.rs b/rust/pvsecret/src/cmd/list.rs +index 0bd9eca..56294ca 100644 +--- a/rust/pvsecret/src/cmd/list.rs ++++ b/rust/pvsecret/src/cmd/list.rs +@@ -2,19 +2,33 @@ + // + // Copyright IBM Corp. 2023 + ++use std::io::ErrorKind; ++ + use crate::cli::{ListSecretOpt, ListSecretOutputType}; + use anyhow::{Context, Error, Result}; +-use log::warn; +-use pv::uv::{ListCmd, SecretList, UvDevice, UvcSuccess}; ++use log::{info, warn}; ++use pv::uv::{ListCmd, SecretList, UvDevice}; + use utils::{get_writer_from_cli_file_arg, STDOUT}; + ++const SECRET_LIST_BUF_SIZE: usize = 4; ++ + /// Do a List Secrets UVC + pub fn list_uvc(uv: &UvDevice) -> Result { +- let mut cmd = ListCmd::default(); +- match uv.send_cmd(&mut cmd)? { +- UvcSuccess::RC_SUCCESS => (), +- UvcSuccess::RC_MORE_DATA => warn!("There is more data available than expected"), +- }; ++ let mut cmd = ListCmd::with_pages(SECRET_LIST_BUF_SIZE); ++ let more_data = match uv.send_cmd(&mut cmd) { ++ Ok(v) => Ok(v), ++ Err(pv::PvCoreError::Io(e)) if e.kind() == ErrorKind::InvalidInput => { ++ info!("Uvdevice does not suport longer list. Fallback to one page list."); ++ cmd = ListCmd::default(); ++ uv.send_cmd(&mut cmd) ++ } ++ Err(e) => Err(e), ++ }? ++ .more_data(); ++ if more_data { ++ warn!("The secret list contains more data but the uvdevice cannot show all."); ++ } ++ + cmd.try_into().map_err(Error::new) + } + +-- +2.47.1 + + +From 0036b024950309a2953a3375daaa3b046c360a9b Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Mon, 5 Aug 2024 09:34:47 +0200 +Subject: [PATCH 23/31] rust/pv*: Allow the use of non-hashes secret IDs + (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Secret IDs identify a secret in the store. Tooling (pvsecret) calculates +them by hashing a user-defined string. With this patch it is now +possible to skip the hash step and directly use the input string as the +ID. Up to the first 31 bytes of the input ASCII-string are used. The last byte +is the NUL char. During list pvsecret tries to interpret the secret +as ASCII string and if possible displays the ASCII characters alongside +the hex number. + +Also, use the Upper/Lower Hex formatters for the hexstring formatting of +SecretId. Display will, additionally show the ASCII representation if +applicable. + +While at it, use Self wherever possible. + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit ff04f76257791593c8f92374f295a0c478e3b0f7) +--- + rust/pv/src/uvsecret/guest_secret.rs | 16 ++- + rust/pv_core/src/uvdevice/secret_list.rs | 163 ++++++++++++++++++++--- + rust/pvsecret/src/cli.rs | 8 ++ + rust/pvsecret/src/cmd/create.rs | 4 +- + rust/pvsecret/src/cmd/retr.rs | 14 +- + 5 files changed, 184 insertions(+), 21 deletions(-) + +diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs +index 3bad6d3..87b58bb 100644 +--- a/rust/pv/src/uvsecret/guest_secret.rs ++++ b/rust/pv/src/uvsecret/guest_secret.rs +@@ -92,7 +92,8 @@ macro_rules! retr_constructor { + } + + impl GuestSecret { +- fn name_to_id(name: &str) -> Result { ++ /// Hashes the name with sha256 ++ pub fn name_to_id(name: &str) -> Result { + let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())? + .to_vec() + .try_into() +@@ -135,6 +136,19 @@ impl GuestSecret { + retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the curve is invalid"] + | #[doc = r"EC PRIVATE Key"] => PKey, ec); + ++ /// Use the name as ID, do not hash it ++ pub fn no_hash_name(&mut self) { ++ match self { ++ Self::Null => (), ++ Self::Association { ++ name, ref mut id, .. ++ } ++ | Self::Retrievable { ++ name, ref mut id, .. ++ } => id.clone_from(&SecretId::from_string(name)), ++ } ++ } ++ + /// Reference to the confidential data + pub fn confidential(&self) -> &[u8] { + match &self { +diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs +index d7c268c..7c7e63b 100644 +--- a/rust/pv_core/src/uvdevice/secret_list.rs ++++ b/rust/pv_core/src/uvdevice/secret_list.rs +@@ -11,7 +11,9 @@ use crate::{ + use byteorder::{BigEndian, ByteOrder}; + use serde::{Deserialize, Serialize, Serializer}; + use std::{ +- fmt::Display, ++ cmp::min, ++ ffi::CStr, ++ fmt::{Debug, Display, LowerHex, UpperHex}, + io::{Cursor, Read, Seek, Write}, + mem::size_of, + slice::Iter, +@@ -35,6 +37,33 @@ impl SecretId { + pub fn from(buf: [u8; Self::ID_SIZE]) -> Self { + buf.into() + } ++ ++ /// Create a Id from a string ++ /// ++ /// Uses the first 31 bytes from `name` as id ++ /// Does not hash anything. Byte 32 is the NUL char ++ pub fn from_string(name: &str) -> Self { ++ let len = min(name.len(), Self::ID_SIZE - 1); ++ let mut res = Self::default(); ++ res.0[0..len].copy_from_slice(&name.as_bytes()[0..len]); ++ res ++ } ++ ++ /// Tries to represent the Id as printable-ASCII string ++ pub fn as_ascii(&self) -> Option<&str> { ++ if let Ok(t) = CStr::from_bytes_until_nul(&self.0) { ++ if let Ok(t) = t.to_str() { ++ if !t.is_empty() ++ && t.chars() ++ .all(|c| c.is_ascii_whitespace() | c.is_ascii_graphic()) ++ && self.0[t.len()..].iter().all(|b| *b == 0) ++ { ++ return Some(t); ++ } ++ } ++ }; ++ None ++ } + } + + impl Serialize for SecretId { +@@ -42,8 +71,8 @@ impl Serialize for SecretId { + where + S: Serializer, + { +- // calls Display at one point +- ser.serialize_str(&self.to_string()) ++ // calls LowerHex at one point ++ ser.serialize_str(&format!("{self:#x}")) + } + } + +@@ -56,12 +85,36 @@ impl<'de> Deserialize<'de> for SecretId { + } + } + ++impl UpperHex for SecretId { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ if f.alternate() { ++ write!(f, "0x")?; ++ } ++ for b in self.0 { ++ write!(f, "{b:02X}")?; ++ } ++ Ok(()) ++ } ++} ++ ++impl LowerHex for SecretId { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ if f.alternate() { ++ write!(f, "0x")?; ++ } ++ for b in self.0 { ++ write!(f, "{b:02x}")?; ++ } ++ Ok(()) ++ } ++} ++ + impl Display for SecretId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +- let mut s = String::with_capacity(32 * 2 + 2); +- s.push_str("0x"); +- let s = self.0.iter().fold(s, |acc, e| acc + &format!("{e:02x}")); +- write!(f, "{s}") ++ if let Some(s) = self.as_ascii() { ++ write!(f, "{s} | ")?; ++ } ++ write!(f, "{self:#x}") + } + } + +@@ -79,7 +132,7 @@ impl AsRef<[u8]> for SecretId { + + /// A secret in a [`SecretList`] + #[repr(C)] +-#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)] ++#[derive(Debug, Clone, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)] + pub struct SecretEntry { + #[serde(serialize_with = "ser_u16")] + index: U16, +@@ -153,11 +206,7 @@ impl Display for SecretEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let stype: ListableSecretType = self.stype.get().into(); + writeln!(f, "{} {}:", self.index, stype)?; +- write!(f, " ")?; +- for b in self.id.as_ref() { +- write!(f, "{b:02x}")?; +- } +- Ok(()) ++ write!(f, " {}", self.id) + } + } + +@@ -269,6 +318,11 @@ impl SecretList { + self.hdr.total_num_secrets.get() as usize + } + ++ /// Find the first [`SecretEntry`] that has the provided [`SecretId`] ++ pub fn find(&self, id: &SecretId) -> Option { ++ self.iter().find(|e| e.id() == id.as_ref()).cloned() ++ } ++ + /// Encodes the list in the same binary format the UV would do + pub fn encode(&self, w: &mut T) -> Result<()> { + w.write_all(self.hdr.as_bytes())?; +@@ -456,7 +510,7 @@ where + type Value = [u8; SecretId::ID_SIZE]; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { +- formatter.write_str("a `32 bytes long hexstring` prepended with 0x") ++ formatter.write_str("a `32 bytes (=64 character) long hexstring` prepended with 0x") + } + + fn visit_str(self, s: &str) -> Result +@@ -464,7 +518,10 @@ where + E: serde::de::Error, + { + if s.len() != SecretId::ID_SIZE * 2 + "0x".len() { +- return Err(serde::de::Error::invalid_length(s.len() - 2, &self)); ++ return Err(serde::de::Error::invalid_length( ++ s.len().saturating_sub("0x".len()), ++ &self, ++ )); + } + let nb = s.strip_prefix("0x").ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self) +@@ -655,4 +712,80 @@ mod test { + ], + ) + } ++ ++ #[test] ++ fn secret_id_display() { ++ let text = "Fancy secret ID"; ++ let id = SecretId::from_string(text); ++ ++ let exp = ++ "Fancy secret ID | 0x46616e6379207365637265742049440000000000000000000000000000000000"; ++ assert_eq!(id.to_string(), exp); ++ } ++ ++ #[test] ++ fn secret_id_long_name() { ++ let text = "the most fanciest secret ID you ever seen in the time the universe exists"; ++ let id = SecretId::from_string(text); ++ let exp = ++ "the most fanciest secret ID you | 0x746865206d6f73742066616e63696573742073656372657420494420796f7500"; ++ assert_eq!(id.to_string(), exp); ++ } ++ ++ #[test] ++ fn secret_id_no_ascii_name() { ++ let text = [0; 32]; ++ let id = SecretId::from(text); ++ ++ let exp = "0x0000000000000000000000000000000000000000000000000000000000000000"; ++ assert_eq!(id.to_string(), exp); ++ } ++ ++ #[test] ++ fn secret_id_no_ascii_name2() { ++ let text = [ ++ 0x25, 0x55, 3, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0, ++ 0, 0, 0, 0, 0, 0, 0, ++ ]; ++ let id = SecretId::from(text); ++ assert_eq!(id.as_ascii(), None); ++ } ++ ++ #[test] ++ fn secret_id_no_ascii_name3() { ++ let text = [ ++ 0x25, 0x55, 0, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0, ++ 0, 0, 0, 0, 0, 0, 0, ++ ]; ++ let id = SecretId::from(text); ++ assert_eq!(id.as_ascii(), None); ++ } ++ ++ #[test] ++ fn secret_id_hex() { ++ let id_str = "Nice Test 123"; ++ let id = SecretId::from_string(id_str); ++ ++ let s = format!("{id:#x}"); ++ assert_eq!( ++ s, ++ "0x4e69636520546573742031323300000000000000000000000000000000000000" ++ ); ++ let s = format!("{id:x}"); ++ assert_eq!( ++ s, ++ "4e69636520546573742031323300000000000000000000000000000000000000" ++ ); ++ let s = format!("{id:#X}"); ++ assert_eq!( ++ s, ++ "0x4E69636520546573742031323300000000000000000000000000000000000000" ++ ); ++ ++ let s = format!("{id:X}"); ++ assert_eq!( ++ s, ++ "4E69636520546573742031323300000000000000000000000000000000000000" ++ ); ++ } + } +diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs +index 4e74768..d858fc2 100644 +--- a/rust/pvsecret/src/cli.rs ++++ b/rust/pvsecret/src/cli.rs +@@ -141,6 +141,12 @@ pub struct CreateSecretOpt { + /// by default. + #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)] + pub user_sign_key: Option, ++ ++ /// Do not hash the name, use it directly as secret ID. ++ /// ++ /// Ignored for meta-secrets. ++ #[arg(long)] ++ pub use_name: bool, + } + + #[derive(Subcommand, Debug)] +@@ -342,6 +348,8 @@ pub enum RetrInpFmt { + Yaml, + /// Use a hex string. + Hex, ++ /// Use a name-string. Will hash it if no secret with the name found. ++ Name, + } + + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)] +diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs +index 73089a1..fab37e6 100644 +--- a/rust/pvsecret/src/cmd/create.rs ++++ b/rust/pvsecret/src/cmd/create.rs +@@ -94,7 +94,7 @@ fn read_private_key(buf: &[u8]) -> Result> { + fn build_asrcb(opt: &CreateSecretOpt) -> Result { + debug!("Build add-secret request"); + +- let secret = match &opt.secret { ++ let mut secret = match &opt.secret { + AddSecretType::Meta => GuestSecret::Null, + AddSecretType::Association { + name, +@@ -112,6 +112,8 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result { + }; + trace!("AddSecret: {secret:x?}"); + ++ opt.use_name.then(|| secret.no_hash_name()); ++ + let mut flags = match &opt.pcf { + Some(v) => (&try_parse_u64(v, "pcf")?).into(), + None => AddSecretFlags::default(), +diff --git a/rust/pvsecret/src/cmd/retr.rs b/rust/pvsecret/src/cmd/retr.rs +index 7f7704c..ad3e91c 100644 +--- a/rust/pvsecret/src/cmd/retr.rs ++++ b/rust/pvsecret/src/cmd/retr.rs +@@ -17,12 +17,17 @@ use utils::get_writer_from_cli_file_arg; + fn retrieve(id: &SecretId) -> Result { + let uv = UvDevice::open()?; + let secrets = list_uvc(&uv)?; +- let secret = secrets +- .into_iter() +- .find(|s| s.id() == id.as_ref()) ++ let secret = match secrets.find(id) { ++ Some(s) => s, ++ // hash it + try again if it is ASCII-representable ++ None => match id.as_ascii() { ++ Some(s) => secrets.find(&GuestSecret::name_to_id(s)?), ++ None => None, ++ } + .ok_or(anyhow!( + "The UV secret-store has no secret with the ID {id}" +- ))?; ++ ))?, ++ }; + + info!("Try to retrieve secret at index: {}", secret.index()); + debug!("Try to retrieve: {secret:?}"); +@@ -43,6 +48,7 @@ pub fn retr(opt: &RetrSecretOptions) -> Result<()> { + RetrInpFmt::Hex => { + serde_yaml::from_str(&opt.input).context("Cannot parse SecretId information")? + } ++ RetrInpFmt::Name => SecretId::from_string(&opt.input), + }; + + let retr_secret = +-- +2.47.1 + + +From cd2f4deb6e180adf0f09afe808a05bab5e4526c3 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 22 Oct 2024 17:53:17 +0200 +Subject: [PATCH 24/31] rust/pvsecret: Update manuals and README (RHEL-46894) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +(cherry picked from commit a8a3e7d49cb0d3a069dacbe54c91a31b76876846) +--- + rust/pvsecret/README.md | 222 ++++++++++++++---- + rust/pvsecret/man/pvsecret-add.1 | 9 +- + .../man/pvsecret-create-association.1 | 26 +- + rust/pvsecret/man/pvsecret-create-meta.1 | 7 +- + .../man/pvsecret-create-retrievable.1 | 74 ++++++ + rust/pvsecret/man/pvsecret-create.1 | 111 +++++---- + rust/pvsecret/man/pvsecret-list.1 | 17 +- + rust/pvsecret/man/pvsecret-lock.1 | 7 +- + rust/pvsecret/man/pvsecret-retrieve.1 | 77 ++++++ + rust/pvsecret/man/pvsecret-verify.1 | 17 +- + rust/pvsecret/man/pvsecret.1 | 34 ++- + 11 files changed, 449 insertions(+), 152 deletions(-) + create mode 100644 rust/pvsecret/man/pvsecret-create-retrievable.1 + create mode 100644 rust/pvsecret/man/pvsecret-retrieve.1 + +diff --git a/rust/pvsecret/README.md b/rust/pvsecret/README.md +index b31d3de..711f81d 100644 +--- a/rust/pvsecret/README.md ++++ b/rust/pvsecret/README.md +@@ -32,7 +32,7 @@ Create a new add-secret request + + - **add** +
    +-Perform an add-secret request (s390x only) ++Submit an add-secret request to the Ultravisor (s390x only) +
+ + - **lock** +@@ -50,23 +50,34 @@ List all ultravisor secrets (s390x only) + Verify that an add-secret request is sane + + ++- **retrieve** ++
    ++Retrieve a secret from the UV secret store (s390x only) ++
++ + ## Options + + `-v`, `--verbose` +
    +-Provide more detailed output ++Provide more detailed output. ++
++ ++ ++`-q`, `--quiet` ++
    ++Provide less output. +
+ + + `--version` +
    +-Print version information and exit ++Print version information and exit. +
+ + + `-h`, `--help` +
    +-Print help ++Print help (see a summary with '-h'). +
+ + +@@ -95,12 +106,17 @@ Create a meta secret + Create an association secret + + ++- **retrievable** ++
    ++Create a retrievable secret ++
++ + ### Options + + `-k`, `--host-key-document ` +
    + Use FILE as a host-key document. Can be specified multiple times and must be +-used at least once. ++specified at least once. +
+ + +@@ -114,7 +130,7 @@ the host-key document beforehand. + + `-C`, `--cert ` +
    +-Use FILE as a certificate to verify the host key or keys. The certificates are ++Use FILE as a certificate to verify the host-key or keys. The certificates are + used to establish a chain of trust for the verification of the host-key + documents. Specify this option twice to specify the IBM Z signing key and the + intermediate CA certificate (signed by the root CA). +@@ -123,15 +139,15 @@ intermediate CA certificate (signed by the root CA). + + `--crl ` +
      +-Use FILE as a certificate revocation list. The list is used to check whether a +-certificate of the chain of trust is revoked. Specify this option multiple times +-to use multiple CRLs. ++Use FILE as a certificate revocation list (CRL). The list is used to check ++whether a certificate of the chain of trust is revoked. Specify this option ++multiple times to use multiple CRLs. +
    + + + `--offline` +
      +-Make no attempt to download CRLs ++Make no attempt to download CRLs. +
    + + +@@ -146,8 +162,7 @@ specified certificate. + `--hdr ` +
      + Specifies the header of the guest image. Can be an IBM Secure Execution image +-created by genprotimg or an extracted IBM Secure Execution header. The header +-must start at a page boundary. ++created by 'pvimg/genprotimg' or an extracted IBM Secure Execution header. +
    + + +@@ -162,7 +177,7 @@ behavior. + + `-o`, `--output ` +
      +-Write the generated request to FILE ++Write the generated request to FILE. +
    + + +@@ -209,15 +224,15 @@ the request. + + `--flags ` +
      +-Flags for the add-secret request ++Flags for the add-secret request. + Possible values: +- - **disable-dump**: Disables host-initiated dumping for the target guest instance ++ - **disable-dump**: Disables host-initiated dumping for the target guest instance. +
    + + + `--user-data ` +
      +-Use the content of FILE as user-data. Passes user data defined in through ++Use the content of FILE as user-data. Passes user data defined in FILE through + the add-secret request to the ultravisor. The user data can be up to 512 bytes + of arbitrary data, and the maximum size depends on the size of the user-signing + key: +@@ -236,19 +251,25 @@ Optional. No user-data by default. + `--user-sign-key ` +
        + Use the content of FILE as user signing key. Adds a signature calculated from +-the key in to the add-secret request. The file must be in DER or PEM +-format containing a private key. Supported are RSA 2048 & 3072-bit and +-EC(secp521r1) keys. The firmware ignores the content, but the request tag +-protects the signature. The user-signing key signs the request. The location of +-the signature is filled with zeros during the signature calculation. The request +-tag also secures the signature. See man pvsecret verify for more details. +-Optional. No signature by default. ++the key in FILE to the add-secret request. The file must be in DER or PEM format ++containing a private key. Supported are RSA 2048 & 3072-bit and EC(secp521r1) ++keys. The firmware ignores the content, but the request tag protects the ++signature. The user-signing key signs the request. The location of the signature ++is filled with zeros during the signature calculation. The request tag also ++secures the signature. See man pvsecret verify for more details. Optional. No ++signature by default. ++
      ++ ++ ++`--use-name` ++
        ++Do not hash the name, use it directly as secret ID. Ignored for meta-secrets. +
      + + + `-h`, `--help` +
        +-Print help ++Print help (see a summary with '-h'). +
      + + +@@ -265,14 +286,15 @@ of secrets. + `pvsecret create association [OPTIONS] ` + #### Description + Create an association secret. Use an association secret to connect a trusted I/O +-device to a guest. The `pvapconfig` tool provides more information about ++device to a guest. The 'pvapconfig' tool provides more information about + association secrets. + #### Arguments + + `` +
        +-String to identify the new secret. The actual secret is set with --input-secret. +-The name is saved in `NAME.yaml` with white-spaces mapped to `_`. ++String that identifies the new secret. The actual secret is set with ++'--input-secret'. The name is saved in `NAME.yaml` with white-spaces mapped to ++`_`. +
      + + +@@ -284,24 +306,76 @@ Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` +
    + + +-`--input-secret ` ++`--input-secret ` +
      + Path from which to read the plaintext secret. Uses a random secret if not +-specified ++specified. ++
    ++ ++ ++`--output-secret ` ++
      ++Save the generated secret as plaintext in SECRET-FILE. The generated secret can ++be used to generate add-secret requests for a different guest with the same ++secret using '--input-secret'. Destroy the secret when it is not used anymore. ++
    ++ ++ ++`-h`, `--help` ++
      ++Print help (see a summary with '-h'). ++
    ++ ++ ++### pvsecret create retrievable ++#### Synopsis ++`pvsecret create retrievable [OPTIONS] --secret --type ` ++`pvsecret create retr [OPTIONS] --secret --type ` ++#### Description ++Create a retrievable secret. A retrievable secret is stored in the per-guest ++storage of the Ultravisor. A SE-guest can retrieve the secret at runtime and use ++it. All retrievable secrets, but the plaintext secret, are retrieved as ++wrapped/protected key objects and only usable inside the current, running ++SE-guest instance. ++#### Arguments ++ ++`` ++
      ++String that identifies the new secret. The actual secret is set with '--secret'. ++The name is saved in `NAME.yaml` with white-spaces mapped to `_`. ++
    ++ ++ ++#### Options ++ ++`--stdout` ++
      ++Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` ++
    ++ ++ ++`--secret ` ++
      ++Use SECRET-FILE as retrievable secret. +
    + + +-`--output-secret ` ++`--type ` +
      +-Save the generated secret as plaintext in FILE. The generated secret can be used +-to generate add-secret requests for a different guest with the same secret using +---input-secret. Destroy the secret when it is not used anymore. ++Specify the secret type. Limitations to the input data apply depending on the ++secret type. ++ Possible values: ++ - **plain**: A plaintext secret. Can be any file up to 8190 bytes long. ++ - **aes**: An AES key. Must be a plain byte file 128, 192, or 256 bit long. ++ - **aes-xts**: An AES-XTS key. Must be a plain byte file 512, or 1024 bit long. ++ - **hmac-sha**: A HMAC-SHA key. Must be a plain byte file 512, or 1024 bit long. ++ - **ec**: An elliptic curve private key. Must be a PEM or DER file. +
    + + + `-h`, `--help` +
      +-Print help ++Print help (see a summary with '-h'). +
    + + +@@ -309,13 +383,14 @@ Print help + ### Synopsis + `pvsecret add ` + ### Description +-Perform an add-secret request (s390x only). Perform an add-secret request using +-a previously generated add-secret request. Only available on s390x. ++Submit an add-secret request to the Ultravisor (s390x only). Perform an ++add-secret request using a previously generated add-secret request. Only ++available on s390x. + ### Arguments + + `` +
      +-Specify the request to be sent ++Specify the request to be sent. +
    + + +@@ -325,8 +400,8 @@ Specify the request to be sent + `pvsecret lock` + ### Description + Lock the secret-store (s390x only). Lock the secret store (s390x only). After +-this command executed successfully, all add-secret requests will fail. Only +-available on s390x. ++this command executed successfully, all subsequent add-secret requests will ++fail. Only available on s390x. + + ## pvsecret list + ### Synopsis +@@ -339,7 +414,7 @@ Execution guest. Only available on s390x. + + `` +
      +-Store the result in FILE ++Store the result in FILE. + Default value: '-' +
    + +@@ -348,18 +423,18 @@ Store the result in FILE + + `--format ` +
      +-Define the output format of the list ++Define the output format of the list. + Default value: 'human' + Possible values: +- - **human**: Human-focused, non-parsable output format +- - **yaml**: Use yaml format +- - **bin**: Use the format the ultravisor uses to pass the list ++ - **human**: Human-focused, non-parsable output format. ++ - **yaml**: Use yaml format. ++ - **bin**: Use the format the ultravisor uses to pass the list. +
    + + + `-h`, `--help` +
      +-Print help ++Print help (see a summary with '-h'). +
    + + +@@ -407,7 +482,7 @@ The verification process works as follows: + + `` +
      +-Specify the request to be checked ++Specify the request to be checked. +
    + + +@@ -435,5 +510,58 @@ contains this user-data with padded zeros if available. + + `-h`, `--help` +
      +-Print help ++Print help (see a summary with '-h'). ++
    ++ ++ ++## pvsecret retrieve ++### Synopsis ++`pvsecret retrieve [OPTIONS] ` ++`pvsecret retr [OPTIONS] ` ++### Description ++Retrieve a secret from the UV secret store (s390x only) ++### Arguments ++ ++`` ++
      ++Specify the secret ID to be retrieved. Input type depends on '--inform'. If ++`yaml` (default) is specified, it must be a yaml created by the create ++subcommand of this tool. If `hex` is specified, it must be a hex 32-byte ++unsigned big endian number string. Leading zeros are required. ++
    ++ ++ ++### Options ++ ++`-o`, `--output ` ++
      ++Specify the output path to place the secret value. ++ Default value: '-' ++
    ++ ++ ++`--inform ` ++
      ++Define input type for the Secret ID. ++ Default value: 'yaml' ++ Possible values: ++ - **yaml**: Use a yaml file. ++ - **hex**: Use a hex string. ++ - **name**: Use a name-string. Will hash it if no secret with the name found. ++
    ++ ++ ++`--outform ` ++
      ++Define the output format for the retrieved secret. ++ Default value: 'pem' ++ Possible values: ++ - **pem**: Write the secret as PEM. ++ - **bin**: Write the secret in binary. ++
    ++ ++ ++`-h`, `--help` ++
      ++Print help (see a summary with '-h'). +
    +diff --git a/rust/pvsecret/man/pvsecret-add.1 b/rust/pvsecret/man/pvsecret-add.1 +index a84702f..5ac54a9 100644 +--- a/rust/pvsecret/man/pvsecret-add.1 ++++ b/rust/pvsecret/man/pvsecret-add.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-add 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-ADD" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret add\fP - Perform an add-secret request (s390x only) +-\fB ++pvsecret-add \- Submit an add-secret request to the Ultravisor (s390x only) + .SH SYNOPSIS + .nf + .fam C +@@ -16,7 +15,7 @@ pvsecret add + .fam C + .fi + .SH DESCRIPTION +-Perform an add-secret request using a previously generated add-secret request. ++Perform an add\-secret request using a previously generated add\-secret request. + Only available on s390x. + .SH OPTIONS + .PP +@@ -29,7 +28,7 @@ Specify the request to be sent. + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret-create-association.1 b/rust/pvsecret/man/pvsecret-create-association.1 +index 5704d30..87a411e 100644 +--- a/rust/pvsecret/man/pvsecret-create-association.1 ++++ b/rust/pvsecret/man/pvsecret-create-association.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-create-association 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-CREATE-ASSOCIATION" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret create association\fP - Create an association secret +-\fB ++pvsecret-create-association \- Create an association secret + .SH SYNOPSIS + .nf + .fam C +@@ -17,14 +16,14 @@ pvsecret create association [OPTIONS] + .fi + .SH DESCRIPTION + Use an association secret to connect a trusted I/O device to a guest. The +-`pvapconfig` tool provides more information about association secrets. ++\fBpvapconfig\fR tool provides more information about association secrets. + .SH OPTIONS + .PP + + .RS 4 +-String to identify the new secret. The actual secret is set with +-\fB--input-secret\fR. The name is saved in `NAME.yaml` with white-spaces mapped +-to `_`. ++String that identifies the new secret. The actual secret is set with ++\fB\-\-input\-secret\fR. The name is saved in `NAME.yaml` with white\-spaces ++mapped to `_`. + .RE + .RE + +@@ -35,24 +34,25 @@ Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` + .RE + .RE + .PP +-\-\-input-secret ++\-\-input\-secret + .RS 4 + Path from which to read the plaintext secret. Uses a random secret if not + specified. + .RE + .RE + .PP +-\-\-output-secret ++\-\-output\-secret + .RS 4 +-Save the generated secret as plaintext in FILE. The generated secret can be used +-to generate add-secret requests for a different guest with the same secret using +-\fB--input-secret\fR. Destroy the secret when it is not used anymore. ++Save the generated secret as plaintext in SECRET\-FILE. The generated secret can ++be used to generate add\-secret requests for a different guest with the same ++secret using \fB\-\-input\-secret\fR. Destroy the secret when it is not used ++anymore. + .RE + .RE + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret-create-meta.1 b/rust/pvsecret/man/pvsecret-create-meta.1 +index c89cee7..78a57a2 100644 +--- a/rust/pvsecret/man/pvsecret-create-meta.1 ++++ b/rust/pvsecret/man/pvsecret-create-meta.1 +@@ -1,14 +1,13 @@ +-.\" Copyright 2023 IBM Corp. ++.\" Copyright 2023, 2024 IBM Corp. + .\" s390-tools is free software; you can redistribute it and/or modify + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-create-meta 1 "2024-01-30" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-CREATE-META" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret create meta\fP - Create a meta secret +-\fB ++pvsecret-create-meta \- Create a meta secret + .SH SYNOPSIS + .nf + .fam C +diff --git a/rust/pvsecret/man/pvsecret-create-retrievable.1 b/rust/pvsecret/man/pvsecret-create-retrievable.1 +new file mode 100644 +index 0000000..0d7575e +--- /dev/null ++++ b/rust/pvsecret/man/pvsecret-create-retrievable.1 +@@ -0,0 +1,74 @@ ++.\" Copyright 2024 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++ ++.TH "PVSECRET-CREATE-RETRIEVABLE" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" ++.nh ++.ad l ++.SH NAME ++pvsecret-create-retrievable \- Create a retrievable secret ++.SH SYNOPSIS ++.nf ++.fam C ++pvsecret create retrievable [OPTIONS] --secret --type ++pvsecret create retr [OPTIONS] --secret --type ++.fam C ++.fi ++.SH DESCRIPTION ++A retrievable secret is stored in the per\-guest storage of the Ultravisor. A ++SE\-guest can retrieve the secret at runtime and use it. All retrievable ++secrets, but the plaintext secret, are retrieved as wrapped/protected key ++objects and only usable inside the current, running SE\-guest instance. ++.SH OPTIONS ++.PP ++ ++.RS 4 ++String that identifies the new secret. The actual secret is set with ++\fB\-\-secret\fR. The name is saved in `NAME.yaml` with white\-spaces mapped to ++`_`. ++.RE ++.RE ++ ++.PP ++\-\-stdout ++.RS 4 ++Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` ++.RE ++.RE ++.PP ++\-\-secret ++.RS 4 ++Use SECRET\-FILE as retrievable secret. ++.RE ++.RE ++.PP ++\-\-type ++.RS 4 ++Specify the secret type. Limitations to the input data apply depending on the ++secret type. ++ ++Possible values: ++.RS 4 ++\- \fBplain\fP: A plaintext secret. Can be any file up to 8190 bytes long. ++ ++\- \fBaes\fP: An AES key. Must be a plain byte file 128, 192, or 256 bit long. ++ ++\- \fBaes-xts\fP: An AES-XTS key. Must be a plain byte file 512, or 1024 bit long. ++ ++\- \fBhmac-sha\fP: A HMAC-SHA key. Must be a plain byte file 512, or 1024 bit long. ++ ++\- \fBec\fP: An elliptic curve private key. Must be a PEM or DER file. ++ ++.RE ++.RE ++.PP ++\-h, \-\-help ++.RS 4 ++Print help (see a summary with \fB\-h\fR). ++.RE ++.RE ++ ++.SH "SEE ALSO" ++.sp ++\fBpvsecret\fR(1) \fBpvsecret-create\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-create.1 b/rust/pvsecret/man/pvsecret-create.1 +index 8237c06..87c8d8b 100644 +--- a/rust/pvsecret/man/pvsecret-create.1 ++++ b/rust/pvsecret/man/pvsecret-create.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-create 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-CREATE" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret create\fP - Create a new add-secret request +-\fB ++pvsecret-create \- Create a new add-secret request + .SH SYNOPSIS + .nf + .fam C +@@ -29,39 +28,46 @@ bound to the Configuration Unique ID from \fBpvattest\fR using \fB--cuid\fR + .SH "PVSECRET CREATE COMMANDS" + .PP + +-\fBmeta\fR ++\fBpvsecret create-meta(1)\fR + .RS 4 + Create a meta secret + .RE + + .PP + +-\fBassociation\fR ++\fBpvsecret create-association(1)\fR + .RS 4 + Create an association secret + .RE + ++.PP ++ ++\fBpvsecret create-retrievable(1)\fR ++.RS 4 ++Create a retrievable secret ++.RE ++ + .SH OPTIONS + .PP +-\-k, \-\-host-key-document ++\-k, \-\-host\-key\-document + .RS 4 +-Use FILE as a host-key document. Can be specified multiple times and must be +-used at least once. ++Use FILE as a host\-key document. Can be specified multiple times and must be ++specified at least once. + .RE + .RE + .PP +-\-\-no-verify ++\-\-no\-verify + .RS 4 +-Disable the host-key document verification. Does not require the host-key ++Disable the host\-key document verification. Does not require the host\-key + documents to be valid. Do not use for a production request unless you verified +-the host-key document beforehand. ++the host\-key document beforehand. + .RE + .RE + .PP + \-C, \-\-cert + .RS 4 +-Use FILE as a certificate to verify the host key or keys. The certificates are +-used to establish a chain of trust for the verification of the host-key ++Use FILE as a certificate to verify the host\-key or keys. The certificates are ++used to establish a chain of trust for the verification of the host\-key + documents. Specify this option twice to specify the IBM Z signing key and the + intermediate CA certificate (signed by the root CA). + .RE +@@ -69,9 +75,9 @@ intermediate CA certificate (signed by the root CA). + .PP + \-\-crl + .RS 4 +-Use FILE as a certificate revocation list. The list is used to check whether a +-certificate of the chain of trust is revoked. Specify this option multiple times +-to use multiple CRLs. ++Use FILE as a certificate revocation list (CRL). The list is used to check ++whether a certificate of the chain of trust is revoked. Specify this option ++multiple times to use multiple CRLs. + .RE + .RE + .PP +@@ -81,27 +87,26 @@ Make no attempt to download CRLs. + .RE + .RE + .PP +-\-\-root-ca ++\-\-root\-ca + .RS 4 +-Use FILE as the root-CA certificate for the verification. If omitted, the system +-wide-root CAs installed on the system are used. Use this only if you trust the +-specified certificate. ++Use FILE as the root\-CA certificate for the verification. If omitted, the ++system wide\-root CAs installed on the system are used. Use this only if you ++trust the specified certificate. + .RE + .RE + .PP + \-\-hdr + .RS 4 + Specifies the header of the guest image. Can be an IBM Secure Execution image +-created by genprotimg or an extracted IBM Secure Execution header. The header +-must start at a page boundary. ++created by \fBpvimg/genprotimg\fR or an extracted IBM Secure Execution header. + .RE + .RE + .PP + \-f, \-\-force + .RS 4 +-Force the generation of add-secret requests on IBM Secure Execution guests. If ++Force the generation of add\-secret requests on IBM Secure Execution guests. If + the program detects that it is running on an IBM Secure Execution guest, it +-denies the generation of add-secret requests. The force flag overwrites this ++denies the generation of add\-secret requests. The force flag overwrites this + behavior. + .RE + .RE +@@ -112,7 +117,7 @@ Write the generated request to FILE. + .RE + .RE + .PP +-\-\-extension-secret ++\-\-extension\-secret + .RS 4 + Use the content of FILE as an extension secret. The file must be exactly 32 + bytes long. If this request is the first, all subsequent requests must have the +@@ -124,7 +129,7 @@ request. + .PP + \-\-cck + .RS 4 +-Use the content of FILE as the customer-communication key (CCK) to derive the ++Use the content of FILE as the customer\-communication key (CCK) to derive the + extension secret. The file must contain exactly 32 bytes of data. If the target + guest was started with bit 1 of the secret control flag set, the ultravisor also + derives the secret from the CCK. Otherwise, the ultravisor interprets the +@@ -133,13 +138,13 @@ all requests. + .RE + .RE + .PP +-\-\-cuid-hex ++\-\-cuid\-hex + .RS 4 +-Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned big +-endian number string. Leading zeros must be provided. If specified, the value +-must match with the Config-UID from the attestation result of that guest. If not +-specified, the CUID will be ignored by the ultravisor during the verification of +-the request. ++Use HEXSTRING as the Configuration Unique ID. Must be a hex 128\-bit unsigned ++big endian number string. Leading zeros must be provided. If specified, the ++value must match with the Config\-UID from the attestation result of that guest. ++If not specified, the CUID will be ignored by the ultravisor during the ++verification of the request. + .RE + .RE + .PP +@@ -147,7 +152,7 @@ the request. + .RS 4 + Use the content of FILE as the Configuration Unique ID. The file must contain + exactly 128 bit of data or a yaml with a `cuid` entry. If specified, the value +-must match the Config-UID from the attestation result of that guest. If not ++must match the Config\-UID from the attestation result of that guest. If not + specified, the CUID will be ignored by the Ultravisor during the verification of + the request. + .RE +@@ -155,52 +160,58 @@ the request. + .PP + \-\-flags + .RS 4 +-Flags for the add-secret request. ++Flags for the add\-secret request. + + Possible values: + .RS 4 +-- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance. ++\- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance. + + .RE + .RE + .PP +-\-\-user-data ++\-\-user\-data + .RS 4 +-Use the content of FILE as user-data. Passes user data defined in through +-the add-secret request to the ultravisor. The user data can be up to 512 bytes +-of arbitrary data, and the maximum size depends on the size of the user-signing ++Use the content of FILE as user\-data. Passes user data defined in FILE through ++the add\-secret request to the ultravisor. The user data can be up to 512 bytes ++of arbitrary data, and the maximum size depends on the size of the user\-signing + key: + +- - No key: user data can be 512 bytes. ++ \- No key: user data can be 512 bytes. + +- - EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes. ++ \- EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes. + +- - RSA 3072 key: user data can be 128 bytes. ++ \- RSA 3072 key: user data can be 128 bytes. + +-The firmware ignores this data, but the request tag protects the user-data. +-Optional. No user-data by default. ++The firmware ignores this data, but the request tag protects the user\-data. ++Optional. No user\-data by default. + .RE + .RE + .PP +-\-\-user-sign-key ++\-\-user\-sign\-key + .RS 4 + Use the content of FILE as user signing key. Adds a signature calculated from +-the key in to the add-secret request. The file must be in DER or PEM +-format containing a private key. Supported are RSA 2048 & 3072-bit and ++the key in FILE to the add\-secret request. The file must be in DER or PEM ++format containing a private key. Supported are RSA 2048 & 3072\-bit and + EC(secp521r1) keys. The firmware ignores the content, but the request tag +-protects the signature. The user-signing key signs the request. The location of ++protects the signature. The user\-signing key signs the request. The location of + the signature is filled with zeros during the signature calculation. The request + tag also secures the signature. See man pvsecret verify for more details. + Optional. No signature by default. + .RE + .RE + .PP ++\-\-use\-name ++.RS 4 ++Do not hash the name, use it directly as secret ID. Ignored for meta\-secrets. ++.RE ++.RE ++.PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + + .SH "SEE ALSO" + .sp +-\fBpvsecret\fR(1) \fBpvsecret-create-meta\fR(1) \fBpvsecret-create-association\fR(1) ++\fBpvsecret\fR(1) \fBpvsecret-create-meta\fR(1) \fBpvsecret-create-association\fR(1) \fBpvsecret-create-retrievable\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-list.1 b/rust/pvsecret/man/pvsecret-list.1 +index 2828179..4dfc303 100644 +--- a/rust/pvsecret/man/pvsecret-list.1 ++++ b/rust/pvsecret/man/pvsecret-list.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-list 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-LIST" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret list\fP - List all ultravisor secrets (s390x only) +-\fB ++pvsecret-list \- List all ultravisor secrets (s390x only) + .SH SYNOPSIS + .nf + .fam C +@@ -16,8 +15,8 @@ pvsecret list [OPTIONS] [FILE] + .fam C + .fi + .SH DESCRIPTION +-Lists the IDs of all non-null secrets currently stored in the ultravisor for the +-currently running IBM Secure Execution guest. Only available on s390x. ++Lists the IDs of all non\-null secrets currently stored in the ultravisor for ++the currently running IBM Secure Execution guest. Only available on s390x. + .SH OPTIONS + .PP + +@@ -35,18 +34,18 @@ Define the output format of the list. + + Possible values: + .RS 4 +-- \fBhuman\fP: Human-focused, non-parsable output format. ++\- \fBhuman\fP: Human-focused, non-parsable output format. + +-- \fByaml\fP: Use yaml format. ++\- \fByaml\fP: Use yaml format. + +-- \fBbin\fP: Use the format the ultravisor uses to pass the list. ++\- \fBbin\fP: Use the format the ultravisor uses to pass the list. + + .RE + .RE + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret-lock.1 b/rust/pvsecret/man/pvsecret-lock.1 +index c59c34d..d5b1ab2 100644 +--- a/rust/pvsecret/man/pvsecret-lock.1 ++++ b/rust/pvsecret/man/pvsecret-lock.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-lock 1 "2024-05-15" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-LOCK" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret lock\fP - Lock the secret-store (s390x only) +-\fB ++pvsecret-lock \- Lock the secret-store (s390x only) + .SH SYNOPSIS + .nf + .fam C +@@ -17,7 +16,7 @@ pvsecret lock + .fi + .SH DESCRIPTION + Lock the secret store (s390x only). After this command executed successfully, +-all add-secret requests will fail. Only available on s390x. ++all subsequent add\-secret requests will fail. Only available on s390x. + .SH "SEE ALSO" + .sp + \fBpvsecret\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-retrieve.1 b/rust/pvsecret/man/pvsecret-retrieve.1 +new file mode 100644 +index 0000000..369037f +--- /dev/null ++++ b/rust/pvsecret/man/pvsecret-retrieve.1 +@@ -0,0 +1,77 @@ ++.\" Copyright 2024 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++ ++.TH "PVSECRET-RETRIEVE" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" ++.nh ++.ad l ++.SH NAME ++pvsecret-retrieve \- Retrieve a secret from the UV secret store (s390x only) ++.SH SYNOPSIS ++.nf ++.fam C ++pvsecret retrieve [OPTIONS] ++pvsecret retr [OPTIONS] ++.fam C ++.fi ++.SH DESCRIPTION ++Retrieve a secret from the UV secret store (s390x only) ++.SH OPTIONS ++.PP ++ ++.RS 4 ++Specify the secret ID to be retrieved. Input type depends on \fB\-\-inform\fR. ++If `yaml` (default) is specified, it must be a yaml created by the create ++subcommand of this tool. If `hex` is specified, it must be a hex 32\-byte ++unsigned big endian number string. Leading zeros are required. ++.RE ++.RE ++ ++.PP ++\-o, \-\-output ++.RS 4 ++Specify the output path to place the secret value. ++[default: '-'] ++.RE ++.RE ++.PP ++\-\-inform ++.RS 4 ++Define input type for the Secret ID. ++[default: 'yaml'] ++ ++Possible values: ++.RS 4 ++\- \fByaml\fP: Use a yaml file. ++ ++\- \fBhex\fP: Use a hex string. ++ ++\- \fBname\fP: Use a name-string. Will hash it if no secret with the name found. ++ ++.RE ++.RE ++.PP ++\-\-outform ++.RS 4 ++Define the output format for the retrieved secret. ++[default: 'pem'] ++ ++Possible values: ++.RS 4 ++\- \fBpem\fP: Write the secret as PEM. ++ ++\- \fBbin\fP: Write the secret in binary. ++ ++.RE ++.RE ++.PP ++\-h, \-\-help ++.RS 4 ++Print help (see a summary with \fB\-h\fR). ++.RE ++.RE ++ ++.SH "SEE ALSO" ++.sp ++\fBpvsecret\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-verify.1 b/rust/pvsecret/man/pvsecret-verify.1 +index a9d636f..136ecad 100644 +--- a/rust/pvsecret/man/pvsecret-verify.1 ++++ b/rust/pvsecret/man/pvsecret-verify.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-verify 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-VERIFY" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret verify\fP - Verify that an add-secret request is sane +-\fB ++pvsecret-verify \- Verify that an add-secret request is sane + .SH SYNOPSIS + .nf + .fam C +@@ -89,12 +88,12 @@ Specify the request to be checked. + .RE + + .PP +-\-\-user-cert ++\-\-user\-cert + .RS 4 + Certificate containing a public key used to verify the user data signature. +-Specifies a public key used to verify the user-data signature. The file must be ++Specifies a public key used to verify the user\-data signature. The file must be + a X509 certificate in DSA or PEM format. The certificate must hold the public +-EC, RSA 2048, or RSA 3072 key corresponding to the private user-key used during ++EC, RSA 2048, or RSA 3072 key corresponding to the private user\-key used during + `create`. No chain of trust is established. Ensuring that the certificate can be + trusted is the responsibility of the user. The EC key must use the NIST/SECG + curve over a 521 bit prime field (secp521r1). +@@ -103,15 +102,15 @@ curve over a 521 bit prime field (secp521r1). + .PP + \-o, \-\-output + .RS 4 +-Store the result in FILE If the request contained abirtary user-data the output +-contains this user-data with padded zeros if available. ++Store the result in FILE If the request contained abirtary user\-data the output ++contains this user\-data with padded zeros if available. + [default: '-'] + .RE + .RE + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret.1 b/rust/pvsecret/man/pvsecret.1 +index b2a1d0f..e8cb132 100644 +--- a/rust/pvsecret/man/pvsecret.1 ++++ b/rust/pvsecret/man/pvsecret.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret\fP - Manage secrets for IBM Secure Execution guests +-\fB ++pvsecret \- Manage secrets for IBM Secure Execution guests + .SH SYNOPSIS + .nf + .fam C +@@ -36,39 +35,46 @@ both the PEM and DER input formats are supported. + .SH "PVSECRET COMMANDS" + .PP + +-\fBcreate\fR ++\fBpvsecret-create(1)\fR + .RS 4 + Create a new add-secret request + .RE + + .PP + +-\fBadd\fR ++\fBpvsecret-add(1)\fR + .RS 4 +-Perform an add-secret request (s390x only) ++Submit an add-secret request to the Ultravisor (s390x only) + .RE + + .PP + +-\fBlock\fR ++\fBpvsecret-lock(1)\fR + .RS 4 + Lock the secret-store (s390x only) + .RE + + .PP + +-\fBlist\fR ++\fBpvsecret-list(1)\fR + .RS 4 + List all ultravisor secrets (s390x only) + .RE + + .PP + +-\fBverify\fR ++\fBpvsecret-verify(1)\fR + .RS 4 + Verify that an add-secret request is sane + .RE + ++.PP ++ ++\fBpvsecret-retrieve(1)\fR ++.RS 4 ++Retrieve a secret from the UV secret store (s390x only) ++.RE ++ + .SH OPTIONS + .PP + \-v, \-\-verbose +@@ -77,6 +83,12 @@ Provide more detailed output. + .RE + .RE + .PP ++\-q, \-\-quiet ++.RS 4 ++Provide less output. ++.RE ++.RE ++.PP + \-\-version + .RS 4 + Print version information and exit. +@@ -85,7 +97,7 @@ Print version information and exit. + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +@@ -138,4 +150,4 @@ On the SE-guest, \fIlock\fP the secret store. + .fi + .SH "SEE ALSO" + .sp +-\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-verify\fR(1) ++\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-verify\fR(1) \fBpvsecret-retrieve\fR(1) +-- +2.47.1 + + +From 2f531935c1dab94336e6e197d9fdb9bcb54ff9ad Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 09:08:43 +0100 +Subject: [PATCH 25/31] zkey: Add support for retrieving a list of ultravisor + secrets (RHEL-23870) + +Add functions to interface with the ultravisor device (/dev/uv) when +running in a secure execution guest to retrieve a list of available +secrets. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit 8c4b2872b8e24c1a27d8201beb5979c66ac05268) +--- + zkey/Makefile | 4 +- + zkey/pvsecrets.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++ + zkey/pvsecrets.h | 89 ++++++++++++++++++++++++++ + 3 files changed, 253 insertions(+), 1 deletion(-) + create mode 100644 zkey/pvsecrets.c + create mode 100644 zkey/pvsecrets.h + +diff --git a/zkey/Makefile b/zkey/Makefile +index 501c5eb..cbecf12 100644 +--- a/zkey/Makefile ++++ b/zkey/Makefile +@@ -92,9 +92,11 @@ keystore.o: keystore.c keystore.h properties.h pkey.h cca.h ep11.h utils.h + zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h cca.h \ + ep11.h misc.h utils.h + kms.o: kms.c kms.h kms-plugin.h utils.h pkey.h ++pvsecrets.o: pvsecrets.h + + zkey: LDLIBS = -ldl -lcrypto +-zkey: zkey.o pkey.o cca.o ep11.o properties.o keystore.o utils.o kms.o $(libs) ++zkey: zkey.o pkey.o cca.o ep11.o properties.o keystore.o utils.o kms.o \ ++ pvsecrets.o $(libs) + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ + + zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c -lcrypto +diff --git a/zkey/pvsecrets.c b/zkey/pvsecrets.c +new file mode 100644 +index 0000000..2874fdf +--- /dev/null ++++ b/zkey/pvsecrets.c +@@ -0,0 +1,161 @@ ++/* ++ * zkey - Generate, re-encipher, and validate secure keys ++ * ++ * Copyright IBM Corp. 2024 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "lib/util_base.h" ++#include "lib/util_file.h" ++#include "lib/util_libc.h" ++#include "lib/util_panic.h" ++#include "lib/util_path.h" ++ ++#include "pvsecrets.h" ++ ++#define pr_verbose(verbose, fmt...) do { \ ++ if (verbose) \ ++ warnx(fmt); \ ++ } while (0) ++ ++/** ++ * Opens the ultravisor device and returns its file descriptor. ++ * This only succeeds when running in a secure execution guest. ++ * A failure of this function indicates that it is not running in a secure ++ * execution guest. ++ * ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns the file descriptor or -1 to indicate an error ++ */ ++int uv_open_device(bool verbose) ++{ ++ unsigned int pvguest = 0, max_retr_secrets = 0; ++ char *path = NULL; ++ int uv_fd, err; ++ ++ uv_fd = open(UVDEVICE, O_RDWR); ++ if (uv_fd < 0) { ++ err = errno; ++ warnx("File '%s:' %s\n", UVDEVICE, strerror(errno)); ++ if (err == EACCES) ++ warnx("Only the 'root' user is allowed to perform " ++ "this command"); ++ else ++ warnx("Ensure that you are running in a secure " ++ "execution guest, and that the 'uvdevice' " ++ "kernel module is loaded."); ++ return -1; ++ } ++ ++ path = util_path_sysfs(SYSFS_UV); ++ if (util_file_read_ui(&pvguest, 10, SYSFS_UV_PV_GUEST, path) != 0 || ++ pvguest != 1) { ++ warnx("You are not running in a secure execution guest."); ++ goto error; ++ } ++ ++ if (util_file_read_ui(&max_retr_secrets, 10, SYSFS_UV_MAX_SECRETS, ++ path) != 0 || ++ max_retr_secrets == 0) { ++ warnx("The ultravisor device is at a too old version, or " ++ "the ultravisor does not support retrievable secrets."); ++ goto error; ++ } ++ free(path); ++ ++ pr_verbose(verbose, "Device '%s' has been opened successfully", ++ UVDEVICE); ++ return uv_fd; ++ ++error: ++ free(path); ++ close(uv_fd); ++ ++ return -1; ++} ++ ++/** ++ * Retrieves a list of secrets from the ultravisor. Calls the supplied callback ++ * function for each secret found. ++ * ++ * @param uv_fd the file descriptor of the ultravisor device ++ * @param cb the callback function ++ * @param cb_private private data to pass to the callback function ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++static int uv_list_secrets(int uv_fd, int (*cb)(u16 idx, u16 type, u32 len, ++ const u8 id[UV_SECRET_ID_LEN], ++ void *cb_private), ++ void *cb_private, bool verbose) ++{ ++ struct uvio_list_secrets *list; ++ struct uvio_ioctl_cb io; ++ unsigned int i; ++ int rc; ++ ++ util_assert(uv_fd != -1, "Internal error: uv_fd is -1"); ++ util_assert(cb != NULL, "Internal error: cb is NULL"); ++ ++ list = util_zalloc(UVIO_LIST_SECRETS_MAX_LEN); ++ ++ memset(&io, 0, sizeof(io)); ++ io.argument_addr = list; ++ io.argument_len = UVIO_LIST_SECRETS_MAX_LEN; ++ ++ rc = ioctl(uv_fd, UVIO_IOCTL_LIST_SECRETS, &io); ++ if (rc != 0) { ++ rc = -errno; ++ ++ pr_verbose(verbose, "ioctl UVIO_IOCTL_LIST_SECRETS: %s", ++ strerror(-rc)); ++ ++ if (rc == -ENOTTY || rc == -EINVAL) ++ warnx("The ultravisor device is at a too old version"); ++ ++ goto out; ++ } ++ ++ if (io.uv_rc != UVIO_RC_SUCCESS) { ++ pr_verbose(verbose, "ioctl UVIO_IOCTL_LIST_SECRETS' uv_rc: %u", ++ io.uv_rc); ++ rc = -EIO; ++ goto out; ++ } ++ ++ pr_verbose(verbose, "Number of secrets: %u", list->num_secrets_stored); ++ ++ for (i = 0; i < list->num_secrets_stored && ++ i < ARRAY_SIZE(list->secret_entries); i++) { ++ if (list->secret_entries[i].secret_type <= ++ UV_SECRET_TYPE_AP_ASSOCIATION) ++ continue; ++ ++ rc = cb(list->secret_entries[i].secret_idx, ++ list->secret_entries[i].secret_type, ++ list->secret_entries[i].secret_len, ++ list->secret_entries[i].secret_id, ++ cb_private); ++ if (rc != 0) ++ break; ++ } ++ ++out: ++ free(list); ++ ++ return rc; ++} +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +new file mode 100644 +index 0000000..2667e85 +--- /dev/null ++++ b/zkey/pvsecrets.h +@@ -0,0 +1,89 @@ ++/* ++ * zkey - Generate, re-encipher, and validate secure keys ++ * ++ * This header file defines functions for the PV secrets support as well ++ * as the interface to the uv kernel module. ++ * ++ * Copyright IBM Corp. 2024 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++ ++#ifndef PVSECRETS_H ++#define PVSECRETS_H ++ ++#include "lib/zt_common.h" ++ ++/* ++ * Definitions for the /dev/uv kernel module interface ++ */ ++#define UVDEVICE "/dev/uv" ++#define SYSFS_UV "firmware/uv" ++#define SYSFS_UV_PV_GUEST "%s/prot_virt_guest" ++#define SYSFS_UV_MAX_SECRETS "%s/query/max_retr_secrets" ++ ++struct uvio_ioctl_cb { ++ u32 flags; ++ u16 uv_rc; /* UV header rc value */ ++ u16 uv_rrc; /* UV header rrc value */ ++ void *argument_addr; /* Userspace address of uvio argument */ ++ u32 argument_len; ++ u8 reserved14[0x40 - 0x14]; /* must be zero */ ++}; ++ ++#define UVIO_IOCTL_LIST_SECRETS_NR 3 ++ ++#define UVIO_TYPE_UVC 'u' ++#define UVIO_IOCTL(nr) _IOWR(UVIO_TYPE_UVC, \ ++ nr, struct uvio_ioctl_cb) ++#define UVIO_IOCTL_LIST_SECRETS UVIO_IOCTL( \ ++ UVIO_IOCTL_LIST_SECRETS_NR) ++ ++#define UVIO_RC_SUCCESS 0x0001 ++#define UVIO_RC_MORE_DATA 0x0100 ++ ++#define UV_SECRET_TYPE_INVALID 0x00 ++#define UV_SECRET_TYPE_NULL 0x01 ++#define UV_SECRET_TYPE_AP_ASSOCIATION 0x02 ++#define UV_SECRET_TYPE_PLAIN_TEXT 0x03 ++#define UV_SECRET_TYPE_AES_128 0x04 ++#define UV_SECRET_TYPE_AES_192 0x05 ++#define UV_SECRET_TYPE_AES_256 0x06 ++#define UV_SECRET_TYPE_AES_XTS_128 0x07 ++#define UV_SECRET_TYPE_AES_XTS_256 0x08 ++#define UV_SECRET_TYPE_HMAC_SHA_256 0x09 ++#define UV_SECRET_TYPE_HMAC_SHA_512 0x0a ++#define UV_SECRET_TYPE_ECDSA_P256 0x11 ++#define UV_SECRET_TYPE_ECDSA_P384 0x12 ++#define UV_SECRET_TYPE_ECDSA_P521 0x13 ++#define UV_SECRET_TYPE_EDDSA_ED25519 0x14 ++#define UV_SECRET_TYPE_EDDSA_ED448 0x15 ++ ++#define UV_SECRET_ID_LEN 32 ++ ++#define UVIO_LIST_SECRETS_MAX_LEN 0x8000 ++ ++struct uvio_list_secret_entry { ++ u16 secret_idx; ++ u16 secret_type; ++ u32 secret_len; ++ u64 reserved; ++ u8 secret_id[UV_SECRET_ID_LEN]; ++} __packed; ++ ++#define UVIO_MAX_SECRET_ENTRIES ((UVIO_LIST_SECRETS_MAX_LEN - 16) / \ ++ sizeof(struct uvio_list_secret_entry)) ++ ++struct uvio_list_secrets { ++ u16 num_secrets_stored; ++ u16 num_secrets_total; ++ u16 next_secret_idx; ++ u16 reserved1; ++ u64 reserved2; ++ struct uvio_list_secret_entry secret_entries[UVIO_MAX_SECRET_ENTRIES]; ++} __packed; ++ ++int uv_open_device(bool verbose); ++ ++#endif +-- +2.47.1 + + +From 99bd7f51d41763cc7135169a90f12f4a7df3d3f2 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 11:22:04 +0100 +Subject: [PATCH 26/31] zkey: Add the 'pvsecrets list' command (RHEL-23870) + +The 'pvsecrets list' command lists the available protected virtualization +secrets. By default, only those pvsecret types are listed, that can be used +with zkey. If option '--all/-a' is specified, then all pvsecret types are +listed. Nevertheless, pvsecret types not supported by zkey can not be used +with zkey. + +This command only works when running in a secure execution guest. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit 5ce79ea667ea946e6591fe898db13becad018667) +--- + zkey/pvsecrets.c | 393 +++++++++++++++++++++++++++++++++++++++++++++++ + zkey/pvsecrets.h | 4 + + zkey/zkey.1 | 91 ++++++++++- + zkey/zkey.c | 135 +++++++++++++++- + 4 files changed, 619 insertions(+), 4 deletions(-) + +diff --git a/zkey/pvsecrets.c b/zkey/pvsecrets.c +index 2874fdf..7f28feb 100644 +--- a/zkey/pvsecrets.c ++++ b/zkey/pvsecrets.c +@@ -7,6 +7,7 @@ + * it under the terms of the MIT license. See LICENSE for details. + */ + ++#include + #include + #include + #include +@@ -17,14 +18,60 @@ + #include + #include + ++#include ++ + #include "lib/util_base.h" + #include "lib/util_file.h" + #include "lib/util_libc.h" + #include "lib/util_panic.h" + #include "lib/util_path.h" ++#include "lib/util_rec.h" + + #include "pvsecrets.h" + ++struct pvsecret_type_info { ++ u16 type; ++ const char *name; ++ bool zkey_usage; ++}; ++ ++static const struct pvsecret_type_info pvsecret_type_info[] = { ++ { .type = UV_SECRET_TYPE_NULL, .name = "NULL", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_AP_ASSOCIATION, .name = "AP-ASSOCIATION", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_PLAIN_TEXT, .name = "PLAIN-TEXT", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_AES_128, .name = "AES-128", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_192, .name = "AES-192", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_256, .name = "AES-256", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_XTS_128, .name = "AES-XTS-128", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_XTS_256, .name = "AES-XTS-256", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_HMAC_SHA_256, .name = "HMAC-SHA-256", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_HMAC_SHA_512, .name = "HMAC-SHA-512", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_ECDSA_P256, .name = "ECDSA-P256", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_ECDSA_P384, .name = "ECDSA-P384", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_ECDSA_P521, .name = "ECDSA-P521", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_EDDSA_ED25519, .name = "EDDSA-ED25519", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_EDDSA_ED448, .name = "EDDSA-ED448", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_INVALID, } ++}; ++ ++#define PVSECRETS_REC_ID "Secret ID" ++#define PVSECRETS_REC_TYPE "Type" ++ + #define pr_verbose(verbose, fmt...) do { \ + if (verbose) \ + warnx(fmt); \ +@@ -159,3 +206,349 @@ out: + + return rc; + } ++ ++/** ++ * Returns true if the secret type is supported by zkey ++ * ++ * @param type the secret type ++ * ++ * @returns true if the type is supported, false otherwise ++ */ ++static bool is_pvsecret_type_supported(u16 type) ++{ ++ unsigned int i; ++ ++ for (i = 0; pvsecret_type_info[i].type != UV_SECRET_TYPE_INVALID; i++) { ++ if (pvsecret_type_info[i].type == type) ++ return pvsecret_type_info[i].zkey_usage; ++ } ++ ++ return false; ++} ++ ++/** ++ * Returns the secret type name for the specified secret type ++ * ++ * @param type the secret type ++ * ++ * @returns a constant string containing the type name ++ */ ++static const char *get_pvsecret_type_name(u16 type) ++{ ++ unsigned int i; ++ ++ for (i = 0; pvsecret_type_info[i].type != UV_SECRET_TYPE_INVALID; i++) { ++ if (pvsecret_type_info[i].type == type) ++ return pvsecret_type_info[i].name; ++ } ++ ++ return "[UNKNOWN]"; ++} ++ ++/** ++ * Returns the secret type for the specified type name ++ * ++ * @param name the secret type name ++ * ++ * @returns the secret type or UV_SECRET_TYPE_INVALID if unknown. ++ */ ++static u16 get_pvsecret_type_by_name(const char *name) ++{ ++ unsigned int i; ++ ++ for (i = 0; pvsecret_type_info[i].type != UV_SECRET_TYPE_INVALID; i++) { ++ if (strcasecmp(pvsecret_type_info[i].name, name) == 0) ++ return pvsecret_type_info[i].type; ++ } ++ ++ return UV_SECRET_TYPE_INVALID; ++} ++ ++/** ++ * Parses a 32 byte hex string into a 32 byte binary secret ID ++ * ++ * @param id_str the hex string to parse ++ * @param id the output buffer to store the secret ID ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++static int parse_secret_id_str(const char *id_str, ++ unsigned char id[UV_SECRET_ID_LEN]) ++{ ++ char hex[3] = { 0 }; ++ unsigned long val; ++ unsigned int i; ++ char *endptr; ++ ++ util_assert(id_str != NULL, "Internal error: id_str is NULL"); ++ util_assert(id != NULL, "Internal error: id is NULL"); ++ ++ if (strncasecmp(id_str, "0x", 2) == 0) ++ id_str += 2; ++ ++ if (strlen(id_str) != UV_SECRET_ID_LEN * 2) ++ return -EINVAL; ++ ++ for (i = 0; i < UV_SECRET_ID_LEN; i++) { ++ hex[0] = id_str[i * 2]; ++ hex[1] = id_str[i * 2 + 1]; ++ ++ errno = 0; ++ val = strtoul(hex, &endptr, 16); ++ if (errno != 0 || *endptr != '\0' || val > 0xff) ++ return -EINVAL; ++ ++ id[i] = val; ++ } ++ ++ return 0; ++} ++ ++/** ++ * Get the 32 byte binary secret ID from the secret name by calculating the ++ * SHA-256 has from the name. ++ * ++ * @param name the name of the secret ++ * @param id the output buffer to store the secret ID ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++static int get_secret_id_from_name(const char *name, ++ unsigned char id[UV_SECRET_ID_LEN]) ++{ ++ util_assert(name != NULL, "Internal error: id_str is NULL"); ++ util_assert(id != NULL, "Internal error: id is NULL"); ++ util_assert(UV_SECRET_ID_LEN == SHA256_DIGEST_LENGTH, ++ "Internal error: UV_SECRET_ID_LEN != SHA256_DIGEST_LENGTH"); ++ ++ if (SHA256((const unsigned char *)name, strlen(name), id) != id) ++ return -EIO; ++ ++ return 0; ++} ++ ++/** ++ * Gets the binary 32 byte secret id from either a hex string or a secret name. ++ * ++ * @param hex the secret id as hex string. Can be NULL. ++ * @param name the secret name. Can be NULL. If the id ++ * parameter is non-NULL, then this parameter is ++ * ignored. ++ * @param id Output: the 32 byte binary secret id. ++ * @param id_str Output: the secret id in printable ascii chars ++ * form, if name is non-NULL and the name length is ++ * less than UV_SECRET_ID_LEN. ++ * ++ * @returns 0 on success, a negative errno in case of an error. ++ * If neither the hex string nor the secret name is specified, 1 is returned, ++ * and the id parameter is not modified. ++ */ ++static int get_secret_id_from_hex_or_name(const char *hex, const char *name, ++ unsigned char id[UV_SECRET_ID_LEN], ++ char id_name[UV_SECRET_ID_LEN]) ++{ ++ int rc; ++ ++ util_assert(id != NULL, "Internal error: id is NULL"); ++ ++ if (hex != NULL) { ++ rc = parse_secret_id_str(hex, id); ++ if (rc != 0) { ++ warnx("Invalid pvsecret id specified: '%s'", hex); ++ return rc; ++ } ++ ++ return 0; ++ } ++ ++ if (name != NULL) { ++ rc = get_secret_id_from_name(name, id); ++ if (rc != 0) { ++ warnx("Failed to get the ID from pvsecret name: '%s'", ++ name); ++ return rc; ++ } ++ ++ if (strlen(name) < UV_SECRET_ID_LEN) { ++ strncpy(id_name, name, UV_SECRET_ID_LEN); ++ id_name[UV_SECRET_ID_LEN - 1] = '\0'; ++ } ++ ++ return 0; ++ } ++ ++ return 1; ++} ++ ++/** ++ * Checks if the secret id is printable. To be printable, all characters up to ++ * the first zero byte must be printable. All bytes after the first zero byte ++ * must be all zero. There must be at least one zero byte as the very last byte ++ * of the id. ++ * ++ * @param id the ID of the secret ++ * @param name Output: the id in the printable form and enclosed ++ * in single quotes if the id is printable. The max ++ * length of the name buffer is UV_SECRET_ID_LEN + 2: ++ * A starting quote, up to UV_SECRET_ID_LEN-1 chars, ++ * an ending quote and a zero termination byte. ++ * ++ * @returns true if the id is printable, false otherwise. ++ */ ++static bool is_printable_name(const u8 id[UV_SECRET_ID_LEN], ++ char name[UV_SECRET_ID_LEN + 2]) ++{ ++ bool end_found = false, printable_name = false; ++ unsigned int i; ++ ++ name[0] = '\''; ++ for (i = 0; i < UV_SECRET_ID_LEN; i++) { ++ if (!end_found) { ++ if (id[i] == '\0') { ++ name[1 + i] = '\''; ++ end_found = true; ++ } else if (isprint(id[i])) { ++ name[1 + i] = id[i]; ++ printable_name = true; ++ } else { ++ printable_name = false; ++ end_found = true; ++ } ++ } else if (id[i] != '\0') { ++ printable_name = false; ++ } ++ } ++ if (!end_found) ++ printable_name = false; ++ ++ return printable_name; ++} ++ ++struct list_secrets_data { ++ struct util_rec *rec; ++ bool all; ++ bool hex; ++ u16 type_filter; ++ bool id_filter; ++ char name[UV_SECRET_ID_LEN]; ++ unsigned char id[UV_SECRET_ID_LEN]; ++ unsigned int matched; ++}; ++ ++/** ++ * Callback used with pvsecrets_list function. Called for each secret. ++ * ++ * @param idx the index of the secret ++ * @param type the type of the secret ++ * @param id the ID of the secret ++ * @param cb_private callback private data ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++static int pvsecrets_list_cb(u16 UNUSED(idx), u16 type, u32 UNUSED(len), ++ const u8 id[UV_SECRET_ID_LEN], void *cb_private) ++{ ++ struct list_secrets_data *list_data = cb_private; ++ char name[2 + UV_SECRET_ID_LEN] = { 0 }; ++ char hex[2 * UV_SECRET_ID_LEN + 1] = { 0 }; ++ unsigned int i; ++ ++ if (!list_data->all && !is_pvsecret_type_supported(type)) ++ return 0; ++ ++ if (list_data->type_filter != 0 && type != list_data->type_filter) ++ return 0; ++ ++ if (list_data->id_filter && ++ memcmp(id, list_data->name, UV_SECRET_ID_LEN) != 0 && ++ memcmp(id, list_data->id, UV_SECRET_ID_LEN) != 0) ++ return 0; ++ ++ for (i = 0; i < UV_SECRET_ID_LEN; i++) ++ sprintf(&hex[i * 2], "%02x", id[i]); ++ ++ if (!list_data->hex && is_printable_name(id, name)) ++ util_rec_set(list_data->rec, PVSECRETS_REC_ID, name); ++ else ++ util_rec_set(list_data->rec, PVSECRETS_REC_ID, hex); ++ util_rec_set(list_data->rec, PVSECRETS_REC_TYPE, ++ get_pvsecret_type_name(type)); ++ ++ if (list_data->matched == 0) ++ util_rec_print_hdr(list_data->rec); ++ ++ util_rec_print(list_data->rec); ++ ++ list_data->matched++; ++ ++ return 0; ++} ++ ++/** ++ * Lists protected virtualization secrets. ++ * ++ * @param uv_fd the file descriptor of the ultravisor device ++ * @param all if true, all secret types are listed ++ * @param hex if true, list the secret ID in hex, even if the ++ * secret ID would be printable ++ * @param type_filter only display secrets of the specified secret type. ++ * Can be NULL. ++ * @param secret_id the secret id to list. Can be NULL. ++ * @param secret_name the secret name to list. Can be NULL. If the id ++ * parameter is non-NULL, then this parameter is ++ * ignored. ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, ++ const char *secret_id, const char *secret_name, ++ bool verbose) ++{ ++ struct list_secrets_data list_data = { 0 }; ++ int rc; ++ ++ util_assert(uv_fd != -1, "Internal error: uv_fd is -1"); ++ ++ list_data.all = all; ++ list_data.hex = hex; ++ list_data.type_filter = UV_SECRET_TYPE_INVALID; ++ ++ if (type_filter != NULL) { ++ list_data.type_filter = get_pvsecret_type_by_name(type_filter); ++ if (list_data.type_filter == UV_SECRET_TYPE_INVALID) { ++ warnx("Invalid pvsecret type specified: %s", ++ type_filter); ++ return -EINVAL; ++ } ++ } ++ ++ if (secret_id != NULL || secret_name != NULL) { ++ rc = get_secret_id_from_hex_or_name(secret_id, secret_name, ++ list_data.id, ++ list_data.name); ++ if (rc < 0) ++ return rc; ++ ++ list_data.id_filter = true; ++ } ++ ++ list_data.rec = util_rec_new_wide("-"); ++ util_rec_def(list_data.rec, PVSECRETS_REC_ID, UTIL_REC_ALIGN_LEFT, ++ UV_SECRET_ID_LEN * 2, PVSECRETS_REC_ID); ++ util_rec_def(list_data.rec, PVSECRETS_REC_TYPE, UTIL_REC_ALIGN_LEFT, ++ 12, PVSECRETS_REC_TYPE); ++ ++ rc = uv_list_secrets(uv_fd, pvsecrets_list_cb, &list_data, verbose); ++ if (rc != 0) { ++ warnx("Failed to list protected virtualization secrets: %s", ++ strerror(-rc)); ++ } ++ ++ util_rec_free(list_data.rec); ++ ++ if (list_data.matched == 0) ++ rc = -ENOENT; ++ ++ return rc; ++} +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +index 2667e85..6acebfd 100644 +--- a/zkey/pvsecrets.h ++++ b/zkey/pvsecrets.h +@@ -86,4 +86,8 @@ struct uvio_list_secrets { + + int uv_open_device(bool verbose); + ++int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, ++ const char *secret_id, const char *secret_name, ++ bool verbose); ++ + #endif +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index 8c5a09a..4386629 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -1,8 +1,8 @@ +-.\" Copyright IBM Corp. 2017, 2020 ++.\" Copyright IBM Corp. 2017, 2024 + .\" s390-tools is free software; you can redistribute it and/or modify + .\" it under the terms of the MIT license. See LICENSE for details. + .\" +-.TH ZKEY 1 "July 2020" "s390-tools" ++.TH ZKEY 1 "February 2024" "s390-tools" + .SH NAME + zkey \- Manage secure AES keys + . +@@ -1162,6 +1162,54 @@ fails. Use option \fB\-\-no\-volume\-check\fP to omit the volume check, and + refresh the keys even if the associated volume(s) do not exist. + . + . ++.SH COMMANDS FOR PROTECTED VIRTUALIZATION ++. ++Use the \fBpvsecrets\fP command to work with protected virtualization (PV) ++secrets. Protected virtualization secrets can be made available to a secure ++execution guest and can be used only within that guest. ++The \fBpvsecrets\fP command provides subcommands for protected ++virtualization specific operations. Use \fBzkey pvsecrets \-\-help\fP to show ++the available subcommands. These subcommands only work when running in a ++secure execution guest. Only the \fBroot\fP user is allowed to perform these ++subcommands. ++. ++.SS "List available protected virtualization secrets" ++. ++.B zkey pvsecrets ++.BR list | li ++.RB [ \-\-all | \-A ] ++.RB [ \-\-hex | \-H ] ++.RB [ \-\-pvsecret\-type | \-T ++.IR pvsecret\-type ] ++.RB [ \-\-pvsecret\-id | \-I ++.IR pvsecret\-id ] ++.RB [ \-\-pvsecret\-name | \-e ++.IR pvsecret\-name ] ++.RB [ \-\-verbose | \-V ] ++. ++.PP ++Use the ++.B pvsecrets list ++command to display a list of protected virtualization (PV) secrets. It displays ++the pvsecret ID as hex string of 32 bytes or as printable name enclosed in ++single quotes (\fB'\fP), if the pvsecret ID consists of only printable ++characters. Specify the \fB\-\-hex\fP option to list all pvsecret IDs as hex ++string. The ++.B pvsecrets list ++command also shows the pvsecret type of each secret. ++.PP ++You can filter the list of pvsecrets by pvsecret ID, pvsecret name and pvsecret ++type. Either the \fB\-\-pvsecret\-id\fP option or the ++\fB\-\-pvsecret\-name\fP option can be specified. By default the ++\fBpvsecrets list\fP command displays only those pvsecrets with types that ++are supported by the \fBzkey\fP tool. To list all pvsecret types, specify the ++\fB\-\-all\fP option. ++.PP ++This command is only available when running in a secure execution guest. ++Only the \fBroot\fP user is allowed to perform this command. ++. ++. ++. + . + .SH OPTIONS + .SS "Options for the generate command" +@@ -2030,6 +2078,45 @@ repository. This option only has an effect when specified together with option + . + . + . ++.SS "Options for the pvsecrets list command" ++.TP ++.BR \-A ", " \-\-all ++List all protected virtualization (PV) secret types, not only those that can be ++used with zkey. ++.TP ++.BR \-H ", " \-\-hex ++Show all protected virtualization (PV) secret IDs in hex, even if the ID ++contains only printable characters. ++.TP ++.BR \-T ", " \-\-pvsecret\-type\~\fIpvsecret\-type\fP ++Type of the protected virtualization (PV) secret to list. If omitted, all ++secret types are listed. Possible values are: \fBPLAIN\-TEXT\fP, \fBAES\-128\fP, ++\fBAES\-192\fP, \fBAES\-256\fP, \fBAES\-XTS\-128\fP, \fBAES\-XTS\-256\fP, ++\fBHMAC\-SHA\-256\fP, \fBHMAC\-SHA\-512\fP, \fBECDSA\-P256\fP, ++\fBECDSA\-P384\fP, \fBECDSA\-P521\fP, \fBEDDSA\-ED25519\fP, and ++\fBEDDSA\-ED448\fP. ++.TP ++.BR \-I ", " \-\-pvsecret\-id\~\fIpvsecret\-id\fP ++ID of the protected virtualization (PV) secret to list. The pvsecret ID is a 32 ++byte hex string, optionally prefixed by \fB0x\fP. You can use the YAML file that ++was created when using the \fBpvsecret create\fP command for adding the ++protected virtualization secret: ++\fB\-\-pvsecret\-id "$(yq .id \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. ++.TP ++.BR \-e ", " \-\-pvsecret\-name\~\fIpvsecret\-name\fP ++Name of the protected virtualization (PV) secret to list. You can use the YAML ++file that was created when using the \fBpvsecret create\fP command for adding ++the protected virtualization secret: ++\fB\-\-pvsecret\-name "$(yq .name \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. ++. ++. ++. + .SS "General options" + .TP + .BR \-V ", " \-\-verbose +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 7c909ff..adc48d6 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -1,7 +1,7 @@ + /* + * zkey - Generate, re-encipher, and validate secure keys + * +- * Copyright IBM Corp. 2017, 2020 ++ * Copyright IBM Corp. 2017, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -34,6 +34,7 @@ + #include "pkey.h" + #include "utils.h" + #include "kms.h" ++#include "pvsecrets.h" + + /* + * Program configuration +@@ -46,7 +47,7 @@ static const struct util_prg prg = { + { + .owner = "IBM Corp.", + .pub_first = 2017, +- .pub_last = 2020, ++ .pub_last = 2024, + }, + UTIL_PRG_COPYRIGHT_END + } +@@ -93,10 +94,16 @@ static struct zkey_globals { + bool open; + bool format; + bool refresh_properties; ++ bool all; ++ bool hex; ++ char *secret_type; ++ char *secret_id; ++ char *secret_name; + struct ext_lib lib; + struct cca_lib cca; + struct ep11_lib ep11; + int pkey_fd; ++ int uv_fd; + struct keystore *keystore; + struct kms_info kms_info; + int first_kms_option; +@@ -104,6 +111,7 @@ static struct zkey_globals { + size_t num_kms_options; + } g = { + .pkey_fd = -1, ++ .uv_fd = -1, + .sector_size = -1, + .lib.cca = &g.cca, + .lib.ep11 = &g.ep11, +@@ -135,6 +143,8 @@ static struct zkey_globals { + #define COMMAND_KMS_LIST "list" + #define COMMAND_KMS_IMPORT "import" + #define COMMAND_KMS_REFRESH "refresh" ++#define COMMAND_PVSECRETS "pvsecrets" ++#define COMMAND_PVSECRETS_LIST "list" + + #define OPT_COMMAND_PLACEHOLDER "PLACEHOLDER" + +@@ -1182,6 +1192,48 @@ static struct util_opt opt_vec[] = { + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + /***********************************************************/ ++ { ++ .flags = UTIL_OPT_FLAG_SECTION, ++ .desc = "OPTIONS", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = {"all", 0, NULL, 'A'}, ++ .desc = "List all protected virtualization (PV) secret types, " ++ "not only those that can be used with zkey.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = {"hex", 0, NULL, 'H'}, ++ .desc = "Show all protected virtualization (PV) secret IDs in " ++ "hex, even if the ID contains only printable " ++ "characters.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = { "pvsecret-type", required_argument, NULL, 'T'}, ++ .argument = "PVSECRET-TYPE", ++ .desc = "Type of the protected virtualization (PV) secret to " ++ "list. If omitted, all pvsecret types are listed.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = { "pvsecret-id", required_argument, NULL, 'I'}, ++ .argument = "PVSECRET-ID", ++ .desc = "ID of the protected virtualization (PV) secret to " ++ "list. Either '--pvsecret-id/-I' or " ++ "'--pvsecret-name/-e' can be specified, but not both.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = { "pvsecret-name", required_argument, NULL, 'e'}, ++ .argument = "PVSECRET-NAME", ++ .desc = "Name of the protected virtualization (PV) secret to " ++ "list. Either '--pvsecret-name/-e' or " ++ "'--pvsecret-id/-I' can be specified, but not both.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ /***********************************************************/ + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, +@@ -1249,6 +1301,7 @@ struct zkey_command { + int need_cca_library; + int need_ep11_library; + int need_pkey_device; ++ int need_uv_device; + char *short_desc; + char *long_desc; + int has_options; +@@ -1285,6 +1338,7 @@ static int command_kms_reencipher(void); + static int command_kms_list(void); + static int command_kms_import(void); + static int command_kms_refresh(void); ++static int command_pvsecrets_list(void); + + static struct zkey_command zkey_kms_commands[] = { + { +@@ -1402,6 +1456,22 @@ static struct zkey_command zkey_kms_commands[] = { + { .command = NULL } + }; + ++static struct zkey_command zkey_pvsecrets_commands[] = { ++ { ++ .command = COMMAND_PVSECRETS_LIST, ++ .abbrev_len = 2, ++ .function = command_pvsecrets_list, ++ .short_desc = "Lists protected virtualization (PV) secrets", ++ .long_desc = "Lists available protected virtualization (PV) " ++ "secrets. This command is only available when " ++ "running in a secure execution guest. Only the " ++ "'root' user is allowed to perform this command", ++ .has_options = 1, ++ .need_uv_device = 1, ++ }, ++ { .command = NULL } ++}; ++ + static struct zkey_command zkey_commands[] = { + { + .command = COMMAND_GENERATE, +@@ -1565,6 +1635,21 @@ static struct zkey_command zkey_commands[] = { + .has_options = 1, + .sub_commands = zkey_kms_commands, + }, ++ { ++ .command = COMMAND_PVSECRETS, ++ .abbrev_len = 2, ++ .short_desc = "Protected virtualization (PV) support", ++ .long_desc = "Provides subcommands for working with protected " ++ "virtualization (PV) secrets. Protected " ++ "virtualization secrets can be made available to " ++ "a secure execution guest and can be used only " ++ "within that guest. Thus, these subcommands are " ++ "only available when running in a secure " ++ "execution guest. Only the 'root' user is allowed " ++ "to perform these subcommands.", ++ .has_options = 1, ++ .sub_commands = zkey_pvsecrets_commands, ++ }, + { .command = NULL } + }; + +@@ -2899,6 +2984,28 @@ static int command_kms_refresh(void) + return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; + } + ++/* ++ * Command handler for 'pvsecrets list'. ++ * ++ * Lists available protected virtualization secrets ++ */ ++static int command_pvsecrets_list(void) ++{ ++ int rc; ++ ++ if (g.secret_id != NULL && g.secret_name != NULL) { ++ warnx("Either '--pvsecret-id/-I' or '--pvsecret-name/-e' can " ++ "be specified, but not both"); ++ util_prg_print_parse_error(); ++ return EXIT_FAILURE; ++ } ++ ++ rc = pvsecrets_list(g.uv_fd, g.all, g.hex, g.secret_type, g.secret_id, ++ g.secret_name, g.verbose); ++ ++ return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; ++} ++ + /** + * Opens the keystore. The keystore directory is either the + * default directory or as specified in an environment variable +@@ -3234,6 +3341,21 @@ int main(int argc, char *argv[]) + case OPT_REMOVE_DUMMY_PASSPHRASE: + g.remove_passphrase = 1; + break; ++ case 'A': ++ g.all = 1; ++ break; ++ case 'H': ++ g.hex = 1; ++ break; ++ case 'T': ++ g.secret_type = optarg; ++ break; ++ case 'I': ++ g.secret_id = optarg; ++ break; ++ case 'e': ++ g.secret_name = optarg; ++ break; + case 'h': + print_help(command, sub_command); + return EXIT_SUCCESS; +@@ -3294,6 +3416,13 @@ int main(int argc, char *argv[]) + goto out; + } + } ++ if (cmd->need_uv_device) { ++ g.uv_fd = uv_open_device(g.verbose); ++ if (g.uv_fd == -1) { ++ rc = EXIT_FAILURE; ++ goto out; ++ } ++ } + + if (g.kms_info.plugin_lib != NULL) { + rc = init_kms_plugin(&g.kms_info, g.verbose); +@@ -3323,6 +3452,8 @@ out: + dlclose(g.ep11.lib_ep11); + if (g.pkey_fd >= 0) + close(g.pkey_fd); ++ if (g.uv_fd >= 0) ++ close(g.uv_fd); + if (g.keystore) + keystore_free(g.keystore); + if (g.kms_options != NULL) +-- +2.47.1 + + +From 3b4fce7cdd079732235da5e01033497752963360 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 16:56:04 +0100 +Subject: [PATCH 27/31] zkey: Add PVSECRETS-AES key type (RHEL-23870) + +Add the definitions and utility functions for the PVSECRETS-AES key type. +A PVSECRETS-AES key token contains the secret id of a protected +virtualization secret. It does not contain the key material, just a +reference to the key in the ultravisor. + +When such a key token is used to perform crypto operations later on, the +PAES kernel cipher will obtain the protected key belonging to this secret +id with the help of the pkey kernel module. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit fdf66dc148c09f1d3300cd3378e3f75a83c6214e) +--- + zkey/keystore.c | 4 +- + zkey/pkey.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++- + zkey/pkey.h | 38 ++++++++++++---- + zkey/pvsecrets.h | 4 +- + 4 files changed, 143 insertions(+), 13 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index 9589d82..771bc08 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -3,7 +3,7 @@ + * + * Keystore handling functions + * +- * Copyright IBM Corp. 2018, 2020 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -360,6 +360,8 @@ static int _keystore_valid_key_type(const char *key_type) + return 1; + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) + return 1; ++ if (strcasecmp(key_type, KEY_TYPE_PVSECRET_AES) == 0) ++ return 1; + + return 0; + } +diff --git a/zkey/pkey.c b/zkey/pkey.c +index 821978b..53c0a55 100644 +--- a/zkey/pkey.c ++++ b/zkey/pkey.c +@@ -1,7 +1,7 @@ + /* + * zkey - Generate, re-encipher, and validate secure keys + * +- * Copyright IBM Corp. 2018 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -26,6 +26,7 @@ + #include "lib/util_panic.h" + + #include "pkey.h" ++#include "pvsecrets.h" + #include "utils.h" + + #ifndef AF_ALG +@@ -1719,6 +1720,38 @@ bool is_ep11_key_session_bound(const u8 *key, size_t key_size) + } + } + ++/** ++ * Check if the specified key is a PVSECRET-AES key token. ++ * ++ * @param[in] key the secure key token ++ * @param[in] key_size the size of the secure key ++ * ++ * @returns true if the key is a PVSECRET token type ++ */ ++bool is_pvsecret_aes_key(const u8 *key, size_t key_size) ++{ ++ struct pvsecrettoken *pvsecret = (struct pvsecrettoken *)key; ++ ++ if (key == NULL || key_size < PVSECRET_KEY_SIZE) ++ return false; ++ ++ if (pvsecret->hdr.type != TOKEN_TYPE_NON_CCA) ++ return false; ++ if (pvsecret->hdr.version != TOKEN_VERSION_PVSECRET) ++ return false; ++ ++ switch (pvsecret->secret_type) { ++ case UV_SECRET_TYPE_AES_128: ++ case UV_SECRET_TYPE_AES_192: ++ case UV_SECRET_TYPE_AES_256: ++ case UV_SECRET_TYPE_AES_XTS_128: ++ case UV_SECRET_TYPE_AES_XTS_256: ++ return true; ++ default: ++ return false; ++ } ++} ++ + /** + * Check if the specified key is an XTS type key + * +@@ -1729,6 +1762,8 @@ bool is_ep11_key_session_bound(const u8 *key, size_t key_size) + */ + bool is_xts_key(const u8 *key, size_t key_size) + { ++ struct pvsecrettoken *pvsecret = (struct pvsecrettoken *)key; ++ + if (is_cca_aes_data_key(key, key_size)) { + if (key_size == 2 * AESDATA_KEY_SIZE && + is_cca_aes_data_key(key + AESDATA_KEY_SIZE, +@@ -1749,11 +1784,41 @@ bool is_xts_key(const u8 *key, size_t key_size) + is_ep11_aes_key_with_header(key + EP11_AES_KEY_SIZE, + key_size - EP11_AES_KEY_SIZE)) + return true; ++ } else if (is_pvsecret_aes_key(key, key_size)) { ++ switch (pvsecret->secret_type) { ++ case UV_SECRET_TYPE_AES_XTS_128: ++ case UV_SECRET_TYPE_AES_XTS_256: ++ return true; ++ default: ++ return false; ++ } + } + + return false; + } + ++/** ++ * Check if the specified key is a secure key type (i.e. requires a crypto card) ++ * ++ * @param[in] key the secure key token ++ * @param[in] key_size the size of the secure key ++ * ++ * @returns true if the key is a secure key type ++ */ ++bool is_secure_key(const u8 *key, size_t key_size) ++{ ++ if (is_cca_aes_data_key(key, key_size)) ++ return true; ++ if (is_cca_aes_cipher_key(key, key_size)) ++ return true; ++ if (is_ep11_aes_key(key, key_size)) ++ return true; ++ if (is_ep11_aes_key_with_header(key, key_size)) ++ return true; ++ ++ return false; ++} ++ + /** + * Gets the size in bits of the effective key of the specified secure key + * +@@ -1771,6 +1836,7 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + struct ep11keytoken *ep11key = (struct ep11keytoken *)key; + struct ep11kblob_header *hdr = (struct ep11kblob_header *)key; ++ struct pvsecrettoken *pvsecret = (struct pvsecrettoken *)key; + + util_assert(bitsize != NULL, "Internal error: bitsize is NULL"); + +@@ -1805,6 +1871,26 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) + (key + EP11_AES_KEY_SIZE); + *bitsize += hdr->bitlen; + } ++ } else if (is_pvsecret_aes_key(key, key_size)) { ++ switch (pvsecret->secret_type) { ++ case UV_SECRET_TYPE_AES_128: ++ *bitsize = 128; ++ break; ++ case UV_SECRET_TYPE_AES_192: ++ *bitsize = 192; ++ break; ++ case UV_SECRET_TYPE_AES_256: ++ *bitsize = 256; ++ break; ++ case UV_SECRET_TYPE_AES_XTS_128: ++ *bitsize = 128 * 2; ++ break; ++ case UV_SECRET_TYPE_AES_XTS_256: ++ *bitsize = 256 * 2; ++ break; ++ default: ++ return -EINVAL; ++ } + } else { + return -EINVAL; + } +@@ -1830,9 +1916,31 @@ const char *get_key_type(const u8 *key, size_t key_size) + return KEY_TYPE_EP11_AES; + if (is_ep11_aes_key_with_header(key, key_size)) + return KEY_TYPE_EP11_AES; ++ if (is_pvsecret_aes_key(key, key_size)) ++ return KEY_TYPE_PVSECRET_AES; ++ + return NULL; + } + ++/** ++ * Returns true if the key type is a secure key type ++ * ++ * @param[in] key_type the type of the key ++ * ++ * @returns true if the key type is a secure key type, false otherwise ++ */ ++bool is_secure_key_type(const char *key_type) ++{ ++ if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) ++ return true; ++ if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) ++ return true; ++ if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) ++ return true; ++ ++ return false; ++} ++ + /** + * Returns the minimum card level for a specific key type + * +diff --git a/zkey/pkey.h b/zkey/pkey.h +index 3b57c5f..ce3bd8b 100644 +--- a/zkey/pkey.h ++++ b/zkey/pkey.h +@@ -4,7 +4,7 @@ + * This header file defines the interface to the pkey kernel module. + * It defines a set of IOCTL commands with its associated structures. + * +- * Copyright IBM Corp. 2017, 2018 ++ * Copyright IBM Corp. 2017, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -41,6 +41,8 @@ struct tokenheader { + #define TOKEN_VERSION_EP11_AES 0x03 + #define TOKEN_VERSION_EP11_AES_WITH_HEADER 0x06 + #define TOKEN_VERSION_EP11_ECC_WITH_HEADER 0x07 ++/* 0x08 is reserved for internal use */ ++#define TOKEN_VERSION_PVSECRET 0x09 + + struct aesdatakeytoken { + u8 type; /* TOKEN_TYPE_INTERNAL (0x01) for internal key token */ +@@ -116,6 +118,15 @@ struct ep11keytoken { + u8 padding[64]; + } __packed; + ++#define UV_SECRET_ID_LEN 32 ++ ++struct pvsecrettoken { ++ struct tokenheader hdr; ++ u16 secret_type; /* the secret type as the UV told us */ ++ u16 secret_len; /* length in bytes of the secret */ ++ u8 secretid[UV_SECRET_ID_LEN]; /* the secret id for this secret */ ++} __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" + +@@ -124,21 +135,26 @@ struct ep11keytoken { + #define EP11_KEY_SIZE sizeof(struct ep11keytoken) + #define EP11_AES_KEY_SIZE (sizeof(struct ep11kblob_header) + \ + sizeof(struct ep11keytoken)) ++#define PVSECRET_KEY_SIZE sizeof(struct pvsecrettoken) + + /* 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( \ +- _MAX(EP11_KEY_SIZE, \ +- EP11_AES_KEY_SIZE), \ +- _MAX(AESDATA_KEY_SIZE, \ +- AESCIPHER_KEY_SIZE)) ++ _MAX( \ ++ _MAX(EP11_KEY_SIZE, \ ++ EP11_AES_KEY_SIZE), \ ++ _MAX(AESDATA_KEY_SIZE, \ ++ AESCIPHER_KEY_SIZE)), \ ++ PVSECRET_KEY_SIZE) + #define MIN_SECURE_KEY_SIZE _MIN( \ +- _MIN(EP11_KEY_SIZE, \ +- EP11_AES_KEY_SIZE), \ +- _MIN(AESDATA_KEY_SIZE, \ +- AESCIPHER_KEY_SIZE)) ++ _MIN( \ ++ _MIN(EP11_KEY_SIZE, \ ++ EP11_AES_KEY_SIZE), \ ++ _MIN(AESDATA_KEY_SIZE, \ ++ AESCIPHER_KEY_SIZE)), \ ++ PVSECRET_KEY_SIZE) + + struct pkey_seckey { + u8 seckey[AESDATA_KEY_SIZE]; /* the secure key blob */ +@@ -285,6 +301,7 @@ struct pkey_apqns4keytype { + #define KEY_TYPE_CCA_AESDATA "CCA-AESDATA" + #define KEY_TYPE_CCA_AESCIPHER "CCA-AESCIPHER" + #define KEY_TYPE_EP11_AES "EP11-AES" ++#define KEY_TYPE_PVSECRET_AES "PVSECRET-AES" + + #define DEFAULT_KEYBITS 256 + #define PAES_BLOCK_SIZE 16 +@@ -342,9 +359,12 @@ 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_pvsecret_aes_key(const u8 *key, size_t key_size); + bool is_xts_key(const u8 *key, size_t key_size); ++bool is_secure_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); ++bool is_secure_key_type(const char *key_type); + int get_min_card_level_for_keytype(const char *key_type); + const struct fw_version *get_min_fw_version_for_keytype(const char *key_type); + enum card_type get_card_type_for_keytype(const char *key_type); +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +index 6acebfd..ad84403 100644 +--- a/zkey/pvsecrets.h ++++ b/zkey/pvsecrets.h +@@ -15,6 +15,8 @@ + + #include "lib/zt_common.h" + ++#include "pkey.h" ++ + /* + * Definitions for the /dev/uv kernel module interface + */ +@@ -60,8 +62,6 @@ struct uvio_ioctl_cb { + #define UV_SECRET_TYPE_EDDSA_ED25519 0x14 + #define UV_SECRET_TYPE_EDDSA_ED448 0x15 + +-#define UV_SECRET_ID_LEN 32 +- + #define UVIO_LIST_SECRETS_MAX_LEN 0x8000 + + struct uvio_list_secret_entry { +-- +2.47.1 + + +From 77a419bf5b575f09c7958bc5656cf1e0039be115 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 15:14:04 +0100 +Subject: [PATCH 28/31] zkey: Add the 'pvsecrets import' command (RHEL-23870) + +The 'pvsecrets import' command imports a protected virtualization secret +into the zkey key repository. Like other key import or key generation +commands, additional information can be associated with the imported key, +such as a textual description, the volume to encrypt with together with +the volume type, the sector size, and a dummy passphrase. You can not +associate a set of APQNs, since a protected virtualization secret does +not need or use a crypto card. + +This command only works when running in a secure execution guest. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit 95bf7eb285f39a8f827cc013393cc69b1265cd68) +--- + zkey/keystore.c | 112 +++++++++++++++++++++++---------- + zkey/keystore.h | 9 ++- + zkey/pvsecrets.c | 126 +++++++++++++++++++++++++++++++++++++ + zkey/pvsecrets.h | 7 +++ + zkey/zkey.1 | 134 ++++++++++++++++++++++++++++++++++++++++ + zkey/zkey.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 513 insertions(+), 32 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index 771bc08..cde0caf 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -2259,9 +2259,11 @@ out_free_key_filenames: + } + + /** +- * Imports a secure key from a file and adds it to the key store ++ * Imports a secure key from a buffer and adds it to the key store + * + * @param[in] keystore the key store ++ * @param[in] secure_key the buffer containing the key ++ * @param[in] secure_key_size the size of the key + * @param[in] name the name of the key + * @param[in] description textual description of the key (optional, can be NULL) + * @param[in] volumes a comma separated list of volumes associated with this +@@ -2274,7 +2276,6 @@ out_free_key_filenames: + * of two and in range 512 - 4096 bytes. 0 means that + * the sector size is not specified and the system + * default is used. +- * @param[in] import_file The name of a secure key containing the key to import + * @param[in] volume_type the type of volume + * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 + * @param[in] passphrase_file the file name of a file containing a passphrase +@@ -2283,25 +2284,23 @@ out_free_key_filenames: + * + * @returns 0 for success or a negative errno in case of an error + */ +-int keystore_import_key(struct keystore *keystore, const char *name, +- const char *description, const char *volumes, +- const char *apqns, bool noapqncheck, size_t sector_size, +- const char *import_file, const char *volume_type, +- bool gen_passphrase, const char *passphrase_file, +- struct ext_lib *lib) ++int keystore_import(struct keystore *keystore, unsigned char *secure_key, ++ size_t secure_key_size, const char *name, ++ const char *description, const char *volumes, ++ const char *apqns, bool noapqncheck, size_t sector_size, ++ const char *volume_type, bool gen_passphrase, ++ const char *passphrase_file, struct ext_lib *lib) + { + struct key_filenames file_names = { 0 }; + struct properties *key_props = NULL; +- size_t secure_key_size; + const char *key_type; + u8 mkvp[MKVP_LENGTH]; + int selected = 1; +- u8 *secure_key; + int rc; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + util_assert(name != NULL, "Internal error: name is NULL"); +- util_assert(import_file != NULL, "Internal error: import_file is NULL"); ++ util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + + rc = _keystore_get_key_filenames(keystore, name, &file_names); + if (rc != 0) +@@ -2311,27 +2310,29 @@ int keystore_import_key(struct keystore *keystore, const char *name, + if (rc != 0) + goto out_free_key_filenames; + +- secure_key = read_secure_key(import_file, &secure_key_size, +- keystore->verbose); +- if (secure_key == NULL) { +- rc = -ENOENT; +- goto out_free_key_filenames; +- } +- + key_type = get_key_type(secure_key, secure_key_size); + if (key_type == NULL) { + warnx("Key '%s' is not a valid secure key", name); +- free(secure_key); + rc = -EINVAL; + goto out_free_key_filenames; + } + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ if (apqns != NULL) { ++ warnx("No APQNs can be associated with keys of type %s", ++ key_type); ++ rc = -EINVAL; ++ goto out_free_props; ++ } ++ goto write_key; ++ } ++ + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + mkvp, keystore->verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); +- goto out_free_key; ++ goto out_free_props; + } + + rc = cross_check_apqns(apqns, mkvp, +@@ -2340,17 +2341,17 @@ int keystore_import_key(struct keystore *keystore, const char *name, + get_card_type_for_keytype(key_type), + true, keystore->verbose); + if (rc == -EINVAL) +- goto out_free_key; ++ goto out_free_props; + if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { + warnx("Your master key setup is improper"); +- goto out_free_key; ++ goto out_free_props; + } + + if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { + if (lib->cca->lib_csulcca == NULL) { + rc = load_cca_library(lib->cca, keystore->verbose); + if (rc != 0) +- goto out_free_key; ++ goto out_free_props; + } + + rc = select_cca_adapter_by_mkvp(lib->cca, mkvp, apqns, +@@ -2365,7 +2366,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, + warnx("No APQN found that is suitable for " + "working with the secure AES key '%s'", name); + rc = 0; +- goto out_free_key; ++ goto out_free_props; + } + + rc = restrict_key_export(lib->cca, secure_key, secure_key_size, +@@ -2375,7 +2376,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, + "key: %s", strerror(-rc)); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); +- goto out_free_key; ++ goto out_free_props; + } + + rc = check_aes_cipher_key(secure_key, secure_key_size); +@@ -2386,15 +2387,14 @@ int keystore_import_key(struct keystore *keystore, const char *name, + if (!prompt_for_yes(keystore->verbose)) { + warnx("Operation aborted"); + rc = -ECANCELED; +- goto out_free_key; ++ goto out_free_props; + } + } + } + ++write_key: + rc = write_secure_key(file_names.skey_filename, secure_key, + secure_key_size, keystore->verbose); +- free(secure_key); +- secure_key = NULL; + if (rc != 0) + goto out_free_props; + +@@ -2414,9 +2414,6 @@ int keystore_import_key(struct keystore *keystore, const char *name, + "Successfully imported a secure key in '%s' and key info in '%s'", + file_names.skey_filename, file_names.info_filename); + +-out_free_key: +- if (secure_key != NULL) +- free(secure_key); + out_free_props: + if (key_props != NULL) + properties_free(key_props); +@@ -2431,6 +2428,59 @@ out_free_key_filenames: + return rc; + } + ++/** ++ * Imports a secure key from a file and adds it to the key store ++ * ++ * @param[in] keystore the key store ++ * @param[in] name the name of the key ++ * @param[in] description textual description of the key (optional, can be NULL) ++ * @param[in] volumes a comma separated list of volumes associated with this ++ * key (optional, can be NULL) ++ * @param[in] apqns a comma separated list of APQNs associated with this ++ * key (optional, can be NULL) ++ * @param[in] noapqncheck if true, the specified APQN(s) are not checked for ++ * existence and type. ++ * @param[in] sector_size the sector size to use with dm-crypt. It must be a ++ * power of two and in range 512 - 4096 bytes. 0 means ++ * that the sector size is not specified and the system ++ * default is used. ++ * @param[in] import_file The name of a secure key containing the key to import ++ * @param[in] volume_type the type of volume ++ * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 ++ * @param[in] passphrase_file the file name of a file containing a passphrase ++ * for LUKS2 (optional, can be NULL) ++ * @param[in] lib the external library struct ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++int keystore_import_key(struct keystore *keystore, const char *name, ++ const char *description, const char *volumes, ++ const char *apqns, bool noapqncheck, size_t sector_size, ++ const char *import_file, const char *volume_type, ++ bool gen_passphrase, const char *passphrase_file, ++ struct ext_lib *lib) ++{ ++ size_t secure_key_size; ++ u8 *secure_key; ++ int rc; ++ ++ util_assert(import_file != NULL, "Internal error: import_file is NULL"); ++ ++ secure_key = read_secure_key(import_file, &secure_key_size, ++ keystore->verbose); ++ if (secure_key == NULL) ++ return -ENOENT; ++ ++ rc = keystore_import(keystore, secure_key, secure_key_size, name, ++ description, volumes, apqns, noapqncheck, ++ sector_size, volume_type, gen_passphrase, ++ passphrase_file, lib); ++ ++ if (secure_key != NULL) ++ free(secure_key); ++ ++ return rc; ++} + + /** + * Changes properties of a key in the keystore. +diff --git a/zkey/keystore.h b/zkey/keystore.h +index 1443b5d..b4cae9a 100644 +--- a/zkey/keystore.h ++++ b/zkey/keystore.h +@@ -3,7 +3,7 @@ + * + * Keystore handling functions + * +- * Copyright IBM Corp. 2018, 2020 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -65,6 +65,13 @@ int keystore_generate_key_kms(struct keystore *keystore, const char *name, + struct kms_option *kms_options, + size_t num_kms_options); + ++int keystore_import(struct keystore *keystore, unsigned char *secure_key, ++ size_t secure_key_size, const char *name, ++ const char *description, const char *volumes, ++ const char *apqns, bool noapqncheck, size_t sector_size, ++ const char *volume_type, bool gen_passphrase, ++ const char *passphrase_file, struct ext_lib *lib); ++ + int keystore_import_key(struct keystore *keystore, const char *name, + const char *description, const char *volumes, + const char *apqns, bool noapqncheck, size_t sector_size, +diff --git a/zkey/pvsecrets.c b/zkey/pvsecrets.c +index 7f28feb..a4b3a5a 100644 +--- a/zkey/pvsecrets.c ++++ b/zkey/pvsecrets.c +@@ -552,3 +552,129 @@ int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, + + return rc; + } ++ ++struct build_secret_key_blob_data { ++ unsigned char id[UV_SECRET_ID_LEN]; ++ char name[UV_SECRET_ID_LEN]; ++ struct pvsecrettoken token; ++ bool found; ++}; ++ ++/** ++ * Callback used to generate a pvsecrets key blob for a specific secret ID. ++ * Called for each secret. ++ * ++ * @param idx the index of the secret ++ * @param type the type of the secret ++ * @param id the ID of the secret ++ * @param cb_private callback private data ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++static int pvsecrets_build_key_blob_cb(u16 UNUSED(idx), u16 type, u32 len, ++ const u8 id[UV_SECRET_ID_LEN], ++ void *cb_private) ++{ ++ struct build_secret_key_blob_data *build_blob_data = cb_private; ++ ++ if (build_blob_data->found) ++ return 0; ++ ++ if (memcmp(id, build_blob_data->name, UV_SECRET_ID_LEN) != 0 && ++ memcmp(id, build_blob_data->id, UV_SECRET_ID_LEN) != 0) ++ return 0; ++ ++ memset(&build_blob_data->token, 0, sizeof(build_blob_data->token)); ++ build_blob_data->token.hdr.type = TOKEN_TYPE_NON_CCA; ++ build_blob_data->token.hdr.version = TOKEN_VERSION_PVSECRET; ++ build_blob_data->token.secret_type = type; ++ build_blob_data->token.secret_len = len; ++ memcpy(build_blob_data->token.secretid, id, UV_SECRET_ID_LEN); ++ ++ build_blob_data->found = true; ++ ++ return 0; ++} ++ ++/** ++ * Imports a protected virtualization secure key from the UV and adds it to the ++ * key store ++ * ++ * @param keystore the key store ++ * @param uv_fd the file descriptor of the ultravisor device ++ * @param secret_id the secret id as 32 byte hex string. Can be NULL if ++ * secret_name is non-NULL. ++ * @param secret_name the secret name. Can be NULL if secret_id is non-NULL. ++ * @param name the name of the key in the repository ++ * @param description textual description of the key (optional, can be NULL) ++ * @param volumes a comma separated list of volumes associated with this ++ * key (optional, can be NULL) ++ * @param volume_type the type of volume ++ * @param sector_size the sector size to use with dm-crypt. It must be a ++ * power of two and in range 512 - 4096 bytes. 0 means ++ * that the sector size is not specified and the system ++ * default is used. ++ * @param gen_passphrase if true, generate a (dummy) passphrase for LUKS2 ++ * @param passphrase_file the file name of a file containing a passphrase ++ * for LUKS2 (optional, can be NULL) ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++int pvsecrets_import(struct keystore *keystore, int uv_fd, ++ const char *secret_id, const char *secret_name, ++ const char *name, const char *description, ++ const char *volumes, const char *volume_type, ++ long sector_size, bool gen_passphrase, ++ const char *passphrase_file, bool verbose) ++{ ++ struct build_secret_key_blob_data build_blob_data = { 0 }; ++ int rc; ++ ++ util_assert(keystore != NULL, "Internal error: keystore is NULL"); ++ util_assert(uv_fd != -1, "Internal error: uv_fd is -1"); ++ util_assert(secret_id != NULL || secret_name != NULL, ++ "Internal error: secret_id and secrest_name is NULL"); ++ util_assert(name != NULL, "Internal error: name is NULL"); ++ ++ rc = get_secret_id_from_hex_or_name(secret_id, secret_name, ++ build_blob_data.id, ++ build_blob_data.name); ++ if (rc < 0) ++ return rc; ++ if (rc > 0) ++ return -EINVAL; ++ ++ rc = uv_list_secrets(uv_fd, pvsecrets_build_key_blob_cb, ++ &build_blob_data, verbose); ++ if (rc != 0) { ++ warnx("Failed to import the pvsecret with %s '%s': %s", ++ secret_id != NULL ? "id" : "name", ++ secret_id != NULL ? secret_id : secret_name, ++ strerror(-rc)); ++ return rc; ++ } ++ ++ if (!build_blob_data.found) { ++ warnx("The pvsecret with %s '%s' does not exist", ++ secret_id != NULL ? "id" : "name", ++ secret_id != NULL ? secret_id : secret_name); ++ return -ENOENT; ++ } ++ ++ if (!is_pvsecret_type_supported(build_blob_data.token.secret_type)) { ++ warnx("The type of the pvsecret with %s '%s' is not supported " ++ "by zkey: %s", secret_id != NULL ? "id" : "name", ++ secret_id != NULL ? secret_id : secret_name, ++ get_pvsecret_type_name( ++ build_blob_data.token.secret_type)); ++ return -EINVAL; ++ } ++ ++ rc = keystore_import(keystore, (unsigned char *)&build_blob_data.token, ++ sizeof(build_blob_data.token), name, description, ++ volumes, NULL, false, sector_size, volume_type, ++ gen_passphrase, passphrase_file, NULL); ++ ++ return rc; ++} +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +index ad84403..9503c51 100644 +--- a/zkey/pvsecrets.h ++++ b/zkey/pvsecrets.h +@@ -16,6 +16,7 @@ + #include "lib/zt_common.h" + + #include "pkey.h" ++#include "keystore.h" + + /* + * Definitions for the /dev/uv kernel module interface +@@ -89,5 +90,11 @@ int uv_open_device(bool verbose); + int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, + const char *secret_id, const char *secret_name, + bool verbose); ++int pvsecrets_import(struct keystore *keystore, int uv_fd, ++ const char *secret_id, const char *secret_name, ++ const char *name, const char *description, ++ const char *volumes, const char *volume_type, ++ long sector_size, bool gen_passphrase, ++ const char *passphrase_file, bool verbose); + + #endif +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index 4386629..ba71a83 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -1208,6 +1208,64 @@ are supported by the \fBzkey\fP tool. To list all pvsecret types, specify the + This command is only available when running in a secure execution guest. + Only the \fBroot\fP user is allowed to perform this command. + . ++.SS "Import protected virtualization secrets into the repository" ++. ++.B zkey pvsecrets ++.BR import | im ++.RB [ \-\-pvsecret\-id | \-I ++.IR pvsecret\-id ] ++.RB [ \-\-pvsecret\-name | \-e ++.IR pvsecret\-name ] ++.B \-\-name | \-N ++.IR key\-name ++.RB [ \-\-description | \-d ++.IR description ] ++.RB [ \-\-volumes | \-l ++.IR volume1:dmname1[,volume2:dmname2[,...]] ] ++.RB [ \-\-sector\-size | \-S ++.IR bytes ] ++.RB [ \-\-volume\-type | \-t ++.IR type ] ++.RB [ \-\-gen\-dummy\-passphrase ] ++.RB [ \-\-set\-dummy\-passphrase ++.IR passphrase\-file ] ++.RB [ \-\-verbose | \-V ] ++. ++.PP ++Use the ++.B pvsecrets import ++command to import a protected virtualization (PV) secret into the repository. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP ++option can be specified. If the pvsecret name is specified, then the ++\fB\-\-name\fP option can be omitted. The name of the protected virtualization ++secret key object in the repository will then be the same as the pvsecret name. ++.PP ++You can use the YAML file that was created when using the \fBpvsecret create\fP ++command for adding the protected virtualization secret: ++\fB\-\-pvsecret\-id "$(yq .id \fP\fIYAML\-FILE\fP\fB)"\fP or ++\fB\-\-pvsecret\-name "$(yq .name \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++.PP ++A protected virtualization secret key object does not contain the key material, ++but only a reference (i.e. the secret ID) to the key in the ultravisor. ++When such a protected virtualization secret key object is used with ++\fBdm\-crypt\fP and the \fBPAES\fP kernel cipher, the key material (i.e. a ++protected key) is retrieved from the ultravisor and the crypto operation is ++performed with it. ++.PP ++When importing a protected virtualization secret in a key repository, ++additional information can be associated with it using the ++.B \-\-description ++, ++.B \-\-volumes ++, or the ++.B \-\-sector\-size ++options. APQNs can not be associated, because protected virtualization secrets ++do not require a crypto card. ++.PP ++This command is only available when running in a secure execution guest. ++Only the \fBroot\fP user is allowed to perform this command. ++. + . + . + . +@@ -2117,6 +2175,82 @@ can be specified, but not both. + . + . + . ++.SS "Options for the pvsecrets import command" ++.TP ++.BR \-I ", " \-\-pvsecret\-id\~\fIpvsecret\-id\fP ++ID of the protected virtualization (PV) secret to import. The pvsecret ID is a ++32 byte hex string, optionally prefixed by \fB0x\fP. You can use the YAML file ++that was created when using the \fBpvsecret create\fP command for adding the ++protected virtualization secret: ++\fB\-\-pvsecret\-id "$(yq .id \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. ++.TP ++.BR \-e ", " \-\-pvsecret\-name\~\fIpvsecret\-name\fP ++Name of the protected virtualization (PV) secret to import. You can use the YAML ++file that was created when using the \fBpvsecret create\fP command for adding ++the protected virtualization secret: ++\fB\-\-pvsecret\-name "$(yq .name \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. If the \fB\-\-pvsecret\-name\fP option is ++specified, then the \fB\-\-name\fP option can be omitted. The name of the ++protected virtualization secret key object in the repository will then be the ++same as the pvsecret name. ++.TP ++.BR \-N ", " \-\-name\~\fIkey\-name\fP ++Specifies the name of the protected virtualization secret key object in the key ++repository. If the \fB\-\-pvsecret\-name\fP option is specified, then the ++\fB\-\-name\fP option can be omitted. The name of the protected virtualization ++secret in the repository will then be the same as the pvsecret name. ++.TP ++.BR \-d ", " \-\-description\~\fIdescription\fP ++Specifies a textual description for the protected virtualization secret in the ++key repository. ++.TP ++.BR \-l ", " \-\-volumes\~\fIvolume1:dmname1[,volume2:dmname2[,...]]\fP ++Specifies a comma-separated list of volumes (block devices) which are ++associated with the protected virtualization secret in the repository. These ++volumes are to be encrypted using \fBdm\-crypt\fP with the protected ++virtualization secret. The volume association also contains the device-mapper ++name, separated by a colon, used with \fBdm\-crypt\fP. A specific volume can ++only be associated with a single key. ++.TP ++.BR \-S ", " \-\-sector\-size\~\fIbytes\fP ++Specifies the sector size in bytes used with \fBdm\-crypt\fP. It must be a power ++of two and in the range of 512 to 4096 bytes. If omitted, the system default ++sector size is used. ++.TP ++.BR \-t ", " \-\-volume\-type\~\fItype\fP ++Specifies the volume type of the associated volumes used with \fBdm\-crypt\fP. ++Possible values are \fBplain\fP and \fBluks2\fP. If omitted, \fBluks2\fP is ++used. This option is only available if ++.B zkey ++has been compiled with LUKS2 support enabled. If LUKS2 support is not enabled, ++the default volume type is \fBplain\fP. ++.TP ++.BR \-\-gen\-dummy\-passphrase ++Generate a dummy passphrase randomly and associate it with the protected ++virtualization secret used to encrypt LUKS2 volume(s). The LUKS2 passphrase is ++of less or no relevance for the security of the volume(s), when a protected ++virtualization secret is used to encrypt the volume(s), and can therefore be ++stored insecurely inside the key repository. If for a certain usage the ++passphrase is of relevance for security, then do not use this option. This ++option can only be specified for keys with a volume type of \fBluks2\fP. ++.TP ++.BR \-\-set\-dummy\-passphrase\~\fIpassphrase\-file\fP ++Set a dummy passphrase that is read from the specified file and associate it ++with the protected virtualization secret used to encrypt LUKS2 volume(s). ++The LUKS2 passphrase is of less or no relevance for the security of the ++volume(s), when an protected virtualization secret is used to encrypt the ++volume(s), and can therefore be stored insecurely inside the key repository. ++If for a certain usage the passphrase is of relevance for security, then do ++not use this option. This option can only be specified for keys with a volume ++type of \fBluks2\fP. ++. ++. ++. + .SS "General options" + .TP + .BR \-V ", " \-\-verbose +diff --git a/zkey/zkey.c b/zkey/zkey.c +index adc48d6..6e9b32a 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -145,6 +145,7 @@ static struct zkey_globals { + #define COMMAND_KMS_REFRESH "refresh" + #define COMMAND_PVSECRETS "pvsecrets" + #define COMMAND_PVSECRETS_LIST "list" ++#define COMMAND_PVSECRETS_IMPORT "import" + + #define OPT_COMMAND_PLACEHOLDER "PLACEHOLDER" + +@@ -1234,6 +1235,103 @@ static struct util_opt opt_vec[] = { + .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, + }, + /***********************************************************/ ++ { ++ .flags = UTIL_OPT_FLAG_SECTION, ++ .desc = "OPTIONS", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "pvsecret-id", required_argument, NULL, 'I'}, ++ .argument = "PVSECRET-ID", ++ .desc = "ID of the protected virtualization (PV) secret to " ++ "import. Either '--pvsecret-id/-I' or " ++ "'--pvsecret-name/-e' can be specified, but not both.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "pvsecret-name", required_argument, NULL, 'e'}, ++ .argument = "PVSECRET-NAME", ++ .desc = "Name of the protected virtualization (PV) secret to " ++ "import. Either '--pvsecret-name/-e' or " ++ "'--pvsecret-id/-I' can be specified, but not both. " ++ "If the '--pvsecret-name/-e' option is specified, but " ++ "the '--name/-N' option is omitted, then the imported " ++ "protected virtualisation secret will be named the " ++ "same as the pvsecret name.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "name", required_argument, NULL, 'N'}, ++ .argument = "NAME", ++ .desc = "Name of the imported protected virtualisation secret " ++ "in the repository. If the '--name/-N' option is " ++ "omitted, but the '--pvsecret-name/-e' is specified, " ++ "then the imported protected virtualisation secret " ++ "will be named the same as the pvsecret name.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "description", required_argument, NULL, 'd'}, ++ .argument = "DESCRIPTION", ++ .desc = "Textual description of the protected virtualisation " ++ "secret in the repository", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "volumes", required_argument, NULL, 'l'}, ++ .argument = "VOLUME:DMNAME[,...]", ++ .desc = "Comma-separated pairs of volume and device-mapper " ++ "names that are associated with the protected " ++ "virtualisation secret in the repository", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "sector-size", required_argument, NULL, 'S'}, ++ .argument = "512|4096", ++ .desc = "The sector size used with dm-crypt. It must be a power " ++ "of two and in range 512 - 4096 bytes. If this option " ++ "is omitted, the system default sector size (512) is " ++ "used", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++#ifdef HAVE_LUKS2_SUPPORT ++ { ++ .option = { "volume-type", required_argument, NULL, 't'}, ++ .argument = "type", ++ .desc = "The type of the associated volume(s). Possible values " ++ "are 'plain' and 'luks2'. When this option is omitted, " ++ "the default is 'luks2'", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++#endif ++ { ++ .option = { "gen-dummy-passphrase", 0, NULL, ++ OPT_GEN_DUMMY_PASSPHRASE}, ++ .desc = "Generate a dummy passphrase and associate it with the " ++ "protected virtualisation secret used to encrypt LUKS2 " ++ "volume(s). The LUKS2 passphrase is of less or no " ++ "relevance for the security of the volume(s), when an " ++ "protected virtualisation secret is used to encrypt " ++ "the volume(s), and can therefore be stored insecurely " ++ "inside the secure key repository.", ++ .flags = UTIL_OPT_FLAG_NOSHORT, ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "set-dummy-passphrase", required_argument, NULL, ++ OPT_SET_DUMMY_PASSPHRASE}, ++ .argument = "passphrase-file", ++ .desc = "Set a dummy passphrase to be associated with the " ++ "protected virtualisation secret used to encrypt LUKS2 " ++ "volume(s). The LUKS2 passphrase is of less or no " ++ "relevance for the security of the volume(s), when a " ++ "protected virtualisation secret is used to encrypt " ++ "the volume(s), and can therefore be stored insecurely " ++ "inside the secure key repository.", ++ .flags = UTIL_OPT_FLAG_NOSHORT, ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ /***********************************************************/ + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, +@@ -1339,6 +1437,7 @@ static int command_kms_list(void); + static int command_kms_import(void); + static int command_kms_refresh(void); + static int command_pvsecrets_list(void); ++static int command_pvsecrets_import(void); + + static struct zkey_command zkey_kms_commands[] = { + { +@@ -1469,6 +1568,19 @@ static struct zkey_command zkey_pvsecrets_commands[] = { + .has_options = 1, + .need_uv_device = 1, + }, ++ { ++ .command = COMMAND_PVSECRETS_IMPORT, ++ .abbrev_len = 2, ++ .function = command_pvsecrets_import, ++ .short_desc = "Imports a protected virtualization (PV) secret", ++ .long_desc = "Imports a protected virtualization (PV) secret " ++ "into the repository. This command is only " ++ "available when running in a secure execution " ++ "guest.", ++ .has_options = 1, ++ .need_keystore = 1, ++ .need_uv_device = 1, ++ }, + { .command = NULL } + }; + +@@ -3006,6 +3118,51 @@ static int command_pvsecrets_list(void) + return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; + } + ++/* ++ * Command handler for 'pvsecrets import'. ++ * ++ * Import a protected virtualization secret into the repository ++ */ ++static int command_pvsecrets_import(void) ++{ ++ int rc; ++ ++ if (g.secret_id == NULL && g.secret_name == NULL) { ++ misc_print_required_parm("--pvsecret-id/-I or " ++ "--pvsecret-name/-e"); ++ return EXIT_FAILURE; ++ } ++ if (g.secret_id != NULL && g.secret_name != NULL) { ++ warnx("Either '--pvsecret-id/-I' or '--pvsecret-name/-e' can " ++ "be specified, but not both"); ++ util_prg_print_parse_error(); ++ return EXIT_FAILURE; ++ } ++ if (g.secret_name == NULL && g.name == NULL) { ++ misc_print_required_parm("--name/-N"); ++ return EXIT_FAILURE; ++ } ++ ++ if (g.sector_size < 0) ++ g.sector_size = 0; ++ ++ if (g.gen_passphrase && g.passphrase_file != NULL) { ++ warnx("Either '--gen-dummy-passphrase' or " ++ "'--set-dummy-passphrase' can be specified, but not " ++ "both"); ++ util_prg_print_parse_error(); ++ return EXIT_FAILURE; ++ } ++ ++ rc = pvsecrets_import(g.keystore, g.uv_fd, g.secret_id, g.secret_name, ++ g.name != NULL ? g.name : g.secret_name, ++ g.description, g.volumes, g.volume_type, ++ g.sector_size, g.gen_passphrase, ++ g.passphrase_file, g.verbose); ++ ++ return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; ++} ++ + /** + * Opens the keystore. The keystore directory is either the + * default directory or as specified in an environment variable +-- +2.47.1 + + +From b20ebd7b65190b261aee21fcfcbd659d5951f9f4 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Mon, 19 Feb 2024 10:21:06 +0100 +Subject: [PATCH 29/31] zkey: Reject key generation and APQN association for + PVSECRET-AES keys (RHEL-23870) + +Keys of type PVSECRET-AES can not be generated using 'zkey generate'. +Furthermore, APQNs can not be associated with keys of type PVSECRET-AES +via 'zkey change'. Reject that with a proper error message. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit 5276d408fd10669b3d8e623455778a675e8dc149) +--- + zkey/keystore.c | 32 +++++++++++++++++++++++--------- + zkey/zkey.1 | 7 +++++++ + zkey/zkey.c | 5 +++++ + 3 files changed, 35 insertions(+), 9 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index cde0caf..db62e0a 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -2009,6 +2009,12 @@ int keystore_generate_key(struct keystore *keystore, const char *name, + return -EINVAL; + } + ++ if (!is_secure_key_type(key_type)) { ++ warnx("Keys of type %s can not be generated. Use 'zkey " ++ "pvsecret import' instead", key_type); ++ return -EINVAL; ++ } ++ + rc = _keystore_get_key_filenames(keystore, name, &file_names); + if (rc != 0) + goto out_free_key_filenames; +@@ -2535,9 +2541,9 @@ int keystore_change_key(struct keystore *keystore, const char *name, + const char *null_ptr = NULL; + char *upd_volumes = NULL; + size_t secure_key_size; ++ u8 *secure_key = NULL; + u8 mkvp[MKVP_LENGTH]; + char sect_size[30]; +- u8 *secure_key; + bool kms_bound; + int rc; + +@@ -2589,13 +2595,6 @@ int keystore_change_key(struct keystore *keystore, const char *name, + goto out; + } + +- rc = _keystore_change_association(key_props, PROP_NAME_APQNS, +- apqns, "APQN", +- _keystore_apqn_check, +- &apqn_check); +- if (rc != 0) +- goto out; +- + secure_key = read_secure_key(file_names.skey_filename, + &secure_key_size, + keystore->verbose); +@@ -2604,11 +2603,24 @@ int keystore_change_key(struct keystore *keystore, const char *name, + goto out; + } + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ warnx("No APQNs can be associated with keys of type %s", ++ get_key_type(secure_key, secure_key_size)); ++ rc = -EINVAL; ++ goto out; ++ } ++ ++ rc = _keystore_change_association(key_props, PROP_NAME_APQNS, ++ apqns, "APQN", ++ _keystore_apqn_check, ++ &apqn_check); ++ if (rc != 0) ++ goto out; ++ + rc = get_master_key_verification_pattern(secure_key, + secure_key_size, + mkvp, + keystore->verbose); +- free(secure_key); + if (rc) + goto out; + +@@ -2742,6 +2754,8 @@ out: + free(upd_volumes); + if (upd_volume_type != NULL) + free(upd_volume_type); ++ if (secure_key != NULL) ++ free(secure_key); + + if (rc != 0) + pr_verbose(keystore, "Failed to change key '%s': %s", +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index ba71a83..baaf847 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -402,6 +402,9 @@ additional information can be associated with a secure key using the + .B \-\-sector\-size + options. + .PP ++Keys of type \fBPVSECRET\-AES\fP do not use a cryptographic adapter, thus APQNs ++can not be associated with them. ++.PP + .B Note: + The \fBimport\fP command requires the CCA host library (libcsulcca.so) + to be installed when secure keys of type \fBCCA\-AESCIPHER\fP are imported. +@@ -564,6 +567,10 @@ APQNs that are associated with the key management system plugin. + Other associated information is also changed in the key management system when + changed using the change command. + .PP ++For keys of type \fBPVSECRET\-AES\fP you can not change or set the APQN ++association. These keys do not use a cryptographic adapter, thus APQNs can not ++be associated with them. ++.PP + .B Note: + The secure key itself cannot be changed, only information about the secure + key is changed. To rename a secure key, use the \fBrename\fP command. +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 6e9b32a..36bdbcc 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -2001,6 +2001,11 @@ static int command_generate(void) + return command_generate_repository(); + if (g.key_type == NULL) + g.key_type = KEY_TYPE_CCA_AESDATA; ++ if (!is_secure_key_type(g.key_type)) { ++ warnx("Keys of type '%s' can not be generated. Use 'zkey " ++ "pvsecret import' instead", g.key_type); ++ return -EXIT_FAILURE; ++ } + if (g.pos_arg != NULL) { + if (g.volumes != NULL) { + warnx("Option '--volumes|-l' is not valid for " +-- +2.47.1 + + +From 9a1bfa5a9977eef50956d588b736392aa2242578 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Mon, 19 Feb 2024 11:26:41 +0100 +Subject: [PATCH 30/31] zkey: Reject re-enciphering of PVSECRET-AES keys + (RHEL-23870) + +Keys of type PVSECRET-AES can not be reenciphered using 'zkey reencipher' +or 'zkey-cryptsetup reencipher'. Reject that with a proper error message. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit a8eb2bd4e7e74445c953906b33d450c2ace5223f) +--- + zkey/keystore.c | 9 +++++++++ + zkey/zkey-cryptsetup.1 | 8 ++++++-- + zkey/zkey-cryptsetup.c | 16 ++++++++++++---- + zkey/zkey.1 | 4 ++++ + zkey/zkey.c | 7 +++++++ + 5 files changed, 38 insertions(+), 6 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index db62e0a..4f795a2 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -3567,6 +3567,15 @@ static int _keystore_process_reencipher(struct keystore *keystore, + goto out; + } + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ warnx("Key '%s' is of type %s and can not be re-enciphered, " ++ "skipping", name, get_key_type(secure_key, ++ secure_key_size)); ++ info->num_skipped++; ++ rc = 0; ++ goto out; ++ } ++ + apqns = properties_get(properties, PROP_NAME_APQNS); + if (apqns != NULL) + apqn_list = str_list_split(apqns); +diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 +index c455f84..185edab 100644 +--- a/zkey/zkey-cryptsetup.1 ++++ b/zkey/zkey-cryptsetup.1 +@@ -1,8 +1,8 @@ +-.\" Copyright IBM Corp. 2018 ++.\" Copyright IBM Corp. 2018, 2024 + .\" s390-tools is free software; you can redistribute it and/or modify + .\" it under the terms of the MIT license. See LICENSE for details. + .\" +-.TH ZKEY\-CRYPTSETUP 1 "May 2018" "s390-tools" ++.TH ZKEY\-CRYPTSETUP 1 "February 2024" "s390-tools" + .SH NAME + zkey\-cryptsetup \- Manage secure AES volume keys of volumes encrypted with + \fBLUKS2\fP and the \fBpaes\fP cipher +@@ -115,6 +115,10 @@ command to re-encipher a secure AES volume key of a volume encrypted with + re-enciphered when the master key of the cryptographic adapter in CCA or EP11 + coprocessor mode changes. + .PP ++Volume keys of type \fBPVSECRET\-AES\fP can not be re-enciphered. These keys do ++not use a cryptographic adapter, thus they do not need to be re-enciphered when ++the master key of a cryptographic adapter changes. ++.PP + The cryptographic adapter in CCA coprocessor mode has three different registers + to store master keys: + .RS 2 +diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c +index 8b55f7d..2b018a2 100644 +--- a/zkey/zkey-cryptsetup.c ++++ b/zkey/zkey-cryptsetup.c +@@ -2,7 +2,7 @@ + * zkey-cryptsetup - Re-encipher or validate volume keys of volumes + * encrypted with LUKS2 and the paes cipher. + * +- * Copyright IBM Corp. 2018 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -82,7 +82,7 @@ static const struct util_prg prg = { + { + .owner = "IBM Corp.", + .pub_first = 2018, +- .pub_last = 2018, ++ .pub_last = 2024, + }, + UTIL_PRG_COPYRIGHT_END + } +@@ -1609,14 +1609,22 @@ static int reencipher_prepare(int token) + if (rc < 0) + goto out; + ++ securekeysize = keysize - integrity_keysize; ++ ++ if (!is_secure_key((u8 *)key, securekeysize)) { ++ warnx("The volume key of device '%s' is of type %s and can " ++ "not be re-enciphered", g.pos_arg, ++ get_key_type((u8 *)key, securekeysize)); ++ rc = -EINVAL; ++ goto out; ++ } ++ + reenc_tok.original_keyslot = rc; + + rc = ensure_is_active_keylot(reenc_tok.original_keyslot); + if (rc != 0) + goto out; + +- securekeysize = keysize - integrity_keysize; +- + rc = generate_key_verification_pattern((u8 *)key, securekeysize, + reenc_tok.verification_pattern, + sizeof(reenc_tok.verification_pattern), +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index baaf847..316db5f 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -266,6 +266,10 @@ command to re-encipher an existing secure key with a new master key. + A secure key must be re-enciphered when the master key of the CCA or EP11 + cryptographic adapter changes. + .PP ++Keys of type \fBPVSECRET\-AES\fP can not be re-enciphered. These keys do not ++use a cryptographic adapter, thus they do not need to be re-enciphered when the ++master of a cryptographic adapter changes. ++.PP + The CCA cryptographic adapter has three different registers to store + master keys: + .RS 2 +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 36bdbcc..90b4610 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -2118,6 +2118,13 @@ static int command_reencipher_file(void) + if (secure_key == NULL) + return EXIT_FAILURE; + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ warnx("A key of type %s can not be re-enciphered", ++ get_key_type(secure_key, secure_key_size)); ++ rc = EXIT_FAILURE; ++ goto out; ++ } ++ + rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size, NULL, + &is_old_mk, NULL, g.verbose); + if (rc != 0) { +-- +2.47.1 + + +From 48ab37947ccda570e98aa046f2c28c11fdd59bdf Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Mon, 19 Feb 2024 10:25:54 +0100 +Subject: [PATCH 31/31] zkey: Support validation of key of type PVSECRET-AES + (RHEL-23870) + +Keys of type PVSECRET-AES can also be verified via the pkey IOCTL +PKEY_VERIFYKEY2, but the card and domain fields must be zero, because such +a key does not use a crypto card. Also XTS keys of type PVSRCRET-AES are +not represented by 2 concatenated keys but by just one key of type +PVSECRET-AES. Thus, special handling is required for XTS keys. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +(cherry picked from commit 833a8e7309ebf0ce70f2ee989ced5f87d6c3550b) +--- + zkey/keystore.c | 50 ++++++++++++++++++++++-------------- + zkey/pkey.c | 46 +++++++++++++++++++++------------- + zkey/zkey-cryptsetup.1 | 4 ++- + zkey/zkey-cryptsetup.c | 45 +++++++++++++++++++-------------- + zkey/zkey.1 | 8 +++--- + zkey/zkey.c | 57 ++++++++++++++++++++++++------------------ + 6 files changed, 126 insertions(+), 84 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index 4f795a2..58f27df 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -3055,19 +3055,25 @@ static void _keystore_print_record(struct util_rec *rec, + util_rec_set(rec, REC_XTS, is_xts ? "Yes" : "No"); + util_rec_set(rec, REC_KEY_TYPE, key_type); + if (validation) { +- if (valid) +- util_rec_set(rec, REC_MASTERKEY, +- "%s master key (MKVP: %s)", +- is_old_mk ? "OLD" : "CURRENT", +- printable_mkvp( +- get_card_type_for_keytype(key_type), +- mkvp)); +- else +- util_rec_set(rec, REC_MASTERKEY, +- "(unknown, MKVP: %s)", +- printable_mkvp( +- get_card_type_for_keytype(key_type), +- mkvp)); ++ if (mkvp != NULL) { ++ if (valid) ++ util_rec_set(rec, REC_MASTERKEY, ++ "%s master key (MKVP: %s)", ++ is_old_mk ? "OLD" : "CURRENT", ++ printable_mkvp( ++ get_card_type_for_keytype( ++ key_type), ++ mkvp)); ++ else ++ util_rec_set(rec, REC_MASTERKEY, ++ "(unknown, MKVP: %s)", ++ printable_mkvp( ++ get_card_type_for_keytype( ++ key_type), ++ mkvp)); ++ } else { ++ util_rec_set(rec, REC_MASTERKEY, "(none)"); ++ } + } + if (volumes_argz != NULL) + util_rec_set_argz(rec, REC_VOLUMES, volumes_argz, +@@ -3294,17 +3300,22 @@ static int _keystore_process_validate(struct keystore *keystore, + valid = 1; + } + +- rc = get_master_key_verification_pattern(secure_key, secure_key_size, +- mkvp, keystore->verbose); +- if (rc != 0) +- goto out; ++ if (is_secure_key(secure_key, secure_key_size)) { ++ rc = get_master_key_verification_pattern(secure_key, ++ secure_key_size, ++ mkvp, ++ keystore->verbose); ++ if (rc != 0) ++ goto out; ++ } + + _keystore_print_record(info->rec, name, properties, 1, + file_names->skey_filename, secure_key_size, + is_xts_key(secure_key, secure_key_size), + clear_key_bitsize, valid, is_old_mk, + _keystore_reencipher_key_exists(file_names), +- mkvp, ++ is_secure_key(secure_key, secure_key_size) ? ++ mkvp : NULL, + _keystore_passphrase_file_exists(file_names) ? + file_names->pass_filename : NULL); + +@@ -3316,7 +3327,8 @@ static int _keystore_process_validate(struct keystore *keystore, + "master key\n", 0); + info->num_warnings++; + } +- if (info->noapqncheck == 0) ++ if (info->noapqncheck == 0 && ++ is_secure_key(secure_key, secure_key_size)) + if (_keystore_display_apqn_status(keystore, properties, + mkvp) != 0) + info->num_warnings++; +diff --git a/zkey/pkey.c b/zkey/pkey.c +index 53c0a55..25deb05 100644 +--- a/zkey/pkey.c ++++ b/zkey/pkey.c +@@ -1287,33 +1287,43 @@ int validate_secure_key(int pkey_fd, + { + struct pkey_verifykey2 verifykey2; + struct pkey_apqn *list = NULL; ++ bool xts, valid, securekey; + u32 i, list_entries = 0; +- bool xts, valid; +- u32 flags; ++ u32 flags = 0; + int rc; + + util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); + util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + +- xts = is_xts_key(secure_key, secure_key_size); ++ securekey = is_secure_key(secure_key, secure_key_size); ++ xts = securekey ? is_xts_key(secure_key, secure_key_size) : false; + +- flags = PKEY_FLAGS_MATCH_CUR_MKVP; +- if (is_cca_aes_data_key(secure_key, secure_key_size) || +- is_cca_aes_cipher_key(secure_key, secure_key_size)) +- flags |= PKEY_FLAGS_MATCH_ALT_MKVP; ++ if (securekey) { ++ flags = PKEY_FLAGS_MATCH_CUR_MKVP; ++ if (is_cca_aes_data_key(secure_key, secure_key_size) || ++ is_cca_aes_cipher_key(secure_key, secure_key_size)) ++ flags |= PKEY_FLAGS_MATCH_ALT_MKVP; + +- rc = build_apqn_list_for_key(pkey_fd, secure_key, +- HALF_KEYSIZE_FOR_XTS(secure_key_size, xts), +- flags, apqns, &list, &list_entries, +- verbose); +- if (rc != 0) { +- pr_verbose(verbose, "Failed to build a list of APQNs that can " +- "validate this secure key: %s", strerror(-rc)); +- return rc; ++ rc = build_apqn_list_for_key(pkey_fd, secure_key, ++ HALF_KEYSIZE_FOR_XTS( ++ secure_key_size, xts), ++ flags, apqns, &list, &list_entries, ++ verbose); ++ if (rc != 0) { ++ pr_verbose(verbose, "Failed to build a list of APQNs " ++ "that can validate this secure " ++ "key: %s", strerror(-rc)); ++ return rc; ++ } ++ } else { ++ list = util_malloc(sizeof(struct pkey_apqn)); ++ list[0].card = 0; ++ list[0].domain = 0; ++ list_entries = 1; + } + + if (is_old_mk != NULL) +- *is_old_mk = true; ++ *is_old_mk = securekey ? true : false; + if (clear_key_bitsize != NULL) + *clear_key_bitsize = 0; + +@@ -1333,7 +1343,7 @@ int validate_secure_key(int pkey_fd, + continue; + } + +- if (is_xts_key(secure_key, secure_key_size)) { ++ if (xts) { + rc = validate_secure_xts_key(pkey_fd, &list[i], + secure_key, + secure_key_size, +@@ -1358,7 +1368,7 @@ int validate_secure_key(int pkey_fd, + * If at least one of the APQNs have a matching current MK, + * then don't report OLD, even if some match the old MK. + */ +- if (is_old_mk && ++ if (securekey && is_old_mk && + (verifykey2.flags & PKEY_FLAGS_MATCH_CUR_MKVP)) + *is_old_mk = false; + } +diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 +index 185edab..ffd600d 100644 +--- a/zkey/zkey-cryptsetup.1 ++++ b/zkey/zkey-cryptsetup.1 +@@ -68,7 +68,9 @@ It also displays the attributes of the secure key, such as key size, whether + it is a secure key that can be used for the XTS cipher mode, and the master key + register (CURRENT or OLD) with which the secure key is enciphered. + For further information about master key registers, see the +-\fBreencipher\fP command. ++\fBreencipher\fP command. Keys of type \fBPVSECRET\-AES\fP do not use a ++cryptographic adapter, thus no master key information is displayed for such ++keys. + .PP + To open a key slot contained in the LUKS2 header of the volume, a passphrase is + required. You are prompted for the passphrase, unless option +diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c +index 2b018a2..65716f3 100644 +--- a/zkey/zkey-cryptsetup.c ++++ b/zkey/zkey-cryptsetup.c +@@ -1346,8 +1346,7 @@ static int check_keysize_and_cipher_mode(const u8 *key, size_t keysize) + } + + if (strncmp(crypt_get_cipher_mode(g.cd), "xts", 3) == 0) { +- if (keysize < 2 * MIN_SECURE_KEY_SIZE || +- (key != NULL && !is_xts_key(key, keysize))) { ++ if (key != NULL && !is_xts_key(key, keysize)) { + warnx("The volume key size %lu is not valid for the " + "cipher mode '%s'", keysize, + crypt_get_cipher_mode(g.cd)); +@@ -1539,8 +1538,9 @@ static int validate_keyslot(int keyslot, char **key, size_t *keysize, + rc = -EINVAL; + goto out; + } +- pr_verbose("Volume key is currently enciphered with %s master key", +- is_old ? "OLD" : "CURRENT"); ++ if (is_secure_key((u8 *)vkey, vkeysize - ikeysize)) ++ pr_verbose("Volume key is currently enciphered with %s " ++ "master key", is_old ? "OLD" : "CURRENT"); + + if (key != NULL) + *key = vkey; +@@ -2023,12 +2023,14 @@ static int command_validate(void) + vp_tok_avail = 1; + } + +- rc = get_master_key_verification_pattern((u8 *)key, seckeysize, +- mkvp, g.verbose); +- if (rc != 0) { +- warnx("Failed to get the master key verification pattern: %s", +- strerror(-rc)); +- goto out; ++ if (is_secure_key((u8 *)key, seckeysize)) { ++ rc = get_master_key_verification_pattern((u8 *)key, seckeysize, ++ mkvp, g.verbose); ++ if (rc != 0) { ++ warnx("Failed to get the master key verification " ++ "pattern: %s", strerror(-rc)); ++ goto out; ++ } + } + + key_type = get_key_type((u8 *)key, seckeysize); +@@ -2041,15 +2043,19 @@ static int command_validate(void) + printf(" Key type: %s\n", key_type); + if (is_valid) { + printf(" Clear key size: %lu bits\n", clear_keysize); +- printf(" Enciphered with: %s master key (MKVP: " +- "%s)\n", is_old_mk ? "OLD" : "CURRENT", +- printable_mkvp(get_card_type_for_keytype(key_type), +- mkvp)); ++ if (is_secure_key((u8 *)key, seckeysize)) { ++ printf(" Enciphered with: %s master key (MKVP: " ++ "%s)\n", is_old_mk ? "OLD" : "CURRENT", ++ printable_mkvp(get_card_type_for_keytype( ++ key_type), mkvp)); ++ } + } else { + printf(" Clear key size: (unknown)\n"); +- printf(" Enciphered with: (unknown, MKVP: %s)\n", +- printable_mkvp(get_card_type_for_keytype(key_type), +- mkvp)); ++ if (is_secure_key((u8 *)key, seckeysize)) { ++ printf(" Enciphered with: (unknown, MKVP: %s)\n", ++ printable_mkvp(get_card_type_for_keytype( ++ key_type), mkvp)); ++ } + } + if (vp_tok_avail) + print_verification_pattern(vp_tok.verification_pattern); +@@ -2065,7 +2071,7 @@ static int command_validate(void) + if (!is_valid) + printf("\nATTENTION: The secure volume key is not valid.\n"); + +- if (is_old_mk) ++ if (is_secure_key((u8 *)key, seckeysize) && is_old_mk) + util_print_indented("\nWARNING: The secure volume key is " + "currently enciphered with the OLD " + "master key. To mitigate the danger of " +@@ -2195,7 +2201,8 @@ static int command_setkey(void) + goto out; + } + +- if (is_old_mk) { ++ if (is_secure_key(newkey, newkey_size - integrity_keysize) && ++ is_old_mk) { + util_asprintf(&msg, "The secure key in file '%s' is " + "enciphered with the master key in the OLD " + "master key register. Do you want to set this " +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index 316db5f..b44eadf 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -209,7 +209,9 @@ It also displays the attributes of the secure key, such as key sizes, whether + it is a secure key that can be used for the XTS cipher mode, the master key + register (CURRENT or OLD) with which the secure key is enciphered, and other key + attributes. For further information about master key registers, see the +-\fBreencipher\fP command. ++\fBreencipher\fP command. Keys of type \fBPVSECRET\-AES\fP do not use a ++cryptographic adapter, thus no master key information is displayed for such ++keys. + .PP + The secure key can either be contained in a file in the file system, or in a + secure key repository. To validate a secure key contained in a file, specify +@@ -1599,8 +1601,8 @@ This option is only used for secure keys contained in the secure key repository. + .TP + .BR \-K ", " \-\-key\-type\~\fItype\fP + Specifies the key type of the secure key. Possible values are +-\fBCCA\-AESDATA\fP, \fBCCA\-AESCIPHER\fP, and \fBEP11\-AES\fP. Only keys with +-the specified key type are listed. ++\fBCCA\-AESDATA\fP, \fBCCA\-AESCIPHER\fP, \fBEP11\-AES\fP, and ++\fBPVSECRET\-AES\fP. Only keys with the specified key type are listed. + This option is only used for secure keys contained in the secure key repository. + .TP + .BR \-L ", " \-\-local\fP +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 90b4610..39a527c 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -558,9 +558,10 @@ static struct util_opt opt_vec[] = { + .option = { "key-type", required_argument, NULL, 'K'}, + .argument = "type", + .desc = "The type of the key. Possible values are '" +- KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' " +- "and '"KEY_TYPE_EP11_AES"'. Use this option to list " +- "all keys with the specified key type.", ++ KEY_TYPE_CCA_AESDATA "', '" KEY_TYPE_CCA_AESCIPHER ++ "', '" KEY_TYPE_EP11_AES "', and '" ++ KEY_TYPE_PVSECRET_AES "'. Use this option to list all " ++ "keys with the specified key type.", + .command = COMMAND_LIST, + }, + { +@@ -2345,13 +2346,16 @@ static int command_validate_file(void) + goto out; + } + +- rc = get_master_key_verification_pattern(secure_key, secure_key_size, +- mkvp, g.verbose); +- if (rc != 0) { +- warnx("Failed to get the master key verification pattern: %s", +- strerror(-rc)); +- rc = EXIT_FAILURE; +- goto out; ++ if (is_secure_key(secure_key, secure_key_size)) { ++ rc = get_master_key_verification_pattern(secure_key, ++ secure_key_size, ++ mkvp, g.verbose); ++ if (rc != 0) { ++ warnx("Failed to get the master key verification " ++ "pattern: %s", strerror(-rc)); ++ rc = EXIT_FAILURE; ++ goto out; ++ } + } + + key_type = get_key_type(secure_key, secure_key_size); +@@ -2363,25 +2367,30 @@ static int command_validate_file(void) + printf(" Clear key size: %lu bits\n", clear_key_size); + printf(" XTS type key: %s\n", + is_xts_key(secure_key, secure_key_size) ? "Yes" : "No"); +- printf(" Enciphered with: %s master key (MKVP: %s)\n", +- is_old_mk ? "OLD" : "CURRENT", +- printable_mkvp(get_card_type_for_keytype(key_type), mkvp)); ++ if (is_secure_key(secure_key, secure_key_size)) { ++ printf(" Enciphered with: %s master key (MKVP: %s)\n", ++ is_old_mk ? "OLD" : "CURRENT", ++ printable_mkvp(get_card_type_for_keytype(key_type), ++ mkvp)); ++ } + printf(" Verification pattern: %.*s\n", VERIFICATION_PATTERN_LEN / 2, + vp); + printf(" %.*s\n", VERIFICATION_PATTERN_LEN / 2, + &vp[VERIFICATION_PATTERN_LEN / 2]); + +- rc = cross_check_apqns(NULL, mkvp, +- get_min_card_level_for_keytype(key_type), +- get_min_fw_version_for_keytype(key_type), +- get_card_type_for_keytype(key_type), +- true, g.verbose); +- if (rc == -EINVAL) +- return EXIT_FAILURE; +- if (rc != 0 && rc != -ENOTSUP) { +- warnx("Your master key setup is improper"); +- rc = EXIT_FAILURE; +- goto out; ++ if (is_secure_key(secure_key, secure_key_size)) { ++ rc = cross_check_apqns(NULL, mkvp, ++ get_min_card_level_for_keytype(key_type), ++ get_min_fw_version_for_keytype(key_type), ++ get_card_type_for_keytype(key_type), ++ true, g.verbose); ++ if (rc == -EINVAL) ++ return EXIT_FAILURE; ++ if (rc != 0 && rc != -ENOTSUP) { ++ warnx("Your master key setup is improper"); ++ rc = EXIT_FAILURE; ++ goto out; ++ } + } + + out: +-- +2.47.1 + diff --git a/s390utils.spec b/s390utils.spec index d6b964e..702386d 100644 --- a/s390utils.spec +++ b/s390utils.spec @@ -14,7 +14,7 @@ Name: s390utils Summary: Utilities and daemons for IBM z Systems -Version: 2.35.0 +Version: 2.36.0 Release: 1%{?dist} Epoch: 2 License: MIT @@ -47,7 +47,7 @@ Patch0: s390-tools-zipl-invert-script-options.patch Patch1: s390-tools-zipl-blscfg-rpm-nvr-sort.patch # upstream fixes/updates -#Patch100: s390utils-%%{version}-rhel.patch +Patch100: s390utils-%{version}-rhel.patch # https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval ExcludeArch: %{ix86} @@ -121,7 +121,7 @@ be used together with the zSeries (s390) Linux kernel and device drivers. %patch -P 1 -p1 -b .blscfg-rpm-nvr-sort # upstream fixes/updates -#%%patch -P 100 -p1 +%patch -P 100 -p1 # remove --strip from install find . -name Makefile | xargs sed -i 's/$(INSTALL) -s/$(INSTALL)/g' @@ -141,6 +141,7 @@ rm -rf ./rust/Cargo.lock %build make \ CFLAGS="%{build_cflags}" CXXFLAGS="%{build_cxxflags}" LDFLAGS="%{build_ldflags}" \ + RUSTFLAGS="%{build_rustflags}" \ %if %{without rust} HAVE_CARGO=0 \ %endif @@ -156,6 +157,7 @@ make \ %install make install \ + RUSTFLAGS="%{build_rustflags}" \ %if %{without rust} HAVE_CARGO=0 \ %endif @@ -253,25 +255,33 @@ touch %{buildroot}%{_sysconfdir}/zipl.conf %{_bindir}/pvattest %{_bindir}/pvextract-hdr %if %{with rust} +%{_bindir}/pvimg %{_bindir}/pvsecret %endif %{_mandir}/man1/genprotimg.1* %{_mandir}/man1/pvattest.1* +%{_mandir}/man1/pvattest-check.1* %{_mandir}/man1/pvattest-create.1* %{_mandir}/man1/pvattest-perform.1* %{_mandir}/man1/pvattest-verify.1* %if %{with rust} +%{_mandir}/man1/pvimg.1* +%{_mandir}/man1/pvimg-create.1* +%{_mandir}/man1/pvimg-info.1* +%{_mandir}/man1/pvimg-test.1* %{_mandir}/man1/pvsecret-add.1* %{_mandir}/man1/pvsecret-create-association.1* %{_mandir}/man1/pvsecret-create-meta.1* +%{_mandir}/man1/pvsecret-create-retrievable.1* %{_mandir}/man1/pvsecret-create.1* %{_mandir}/man1/pvsecret-list.1* %{_mandir}/man1/pvsecret-lock.1* +%{_mandir}/man1/pvsecret-retrieve.1* %{_mandir}/man1/pvsecret-verify.1* %{_mandir}/man1/pvsecret.1* %endif %dir %{_datadir}/s390-tools -%{_datadir}/s390-tools/genprotimg/ +%{_datadir}/s390-tools/pvimg/ # # enf of multi-arch section @@ -397,6 +407,7 @@ BuildRequires: cryptsetup-devel >= 2.0.3 BuildRequires: json-c-devel BuildRequires: rpm-devel BuildRequires: libxml2-devel +BuildRequires: libnl3-devel %description base @@ -571,6 +582,7 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_sbindir}/lstape %{_sbindir}/lszcrypt %{_sbindir}/lszfcp +%{_sbindir}/opticsmon %{_sbindir}/pai %{_sbindir}/qetharp %{_sbindir}/qethconf @@ -591,8 +603,11 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_sbindir}/zipl-switch-to-blscfg %{_sbindir}/znetconf %{_sbindir}/zpcictl +%{_bindir}/cpacfinfo %{_bindir}/dump2tar %{_bindir}/genprotimg +%{_bindir}/pvapconfig +%{_bindir}/pvimg %{_bindir}/mk-s390image %if %{with rust} %{_bindir}/pvapconfig @@ -605,6 +620,7 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_bindir}/zkey %{_bindir}/zkey-cryptsetup %{_unitdir}/dumpconf.service +%{_unitdir}/opticsmon.service %ghost %config(noreplace) %{_sysconfdir}/zipl.conf %config(noreplace) %{_sysconfdir}/sysconfig/dumpconf %{_sysconfdir}/mdevctl.d/* @@ -624,22 +640,30 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %dir %{_libdir}/zkey %{_libdir}/zkey/zkey-ekmfweb.so %{_libdir}/zkey/zkey-kmip.so +%{_mandir}/man1/cpacfinfo.1* %{_mandir}/man1/dump2tar.1* %{_mandir}/man1/genprotimg.1* %if %{with rust} %{_mandir}/man1/pvapconfig.1* %endif %{_mandir}/man1/pvattest.1* +%{_mandir}/man1/pvattest-check.1* %{_mandir}/man1/pvattest-create.1* %{_mandir}/man1/pvattest-perform.1* %{_mandir}/man1/pvattest-verify.1* %if %{with rust} +%{_mandir}/man1/pvimg.1* +%{_mandir}/man1/pvimg-create.1* +%{_mandir}/man1/pvimg-info.1* +%{_mandir}/man1/pvimg-test.1* %{_mandir}/man1/pvsecret-add.1* %{_mandir}/man1/pvsecret-create-association.1* %{_mandir}/man1/pvsecret-create-meta.1* +%{_mandir}/man1/pvsecret-create-retrievable.1* %{_mandir}/man1/pvsecret-create.1* %{_mandir}/man1/pvsecret-list.1* %{_mandir}/man1/pvsecret-lock.1* +%{_mandir}/man1/pvsecret-retrieve.1* %{_mandir}/man1/pvsecret-verify.1* %{_mandir}/man1/pvsecret.1* %endif @@ -677,6 +701,7 @@ getent group zkeyadm > /dev/null || groupadd -r zkeyadm %{_mandir}/man8/lstape.8* %{_mandir}/man8/lszcrypt.8* %{_mandir}/man8/lszfcp.8* +%{_mandir}/man8/opticsmon.8* %{_mandir}/man8/pai.8* %{_mandir}/man8/qetharp.8* %{_mandir}/man8/qethconf.8* @@ -715,7 +740,7 @@ BuildArch: noarch %files se-data %dir %{_datadir}/s390-tools -%{_datadir}/s390-tools/genprotimg/ +%{_datadir}/s390-tools/pvimg/ # # *********************** s390-tools osasnmpd package *********************** @@ -1060,6 +1085,21 @@ User-space development files for the s390/s390x architecture. %changelog +* Fri Jan 17 2025 Dan Horák - 2:2.36.0-1 +- rebased to 2.36.0 (RHEL-50026) +- cpacfinfo: new tool to provide CPACF information (RHEL-50020) +- chpstat: Additional Channel Measurements (RHEL-50023) +- zPCI: Optics Monitoring for PF Access Mode (RHEL-50024) +- KVM: Support extended attestation for Secure Execution (RHEL-50009) +- KVM: genprotimg: validate if SE image can run on particular host(s) (RHEL-50008) +- Vertical CPU Polarization Support Stage 2 (RHEL-64705) +- KVM: Support unencrypted SE images by exposing resp. SE header flag (RHEL-70853) +- KVM: Rewrite genprotimg in Rust (RHEL-72023) +- Provide genprotimg / pvimg info command to display encrypted & unencrypted SE image information (RHEL-71819) +- KVM: Support retrievable secrets in Secure Execution guests (RHEL-50019) +- zkey key support for dm-crypt with XTS keys (RHEL-50010) +- Resolves: RHEL-50026 RHEL-50020 RHEL-50023 RHEL-50024 RHEL-50009 RHEL-50008 RHEL-64705 RHEL-70853 RHEL-72023 RHEL-71819 RHEL-50019 RHEL-50010 + * Tue Oct 08 2024 Dan Horák - 2:2.35.0-1 - rebased to 2.35.0 (RHEL-50026) - cpacfstats: support MSA 10 and MSA 11 (RHEL-50030) diff --git a/sources b/sources index aa65d49..444acfa 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (s390-tools-2.35.0.tar.gz) = 1ee6e7dc5a0c504bb23bde00e5e6a2ed333e7712677056075caf68a02eb222cd9c00d16fcf17e0a7c9f89609901d343b55953b0036c0d2cab35830ea8eb53fea -SHA512 (s390-tools-2.35.0-rust-vendor.tar.xz) = 116adeac121c56d7d5bc37b3a7972a78e1ce86fd53f919d293d4bfe92999449ce6f30a592fdb3f4d48fda64817c9b19587a038bf832c7b745a715b9b469e1ee4 +SHA512 (s390-tools-2.36.0.tar.gz) = 5704dcbfc88972ff74b5d81acf549e719b90f2ce60b66ca6935c16c0b266ca6728adca72133aa51e1a5acfbabfd6f121b7f9b5023be498c5a203217adc4f5c46 +SHA512 (s390-tools-2.36.0-rust-vendor.tar.xz) = 2e4f53b64e71307e79a3a433c7bd2a6304b0c4e6e34cb84c57e459e86ec25c14b64eab870f0081f2f31d5b8003b071e8f45cf639111a1e759f0dfa070a96d765