s390utils/s390utils-2.36.0-rhel.patch
2025-01-30 10:30:11 +01:00

9768 lines
328 KiB
Diff

From c0c76b5735daa9690be297335d21181a70eaaded Mon Sep 17 00:00:00 2001
From: Eduard Shishkin <edward6@linux.ibm.com>
Date: Mon, 16 Dec 2024 13:55:52 +0100
Subject: [PATCH 01/32] 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 <edward6@linux.ibm.com>
Acked-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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.48.1
From cd32b1c9ebc8d7efa955efd15ba7261e7b7fd083 Mon Sep 17 00:00:00 2001
From: Niklas Schnelle <schnelle@linux.ibm.com>
Date: Fri, 6 Dec 2024 15:28:08 +0100
Subject: [PATCH 02/32] 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 <pasic@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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.48.1
From 95f31c8471f9e6f353afca7da42bc3042472aa5d Mon Sep 17 00:00:00 2001
From: Niklas Schnelle <schnelle@linux.ibm.com>
Date: Mon, 9 Dec 2024 15:08:03 +0100
Subject: [PATCH 03/32] 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 <pasic@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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.48.1
From 95277a7eac2391196e39548221c551b79dcbd77a Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Wed, 11 Dec 2024 19:25:59 +0100
Subject: [PATCH 04/32] 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 <seiden@linux.ibm.com>
Acked-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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<bool>,
+
+ /// 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<bool>,
+
+ /// 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<bool>,
}
#[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<OwnExitCode> {
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.48.1
From 0bc601307846a7cdd667355dbae21c877d603e50 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Thu, 12 Dec 2024 20:19:55 +0100
Subject: [PATCH 05/32] 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 <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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 <FILE>
+\-i, \-\-kernel, \-\-image <FILE>
.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 <FILE>
+\-i, \-\-kernel, \-\-image <FILE>
.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.48.1
From 840452df23ebe54db82e8d0cf94352bddb758ed4 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Thu, 12 Dec 2024 20:19:56 +0100
Subject: [PATCH 06/32] rust/pvimg: Add '--cck <FILE>' 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 <FILE>' 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 <buendgen@de.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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 <FILE>
+\-\-cck, \-\-comm\-key <FILE>
.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 <FILE>
+\-\-cck, \-\-comm\-key <FILE>
.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<bool>,
/// Disable Secure Execution guest dump support (default).
@@ -105,9 +105,9 @@ pub struct CreateBootImageLegacyFlags {
pub disable_dump: Option<bool>,
/// 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<bool>,
/// 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<PathBuf>,
+ #[arg(long, value_name = "FILE", visible_alias = "comm-key")]
+ pub cck: Option<PathBuf>,
#[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<OwnExitCode> {
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.48.1
From 2a9d164010c0eaa6098083062ac0cdcb9be84b78 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Wed, 8 Jan 2025 12:33:05 +0100
Subject: [PATCH 07/32] 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 <FILE>' command line option and make '--comm-key' an alias")
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Nico Boehr <nrb@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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<PathBuf>,
--
2.48.1
From fa2dcf81a6c002192f351040ff68f8d60370e93c Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Fri, 6 Dec 2024 20:45:36 +0100
Subject: [PATCH 08/32] 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 <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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.48.1
From 979b4bad1653a7b897a43e8fe7ee393de77fc4e4 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Tue, 17 Dec 2024 12:20:30 +0100
Subject: [PATCH 09/32] 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 <FILE>' 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 <FILE>
--format json <SE_IMG>'. While updating the manpages, add missing hyphen
escapes in the manpages.
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Acked-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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 <FILE>
+.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 <FILE>
+.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 <FILE>
.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<PathBuf>,
}
@@ -332,6 +335,14 @@ pub struct CreateBootImageArgs {
#[arg(long, value_name = "FILE", visible_alias = "comm-key")]
pub cck: Option<PathBuf>,
+ /// 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<PathBuf>,
+
#[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<PathBuf>,
- /// 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<PathBuf>,
-
/// 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<UserProvidedKeys> {
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<OwnExitCode> {
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.48.1
From 9155c5e49a8fff05d479ddc81d8d25e819278803 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Wed, 18 Dec 2024 13:41:13 +0100
Subject: [PATCH 10/32] 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 <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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<P: AsRef<Path>>(template: P) -> Result<PathBuf, std::io::Error> {
// 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.48.1
From e412cecd6ad9be261b17cf9ad64b786c7da58e4f Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Tue, 17 Dec 2024 11:58:01 +0100
Subject: [PATCH 11/32] 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 <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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<u8> {
- [serialize_to_bytes(&self.common).unwrap(), self.data.aad()].concat()
+ fn aad(&self) -> Result<Vec<u8>> {
+ Ok([serialize_to_bytes(&self.common)?, self.data.aad()?].concat())
}
fn data(&self) -> Vec<u8> {
@@ -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<u8> {
- let data_aad = self.data.aad();
+ fn aad(&self) -> Result<Vec<u8>> {
+ 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<Vec<u8>> {
+ fn data(&self) -> Result<Confidential<Vec<u8>>> {
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<u8> {
- serialize_to_bytes(&self.aad).unwrap()
+ fn aad(&self) -> Result<Vec<u8>> {
+ serialize_to_bytes(&self.aad)
}
fn data(&self) -> Vec<u8> {
@@ -508,12 +521,12 @@ impl AeadDataTrait for SeHdrBinV1 {
}
impl AeadPlainDataTrait for SeHdrDataV1 {
- fn aad(&self) -> Vec<u8> {
- serialize_to_bytes(&self.aad).unwrap()
+ fn aad(&self) -> Result<Vec<u8>> {
+ serialize_to_bytes(&self.aad)
}
- fn data(&self) -> Confidential<Vec<u8>> {
- serialize_to_bytes(self.data.value()).unwrap().into()
+ fn data(&self) -> Result<Confidential<Vec<u8>>> {
+ Ok(serialize_to_bytes(self.data.value())?.into())
}
fn tag(&self) -> Vec<u8> {
@@ -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<u8>;
+ fn aad(&self) -> Result<Vec<u8>>;
/// Returns the encrypted data.
fn data(&self) -> Vec<u8>;
@@ -47,10 +47,10 @@ pub trait AeadDataTrait {
#[enum_dispatch]
pub trait AeadPlainDataTrait {
/// Returns the authenticated associated data.
- fn aad(&self) -> Vec<u8>;
+ fn aad(&self) -> Result<Vec<u8>>;
/// Returns the unencrypted data.
- fn data(&self) -> Confidential<Vec<u8>>;
+ fn data(&self) -> Result<Confidential<Vec<u8>>>;
/// Returns the tag data.
fn tag(&self) -> Vec<u8>;
@@ -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.48.1
From 1617c8482e0846a3afb4af2772011e4621442f58 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Tue, 17 Dec 2024 18:13:31 +0100
Subject: [PATCH 12/32] 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 <buendgen@de.ibm.com>
Acked-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit 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 <FILE>
+\-\-hdr\-key <FILE>
.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<PathBuf>,
+ #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, alias = "key")]
+ pub hdr_key: Option<PathBuf>,
}
#[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<OwnExitCode> {
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.48.1
From 78b388c1613724270ec34cef28b7be181f5e0db5 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Thu, 18 Jul 2024 10:55:45 +0200
Subject: [PATCH 13/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<P: AsRef<Path>>(
+ 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<P: AsRef<Path>>(
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.48.1
From a5d58d0e6fd5d90ef12956ee7354a3f43d17f2ea Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 10:46:29 +0100
Subject: [PATCH 14/32] 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 <marc@linux.ibm.com>
Acked-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<Self, ErrorStack> {
+ 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<u8> {
+ 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<BioMemSlice<'a>, 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<MemBioSlice<'a>, 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<Vec<X509Crl>, 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<Self> {
+ 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<AsciiCString>,
+ data: &'d [u8],
+}
+
+impl<'d> InnerPem<'d> {
+ fn new(name: &str, header: Option<String>, data: &'d [u8]) -> Result<Self> {
+ 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<Vec<u8>> {
+ 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 <name>-----
+///<header>
+///
+///<Base64 formatted binary data>
+///-----END <name>-----
+
+#[derive(Debug)]
+pub struct Pem {
+ pem: Confidential<String>,
+}
+
+#[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<D, H>(name: &str, header: H, data: D) -> Result<Self>
+ where
+ D: AsRef<[u8]>,
+ H: Into<Option<String>>,
+ {
+ 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<Vec<u8>> {
+ 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.48.1
From 0f2055ca030a868e43e6076cba5cc9cc1277241c Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 20 Feb 2024 14:50:47 +0100
Subject: [PATCH 15/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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.48.1
From ad6a20789e0b02bdfe1d7a685b897639fc298c2f Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 11:56:57 +0100
Subject: [PATCH 16/32] 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 <schlameuss@linux.ibm.com>
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<T, E = Error> = std::result::Result<T, E>;
@@ -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<Vec<u8>>,
+}
+
+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<Self> {
+ 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<Vec<u8>> {
+ 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<SecretEntry> for RetrieveCmd {
+ type Error = Error;
+
+ fn try_from(entry: SecretEntry) -> Result<Self> {
+ 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<BigEndian> {
+ &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.48.1
From cf2fe8bed95ca8b6513d02a85b83504a68a2584b Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 12:16:44 +0100
Subject: [PATCH 17/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<Self> {
+ 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<Self> {
+ 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<Self> {
+ 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<u8>) -> Vec<u8> {
+ 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: <secret type nb> (String name)
+impl Serialize for RetrievableSecret {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<D>(de: D) -> Result<Self, D::Error>
+ 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: `<number> (String name)` number in [3,10]|[17,21]",
+ )
+ }
+ fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+ 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<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error>
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<U16<BigEndian>> for ListableSecretType {
- fn from(value: U16<BigEndian>) -> Self {
- match value.get() {
+impl<O: ByteOrder> From<U16<O>> for ListableSecretType {
+ fn from(value: U16<O>) -> Self {
+ value.get().into()
+ }
+}
+
+impl From<u16> 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<ListableSecretType> for U16<BigEndian> {
+impl<O: ByteOrder> From<ListableSecretType> for U16<O> {
+ fn from(value: ListableSecretType) -> Self {
+ Self::new(value.into())
+ }
+}
+
+impl From<ListableSecretType> 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.48.1
From 67480b7219b711226352257bd2690448d9521c06 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 12:19:22 +0100
Subject: [PATCH 18/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<usize> {
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<Vec<u8>>,
+ },
+}
+
+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<Self> {
+ 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<SecretId> {
+ 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<Option<[u8; ASSOC_SECRET_SIZE]>>,
{
- 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<Vec<u8>>, 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<Vec<u8>>, 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<Vec<u8>>, 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<Vec<u8>>, 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<Private>, 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<SecretId> {
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<Vec<u8>>);
+
+fn extend_to_multiple(mut key: Vec<u8>, multiple: usize) -> Confidential<Vec<u8>> {
+ 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<BigEndian> | payload (0-8190) bytes
+/// ```
+fn plaintext(inp: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
+ 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<BigEndian> = (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<Vec<u8>>) -> Result<RetrKeyInfo> {
+ 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<Vec<u8>>) -> Result<RetrKeyInfo> {
+ 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<Vec<u8>>) -> Result<RetrKeyInfo> {
+ 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<Private>) -> Result<RetrKeyInfo> {
+ 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<BigEndian> = 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<Self> {
- 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: <name>
+///
+///<protected key in base64>
+///-----END IBM PROTECTED KEY-----
+/// ```
+#[derive(Debug, PartialEq, Eq)]
+pub struct IbmProtectedKey {
+ kind: ListableSecretType,
+ key: Confidential<Vec<u8>>,
+}
+
+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<Vec<u8>> {
+ 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> {
+ Pem::new(
+ "IBM PROTECTED KEY",
+ format!("kind: {}", self.kind),
+ self.key.value(),
+ )
+ }
+
+ fn new<K>(kind: ListableSecretType, key: K) -> Self
+ where
+ K: Into<Confidential<Vec<u8>>>,
+ {
+ Self {
+ kind,
+ key: key.into(),
+ }
+ }
+}
+
+impl From<RetrieveCmd> 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::<BigEndian>::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<Vec<u8>>),
+ /// 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<Vec<u8>> {
+ 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<Pem> {
+ 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.48.1
From ce872d9b3104209d2a8bee32fcc1b1f2f2e0e2ad Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Fri, 13 Dec 2024 15:04:02 +0100
Subject: [PATCH 19/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<String>,
- /// 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<String>,
},
}
@@ -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<CreateSecretOpt>),
- /// 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.48.1
From 1c97c4569b0521896927547d0c9581d7808f3905 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Mon, 19 Feb 2024 15:15:16 +0100
Subject: [PATCH 20/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<String>,
},
+
+ /// 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<P, D>(path: &P, data: D, ctx: &str) -> pv::Result<()>
where
P: AsRef<Path>,
@@ -32,6 +33,23 @@ where
Ok(())
}
+fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Result<GuestSecret> {
+ 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<AddSecretRequest> {
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<AddSecretRequest> {
.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<P: AsRef<Path>>(
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<SecretList> {
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<RetrievedSecret> {
+ 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.48.1
From bb6654ed02453ccd5bb87ca9938f06214351fe22 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Wed, 12 Jun 2024 16:23:31 +0200
Subject: [PATCH 21/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<BigEndian>,
+ #[serde(serialize_with = "ser_u16")]
+ total_num_secrets: U16<BigEndian>,
+ #[serde(skip)]
+ next_secret_idx: U16<BigEndian>,
+ #[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<SecretEntry>,
}
@@ -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<SecretEntry>) -> 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<SecretEntry>) -> 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<T: Write>(&self, w: &mut T) -> Result<()> {
- let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
- w.write_u16::<BigEndian>(num_s)?;
- w.write_u16::<BigEndian>(
- 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: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
- let num_s = r.read_u16::<BigEndian>()?;
- let total_num_secrets = r.read_u16::<BigEndian>()? as usize;
- let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
- r.seek(std::io::SeekFrom::Current(12))?; // skip reserved bytes
+ let mut buf = [0u8; size_of::<SecretListHdr>()];
+ 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<ListCmd> 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.48.1
From 6f5bd4d347f81b077b838ec907bd53d061680392 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Wed, 12 Jun 2024 16:35:15 +0200
Subject: [PATCH 22/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<SecretList> {
- 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.48.1
From 0036b024950309a2953a3375daaa3b046c360a9b Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Mon, 5 Aug 2024 09:34:47 +0200
Subject: [PATCH 23/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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<SecretId> {
+ /// Hashes the name with sha256
+ pub fn name_to_id(name: &str) -> Result<SecretId> {
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<Private>, 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<BigEndian>,
@@ -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<SecretEntry> {
+ 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<T: Write>(&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<E>(self, s: &str) -> Result<Self::Value, E>
@@ -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<String>,
+
+ /// 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<PKey<Private>> {
fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
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<AddSecretRequest> {
};
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<RetrievedSecret> {
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.48.1
From cd2f4deb6e180adf0f09afe808a05bab5e4526c3 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 22 Oct 2024 17:53:17 +0200
Subject: [PATCH 24/32] 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 <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(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**
<ul>
-Perform an add-secret request (s390x only)
+Submit an add-secret request to the Ultravisor (s390x only)
</ul>
- **lock**
@@ -50,23 +50,34 @@ List all ultravisor secrets (s390x only)
Verify that an add-secret request is sane
</ul>
+- **retrieve**
+<ul>
+Retrieve a secret from the UV secret store (s390x only)
+</ul>
+
## Options
`-v`, `--verbose`
<ul>
-Provide more detailed output
+Provide more detailed output.
+</ul>
+
+
+`-q`, `--quiet`
+<ul>
+Provide less output.
</ul>
`--version`
<ul>
-Print version information and exit
+Print version information and exit.
</ul>
`-h`, `--help`
<ul>
-Print help
+Print help (see a summary with '-h').
</ul>
@@ -95,12 +106,17 @@ Create a meta secret
Create an association secret
</ul>
+- **retrievable**
+<ul>
+Create a retrievable secret
+</ul>
+
### Options
`-k`, `--host-key-document <FILE>`
<ul>
Use FILE as a host-key document. Can be specified multiple times and must be
-used at least once.
+specified at least once.
</ul>
@@ -114,7 +130,7 @@ the host-key document beforehand.
`-C`, `--cert <FILE>`
<ul>
-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 <FILE>`
<ul>
-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.
</ul>
`--offline`
<ul>
-Make no attempt to download CRLs
+Make no attempt to download CRLs.
</ul>
@@ -146,8 +162,7 @@ specified certificate.
`--hdr <FILE>`
<ul>
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.
</ul>
@@ -162,7 +177,7 @@ behavior.
`-o`, `--output <FILE>`
<ul>
-Write the generated request to FILE
+Write the generated request to FILE.
</ul>
@@ -209,15 +224,15 @@ the request.
`--flags <FLAGS>`
<ul>
-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.
</ul>
`--user-data <FILE>`
<ul>
-Use the content of FILE as user-data. Passes user data defined in <FILE> 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 <FILE>`
<ul>
Use the content of FILE as user signing key. Adds a signature calculated from
-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.
+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.
+</ul>
+
+
+`--use-name`
+<ul>
+Do not hash the name, use it directly as secret ID. Ignored for meta-secrets.
</ul>
`-h`, `--help`
<ul>
-Print help
+Print help (see a summary with '-h').
</ul>
@@ -265,14 +286,15 @@ of secrets.
`pvsecret create association [OPTIONS] <NAME>`
#### 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
`<NAME>`
<ul>
-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
+`_`.
</ul>
@@ -284,24 +306,76 @@ Print the hashed name to stdout. The hashed name is not written to `NAME.yaml`
</ul>
-`--input-secret <FILE>`
+`--input-secret <SECRET-FILE>`
<ul>
Path from which to read the plaintext secret. Uses a random secret if not
-specified
+specified.
+</ul>
+
+
+`--output-secret <SECRET-FILE>`
+<ul>
+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.
+</ul>
+
+
+`-h`, `--help`
+<ul>
+Print help (see a summary with '-h').
+</ul>
+
+
+### pvsecret create retrievable
+#### Synopsis
+`pvsecret create retrievable [OPTIONS] --secret <SECRET-FILE> --type <TYPE> <NAME>`
+`pvsecret create retr [OPTIONS] --secret <SECRET-FILE> --type <TYPE> <NAME>`
+#### 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
+
+`<NAME>`
+<ul>
+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 `_`.
+</ul>
+
+
+#### Options
+
+`--stdout`
+<ul>
+Print the hashed name to stdout. The hashed name is not written to `NAME.yaml`
+</ul>
+
+
+`--secret <SECRET-FILE>`
+<ul>
+Use SECRET-FILE as retrievable secret.
</ul>
-`--output-secret <FILE>`
+`--type <TYPE>`
<ul>
-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.
</ul>
`-h`, `--help`
<ul>
-Print help
+Print help (see a summary with '-h').
</ul>
@@ -309,13 +383,14 @@ Print help
### Synopsis
`pvsecret add <FILE>`
### 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
`<FILE>`
<ul>
-Specify the request to be sent
+Specify the request to be sent.
</ul>
@@ -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.
`<FILE>`
<ul>
-Store the result in FILE
+Store the result in FILE.
Default value: '-'
</ul>
@@ -348,18 +423,18 @@ Store the result in FILE
`--format <FORMAT>`
<ul>
-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.
</ul>
`-h`, `--help`
<ul>
-Print help
+Print help (see a summary with '-h').
</ul>
@@ -407,7 +482,7 @@ The verification process works as follows:
`<FILE>`
<ul>
-Specify the request to be checked
+Specify the request to be checked.
</ul>
@@ -435,5 +510,58 @@ contains this user-data with padded zeros if available.
`-h`, `--help`
<ul>
-Print help
+Print help (see a summary with '-h').
+</ul>
+
+
+## pvsecret retrieve
+### Synopsis
+`pvsecret retrieve [OPTIONS] <ID>`
+`pvsecret retr [OPTIONS] <ID>`
+### Description
+Retrieve a secret from the UV secret store (s390x only)
+### Arguments
+
+`<ID>`
+<ul>
+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.
+</ul>
+
+
+### Options
+
+`-o`, `--output <FILE>`
+<ul>
+Specify the output path to place the secret value.
+ Default value: '-'
+</ul>
+
+
+`--inform <INFORM>`
+<ul>
+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.
+</ul>
+
+
+`--outform <OUTFORM>`
+<ul>
+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.
+</ul>
+
+
+`-h`, `--help`
+<ul>
+Print help (see a summary with '-h').
</ul>
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 <FILE>
.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] <NAME>
.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
<NAME>
.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 <FILE>
+\-\-input\-secret <SECRET-FILE>
.RS 4
Path from which to read the plaintext secret. Uses a random secret if not
specified.
.RE
.RE
.PP
-\-\-output-secret <FILE>
+\-\-output\-secret <SECRET-FILE>
.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 <SECRET-FILE> --type <TYPE> <NAME>
+pvsecret create retr [OPTIONS] --secret <SECRET-FILE> --type <TYPE> <NAME>
+.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
+<NAME>
+.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 <SECRET-FILE>
+.RS 4
+Use SECRET\-FILE as retrievable secret.
+.RE
+.RE
+.PP
+\-\-type <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 <FILE>
+\-k, \-\-host\-key\-document <FILE>
.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 <FILE>
.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 <FILE>
.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>
+\-\-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 <FILE>
.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 <FILE>
+\-\-extension\-secret <FILE>
.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 <FILE>
.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 <HEXSTRING>
+\-\-cuid\-hex <HEXSTRING>
.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 <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 <FILE>
+\-\-user\-data <FILE>
.RS 4
-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
+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 <FILE>
+\-\-user\-sign\-key <FILE>
.RS 4
Use the content of FILE as user signing key. Adds a signature calculated from
-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
+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
<FILE>
@@ -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] <ID>
+pvsecret retr [OPTIONS] <ID>
+.fam C
+.fi
+.SH DESCRIPTION
+Retrieve a secret from the UV secret store (s390x only)
+.SH OPTIONS
+.PP
+<ID>
+.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 <FILE>
+.RS 4
+Specify the output path to place the secret value.
+[default: '-']
+.RE
+.RE
+.PP
+\-\-inform <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 <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 <FILE>
+\-\-user\-cert <FILE>
.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 <FILE>
.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.48.1
From 2f531935c1dab94336e6e197d9fdb9bcb54ff9ad Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Thu, 15 Feb 2024 09:08:43 +0100
Subject: [PATCH 25/32] 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#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.48.1
From 99bd7f51d41763cc7135169a90f12f4a7df3d3f2 Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Thu, 15 Feb 2024 11:22:04 +0100
Subject: [PATCH 26/32] 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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 <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
@@ -17,14 +18,60 @@
#include <sys/types.h>
#include <unistd.h>
+#include <openssl/sha.h>
+
#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.48.1
From 3b4fce7cdd079732235da5e01033497752963360 Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Thu, 15 Feb 2024 16:56:04 +0100
Subject: [PATCH 27/32] 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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.48.1
From 77a419bf5b575f09c7958bc5656cf1e0039be115 Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Thu, 15 Feb 2024 15:14:04 +0100
Subject: [PATCH 28/32] 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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.48.1
From b20ebd7b65190b261aee21fcfcbd659d5951f9f4 Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Mon, 19 Feb 2024 10:21:06 +0100
Subject: [PATCH 29/32] 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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.48.1
From 9a1bfa5a9977eef50956d588b736392aa2242578 Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Mon, 19 Feb 2024 11:26:41 +0100
Subject: [PATCH 30/32] 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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.48.1
From 48ab37947ccda570e98aa046f2c28c11fdd59bdf Mon Sep 17 00:00:00 2001
From: Ingo Franzki <ifranzki@linux.ibm.com>
Date: Mon, 19 Feb 2024 10:25:54 +0100
Subject: [PATCH 31/32] 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
(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.48.1
From 5f6ea5bf9a1f22e2fe07267618072eb5182c5558 Mon Sep 17 00:00:00 2001
From: Jakob Naucke <naucke@linux.ibm.com>
Date: Wed, 15 Jan 2025 17:36:01 +0100
Subject: [PATCH 32/32] rust/pvimg: Fix flag parsing for allowing dump
(RHEL-76913)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Parsing of pvimg flags did not pick up allowing dumping correctly.
Fixes: f4cf4ae6ebb1 (rust: Add a new tool called 'pvimg')
Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jakob Naucke <naucke@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
(cherry picked from commit caaf2b2116235d282c2561f0bf6f62b0033c78c4)
---
rust/pvimg/src/cmd/create.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/pvimg/src/cmd/create.rs b/rust/pvimg/src/cmd/create.rs
index 3e2ca65..c9d3974 100644
--- a/rust/pvimg/src/cmd/create.rs
+++ b/rust/pvimg/src/cmd/create.rs
@@ -55,7 +55,7 @@ fn parse_flags(
.and(Some(PcfV1::all_disabled([PcfV1::AllowDumping]))),
lf.enable_dump
.filter(|x| *x)
- .and(Some(PcfV1::all_disabled([PcfV1::AllowDumping]))),
+ .and(Some(PcfV1::all_enabled([PcfV1::AllowDumping]))),
lf.disable_pckmo
.filter(|x| *x)
.and(Some(PcfV1::all_disabled([
--
2.48.1