mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03f7566436 | |||
| 177d8ae0dd | |||
| 08d21b24b4 | |||
| ed74e84862 | |||
| 0263637d9f | |||
| 1aa1558f91 | |||
| 29a1ef0023 | |||
| 39eb46763b | |||
| ce1464ad5f | |||
| c6d74eebfb | |||
| ad129057b5 | |||
| 5b8fdc2bec | |||
| 784cff18c3 | |||
| fb33935c9b | |||
| 175b336d32 | |||
| 9a5c9cd7f9 | |||
| 73c227fa9a | |||
| 1078e6d45f | |||
| 50808edb7c | |||
| 9dca2d3181 | |||
| fc853f3259 | |||
| 89577c2f8c | |||
| d6fc4921fb | |||
| cb9c2f993c | |||
| 637c8196a6 | |||
| c87d11abfe | |||
| 20fffb31d7 | |||
| 2d330cd45f | |||
| 618e22e38b | |||
| 173a055372 | |||
| 4670b108a6 | |||
| ebe56592c8 | |||
| 1746d2bb6a | |||
| 91123e5d45 | |||
| 0f0e8cf33b | |||
| 7586a1ad59 | |||
| 1da328921b | |||
| f96f09a5c9 | |||
| 4fd607ae5c | |||
| f1a11a01df | |||
| 2eceedeb11 | |||
| a9dbb8a1a2 | |||
| 78dd82d129 | |||
| 113e9ebfef | |||
| f9ef8a0106 | |||
| 2c63c69af4 | |||
| 8a4a4e0557 | |||
| e8550a4f8d | |||
| e7bc4a5d31 | |||
| 8b38bf37b0 | |||
| 3696d6bd08 | |||
| 6847b6a8cf | |||
| 1f339a9e7e | |||
| 905c3921af | |||
| e55e0e470a | |||
| 9d9fca6e72 |
+5
-3
@@ -102,9 +102,11 @@ lsstp/_lsstp
|
|||||||
lsstp/lsstp.bash
|
lsstp/lsstp.bash
|
||||||
mon_tools/mon_fsstatd
|
mon_tools/mon_fsstatd
|
||||||
mon_tools/mon_procd
|
mon_tools/mon_procd
|
||||||
opticsmon/opticsmon
|
zpcimon/zpcimon
|
||||||
opticsmon/_opticsmon
|
zpcimon/zpcimon.bash
|
||||||
opticsmon/opticsmon.bash
|
zpcimon/_zpcimon
|
||||||
|
zpcimon/opticsmon.bash
|
||||||
|
zpcimon/_opticsmon
|
||||||
osasnmpd/osasnmpd
|
osasnmpd/osasnmpd
|
||||||
qetharp/qetharp
|
qetharp/qetharp
|
||||||
qethqoat/qethqoat
|
qethqoat/qethqoat
|
||||||
|
|||||||
@@ -1,6 +1,32 @@
|
|||||||
Release history for s390-tools (MIT version)
|
Release history for s390-tools (MIT version)
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
|
* __v2.44.0 (2026-07-31)__
|
||||||
|
|
||||||
|
For Linux kernel version: 7.2
|
||||||
|
|
||||||
|
Changes of existing tools / libraries:
|
||||||
|
- create-sehdr: Enable quantum safe keys usage
|
||||||
|
- dbginfo.sh: Let zpcimon log both optical module and SMART data
|
||||||
|
- libutil/util_fmt: Add util_fmt_type_to_name()
|
||||||
|
- nvmemon: Skip SCLP on NVMes with non-IBM subsystem vendor ID
|
||||||
|
- opticsmon: zpcimon: Rename opticsmon to zpcimon
|
||||||
|
- pvattest: Enable quantum safe keys usage
|
||||||
|
- pvimg: Add '--flags <...>' and '--disable-flags <...>' option
|
||||||
|
- pvimg: Enable quantum safe keys usage
|
||||||
|
- pvsecret: Enable quantum safe keys usage
|
||||||
|
- pvverify: Enable quantum safe keys usage
|
||||||
|
- zmemtopo: Add CLI option to filter partitions by name
|
||||||
|
- zpcimon: Allow setting output format using --format CLI option
|
||||||
|
- zpcimon: Monitor for hotplug of NVMes to trigger SMART collection
|
||||||
|
|
||||||
|
Bug Fixes:
|
||||||
|
- opticsmon: Fix wrong size check for OPTICS_QSFP28_LOS_IMPLEMENTED_OFFSET
|
||||||
|
- opticsmon: Handle error return of ethtool_nl_connect()
|
||||||
|
- opticsmon: Fix error path free of struct optics in ethtool_nl_get_optics()
|
||||||
|
- opticsmon: Close epoll fd in monitor_wait_loop()
|
||||||
|
- opticsmon: Fix wrong JSON print for tx_fault
|
||||||
|
|
||||||
* __v2.43.1 (2026-07-22)__
|
* __v2.43.1 (2026-07-22)__
|
||||||
|
|
||||||
For Linux kernel version: 7.1
|
For Linux kernel version: 7.1
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
|
|||||||
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
|
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
|
||||||
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
|
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
|
||||||
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
|
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
|
||||||
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon \
|
lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust zpcimon \
|
||||||
zpwr zmemtopo
|
zpwr zmemtopo
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ build options:
|
|||||||
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
||||||
| openssl | `HAVE_OPENSSL` | zkey, libekmfweb, libkmipclient, |
|
| openssl | `HAVE_OPENSSL` | zkey, libekmfweb, libkmipclient, |
|
||||||
| | | zgetdump, rust/pvattest, rust/pvimg, |
|
| | | zgetdump, rust/pvattest, rust/pvimg, |
|
||||||
| | | zgetdump/pvsecret, opticsmon |
|
| | | zgetdump/pvsecret, zpcimon |
|
||||||
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
||||||
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
||||||
| | | libkmipclient |
|
| | | libkmipclient |
|
||||||
@@ -325,8 +325,9 @@ build options:
|
|||||||
| | | rust/pvattest, rust/pvsecret, |
|
| | | rust/pvattest, rust/pvsecret, |
|
||||||
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
||||||
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
||||||
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
|
| libudev | `HAVE_LIBUDEV` | cpacfstatsd, zpcimon |
|
||||||
| libnl3 | `HAVE_LIBNL3` | opticsmon |
|
| libnl3 | `HAVE_LIBNL3` | zpcimon |
|
||||||
|
| libnvme | `HAVE_LIBNVME` | zpcimon |
|
||||||
|
|
||||||
This table lists additional build or install options:
|
This table lists additional build or install options:
|
||||||
|
|
||||||
@@ -374,11 +375,11 @@ the different tools are provided:
|
|||||||
|
|
||||||
The runtime requirements are: openssl-libs (>= 1.1.1) and libcurl.
|
The runtime requirements are: openssl-libs (>= 1.1.1) and libcurl.
|
||||||
|
|
||||||
* opticsmon:
|
* zpcimon:
|
||||||
For building opticsmon OpenSSL and the Netlink Library Suite (libnl3) are
|
For building zpcimon OpenSSL, the Netlink Library Suite (libnl3),
|
||||||
required.
|
and libnvme are required.
|
||||||
Tip: you may skip the opticsmon build by adding
|
Tip: you may skip the zpcimon build by adding
|
||||||
`HAVE_OPENSSL=0` or `HAVE_LIBNL3=0`
|
`HAVE_OPENSSL=0` or `HAVE_LIBNL3=0` or `HAVE_LIBNVME=0`.
|
||||||
|
|
||||||
* osasnmpd:
|
* osasnmpd:
|
||||||
You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm)
|
You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm)
|
||||||
|
|||||||
+2
-2
@@ -32,8 +32,8 @@ endif
|
|||||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||||
VERSION := 2
|
VERSION := 2
|
||||||
RELEASE := 43
|
RELEASE := 44
|
||||||
PATCHLEVEL := 1
|
PATCHLEVEL := 0
|
||||||
DISTRELEASE := build-$(shell date +%Y%m%d)
|
DISTRELEASE := build-$(shell date +%Y%m%d)
|
||||||
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||||
export S390_TOOLS_RELEASE
|
export S390_TOOLS_RELEASE
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
#include "util_list.h"
|
#include "util_list.h"
|
||||||
|
|
||||||
|
#define PCI_BDF_LEN 13 /* DDDD:BB:dd.f\0 */
|
||||||
|
#define PCI_VENDOR_ID_IBM 0x1014
|
||||||
|
|
||||||
enum zpci_pft {
|
enum zpci_pft {
|
||||||
ZPCI_PFT_UNCLASSIFIED = 0x00,
|
ZPCI_PFT_UNCLASSIFIED = 0x00,
|
||||||
|
|
||||||
@@ -65,6 +68,9 @@ struct zpci_dev {
|
|||||||
bool uid_is_unique;
|
bool uid_is_unique;
|
||||||
/* Configuration state 0 - Standby, 1 Configured */
|
/* Configuration state 0 - Standby, 1 Configured */
|
||||||
bool conf;
|
bool conf;
|
||||||
|
/* Common PCI attributes */
|
||||||
|
uint16_t subsystem_vendor;
|
||||||
|
uint16_t subsystem_device;
|
||||||
|
|
||||||
/* Associated netdevs if any */
|
/* Associated netdevs if any */
|
||||||
int num_netdevs;
|
int num_netdevs;
|
||||||
@@ -96,4 +102,6 @@ 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_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_name,
|
||||||
struct zpci_netdev **netdev);
|
struct zpci_netdev **netdev);
|
||||||
|
|
||||||
|
char *zpci_get_nvme_device_node(const char *pci_addr);
|
||||||
|
|
||||||
#endif /* LIB_ZPCI_PCI_LIST_H */
|
#endif /* LIB_ZPCI_PCI_LIST_H */
|
||||||
|
|||||||
@@ -21,9 +21,11 @@
|
|||||||
#define SCLP_ERRNOTIFY_AQ_DECONF 1
|
#define SCLP_ERRNOTIFY_AQ_DECONF 1
|
||||||
#define SCLP_ERRNOTIFY_AQ_REPORT_ERR 2
|
#define SCLP_ERRNOTIFY_AQ_REPORT_ERR 2
|
||||||
#define SCLP_ERRNOTIFY_AQ_OPTICS_DATA 3
|
#define SCLP_ERRNOTIFY_AQ_OPTICS_DATA 3
|
||||||
|
#define SCLP_ERRNOTIFY_AQ_NVME_SMART_DATA 4
|
||||||
|
|
||||||
#define SCLP_ERRNOTIFY_ID_ZPCICTL 0x4713
|
#define SCLP_ERRNOTIFY_ID_ZPCICTL 0x4713
|
||||||
#define SCLP_ERRNOTIFY_ID_OPTICSMON 0x4714
|
#define SCLP_ERRNOTIFY_ID_OPTICSMON 0x4714
|
||||||
|
#define SCLP_ERRNOTIFY_ID_NVMEMON 0x4715
|
||||||
|
|
||||||
#define SCLP_ERRNOTIFY_DATA_SIZE 4054
|
#define SCLP_ERRNOTIFY_DATA_SIZE 4054
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,16 @@ void util_fmt_exit(void);
|
|||||||
*/
|
*/
|
||||||
bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type);
|
bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* util_fmt_type_to_name() - Get name of given format type identifier.
|
||||||
|
* @type: Format type identifier
|
||||||
|
*
|
||||||
|
* Get the name corresponding to the given format type identifier.
|
||||||
|
*
|
||||||
|
* Return: name of the format type
|
||||||
|
*/
|
||||||
|
const char *util_fmt_type_to_name(enum util_fmt_t type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* util_fmt_set_indent() - Set indentation parameters.
|
* util_fmt_set_indent() - Set indentation parameters.
|
||||||
* @base : Base indentation level to apply to all output lines (default 0)
|
* @base : Base indentation level to apply to all output lines (default 0)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#define MSEC_PER_SEC 1000L
|
#define MSEC_PER_SEC 1000L
|
||||||
|
|
||||||
|
#define SEC_PER_DAY (24 * 60 * 60)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert nanoseconds to microseconds
|
* Convert nanoseconds to microseconds
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -72,6 +72,11 @@
|
|||||||
#define __section(x) __attribute__((__section__(#x)))
|
#define __section(x) __attribute__((__section__(#x)))
|
||||||
#define __noinline __attribute__((__noinline__))
|
#define __noinline __attribute__((__noinline__))
|
||||||
#define __big_endian
|
#define __big_endian
|
||||||
|
#ifdef __CHECKER__
|
||||||
|
# define __force __attribute__((force))
|
||||||
|
#else
|
||||||
|
# define __force
|
||||||
|
#endif
|
||||||
/* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define
|
/* The Linux kernel (in stddef.h) and glibc (sys/cdefs.h) define
|
||||||
* __always_inline. Therefore undefine it first to allow the headers
|
* __always_inline. Therefore undefine it first to allow the headers
|
||||||
* to be included first.
|
* to be included first.
|
||||||
|
|||||||
+25
-5
@@ -89,11 +89,26 @@ static const struct {
|
|||||||
const char *name;
|
const char *name;
|
||||||
enum util_fmt_t fmt;
|
enum util_fmt_t fmt;
|
||||||
} formats[] = {
|
} formats[] = {
|
||||||
{ "json", FMT_JSON },
|
[FMT_JSON] = {
|
||||||
{ "json-seq", FMT_JSONSEQ },
|
.name = "json",
|
||||||
{ "jsonl", FMT_JSONL },
|
.fmt = FMT_JSON
|
||||||
{ "pairs", FMT_PAIRS },
|
},
|
||||||
{ "csv", FMT_CSV },
|
[FMT_JSONSEQ] = {
|
||||||
|
.name = "json-seq",
|
||||||
|
.fmt = FMT_JSONSEQ
|
||||||
|
},
|
||||||
|
[FMT_JSONL] = {
|
||||||
|
.name = "jsonl",
|
||||||
|
.fmt = FMT_JSONL
|
||||||
|
},
|
||||||
|
[FMT_PAIRS] = {
|
||||||
|
.name = "pairs",
|
||||||
|
.fmt = FMT_PAIRS
|
||||||
|
},
|
||||||
|
[FMT_CSV] = {
|
||||||
|
.name = "csv",
|
||||||
|
.fmt = FMT_CSV
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Signal mask for blocking INT and TERM signals. */
|
/* Signal mask for blocking INT and TERM signals. */
|
||||||
@@ -112,6 +127,11 @@ bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *util_fmt_type_to_name(enum util_fmt_t type)
|
||||||
|
{
|
||||||
|
return formats[type].name;
|
||||||
|
}
|
||||||
|
|
||||||
bool util_fmt_is_json(enum util_fmt_t type)
|
bool util_fmt_is_json(enum util_fmt_t type)
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ static void zpci_print(struct zpci_dev *zdev)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (!zdev->conf) {
|
if (!zdev->conf) {
|
||||||
printf("fid: %8x address: %s\n", zdev->fid, pci_addr);
|
printf("fid:\t%4x address: %s\n", zdev->fid, pci_addr);
|
||||||
} else {
|
} else {
|
||||||
printf("fid: %8x address: %s uid: %4x%s pchid: %4x vfn: %4d port: %1d pft: %s ",
|
printf("fid:\t%4x address: %s uid: %4x%s pchid: %4x vfn: %4d\n",
|
||||||
zdev->fid, pci_addr, zdev->uid, (zdev->uid_is_unique) ? " (unique)" : "",
|
zdev->fid, pci_addr, zdev->uid, (zdev->uid_is_unique) ? " (unique)" : "",
|
||||||
zdev->pchid, zdev->vfn, zdev->port, zpci_pft_str(zdev));
|
zdev->pchid, zdev->vfn);
|
||||||
|
printf("\n");
|
||||||
|
printf("\tport: %1d pft: %s subsystem_device: %4x subsystem_vendor: %4x\n",
|
||||||
|
zdev->port, zpci_pft_str(zdev), zdev->subsystem_device,
|
||||||
|
zdev->subsystem_vendor);
|
||||||
if (zdev->num_netdevs) {
|
if (zdev->num_netdevs) {
|
||||||
printf("netdevs: ");
|
printf("\tnetdevs: ");
|
||||||
for (i = 0; i < zdev->num_netdevs; i++) {
|
for (i = 0; i < zdev->num_netdevs; i++) {
|
||||||
printf("%s (%s)", zdev->netdevs[i].name,
|
printf("%s (%s)", zdev->netdevs[i].name,
|
||||||
zpci_operstate_str(zdev->netdevs[i].operstate));
|
zpci_operstate_str(zdev->netdevs[i].operstate));
|
||||||
|
|||||||
+55
-1
@@ -20,6 +20,7 @@
|
|||||||
#include "lib/util_list.h"
|
#include "lib/util_list.h"
|
||||||
#include "lib/util_path.h"
|
#include "lib/util_path.h"
|
||||||
#include "lib/util_scandir.h"
|
#include "lib/util_scandir.h"
|
||||||
|
#include "lib/util_sys.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the function type name for the given device
|
* Get the function type name for the given device
|
||||||
@@ -137,7 +138,7 @@ const char *zpci_operstate_str(operstate_t state)
|
|||||||
static int zpci_populate_from_slot_dir(struct zpci_dev *zdev, const char *slot_dir,
|
static int zpci_populate_from_slot_dir(struct zpci_dev *zdev, const char *slot_dir,
|
||||||
const char *slot_name)
|
const char *slot_name)
|
||||||
{
|
{
|
||||||
char buf_addr[11]; /* "dddd:bb:dd\0" */
|
char buf_addr[PCI_BDF_LEN];
|
||||||
uint8_t bus, df;
|
uint8_t bus, df;
|
||||||
uint32_t domain;
|
uint32_t domain;
|
||||||
int val, rc;
|
int val, rc;
|
||||||
@@ -261,6 +262,16 @@ static int zpci_populate_from_dev_dir(struct zpci_dev *zdev)
|
|||||||
goto out_path;
|
goto out_path;
|
||||||
zdev->pft = val;
|
zdev->pft = val;
|
||||||
|
|
||||||
|
rc = util_file_read_i(&val, 16, "%s/subsystem_device", path);
|
||||||
|
if (rc)
|
||||||
|
goto out_path;
|
||||||
|
zdev->subsystem_device = val;
|
||||||
|
|
||||||
|
rc = util_file_read_i(&val, 16, "%s/subsystem_vendor", path);
|
||||||
|
if (rc)
|
||||||
|
goto out_path;
|
||||||
|
zdev->subsystem_vendor = val;
|
||||||
|
|
||||||
if (util_path_is_readable("%s/net", path)) {
|
if (util_path_is_readable("%s/net", path)) {
|
||||||
rc = zpci_populate_netdevices(zdev, path);
|
rc = zpci_populate_netdevices(zdev, path);
|
||||||
if (rc)
|
if (rc)
|
||||||
@@ -387,3 +398,46 @@ struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_n
|
|||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the NVMe device file name given a PCI address
|
||||||
|
*
|
||||||
|
* This function retrieves the NVMe device file "/dev/nvmeX"
|
||||||
|
* for a given PCI address. The device name can be used to construct
|
||||||
|
* the path /dev/nvmeX which is the NVMe's controller's character
|
||||||
|
* device used for example to retrieve S.M.A.R.T. data.
|
||||||
|
*
|
||||||
|
* @param[in] pci_addr The "DDDD:bb:dd.f" format PCI address
|
||||||
|
*
|
||||||
|
* @return The NVMe device file name if one is found NULL otherwise
|
||||||
|
*/
|
||||||
|
char *zpci_get_nvme_device_node(const char *pci_addr)
|
||||||
|
{
|
||||||
|
char *path, *dev = NULL;
|
||||||
|
char dev_addr[PCI_BDF_LEN];
|
||||||
|
struct dirent **de_vec;
|
||||||
|
int count, i;
|
||||||
|
|
||||||
|
path = util_path_sysfs("bus/pci/devices/%s/nvme", pci_addr);
|
||||||
|
count = util_scandir(&de_vec, alphasort, path, "nvme*");
|
||||||
|
if (count == -1) {
|
||||||
|
warnx("Could not read directory %s: %s", path, strerror(errno));
|
||||||
|
goto exit_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
util_asprintf(&dev, "/dev/%s", de_vec[i]->d_name);
|
||||||
|
if (util_sys_get_dev_addr(dev, dev_addr) != 0)
|
||||||
|
goto free_continue;
|
||||||
|
if (strcmp(dev_addr, pci_addr) == 0)
|
||||||
|
break;
|
||||||
|
free_continue:
|
||||||
|
free(dev);
|
||||||
|
dev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
util_scandir_free(de_vec, count);
|
||||||
|
exit_path:
|
||||||
|
free(path);
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,407 +0,0 @@
|
|||||||
/*
|
|
||||||
* opticsmon - Report optics monitoring data to firmware
|
|
||||||
*
|
|
||||||
* 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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#include <sys/epoll.h>
|
|
||||||
#include <sys/signalfd.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <sys/timerfd.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <linux/if.h>
|
|
||||||
|
|
||||||
#include "lib/util_list.h"
|
|
||||||
#include "lib/pci_list.h"
|
|
||||||
#include "lib/util_prg.h"
|
|
||||||
#include "lib/util_opt.h"
|
|
||||||
#include "lib/util_fmt.h"
|
|
||||||
#include "lib/util_libc.h"
|
|
||||||
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
|
|
||||||
#include "ethtool.h"
|
|
||||||
#include "link_mon.h"
|
|
||||||
#include "optics_info.h"
|
|
||||||
#include "optics_sclp.h"
|
|
||||||
#include "opticsmon_cli.h"
|
|
||||||
|
|
||||||
#define API_LEVEL 1
|
|
||||||
|
|
||||||
struct options {
|
|
||||||
bool monitor;
|
|
||||||
bool report;
|
|
||||||
bool module_info;
|
|
||||||
bool quiet;
|
|
||||||
|
|
||||||
uint32_t interval_seconds;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct opticsmon_ctx {
|
|
||||||
struct options opts;
|
|
||||||
struct ethtool_nl_ctx ethtool_ctx;
|
|
||||||
struct link_mon_nl_ctx lctx;
|
|
||||||
struct util_list *zpci_list;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct util_prg prg = {
|
|
||||||
.desc = "Use opticsmon to monitor the health of the optical modules\n"
|
|
||||||
"of directly attached PCI based NICs",
|
|
||||||
.copyright_vec = { {
|
|
||||||
.owner = "IBM Corp.",
|
|
||||||
.pub_first = 2024,
|
|
||||||
.pub_last = 2024,
|
|
||||||
},
|
|
||||||
UTIL_PRG_COPYRIGHT_END }
|
|
||||||
};
|
|
||||||
|
|
||||||
static void parse_cmdline(int argc, char *argv[], struct options *opts)
|
|
||||||
{
|
|
||||||
uint32_t seconds;
|
|
||||||
int cmd, ret;
|
|
||||||
|
|
||||||
util_prg_init(&prg);
|
|
||||||
util_opt_init(opt_vec, NULL);
|
|
||||||
|
|
||||||
do {
|
|
||||||
cmd = util_opt_getopt_long(argc, argv);
|
|
||||||
|
|
||||||
switch (cmd) {
|
|
||||||
case 'm':
|
|
||||||
opts->monitor = true;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
opts->report = true;
|
|
||||||
break;
|
|
||||||
case 'q':
|
|
||||||
opts->quiet = true;
|
|
||||||
break;
|
|
||||||
case OPT_DUMP:
|
|
||||||
opts->module_info = true;
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
ret = sscanf(optarg, "%u", &seconds);
|
|
||||||
if (ret != 1) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Failed to parse interval argument \"%s\" as seconds\n",
|
|
||||||
optarg);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
if (seconds < 86400)
|
|
||||||
opts->interval_seconds = seconds;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
util_prg_print_help();
|
|
||||||
util_opt_print_help();
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
case 'v':
|
|
||||||
util_prg_print_version();
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
case -1:
|
|
||||||
/* End of options string */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (cmd != -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int module_info_pair(struct optics *oi)
|
|
||||||
{
|
|
||||||
size_t b64_calclen, b64len;
|
|
||||||
int rc = EXIT_SUCCESS;
|
|
||||||
char *b64;
|
|
||||||
|
|
||||||
b64_calclen = (oi->size / 3) * 4;
|
|
||||||
if (oi->size % 3 > 0)
|
|
||||||
b64_calclen += 4;
|
|
||||||
|
|
||||||
b64 = util_zalloc(b64_calclen + 1); /* adds NUL byte */
|
|
||||||
b64len = EVP_EncodeBlock((unsigned char *)b64, oi->raw, oi->size);
|
|
||||||
if (b64len != b64_calclen) {
|
|
||||||
fprintf(stderr, "encoding base64 via openssl failed\n");
|
|
||||||
rc = EXIT_FAILURE;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
util_fmt_pair(FMT_QUOTE, "module_info", b64);
|
|
||||||
out:
|
|
||||||
free(b64);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void optics_json_print(struct opticsmon_ctx *ctx, struct zpci_netdev *nd, struct optics *oi)
|
|
||||||
{
|
|
||||||
util_fmt_obj_start(FMT_DEFAULT, "netdev");
|
|
||||||
util_fmt_pair(FMT_QUOTE, "name", nd->name);
|
|
||||||
util_fmt_pair(FMT_QUOTE, "operstate", zpci_operstate_str(nd->operstate));
|
|
||||||
util_fmt_obj_start(FMT_DEFAULT, "optics");
|
|
||||||
util_fmt_pair(FMT_QUOTE, "type", optics_type_str(optics_type(oi)));
|
|
||||||
util_fmt_pair(FMT_QUOTE, "rx_los", optics_los_str(optics_rx_los(oi)));
|
|
||||||
util_fmt_pair(FMT_QUOTE, "tx_los", optics_los_str(optics_tx_los(oi)));
|
|
||||||
util_fmt_pair(FMT_QUOTE, "tx_fault", optics_los_str(optics_rx_los(oi)));
|
|
||||||
if (ctx->opts.module_info)
|
|
||||||
module_info_pair(oi);
|
|
||||||
util_fmt_obj_end();
|
|
||||||
util_fmt_obj_end();
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dump_adapter_data(struct opticsmon_ctx *ctx, struct zpci_dev *zdev)
|
|
||||||
{
|
|
||||||
struct optics **ois;
|
|
||||||
int num_ois = 0;
|
|
||||||
char *pci_addr;
|
|
||||||
int i, rc;
|
|
||||||
|
|
||||||
ois = util_zalloc(sizeof(ois[0]) * zdev->num_netdevs);
|
|
||||||
for (i = 0; i < zdev->num_netdevs; i++) {
|
|
||||||
rc = ethtool_nl_get_optics(&ctx->ethtool_ctx, zdev->netdevs[i].name, &ois[i]);
|
|
||||||
if (rc)
|
|
||||||
goto free_ois;
|
|
||||||
num_ois++;
|
|
||||||
}
|
|
||||||
if (!ctx->opts.quiet) {
|
|
||||||
util_fmt_obj_start(FMT_DEFAULT, "adapter");
|
|
||||||
util_fmt_pair(FMT_QUOTE, "pft", zpci_pft_str(zdev));
|
|
||||||
util_fmt_obj_start(FMT_DEFAULT, "ids");
|
|
||||||
util_fmt_pair(FMT_QUOTE, "fid", "0x%0x", zdev->fid);
|
|
||||||
if (zdev->uid_is_unique)
|
|
||||||
util_fmt_pair(FMT_QUOTE, "uid", "0x%0x", zdev->uid);
|
|
||||||
pci_addr = zpci_pci_addr(zdev);
|
|
||||||
util_fmt_pair(FMT_QUOTE, "pci_address", pci_addr);
|
|
||||||
free(pci_addr);
|
|
||||||
util_fmt_obj_end();
|
|
||||||
util_fmt_obj_start(FMT_LIST, "netdevs");
|
|
||||||
for (i = 0; i < zdev->num_netdevs; i++)
|
|
||||||
optics_json_print(ctx, &zdev->netdevs[i], ois[i]);
|
|
||||||
util_fmt_obj_end(); /* netdevs list */
|
|
||||||
util_fmt_obj_end(); /* adapter */
|
|
||||||
fflush(stdout);
|
|
||||||
}
|
|
||||||
if (ctx->opts.report) {
|
|
||||||
for (i = 0; i < zdev->num_netdevs; i++) {
|
|
||||||
rc = sclp_issue_optics_report(zdev, ois[i]);
|
|
||||||
if (rc == -ENOTSUP) {
|
|
||||||
fprintf(stderr, "Skipping %s which does not support reporting\n",
|
|
||||||
zdev->netdevs[i].name);
|
|
||||||
} else if (rc < 0) {
|
|
||||||
fprintf(stderr, "Error issuing SCLP for optics data failed: %s\n",
|
|
||||||
strerror(-rc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free_ois:
|
|
||||||
for (i = 0; i < num_ois; i++)
|
|
||||||
optics_free(ois[i]);
|
|
||||||
free(ois);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void zpci_list_reload(struct util_list **zpci_list)
|
|
||||||
{
|
|
||||||
if (*zpci_list)
|
|
||||||
zpci_free_dev_list(*zpci_list);
|
|
||||||
*zpci_list = zpci_dev_list();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dump_all_adapter_data(struct opticsmon_ctx *ctx)
|
|
||||||
{
|
|
||||||
struct zpci_dev *zdev;
|
|
||||||
|
|
||||||
zpci_list_reload(&ctx->zpci_list);
|
|
||||||
util_list_iterate(ctx->zpci_list, zdev) {
|
|
||||||
/* Filter non-NIC devices and VFs */
|
|
||||||
if (zpci_is_vf(zdev) || !zdev->num_netdevs)
|
|
||||||
continue;
|
|
||||||
dump_adapter_data(ctx, zdev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int oneshot_mode(struct opticsmon_ctx *ctx)
|
|
||||||
{
|
|
||||||
util_fmt_init(stdout, FMT_JSON, FMT_DEFAULT, API_LEVEL);
|
|
||||||
if (!ctx->opts.quiet)
|
|
||||||
util_fmt_obj_start(FMT_LIST, "adapters");
|
|
||||||
dump_all_adapter_data(ctx);
|
|
||||||
if (!ctx->opts.quiet)
|
|
||||||
util_fmt_obj_end();
|
|
||||||
util_fmt_exit();
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_link_change(struct zpci_netdev *netdev, void *arg)
|
|
||||||
{
|
|
||||||
struct opticsmon_ctx *ctx = arg;
|
|
||||||
struct zpci_netdev *found_netdev;
|
|
||||||
struct zpci_dev *zdev = NULL;
|
|
||||||
int reloads = 1;
|
|
||||||
|
|
||||||
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 (found_netdev->operstate == netdev->operstate)
|
|
||||||
return;
|
|
||||||
/* Update operation state for VFs even though
|
|
||||||
* they are skipped just for a consistent view
|
|
||||||
*/
|
|
||||||
found_netdev->operstate = netdev->operstate;
|
|
||||||
/* Only collect optics data for PFs */
|
|
||||||
if (!zpci_is_vf(zdev))
|
|
||||||
dump_adapter_data(ctx, zdev);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Could be uninitalized list or a new device, retry after reload */
|
|
||||||
zpci_list_reload(&ctx->zpci_list);
|
|
||||||
reloads--;
|
|
||||||
} while (reloads > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MAX_EVENTS 8
|
|
||||||
|
|
||||||
static int monitor_wait_loop(struct opticsmon_ctx *ctx, int sigfd, int timerfd)
|
|
||||||
{
|
|
||||||
struct epoll_event events[MAX_EVENTS];
|
|
||||||
struct signalfd_siginfo fdsi;
|
|
||||||
int i, nlfd, epfd, nfds;
|
|
||||||
struct epoll_event ev;
|
|
||||||
uint64_t expirations;
|
|
||||||
ssize_t sread;
|
|
||||||
|
|
||||||
epfd = epoll_create1(EPOLL_CLOEXEC);
|
|
||||||
|
|
||||||
ev.events = EPOLLIN;
|
|
||||||
ev.data.fd = sigfd;
|
|
||||||
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) == -1)
|
|
||||||
return -EIO;
|
|
||||||
|
|
||||||
ev.events = EPOLLIN;
|
|
||||||
ev.data.fd = timerfd;
|
|
||||||
if (epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &ev) == -1)
|
|
||||||
return -EIO;
|
|
||||||
|
|
||||||
nlfd = link_mon_nl_waitfd_getfd(&ctx->lctx);
|
|
||||||
ev.events = EPOLLIN;
|
|
||||||
ev.data.fd = nlfd;
|
|
||||||
if (epoll_ctl(epfd, EPOLL_CTL_ADD, nlfd, &ev) == -1)
|
|
||||||
return -EIO;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
|
|
||||||
if (nfds < 0)
|
|
||||||
return nfds;
|
|
||||||
for (i = 0; i < nfds; i++) {
|
|
||||||
/* signal fd */
|
|
||||||
if (events[i].data.fd == sigfd) {
|
|
||||||
sread = read(sigfd, &fdsi, sizeof(fdsi));
|
|
||||||
if (sread != sizeof(fdsi))
|
|
||||||
return -EIO;
|
|
||||||
switch (fdsi.ssi_signo) {
|
|
||||||
case SIGINT:
|
|
||||||
case SIGTERM:
|
|
||||||
case SIGQUIT:
|
|
||||||
return 0;
|
|
||||||
/* Unexpected signal */
|
|
||||||
default:
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
/* timer fd */
|
|
||||||
} else if (events[i].data.fd == timerfd) {
|
|
||||||
sread = read(timerfd, &expirations, sizeof(uint64_t));
|
|
||||||
if (sread != sizeof(uint64_t))
|
|
||||||
return -EIO;
|
|
||||||
if (!expirations)
|
|
||||||
continue;
|
|
||||||
dump_all_adapter_data(ctx);
|
|
||||||
/* netlink fd */
|
|
||||||
} else if (events[i].data.fd == nlfd) {
|
|
||||||
link_mon_nl_waitfd_read(&ctx->lctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int monitor_mode(struct opticsmon_ctx *ctx)
|
|
||||||
{
|
|
||||||
struct itimerspec timerspec;
|
|
||||||
int sigfd, timerfd, ret;
|
|
||||||
sigset_t mask;
|
|
||||||
|
|
||||||
sigemptyset(&mask);
|
|
||||||
sigaddset(&mask, SIGINT);
|
|
||||||
sigaddset(&mask, SIGQUIT);
|
|
||||||
sigaddset(&mask, SIGTERM);
|
|
||||||
|
|
||||||
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
|
|
||||||
return -EIO;
|
|
||||||
|
|
||||||
sigfd = signalfd(-1, &mask, 0);
|
|
||||||
if (sigfd == -1) {
|
|
||||||
fprintf(stderr, "Failed to create signalfd\n");
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
|
|
||||||
if (timerfd == -1) {
|
|
||||||
fprintf(stderr, "Failed to create timerfd\n");
|
|
||||||
ret = -EIO;
|
|
||||||
goto close_signalfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set initial expiration to 1 ns so we gather optics data at startup */
|
|
||||||
timerspec.it_value.tv_sec = 0;
|
|
||||||
timerspec.it_value.tv_nsec = 1;
|
|
||||||
timerspec.it_interval.tv_sec = ctx->opts.interval_seconds;
|
|
||||||
timerspec.it_interval.tv_nsec = 0;
|
|
||||||
ret = timerfd_settime(timerfd, 0, &timerspec, NULL);
|
|
||||||
if (ret == -1) {
|
|
||||||
fprintf(stderr, "Failed to arm timer\n");
|
|
||||||
goto close_timerfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
util_fmt_init(stdout, FMT_JSONSEQ, FMT_DEFAULT, API_LEVEL);
|
|
||||||
ret = link_mon_nl_waitfd_create(&ctx->lctx, on_link_change, ctx);
|
|
||||||
if (ret) {
|
|
||||||
fprintf(stderr, "Failed to create link monitoring socket\n");
|
|
||||||
goto close_timerfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_wait_loop(ctx, sigfd, timerfd);
|
|
||||||
|
|
||||||
link_mon_nl_waitfd_destroy(&ctx->lctx);
|
|
||||||
util_fmt_exit();
|
|
||||||
close_signalfd:
|
|
||||||
close(sigfd);
|
|
||||||
close_timerfd:
|
|
||||||
close(timerfd);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
struct opticsmon_ctx ctx = { .opts = { .interval_seconds = 86400 } };
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
parse_cmdline(argc, argv, &ctx.opts);
|
|
||||||
ethtool_nl_connect(&ctx.ethtool_ctx);
|
|
||||||
if (ctx.opts.monitor)
|
|
||||||
ret = monitor_mode(&ctx);
|
|
||||||
else
|
|
||||||
ret = oneshot_mode(&ctx);
|
|
||||||
ethtool_nl_close(&ctx.ethtool_ctx);
|
|
||||||
|
|
||||||
if (ctx.zpci_list)
|
|
||||||
zpci_free_dev_list(ctx.zpci_list);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
Generated
+12
@@ -1222,9 +1222,21 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
"openssl",
|
||||||
"s390_pv",
|
"s390_pv",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"utils_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utils_macros"
|
||||||
|
version = "0.12.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ members = [
|
|||||||
"pvsecret",
|
"pvsecret",
|
||||||
"pvverify",
|
"pvverify",
|
||||||
"utils",
|
"utils",
|
||||||
|
"utils_macros",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGE
|
|||||||
pub enum SeHdrVersion {
|
pub enum SeHdrVersion {
|
||||||
/// Secure Execution header v1
|
/// Secure Execution header v1
|
||||||
One = 0x100,
|
One = 0x100,
|
||||||
|
/// Secure Execution header v2
|
||||||
|
Two = 0x200,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Struct containing all SE-header tags.
|
/// Struct containing all SE-header tags.
|
||||||
@@ -241,6 +243,7 @@ impl BootHdrTags {
|
|||||||
// Some sanity checks
|
// Some sanity checks
|
||||||
let hdr_version = match hdr_head.version.get() {
|
let hdr_version = match hdr_head.version.get() {
|
||||||
0x100 => SeHdrVersion::One,
|
0x100 => SeHdrVersion::One,
|
||||||
|
0x200 => SeHdrVersion::Two,
|
||||||
_ => {
|
_ => {
|
||||||
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
|
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
|
||||||
return Err(Error::InvBootHdr);
|
return Err(Error::InvBootHdr);
|
||||||
|
|||||||
+458
-2
@@ -7,12 +7,14 @@ use std::fmt::Display;
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
use openssl::bn::BigNumContext;
|
||||||
use openssl::derive::Deriver;
|
use openssl::derive::Deriver;
|
||||||
use openssl::ec::{EcGroup, EcKey};
|
use openssl::ec::{EcGroup, EcKey, EcPoint};
|
||||||
|
use openssl::error::ErrorStack;
|
||||||
use openssl::hash::{DigestBytes, MessageDigest};
|
use openssl::hash::{DigestBytes, MessageDigest};
|
||||||
use openssl::md::MdRef;
|
use openssl::md::MdRef;
|
||||||
use openssl::nid::Nid;
|
use openssl::nid::Nid;
|
||||||
use openssl::pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public};
|
use openssl::pkey::{HasPublic, Id, KeyType, PKey, PKeyRef, Private, Public};
|
||||||
use openssl::pkey_ctx::{HkdfMode, PkeyCtx};
|
use openssl::pkey_ctx::{HkdfMode, PkeyCtx};
|
||||||
use openssl::rand::rand_bytes;
|
use openssl::rand::rand_bytes;
|
||||||
use openssl::rsa::Padding;
|
use openssl::rsa::Padding;
|
||||||
@@ -23,8 +25,51 @@ use openssl::symm::{
|
|||||||
use pv_core::request::Confidential;
|
use pv_core::request::Confidential;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
use crate::openssl_extensions::PkeyEncapsulateContext;
|
||||||
|
use crate::req::get_pub_ecdh_points;
|
||||||
|
use crate::request::EcPubKeyCoord;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
|
/// Compute ECDH shared secret from public and private keys
|
||||||
|
///
|
||||||
|
/// It is expected that the public and private key are with respect to EC-P521.
|
||||||
|
///
|
||||||
|
/// Note that the output is the concatenation of the 80-bytes-left-padded x and
|
||||||
|
/// 80-bytes-left-padded y coordinate.
|
||||||
|
fn ecdh_shared_secret(
|
||||||
|
pub_key: &PKeyRef<Public>,
|
||||||
|
priv_key: &PKeyRef<Private>,
|
||||||
|
) -> Result<[u8; 160], ErrorStack> {
|
||||||
|
let pub_key = pub_key.ec_key()?;
|
||||||
|
let priv_key = priv_key.ec_key()?;
|
||||||
|
|
||||||
|
// Verify both keys use the EC-P521 curve (SECP521R1)
|
||||||
|
assert_eq!(
|
||||||
|
pub_key.group().curve_name(),
|
||||||
|
Some(Nid::SECP521R1),
|
||||||
|
"Public key must use EC-P521 curve"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
priv_key.group().curve_name(),
|
||||||
|
Some(Nid::SECP521R1),
|
||||||
|
"Private key must use EC-P521 curve"
|
||||||
|
);
|
||||||
|
|
||||||
|
pub_key.check_key()?;
|
||||||
|
priv_key.check_key()?;
|
||||||
|
let group = pub_key.group();
|
||||||
|
let mut bn_ctx = BigNumContext::new()?;
|
||||||
|
let mut point = EcPoint::new(group)?;
|
||||||
|
point.mul2(
|
||||||
|
group,
|
||||||
|
pub_key.public_key(),
|
||||||
|
priv_key.private_key(),
|
||||||
|
&mut bn_ctx,
|
||||||
|
)?;
|
||||||
|
let coord = get_pub_ecdh_points(&point, group)?;
|
||||||
|
Ok(coord)
|
||||||
|
}
|
||||||
|
|
||||||
/// An AES256-GCM key that will purge itself out of the memory when going out of scope
|
/// An AES256-GCM key that will purge itself out of the memory when going out of scope
|
||||||
pub type Aes256GcmKey = Confidential<[u8; SymKeyType::AES_256_GCM_KEY_LEN]>;
|
pub type Aes256GcmKey = Confidential<[u8; SymKeyType::AES_256_GCM_KEY_LEN]>;
|
||||||
/// An AES256-XTS key that will purge itself out of the memory when going out of scope
|
/// An AES256-XTS key that will purge itself out of the memory when going out of scope
|
||||||
@@ -74,6 +119,7 @@ impl SymKeyType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the [`SymKeyType`] is an AEAD key
|
/// Returns true if the [`SymKeyType`] is an AEAD key
|
||||||
|
#[must_use]
|
||||||
pub const fn is_aead(&self) -> bool {
|
pub const fn is_aead(&self) -> bool {
|
||||||
self.tag_len().is_some()
|
self.tag_len().is_some()
|
||||||
}
|
}
|
||||||
@@ -214,6 +260,220 @@ pub fn derive_aes256_gcm_key(k1: &PKeyRef<Private>, k2: &PKeyRef<Public>) -> Res
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines the KeyType of a given PKey by testing against all known key types.
|
||||||
|
///
|
||||||
|
/// This function iterates through all known OpenSSL key types and uses the `is_a()`
|
||||||
|
/// method to identify which type the provided key matches. This is more reliable than
|
||||||
|
/// using `Id` to `KeyType` conversion, especially for newer key types like ML-KEM
|
||||||
|
/// that may not have a direct `Id` mapping.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `key` - A reference to the PKey to identify
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Some(KeyType)` - If the key matches one of the known key types
|
||||||
|
/// * `None` - If the key type is not recognized or doesn't match any known types
|
||||||
|
fn pkey_to_keytype<T>(key: &PKeyRef<T>) -> Option<KeyType> {
|
||||||
|
const KNOWN_KEY_TYPES: &[KeyType] = &[
|
||||||
|
KeyType::RSA,
|
||||||
|
KeyType::RSA_PSS,
|
||||||
|
KeyType::DSA,
|
||||||
|
KeyType::DH,
|
||||||
|
KeyType::EC,
|
||||||
|
KeyType::HMAC,
|
||||||
|
KeyType::CMAC,
|
||||||
|
KeyType::X25519,
|
||||||
|
KeyType::ED25519,
|
||||||
|
KeyType::X448,
|
||||||
|
KeyType::ED448,
|
||||||
|
KeyType::ML_KEM_512,
|
||||||
|
KeyType::ML_KEM_768,
|
||||||
|
KeyType::ML_KEM_1024,
|
||||||
|
];
|
||||||
|
|
||||||
|
KNOWN_KEY_TYPES
|
||||||
|
.iter()
|
||||||
|
.find(|&&key_type| key.is_a(key_type))
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_type_str(t: KeyType) -> &'static str {
|
||||||
|
if t == KeyType::RSA {
|
||||||
|
"RSA"
|
||||||
|
} else if t == KeyType::RSA_PSS {
|
||||||
|
"RSA-PSS"
|
||||||
|
} else if t == KeyType::DSA {
|
||||||
|
"DSA"
|
||||||
|
} else if t == KeyType::DH {
|
||||||
|
"DH"
|
||||||
|
} else if t == KeyType::EC {
|
||||||
|
"EC"
|
||||||
|
} else if t == KeyType::HMAC {
|
||||||
|
"HMAC"
|
||||||
|
} else if t == KeyType::CMAC {
|
||||||
|
"CMAC"
|
||||||
|
} else if t == KeyType::X25519 {
|
||||||
|
"X25519"
|
||||||
|
} else if t == KeyType::ED25519 {
|
||||||
|
"ED25519"
|
||||||
|
} else if t == KeyType::X448 {
|
||||||
|
"X448"
|
||||||
|
} else if t == KeyType::ED448 {
|
||||||
|
"ED448"
|
||||||
|
} else if t == KeyType::ML_KEM_512 {
|
||||||
|
"ML-KEM-512"
|
||||||
|
} else if t == KeyType::ML_KEM_768 {
|
||||||
|
"ML-KEM-768"
|
||||||
|
} else if t == KeyType::ML_KEM_1024 {
|
||||||
|
"ML-KEM-1024"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that a key matches the expected key type.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the key doesn't match the expected type.
|
||||||
|
pub(crate) fn validate_key_type<T: HasPublic>(
|
||||||
|
key: &PKeyRef<T>,
|
||||||
|
key_name: &str,
|
||||||
|
expected_type: KeyType,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !key.is_a(expected_type) {
|
||||||
|
return Err(Error::RetrInvKey {
|
||||||
|
what: "key type",
|
||||||
|
kind: key_name.to_string(),
|
||||||
|
value: pkey_to_keytype(key)
|
||||||
|
.map(key_type_str)
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string(),
|
||||||
|
exp: key_type_str(expected_type).to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that a key is an EC key with the specified curve.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the key is not an EC key or doesn't use the expected curve.
|
||||||
|
pub(crate) fn validate_ec_key<T: HasPublic>(
|
||||||
|
key: &PKeyRef<T>,
|
||||||
|
key_name: &str,
|
||||||
|
expected_curve: Nid,
|
||||||
|
) -> Result<()> {
|
||||||
|
if key.id() != Id::EC {
|
||||||
|
return Err(Error::RetrInvKey {
|
||||||
|
what: "key type",
|
||||||
|
kind: key_name.to_string(),
|
||||||
|
value: pkey_to_keytype(key)
|
||||||
|
.map(key_type_str)
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string(),
|
||||||
|
exp: format!("EC ({})", expected_curve.long_name().unwrap_or("unknown")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let ec_key = key.ec_key()?;
|
||||||
|
if ec_key.group().curve_name() != Some(expected_curve) {
|
||||||
|
return Err(Error::RetrInvKey {
|
||||||
|
what: "curve",
|
||||||
|
kind: key_name.to_string(),
|
||||||
|
value: ec_key
|
||||||
|
.group()
|
||||||
|
.curve_name()
|
||||||
|
.and_then(|nid| nid.long_name().ok())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string(),
|
||||||
|
exp: expected_curve.long_name().unwrap_or("unknown").to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive a symmetric AES 256 GCM key from a private target key, a public
|
||||||
|
/// customer key, and a public ML-KEM target key.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The derived key and the ML-KEM ciphertext (KC).
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if something went bad in OpenSSL or the
|
||||||
|
/// wrong key types were used.
|
||||||
|
pub fn derive_aes256_gcm_key_hybrid(
|
||||||
|
priv_ecdh_cust_key: &PKeyRef<Private>,
|
||||||
|
pub_ecdh_target_key: &PKeyRef<Public>,
|
||||||
|
pub_mlkem_target_key: &PKeyRef<Public>,
|
||||||
|
) -> Result<(Aes256GcmKey, Vec<u8>)> {
|
||||||
|
let mut buffer: Vec<u8> = vec![0, 0, 0, 1];
|
||||||
|
|
||||||
|
validate_ec_key(priv_ecdh_cust_key, "ECDH customer key", Nid::SECP521R1)?;
|
||||||
|
validate_ec_key(pub_ecdh_target_key, "ECDH target key", Nid::SECP521R1)?;
|
||||||
|
validate_key_type(
|
||||||
|
pub_mlkem_target_key,
|
||||||
|
"ML-KEM target key",
|
||||||
|
KeyType::ML_KEM_1024,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Derive the ECDH shared secret
|
||||||
|
let ecdh_derived_secret = ecdh_shared_secret(pub_ecdh_target_key, priv_ecdh_cust_key)?;
|
||||||
|
assert_eq!(ecdh_derived_secret.as_ref().len(), 160);
|
||||||
|
buffer.extend_from_slice(ecdh_derived_secret.as_ref());
|
||||||
|
|
||||||
|
// Derive the ML-KEM shared secret
|
||||||
|
let mut ctx = PkeyCtx::new(pub_mlkem_target_key)?;
|
||||||
|
ctx.encapsulate_init()?;
|
||||||
|
let (mut ciphertext, mut shared_secret) = (vec![], vec![]);
|
||||||
|
ctx.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)?;
|
||||||
|
assert_eq!(ciphertext.len(), 1568);
|
||||||
|
assert_eq!(shared_secret.len(), 32);
|
||||||
|
buffer.extend_from_slice(&shared_secret);
|
||||||
|
|
||||||
|
// Append the private ECDH customer key
|
||||||
|
let pub_ecdh_cust_key = EcPubKeyCoord::try_from(priv_ecdh_cust_key)?;
|
||||||
|
assert_eq!(pub_ecdh_cust_key.as_ref().len(), 160);
|
||||||
|
buffer.extend_from_slice(pub_ecdh_cust_key.as_ref());
|
||||||
|
|
||||||
|
// Append the ciphertext
|
||||||
|
buffer.extend_from_slice(&ciphertext);
|
||||||
|
|
||||||
|
// Append the public ECDH target key
|
||||||
|
let pub_ecdh_target_key: EcPubKeyCoord = pub_ecdh_target_key.try_into()?;
|
||||||
|
assert_eq!(pub_ecdh_target_key.as_ref().len(), 160);
|
||||||
|
buffer.extend_from_slice(pub_ecdh_target_key.as_ref());
|
||||||
|
|
||||||
|
// Append the public ML-KEM target key
|
||||||
|
assert_eq!(pub_mlkem_target_key.raw_public_key()?.len(), 1568);
|
||||||
|
buffer.extend_from_slice(&pub_mlkem_target_key.raw_public_key()?);
|
||||||
|
|
||||||
|
// Append the magic string
|
||||||
|
const STRING: &str = "PQC Secure Execution with Format-2 Key Slots KS2";
|
||||||
|
assert_eq!(STRING.len(), 48);
|
||||||
|
buffer.extend_from_slice(STRING.as_bytes());
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
assert_eq!(buffer.len(), 4 + 160 + 32 + 160 + 1568 + 160 + 1568 + 48);
|
||||||
|
|
||||||
|
let secr = Confidential::new(buffer);
|
||||||
|
|
||||||
|
// Panic: does not panic as SHA256 digest is 32 bytes long
|
||||||
|
Ok((
|
||||||
|
Aes256GcmKey::new(
|
||||||
|
hash(MessageDigest::sha256(), secr.value())?
|
||||||
|
.as_ref()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
ciphertext,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate a random array.
|
/// Generate a random array.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -760,6 +1020,112 @@ mod tests {
|
|||||||
assert_eq!(&calc_key, &exp_key);
|
assert_eq!(&calc_key, &exp_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derive_aes256_gcm_key_hybrid() {
|
||||||
|
let (cust_key, host_key_1, host_key_2) = get_test_keys_hybrid();
|
||||||
|
let entropy = [0x5au8; 4096];
|
||||||
|
let nonce = [0xa5u8; 48];
|
||||||
|
|
||||||
|
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
|
||||||
|
|
||||||
|
let exp_key: Aes256GcmKey = [
|
||||||
|
197, 167, 157, 112, 186, 112, 72, 125, 192, 219, 168, 132, 178, 167, 249, 123, 149, 3,
|
||||||
|
151, 166, 162, 66, 120, 39, 41, 230, 143, 54, 172, 10, 200, 143,
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
let exp_kc = [
|
||||||
|
54, 117, 96, 77, 148, 147, 170, 100, 34, 177, 95, 7, 35, 243, 145, 115, 7, 87, 178, 9,
|
||||||
|
169, 99, 193, 99, 244, 195, 23, 78, 11, 153, 221, 196, 5, 192, 253, 192, 86, 49, 194,
|
||||||
|
236, 43, 69, 183, 125, 166, 87, 158, 188, 13, 152, 19, 6, 253, 29, 194, 0, 101, 236,
|
||||||
|
28, 171, 3, 236, 53, 186, 191, 109, 7, 83, 220, 93, 126, 29, 19, 203, 201, 39, 59, 7,
|
||||||
|
131, 51, 81, 73, 254, 69, 105, 185, 214, 179, 155, 194, 189, 122, 106, 130, 249, 48, 4,
|
||||||
|
33, 245, 170, 163, 4, 223, 208, 138, 224, 203, 119, 105, 59, 187, 153, 235, 90, 79,
|
||||||
|
127, 29, 136, 230, 142, 78, 83, 27, 131, 58, 126, 76, 53, 129, 20, 85, 108, 86, 64,
|
||||||
|
244, 90, 84, 177, 239, 105, 90, 41, 118, 189, 88, 174, 224, 216, 29, 10, 123, 81, 212,
|
||||||
|
203, 197, 120, 20, 190, 3, 45, 37, 194, 208, 249, 232, 221, 67, 10, 62, 121, 143, 169,
|
||||||
|
227, 165, 17, 30, 85, 223, 44, 141, 114, 142, 105, 119, 187, 41, 46, 8, 6, 17, 29, 165,
|
||||||
|
117, 254, 92, 174, 231, 25, 117, 69, 112, 216, 80, 73, 185, 54, 50, 119, 145, 220, 174,
|
||||||
|
26, 105, 81, 114, 210, 144, 148, 109, 218, 64, 78, 231, 196, 229, 88, 46, 128, 106,
|
||||||
|
125, 204, 58, 184, 127, 193, 207, 86, 163, 98, 164, 57, 242, 29, 59, 251, 227, 185, 60,
|
||||||
|
18, 68, 74, 47, 203, 61, 164, 78, 245, 100, 87, 148, 210, 97, 158, 252, 79, 78, 50,
|
||||||
|
143, 35, 231, 215, 211, 75, 133, 214, 227, 140, 27, 21, 46, 221, 84, 89, 165, 161, 227,
|
||||||
|
46, 117, 193, 254, 190, 237, 130, 28, 57, 52, 14, 235, 154, 115, 172, 185, 67, 116, 34,
|
||||||
|
242, 158, 209, 0, 126, 196, 93, 224, 29, 246, 145, 65, 73, 185, 196, 4, 107, 124, 241,
|
||||||
|
157, 230, 168, 244, 238, 84, 188, 173, 17, 238, 26, 161, 24, 176, 229, 226, 33, 244,
|
||||||
|
167, 41, 107, 156, 29, 226, 248, 64, 146, 191, 210, 234, 76, 144, 219, 92, 136, 173,
|
||||||
|
241, 98, 0, 71, 135, 214, 196, 116, 63, 243, 73, 71, 130, 171, 86, 204, 149, 69, 164,
|
||||||
|
20, 177, 122, 95, 226, 95, 126, 106, 160, 59, 97, 137, 8, 73, 113, 189, 172, 24, 114,
|
||||||
|
60, 62, 249, 193, 3, 99, 34, 153, 42, 238, 77, 181, 80, 185, 223, 39, 8, 44, 215, 119,
|
||||||
|
214, 30, 136, 19, 215, 35, 184, 69, 94, 10, 170, 179, 51, 183, 105, 237, 237, 48, 199,
|
||||||
|
122, 159, 87, 183, 71, 230, 87, 102, 77, 81, 116, 28, 126, 195, 72, 50, 157, 223, 243,
|
||||||
|
83, 36, 16, 168, 111, 209, 132, 12, 96, 56, 140, 57, 144, 75, 253, 119, 123, 168, 2,
|
||||||
|
79, 214, 121, 80, 154, 93, 235, 222, 130, 181, 166, 97, 51, 106, 21, 138, 224, 8, 144,
|
||||||
|
223, 162, 152, 183, 6, 80, 64, 144, 21, 155, 56, 255, 108, 248, 125, 196, 46, 99, 119,
|
||||||
|
94, 104, 63, 46, 15, 165, 30, 98, 75, 212, 193, 116, 151, 189, 65, 42, 83, 253, 183,
|
||||||
|
41, 195, 45, 206, 178, 66, 36, 215, 197, 105, 236, 79, 91, 135, 164, 71, 187, 199, 200,
|
||||||
|
150, 226, 182, 254, 6, 234, 109, 3, 17, 116, 249, 44, 211, 184, 61, 189, 44, 181, 249,
|
||||||
|
8, 58, 230, 236, 8, 188, 14, 178, 100, 120, 250, 29, 1, 204, 158, 46, 161, 39, 66, 76,
|
||||||
|
42, 114, 149, 160, 31, 87, 254, 181, 224, 17, 162, 163, 99, 11, 34, 149, 50, 203, 205,
|
||||||
|
224, 38, 18, 233, 161, 49, 7, 151, 63, 81, 68, 71, 174, 49, 22, 143, 93, 50, 0, 154,
|
||||||
|
152, 178, 134, 147, 152, 118, 196, 241, 233, 67, 102, 149, 179, 213, 176, 118, 64, 172,
|
||||||
|
143, 134, 196, 232, 154, 110, 129, 155, 159, 103, 117, 202, 11, 35, 75, 104, 5, 11,
|
||||||
|
160, 147, 174, 49, 248, 45, 247, 16, 7, 64, 209, 255, 170, 243, 242, 40, 158, 94, 239,
|
||||||
|
194, 225, 113, 24, 90, 243, 73, 137, 217, 175, 130, 50, 133, 139, 250, 145, 190, 76,
|
||||||
|
151, 183, 30, 86, 146, 59, 171, 214, 211, 135, 203, 192, 42, 189, 90, 47, 152, 132,
|
||||||
|
168, 252, 175, 71, 234, 118, 207, 161, 176, 254, 189, 54, 174, 160, 178, 158, 133, 122,
|
||||||
|
63, 75, 95, 201, 55, 139, 2, 208, 232, 110, 74, 201, 196, 135, 244, 156, 87, 208, 101,
|
||||||
|
203, 121, 187, 16, 106, 80, 120, 165, 44, 147, 182, 114, 173, 186, 185, 255, 99, 85,
|
||||||
|
88, 26, 27, 43, 203, 176, 207, 88, 20, 253, 169, 210, 168, 109, 75, 234, 239, 8, 243,
|
||||||
|
244, 65, 164, 193, 255, 240, 215, 54, 158, 188, 93, 93, 54, 46, 77, 152, 78, 174, 154,
|
||||||
|
67, 248, 24, 235, 172, 240, 83, 224, 17, 100, 217, 15, 172, 176, 46, 85, 107, 105, 127,
|
||||||
|
147, 158, 202, 255, 145, 237, 84, 223, 100, 214, 38, 133, 169, 112, 227, 138, 220, 125,
|
||||||
|
72, 197, 5, 227, 94, 245, 42, 70, 33, 209, 243, 70, 229, 37, 118, 214, 147, 43, 87, 39,
|
||||||
|
241, 107, 26, 169, 28, 72, 223, 133, 145, 44, 248, 213, 52, 127, 250, 99, 193, 115,
|
||||||
|
113, 147, 89, 112, 237, 199, 208, 36, 155, 106, 144, 73, 249, 8, 116, 198, 107, 120,
|
||||||
|
233, 145, 11, 155, 178, 7, 66, 157, 255, 206, 128, 155, 233, 111, 148, 194, 214, 238,
|
||||||
|
252, 230, 96, 119, 30, 37, 73, 133, 129, 87, 185, 149, 251, 156, 17, 8, 83, 106, 207,
|
||||||
|
98, 203, 100, 39, 199, 127, 253, 59, 37, 121, 161, 216, 146, 6, 178, 183, 243, 191, 91,
|
||||||
|
106, 243, 132, 111, 216, 163, 87, 210, 197, 173, 146, 65, 131, 194, 96, 70, 6, 7, 192,
|
||||||
|
45, 173, 71, 44, 134, 122, 60, 173, 208, 238, 22, 187, 208, 212, 51, 191, 185, 174, 3,
|
||||||
|
125, 28, 134, 216, 209, 4, 224, 199, 16, 15, 56, 70, 188, 216, 92, 24, 96, 57, 125,
|
||||||
|
138, 151, 73, 254, 245, 106, 53, 4, 150, 74, 43, 42, 4, 157, 238, 125, 168, 41, 224,
|
||||||
|
22, 249, 45, 117, 32, 180, 161, 41, 39, 180, 96, 24, 2, 102, 57, 116, 34, 75, 90, 72,
|
||||||
|
134, 176, 2, 196, 59, 143, 182, 201, 117, 178, 153, 81, 108, 167, 122, 139, 71, 197,
|
||||||
|
55, 114, 60, 161, 130, 14, 29, 79, 152, 55, 136, 62, 190, 228, 202, 53, 126, 4, 173,
|
||||||
|
99, 28, 190, 224, 255, 134, 123, 166, 162, 244, 55, 26, 81, 120, 207, 193, 10, 103,
|
||||||
|
153, 215, 220, 12, 71, 67, 217, 154, 212, 44, 200, 232, 0, 178, 39, 44, 22, 7, 14, 215,
|
||||||
|
183, 192, 104, 51, 46, 93, 102, 195, 65, 9, 191, 241, 237, 151, 5, 64, 103, 228, 162,
|
||||||
|
41, 123, 29, 5, 80, 203, 198, 234, 230, 107, 53, 60, 58, 253, 47, 152, 22, 77, 81, 86,
|
||||||
|
215, 132, 152, 135, 6, 218, 46, 92, 192, 218, 198, 234, 76, 178, 25, 203, 48, 61, 76,
|
||||||
|
215, 96, 6, 49, 195, 37, 225, 10, 175, 222, 186, 133, 63, 50, 236, 215, 247, 17, 199,
|
||||||
|
8, 134, 64, 246, 194, 167, 105, 15, 57, 62, 50, 51, 243, 192, 242, 122, 11, 46, 202,
|
||||||
|
47, 10, 71, 153, 212, 226, 38, 12, 90, 150, 154, 152, 233, 7, 172, 111, 185, 160, 246,
|
||||||
|
0, 166, 113, 90, 37, 203, 166, 43, 53, 255, 211, 127, 139, 73, 7, 10, 164, 1, 168, 223,
|
||||||
|
87, 127, 43, 47, 87, 68, 84, 247, 223, 108, 108, 113, 36, 17, 50, 98, 236, 48, 10, 219,
|
||||||
|
182, 107, 240, 198, 207, 20, 178, 9, 142, 14, 93, 163, 166, 147, 38, 176, 172, 156, 73,
|
||||||
|
174, 238, 175, 231, 130, 159, 51, 128, 76, 34, 37, 138, 19, 3, 59, 71, 78, 144, 238,
|
||||||
|
226, 214, 188, 27, 42, 142, 245, 238, 131, 190, 211, 240, 41, 122, 69, 124, 171, 75,
|
||||||
|
115, 45, 144, 133, 176, 19, 81, 125, 230, 149, 235, 159, 6, 155, 195, 119, 62, 140, 50,
|
||||||
|
52, 209, 124, 3, 93, 232, 20, 130, 138, 110, 60, 183, 177, 161, 52, 114, 91, 19, 211,
|
||||||
|
156, 185, 202, 200, 36, 103, 253, 113, 45, 245, 177, 238, 43, 144, 38, 221, 0, 102, 50,
|
||||||
|
255, 20, 154, 56, 156, 155, 92, 157, 57, 209, 77, 84, 88, 24, 116, 116, 54, 213, 222,
|
||||||
|
76, 212, 193, 168, 216, 247, 125, 135, 114, 226, 128, 140, 250, 103, 82, 215, 238, 32,
|
||||||
|
74, 252, 45, 224, 23, 95, 126, 124, 135, 124, 128, 53, 203, 40, 65, 222, 8, 83, 178,
|
||||||
|
211, 64, 141, 64, 98, 188, 134, 100, 65, 166, 52, 249, 1, 206, 58, 55, 195, 23, 218,
|
||||||
|
239, 41, 73, 88, 113, 148, 132, 209, 93, 37, 205, 58, 92, 14, 1, 133, 168, 162, 192,
|
||||||
|
147, 70, 167, 101, 170, 152, 159, 0, 212, 26, 97, 49, 43, 217, 173, 38, 215, 136, 26,
|
||||||
|
208, 244, 19, 83, 207, 38, 224, 254, 92, 169, 219, 236, 172, 49, 55, 98, 55, 15, 187,
|
||||||
|
173, 114, 99, 130, 211, 78, 168, 221, 209, 250, 88, 189, 17, 186, 172, 129, 56, 90,
|
||||||
|
238, 120, 23, 176, 87, 133, 81, 244, 29, 2, 215, 34, 88, 247, 231, 167, 56,
|
||||||
|
];
|
||||||
|
|
||||||
|
let (exc_key, kc) =
|
||||||
|
super::derive_aes256_gcm_key_hybrid(&cust_key, &host_key_1, &host_key_2).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(exc_key, exp_key);
|
||||||
|
assert_eq!(kc, exp_kc);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hkdf_rfc_5869() {
|
fn hkdf_rfc_5869() {
|
||||||
use openssl::md::Md;
|
use openssl::md::Md;
|
||||||
@@ -933,4 +1299,94 @@ mod tests {
|
|||||||
}))
|
}))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_ec_key_valid() {
|
||||||
|
let (cust_key, host_key) = get_test_keys();
|
||||||
|
|
||||||
|
// Both test keys are SECP521R1 EC keys
|
||||||
|
assert!(validate_ec_key(&cust_key, "customer key", Nid::SECP521R1).is_ok());
|
||||||
|
assert!(validate_ec_key(&host_key, "host key", Nid::SECP521R1).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_ec_key_wrong_curve() {
|
||||||
|
let (cust_key, _) = get_test_keys();
|
||||||
|
|
||||||
|
// Test key is SECP521R1, but we expect SECP384R1
|
||||||
|
let result = validate_ec_key(&cust_key, "customer key", Nid::SECP384R1);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
if let Err(Error::RetrInvKey {
|
||||||
|
what,
|
||||||
|
kind,
|
||||||
|
value,
|
||||||
|
exp,
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
assert_eq!(what, "curve");
|
||||||
|
assert_eq!(kind, "customer key");
|
||||||
|
assert_eq!(value, "secp521r1");
|
||||||
|
assert_eq!(exp, "secp384r1");
|
||||||
|
} else {
|
||||||
|
panic!("Expected RetrInvKey error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_ec_key_not_ec() {
|
||||||
|
let keypair = crate::get_test_asset!("keys/rsa2048key.pem");
|
||||||
|
let keypair = PKey::private_key_from_pem(keypair).unwrap();
|
||||||
|
|
||||||
|
// RSA key is not an EC key
|
||||||
|
let result = validate_ec_key(&keypair, "EC key", Nid::SECP521R1);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
if let Err(Error::RetrInvKey {
|
||||||
|
what,
|
||||||
|
kind,
|
||||||
|
value,
|
||||||
|
exp,
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
assert_eq!(what, "key type");
|
||||||
|
assert_eq!(kind, "EC key");
|
||||||
|
assert_eq!(value, "RSA");
|
||||||
|
assert_eq!(exp, "EC (secp521r1)");
|
||||||
|
} else {
|
||||||
|
panic!("Expected RetrInvKey error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_mlkem_key_valid() {
|
||||||
|
let (_, _, mlkem_key) = get_test_keys_hybrid();
|
||||||
|
|
||||||
|
// The third key from get_test_keys_hybrid is ML-KEM-1024
|
||||||
|
assert!(validate_key_type(&mlkem_key, "ML-KEM key", KeyType::ML_KEM_1024).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_mlkem_key_wrong_type() {
|
||||||
|
let (ec_key, _) = get_test_keys();
|
||||||
|
|
||||||
|
// EC key is not ML-KEM
|
||||||
|
let result = validate_key_type(&ec_key, "EC key", KeyType::ML_KEM_1024);
|
||||||
|
assert!(result.is_err());
|
||||||
|
|
||||||
|
if let Err(Error::RetrInvKey {
|
||||||
|
what,
|
||||||
|
kind,
|
||||||
|
value,
|
||||||
|
exp,
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
assert_eq!(what, "key type");
|
||||||
|
assert_eq!(kind, "EC key");
|
||||||
|
assert_eq!(value, "EC");
|
||||||
|
assert_eq!(exp, "ML-KEM-1024");
|
||||||
|
} else {
|
||||||
|
panic!("Expected RetrInvKey error");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-2
@@ -64,8 +64,11 @@ pub enum Error {
|
|||||||
#[error("No user-key for verification provided and user-data is signed")]
|
#[error("No user-key for verification provided and user-data is signed")]
|
||||||
BinAsrcbNoUserDataSgnKey,
|
BinAsrcbNoUserDataSgnKey,
|
||||||
|
|
||||||
#[error("Input does not contain an add-secret request version 1")]
|
#[error("Input contains an unknown add-secret request version {0}")]
|
||||||
BinAsrcbInvVersion,
|
BinAsrcbInvVersion(u32),
|
||||||
|
|
||||||
|
#[error("Unsupported add-secret request version: {0}")]
|
||||||
|
UnsupportedAddSecretVersion(u32),
|
||||||
|
|
||||||
#[error("Provided user-data key type ({key}) does not match with the user-data ({kind})")]
|
#[error("Provided user-data key type ({key}) does not match with the user-data ({kind})")]
|
||||||
AsrcbUserDataKeyMismatch { key: String, kind: UserDataType },
|
AsrcbUserDataKeyMismatch { key: String, kind: UserDataType },
|
||||||
@@ -84,6 +87,9 @@ pub enum Error {
|
|||||||
#[error("The provided host key document in {0} contains no certificate!")]
|
#[error("The provided host key document in {0} contains no certificate!")]
|
||||||
NoHkdInFile(String),
|
NoHkdInFile(String),
|
||||||
|
|
||||||
|
#[error("The provided host key document in {0} contains the wrong number of keys!")]
|
||||||
|
WrongNumberOfKeys(String),
|
||||||
|
|
||||||
#[error("Invalid input size ({0}) for boot hdr")]
|
#[error("Invalid input size ({0}) for boot hdr")]
|
||||||
InvBootHdrSize(usize),
|
InvBootHdrSize(usize),
|
||||||
|
|
||||||
@@ -135,6 +141,12 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("Unsupported cipher: {:?}", .0.as_raw())]
|
#[error("Unsupported cipher: {:?}", .0.as_raw())]
|
||||||
UnsupportedCipher(Nid),
|
UnsupportedCipher(Nid),
|
||||||
|
|
||||||
|
#[error("{}", .0)]
|
||||||
|
InvalidHkd(String),
|
||||||
|
|
||||||
|
#[error("All host keys must use the same version (all hybrid or all non-hybrid)")]
|
||||||
|
MixedHostkeyVersions,
|
||||||
}
|
}
|
||||||
|
|
||||||
// used in macros
|
// used in macros
|
||||||
|
|||||||
+5
-2
@@ -89,13 +89,16 @@ pub use crate::error::HkdVerifyErrorType;
|
|||||||
|
|
||||||
/// Functionalities to build UV requests
|
/// Functionalities to build UV requests
|
||||||
pub mod request {
|
pub mod request {
|
||||||
pub use crate::brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData};
|
pub use crate::brcb::{seek_se_hdr_start, BootHdrTags, SeHdrVersion, SeImgMetaData};
|
||||||
pub use crate::crypto::{
|
pub use crate::crypto::{
|
||||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
|
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
|
||||||
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, SymKeyType,
|
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, SymKeyType,
|
||||||
SHA_512_HASH_LEN,
|
SHA_512_HASH_LEN,
|
||||||
};
|
};
|
||||||
pub use crate::req::{EcPubKeyCoord, Encrypt, HostKey, Keyslot, ReqEncrCtx, Request};
|
pub use crate::req::{
|
||||||
|
EcPubKeyCoord, Encrypt, HostKey, HybridPKey, Keyslot, KeyslotV1, KeyslotV2, ReqEncrCtx,
|
||||||
|
Request,
|
||||||
|
};
|
||||||
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
|
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
|
||||||
|
|
||||||
/// Reexports some useful OpenSSL symbols
|
/// Reexports some useful OpenSSL symbols
|
||||||
|
|||||||
@@ -14,5 +14,4 @@ mod stackable_crl;
|
|||||||
pub use akid::*;
|
pub use akid::*;
|
||||||
pub use bio::*;
|
pub use bio::*;
|
||||||
pub use crl::*;
|
pub use crl::*;
|
||||||
#[expect(unused_imports)]
|
|
||||||
pub use ml_kem::*;
|
pub use ml_kem::*;
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ impl EcPubKeyCoord {
|
|||||||
|
|
||||||
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
|
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
|
||||||
/// The two coordinates are padded to 80 bytes each.
|
/// The two coordinates are padded to 80 bytes each.
|
||||||
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
|
pub(crate) fn get_pub_ecdh_points(
|
||||||
|
pkey: &EcPointRef,
|
||||||
|
grp: &EcGroupRef,
|
||||||
|
) -> Result<[u8; 160], ErrorStack> {
|
||||||
let mut x = BigNum::new()?;
|
let mut x = BigNum::new()?;
|
||||||
let mut y = BigNum::new()?;
|
let mut y = BigNum::new()?;
|
||||||
let mut bn_ctx = BigNumContext::new()?;
|
let mut bn_ctx = BigNumContext::new()?;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
// Copyright IBM Corp.
|
// Copyright IBM Corp.
|
||||||
|
|
||||||
//! Request header structure for UV requests.
|
//! Request header structure for UV requests.
|
||||||
|
|
||||||
|
use pv_core::request::{RequestMagic, RequestVersion};
|
||||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U32};
|
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U32};
|
||||||
|
|
||||||
use crate::assert_size;
|
use crate::assert_size;
|
||||||
@@ -25,13 +27,13 @@ pub struct RequestHdr {
|
|||||||
assert_size!(RequestHdr, 48);
|
assert_size!(RequestHdr, 48);
|
||||||
|
|
||||||
impl RequestHdr {
|
impl RequestHdr {
|
||||||
pub(crate) fn new(
|
pub(super) fn new(
|
||||||
rqvn: u32,
|
rqvn: RequestVersion,
|
||||||
rql: u32,
|
rql: u32,
|
||||||
iv: [u8; 12],
|
iv: [u8; 12],
|
||||||
nks: u8,
|
nks: u8,
|
||||||
sea: u32,
|
sea: u32,
|
||||||
magic: Option<[u8; 8]>,
|
magic: Option<RequestMagic>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
magic: magic.unwrap_or_default(),
|
magic: magic.unwrap_or_default(),
|
||||||
|
|||||||
+315
-3
@@ -4,7 +4,56 @@
|
|||||||
|
|
||||||
//! Host key types for UV requests
|
//! Host key types for UV requests
|
||||||
|
|
||||||
use openssl::pkey::{PKey, Public};
|
use openssl::nid::Nid;
|
||||||
|
use openssl::pkey::{KeyType, PKey, PKeyRef, Public};
|
||||||
|
|
||||||
|
use crate::crypto::{validate_ec_key, validate_key_type};
|
||||||
|
pub use crate::error::Result;
|
||||||
|
|
||||||
|
/// Hybrid public key (ECDH and ML-KEM)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct HybridPKey {
|
||||||
|
/// ECDH public key
|
||||||
|
pub(super) ec_key: PKey<Public>,
|
||||||
|
|
||||||
|
/// ML-KEM public key
|
||||||
|
pub(super) mlkem_key: PKey<Public>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HybridPKey {
|
||||||
|
/// Creates a new hybrid public key with validation.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `ec_key`: ECDH public key (must be SECP521R1)
|
||||||
|
/// - `mlkem_key`: ML-KEM public key (must be ML-KEM-1024)
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if:
|
||||||
|
/// - EC key is not SECP521R1 curve
|
||||||
|
/// - ML-KEM key is not ML-KEM-1024
|
||||||
|
pub fn new(ec_key: PKey<Public>, mlkem_key: PKey<Public>) -> Result<Self> {
|
||||||
|
validate_ec_key(&ec_key, "ECDH key", Nid::SECP521R1)?;
|
||||||
|
validate_key_type(&mlkem_key, "ML-KEM key", KeyType::ML_KEM_1024)?;
|
||||||
|
|
||||||
|
Ok(Self { ec_key, mlkem_key })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the EC key
|
||||||
|
pub fn ec_key(&self) -> &PKeyRef<Public> {
|
||||||
|
&self.ec_key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the ML-KEM key
|
||||||
|
pub fn mlkem_key(&self) -> &PKeyRef<Public> {
|
||||||
|
&self.mlkem_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<HybridPKey> for HybridPKey {
|
||||||
|
fn as_ref(&self) -> &HybridPKey {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Versioned host keys container
|
/// Versioned host keys container
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@@ -12,15 +61,33 @@ use openssl::pkey::{PKey, Public};
|
|||||||
pub enum HostKey {
|
pub enum HostKey {
|
||||||
/// ECDH public key
|
/// ECDH public key
|
||||||
V1(PKey<Public>),
|
V1(PKey<Public>),
|
||||||
|
|
||||||
|
/// Hybrid public key (ECDH and ML-KEM)
|
||||||
|
V2(HybridPKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostKey {
|
impl HostKey {
|
||||||
/// Return the ECDH public key
|
/// Return the ECDH public key
|
||||||
pub fn ec_key(&self) -> &PKey<Public> {
|
pub fn ec_key(&self) -> Option<&PKeyRef<Public>> {
|
||||||
match self {
|
Some(match self {
|
||||||
HostKey::V1(ec_key) => ec_key,
|
HostKey::V1(ec_key) => ec_key,
|
||||||
|
HostKey::V2(hybrid) => hybrid.ec_key(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the ML-KEM public key
|
||||||
|
pub fn mlkem_key(&self) -> Option<&PKeyRef<Public>> {
|
||||||
|
match self {
|
||||||
|
HostKey::V1(_) => None,
|
||||||
|
HostKey::V2(hybrid) => Some(hybrid.mlkem_key()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test if the hostkey is hybrid
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_hybrid(&self) -> bool {
|
||||||
|
matches!(self, HostKey::V2(_))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<HostKey> for HostKey {
|
impl AsRef<HostKey> for HostKey {
|
||||||
@@ -28,3 +95,248 @@ impl AsRef<HostKey> for HostKey {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use openssl::ec::{EcGroup, EcKey};
|
||||||
|
use openssl::pkey::Private;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::openssl_extensions::generate_ml_kem;
|
||||||
|
use crate::test_utils::get_test_key_and_cert_hybrid;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
fn to_public_key(key: &PKey<Private>) -> Result<PKey<Public>> {
|
||||||
|
let der = key.public_key_to_der()?;
|
||||||
|
Ok(PKey::public_key_from_der(&der)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hostkey_v1_variant() {
|
||||||
|
let (_, ec_key) = crate::test_utils::get_test_key_and_cert();
|
||||||
|
let hostkey = HostKey::V1(ec_key.public_key().unwrap());
|
||||||
|
|
||||||
|
assert!(!hostkey.is_hybrid(), "V1 HostKey should not be hybrid");
|
||||||
|
assert!(matches!(hostkey, HostKey::V1(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hostkey_v2_variant() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let hostkey = HostKey::V2(hybrid);
|
||||||
|
|
||||||
|
assert!(hostkey.is_hybrid(), "V2 HostKey should be hybrid");
|
||||||
|
assert!(matches!(hostkey, HostKey::V2(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hostkey_ec_key_access() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let v1_key = HostKey::V1(ec_key.public_key().unwrap());
|
||||||
|
let v2_key = HostKey::V2(hybrid);
|
||||||
|
|
||||||
|
assert!(v1_key.ec_key().unwrap().public_key_to_der().is_ok());
|
||||||
|
assert!(v2_key.ec_key().unwrap().public_key_to_der().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hostkey_v2_mlkem_key_access() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let hostkey = HostKey::V2(hybrid);
|
||||||
|
|
||||||
|
assert!(hostkey.mlkem_key().unwrap().public_key_to_der().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hostkey_v1_has_no_mlkem_key() {
|
||||||
|
let (_, ec_key) = crate::test_utils::get_test_key_and_cert();
|
||||||
|
let hostkey = HostKey::V1(ec_key.public_key().unwrap());
|
||||||
|
|
||||||
|
assert!(hostkey.mlkem_key().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hybrid_public_key_structure() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verify both components are present and valid
|
||||||
|
assert!(!hybrid.ec_key().public_key_to_der().unwrap().is_empty());
|
||||||
|
assert!(!hybrid.mlkem_key().public_key_to_der().unwrap().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hybrid_pkey_invalid_ec_curve() {
|
||||||
|
// Generate a P-256 key instead of P-521
|
||||||
|
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
|
||||||
|
let wrong_ec_key = PKey::from_ec_key(EcKey::generate(&group).unwrap()).unwrap();
|
||||||
|
|
||||||
|
let (_, _, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
|
||||||
|
let result = HybridPKey::new(
|
||||||
|
to_public_key(&wrong_ec_key).unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
);
|
||||||
|
assert!(result.is_err(), "Should reject EC key with wrong curve");
|
||||||
|
|
||||||
|
if let Err(Error::RetrInvKey {
|
||||||
|
what,
|
||||||
|
kind,
|
||||||
|
value,
|
||||||
|
exp,
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
assert_eq!(what, "curve");
|
||||||
|
assert_eq!(kind, "ECDH key");
|
||||||
|
assert_eq!(value, "prime256v1");
|
||||||
|
assert_eq!(exp, "secp521r1");
|
||||||
|
} else {
|
||||||
|
panic!("Expected RetrInvKey error for wrong curve");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hybrid_pkey_invalid_mlkem_type() {
|
||||||
|
let (_, ec_key, _) = get_test_key_and_cert_hybrid();
|
||||||
|
|
||||||
|
// Generate ML-KEM-512 instead of ML-KEM-1024
|
||||||
|
let wrong_mlkem_key = generate_ml_kem(KeyType::ML_KEM_512).unwrap();
|
||||||
|
|
||||||
|
let result = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
to_public_key(&wrong_mlkem_key).unwrap(),
|
||||||
|
);
|
||||||
|
assert!(result.is_err(), "Should reject ML-KEM key with wrong type");
|
||||||
|
|
||||||
|
if let Err(Error::RetrInvKey {
|
||||||
|
what,
|
||||||
|
kind,
|
||||||
|
value,
|
||||||
|
exp,
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
assert_eq!(what, "key type");
|
||||||
|
assert_eq!(kind, "ML-KEM key");
|
||||||
|
assert_eq!(value, "ML-KEM-512");
|
||||||
|
assert_eq!(exp, "ML-KEM-1024");
|
||||||
|
} else {
|
||||||
|
panic!("Expected RetrInvKey error for wrong ML-KEM type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hybrid_pkey_clone() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let cloned = hybrid.clone();
|
||||||
|
|
||||||
|
// Verify both original and clone have valid keys
|
||||||
|
assert!(!hybrid.ec_key().public_key_to_der().unwrap().is_empty());
|
||||||
|
assert!(!hybrid.mlkem_key().public_key_to_der().unwrap().is_empty());
|
||||||
|
assert!(!cloned.ec_key().public_key_to_der().unwrap().is_empty());
|
||||||
|
assert!(!cloned.mlkem_key().public_key_to_der().unwrap().is_empty());
|
||||||
|
|
||||||
|
// Verify the keys are equivalent
|
||||||
|
assert_eq!(
|
||||||
|
hybrid.ec_key().public_key_to_der().unwrap(),
|
||||||
|
cloned.ec_key().public_key_to_der().unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
hybrid.mlkem_key().public_key_to_der().unwrap(),
|
||||||
|
cloned.mlkem_key().public_key_to_der().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hostkey_clone() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Test cloning V1
|
||||||
|
let v1_key = HostKey::V1(ec_key.public_key().unwrap());
|
||||||
|
let v1_cloned = v1_key.clone();
|
||||||
|
assert!(!v1_cloned.is_hybrid());
|
||||||
|
assert_eq!(
|
||||||
|
v1_key.ec_key().unwrap().public_key_to_der().unwrap(),
|
||||||
|
v1_cloned.ec_key().unwrap().public_key_to_der().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test cloning V2
|
||||||
|
let v2_key = HostKey::V2(hybrid);
|
||||||
|
let v2_cloned = v2_key.clone();
|
||||||
|
assert!(v2_cloned.is_hybrid());
|
||||||
|
assert_eq!(
|
||||||
|
v2_key.ec_key().unwrap().public_key_to_der().unwrap(),
|
||||||
|
v2_cloned.ec_key().unwrap().public_key_to_der().unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
v2_key.mlkem_key().unwrap().public_key_to_der().unwrap(),
|
||||||
|
v2_cloned.mlkem_key().unwrap().public_key_to_der().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hybrid_pkey_as_ref() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let hybrid_ref: &HybridPKey = hybrid.as_ref();
|
||||||
|
assert!(!hybrid_ref.ec_key().public_key_to_der().unwrap().is_empty());
|
||||||
|
assert!(!hybrid_ref
|
||||||
|
.mlkem_key()
|
||||||
|
.public_key_to_der()
|
||||||
|
.unwrap()
|
||||||
|
.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hostkey_as_ref() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let v1_key = HostKey::V1(ec_key.public_key().unwrap());
|
||||||
|
let v1_ref: &HostKey = v1_key.as_ref();
|
||||||
|
assert!(!v1_ref.is_hybrid());
|
||||||
|
|
||||||
|
let v2_key = HostKey::V2(hybrid);
|
||||||
|
let v2_ref: &HostKey = v2_key.as_ref();
|
||||||
|
assert!(v2_ref.is_hybrid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+323
-6
@@ -4,13 +4,11 @@
|
|||||||
|
|
||||||
//! IBM Z Host key-slot implementations.
|
//! IBM Z Host key-slot implementations.
|
||||||
|
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::{DigestBytes, MessageDigest};
|
||||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||||
|
|
||||||
use super::ec_coord::EcPubKeyCoord;
|
use crate::crypto::{derive_aes256_gcm_key, derive_aes256_gcm_key_hybrid, encrypt_aead, hash};
|
||||||
use super::encrypt::Encrypt;
|
use crate::req::{EcPubKeyCoord, Encrypt, HostKey, HybridPKey};
|
||||||
use crate::crypto::{derive_aes256_gcm_key, encrypt_aead, hash};
|
|
||||||
use crate::request::HostKey;
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
/// IBM Z Host key-slot
|
/// IBM Z Host key-slot
|
||||||
@@ -71,12 +69,107 @@ impl Encrypt for KeyslotV1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// IBM Z hybrid (V2) Host key-slot
|
||||||
|
///
|
||||||
|
/// Layout in binary format:
|
||||||
|
/// ```none
|
||||||
|
/// _______________________________________________________________
|
||||||
|
/// | Public Host Key Hash (64) |
|
||||||
|
/// | Wrapped(=Encrypted) Request Protection Key(32) |
|
||||||
|
/// | Key Slot Tag (16) |
|
||||||
|
/// | ML-KEM Ciphertext (1568) |
|
||||||
|
/// |_____________________________________________________________|
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KeyslotV2 {
|
||||||
|
ec_hostkey: PKey<Public>,
|
||||||
|
mlkem_hostkey: PKey<Public>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyslotV2 {
|
||||||
|
/// Size of a hybrid host-key hash
|
||||||
|
pub const PHKH_SIZE: u32 = 0x40;
|
||||||
|
/// Size of a complete V2 keyslot in bytes
|
||||||
|
pub const SIZE: usize = 1680;
|
||||||
|
|
||||||
|
/// Creates a new HybridKeyslot from the provided hybrid public key
|
||||||
|
pub fn new(hostkey: HybridPKey) -> Self {
|
||||||
|
let HybridPKey { ec_key, mlkem_key } = hostkey;
|
||||||
|
Self {
|
||||||
|
ec_hostkey: ec_key,
|
||||||
|
mlkem_hostkey: mlkem_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// calculates the sha512 of this hybrid key
|
||||||
|
pub fn sha512(&self) -> Result<DigestBytes> {
|
||||||
|
let mut phk_buf = Vec::<u8>::with_capacity(160 + 1568);
|
||||||
|
let ec_phk: EcPubKeyCoord = self.ec_hostkey.as_ref().try_into()?;
|
||||||
|
phk_buf.extend_from_slice(ec_phk.as_ref());
|
||||||
|
phk_buf.extend_from_slice(&self.mlkem_hostkey.raw_public_key()?);
|
||||||
|
assert_eq!(phk_buf.len(), 160 + 1568);
|
||||||
|
|
||||||
|
let hash = hash(MessageDigest::sha512(), &phk_buf)?;
|
||||||
|
assert_eq!(hash.len(), 64);
|
||||||
|
Ok(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypts `secret` using `self` and `priv_key` the encryption.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// the encrypted data.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if OpenSSL could not encrypt the secret.
|
||||||
|
pub fn encrypt(&self, secret: &[u8], priv_key: &PKeyRef<Private>) -> Result<Vec<u8>> {
|
||||||
|
let mut res = Vec::with_capacity(1680);
|
||||||
|
self.encrypt_to(secret, priv_key, &mut res)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypts the given request protection key `prot_key`.
|
||||||
|
///
|
||||||
|
/// The AES256 encryption key is derived from `self` as public key, and `priv_key` as private
|
||||||
|
/// key.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The encrypted HybridKeyslot.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if OpenSSL could not encrypt the secret.
|
||||||
|
fn encrypt_to(
|
||||||
|
&self,
|
||||||
|
prot_key: &[u8],
|
||||||
|
priv_key: &PKeyRef<Private>,
|
||||||
|
to: &mut Vec<u8>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (derived_key, ciphertext) =
|
||||||
|
derive_aes256_gcm_key_hybrid(priv_key, &self.ec_hostkey, &self.mlkem_hostkey)?;
|
||||||
|
let mut wrpk_and_kst =
|
||||||
|
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
|
||||||
|
assert_eq!(wrpk_and_kst.len(), 48);
|
||||||
|
|
||||||
|
to.reserve(1680);
|
||||||
|
let hash = self.sha512()?;
|
||||||
|
assert_eq!(hash.len(), 64);
|
||||||
|
to.extend_from_slice(&hash);
|
||||||
|
to.append(&mut wrpk_and_kst);
|
||||||
|
assert_eq!(ciphertext.len(), 1568);
|
||||||
|
to.extend_from_slice(&ciphertext);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Versioned keyslot container
|
/// Versioned keyslot container
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Keyslot {
|
pub enum Keyslot {
|
||||||
/// V1 key-slots with ECDH keys
|
/// V1 key-slots with ECDH keys
|
||||||
V1(KeyslotV1),
|
V1(KeyslotV1),
|
||||||
|
/// V2 key-slots with hybrid ECDH/ML-KEM keys
|
||||||
|
V2(KeyslotV2),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyslot {
|
impl Keyslot {
|
||||||
@@ -84,27 +177,34 @@ impl Keyslot {
|
|||||||
pub fn new(hostkey: HostKey) -> Self {
|
pub fn new(hostkey: HostKey) -> Self {
|
||||||
match hostkey {
|
match hostkey {
|
||||||
HostKey::V1(key) => Keyslot::V1(KeyslotV1::new(key)),
|
HostKey::V1(key) => Keyslot::V1(KeyslotV1::new(key)),
|
||||||
|
HostKey::V2(key) => Keyslot::V2(KeyslotV2::new(key)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the public host key hash size for the given version of the key-slot in bytes
|
/// Return the public host key hash size for the given version of the key-slot in bytes
|
||||||
|
#[must_use]
|
||||||
pub fn phkh_size(&self) -> u32 {
|
pub fn phkh_size(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Keyslot::V1(_) => KeyslotV1::PHKH_SIZE,
|
Keyslot::V1(_) => KeyslotV1::PHKH_SIZE,
|
||||||
|
Keyslot::V2(_) => KeyslotV2::PHKH_SIZE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the size of the key-slot in bytes
|
/// Return the size of the key-slot in bytes
|
||||||
|
#[must_use]
|
||||||
pub fn size(&self) -> usize {
|
pub fn size(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Keyslot::V1(_) => KeyslotV1::SIZE,
|
Keyslot::V1(_) => KeyslotV1::SIZE,
|
||||||
|
Keyslot::V2(_) => KeyslotV2::SIZE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return whether the key-slot uses hybrid keys
|
/// Return whether the key-slot uses hybrid keys
|
||||||
|
#[must_use]
|
||||||
pub fn is_hybrid(&self) -> bool {
|
pub fn is_hybrid(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Keyslot::V1(_) => false,
|
Keyslot::V1(_) => false,
|
||||||
|
Keyslot::V2(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,6 +218,7 @@ impl Encrypt for Keyslot {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Keyslot::V1(ks) => ks.encrypt_to(secret, priv_key, to),
|
Keyslot::V1(ks) => ks.encrypt_to(secret, priv_key, to),
|
||||||
|
Keyslot::V2(ks) => ks.encrypt_to(secret, priv_key, to),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,11 +229,17 @@ impl From<PKey<Public>> for Keyslot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<HybridPKey> for Keyslot {
|
||||||
|
fn from(key: HybridPKey) -> Self {
|
||||||
|
Keyslot::V2(KeyslotV2::new(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::get_test_asset;
|
use crate::get_test_asset;
|
||||||
use crate::test_utils::*;
|
use crate::test_utils::{DeterministicTestRandGuard, *};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn keyslot() {
|
fn keyslot() {
|
||||||
@@ -147,4 +254,214 @@ mod tests {
|
|||||||
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
|
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
|
||||||
assert_ne!(exp_keyslot, encr_ks);
|
assert_ne!(exp_keyslot, encr_ks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyslot_v2() {
|
||||||
|
// Install deterministic RNG for reproducible encryption
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 64], &[0x17; 16]).unwrap();
|
||||||
|
|
||||||
|
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
|
||||||
|
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
|
||||||
|
let exp_keyslot = vec![
|
||||||
|
255, 94, 191, 53, 220, 196, 47, 37, 93, 227, 234, 101, 1, 174, 171, 68, 42, 136, 92,
|
||||||
|
238, 72, 6, 17, 77, 231, 225, 174, 22, 222, 188, 212, 15, 248, 145, 72, 126, 139, 17,
|
||||||
|
233, 225, 156, 46, 233, 151, 54, 2, 175, 88, 215, 254, 243, 222, 37, 81, 50, 110, 18,
|
||||||
|
76, 252, 12, 210, 146, 66, 23, 123, 246, 141, 14, 70, 157, 73, 124, 205, 112, 192, 82,
|
||||||
|
160, 243, 2, 154, 134, 145, 176, 147, 30, 217, 221, 93, 170, 239, 165, 37, 30, 192, 47,
|
||||||
|
94, 129, 165, 12, 137, 239, 12, 180, 199, 240, 160, 180, 205, 117, 84, 16, 148, 51, 48,
|
||||||
|
243, 216, 50, 132, 187, 115, 137, 3, 109, 145, 172, 159, 224, 25, 63, 96, 241, 105, 89,
|
||||||
|
35, 42, 180, 248, 35, 166, 81, 116, 175, 179, 252, 81, 188, 189, 42, 17, 60, 231, 132,
|
||||||
|
32, 111, 90, 138, 201, 16, 3, 97, 246, 130, 173, 85, 218, 187, 232, 92, 52, 120, 56,
|
||||||
|
142, 137, 101, 31, 21, 194, 186, 198, 38, 240, 58, 150, 93, 86, 18, 186, 5, 12, 41, 37,
|
||||||
|
94, 162, 119, 16, 237, 82, 252, 178, 159, 57, 58, 162, 20, 173, 140, 10, 172, 203, 241,
|
||||||
|
94, 110, 117, 32, 36, 217, 7, 168, 50, 105, 43, 33, 174, 140, 55, 88, 26, 233, 81, 253,
|
||||||
|
98, 121, 190, 70, 146, 84, 186, 6, 51, 181, 2, 59, 216, 45, 78, 4, 82, 90, 21, 180, 99,
|
||||||
|
239, 195, 254, 33, 171, 124, 97, 36, 110, 119, 142, 98, 125, 70, 236, 51, 192, 32, 8,
|
||||||
|
214, 224, 101, 169, 45, 173, 137, 194, 78, 114, 224, 76, 174, 188, 133, 227, 167, 169,
|
||||||
|
47, 241, 190, 15, 145, 217, 87, 254, 15, 150, 52, 138, 192, 230, 52, 129, 36, 8, 40,
|
||||||
|
37, 126, 103, 85, 86, 25, 78, 53, 243, 107, 193, 18, 102, 3, 83, 212, 5, 252, 224, 241,
|
||||||
|
7, 205, 220, 168, 183, 15, 37, 52, 190, 12, 9, 152, 124, 210, 65, 152, 210, 199, 81,
|
||||||
|
95, 240, 141, 196, 236, 8, 249, 221, 116, 130, 42, 52, 208, 218, 180, 27, 249, 59, 245,
|
||||||
|
21, 165, 142, 234, 140, 75, 89, 136, 250, 155, 157, 12, 154, 152, 188, 178, 227, 237,
|
||||||
|
124, 111, 176, 131, 227, 238, 164, 22, 88, 80, 217, 42, 217, 80, 148, 118, 12, 136, 95,
|
||||||
|
53, 69, 209, 131, 74, 91, 193, 253, 229, 146, 11, 58, 125, 19, 8, 229, 60, 177, 244,
|
||||||
|
74, 41, 92, 91, 62, 64, 221, 20, 213, 227, 4, 40, 22, 177, 155, 236, 147, 165, 27, 4,
|
||||||
|
99, 4, 151, 155, 82, 72, 151, 22, 102, 183, 192, 242, 18, 104, 146, 205, 107, 21, 254,
|
||||||
|
88, 23, 69, 246, 57, 217, 249, 124, 246, 54, 163, 244, 38, 74, 215, 144, 50, 45, 142,
|
||||||
|
36, 216, 88, 39, 70, 67, 88, 130, 1, 8, 205, 240, 159, 210, 205, 233, 237, 60, 81, 176,
|
||||||
|
112, 172, 187, 121, 239, 198, 43, 17, 49, 55, 170, 228, 243, 255, 76, 72, 121, 125,
|
||||||
|
157, 250, 93, 251, 55, 25, 4, 129, 67, 195, 30, 37, 6, 76, 10, 240, 178, 255, 151, 138,
|
||||||
|
36, 255, 32, 237, 133, 162, 130, 91, 9, 238, 67, 134, 4, 86, 225, 179, 166, 219, 4,
|
||||||
|
144, 7, 125, 248, 75, 132, 54, 56, 51, 237, 206, 252, 96, 9, 208, 140, 127, 143, 180,
|
||||||
|
32, 179, 254, 18, 233, 187, 122, 159, 172, 77, 204, 9, 179, 242, 153, 18, 138, 82, 13,
|
||||||
|
210, 140, 207, 9, 217, 216, 241, 106, 205, 109, 195, 223, 82, 172, 181, 48, 91, 124,
|
||||||
|
235, 85, 17, 18, 166, 216, 86, 120, 185, 54, 147, 156, 84, 106, 30, 235, 234, 114, 27,
|
||||||
|
23, 121, 210, 251, 136, 117, 53, 177, 28, 153, 144, 17, 70, 190, 206, 27, 135, 42, 227,
|
||||||
|
39, 243, 86, 170, 11, 63, 191, 102, 89, 92, 137, 103, 59, 142, 236, 203, 156, 231, 255,
|
||||||
|
30, 5, 86, 53, 59, 76, 181, 218, 184, 244, 21, 50, 100, 72, 81, 98, 193, 34, 58, 67,
|
||||||
|
150, 15, 17, 162, 216, 119, 213, 203, 50, 27, 158, 61, 105, 134, 151, 200, 68, 103, 74,
|
||||||
|
207, 12, 5, 163, 30, 198, 28, 41, 41, 97, 93, 251, 213, 133, 41, 225, 178, 67, 160, 85,
|
||||||
|
132, 146, 1, 201, 99, 49, 185, 27, 150, 213, 165, 134, 45, 248, 204, 67, 145, 49, 81,
|
||||||
|
35, 246, 25, 215, 209, 159, 106, 212, 14, 149, 193, 163, 90, 24, 83, 230, 178, 216,
|
||||||
|
194, 130, 118, 169, 81, 49, 49, 145, 96, 206, 216, 15, 134, 43, 130, 25, 110, 84, 39,
|
||||||
|
175, 223, 183, 209, 123, 5, 166, 244, 19, 89, 2, 47, 226, 3, 93, 156, 163, 67, 156,
|
||||||
|
237, 17, 59, 69, 99, 94, 32, 180, 64, 64, 115, 75, 44, 241, 150, 43, 169, 17, 36, 125,
|
||||||
|
216, 132, 92, 3, 244, 16, 83, 179, 192, 65, 133, 208, 19, 156, 17, 60, 54, 29, 253,
|
||||||
|
237, 18, 158, 145, 142, 232, 147, 2, 91, 21, 145, 125, 204, 243, 161, 245, 110, 140,
|
||||||
|
219, 206, 70, 235, 211, 167, 138, 104, 132, 248, 157, 65, 153, 216, 47, 125, 205, 237,
|
||||||
|
137, 220, 25, 228, 146, 194, 10, 169, 201, 224, 88, 119, 38, 140, 120, 125, 140, 46,
|
||||||
|
184, 221, 30, 10, 47, 34, 140, 173, 64, 38, 48, 77, 236, 206, 163, 111, 80, 46, 40,
|
||||||
|
232, 63, 247, 222, 25, 20, 246, 143, 9, 107, 172, 180, 84, 188, 234, 102, 87, 181, 173,
|
||||||
|
83, 14, 163, 170, 91, 29, 209, 93, 52, 158, 213, 6, 91, 71, 54, 244, 189, 198, 60, 21,
|
||||||
|
131, 210, 35, 18, 36, 164, 188, 87, 54, 73, 208, 115, 11, 248, 57, 107, 93, 23, 49,
|
||||||
|
129, 221, 61, 12, 172, 31, 199, 129, 196, 5, 184, 78, 226, 210, 83, 232, 153, 64, 17,
|
||||||
|
119, 243, 45, 73, 50, 129, 35, 94, 243, 146, 86, 136, 202, 86, 87, 97, 193, 59, 160,
|
||||||
|
181, 95, 150, 148, 117, 96, 31, 151, 97, 159, 53, 72, 171, 239, 210, 67, 207, 201, 109,
|
||||||
|
160, 230, 235, 176, 35, 235, 98, 128, 166, 195, 144, 200, 156, 7, 72, 242, 15, 95, 99,
|
||||||
|
69, 41, 219, 254, 244, 80, 158, 177, 64, 83, 11, 235, 98, 201, 130, 176, 119, 22, 214,
|
||||||
|
135, 215, 37, 65, 108, 91, 34, 94, 0, 153, 161, 87, 144, 177, 12, 122, 205, 11, 72,
|
||||||
|
233, 213, 63, 37, 12, 255, 235, 188, 194, 189, 65, 169, 185, 207, 131, 182, 243, 233,
|
||||||
|
30, 245, 39, 42, 188, 157, 28, 121, 231, 194, 121, 250, 121, 11, 28, 252, 98, 151, 238,
|
||||||
|
124, 56, 138, 106, 116, 187, 203, 21, 75, 239, 132, 19, 62, 43, 42, 12, 214, 87, 225,
|
||||||
|
217, 169, 134, 183, 193, 241, 175, 140, 36, 147, 248, 86, 6, 58, 212, 72, 112, 0, 56,
|
||||||
|
168, 128, 68, 253, 173, 225, 152, 167, 138, 254, 97, 123, 227, 163, 135, 198, 49, 49,
|
||||||
|
138, 249, 234, 245, 78, 150, 140, 170, 199, 41, 206, 246, 19, 117, 241, 27, 112, 102,
|
||||||
|
4, 94, 237, 30, 0, 68, 252, 163, 205, 10, 63, 146, 147, 37, 153, 197, 52, 162, 50, 250,
|
||||||
|
219, 130, 197, 3, 60, 246, 133, 83, 140, 103, 227, 50, 212, 121, 165, 114, 139, 225,
|
||||||
|
195, 145, 107, 249, 127, 194, 23, 112, 141, 242, 14, 218, 42, 131, 19, 245, 143, 73,
|
||||||
|
79, 194, 135, 224, 171, 249, 169, 129, 160, 153, 75, 66, 26, 12, 254, 180, 212, 229,
|
||||||
|
172, 134, 159, 122, 212, 219, 21, 29, 33, 11, 176, 149, 73, 169, 26, 150, 96, 133, 90,
|
||||||
|
217, 18, 37, 244, 48, 249, 4, 180, 129, 9, 45, 219, 106, 215, 28, 81, 118, 48, 98, 109,
|
||||||
|
167, 72, 107, 187, 78, 127, 251, 184, 170, 74, 57, 188, 91, 196, 229, 251, 70, 163, 68,
|
||||||
|
227, 238, 71, 115, 155, 246, 146, 97, 208, 21, 245, 62, 127, 47, 79, 131, 217, 41, 153,
|
||||||
|
52, 237, 159, 60, 98, 18, 169, 149, 6, 84, 135, 2, 45, 170, 165, 213, 201, 127, 23,
|
||||||
|
209, 2, 158, 235, 240, 195, 255, 76, 54, 189, 113, 50, 105, 230, 191, 217, 29, 46, 181,
|
||||||
|
80, 197, 60, 177, 243, 61, 52, 24, 140, 134, 147, 176, 198, 22, 92, 156, 217, 189, 134,
|
||||||
|
17, 122, 53, 49, 14, 87, 128, 99, 207, 123, 113, 169, 195, 206, 127, 211, 21, 216, 166,
|
||||||
|
18, 137, 110, 148, 70, 26, 54, 52, 113, 189, 45, 89, 254, 218, 247, 193, 71, 19, 153,
|
||||||
|
35, 179, 49, 237, 199, 176, 251, 143, 95, 115, 195, 3, 122, 161, 243, 220, 39, 102,
|
||||||
|
147, 134, 25, 172, 164, 167, 110, 123, 221, 177, 28, 236, 100, 165, 186, 179, 45, 189,
|
||||||
|
183, 76, 171, 127, 209, 108, 220, 83, 207, 136, 98, 54, 76, 37, 188, 57, 157, 204, 109,
|
||||||
|
150, 181, 110, 57, 5, 26, 168, 34, 11, 117, 3, 184, 147, 155, 122, 244, 251, 215, 1,
|
||||||
|
211, 226, 185, 214, 120, 206, 212, 75, 203, 174, 140, 20, 93, 93, 207, 28, 15, 122, 9,
|
||||||
|
83, 98, 107, 51, 202, 151, 220, 42, 95, 17, 141, 141, 201, 149, 253, 55, 169, 170, 237,
|
||||||
|
166, 92, 92, 20, 89, 124, 167, 102, 161, 87, 97, 88, 20, 245, 175, 32, 111, 0, 2, 192,
|
||||||
|
25, 63, 182, 10, 226, 165, 162, 223, 35, 243, 198, 189, 167, 137, 134, 207, 84, 240, 8,
|
||||||
|
95, 137, 31, 159, 58, 211, 161, 94, 150, 54, 188, 145, 253, 206, 156, 130, 121, 192,
|
||||||
|
221, 73, 249, 92, 91, 184, 211, 131, 206, 205, 183, 159, 195, 170, 47, 13, 131, 6, 132,
|
||||||
|
103, 121, 40, 252, 250, 251, 85, 253, 68, 66, 211, 24, 104, 48, 150, 176, 62, 201, 161,
|
||||||
|
93, 204, 120, 196, 159, 208, 199, 96, 228, 239, 29, 239, 128, 156, 14, 131, 25, 27,
|
||||||
|
157, 249, 253, 250, 193, 148, 133, 59, 138, 202, 239,
|
||||||
|
];
|
||||||
|
|
||||||
|
let keyslot = Keyslot::new(host_key);
|
||||||
|
let encr_keyslot = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(encr_keyslot, exp_keyslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_v2_constants() {
|
||||||
|
// Test V2 keyslot constants
|
||||||
|
assert_eq!(
|
||||||
|
KeyslotV2::SIZE,
|
||||||
|
1680,
|
||||||
|
"V2 keyslot size should be 1680 bytes"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
KeyslotV2::PHKH_SIZE,
|
||||||
|
0x40,
|
||||||
|
"V2 PHKH size should be 64 bytes (SHA-512)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_v2_creation() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let keyslot = KeyslotV2::new(hybrid);
|
||||||
|
|
||||||
|
// Verify keyslot was created successfully
|
||||||
|
assert!(keyslot.ec_hostkey.public_key_to_der().is_ok());
|
||||||
|
assert!(keyslot.mlkem_hostkey.public_key_to_der().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_enum_v1_variant() {
|
||||||
|
let (_, host_key) = get_test_keys();
|
||||||
|
let keyslot = Keyslot::V1(KeyslotV1(host_key));
|
||||||
|
|
||||||
|
assert_eq!(keyslot.phkh_size(), KeyslotV1::PHKH_SIZE);
|
||||||
|
assert_eq!(keyslot.size(), KeyslotV1::SIZE);
|
||||||
|
assert!(matches!(keyslot, Keyslot::V1(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_enum_v2_variant() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let keyslot = Keyslot::V2(KeyslotV2::new(hybrid));
|
||||||
|
|
||||||
|
assert_eq!(keyslot.phkh_size(), KeyslotV2::PHKH_SIZE);
|
||||||
|
assert_eq!(keyslot.size(), KeyslotV2::SIZE);
|
||||||
|
assert!(matches!(keyslot, Keyslot::V2(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_from_hostkey_v1() {
|
||||||
|
let (_, ec_key) = get_test_keys();
|
||||||
|
let hostkey = HostKey::V1(ec_key);
|
||||||
|
let keyslot = Keyslot::new(hostkey);
|
||||||
|
|
||||||
|
assert!(matches!(keyslot, Keyslot::V1(_)));
|
||||||
|
assert_eq!(keyslot.size(), KeyslotV1::SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_from_hostkey_v2() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let hostkey = HostKey::V2(hybrid);
|
||||||
|
let keyslot = Keyslot::new(hostkey);
|
||||||
|
|
||||||
|
assert!(matches!(keyslot, Keyslot::V2(_)));
|
||||||
|
assert_eq!(keyslot.size(), KeyslotV2::SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_from_hybrid_public_key() {
|
||||||
|
let (_, ec_key, mlkem_key) = get_test_key_and_cert_hybrid();
|
||||||
|
let hybrid = HybridPKey::new(
|
||||||
|
ec_key.public_key().unwrap(),
|
||||||
|
mlkem_key.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let keyslot: Keyslot = hybrid.into();
|
||||||
|
assert!(matches!(keyslot, Keyslot::V2(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_keyslot_sizes() {
|
||||||
|
// Document the sizes for V1 and V2
|
||||||
|
assert_eq!(KeyslotV1::SIZE, 80, "V1 keyslot size");
|
||||||
|
assert_eq!(
|
||||||
|
KeyslotV2::SIZE,
|
||||||
|
1680,
|
||||||
|
"V2 keyslot size (includes ML-KEM ciphertext)"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ mod request;
|
|||||||
|
|
||||||
// Re-export public types
|
// Re-export public types
|
||||||
pub use context::ReqEncrCtx;
|
pub use context::ReqEncrCtx;
|
||||||
|
pub(crate) use ec_coord::get_pub_ecdh_points;
|
||||||
pub use ec_coord::EcPubKeyCoord;
|
pub use ec_coord::EcPubKeyCoord;
|
||||||
pub use encrypt::{Aad, Encrypt};
|
pub use encrypt::{Aad, Encrypt};
|
||||||
pub use header::RequestHdr;
|
pub use header::RequestHdr;
|
||||||
pub use hostkey::HostKey;
|
pub use hostkey::{HostKey, HybridPKey};
|
||||||
pub use keyslot::{Keyslot, KeyslotV1};
|
pub use keyslot::{Keyslot, KeyslotV1, KeyslotV2};
|
||||||
pub use request::{BinReqValues, Request};
|
pub use request::{BinReqValues, Request};
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ pub trait Request {
|
|||||||
/// Add a host-key to this request
|
/// Add a host-key to this request
|
||||||
///
|
///
|
||||||
/// Must be called at least once, otherwise {`Request::encrypt`} will fail
|
/// Must be called at least once, otherwise {`Request::encrypt`} will fail
|
||||||
fn add_hostkey(&mut self, hostkey: HostKey);
|
fn add_hostkey(&mut self, hostkey: HostKey) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct to represent some parts of a binary/encrypted request.
|
/// A struct to represent some parts of a binary/encrypted request.
|
||||||
@@ -148,10 +148,9 @@ mod tests {
|
|||||||
use zerocopy::IntoBytes;
|
use zerocopy::IntoBytes;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::crypto::SymKey;
|
||||||
use crate::get_test_asset;
|
use crate::get_test_asset;
|
||||||
use crate::req::header::RequestHdr;
|
use crate::req::{Aad, HybridPKey, Keyslot};
|
||||||
use crate::req::{Aad, HostKey, Keyslot, ReqEncrCtx};
|
|
||||||
use crate::request::SymKey;
|
|
||||||
use crate::test_utils::*;
|
use crate::test_utils::*;
|
||||||
|
|
||||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||||
@@ -187,6 +186,37 @@ mod tests {
|
|||||||
assert_eq!(&aad, &aad_exp);
|
assert_eq!(&aad, &aad_exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encr_build_aad_v2() {
|
||||||
|
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
|
||||||
|
let ks = Keyslot::new(HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap()));
|
||||||
|
let ctx = ReqEncrCtx::new_aes_256(
|
||||||
|
Some([0x11; 12]),
|
||||||
|
Some(cust_key),
|
||||||
|
Some(SymKey::Aes256([0x17; 32].into())),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let v = [0x55; 8];
|
||||||
|
let aad = Aad::Plain(&v);
|
||||||
|
let aad = ctx
|
||||||
|
.build_aad(0x200, &vec![aad, Aad::Ks(&ks)], 16, Some(TEST_MAGIC))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let aad_exp = vec![
|
||||||
|
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
|
||||||
|
0, 0, 2, 0, // vers
|
||||||
|
0, 0, 6, 232, // size
|
||||||
|
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, // iv
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // res
|
||||||
|
1, // nks
|
||||||
|
0, 0, 0, 0, // res
|
||||||
|
0, 0, 0, 16, // sea
|
||||||
|
0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // aad
|
||||||
|
];
|
||||||
|
// only compare non-randomized part
|
||||||
|
assert_eq!(aad[..aad_exp.len()], aad_exp);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encr_build_aad_nks_no() {
|
fn encr_build_aad_nks_no() {
|
||||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||||
@@ -210,6 +240,21 @@ mod tests {
|
|||||||
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
||||||
assert!(matches!(aad, Err(Error::ManyHostkeys)));
|
assert!(matches!(aad, Err(Error::ManyHostkeys)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encr_build_aad_nks_many_v2() {
|
||||||
|
let (_, host_key1, host_key2) = get_test_keys_hybrid();
|
||||||
|
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
|
||||||
|
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||||
|
|
||||||
|
let ks: Vec<Keyslot> = (0..257).map(|_| Keyslot::new(host_key.clone())).collect();
|
||||||
|
let mut aad = Vec::<Aad>::new();
|
||||||
|
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
|
||||||
|
|
||||||
|
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
||||||
|
assert!(matches!(aad, Err(Error::ManyHostkeys)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encr_build_aad_nks() {
|
fn encr_build_aad_nks() {
|
||||||
let (_, host_key) = get_test_keys();
|
let (_, host_key) = get_test_keys();
|
||||||
@@ -229,6 +274,25 @@ mod tests {
|
|||||||
assert_eq!(aad.get(39).unwrap(), &3u8);
|
assert_eq!(aad.get(39).unwrap(), &3u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encr_build_aad_nks_v2() {
|
||||||
|
let (_, host_key1, host_key2) = get_test_keys_hybrid();
|
||||||
|
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
|
||||||
|
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||||
|
|
||||||
|
let ks = [
|
||||||
|
Keyslot::new(host_key.clone()),
|
||||||
|
Keyslot::new(host_key.clone()),
|
||||||
|
Keyslot::new(host_key),
|
||||||
|
];
|
||||||
|
let mut aad = Vec::<Aad>::new();
|
||||||
|
ks.iter().for_each(|ks| aad.push(Aad::Ks(ks)));
|
||||||
|
|
||||||
|
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(aad.get(39).unwrap(), &3u8);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn req_hdr() {
|
fn req_hdr() {
|
||||||
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
|
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
|
||||||
|
|||||||
@@ -89,6 +89,27 @@ pub fn get_test_key_and_cert() -> (PKey<Private>, X509) {
|
|||||||
(cust_key, host_key)
|
(cust_key, host_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_test_key_and_cert_hybrid() -> (PKey<Private>, X509, X509) {
|
||||||
|
let pub_key = get_test_asset!("keys/public_cust.bin");
|
||||||
|
let priv_key = get_test_asset!("keys/private_cust.bin");
|
||||||
|
let host_key = get_test_asset!("keys/host.ec.crt");
|
||||||
|
let host_keys = get_test_asset!("keys/host.hybrid.crt");
|
||||||
|
|
||||||
|
assert_eq!(pub_key.len(), 160);
|
||||||
|
assert_eq!(priv_key.len(), 80);
|
||||||
|
|
||||||
|
let cust_key = get_keypair(pub_key, priv_key).unwrap();
|
||||||
|
let host_key1 = X509::from_pem(host_key).unwrap();
|
||||||
|
let host_keys = X509::stack_from_pem(host_keys).unwrap();
|
||||||
|
assert_eq!(host_keys.len(), 2);
|
||||||
|
|
||||||
|
println!("host_key1 = {host_key1:?}");
|
||||||
|
println!("host_keys[0] = {:?}", host_keys[0]);
|
||||||
|
println!("host_keys[1] = {:?}", host_keys[1]);
|
||||||
|
|
||||||
|
(cust_key, host_key1, host_keys[1].clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// TEST ONLY! Get a fixed private/public pair and a fixed public key
|
/// TEST ONLY! Get a fixed private/public pair and a fixed public key
|
||||||
///
|
///
|
||||||
/// Intended for TESTING only. All parts of the key including the private key are checked in git and
|
/// Intended for TESTING only. All parts of the key including the private key are checked in git and
|
||||||
@@ -98,6 +119,15 @@ pub fn get_test_keys() -> (PKey<Private>, PKey<Public>) {
|
|||||||
(cust_key, host.public_key().unwrap())
|
(cust_key, host.public_key().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_test_keys_hybrid() -> (PKey<Private>, PKey<Public>, PKey<Public>) {
|
||||||
|
let (cust_key, host_key_1, host_key_2) = get_test_key_and_cert_hybrid();
|
||||||
|
(
|
||||||
|
cust_key,
|
||||||
|
host_key_1.public_key().unwrap(),
|
||||||
|
host_key_2.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_ecdh_pubkey(coords: &[u8]) -> Result<PKey<Public>, ErrorStack> {
|
fn read_ecdh_pubkey(coords: &[u8]) -> Result<PKey<Public>, ErrorStack> {
|
||||||
assert!(coords.len() == 160);
|
assert!(coords.len() == 160);
|
||||||
let x = BigNum::from_slice(&coords[..80])?;
|
let x = BigNum::from_slice(&coords[..80])?;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use pv_core::static_assert;
|
||||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||||
|
|
||||||
use super::additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE};
|
use super::additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE};
|
||||||
@@ -14,7 +15,7 @@ use crate::misc::Flags;
|
|||||||
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
|
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
|
||||||
use crate::request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize};
|
use crate::request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize};
|
||||||
use crate::uv::UvFlags;
|
use crate::uv::UvFlags;
|
||||||
use crate::{assert_size, static_assert, Error, Result};
|
use crate::{assert_size, Error, Result};
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::{
|
use crate::{
|
||||||
request::SymKeyType,
|
request::SymKeyType,
|
||||||
@@ -60,9 +61,9 @@ use crate::{
|
|||||||
/// let hkd = s390_pv::misc::read_certs(&std::fs::read("host-key-document.crt")?)?;
|
/// let hkd = s390_pv::misc::read_certs(&std::fs::read("host-key-document.crt")?)?;
|
||||||
/// // IBM issued HKD certificates typically have one X509
|
/// // IBM issued HKD certificates typically have one X509
|
||||||
/// let hkd = hkd.first().unwrap().public_key()?;
|
/// let hkd = hkd.first().unwrap().public_key()?;
|
||||||
/// arcb.add_hostkey(HostKey::V1(hkd));
|
/// arcb.add_hostkey(HostKey::V1(hkd))?;
|
||||||
/// // you can add multiple hostkeys
|
/// // you can add multiple hostkeys
|
||||||
/// // arcb.add_hostkey(another_hkd);
|
/// // arcb.add_hostkey(HostKey::V1(another_hkd))?;
|
||||||
/// // encrypt it
|
/// // encrypt it
|
||||||
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
|
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
|
||||||
/// let arcb = arcb.encrypt(&ctx)?;
|
/// let arcb = arcb.encrypt(&ctx)?;
|
||||||
@@ -106,7 +107,13 @@ impl AttestationRequest {
|
|||||||
|
|
||||||
/// Returns a reference to the flags of this [`AttestationRequest`].
|
/// Returns a reference to the flags of this [`AttestationRequest`].
|
||||||
pub fn flags(&self) -> &AttestationFlags {
|
pub fn flags(&self) -> &AttestationFlags {
|
||||||
&self.aad.flags
|
self.aad.flags()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the request version, derived from the type of added host-keys.
|
||||||
|
/// Returns [`AttestationVersion::One`] if no host-keys have been added yet.
|
||||||
|
pub fn version(&self) -> AttestationVersion {
|
||||||
|
self.version
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a copy of the confidential data of this [`AttestationRequest`].
|
/// Returns a copy of the confidential data of this [`AttestationRequest`].
|
||||||
@@ -142,6 +149,7 @@ impl AttestationRequest {
|
|||||||
let values = BinReqValues::get(arcb)?;
|
let values = BinReqValues::get(arcb)?;
|
||||||
match values.version().try_into()? {
|
match values.version().try_into()? {
|
||||||
AttestationVersion::One => (),
|
AttestationVersion::One => (),
|
||||||
|
AttestationVersion::Two => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(values)
|
Ok(values)
|
||||||
@@ -235,8 +243,19 @@ impl Request for AttestationRequest {
|
|||||||
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
|
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_hostkey(&mut self, hostkey: HostKey) {
|
fn add_hostkey(&mut self, hostkey: HostKey) -> Result<()> {
|
||||||
self.keyslots.push(Keyslot::new(hostkey))
|
match self.version {
|
||||||
|
AttestationVersion::One if !hostkey.is_hybrid() => Ok(()),
|
||||||
|
AttestationVersion::Two if hostkey.is_hybrid() => Ok(()),
|
||||||
|
AttestationVersion::One => Err(Error::InvalidHkd(
|
||||||
|
"Add classical hostkey to a v1 attestation request".to_string(),
|
||||||
|
)),
|
||||||
|
AttestationVersion::Two => Err(Error::InvalidHkd(
|
||||||
|
"Add hybrid key to a v2 attetstation request".to_string(),
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
self.keyslots.push(Keyslot::new(hostkey));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +265,8 @@ impl Request for AttestationRequest {
|
|||||||
pub enum AttestationVersion {
|
pub enum AttestationVersion {
|
||||||
/// Version 1 (= 0x0100)
|
/// Version 1 (= 0x0100)
|
||||||
One = 0x0100,
|
One = 0x0100,
|
||||||
|
/// Version 2 (= 0x0200)
|
||||||
|
Two = 0x0200,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u32> for AttestationVersion {
|
impl TryFrom<u32> for AttestationVersion {
|
||||||
@@ -254,6 +275,8 @@ impl TryFrom<u32> for AttestationVersion {
|
|||||||
fn try_from(value: u32) -> Result<Self> {
|
fn try_from(value: u32) -> Result<Self> {
|
||||||
if value == Self::One as u32 {
|
if value == Self::One as u32 {
|
||||||
Ok(Self::One)
|
Ok(Self::One)
|
||||||
|
} else if value == Self::Two as u32 {
|
||||||
|
Ok(Self::Two)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BinArcbInvVersion(value))
|
Err(Error::BinArcbInvVersion(value))
|
||||||
}
|
}
|
||||||
@@ -412,8 +435,8 @@ impl Zeroize for ReqConfData {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::get_test_asset;
|
use crate::get_test_asset;
|
||||||
use crate::request::SymKey;
|
use crate::request::{HybridPKey, SymKey};
|
||||||
use crate::test_utils::get_test_keys;
|
use crate::test_utils::{get_test_keys, get_test_keys_hybrid};
|
||||||
|
|
||||||
const ARPK: [u8; 32] = [0x17; 32];
|
const ARPK: [u8; 32] = [0x17; 32];
|
||||||
const NONCE: [u8; 16] = [0xab; 16];
|
const NONCE: [u8; 16] = [0xab; 16];
|
||||||
@@ -421,6 +444,8 @@ mod test {
|
|||||||
|
|
||||||
fn mk_arcb() -> Vec<u8> {
|
fn mk_arcb() -> Vec<u8> {
|
||||||
let (cust_key, host_key) = get_test_keys();
|
let (cust_key, host_key) = get_test_keys();
|
||||||
|
let host_key = HostKey::V1(host_key);
|
||||||
|
|
||||||
let ctx = ReqEncrCtx::new_aes_256(
|
let ctx = ReqEncrCtx::new_aes_256(
|
||||||
Some([0x55; 12]),
|
Some([0x55; 12]),
|
||||||
Some(cust_key),
|
Some(cust_key),
|
||||||
@@ -443,7 +468,37 @@ mod test {
|
|||||||
arcb.conf.value_mut().nonce = NONCE;
|
arcb.conf.value_mut().nonce = NONCE;
|
||||||
arcb.conf.value_mut().meas_key = MEAS;
|
arcb.conf.value_mut().meas_key = MEAS;
|
||||||
|
|
||||||
arcb.add_hostkey(HostKey::V1(host_key));
|
arcb.add_hostkey(host_key).unwrap();
|
||||||
|
arcb.encrypt(&ctx).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mk_arcb_v2() -> Vec<u8> {
|
||||||
|
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
|
||||||
|
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
|
||||||
|
|
||||||
|
let ctx = ReqEncrCtx::new_aes_256(
|
||||||
|
Some([0x55; 12]),
|
||||||
|
Some(cust_key),
|
||||||
|
Some(SymKey::Aes256(ARPK.into())),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut flags = AttestationFlags::default();
|
||||||
|
flags.set_image_phkh();
|
||||||
|
flags.set_attest_phkh();
|
||||||
|
|
||||||
|
let mut arcb = AttestationRequest::new(
|
||||||
|
AttestationVersion::Two,
|
||||||
|
AttestationMeasAlg::HmacSha512,
|
||||||
|
flags,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// manually set confidential data (API does not allow this)
|
||||||
|
arcb.conf.value_mut().nonce = NONCE;
|
||||||
|
arcb.conf.value_mut().meas_key = MEAS;
|
||||||
|
|
||||||
|
arcb.add_hostkey(host_key).unwrap();
|
||||||
arcb.encrypt(&ctx).unwrap()
|
arcb.encrypt(&ctx).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,6 +510,32 @@ mod test {
|
|||||||
assert_eq!(request, exp);
|
assert_eq!(request, exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arcb_v2() {
|
||||||
|
let request = mk_arcb_v2();
|
||||||
|
// Expected bytes for a V2 ARCB: rqvn = 0x0200 (bytes 8-11), rest deterministic.
|
||||||
|
// The first 288 bytes cover header + customer-public-key + one V2 keyslot header.
|
||||||
|
let exp: [u8; 288] = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 7, 208, 85, 85, 85, 85, 85, 85, 85, 85, 85,
|
||||||
|
85, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 80, 112, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199,
|
||||||
|
93, 52, 249, 22, 82, 219, 69, 123, 11, 32, 156, 70, 164, 145, 164, 78, 226, 177, 110,
|
||||||
|
35, 194, 216, 218, 241, 22, 103, 138, 98, 242, 76, 227, 50, 197, 153, 95, 8, 69, 107,
|
||||||
|
102, 177, 109, 213, 90, 146, 197, 7, 241, 227, 26, 247, 140, 100, 168, 46, 122, 84, 27,
|
||||||
|
21, 19, 80, 21, 242, 2, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 128, 88,
|
||||||
|
167, 241, 165, 195, 80, 151, 83, 58, 2, 169, 56, 121, 231, 222, 103, 186, 40, 11, 206,
|
||||||
|
131, 101, 236, 148, 178, 185, 8, 245, 137, 195, 169, 152, 216, 190, 30, 99, 7, 215, 74,
|
||||||
|
224, 26, 220, 70, 130, 95, 246, 187, 111, 160, 92, 17, 71, 207, 226, 204, 244, 162, 79,
|
||||||
|
61, 131, 61, 218, 112, 255, 94, 191, 53, 220, 196, 47, 37, 93, 227, 234, 101, 1, 174,
|
||||||
|
171, 68, 42, 136, 92, 238, 72, 6, 17, 77, 231, 225, 174, 22, 222, 188, 212, 15, 248,
|
||||||
|
145, 72, 126, 139, 17, 233, 225, 156, 46, 233, 151, 54, 2, 175, 88, 215, 254, 243, 222,
|
||||||
|
37, 81, 50, 110, 18, 76, 252, 12, 210, 146, 66, 23,
|
||||||
|
];
|
||||||
|
|
||||||
|
// only compare non-randomized part
|
||||||
|
assert_eq!(request[..288], exp[..288]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn auth_bin() {
|
fn auth_bin() {
|
||||||
let request = mk_arcb();
|
let request = mk_arcb();
|
||||||
@@ -464,6 +545,15 @@ mod test {
|
|||||||
assert_eq!(exp, auth_bin.as_bytes());
|
assert_eq!(exp, auth_bin.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_bin_v2() {
|
||||||
|
let request = mk_arcb_v2();
|
||||||
|
let auth_bin = AttestationRequest::auth_bin(&request).unwrap();
|
||||||
|
let exp = &request[0x30..0x40];
|
||||||
|
|
||||||
|
assert_eq!(exp, auth_bin.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decrypt_bin() {
|
fn decrypt_bin() {
|
||||||
let request = mk_arcb();
|
let request = mk_arcb();
|
||||||
@@ -473,6 +563,100 @@ mod test {
|
|||||||
assert_eq!(conf.nonce().as_ref().unwrap().value(), &NONCE);
|
assert_eq!(conf.nonce().as_ref().unwrap().value(), &NONCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decrypt_bin_v2() {
|
||||||
|
let request = mk_arcb_v2();
|
||||||
|
let arpk = SymKey::Aes256(ARPK.into());
|
||||||
|
let (_, conf) = AttestationRequest::decrypt_bin(&request, &arpk).unwrap();
|
||||||
|
assert_eq!(conf.measurement_key(), &MEAS);
|
||||||
|
assert_eq!(conf.nonce().as_ref().unwrap().value(), &NONCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arcb_v1_version() {
|
||||||
|
// Without any host-keys, version defaults to One
|
||||||
|
let arcb = AttestationRequest::new(
|
||||||
|
AttestationVersion::One,
|
||||||
|
AttestationMeasAlg::HmacSha512,
|
||||||
|
AttestationFlags::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(arcb.version(), AttestationVersion::One);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arcb_v2_version() {
|
||||||
|
// After adding a V2 host-key, version is Two
|
||||||
|
let mut arcb = AttestationRequest::new(
|
||||||
|
AttestationVersion::Two,
|
||||||
|
AttestationMeasAlg::HmacSha512,
|
||||||
|
AttestationFlags::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let (_, host_key1, host_key2) = get_test_keys_hybrid();
|
||||||
|
arcb.add_hostkey(HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(arcb.version(), AttestationVersion::Two);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attestation_version_try_from() {
|
||||||
|
// Test version conversion from u32
|
||||||
|
assert_eq!(
|
||||||
|
AttestationVersion::try_from(0x0100).unwrap(),
|
||||||
|
AttestationVersion::One
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
AttestationVersion::try_from(0x0200).unwrap(),
|
||||||
|
AttestationVersion::Two
|
||||||
|
);
|
||||||
|
|
||||||
|
// Invalid version should error
|
||||||
|
assert!(AttestationVersion::try_from(0x0300).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attestation_flags_expected_size() {
|
||||||
|
// Test expected additional data size calculation for V1
|
||||||
|
let mut flags = AttestationFlags::default();
|
||||||
|
|
||||||
|
// Image PHKH flag - should be 32 bytes
|
||||||
|
flags.set_image_phkh();
|
||||||
|
assert_eq!(flags.expected_additional_size(), 32);
|
||||||
|
|
||||||
|
// Add attest PHKH flag - should be 64 bytes (32 + 32)
|
||||||
|
flags.set_attest_phkh();
|
||||||
|
assert_eq!(flags.expected_additional_size(), 64);
|
||||||
|
|
||||||
|
// Add secret store hash - should be 128 bytes (64 + 64)
|
||||||
|
flags.set_secret_store_hash();
|
||||||
|
assert_eq!(flags.expected_additional_size(), 128);
|
||||||
|
|
||||||
|
// Add firmware state - should be 448 bytes (128 + 320)
|
||||||
|
flags.set_firmware_state();
|
||||||
|
assert_eq!(flags.expected_additional_size(), 448);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn confidential_data_v2() {
|
||||||
|
// Test confidential data extraction (version-independent)
|
||||||
|
let arcb = AttestationRequest::new(
|
||||||
|
AttestationVersion::Two,
|
||||||
|
AttestationMeasAlg::HmacSha512,
|
||||||
|
AttestationFlags::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let conf = arcb.confidential_data();
|
||||||
|
|
||||||
|
// Should have measurement key and nonce
|
||||||
|
assert_eq!(conf.measurement_key().len(), 64);
|
||||||
|
assert!(conf.nonce().is_some());
|
||||||
|
assert_eq!(conf.nonce().as_ref().unwrap().value().len(), 16);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decrypt_bin_fail_magic() {
|
fn decrypt_bin_fail_magic() {
|
||||||
let arpk = SymKey::Aes256(ARPK.into());
|
let arpk = SymKey::Aes256(ARPK.into());
|
||||||
@@ -484,6 +668,17 @@ mod test {
|
|||||||
assert!(matches!(ret, Err(Error::NoArcb)));
|
assert!(matches!(ret, Err(Error::NoArcb)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decrypt_bin_fail_magic_v2() {
|
||||||
|
let arpk = SymKey::Aes256(ARPK.into());
|
||||||
|
let mut tamp_arcb = mk_arcb_v2();
|
||||||
|
|
||||||
|
// tamper magic
|
||||||
|
tamp_arcb[0] = 17;
|
||||||
|
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
|
||||||
|
assert!(matches!(ret, Err(Error::NoArcb)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decrypt_bin_fail_mai() {
|
fn decrypt_bin_fail_mai() {
|
||||||
let arpk = SymKey::Aes256(ARPK.into());
|
let arpk = SymKey::Aes256(ARPK.into());
|
||||||
@@ -499,6 +694,21 @@ mod test {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decrypt_bin_fail_mai_v2() {
|
||||||
|
let arpk = SymKey::Aes256(ARPK.into());
|
||||||
|
let mut tamp_arcb = mk_arcb_v2();
|
||||||
|
|
||||||
|
// tamper MAI
|
||||||
|
tamp_arcb[0x3b] = 17;
|
||||||
|
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
|
||||||
|
println!("{ret:?}");
|
||||||
|
assert!(matches!(
|
||||||
|
ret,
|
||||||
|
Err(Error::PvCore(pv_core::Error::BinArcbInvAlgorithm(17)))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decrypt_bin_fail_aad() {
|
fn decrypt_bin_fail_aad() {
|
||||||
let arpk = SymKey::Aes256(ARPK.into());
|
let arpk = SymKey::Aes256(ARPK.into());
|
||||||
@@ -509,4 +719,15 @@ mod test {
|
|||||||
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
|
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
|
||||||
assert!(matches!(ret, Err(Error::GcmTagMismatch)));
|
assert!(matches!(ret, Err(Error::GcmTagMismatch)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decrypt_bin_fail_aad_v2() {
|
||||||
|
let arpk = SymKey::Aes256(ARPK.into());
|
||||||
|
let mut tamp_arcb = mk_arcb_v2();
|
||||||
|
|
||||||
|
// tamper AAD
|
||||||
|
tamp_arcb[0x3c] = 17;
|
||||||
|
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
|
||||||
|
assert!(matches!(ret, Err(Error::GcmTagMismatch)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::mem::size_of;
|
|||||||
|
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use openssl::pkey::{PKeyRef, Private};
|
use openssl::pkey::{PKeyRef, Private};
|
||||||
|
use pv_core::misc::write_file;
|
||||||
use zerocopy::{BigEndian, IntoBytes, U16, U32};
|
use zerocopy::{BigEndian, IntoBytes, U16, U32};
|
||||||
|
|
||||||
use super::AttNonce;
|
use super::AttNonce;
|
||||||
@@ -101,6 +102,7 @@ impl AttestationItems {
|
|||||||
}
|
}
|
||||||
items.extend_from_slice(additional);
|
items.extend_from_slice(additional);
|
||||||
assert!(items.len() == size);
|
assert!(items.len() == size);
|
||||||
|
write_file("additional_data_parsed", &items, "blah").unwrap();
|
||||||
Self(items.into())
|
Self(items.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+320
-24
@@ -6,31 +6,32 @@ use openssl::md::Md;
|
|||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use pv_core::request::RequestVersion;
|
use pv_core::request::RequestVersion;
|
||||||
use pv_core::secret::AddSecretMagic;
|
use pv_core::secret::AddSecretMagic;
|
||||||
|
use pv_core::static_assert;
|
||||||
use pv_core::uv::SecretId;
|
use pv_core::uv::SecretId;
|
||||||
use zerocopy::{Immutable, IntoBytes};
|
use zerocopy::{Immutable, IntoBytes};
|
||||||
|
|
||||||
use super::guest_secret::ListableSecretHdr;
|
use super::guest_secret::{ListableSecretHdr, SecretAuth};
|
||||||
use super::user_data::UserData;
|
use super::user_data::UserData;
|
||||||
use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult};
|
use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult};
|
||||||
use crate::misc::Flags;
|
use crate::misc::Flags;
|
||||||
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx};
|
use crate::req::{Aad, BinReqValues, HostKey, Keyslot, ReqEncrCtx, RequestHdr};
|
||||||
use crate::request::{BootHdrTags, Confidential, Request};
|
use crate::request::{BootHdrTags, Confidential, Request};
|
||||||
use crate::secret::{ExtSecret, GuestSecret};
|
use crate::secret::{ExtSecret, GuestSecret};
|
||||||
use crate::uv::{ConfigUid, UvFlags};
|
use crate::uv::{ConfigUid, UvFlags};
|
||||||
use crate::{assert_size, Result};
|
use crate::{assert_size, Error, Result};
|
||||||
|
|
||||||
/// Authenticated data w/o user data
|
/// Authenticated data w/o user data
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
|
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
|
||||||
struct ReqAuthData {
|
struct ReqAuthDataV1 {
|
||||||
flags: UvFlags,
|
flags: UvFlags,
|
||||||
boot_tags: BootHdrTags,
|
boot_tags: BootHdrTags,
|
||||||
cuid: ConfigUid,
|
cuid: ConfigUid,
|
||||||
reserved90: [u8; 0x100],
|
reserved90: [u8; 0x100],
|
||||||
}
|
}
|
||||||
assert_size!(ReqAuthData, 0x1e8);
|
assert_size!(ReqAuthDataV1, 0x1e8);
|
||||||
|
|
||||||
impl ReqAuthData {
|
impl ReqAuthDataV1 {
|
||||||
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
|
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F) -> Self {
|
||||||
Self {
|
Self {
|
||||||
flags: flags.into(),
|
flags: flags.into(),
|
||||||
@@ -41,6 +42,30 @@ impl ReqAuthData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Authenticated data w/o user data for v2 header: move up secret header 2
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, IntoBytes, Immutable)]
|
||||||
|
struct ReqAuthDataV2 {
|
||||||
|
flags: UvFlags,
|
||||||
|
boot_tags: BootHdrTags,
|
||||||
|
cuid: ConfigUid,
|
||||||
|
secr_auth: [u8; 0x30],
|
||||||
|
reservedd0: [u8; 0x100 - 0x30],
|
||||||
|
}
|
||||||
|
assert_size!(ReqAuthDataV2, 0x1e8);
|
||||||
|
|
||||||
|
impl ReqAuthDataV2 {
|
||||||
|
fn new<F: Into<UvFlags>>(boot_tags: BootHdrTags, flags: F, secr_auth: [u8; 0x30]) -> Self {
|
||||||
|
Self {
|
||||||
|
flags: flags.into(),
|
||||||
|
boot_tags,
|
||||||
|
cuid: [0; 0x10],
|
||||||
|
secr_auth,
|
||||||
|
reservedd0: [0; 0xd0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ReqConfData {
|
struct ReqConfData {
|
||||||
secret: GuestSecret,
|
secret: GuestSecret,
|
||||||
@@ -92,6 +117,8 @@ impl From<AddSecretFlags> for UvFlags {
|
|||||||
pub enum AddSecretVersion {
|
pub enum AddSecretVersion {
|
||||||
/// Version 1 (= 0x0100)
|
/// Version 1 (= 0x0100)
|
||||||
One = 0x0100,
|
One = 0x0100,
|
||||||
|
/// Version 2 (= 0x0200)
|
||||||
|
Two = 0x0200,
|
||||||
|
|
||||||
#[cfg(not(doc))]
|
#[cfg(not(doc))]
|
||||||
#[cfg(any(debug_assertions, test))]
|
#[cfg(any(debug_assertions, test))]
|
||||||
@@ -99,17 +126,182 @@ pub enum AddSecretVersion {
|
|||||||
Inv = 0,
|
Inv = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for AddSecretVersion {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self> {
|
||||||
|
if value == Self::One as u32 {
|
||||||
|
Ok(Self::One)
|
||||||
|
} else if value == Self::Two as u32 {
|
||||||
|
Ok(Self::Two)
|
||||||
|
} else {
|
||||||
|
Err(Error::BinAsrcbInvVersion(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<AddSecretVersion> for RequestVersion {
|
impl From<AddSecretVersion> for RequestVersion {
|
||||||
fn from(val: AddSecretVersion) -> Self {
|
fn from(val: AddSecretVersion) -> Self {
|
||||||
val as Self
|
val as Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::request::SeHdrVersion> for AddSecretVersion {
|
||||||
|
fn from(val: crate::request::SeHdrVersion) -> Self {
|
||||||
|
match val {
|
||||||
|
crate::request::SeHdrVersion::One => AddSecretVersion::One,
|
||||||
|
crate::request::SeHdrVersion::Two => AddSecretVersion::Two,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for authenticated data in add-secret requests.
|
||||||
|
///
|
||||||
|
/// This trait provides a common interface for different versions of authenticated data,
|
||||||
|
/// allowing flexible addition of new versions in the future.
|
||||||
|
trait ReqAuthData: IntoBytes + Immutable {
|
||||||
|
/// Get the configuration UID
|
||||||
|
fn cuid(&self) -> &ConfigUid;
|
||||||
|
|
||||||
|
/// Set the configuration UID
|
||||||
|
fn set_cuid(&mut self, cuid: ConfigUid);
|
||||||
|
|
||||||
|
/// Get the boot tags
|
||||||
|
fn boot_tags(&self) -> &BootHdrTags;
|
||||||
|
|
||||||
|
/// Get the flags
|
||||||
|
fn flags(&self) -> &UvFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReqAuthData for ReqAuthDataV1 {
|
||||||
|
fn cuid(&self) -> &ConfigUid {
|
||||||
|
&self.cuid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cuid(&mut self, cuid: ConfigUid) {
|
||||||
|
self.cuid = cuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot_tags(&self) -> &BootHdrTags {
|
||||||
|
&self.boot_tags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flags(&self) -> &UvFlags {
|
||||||
|
&self.flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReqAuthData for ReqAuthDataV2 {
|
||||||
|
fn cuid(&self) -> &ConfigUid {
|
||||||
|
&self.cuid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cuid(&mut self, cuid: ConfigUid) {
|
||||||
|
self.cuid = cuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot_tags(&self) -> &BootHdrTags {
|
||||||
|
&self.boot_tags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flags(&self) -> &UvFlags {
|
||||||
|
&self.flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enum holding version-specific authenticated data
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ReqAuthDataVersion {
|
||||||
|
V1(ReqAuthDataV1),
|
||||||
|
V2(ReqAuthDataV2),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReqAuthDataVersion {
|
||||||
|
fn new(
|
||||||
|
version: AddSecretVersion,
|
||||||
|
boot_tags: BootHdrTags,
|
||||||
|
flags: AddSecretFlags,
|
||||||
|
conf_data: &SecretAuth,
|
||||||
|
) -> Result<Self> {
|
||||||
|
Ok(match version {
|
||||||
|
AddSecretVersion::One => Self::V1(ReqAuthDataV1::new(boot_tags, flags)),
|
||||||
|
AddSecretVersion::Two => Self::V2(ReqAuthDataV2::new(
|
||||||
|
boot_tags,
|
||||||
|
flags,
|
||||||
|
conf_data.get(version).try_into().expect(
|
||||||
|
"SecretAuth::get() must return exactly 0x30 bytes for AddSecretVersion::Two",
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
#[cfg(any(debug_assertions, test))]
|
||||||
|
AddSecretVersion::Inv => panic!("Invalid version for production use"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn cuid(&self) -> &ConfigUid {
|
||||||
|
match self {
|
||||||
|
Self::V1(v) => v.cuid(),
|
||||||
|
Self::V2(v) => v.cuid(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cuid(&mut self, cuid: ConfigUid) {
|
||||||
|
match self {
|
||||||
|
Self::V1(v) => v.set_cuid(cuid),
|
||||||
|
Self::V2(v) => v.set_cuid(cuid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot_tags(&self) -> &BootHdrTags {
|
||||||
|
match self {
|
||||||
|
Self::V1(v) => v.boot_tags(),
|
||||||
|
Self::V2(v) => v.boot_tags(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn flags(&self) -> &UvFlags {
|
||||||
|
match self {
|
||||||
|
Self::V1(v) => v.flags(),
|
||||||
|
Self::V2(v) => v.flags(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
Self::V1(v) => v.as_bytes(),
|
||||||
|
Self::V2(v) => v.as_bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add-secret request Control Block
|
/// Add-secret request Control Block
|
||||||
///
|
///
|
||||||
/// An ASRCB wraps a secret to securely transport it to the Ultravisor.
|
/// An ASRCB wraps a secret to securely transport it to the Ultravisor.
|
||||||
///
|
///
|
||||||
/// Layout:
|
/// Layout V2:
|
||||||
|
/// ```none
|
||||||
|
/// _______________________________________________________________
|
||||||
|
/// | generic header (48)
|
||||||
|
/// | --------------------------------------------------- |
|
||||||
|
/// | Plaintext Add-Secret flags (8) |
|
||||||
|
/// | SE header tags: PLD(64) ALD(64) TLD(64) HeaderTag(16) |
|
||||||
|
/// | Configuration unique ID(16) (Attestation) |
|
||||||
|
/// | Optional, defaults to 0 |
|
||||||
|
/// | Secret header (48) |
|
||||||
|
/// | Reserved(208) |
|
||||||
|
/// | User Data(512) (reserved) |
|
||||||
|
/// | Customer Public Key (160) generated for each request |
|
||||||
|
/// | N Keyslots(1680 each) |
|
||||||
|
/// | --------------------------------------------------- |
|
||||||
|
/// | Secret to add (Secret type dependent)(may be 0 bytes) | Encrypted
|
||||||
|
/// | Extension secret(32) Optional, defaults to 0 | Encrypted
|
||||||
|
/// | --------------------------------------------------- |
|
||||||
|
/// | AES GCM Tag (16) |
|
||||||
|
/// |_____________________________________________________________|
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Layout V1:
|
||||||
/// ```none
|
/// ```none
|
||||||
/// _______________________________________________________________
|
/// _______________________________________________________________
|
||||||
/// | generic header (48)
|
/// | generic header (48)
|
||||||
@@ -133,15 +325,19 @@ impl From<AddSecretVersion> for RequestVersion {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AddSecretRequest {
|
pub struct AddSecretRequest {
|
||||||
version: AddSecretVersion,
|
version: AddSecretVersion,
|
||||||
aad: ReqAuthData,
|
aad: ReqAuthDataVersion,
|
||||||
keyslots: Vec<Keyslot>,
|
keyslots: Vec<Keyslot>,
|
||||||
conf: ReqConfData,
|
conf: ReqConfData,
|
||||||
user_data: UserData,
|
user_data: UserData,
|
||||||
}
|
}
|
||||||
|
static_assert!(AddSecretRequest::USER_DATA_OFFS == 0x218);
|
||||||
|
static_assert!(
|
||||||
|
AddSecretRequest::USER_DATA_OFFS == size_of::<RequestHdr>() + size_of::<ReqAuthDataV2>()
|
||||||
|
);
|
||||||
|
|
||||||
impl AddSecretRequest {
|
impl AddSecretRequest {
|
||||||
/// Offset of the user-data in the add-secret request in bytes
|
/// Offset of the user-data in the add-secret request in bytes
|
||||||
pub(super) const V1_USER_DATA_OFFS: usize = 0x218;
|
pub(super) const USER_DATA_OFFS: usize = size_of::<RequestHdr>() + size_of::<ReqAuthDataV1>();
|
||||||
|
|
||||||
/// Create a new add-secret request.
|
/// Create a new add-secret request.
|
||||||
///
|
///
|
||||||
@@ -152,22 +348,23 @@ impl AddSecretRequest {
|
|||||||
secret: GuestSecret,
|
secret: GuestSecret,
|
||||||
boot_tags: BootHdrTags,
|
boot_tags: BootHdrTags,
|
||||||
flags: AddSecretFlags,
|
flags: AddSecretFlags,
|
||||||
) -> Self {
|
) -> Result<Self> {
|
||||||
Self {
|
let conf = ReqConfData {
|
||||||
conf: ReqConfData {
|
extension_secret: Confidential::new([0; 32]),
|
||||||
extension_secret: Confidential::new([0; 32]),
|
secret,
|
||||||
secret,
|
};
|
||||||
},
|
Ok(Self {
|
||||||
aad: ReqAuthData::new(boot_tags, flags),
|
aad: ReqAuthDataVersion::new(version, boot_tags, flags, &conf.secret.auth())?,
|
||||||
|
conf,
|
||||||
keyslots: vec![],
|
keyslots: vec![],
|
||||||
version,
|
version,
|
||||||
user_data: UserData::Null,
|
user_data: UserData::Null,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the Configuration Unique Id of this [`AddSecretRequest`].
|
/// Sets the Configuration Unique Id of this [`AddSecretRequest`].
|
||||||
pub fn set_cuid(&mut self, cuid: ConfigUid) {
|
pub fn set_cuid(&mut self, cuid: ConfigUid) {
|
||||||
self.aad.cuid = cuid;
|
self.aad.set_cuid(cuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the extension secret of this [`AddSecretRequest`].
|
/// Sets the extension secret of this [`AddSecretRequest`].
|
||||||
@@ -182,7 +379,7 @@ impl AddSecretRequest {
|
|||||||
ExtSecret::Derived(cck) => hkdf_rfc_5869(
|
ExtSecret::Derived(cck) => hkdf_rfc_5869(
|
||||||
Md::sha512(),
|
Md::sha512(),
|
||||||
cck.value(),
|
cck.value(),
|
||||||
self.aad.boot_tags.tag(),
|
self.aad.boot_tags().tag(),
|
||||||
DER_EXT_SECRET_INFO,
|
DER_EXT_SECRET_INFO,
|
||||||
)?
|
)?
|
||||||
.into(),
|
.into(),
|
||||||
@@ -221,7 +418,6 @@ impl AddSecretRequest {
|
|||||||
let cust_pub_key = ctx.key_coords()?;
|
let cust_pub_key = ctx.key_coords()?;
|
||||||
let secr_auth = self.conf.secret.auth();
|
let secr_auth = self.conf.secret.auth();
|
||||||
let user_data = self.user_data.data();
|
let user_data = self.user_data.data();
|
||||||
|
|
||||||
let mut aad: Vec<Aad> = Vec::with_capacity(5 + self.keyslots.len());
|
let mut aad: Vec<Aad> = Vec::with_capacity(5 + self.keyslots.len());
|
||||||
aad.push(Aad::Plain(self.aad.as_bytes()));
|
aad.push(Aad::Plain(self.aad.as_bytes()));
|
||||||
if let Some(data) = user_data.0 {
|
if let Some(data) = user_data.0 {
|
||||||
@@ -232,7 +428,13 @@ impl AddSecretRequest {
|
|||||||
}
|
}
|
||||||
aad.push(Aad::Plain(cust_pub_key.as_ref()));
|
aad.push(Aad::Plain(cust_pub_key.as_ref()));
|
||||||
self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k)));
|
self.keyslots.iter().for_each(|k| aad.push(Aad::Ks(k)));
|
||||||
aad.push(Aad::Plain(secr_auth.get()));
|
// write secret header (1) only for v1
|
||||||
|
match self.version {
|
||||||
|
AddSecretVersion::One => aad.push(Aad::Plain(secr_auth.get(AddSecretVersion::One))),
|
||||||
|
AddSecretVersion::Two => {}
|
||||||
|
#[cfg(any(debug_assertions, test))]
|
||||||
|
_ => return Err(Error::UnsupportedAddSecretVersion(self.version as u32)),
|
||||||
|
}
|
||||||
|
|
||||||
ctx.build_aad(self.version.into(), &aad, conf_len, self.user_data.magic())
|
ctx.build_aad(self.version.into(), &aad, conf_len, self.user_data.magic())
|
||||||
}
|
}
|
||||||
@@ -273,7 +475,7 @@ impl AddSecretRequest {
|
|||||||
// add signature to authenticated data starting with USER_DATA_OFFS
|
// add signature to authenticated data starting with USER_DATA_OFFS
|
||||||
self.user_data.sign(
|
self.user_data.sign(
|
||||||
&mut buf[aad_range.start..encr_range.end],
|
&mut buf[aad_range.start..encr_range.end],
|
||||||
Self::V1_USER_DATA_OFFS,
|
Self::USER_DATA_OFFS,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// encrypt again with signed data
|
// encrypt again with signed data
|
||||||
@@ -309,7 +511,101 @@ impl Request for AddSecretRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_hostkey(&mut self, hostkey: HostKey) {
|
fn add_hostkey(&mut self, hostkey: HostKey) -> Result<()> {
|
||||||
self.keyslots.push(Keyslot::new(hostkey))
|
match self.version {
|
||||||
|
AddSecretVersion::One if !hostkey.is_hybrid() => Ok(()),
|
||||||
|
AddSecretVersion::Two if hostkey.is_hybrid() => Ok(()),
|
||||||
|
AddSecretVersion::One => Err(Error::InvalidHkd(
|
||||||
|
"Add classical hostkey to a v1 attestation request".to_string(),
|
||||||
|
)),
|
||||||
|
AddSecretVersion::Two => Err(Error::InvalidHkd(
|
||||||
|
"Add hybrid key to a v2 attetstation request".to_string(),
|
||||||
|
)),
|
||||||
|
#[cfg(any(debug_assertions, test))]
|
||||||
|
AddSecretVersion::Inv => panic!("Invalid version for production use"),
|
||||||
|
}?;
|
||||||
|
self.keyslots.push(Keyslot::new(hostkey));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::request::SeHdrVersion;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_secret_version_v1() {
|
||||||
|
// Test V1 version constant
|
||||||
|
assert_eq!(AddSecretVersion::One as u32, 0x0100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_secret_version_v2() {
|
||||||
|
// Test V2 version constant
|
||||||
|
assert_eq!(AddSecretVersion::Two as u32, 0x0200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_secret_version_conversion_v1() {
|
||||||
|
// Test conversion from SeHdrVersion to AddSecretVersion for V1
|
||||||
|
let v1: AddSecretVersion = SeHdrVersion::One.into();
|
||||||
|
assert_eq!(v1, AddSecretVersion::One);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_secret_version_conversion_v2() {
|
||||||
|
// Test conversion from SeHdrVersion to AddSecretVersion for V2
|
||||||
|
let v2: AddSecretVersion = SeHdrVersion::Two.into();
|
||||||
|
assert_eq!(v2, AddSecretVersion::Two);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_secret_version_into_request_version() {
|
||||||
|
// Test conversion from AddSecretVersion to RequestVersion
|
||||||
|
let v1: RequestVersion = AddSecretVersion::One.into();
|
||||||
|
assert_eq!(v1, 0x0100);
|
||||||
|
|
||||||
|
let v2: RequestVersion = AddSecretVersion::Two.into();
|
||||||
|
assert_eq!(v2, 0x0200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_secret_flags_default() {
|
||||||
|
// Test default flags have no bits set
|
||||||
|
let flags = AddSecretFlags::default();
|
||||||
|
let uv_flags: UvFlags = flags.into();
|
||||||
|
|
||||||
|
// Default should have all bits cleared
|
||||||
|
assert_eq!(uv_flags.as_bytes(), &[0u8; 8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_secret_flags_disable_dump() {
|
||||||
|
// Test disable dump flag sets bit 0
|
||||||
|
let mut flags = AddSecretFlags::default();
|
||||||
|
flags.set_disable_dump();
|
||||||
|
|
||||||
|
let uv_flags: UvFlags = flags.into();
|
||||||
|
|
||||||
|
// Bit 0 should be set, so bytes should not be all zeros
|
||||||
|
assert_ne!(uv_flags.as_bytes(), &[0u8; 8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn req_auth_data_size() {
|
||||||
|
// Test ReqAuthData size constant
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
assert_eq!(size_of::<ReqAuthDataV1>(), 0x1e8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(debug_assertions, test))]
|
||||||
|
fn add_secret_version_inv_for_testing() {
|
||||||
|
// Test that invalid version exists for testing
|
||||||
|
assert_eq!(AddSecretVersion::Inv as u32, 0);
|
||||||
|
assert_ne!(AddSecretVersion::Inv, AddSecretVersion::One);
|
||||||
|
assert_ne!(AddSecretVersion::Inv, AddSecretVersion::Two);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use super::asrcb::AddSecretRequest;
|
|||||||
use crate::crypto::{hash, random_array, SymKeyType};
|
use crate::crypto::{hash, random_array, SymKeyType};
|
||||||
use crate::request::openssl::{NID_ED25519, NID_ED448};
|
use crate::request::openssl::{NID_ED25519, NID_ED448};
|
||||||
use crate::request::Confidential;
|
use crate::request::Confidential;
|
||||||
|
use crate::secret::AddSecretVersion;
|
||||||
use crate::uv::{
|
use crate::uv::{
|
||||||
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
||||||
RetrieveCmd, SecretId,
|
RetrieveCmd, SecretId,
|
||||||
@@ -388,12 +389,18 @@ pub(crate) enum SecretAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SecretAuth {
|
impl SecretAuth {
|
||||||
const NULL_HDR: NullSecretHdr = NullSecretHdr::new();
|
const NULL_HDR_V1: NullSecretHdrV1 = NullSecretHdrV1::new();
|
||||||
|
const NULL_HDR_V2: NullSecretHdrV2 = NullSecretHdrV2::new();
|
||||||
const UPDATE_CCK_HDR: UpdateCckHdr = UpdateCckHdr::new();
|
const UPDATE_CCK_HDR: UpdateCckHdr = UpdateCckHdr::new();
|
||||||
|
|
||||||
pub fn get(&self) -> &[u8] {
|
pub fn get(&self, version: AddSecretVersion) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Self::Null => Self::NULL_HDR.as_bytes(),
|
Self::Null => match version {
|
||||||
|
AddSecretVersion::One => Self::NULL_HDR_V1.as_bytes(),
|
||||||
|
AddSecretVersion::Two => Self::NULL_HDR_V2.as_bytes(),
|
||||||
|
#[cfg(any(debug_assertions, test))]
|
||||||
|
_ => panic!("Invalid AddSecretVersion"),
|
||||||
|
},
|
||||||
Self::Listable(h) => h.as_bytes(),
|
Self::Listable(h) => h.as_bytes(),
|
||||||
Self::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(),
|
Self::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(),
|
||||||
}
|
}
|
||||||
@@ -402,15 +409,15 @@ impl SecretAuth {
|
|||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
||||||
struct NullSecretHdr {
|
struct NullSecretHdrV1 {
|
||||||
res0: u16,
|
res0: u16,
|
||||||
kind: U16<BigEndian>,
|
kind: U16<BigEndian>,
|
||||||
secret_len: U32<BigEndian>,
|
secret_len: U32<BigEndian>,
|
||||||
res8: u64,
|
res8: u64,
|
||||||
}
|
}
|
||||||
assert_size!(NullSecretHdr, 0x10);
|
assert_size!(NullSecretHdrV1, 0x10);
|
||||||
|
|
||||||
impl NullSecretHdr {
|
impl NullSecretHdrV1 {
|
||||||
const fn new() -> Self {
|
const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
res0: 0,
|
res0: 0,
|
||||||
@@ -421,6 +428,29 @@ impl NullSecretHdr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
||||||
|
struct NullSecretHdrV2 {
|
||||||
|
res0: u16,
|
||||||
|
kind: U16<BigEndian>,
|
||||||
|
secret_len: U32<BigEndian>,
|
||||||
|
res8: u64,
|
||||||
|
reserved1: [u8; 32],
|
||||||
|
}
|
||||||
|
assert_size!(NullSecretHdrV2, 0x30);
|
||||||
|
|
||||||
|
impl NullSecretHdrV2 {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
res0: 0,
|
||||||
|
kind: U16::new(ListableSecretType::NULL),
|
||||||
|
secret_len: U32::ZERO,
|
||||||
|
res8: 0,
|
||||||
|
reserved1: [0; 32],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
||||||
pub(crate) struct ListableSecretHdr {
|
pub(crate) struct ListableSecretHdr {
|
||||||
@@ -701,7 +731,7 @@ mod test {
|
|||||||
let gs_bytes = gs.auth();
|
let gs_bytes = gs.auth();
|
||||||
let exp = vec![0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
let exp = vec![0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
|
||||||
assert_eq!(exp, gs_bytes.get());
|
assert_eq!(exp, gs_bytes.get(AddSecretVersion::One));
|
||||||
assert_eq!(&Vec::<u8>::new(), gs.confidential())
|
assert_eq!(&Vec::<u8>::new(), gs.confidential())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,7 +746,7 @@ mod test {
|
|||||||
let mut exp = vec![0u8, 0, 0, 2, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
|
let mut exp = vec![0u8, 0, 0, 2, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
exp.extend([1; 32]);
|
exp.extend([1; 32]);
|
||||||
|
|
||||||
assert_eq!(exp, gs_bytes_auth.get());
|
assert_eq!(exp, gs_bytes_auth.get(AddSecretVersion::One));
|
||||||
assert_eq!(&[2; 32], gs.confidential());
|
assert_eq!(&[2; 32], gs.confidential());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,7 +759,7 @@ mod test {
|
|||||||
secret: vec![2; 32].into(),
|
secret: vec![2; 32].into(),
|
||||||
};
|
};
|
||||||
let auth = gs.auth();
|
let auth = gs.auth();
|
||||||
let gs_bytes_auth = auth.get();
|
let gs_bytes_auth = auth.get(AddSecretVersion::One);
|
||||||
let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
|
let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
exp.extend([1; 32]);
|
exp.extend([1; 32]);
|
||||||
|
|
||||||
@@ -746,7 +776,7 @@ mod test {
|
|||||||
let mut exp = vec![0u8, 0, 0, 0x16, 0, 0, 0, 0x20];
|
let mut exp = vec![0u8, 0, 0, 0x16, 0, 0, 0, 0x20];
|
||||||
exp.extend([0; 40]);
|
exp.extend([0; 40]);
|
||||||
|
|
||||||
assert_eq!(exp, gs_bytes_auth.get());
|
assert_eq!(exp, gs_bytes_auth.get(AddSecretVersion::One));
|
||||||
assert_eq!(&[2; 32], gs.confidential());
|
assert_eq!(&[2; 32], gs.confidential());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ impl RetrievedSecret {
|
|||||||
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
|
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data in PEM format.
|
/// Get the data in PEM format.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::crypto::{sign_msg, verify_signature};
|
|||||||
use crate::req::BinReqValues;
|
use crate::req::BinReqValues;
|
||||||
use crate::request::openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public};
|
use crate::request::openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public};
|
||||||
use crate::request::RequestMagic;
|
use crate::request::RequestMagic;
|
||||||
use crate::secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType};
|
use crate::secret::{AddSecretMagic, AddSecretRequest, UserDataType};
|
||||||
use crate::{assert_size, Error, Result};
|
use crate::{assert_size, Error, Result};
|
||||||
|
|
||||||
/// User data.
|
/// User data.
|
||||||
@@ -261,21 +261,19 @@ pub fn verify_asrcb_and_get_user_data(
|
|||||||
// check that the provided buffer contains an Add Secret request
|
// check that the provided buffer contains an Add Secret request
|
||||||
let magic = AddSecretMagic::try_from_bytes(&asrcb)?;
|
let magic = AddSecretMagic::try_from_bytes(&asrcb)?;
|
||||||
let req = BinReqValues::get(&asrcb)?;
|
let req = BinReqValues::get(&asrcb)?;
|
||||||
if req.version() != AddSecretVersion::One as u32 {
|
|
||||||
return Err(Error::BinAsrcbInvVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
// preventing the two lines after the truncate from panicking
|
// preventing the two lines after the truncate from panicking
|
||||||
let req_len = req.len();
|
let req_len = req.len();
|
||||||
if asrcb.len() < req_len
|
if asrcb.len() < req_len
|
||||||
|| req_len < AddSecretRequest::V1_USER_DATA_OFFS + UserData::USER_DATA_SIZE
|
|| req_len < AddSecretRequest::USER_DATA_OFFS + UserData::USER_DATA_SIZE
|
||||||
{
|
{
|
||||||
return Err(pv_core::Error::NoAsrcb.into());
|
return Err(pv_core::Error::NoAsrcb.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// forget the tag (and all additional data that might be behind the tag)
|
// forget the tag (and all additional data that might be behind the tag)
|
||||||
asrcb.truncate(req_len - BinReqValues::TAG_LEN);
|
asrcb.truncate(req_len - BinReqValues::TAG_LEN);
|
||||||
// get a mutable refrenence on the 512 bytes of user data
|
// get a mutable refrenence on the 512 bytes of user data
|
||||||
let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::V1_USER_DATA_OFFS);
|
let (_, user_data) = asrcb.split_at_mut(AddSecretRequest::USER_DATA_OFFS);
|
||||||
let user_data = &mut user_data[..UserData::USER_DATA_SIZE];
|
let user_data = &mut user_data[..UserData::USER_DATA_SIZE];
|
||||||
|
|
||||||
// depending on the user_data_type do:
|
// depending on the user_data_type do:
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
use openssl::ec::{EcGroup, EcKey};
|
use openssl::ec::{EcGroup, EcKey};
|
||||||
use openssl::nid::Nid;
|
use openssl::nid::Nid;
|
||||||
use openssl::pkey::Private;
|
use openssl::pkey::Private;
|
||||||
use s390_pv::request::openssl::pkey::{PKey, Public};
|
use s390_pv::request::openssl::pkey::PKey;
|
||||||
use s390_pv::request::{BootHdrTags, HostKey, ReqEncrCtx, Request, SymKey};
|
use s390_pv::request::{BootHdrTags, HostKey, HybridPKey, ReqEncrCtx, Request, SymKey};
|
||||||
use s390_pv::secret::{
|
use s390_pv::secret::{
|
||||||
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret,
|
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret,
|
||||||
GuestSecret,
|
GuestSecret,
|
||||||
};
|
};
|
||||||
use s390_pv::test_utils::get_test_keys;
|
use s390_pv::test_utils::{get_test_keys, get_test_keys_hybrid, DeterministicTestRandGuard};
|
||||||
use s390_pv::uv::ConfigUid;
|
use s390_pv::uv::ConfigUid;
|
||||||
use s390_pv::{get_test_asset, Result};
|
use s390_pv::{get_test_asset, Result};
|
||||||
|
|
||||||
@@ -29,8 +29,13 @@ fn create_asrcb(
|
|||||||
hkd: HostKey,
|
hkd: HostKey,
|
||||||
ctx: &ReqEncrCtx,
|
ctx: &ReqEncrCtx,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags);
|
let asrcb = match hkd {
|
||||||
|
HostKey::V1(_) => AddSecretRequest::new(AddSecretVersion::One, guest_secret, TAGS, flags),
|
||||||
|
HostKey::V2(_) => AddSecretRequest::new(AddSecretVersion::Two, guest_secret, TAGS, flags),
|
||||||
|
_ => unreachable!("Unknown HostKey version"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut asrcb = asrcb?;
|
||||||
if let Some(s) = ext_secret {
|
if let Some(s) = ext_secret {
|
||||||
asrcb.set_ext_secret(s)?
|
asrcb.set_ext_secret(s)?
|
||||||
};
|
};
|
||||||
@@ -38,12 +43,25 @@ fn create_asrcb(
|
|||||||
asrcb.set_cuid(c);
|
asrcb.set_cuid(c);
|
||||||
};
|
};
|
||||||
|
|
||||||
asrcb.add_hostkey(hkd);
|
asrcb.add_hostkey(hkd)?;
|
||||||
asrcb.encrypt(ctx)
|
asrcb.encrypt(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_crypto() -> (PKey<Public>, ReqEncrCtx) {
|
fn get_crypto() -> (HostKey, ReqEncrCtx) {
|
||||||
let (cust_key, host_key) = get_test_keys();
|
let (cust_key, host_key) = get_test_keys();
|
||||||
|
let host_key = HostKey::V1(host_key);
|
||||||
|
let ctx = ReqEncrCtx::new_aes_256(
|
||||||
|
Some([0x55; 12]),
|
||||||
|
Some(cust_key),
|
||||||
|
Some(SymKey::Aes256([0x17; 32].into())),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
(host_key, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_crypto_v2() -> (HostKey, ReqEncrCtx) {
|
||||||
|
let (cust_key, host_key1, host_key2) = get_test_keys_hybrid();
|
||||||
|
let host_key = HostKey::V2(HybridPKey::new(host_key1, host_key2).unwrap());
|
||||||
let ctx = ReqEncrCtx::new_aes_256(
|
let ctx = ReqEncrCtx::new_aes_256(
|
||||||
Some([0x55; 12]),
|
Some([0x55; 12]),
|
||||||
Some(cust_key),
|
Some(cust_key),
|
||||||
@@ -67,14 +85,25 @@ where
|
|||||||
true => Some(CUID),
|
true => Some(CUID),
|
||||||
false => None,
|
false => None,
|
||||||
};
|
};
|
||||||
create_asrcb(
|
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
|
||||||
guest_secret,
|
}
|
||||||
ext_secret.into(),
|
|
||||||
flags,
|
fn gen_asrcb_v2<E>(
|
||||||
cuid,
|
guest_secret: GuestSecret,
|
||||||
HostKey::V1(host_key),
|
ext_secret: E,
|
||||||
&ctx,
|
flags: AddSecretFlags,
|
||||||
)
|
cuid: bool,
|
||||||
|
) -> Result<Vec<u8>>
|
||||||
|
where
|
||||||
|
E: Into<Option<ExtSecret>>,
|
||||||
|
{
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
|
||||||
|
let (host_key, ctx) = get_crypto_v2();
|
||||||
|
let cuid = match cuid {
|
||||||
|
true => Some(CUID),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn association() -> GuestSecret {
|
fn association() -> GuestSecret {
|
||||||
@@ -96,9 +125,21 @@ fn no_flag() -> AddSecretFlags {
|
|||||||
fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
|
fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
|
||||||
let (host_key, ctx) = get_crypto();
|
let (host_key, ctx) = get_crypto();
|
||||||
let mut asrcb =
|
let mut asrcb =
|
||||||
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
|
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag())
|
||||||
|
.expect("AddSecretRequest::new failed");
|
||||||
|
|
||||||
asrcb.add_hostkey(HostKey::V1(host_key));
|
asrcb.add_hostkey(host_key).unwrap();
|
||||||
|
asrcb.set_user_data(user_data, Some(skey)).unwrap();
|
||||||
|
asrcb.encrypt(&ctx).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_signed_asrcb_v2(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
|
||||||
|
let (host_key, ctx) = get_crypto_v2();
|
||||||
|
let mut asrcb =
|
||||||
|
AddSecretRequest::new(AddSecretVersion::Two, GuestSecret::Null, TAGS, no_flag())
|
||||||
|
.expect("AddSecretRequest::new failed");
|
||||||
|
|
||||||
|
asrcb.add_hostkey(host_key).unwrap();
|
||||||
asrcb.set_user_data(user_data, Some(skey)).unwrap();
|
asrcb.set_user_data(user_data, Some(skey)).unwrap();
|
||||||
asrcb.encrypt(&ctx).unwrap()
|
asrcb.encrypt(&ctx).unwrap()
|
||||||
}
|
}
|
||||||
@@ -108,9 +149,10 @@ fn null_none_default_ncuid_one_user_unsgn() {
|
|||||||
let user_data_orig = vec![0x56; 0x183];
|
let user_data_orig = vec![0x56; 0x183];
|
||||||
let (host_key, ctx) = get_crypto();
|
let (host_key, ctx) = get_crypto();
|
||||||
let mut asrcb =
|
let mut asrcb =
|
||||||
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
|
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag())
|
||||||
|
.expect("AddSecretRequest::new failed");
|
||||||
|
|
||||||
asrcb.add_hostkey(HostKey::V1(host_key));
|
asrcb.add_hostkey(host_key).unwrap();
|
||||||
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
|
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
|
||||||
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
||||||
|
|
||||||
@@ -121,6 +163,7 @@ fn null_none_default_ncuid_one_user_unsgn() {
|
|||||||
&user_data.as_ref().unwrap()[..user_data_orig.len()]
|
&user_data.as_ref().unwrap()[..user_data_orig.len()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn null_none_default_ncuid_one_user_ec() {
|
fn null_none_default_ncuid_one_user_ec() {
|
||||||
let (usr_sgn_key, _) = get_test_keys();
|
let (usr_sgn_key, _) = get_test_keys();
|
||||||
@@ -242,8 +285,11 @@ fn null_none_default_ncuid_one() {
|
|||||||
fn null_none_default_cuid_seven() {
|
fn null_none_default_cuid_seven() {
|
||||||
let (hkd, ctx) = get_crypto();
|
let (hkd, ctx) = get_crypto();
|
||||||
let mut asrcb =
|
let mut asrcb =
|
||||||
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
|
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag())
|
||||||
(0..7).for_each(|_| asrcb.add_hostkey(HostKey::V1(hkd.clone())));
|
.expect("AddSecretRequest::new failed");
|
||||||
|
for _ in 0..7 {
|
||||||
|
asrcb.add_hostkey(hkd.clone()).unwrap()
|
||||||
|
}
|
||||||
asrcb.set_cuid(CUID);
|
asrcb.set_cuid(CUID);
|
||||||
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
||||||
|
|
||||||
@@ -259,3 +305,164 @@ fn verify_no_user_data() {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// V2 tests (using hybrid keys)
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_default_ncuid_two_user_unsgn() {
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
|
||||||
|
let user_data_orig = vec![0x56; 0x183];
|
||||||
|
let (host_key, ctx) = get_crypto_v2();
|
||||||
|
let mut asrcb =
|
||||||
|
AddSecretRequest::new(AddSecretVersion::Two, GuestSecret::Null, TAGS, no_flag())
|
||||||
|
.expect("AddSecretRequest::new failed");
|
||||||
|
|
||||||
|
asrcb.add_hostkey(host_key).unwrap();
|
||||||
|
asrcb.set_user_data(user_data_orig.clone(), None).unwrap();
|
||||||
|
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
||||||
|
|
||||||
|
let user_data = verify_asrcb_and_get_user_data(asrcb, None).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
user_data_orig.as_slice(),
|
||||||
|
&user_data.as_ref().unwrap()[..user_data_orig.len()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_default_ncuid_two_user_ec() {
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
|
||||||
|
let (usr_sgn_key, _) = get_test_keys();
|
||||||
|
|
||||||
|
let usr_vrfy_key = usr_sgn_key.ec_key().unwrap();
|
||||||
|
let usr_vrfy_key = usr_vrfy_key.public_key();
|
||||||
|
let usr_vrfy_key = PKey::from_ec_key(
|
||||||
|
EcKey::from_public_key(
|
||||||
|
&EcGroup::from_curve_name(Nid::SECP521R1).unwrap(),
|
||||||
|
usr_vrfy_key,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let user_data_orig = vec![0x56; 0x100];
|
||||||
|
let asrcb = create_signed_asrcb_v2(usr_sgn_key, user_data_orig.clone());
|
||||||
|
|
||||||
|
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
user_data_orig.as_slice(),
|
||||||
|
&user_data.as_ref().unwrap()[..user_data_orig.len()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_default_ncuid_two_user_rsa2048() {
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
|
||||||
|
let usr_sgn_key = get_test_asset!("keys/rsa2048key.pem");
|
||||||
|
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
|
||||||
|
let user_data_orig = vec![0x56; 0x100];
|
||||||
|
let asrcb = create_signed_asrcb_v2(usr_sgn_key, user_data_orig.clone());
|
||||||
|
|
||||||
|
let usr_vrfy_key = get_test_asset!("keys/rsa2048key.pub.pem");
|
||||||
|
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
|
||||||
|
|
||||||
|
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
user_data_orig.as_slice(),
|
||||||
|
&user_data.as_ref().unwrap()[..user_data_orig.len()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_default_ncuid_two_user_rsa3072() {
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
|
||||||
|
let usr_sgn_key = get_test_asset!("keys/rsa3072key.pem");
|
||||||
|
let usr_sgn_key = PKey::private_key_from_pem(usr_sgn_key).unwrap();
|
||||||
|
let user_data_orig = vec![0x56; 0x80];
|
||||||
|
let asrcb = create_signed_asrcb_v2(usr_sgn_key, user_data_orig.clone());
|
||||||
|
|
||||||
|
let usr_vrfy_key = get_test_asset!("keys/rsa3072key.pub.pem");
|
||||||
|
let usr_vrfy_key = PKey::public_key_from_pem(usr_vrfy_key).unwrap();
|
||||||
|
|
||||||
|
let user_data = verify_asrcb_and_get_user_data(asrcb, Some(usr_vrfy_key)).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
user_data_orig.as_slice(),
|
||||||
|
&user_data.as_ref().unwrap()[..user_data_orig.len()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_default_cuid_two() {
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
|
||||||
|
let asrcb = gen_asrcb_v2(GuestSecret::Null, None, no_flag(), true).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assoc_none_default_cuid_two() {
|
||||||
|
let asrcb = gen_asrcb_v2(association(), None, no_flag(), true).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/assoc_none_default_cuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_simple_default_cuid_two() {
|
||||||
|
let asrcb = gen_asrcb_v2(GuestSecret::Null, ext_simple(), no_flag(), true).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/null_simple_default_cuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assoc_simple_default_cuid_two() {
|
||||||
|
let asrcb = gen_asrcb_v2(association(), ext_simple(), no_flag(), true).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/assoc_simple_default_cuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_derived_default_cuid_two() {
|
||||||
|
let asrcb = gen_asrcb_v2(GuestSecret::Null, ext_derived(), no_flag(), true).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/null_derived_default_cuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assoc_derived_default_cuid_two() {
|
||||||
|
let asrcb = gen_asrcb_v2(association(), ext_derived(), no_flag(), true).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/assoc_derived_default_cuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_dump_cuid_two() {
|
||||||
|
let mut flags = no_flag();
|
||||||
|
flags.set_disable_dump();
|
||||||
|
let asrcb = gen_asrcb_v2(GuestSecret::Null, None, flags, true).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/null_none_dump_cuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_default_ncuid_two() {
|
||||||
|
let asrcb = gen_asrcb_v2(GuestSecret::Null, None, no_flag(), false).unwrap();
|
||||||
|
let exp = get_test_asset!("exp/asrcb/null_none_default_ncuid_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_none_default_cuid_seven_two() {
|
||||||
|
let _guard = DeterministicTestRandGuard::install(&[0x42; 4096], &[0x11; 32]).unwrap();
|
||||||
|
let (hkd, ctx) = get_crypto_v2();
|
||||||
|
let mut asrcb =
|
||||||
|
AddSecretRequest::new(AddSecretVersion::Two, GuestSecret::Null, TAGS, no_flag())
|
||||||
|
.expect("AddSecretRequest::new failed");
|
||||||
|
for _ in 0..7 {
|
||||||
|
asrcb.add_hostkey(hkd.clone()).unwrap()
|
||||||
|
}
|
||||||
|
asrcb.set_cuid(CUID);
|
||||||
|
let asrcb = asrcb.encrypt(&ctx).unwrap();
|
||||||
|
|
||||||
|
let exp = get_test_asset!("exp/asrcb/null_none_default_cuid_seven_two");
|
||||||
|
assert_eq!(asrcb, exp);
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OUT_PREFIX=${1:-host}
|
||||||
|
SUBJ="/C=US/O=IBM/OU=Testing/CN=Qsafe HK"
|
||||||
|
|
||||||
|
if [ -f "${OUT_PREFIX}.hybrid.crt" ]; then
|
||||||
|
echo "[*] Nothing to do... ${OUT_PREFIX}.hybrid.crt already exists"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[*] Generating ML-KEM-1024 keypair..."
|
||||||
|
|
||||||
|
if openssl list -kem-algorithms 2>/dev/null | grep -qi ML-KEM-1024; then
|
||||||
|
echo " -> OpenSSL OQS provider detected"
|
||||||
|
|
||||||
|
if ! [ -f "${OUT_PREFIX}.mlkem.key" ]; then
|
||||||
|
echo "[*] Generate ML-KEM key (target public key)"
|
||||||
|
openssl genpkey -algorithm MLKEM1024 -out "${OUT_PREFIX}.mlkem.key"
|
||||||
|
openssl pkey -in "${OUT_PREFIX}.mlkem.key" -pubout -out "${OUT_PREFIX}.mlkem.pub.pem"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -f "issuer.key" ] || ! [ -f "issuer.crt" ]; then
|
||||||
|
echo "[*] Generate RSA issuer (signing key)"
|
||||||
|
openssl req -x509 -newkey rsa:4096 -nodes \
|
||||||
|
-keyout issuer.key \
|
||||||
|
-out issuer.crt \
|
||||||
|
-days 365 \
|
||||||
|
-subj "${SUBJ}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -f "${OUT_PREFIX}.mlkem.crt" ]; then
|
||||||
|
echo "[*] Create certificate with injected ML-KEM public key"
|
||||||
|
|
||||||
|
openssl req -new -newkey rsa:2048 -nodes \
|
||||||
|
-keyout tmp.key \
|
||||||
|
-subj "${SUBJ}" |
|
||||||
|
openssl x509 -req \
|
||||||
|
-CA issuer.crt \
|
||||||
|
-CAkey issuer.key \
|
||||||
|
-CAcreateserial \
|
||||||
|
-days 365 \
|
||||||
|
-set_serial 0x66A376DBB5502C74C6 \
|
||||||
|
-force_pubkey "${OUT_PREFIX}.mlkem.pub.pem" \
|
||||||
|
-extfile <(
|
||||||
|
cat <<EOF
|
||||||
|
basicConstraints=CA:FALSE
|
||||||
|
keyUsage=critical,keyAgreement
|
||||||
|
subjectKeyIdentifier=hash
|
||||||
|
authorityKeyIdentifier=keyid
|
||||||
|
crlDistributionPoints=URI:http://issuer.crl
|
||||||
|
EOF
|
||||||
|
) \
|
||||||
|
-out "${OUT_PREFIX}.mlkem.crt"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " -> No PQ support, using placeholder"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[*] Building hybrid PEM: ${OUT_PREFIX}.hybrid.pem"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "-----BEGIN CERTIFICATE-----"
|
||||||
|
|
||||||
|
sed '/-----/d' "${OUT_PREFIX}.pem.crt"
|
||||||
|
|
||||||
|
echo "-----END CERTIFICATE-----"
|
||||||
|
echo "-----BEGIN CERTIFICATE-----"
|
||||||
|
sed '/-----/d' "${OUT_PREFIX}.mlkem.crt"
|
||||||
|
|
||||||
|
echo "-----END CERTIFICATE-----"
|
||||||
|
} >"${OUT_PREFIX}.hybrid.crt"
|
||||||
|
|
||||||
|
echo "[✓] Done"
|
||||||
|
echo " -> ${OUT_PREFIX}.hybrid.crt"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICXDCCAb6gAwIBAgIUcOg3S9/SHLdnpu6iaI7QI2cszsgwCgYIKoZIzj0EAwIw
|
||||||
|
QDELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0lCTTEQMA4GA1UECwwHVGVzdGluZzER
|
||||||
|
MA8GA1UEAwwIUXNhZmUgSEswHhcNMjYwNjI1MTM1MTE4WhcNMzYwNjIyMTM1MTE4
|
||||||
|
WjBAMQswCQYDVQQGEwJVUzEMMAoGA1UECgwDSUJNMRAwDgYDVQQLDAdUZXN0aW5n
|
||||||
|
MREwDwYDVQQDDAhRc2FmZSBISzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAfB0
|
||||||
|
HaSx/tEDOnA0tv/tSFc2v1t0YarC8L9QcV0bU+o4Gk6OBriQi0GbUna4CndAPWrr
|
||||||
|
BYvC/ZVd2nKqZhx5WGKNAAq/NObSdvH6uNfjZY27pS56fB2abxa5UzlH/KkYAU+J
|
||||||
|
m1sFjZhOqD1Bvb/mizhz1WqdHucGSQYO55WDL+KH06Goo1MwUTAdBgNVHQ4EFgQU
|
||||||
|
/n03CIsLupLkHh5OwcdJFfucD8IwHwYDVR0jBBgwFoAU/n03CIsLupLkHh5OwcdJ
|
||||||
|
FfucD8IwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgOBiwAwgYcCQTTEnxAy
|
||||||
|
8/inrBEMtAINb9yA+LAXk5mDQIa7WXOiKRiJKLqHyrjPXqMIAtNgWwSmLggHiIYE
|
||||||
|
R4MiacrfbVdEOda3AkIBLv0XRN8zEaMLDlis6EdhgF+/AXFtsYxsjafZpoqnfx2X
|
||||||
|
YfbIgFpLUTcYWwoPi5uopbiAwv3AQy3x5CEFwG3e5PY=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAKog0FWiwlXuAS1jK
|
||||||
|
dNsahw9KZ+r4zvZrp1LmDSOQNOfkauXHH1EmBEnUkYccsi7PibYkz80sxbtk+NVW
|
||||||
|
Kk6pGk2hgYkDgYYABAHwdB2ksf7RAzpwNLb/7UhXNr9bdGGqwvC/UHFdG1PqOBpO
|
||||||
|
jga4kItBm1J2uAp3QD1q6wWLwv2VXdpyqmYceVhijQAKvzTm0nbx+rjX42WNu6Uu
|
||||||
|
enwdmm8WuVM5R/ypGAFPiZtbBY2YTqg9Qb2/5os4c9VqnR7nBkkGDueVgy/ih9Oh
|
||||||
|
qA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFFDCCAvygAwIBAgIUO+SNGBgRvkmR/lKiZhiN4l4zS70wDQYJKoZIhvcNAQEN
|
||||||
|
BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
|
||||||
|
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
|
||||||
|
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
|
||||||
|
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl
|
||||||
|
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0
|
||||||
|
NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
|
||||||
|
bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h
|
||||||
|
bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv
|
||||||
|
cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw
|
||||||
|
EAYHKoZIzj0CAQYFK4EEACMDgYYABAF2iBzYS4tt5xI8fpAO33jn97aEmZFGsSY7
|
||||||
|
qLhshEfwirbUacKxKO2eHDUBWAWs09MCM9ORIvfi+KocKxR7eIO0BgDDRVbCXPkv
|
||||||
|
Kc5mvUQRTWt7PHjhOj+QvbkAQPaHblJi93imGpN9GzSALrJX404Gct1fKjR63aoo
|
||||||
|
IDUJKnDDXC55c6OBhzCBhDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vMTI3LjAu
|
||||||
|
MC4xOjEyMzQvY3JsL2libS5jcmwwDgYDVR0PAQH/BAQDAgMIMB8GA1UdIwQYMBaA
|
||||||
|
FN6M1/Do0NgBB3F9b9loWSA+sGd8MB0GA1UdDgQWBBR6ESQ+J+iXQe0a/KZ2+N+Z
|
||||||
|
8a9SZjANBgkqhkiG9w0BAQ0FAAOCAgEAVWJHoCxxkqh7b+y93PY4KPy4jJC4gN1S
|
||||||
|
dAPqttD/yteJ/4mbVel1/KNSoQBk5EpJmRqeBwHCgGJaT/TxYXNw/b8mRPxe/xbb
|
||||||
|
wieZMqlSmH028UjYDku1eM0IgHISgoCesIR95D5iAOWbMMVUwIHIHTfmhK7DmZVe
|
||||||
|
SPf7RIkctrpYxZh0Gw8KLZO6Mfy/9tq3dps0A7KS6jjdrF+M9LavPGwFvtfvRMTi
|
||||||
|
rdteByO2saGAKDvrjtievwlWCNBJlKV1arW9krN7eqJY5YO6eRbX6UjuhbPRgjte
|
||||||
|
eZ4jL121TBJaKZU7Q/lvYHIWfzstwQdiem2Ua1GyiiEvPZrQlmqQ3gDBtwJQGB4Q
|
||||||
|
2myP7MY7THiKObjaB8qRsVxKM78ktwAtAYSZv7gZlmSJ/uTMzDV5D2TQxqs7zwCj
|
||||||
|
sV+psUn4nvh58xP+DW+MYbF/Cpmzvul9FjMKBs270vE1q+gMot27rbQHRRJ4lVN5
|
||||||
|
khiG6Oi6blVkPKExIVIiaZ9diXK6NhtWp15PWljNiDxZO+zpkeuw7cKLn/idzmvP
|
||||||
|
Gcj6m7DqcdsSIHNKbR5iM2VuDhg/j8uBD3uF2Wlymp31TBQgdYWSihJpwZKHNqJD
|
||||||
|
uq9SmegwI5gYg64KwABZM9hGbl/krXt/0CeCR5HRc+fthanKx/tO2tbCuVT3FR5J
|
||||||
|
XpKNUy1D78o=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIJlTCCB32gAwIBAgIJZqN227VQLHTGMA0GCSqGSIb3DQEBCwUAMEAxCzAJBgNV
|
||||||
|
BAYTAlVTMQwwCgYDVQQKDANJQk0xEDAOBgNVBAsMB1Rlc3RpbmcxETAPBgNVBAMM
|
||||||
|
CFFzYWZlIEhLMB4XDTI2MDcxMzA4NDg1MFoXDTI3MDcxMzA4NDg1MFowQDELMAkG
|
||||||
|
A1UEBhMCVVMxDDAKBgNVBAoMA0lCTTEQMA4GA1UECwwHVGVzdGluZzERMA8GA1UE
|
||||||
|
AwwIUXNhZmUgSEswggYyMAsGCWCGSAFlAwQEAwOCBiEAIAunnYUo3qtiqjiGykgq
|
||||||
|
tqGahVSQEMcZxZueg+UM6bKFRLYkxCkC+sIp/uIjDyp7QDRq75JIfwUXarNTd2ma
|
||||||
|
NfJRTktE3kY86rF8sCZ0h3hv13wxTVoWWNFiysenJqMsA0O/fEkGu5dQlqbDL7g1
|
||||||
|
7Wwy/OBgs3xViKB7KUxBH5TGzbq0YGEDbXY1gSx/cTApvfDPVLGSI/QqoxIBF4gf
|
||||||
|
hciO6CZcAJ1GfbKzwSLNWWtn1WqLM7m4omIkvjDIXtdk6SyX7owLbVdRbMsjCQot
|
||||||
|
6ccXWFjPhqisAtaLmrcZzRyk8TLNx+MtGKo5dHZ6lqiyotKrK6o/Y0yobuxAwJvN
|
||||||
|
cZWO+/UyHBuJpvGItWFkmSSMJ9iZv1dpManJe0GfSIe72veACYBhatPMdRmvCieg
|
||||||
|
r0yn1Vq5/kDIibw6/5LJpdYu8Tpg8oXIcEcN86fLBbqKALZtFpFCgTSXU4PDodCQ
|
||||||
|
QhLB9ki/YOR5yVqE50pa2DeyG8g8G8dnoVFRtaTNdIIf+QNZcXaa3/VstqAOgVIY
|
||||||
|
xomsb5GI9RqpSUUTxzhCPivJAHeGfLfG9Mk2W2bNlrqdw6u6GTPEm+GhQBtAl2pP
|
||||||
|
HBwrTvAeDAODECKgE5pJk3wXckwp4FpjIfaLFTlGaKkhlNS9jYmrVHYZkfFEdbUs
|
||||||
|
ApAzVyoQa0tWM+ilCbLLKGIjn1BIPsmkvCAxcTUUBDy5JgVjlJd0BXbLK6pEKTfA
|
||||||
|
BiO+0fRvZcQ/FxJnrndzoDQP90ie72OwK4dIn+Ku5dFoFkjN2dpS26qStahyZJg2
|
||||||
|
gqweLtdB+SgZBzJW9YFClHxK77AlI1otNJttDhaLE3keBgeyLgqgqutMEmOZYxcp
|
||||||
|
C7OSQNYFN2Ifw+hoHvwykPRpBLkZiTkccEsQEsJyP4A8A7e1cild1aNSc/On9MtV
|
||||||
|
UvIEuANLNVUCymi48kWTS7yXeyGLD2szhgM6ihYIvukqXYiuO7sR58SvDLdIIxZ5
|
||||||
|
HTJTQbKPsQPKkPRNLJV+DOPKVTN6zKA7DSfPNbg3hRApx+NTH9xqdplLZaV7j+FU
|
||||||
|
rPBYEuc3LVmqamlOH/wYOSSPyMbF0VJCldyIUeMOqBKGItUc9ito3eBsfSoYJ0xX
|
||||||
|
BitqLCnGCtSkZEaNRay6ujka7vg3U/FS59oWxap6Ttm6HWCExSxMZTh33GSTBEao
|
||||||
|
zHyWd3RtxzGapngsnixEFNV6ypsyqGJbHOJ7k2drUjRIqXJ5yjgp8UKy9vO2XoMi
|
||||||
|
5Yw2qmOil6QR5Ck7QgJBILAGVkOVktdimeVtlaoIe1gnX6Rk5EA1AZQyqeBw62MC
|
||||||
|
XJKJTOxnenJYQys6vSw1H0Sd7EPITKaSGAtd30y3ljdrP6YG4dwwjAmsDdmdNgR2
|
||||||
|
z0BvTnG8PQqRNIWfPXsnkHqcpycN0sieDnG0m1hP14JONACtuNuKkNunQKQgBMMV
|
||||||
|
g9w4Rcc3L2Um+Ean/UI4LJJLYAkPIXAwfPqZmBNXTBqWNHCeFNW5nakn9reBR2xz
|
||||||
|
rPFutTu3QGaDoCFkeqAKiADGwsi0JqqU2iyvT3WbQ4EvQDgJo8soVIfCFXpYhuSn
|
||||||
|
yAEUSXrLvSgP0ht80EDLiMd7Xom75+hXx+UbX1qrd0ubY8nJRShSznwmJnUnB0x5
|
||||||
|
z/zJ18o9mdWb+/M2IbuZ2CFeX1sJItlxFvsNUswuIDQoQmtWiUm2a6RElsOwAGg8
|
||||||
|
yTurJuKqfTcsQNWbr4g+cTFsTZRYwDFEAyUlfqJicppO6ohlAGaCYgcA8HiOB5SU
|
||||||
|
3pdf/PDG5GG52ZpnhrlqBSOjuUolKzGtwCCHAfohk9LD+BFJUSyuBuxZyMEh4tsT
|
||||||
|
84Cgy+tZUCipQeS0eMKSyKs/7pEndBhAhMBc0mZDY1MNaethKPRm83xVnZMUFTJr
|
||||||
|
CQi3WlAgXdsqWHkak/aXf2CdTOxBEaq2fhSP5cJLDABlJQivSUbJwLOBU0OewSE8
|
||||||
|
9vougwvD7DkmZxavwFQ8ggOmjUqp3oGbmdRlcLWuOOYUW6Nz/eC1XJEq7qmuQALF
|
||||||
|
taZIYXxj55ePw8ysPtOKRQqpRfs2w5I/wOJy4FxDHmJ4Wjg1VBA6QzxpFHJ7n4hJ
|
||||||
|
ydO9p/6dl6BHWK6klp/F7xGjgYEwfzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwID
|
||||||
|
CDAdBgNVHQ4EFgQU+Ua9eLZDQEHqy6feSZpQkKTPEr4wHwYDVR0jBBgwFoAUaSni
|
||||||
|
RLnV8oxx0lUba0yC4VPe6egwIgYDVR0fBBswGTAXoBWgE4YRaHR0cDovL2lzc3Vl
|
||||||
|
ci5jcmwwDQYJKoZIhvcNAQELBQADggIBAEIGXfa9ORdcmtF9EO5YctN0ZoZOIWgK
|
||||||
|
HM6S0UNe8VghZOKpn/7RArS/04CXA1hb12I6CWxRrvU3qxak95OWHKl405tS0ysz
|
||||||
|
nipuguN+r8B59KlcIvnh2vbctWf2uW9SJF9uWu6cSL/T27TAA0V07412givkXOIZ
|
||||||
|
kfgPpW19fqQbNuhoY2yYOh0bl/vFWtY4dORbR86mo3RV9JUQ8KtsVIdJOuz0RcnH
|
||||||
|
3L+AavQLblLfCdmKJnBC1Zia1d/295wOdUj8n1d+Eq6ho66YBlLJN1zrAZHELPj2
|
||||||
|
l6dNal+AxCyPgTJ+h2owWRTOZOqMB8/Dun1m1EnwZKowQg+HTPT0NyMwZpfMfK7m
|
||||||
|
IHe7WZE/5tXPRnUl6KfDXSSrkxrXeAHJmkSnOEyLS4YheKEzBkMTaPU2YzdBPXy0
|
||||||
|
gfr9SIRmBkQSwE0pCJgVhcO0yvTlKhXiWrXPZ7uXMnCbXLJzhfiVWIJs+aqyKo1f
|
||||||
|
8mbJK23JOFv42LAZ1j1l7WWNPCAlGK4KgWCIHXeplY1XSyQ6jXECHzegRWPKm2aq
|
||||||
|
SFr+5QQcedN+//3Ss3zDnEhGxjmlz2TB1lrgh5fcXQbKlkQ5HuPv1CFhhc7oUg0x
|
||||||
|
DEGDCA08LfeuLeRhecF95ws9ICzHSLJL+RMujcQFEeQZq3uRG5PsKllgCiE2Rk9T
|
||||||
|
jq+SvDvKMqeV
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
jq+SvDvKMqeV
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
+JA=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIGMjALBglghkgBZQMEBAMDggYhACALp52FKN6rYqo4hspIKrahmoVUkBDHGcWb
|
||||||
|
noPlDOmyhUS2JMQpAvrCKf7iIw8qe0A0au+SSH8FF2qzU3dpmjXyUU5LRN5GPOqx
|
||||||
|
fLAmdId4b9d8MU1aFljRYsrHpyajLANDv3xJBruXUJamwy+4Ne1sMvzgYLN8VYig
|
||||||
|
eylMQR+Uxs26tGBhA212NYEsf3EwKb3wz1SxkiP0KqMSAReIH4XIjugmXACdRn2y
|
||||||
|
s8EizVlrZ9VqizO5uKJiJL4wyF7XZOksl+6MC21XUWzLIwkKLenHF1hYz4aorALW
|
||||||
|
i5q3Gc0cpPEyzcfjLRiqOXR2epaosqLSqyuqP2NMqG7sQMCbzXGVjvv1Mhwbiabx
|
||||||
|
iLVhZJkkjCfYmb9XaTGpyXtBn0iHu9r3gAmAYWrTzHUZrwonoK9Mp9Vauf5AyIm8
|
||||||
|
Ov+SyaXWLvE6YPKFyHBHDfOnywW6igC2bRaRQoE0l1ODw6HQkEISwfZIv2Dkecla
|
||||||
|
hOdKWtg3shvIPBvHZ6FRUbWkzXSCH/kDWXF2mt/1bLagDoFSGMaJrG+RiPUaqUlF
|
||||||
|
E8c4Qj4ryQB3hny3xvTJNltmzZa6ncOruhkzxJvhoUAbQJdqTxwcK07wHgwDgxAi
|
||||||
|
oBOaSZN8F3JMKeBaYyH2ixU5RmipIZTUvY2Jq1R2GZHxRHW1LAKQM1cqEGtLVjPo
|
||||||
|
pQmyyyhiI59QSD7JpLwgMXE1FAQ8uSYFY5SXdAV2yyuqRCk3wAYjvtH0b2XEPxcS
|
||||||
|
Z653c6A0D/dInu9jsCuHSJ/iruXRaBZIzdnaUtuqkrWocmSYNoKsHi7XQfkoGQcy
|
||||||
|
VvWBQpR8Su+wJSNaLTSbbQ4WixN5HgYHsi4KoKrrTBJjmWMXKQuzkkDWBTdiH8Po
|
||||||
|
aB78MpD0aQS5GYk5HHBLEBLCcj+APAO3tXIpXdWjUnPzp/TLVVLyBLgDSzVVAspo
|
||||||
|
uPJFk0u8l3shiw9rM4YDOooWCL7pKl2Irju7EefErwy3SCMWeR0yU0Gyj7EDypD0
|
||||||
|
TSyVfgzjylUzesygOw0nzzW4N4UQKcfjUx/canaZS2Wle4/hVKzwWBLnNy1Zqmpp
|
||||||
|
Th/8GDkkj8jGxdFSQpXciFHjDqgShiLVHPYraN3gbH0qGCdMVwYraiwpxgrUpGRG
|
||||||
|
jUWsuro5Gu74N1PxUufaFsWqek7Zuh1ghMUsTGU4d9xkkwRGqMx8lnd0bccxmqZ4
|
||||||
|
LJ4sRBTVesqbMqhiWxzie5Nna1I0SKlyeco4KfFCsvbztl6DIuWMNqpjopekEeQp
|
||||||
|
O0ICQSCwBlZDlZLXYpnlbZWqCHtYJ1+kZORANQGUMqngcOtjAlySiUzsZ3pyWEMr
|
||||||
|
Or0sNR9EnexDyEymkhgLXd9Mt5Y3az+mBuHcMIwJrA3ZnTYEds9Ab05xvD0KkTSF
|
||||||
|
nz17J5B6nKcnDdLIng5xtJtYT9eCTjQArbjbipDbp0CkIATDFYPcOEXHNy9lJvhG
|
||||||
|
p/1COCySS2AJDyFwMHz6mZgTV0waljRwnhTVuZ2pJ/a3gUdsc6zxbrU7t0Bmg6Ah
|
||||||
|
ZHqgCogAxsLItCaqlNosr091m0OBL0A4CaPLKFSHwhV6WIbkp8gBFEl6y70oD9Ib
|
||||||
|
fNBAy4jHe16Ju+foV8flG19aq3dLm2PJyUUoUs58JiZ1JwdMec/8ydfKPZnVm/vz
|
||||||
|
NiG7mdghXl9bCSLZcRb7DVLMLiA0KEJrVolJtmukRJbDsABoPMk7qybiqn03LEDV
|
||||||
|
m6+IPnExbE2UWMAxRAMlJX6iYnKaTuqIZQBmgmIHAPB4jgeUlN6XX/zwxuRhudma
|
||||||
|
Z4a5agUjo7lKJSsxrcAghwH6IZPSw/gRSVEsrgbsWcjBIeLbE/OAoMvrWVAoqUHk
|
||||||
|
tHjCksirP+6RJ3QYQITAXNJmQ2NTDWnrYSj0ZvN8VZ2TFBUyawkIt1pQIF3bKlh5
|
||||||
|
GpP2l39gnUzsQRGqtn4Uj+XCSwwAZSUIr0lGycCzgVNDnsEhPPb6LoMLw+w5JmcW
|
||||||
|
r8BUPIIDpo1Kqd6Bm5nUZXC1rjjmFFujc/3gtVyRKu6prkACxbWmSGF8Y+eXj8PM
|
||||||
|
rD7TikUKqUX7NsOSP8DicuBcQx5ieFo4NVQQOkM8aRRye5+IScnTvaf+nZegR1iu
|
||||||
|
pJafxe8R
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
YFF7USKdWC1isfkk3OmsHvIwXzfRUA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -5,7 +5,10 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Args, Parser, Subcommand, ValueEnum, ValueHint};
|
use clap::{Args, Parser, Subcommand, ValueEnum, ValueHint};
|
||||||
use utils::{CertificateOptions, DeprecatedVerbosityOptions};
|
use utils::{
|
||||||
|
AutoOrExplicit, CertificateOptions, DeprecatedVerbosityOptions, HkdVersion, ValueEnumDisplay,
|
||||||
|
ValueEnumFromStr,
|
||||||
|
};
|
||||||
|
|
||||||
/// create, perform, and verify attestation measurements
|
/// create, perform, and verify attestation measurements
|
||||||
///
|
///
|
||||||
@@ -67,6 +70,28 @@ pub enum Command {
|
|||||||
Version,
|
Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Secure Execution attestation version for CLI
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)]
|
||||||
|
pub enum AttVersion {
|
||||||
|
#[value(name = "1")]
|
||||||
|
/// Version 1 - uses traditional cryptographic keys
|
||||||
|
V1,
|
||||||
|
#[value(name = "2")]
|
||||||
|
/// Version 2 - uses hybrid (post-quantum) cryptographic keys
|
||||||
|
V2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AttVersionSelection = AutoOrExplicit<AttVersion>;
|
||||||
|
|
||||||
|
impl From<AttVersion> for HkdVersion {
|
||||||
|
fn from(val: AttVersion) -> Self {
|
||||||
|
match val {
|
||||||
|
AttVersion::V1 => Self::Classical,
|
||||||
|
AttVersion::V2 => Self::Hybrid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct CreateAttOpt {
|
pub struct CreateAttOpt {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
@@ -94,6 +119,10 @@ pub struct CreateAttOpt {
|
|||||||
value_delimiter = ','
|
value_delimiter = ','
|
||||||
)]
|
)]
|
||||||
pub add_data: Vec<AttAddFlags>,
|
pub add_data: Vec<AttAddFlags>,
|
||||||
|
|
||||||
|
/// Specify the Attestation Request version to use.
|
||||||
|
#[arg(long = "att-version", value_name = "VERSION", default_value_t = AttVersion::V1)]
|
||||||
|
pub att_version: AttVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, ValueEnum, Clone, Copy)]
|
#[derive(Debug, ValueEnum, Clone, Copy)]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pub use check::check;
|
|||||||
pub use create::create;
|
pub use create::create;
|
||||||
pub use verify::verify;
|
pub use verify::verify;
|
||||||
|
|
||||||
pub const CMD_FN: &[&str] = &["+create", "+verify"];
|
pub const CMD_FN: &[&str] = &["+create", "+verify", "+quantumsafe"];
|
||||||
// s390 branch
|
// s390 branch
|
||||||
#[cfg(target_arch = "s390x")]
|
#[cfg(target_arch = "s390x")]
|
||||||
mod uv_cmd {
|
mod uv_cmd {
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ use std::fmt::Display;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::{debug, info};
|
use log::{debug, info, warn};
|
||||||
use pv::misc::{read_certs, read_file};
|
use pv::misc::{read_certs, read_file};
|
||||||
use pv::request::openssl::DigestBytes;
|
use pv::request::{EcPubKeyCoord, HybridPKey, KeyslotV2};
|
||||||
use pv::request::EcPubKeyCoord;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use utils::HexSlice;
|
use utils::HexSlice;
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ impl Display for HkCheck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_host_keys<A: AsRef<Path>>(hkds: &[A]) -> Result<Vec<(&Path, DigestBytes)>> {
|
fn load_host_keys<A: AsRef<Path>>(hkds: &[A]) -> Result<Vec<(&Path, Vec<u8>)>> {
|
||||||
let mut hkd_hash = Vec::with_capacity(hkds.len());
|
let mut hkd_hash = Vec::with_capacity(hkds.len());
|
||||||
for hkd in hkds {
|
for hkd in hkds {
|
||||||
let hkd = hkd.as_ref();
|
let hkd = hkd.as_ref();
|
||||||
@@ -45,21 +44,41 @@ fn load_host_keys<A: AsRef<Path>>(hkds: &[A]) -> Result<Vec<(&Path, DigestBytes)
|
|||||||
hkd: hkd.display().to_string(),
|
hkd: hkd.display().to_string(),
|
||||||
source,
|
source,
|
||||||
})?;
|
})?;
|
||||||
let ec_coord: EcPubKeyCoord = certs.first().unwrap().public_key()?.as_ref().try_into()?;
|
|
||||||
hkd_hash.push((hkd, ec_coord.sha256()?));
|
let ec_key = certs.first().unwrap().public_key()?;
|
||||||
|
|
||||||
|
match certs.len() {
|
||||||
|
1 => {
|
||||||
|
let ec_coord: EcPubKeyCoord = ec_key.as_ref().try_into()?;
|
||||||
|
hkd_hash.push((hkd, ec_coord.sha256()?.as_ref().to_vec()));
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let mlkem_key = certs[1].public_key()?;
|
||||||
|
let ks = KeyslotV2::new(HybridPKey::new(ec_key, mlkem_key)?);
|
||||||
|
let hash = ks.sha512()?;
|
||||||
|
hkd_hash.push((hkd, hash[..32].to_vec()));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!(
|
||||||
|
"The host-key document in '{}' contains more than two certificates!",
|
||||||
|
hkd.display()
|
||||||
|
);
|
||||||
|
Err(pv::Error::WrongNumberOfKeys(hkd.display().to_string()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(hkd_hash)
|
Ok(hkd_hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_phkh<'a>(
|
fn contains_phkh<'a>(
|
||||||
hkd_hashes: &[(&'a Path, DigestBytes)],
|
hkd_hashes: &[(&'a Path, Vec<u8>)],
|
||||||
phkh: &HexSlice<'_>,
|
phkh: &HexSlice<'_>,
|
||||||
mode: HkCheck,
|
mode: HkCheck,
|
||||||
check_enforced: bool,
|
check_enforced: bool,
|
||||||
) -> CheckState<HostKeyCheck<'a>> {
|
) -> CheckState<HostKeyCheck<'a>> {
|
||||||
let hk: Vec<_> = hkd_hashes
|
let hk: Vec<_> = hkd_hashes
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(path, hash)| match hash.as_ref() == phkh.as_ref() {
|
.filter_map(|(path, hash)| match hash == phkh.as_ref() {
|
||||||
true => Some(*path),
|
true => Some(*path),
|
||||||
false => None,
|
false => None,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, Attes
|
|||||||
use pv::misc::{create_file, write_file};
|
use pv::misc::{create_file, write_file};
|
||||||
use pv::request::{HostKey, ReqEncrCtx, Request, SymKey, SymKeyType};
|
use pv::request::{HostKey, ReqEncrCtx, Request, SymKey, SymKeyType};
|
||||||
|
|
||||||
use crate::cli::{AttAddFlags, CreateAttOpt};
|
use crate::cli::{AttAddFlags, AttVersion, AttVersionSelection, CreateAttOpt};
|
||||||
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatVersion};
|
use crate::exchange::{ExchangeFormatRequest, ExchangeFormatVersion};
|
||||||
|
|
||||||
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
|
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
|
||||||
@@ -26,18 +26,57 @@ fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
|
|||||||
att_flags
|
att_flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Auto-detect the attestation version based on the host keys.
|
||||||
|
///
|
||||||
|
/// Returns Two if any host key is a hybrid key, otherwise returns V1.
|
||||||
|
fn auto_detect_version(host_keys: &[HostKey]) -> AttestationVersion {
|
||||||
|
let use_hybrid_keys = host_keys.iter().any(|k: &HostKey| k.is_hybrid());
|
||||||
|
if use_hybrid_keys {
|
||||||
|
AttestationVersion::Two
|
||||||
|
} else {
|
||||||
|
AttestationVersion::One
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AttVersion> for AttestationVersion {
|
||||||
|
fn from(value: AttVersion) -> Self {
|
||||||
|
match value {
|
||||||
|
AttVersion::V1 => Self::One,
|
||||||
|
AttVersion::V2 => Self::Two,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the attestation version to use.
|
||||||
|
///
|
||||||
|
/// If an explicit version is provided via CLI, use that.
|
||||||
|
/// Otherwise, auto-detect based on the host key types.
|
||||||
|
fn determine_version(
|
||||||
|
cli_version: AttVersionSelection,
|
||||||
|
host_keys: &[HostKey],
|
||||||
|
) -> AttestationVersion {
|
||||||
|
match cli_version {
|
||||||
|
AttVersionSelection::Auto => auto_detect_version(host_keys),
|
||||||
|
AttVersionSelection::Explicit(att_version) => att_version.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
|
pub fn create(opt: &CreateAttOpt) -> Result<ExitCode> {
|
||||||
let att_version = AttestationVersion::One;
|
let hkds = opt.certificate_args.get_verified_hkds(
|
||||||
|
"attestation request",
|
||||||
|
AttVersionSelection::Explicit(opt.att_version).map(|v| v.into()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let att_version = determine_version(AttVersionSelection::Explicit(opt.att_version), &hkds);
|
||||||
let meas_alg = AttestationMeasAlg::HmacSha512;
|
let meas_alg = AttestationMeasAlg::HmacSha512;
|
||||||
|
|
||||||
let mut arcb = AttestationRequest::new(att_version, meas_alg, flags(&opt.add_data))?;
|
let mut arcb = AttestationRequest::new(att_version, meas_alg, flags(&opt.add_data))?;
|
||||||
debug!("Generated Attestation request");
|
debug!("Generated Attestation request");
|
||||||
|
|
||||||
// Add host-key documents
|
// Add host-key documents
|
||||||
opt.certificate_args
|
for k in hkds.into_iter() {
|
||||||
.get_verified_hkds("attestation request")?
|
arcb.add_hostkey(k)?
|
||||||
.into_iter()
|
}
|
||||||
.for_each(|k| arcb.add_hostkey(HostKey::V1(k)));
|
|
||||||
debug!("Added all host-keys");
|
debug!("Added all host-keys");
|
||||||
|
|
||||||
let encr_ctx =
|
let encr_ctx =
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use pv::request::MagicValue;
|
|||||||
use pv::uv::{AttestationCmd, ConfigUid};
|
use pv::uv::{AttestationCmd, ConfigUid};
|
||||||
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
|
use zerocopy::{BigEndian, ByteOrder, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
|
||||||
|
|
||||||
|
use crate::additional;
|
||||||
|
|
||||||
const INV_EXCHANGE_FMT_ERROR_TEXT: &str = "The input has not the correct format:";
|
const INV_EXCHANGE_FMT_ERROR_TEXT: &str = "The input has not the correct format:";
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -99,10 +101,18 @@ impl ExchangeFormatV1Hdr {
|
|||||||
let measurement_entry = Entry::from_exp(Some(measurement));
|
let measurement_entry = Entry::from_exp(Some(measurement));
|
||||||
let exp_add = match additional {
|
let exp_add = match additional {
|
||||||
0 => None,
|
0 => None,
|
||||||
size => Some(size),
|
size => {
|
||||||
|
if size > AttestationCmd::ADDITIONAL_MAX_SIZE {
|
||||||
|
bail!(
|
||||||
|
"Additional data size ({}) exceeds maximum allowed size ({})",
|
||||||
|
size,
|
||||||
|
AttestationCmd::ADDITIONAL_MAX_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(size)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// TODO min and max size check?
|
let additional_entry = Entry::from_exp(exp_add);
|
||||||
let additional_entry = Entry::from_exp(exp_add); //, AttestationCmd::ADDITIONAL_MAX_SIZE, &mut offset);
|
|
||||||
let user_entry = Entry::from_none();
|
let user_entry = Entry::from_none();
|
||||||
let cuid_entry = Entry::from_none();
|
let cuid_entry = Entry::from_none();
|
||||||
|
|
||||||
@@ -530,7 +540,6 @@ impl ExchangeFormatResponse {
|
|||||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation request.",
|
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation request.",
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
// TODO remove unwrap
|
|
||||||
let measurement = hdr.measurement.read(reader)?.data().ok_or(anyhow!(
|
let measurement = hdr.measurement.read(reader)?.data().ok_or(anyhow!(
|
||||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (Measurement missing).",
|
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (Measurement missing).",
|
||||||
))?;
|
))?;
|
||||||
@@ -614,9 +623,12 @@ mod test {
|
|||||||
measurement: usize,
|
measurement: usize,
|
||||||
additional: usize,
|
additional: usize,
|
||||||
) {
|
) {
|
||||||
// TODO as 32 checks
|
let ctx_write = ExchangeFormatRequest::new(
|
||||||
let ctx_write = ExchangeFormatRequest::new(arcb, measurement as u32, additional as u32)
|
arcb,
|
||||||
.expect("exchange fmt creation");
|
measurement.try_into().unwrap(),
|
||||||
|
additional.try_into().unwrap(),
|
||||||
|
)
|
||||||
|
.expect("exchange fmt creation");
|
||||||
|
|
||||||
// let mut out = create_file(path).unwrap();
|
// let mut out = create_file(path).unwrap();
|
||||||
let mut out = vec![];
|
let mut out = vec![];
|
||||||
@@ -693,6 +705,50 @@ mod test {
|
|||||||
ExchangeFormatRequest::new(ARCB.to_vec(), 0, ADDITIONAL.len() as u32).unwrap_err();
|
ExchangeFormatRequest::new(ARCB.to_vec(), 0, ADDITIONAL.len() as u32).unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_additional_data_size_validation() {
|
||||||
|
// Test for TODO 1 fix: Additional data size validation
|
||||||
|
let arcb = ARCB.to_vec();
|
||||||
|
|
||||||
|
// Test with valid size at maximum
|
||||||
|
let result = ExchangeFormatV1Hdr::new_request(
|
||||||
|
&arcb,
|
||||||
|
MEASUREMENT.len() as u32,
|
||||||
|
AttestationCmd::ADDITIONAL_MAX_SIZE,
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Maximum additional data size should be accepted"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with size exceeding maximum
|
||||||
|
let result = ExchangeFormatV1Hdr::new_request(
|
||||||
|
&arcb,
|
||||||
|
MEASUREMENT.len() as u32,
|
||||||
|
AttestationCmd::ADDITIONAL_MAX_SIZE + 1,
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Additional data size exceeding maximum should fail"
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
let error_msg = e.to_string();
|
||||||
|
assert!(
|
||||||
|
error_msg.contains("exceeds maximum"),
|
||||||
|
"Error should mention exceeding maximum: {}",
|
||||||
|
error_msg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with zero size (no additional data)
|
||||||
|
let result = ExchangeFormatV1Hdr::new_request(&arcb, MEASUREMENT.len() as u32, 0);
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Zero additional data size should be accepted"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn min_req() {
|
fn min_req() {
|
||||||
test_read_write_request(
|
test_read_write_request(
|
||||||
|
|||||||
@@ -10,15 +10,18 @@ use std::path::PathBuf;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Error};
|
use anyhow::{anyhow, Context, Error};
|
||||||
use clap::{Parser, ValueHint};
|
use clap::{Parser, ValueEnum, ValueHint};
|
||||||
use log::{info, warn};
|
use log::info;
|
||||||
use pv::misc::{decode_hex, open_file, read_certs, read_file, try_parse_u64};
|
use pv::misc::{decode_hex, open_file, read_file, try_parse_u64};
|
||||||
use pv::request::SymKeyType;
|
use pv::request::{HostKey, NoVerifyHkd, SymKeyType};
|
||||||
use pv::{Error as PvError, Result};
|
use pv::Result;
|
||||||
use pvimg::misc::PSW;
|
use pvimg::misc::PSW;
|
||||||
use pvimg::secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder};
|
use pvimg::secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder};
|
||||||
use pvimg::uvdata::{BuilderTrait, SeHdrBuilder, SeHdrVersion};
|
use pvimg::uvdata::{BuilderTrait, SeHdrBuilder, SeHdrControlFlags, SeHdrVersion, SeTarget};
|
||||||
use utils::{AtomicFile, AtomicFileOperation, HexSlice, PvLogger, VerbosityOptions};
|
use utils::{
|
||||||
|
AtomicFile, AtomicFileOperation, HexSlice, HkdLoader, HkdVersionSelection, PvLogger,
|
||||||
|
VerbosityOptions,
|
||||||
|
};
|
||||||
|
|
||||||
/// Converts the hexstring into a byte vector.
|
/// Converts the hexstring into a byte vector.
|
||||||
///
|
///
|
||||||
@@ -87,6 +90,38 @@ impl Display for ComponentArg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SE-header version selection
|
||||||
|
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||||
|
enum SeHdrVersionArg {
|
||||||
|
/// SE-header version 1
|
||||||
|
#[value(name = "1")]
|
||||||
|
V1,
|
||||||
|
/// SE-header version 2
|
||||||
|
#[value(name = "2")]
|
||||||
|
V2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SeHdrVersionArg> for SeHdrVersion {
|
||||||
|
fn from(arg: SeHdrVersionArg) -> Self {
|
||||||
|
match arg {
|
||||||
|
SeHdrVersionArg::V1 => SeHdrVersion::V1,
|
||||||
|
SeHdrVersionArg::V2 => SeHdrVersion::V2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeHdrVersionArg {
|
||||||
|
/// Detect the SE header version from the keys.
|
||||||
|
/// Returns V2 if any key is hybrid, otherwise V1.
|
||||||
|
pub fn detect<K: AsRef<[HostKey]>>(keys: K) -> Self {
|
||||||
|
if keys.as_ref().iter().any(|k| !k.is_hybrid()) {
|
||||||
|
Self::V1
|
||||||
|
} else {
|
||||||
|
Self::V2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a Secure Execution header.
|
/// Create a Secure Execution header.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
@@ -129,6 +164,10 @@ pub struct Args {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub output: PathBuf,
|
pub output: PathBuf,
|
||||||
|
|
||||||
|
/// SE-header version to build
|
||||||
|
#[arg(long, value_enum)]
|
||||||
|
version: Option<SeHdrVersionArg>,
|
||||||
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub verbosity: VerbosityOptions,
|
pub verbosity: VerbosityOptions,
|
||||||
}
|
}
|
||||||
@@ -203,36 +242,26 @@ fn main() -> anyhow::Result<()> {
|
|||||||
info!("\n# Creating Secure Execution Header");
|
info!("\n# Creating Secure Execution Header");
|
||||||
let addr = args.psw_addr;
|
let addr = args.psw_addr;
|
||||||
let mask = args.psw_mask;
|
let mask = args.psw_mask;
|
||||||
let mut builder = SeHdrBuilder::new(
|
|
||||||
SeHdrVersion::V1,
|
|
||||||
PSW { addr, mask },
|
|
||||||
secure_comp_builer.finish()?,
|
|
||||||
)?;
|
|
||||||
let mut target_pub_keys = vec![];
|
let mut target_pub_keys = vec![];
|
||||||
for hkd_path in args.host_key_documents {
|
for hkd_path in args.host_key_documents {
|
||||||
info!(
|
info!(
|
||||||
"Use the file '{}' as a host key document",
|
"Use the file '{}' as a host key document",
|
||||||
hkd_path.display()
|
hkd_path.display()
|
||||||
);
|
);
|
||||||
let hkd_data = read_file(&hkd_path, "host key document")?;
|
let cert = HkdLoader::load_and_verify(&hkd_path, &NoVerifyHkd, HkdVersionSelection::Auto)?;
|
||||||
let certs = read_certs(&hkd_data)?;
|
target_pub_keys.push(cert);
|
||||||
if certs.is_empty() {
|
|
||||||
return Err(PvError::NoHkdInFile(hkd_path.display().to_string()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if certs.len() > 1 {
|
|
||||||
warn!("The host key document in '{}' contains more than one certificate! All keys will be used.",
|
|
||||||
hkd_path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
for cert in &certs {
|
|
||||||
target_pub_keys.push(cert.public_key()?);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let version: SeHdrVersion = args
|
||||||
|
.version
|
||||||
|
.unwrap_or(SeHdrVersionArg::detect(&target_pub_keys))
|
||||||
|
.into();
|
||||||
|
let target = SeTarget::from_se_hdr_version(version);
|
||||||
|
let pcf = SeHdrControlFlags::from_u64(try_parse_u64(&args.pcf, "pcf")?, target, true);
|
||||||
|
let scf = SeHdrControlFlags::from_u64(try_parse_u64(&args.scf, "scf")?, target, false);
|
||||||
|
info!("SE-header version ...: {}", version);
|
||||||
|
let mut builder = SeHdrBuilder::new(version, PSW { addr, mask }, secure_comp_builer.finish()?)?;
|
||||||
builder.add_hostkeys(&target_pub_keys)?;
|
builder.add_hostkeys(&target_pub_keys)?;
|
||||||
|
|
||||||
let pcf = try_parse_u64(&args.pcf, "pcf")?.into();
|
|
||||||
let scf = try_parse_u64(&args.scf, "scf")?.into();
|
|
||||||
info!(
|
info!(
|
||||||
"PSW addr ............: {addr:#018x}\n\
|
"PSW addr ............: {addr:#018x}\n\
|
||||||
PSW mask ............: {mask:#018x}\n\
|
PSW mask ............: {mask:#018x}\n\
|
||||||
|
|||||||
+586
-114
@@ -13,7 +13,56 @@ use std::string::ToString;
|
|||||||
use clap::builder::{PossibleValue, TypedValueParser};
|
use clap::builder::{PossibleValue, TypedValueParser};
|
||||||
use clap::{Arg, ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, ValueHint};
|
use clap::{Arg, ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, ValueHint};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use utils::{CertificateOptions, DeprecatedVerbosityOptions};
|
use utils::{
|
||||||
|
AutoOrExplicit, CertificateOptions, DeprecatedVerbosityOptions, HkdVersion, ValueEnumDisplay,
|
||||||
|
ValueEnumFromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// SE header control flags for CLI
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ValueEnum, ValueEnumDisplay)]
|
||||||
|
#[value(rename_all = "kebab-case")]
|
||||||
|
pub enum SeHdrFlagName {
|
||||||
|
/// Confidential guest dump support
|
||||||
|
ConfidentialDump,
|
||||||
|
/// DEA/TDEA PCKMO key encryption support
|
||||||
|
PckmoDeaTdea,
|
||||||
|
/// AES PCKMO key encryption support
|
||||||
|
PckmoAes,
|
||||||
|
/// ECC PCKMO key encryption support
|
||||||
|
PckmoEcc,
|
||||||
|
/// HMAC PCKMO key encryption support
|
||||||
|
PckmoHmac,
|
||||||
|
/// Backup target keys support
|
||||||
|
BackupTargetKeys,
|
||||||
|
/// CCK-derived extension secret enforcement for add-secret requests
|
||||||
|
CckExtensionSecretEnforcement,
|
||||||
|
/// CCK update support
|
||||||
|
CckUpdate,
|
||||||
|
/// Image components without encryption
|
||||||
|
NoComponentEncryption,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Secure Execution header version for CLI
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)]
|
||||||
|
pub enum HdrVersion {
|
||||||
|
#[value(name = "1")]
|
||||||
|
/// Version 1 - uses traditional cryptographic keys
|
||||||
|
V1,
|
||||||
|
#[value(name = "2")]
|
||||||
|
/// Version 2 - uses hybrid (post-quantum) cryptographic keys
|
||||||
|
V2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type HdrVersionSelection = AutoOrExplicit<HdrVersion>;
|
||||||
|
|
||||||
|
impl From<HdrVersion> for HkdVersion {
|
||||||
|
fn from(val: HdrVersion) -> Self {
|
||||||
|
match val {
|
||||||
|
HdrVersion::V1 => Self::Classical,
|
||||||
|
HdrVersion::V2 => Self::Hybrid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create and inspect IBM Secure Execution images.
|
/// Create and inspect IBM Secure Execution images.
|
||||||
///
|
///
|
||||||
@@ -56,6 +105,28 @@ impl CliOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines a requirement rule for CLI validation.
|
||||||
|
///
|
||||||
|
/// A requirement specifies that certain flags require a specific option to be present.
|
||||||
|
/// For example, the `ConfidentialDump` flag requires the `--cck` option.
|
||||||
|
struct Requirement {
|
||||||
|
/// The flags that trigger this requirement
|
||||||
|
flags: &'static [SeHdrFlagName],
|
||||||
|
/// The option name that must be present (e.g., "cck")
|
||||||
|
option: &'static str,
|
||||||
|
/// Whether the required option is present
|
||||||
|
present: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a set of flags that cannot be used together.
|
||||||
|
///
|
||||||
|
/// When multiple flags from this set are present in the command line,
|
||||||
|
/// a validation error is raised with a dynamically generated message
|
||||||
|
/// listing the conflicting flags.
|
||||||
|
struct MutuallyExclusiveFlags {
|
||||||
|
flags: &'static [SeHdrFlagName],
|
||||||
|
}
|
||||||
|
|
||||||
/// Validates the given command line options.
|
/// Validates the given command line options.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -71,6 +142,92 @@ pub fn validate_cli(opts: &CliOptions) -> Result<(), clap::error::Error> {
|
|||||||
{
|
{
|
||||||
warn!("Use bootloader directory: {}", dir.display());
|
warn!("Use bootloader directory: {}", dir.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that a user provided CCK is available
|
||||||
|
let rules = [Requirement {
|
||||||
|
flags: &[
|
||||||
|
SeHdrFlagName::ConfidentialDump,
|
||||||
|
SeHdrFlagName::CckExtensionSecretEnforcement,
|
||||||
|
],
|
||||||
|
option: LONG_FLAG_CCK,
|
||||||
|
present: create_opts.keys.cck.is_some(),
|
||||||
|
}];
|
||||||
|
for r in rules {
|
||||||
|
let offenders: Vec<_> = r
|
||||||
|
.flags
|
||||||
|
.iter()
|
||||||
|
.filter(|f| create_opts.flags.contains(f))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !offenders.is_empty() && !r.present {
|
||||||
|
return Err(clap::Error::raw(
|
||||||
|
clap::error::ErrorKind::MissingRequiredArgument,
|
||||||
|
format!(
|
||||||
|
"flag(s) {} require(s) --{}",
|
||||||
|
offenders
|
||||||
|
.iter()
|
||||||
|
.map(|f| format!("{:?}", f))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
r.option
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for conflicts between --flags and --disable-flags
|
||||||
|
if !create_opts.flags.is_empty() && !create_opts.disable_flags.is_empty() {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
let flags_set: HashSet<_> = create_opts.flags.iter().collect();
|
||||||
|
let disable_flags_set: HashSet<_> = create_opts.disable_flags.iter().collect();
|
||||||
|
|
||||||
|
let conflicts: Vec<_> = flags_set
|
||||||
|
.intersection(&disable_flags_set)
|
||||||
|
.copied()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !conflicts.is_empty() {
|
||||||
|
return Err(clap::Error::raw(
|
||||||
|
clap::error::ErrorKind::ArgumentConflict,
|
||||||
|
// Print the flag name using the kebab-case notation (using to_possible_value)
|
||||||
|
format!(
|
||||||
|
"Conflicting flags detected: the following flags are specified in both --flags and --disable-flags: {}",
|
||||||
|
conflicts.iter().map(|x| format!("{x}")).collect::<Vec<_>>().join(", ")
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for mutually exclusive flags within --flags
|
||||||
|
let exclusion_rules = [MutuallyExclusiveFlags {
|
||||||
|
flags: &[
|
||||||
|
SeHdrFlagName::CckExtensionSecretEnforcement,
|
||||||
|
SeHdrFlagName::CckUpdate,
|
||||||
|
],
|
||||||
|
}];
|
||||||
|
|
||||||
|
for rule in exclusion_rules {
|
||||||
|
let conflicting_flags: Vec<_> = rule
|
||||||
|
.flags
|
||||||
|
.iter()
|
||||||
|
.filter(|f| create_opts.flags.contains(f))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if conflicting_flags.len() > 1 {
|
||||||
|
return Err(clap::Error::raw(
|
||||||
|
clap::error::ErrorKind::ArgumentConflict,
|
||||||
|
format!(
|
||||||
|
"The following flags cannot be used together: {}",
|
||||||
|
conflicting_flags
|
||||||
|
.iter()
|
||||||
|
.map(|f| format!("'{}'", f))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
@@ -100,6 +257,8 @@ pub struct ComponentPaths {
|
|||||||
pub parmfile: Option<PathBuf>,
|
pub parmfile: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LONG_FLAG_CCK: &str = "cck";
|
||||||
|
|
||||||
/// CLI Argument collection for handling user-provided keys.
|
/// CLI Argument collection for handling user-provided keys.
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
#[cfg_attr(test, derive(Default))]
|
#[cfg_attr(test, derive(Default))]
|
||||||
@@ -109,7 +268,7 @@ pub struct UserKeys {
|
|||||||
/// The file must contain exactly 32 bytes of data. In previous versions,
|
/// The file must contain exactly 32 bytes of data. In previous versions,
|
||||||
/// this option was called '--comm-key'.
|
/// this option was called '--comm-key'.
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long = LONG_FLAG_CCK,
|
||||||
value_name = "FILE",
|
value_name = "FILE",
|
||||||
group = "cck-available",
|
group = "cck-available",
|
||||||
visible_alias = "comm-key"
|
visible_alias = "comm-key"
|
||||||
@@ -184,11 +343,13 @@ pub struct CreateBootImageLegacyFlags {
|
|||||||
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo", group="header-flags")]
|
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo", group="header-flags")]
|
||||||
pub disable_pckmo: Option<bool>,
|
pub disable_pckmo: Option<bool>,
|
||||||
|
|
||||||
/// Enable the support for the HMAC PCKMO key encryption function.
|
/// Enable the support for the HMAC PCKMO key encryption function (default for header version
|
||||||
|
/// 2).
|
||||||
#[arg(long, action = clap::ArgAction::SetTrue, group="header-flags")]
|
#[arg(long, action = clap::ArgAction::SetTrue, group="header-flags")]
|
||||||
pub enable_pckmo_hmac: Option<bool>,
|
pub enable_pckmo_hmac: Option<bool>,
|
||||||
|
|
||||||
/// Disable the support for the HMAC PCKMO key encryption function (default).
|
/// Disable the support for the HMAC PCKMO key encryption function (default for header version
|
||||||
|
/// 1).
|
||||||
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo_hmac", group="header-flags")]
|
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo_hmac", group="header-flags")]
|
||||||
pub disable_pckmo_hmac: Option<bool>,
|
pub disable_pckmo_hmac: Option<bool>,
|
||||||
|
|
||||||
@@ -519,7 +680,6 @@ impl GenprotimgCliOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[cfg_attr(test, derive(Default))]
|
|
||||||
pub struct CreateBootImageArgs {
|
pub struct CreateBootImageArgs {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub component_paths: ComponentPaths,
|
pub component_paths: ComponentPaths,
|
||||||
@@ -542,16 +702,62 @@ pub struct CreateBootImageArgs {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub overwrite: bool,
|
pub overwrite: bool,
|
||||||
|
|
||||||
|
/// Specify the Secure Execution header version to use.
|
||||||
|
#[arg(long = "hdr-version", value_name = "VERSION", default_value_t = HdrVersion::V1)]
|
||||||
|
pub hdr_version: HdrVersion,
|
||||||
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub keys: UserKeys,
|
pub keys: UserKeys,
|
||||||
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
|
// TODO Declare as deprecated
|
||||||
pub legacy_flags: CreateBootImageLegacyFlags,
|
pub legacy_flags: CreateBootImageLegacyFlags,
|
||||||
|
|
||||||
|
/// Set control flags using comma-separated flag names.
|
||||||
|
///
|
||||||
|
/// Specify flags to enable using their names.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
value_name = "FLAGS",
|
||||||
|
value_delimiter = ',',
|
||||||
|
conflicts_with_all = ["header-flags", "x_pcf", "x_scf"]
|
||||||
|
)]
|
||||||
|
pub flags: Vec<SeHdrFlagName>,
|
||||||
|
|
||||||
|
/// Set control flags using comma-separated flag names.
|
||||||
|
///
|
||||||
|
/// Specify flags to disable using their names.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
value_name = "FLAGS",
|
||||||
|
value_delimiter = ',',
|
||||||
|
conflicts_with_all = ["header-flags", "x_pcf", "x_scf"]
|
||||||
|
)]
|
||||||
|
pub disable_flags: Vec<SeHdrFlagName>,
|
||||||
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub experimental_args: CreateBootImageExperimentalArgs,
|
pub experimental_args: CreateBootImageExperimentalArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Default for CreateBootImageArgs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
component_paths: ComponentPaths::default(),
|
||||||
|
output: PathBuf::new(),
|
||||||
|
certificate_args: CertificateOptions::default(),
|
||||||
|
no_component_check: false,
|
||||||
|
overwrite: false,
|
||||||
|
hdr_version: HdrVersion::V1,
|
||||||
|
keys: UserKeys::default(),
|
||||||
|
legacy_flags: CreateBootImageLegacyFlags::default(),
|
||||||
|
flags: Vec::new(),
|
||||||
|
disable_flags: Vec::new(),
|
||||||
|
experimental_args: CreateBootImageExperimentalArgs::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Experimental options
|
/// Experimental options
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
#[cfg_attr(test, derive(Default))]
|
#[cfg_attr(test, derive(Default))]
|
||||||
@@ -666,6 +872,62 @@ mod test {
|
|||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to test valid args - generic over parse and convert functions
|
||||||
|
fn test_valid_args<F>(args_list: Vec<Vec<&str>>, parse_and_convert: F, name: &str)
|
||||||
|
where
|
||||||
|
F: Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||||
|
{
|
||||||
|
for arg in args_list {
|
||||||
|
let res = parse_and_convert(&arg);
|
||||||
|
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||||
|
if let Err(e) = &res {
|
||||||
|
println!("{name} arg: {arg:?}");
|
||||||
|
println!("{e}");
|
||||||
|
}
|
||||||
|
assert!(res.is_ok(), "{name} failed for: {arg:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to test invalid args - expects parse or validation to fail
|
||||||
|
fn test_invalid_args<F>(
|
||||||
|
test_cases: &[(&[Vec<String>], clap::error::ErrorKind, &str)],
|
||||||
|
cmd_prefix: &[&str],
|
||||||
|
parse_and_convert: F,
|
||||||
|
name: &str,
|
||||||
|
) where
|
||||||
|
F: Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||||
|
{
|
||||||
|
for (test_group, expected_kind, kind_name) in test_cases {
|
||||||
|
for args in *test_group {
|
||||||
|
let full_args = [
|
||||||
|
cmd_prefix.to_vec(),
|
||||||
|
Vec::from_iter(args.iter().map(String::as_str)),
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
|
||||||
|
// Try parse and convert
|
||||||
|
let parse_result = parse_and_convert(&full_args);
|
||||||
|
|
||||||
|
let err = match parse_result {
|
||||||
|
Ok(cli_opts) => {
|
||||||
|
// Parse succeeded, validation must fail
|
||||||
|
validate_cli(&cli_opts).expect_err(&format!(
|
||||||
|
"{name}: Expected error ({kind_name}) but both parse and validation succeeded for: {full_args:?}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Err(e) => e, // Parse failed as expected
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
err.kind(),
|
||||||
|
*expected_kind,
|
||||||
|
"{name}: Expected {kind_name} but got {:?} for args: {full_args:?}\nError: {err}",
|
||||||
|
err.kind()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn genprotimg_and_pvimg_create_args() {
|
fn genprotimg_and_pvimg_create_args() {
|
||||||
@@ -734,16 +996,67 @@ mod test {
|
|||||||
// --comm-key with --enable-cck-update (alias test)
|
// --comm-key with --enable-cck-update (alias test)
|
||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("comm-key", ["--comm-key", "/dev/null"]),
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("comm-key", ["--comm-key", "/dev/null"]),
|
||||||
CliOption::new("enable-cck-update", ["--enable-cck-update"])])),
|
CliOption::new("enable-cck-update", ["--enable-cck-update"])])),
|
||||||
|
|
||||||
|
// --disable-flags tests
|
||||||
|
// Test --disable-flags alone
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("disable-flags", ["--disable-flags", &SeHdrFlagName::PckmoHmac.to_string()])])),
|
||||||
|
// Test --flags and --disable-flags without conflict
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("flags", ["--flags", &SeHdrFlagName::ConfidentialDump.to_string()]),
|
||||||
|
CliOption::new("disable-flags", ["--disable-flags", &SeHdrFlagName::PckmoHmac.to_string()]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
|
// Test multiple --disable-flags
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("disable-flags", ["--disable-flags", &format!("{},{}", SeHdrFlagName::PckmoHmac, SeHdrFlagName::BackupTargetKeys)])])),
|
||||||
|
// Test --flags and --disable-flags with different flags
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("flags", ["--flags", &format!("{},{}", SeHdrFlagName::ConfidentialDump, SeHdrFlagName::BackupTargetKeys)]),
|
||||||
|
CliOption::new("disable-flags", ["--disable-flags", &format!("{},{}", SeHdrFlagName::PckmoHmac, SeHdrFlagName::NoComponentEncryption)]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
|
|
||||||
|
// --flags tests (equivalent to --enable-* tests)
|
||||||
|
// Test --flags with confidential-dump (equivalent to --enable-dump)
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("flags", ["--flags", &SeHdrFlagName::ConfidentialDump.to_string()]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
|
|
||||||
|
// Test with --overwrite
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("overwrite", ["--overwrite"])])),
|
||||||
|
|
||||||
|
// Test with --no-component-check
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("no-component-check", ["--no-component-check"])])),
|
||||||
|
|
||||||
|
// Test with all PCKMO flags
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("flags", ["--flags", &format!("{},{},{},{}",
|
||||||
|
SeHdrFlagName::PckmoDeaTdea, SeHdrFlagName::PckmoAes, SeHdrFlagName::PckmoEcc, SeHdrFlagName::PckmoHmac)])])),
|
||||||
|
|
||||||
|
// Test with NoComponentEncryption flag
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("flags", ["--flags", &SeHdrFlagName::NoComponentEncryption.to_string()])])),
|
||||||
|
|
||||||
|
// Test with HdrVersion V2
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("hdr-version", ["--hdr-version", "2"])])),
|
||||||
];
|
];
|
||||||
let invalid_create_args = [
|
// Invalid test cases grouped by expected error kind
|
||||||
|
let invalid_missing_required = [
|
||||||
flat_map_collect(remove(mvcanv.clone(), "no-verify")),
|
flat_map_collect(remove(mvcanv.clone(), "no-verify")),
|
||||||
flat_map_collect(remove(mvcanv.clone(), "image")),
|
flat_map_collect(remove(mvcanv.clone(), "image")),
|
||||||
flat_map_collect(remove(mvcanv.clone(), "hkd")),
|
flat_map_collect(remove(mvcanv.clone(), "hkd")),
|
||||||
flat_map_collect(remove(mvcanv, "output")),
|
flat_map_collect(remove(mvcanv, "output")),
|
||||||
|
// missing both `--cck' and `--enable-cck-update' (required by --enable-dump)
|
||||||
// missing both `--cck' and `--enable-cck-update'
|
|
||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"])])),
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"])])),
|
||||||
|
// Test --flags with confidential-dump but missing --cck (equivalent to --enable-dump without --cck)
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("flags", ["--flags", &SeHdrFlagName::ConfidentialDump.to_string()])])),
|
||||||
|
// --enable-cck-extension-secret without cck-available (validation error, semantically missing required)
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-cck-extension-secret", ["--enable-cck-extension-secret"])])),
|
||||||
|
];
|
||||||
|
|
||||||
|
let invalid_value = [
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-header-key", ["--hdr-key"]),])),
|
||||||
|
];
|
||||||
|
|
||||||
|
let invalid_conflict = [
|
||||||
// -v and -q cannot be combined
|
// -v and -q cannot be combined
|
||||||
flat_map_collect(insert(mvca.clone(), vec![
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
CliOption::new("verbose", ["-v"]),
|
CliOption::new("verbose", ["-v"]),
|
||||||
@@ -761,7 +1074,6 @@ mod test {
|
|||||||
CliOption::new("disable-pckmo", ["--disable-pckmo"])])),
|
CliOption::new("disable-pckmo", ["--disable-pckmo"])])),
|
||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-image-encryption", ["--enable-image-encryption"]),
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-image-encryption", ["--enable-image-encryption"]),
|
||||||
CliOption::new("disable-image-encryption", ["--disable-image-encryption"])])),
|
CliOption::new("disable-image-encryption", ["--disable-image-encryption"])])),
|
||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-header-key", ["--hdr-key"]),])),
|
|
||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("extension", ["--enable-cck-extension-secret"]),
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("extension", ["--enable-cck-extension-secret"]),
|
||||||
CliOption::new("update", ["--enable-cck-update"])])),
|
CliOption::new("update", ["--enable-cck-update"])])),
|
||||||
|
|
||||||
@@ -776,13 +1088,55 @@ mod test {
|
|||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("disable-image-encryption", ["--disable-image-encryption"]),
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("disable-image-encryption", ["--disable-image-encryption"]),
|
||||||
CliOption::new("image-key", ["--image-key", "/dev/null"])])),
|
CliOption::new("image-key", ["--image-key", "/dev/null"])])),
|
||||||
|
|
||||||
// cck-available group tests: invalid combinations
|
|
||||||
// --enable-cck-extension-secret without cck-available
|
|
||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-cck-extension-secret", ["--enable-cck-extension-secret"])])),
|
|
||||||
// --enable-cck-extension-secret with --enable-cck-update and --cck (conflict takes precedence)
|
// --enable-cck-extension-secret with --enable-cck-update and --cck (conflict takes precedence)
|
||||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-cck-extension-secret", ["--enable-cck-extension-secret"]),
|
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-cck-extension-secret", ["--enable-cck-extension-secret"]),
|
||||||
CliOption::new("enable-cck-update", ["--enable-cck-update"]),
|
CliOption::new("enable-cck-update", ["--enable-cck-update"]),
|
||||||
CliOption::new("cck", ["--cck", "/dev/null"])])),
|
CliOption::new("cck", ["--cck", "/dev/null"])])),
|
||||||
|
|
||||||
|
// --disable-flags conflict tests
|
||||||
|
// Test conflict between --flags and --disable-flags (same flag in both)
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("flags", ["--flags", &SeHdrFlagName::ConfidentialDump.to_string()]),
|
||||||
|
CliOption::new("disable-flags", ["--disable-flags", &SeHdrFlagName::ConfidentialDump.to_string()]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
|
// Test multiple conflicts
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("flags", ["--flags", &format!("{},{}", SeHdrFlagName::ConfidentialDump, SeHdrFlagName::PckmoHmac)]),
|
||||||
|
CliOption::new("disable-flags", ["--disable-flags", &format!("{},{}", SeHdrFlagName::ConfidentialDump, SeHdrFlagName::PckmoHmac)]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
|
|
||||||
|
// --flags conflicts with --x-pcf
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("flags", ["--flags", &SeHdrFlagName::ConfidentialDump.to_string()]),
|
||||||
|
CliOption::new("x-pcf", ["--x-pcf", "0x0"]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
|
// --flags conflicts with --x-scf
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("flags", ["--flags", &SeHdrFlagName::ConfidentialDump.to_string()]),
|
||||||
|
CliOption::new("x-scf", ["--x-scf", "0x0"]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
|
// --disable-flags conflicts with --x-pcf
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("disable-flags", ["--disable-flags", &SeHdrFlagName::PckmoHmac.to_string()]),
|
||||||
|
CliOption::new("x-pcf", ["--x-pcf", "0x0"])
|
||||||
|
])),
|
||||||
|
// --disable-flags conflicts with --x-scf
|
||||||
|
flat_map_collect(insert(mvca.clone(), vec![
|
||||||
|
CliOption::new("disable-flags", ["--disable-flags", &SeHdrFlagName::PckmoHmac.to_string()]),
|
||||||
|
CliOption::new("x-scf", ["--x-scf", "0x0"])
|
||||||
|
])),
|
||||||
|
|
||||||
|
// Test --flags with cck-extension-secret and cck-update and --cck
|
||||||
|
// (conflict, equivalent to --enable-cck-extension-secret)
|
||||||
|
flat_map_collect(insert(mvca, vec![
|
||||||
|
CliOption::new("flags-cck-extension-secret", ["--flags", &SeHdrFlagName::CckExtensionSecretEnforcement.to_string()]),
|
||||||
|
CliOption::new("flags-enable-cck-update", ["--flags", &SeHdrFlagName::CckUpdate.to_string()]),
|
||||||
|
CliOption::new("cck", ["--cck", "/dev/null"])
|
||||||
|
])),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut genprotimg_valid_args = vec![
|
let mut genprotimg_valid_args = vec![
|
||||||
@@ -794,53 +1148,31 @@ mod test {
|
|||||||
vec!["pvimg", "version"],
|
vec!["pvimg", "version"],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Test for invalid combinations
|
|
||||||
let mut genprotimg_invalid_args = vec![
|
|
||||||
vec!["genprotimg"],
|
|
||||||
];
|
|
||||||
let mut pvimg_invalid_args = vec![
|
|
||||||
vec!["pvimg"],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Test that `genprotimg` and `pvimg create` behave equally.
|
// Test that `genprotimg` and `pvimg create` behave equally.
|
||||||
for create_args in &valid_create_args {
|
for create_args in &valid_create_args {
|
||||||
genprotimg_valid_args.push([["genprotimg"].to_vec(), Vec::from_iter(create_args.iter().map(String::as_str))].concat());
|
genprotimg_valid_args.push([["genprotimg"].to_vec(), Vec::from_iter(create_args.iter().map(String::as_str))].concat());
|
||||||
pvimg_valid_args.push([["pvimg", "create"].to_vec(), Vec::from_iter(create_args.iter().map(String::as_str))].concat());
|
pvimg_valid_args.push([["pvimg", "create"].to_vec(), Vec::from_iter(create_args.iter().map(String::as_str))].concat());
|
||||||
}
|
}
|
||||||
|
|
||||||
for invalid_create_args in &invalid_create_args {
|
// Test invalid args with expected error kinds
|
||||||
genprotimg_invalid_args.push([["genprotimg"].to_vec(), Vec::from_iter(invalid_create_args.iter().map(String::as_str))].concat());
|
let test_cases = [
|
||||||
pvimg_invalid_args.push([["pvimg", "create"].to_vec(), Vec::from_iter(invalid_create_args.iter().map(String::as_str))].concat());
|
(&invalid_missing_required[..], clap::error::ErrorKind::MissingRequiredArgument, "MissingRequiredArgument"),
|
||||||
}
|
(&invalid_value[..], clap::error::ErrorKind::InvalidValue, "InvalidValue"),
|
||||||
|
(&invalid_conflict[..], clap::error::ErrorKind::ArgumentConflict, "ArgumentConflict"),
|
||||||
|
];
|
||||||
|
|
||||||
for arg in pvimg_valid_args {
|
// Parse and convert functions for each CLI variant
|
||||||
let res = CliOptions::try_parse_from(&arg);
|
let parse_pvimg = |args: &[&str]| CliOptions::try_parse_from(args);
|
||||||
#[allow(clippy::use_debug, clippy::print_stdout)]
|
// The into converts Result<GenprotimgCliOptions, Error> into Result<CliOptions, Error>
|
||||||
if let Err(e) = &res {
|
let parse_genprotimg = |args: &[&str]| GenprotimgCliOptions::try_parse_from(args).map(Into::into);
|
||||||
println!("arg: {arg:?}");
|
|
||||||
println!("{e}");
|
|
||||||
}
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in pvimg_invalid_args {
|
// Test both CLI variants
|
||||||
let res = CliOptions::try_parse_from(&arg);
|
for (name, valid_args, cmd_prefix, parse_fn) in [
|
||||||
assert!(res.is_err());
|
("pvimg", pvimg_valid_args, &["pvimg", "create"] as &[&str], &parse_pvimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>),
|
||||||
}
|
("genprotimg", genprotimg_valid_args, &["genprotimg"] as &[&str], &parse_genprotimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>),
|
||||||
|
] {
|
||||||
for arg in genprotimg_valid_args {
|
test_valid_args(valid_args, parse_fn, name);
|
||||||
let res = GenprotimgCliOptions::try_parse_from(&arg);
|
test_invalid_args(&test_cases, cmd_prefix, parse_fn, name);
|
||||||
#[allow(clippy::use_debug, clippy::print_stdout)]
|
|
||||||
if let Err(e) = &res {
|
|
||||||
println!("arg: {arg:?}");
|
|
||||||
println!("{e}");
|
|
||||||
}
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in genprotimg_invalid_args {
|
|
||||||
let res = GenprotimgCliOptions::try_parse_from(&arg);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,13 +1220,37 @@ mod test {
|
|||||||
CliOption::new("verbose", ["-VVV"]),
|
CliOption::new("verbose", ["-VVV"]),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
// Test with --host-key-document
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-document", ["--host-key-document", "/dev/null"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
// Test with multiple --host-key-document (comma-separated)
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new(
|
||||||
|
"host-key-document",
|
||||||
|
["--host-key-document", "/dev/null,/dev/zero"],
|
||||||
|
),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
];
|
];
|
||||||
|
|
||||||
let invalid_test_args = [
|
// Invalid test cases grouped by expected error kind
|
||||||
|
let invalid_missing_required = [
|
||||||
|
// Missing required test-args group (only image provided, no host-key info)
|
||||||
flat_map_collect(insert(
|
flat_map_collect(insert(
|
||||||
args.clone(),
|
args.clone(),
|
||||||
vec![CliOption::new("image", ["/dev/null"])],
|
vec![CliOption::new("image", ["/dev/null"])],
|
||||||
)),
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let invalid_conflict = [
|
||||||
// the argument '--key-hashes[=<FILE>]' cannot be used with '--host-key-document
|
// the argument '--key-hashes[=<FILE>]' cannot be used with '--host-key-document
|
||||||
// <FILE>'
|
// <FILE>'
|
||||||
flat_map_collect(insert(
|
flat_map_collect(insert(
|
||||||
@@ -905,20 +1261,17 @@ mod test {
|
|||||||
CliOption::new("image", ["/dev/null"]),
|
CliOption::new("image", ["/dev/null"]),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
flat_map_collect(insert(
|
|
||||||
args,
|
|
||||||
vec![
|
|
||||||
CliOption::new("host-key-hashes2", ["--key-hashes", "/sys/null"]),
|
|
||||||
CliOption::new("image", ["--", "/dev/null"]),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut pvimg_valid_args = vec![];
|
let invalid_unknown_arg = [flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("host-key-hashes2", ["--key-hashes", "/sys/null"]),
|
||||||
|
CliOption::new("image", ["--", "/dev/null"]),
|
||||||
|
],
|
||||||
|
))];
|
||||||
|
|
||||||
// Test for invalid combinations
|
let mut pvimg_valid_args = vec![];
|
||||||
// Input is missing
|
|
||||||
let mut pvimg_invalid_args = vec![vec!["pvimg", "test"]];
|
|
||||||
|
|
||||||
for create_args in &valid_test_args {
|
for create_args in &valid_test_args {
|
||||||
pvimg_valid_args.push(
|
pvimg_valid_args.push(
|
||||||
@@ -930,30 +1283,36 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for invalid_test_arg in &invalid_test_args {
|
let test_cases = [
|
||||||
pvimg_invalid_args.push(
|
(
|
||||||
[
|
&invalid_missing_required[..],
|
||||||
["pvimg", "test"].to_vec(),
|
clap::error::ErrorKind::MissingRequiredArgument,
|
||||||
Vec::from_iter(invalid_test_arg.iter().map(String::as_str)),
|
"MissingRequiredArgument",
|
||||||
]
|
),
|
||||||
.concat(),
|
(
|
||||||
);
|
&invalid_conflict[..],
|
||||||
}
|
clap::error::ErrorKind::ArgumentConflict,
|
||||||
|
"ArgumentConflict",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&invalid_unknown_arg[..],
|
||||||
|
clap::error::ErrorKind::UnknownArgument,
|
||||||
|
"UnknownArgument",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
for arg in pvimg_valid_args {
|
let parse_pvimg = |args: &[&str]| CliOptions::try_parse_from(args);
|
||||||
let res = CliOptions::try_parse_from(&arg);
|
test_valid_args(
|
||||||
#[allow(clippy::use_debug, clippy::print_stdout)]
|
pvimg_valid_args,
|
||||||
if let Err(e) = &res {
|
&parse_pvimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||||
println!("arg: {arg:?}");
|
"pvimg test",
|
||||||
println!("{e}");
|
);
|
||||||
}
|
test_invalid_args(
|
||||||
assert!(res.is_ok());
|
&test_cases,
|
||||||
}
|
&["pvimg", "test"],
|
||||||
|
&parse_pvimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||||
for arg in pvimg_invalid_args {
|
"pvimg test",
|
||||||
let res = CliOptions::try_parse_from(&arg);
|
);
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1044,9 +1403,27 @@ mod test {
|
|||||||
["--print-schema", "json"],
|
["--print-schema", "json"],
|
||||||
)],
|
)],
|
||||||
)),
|
)),
|
||||||
|
// --show-secrets with --hdr-key
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("hdr-key", ["--hdr-key", "/dev/null"]),
|
||||||
|
CliOption::new("show-secrets", ["--show-secrets"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
// text:full format
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("format", ["--format", "text:full"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
];
|
];
|
||||||
|
|
||||||
let invalid_test_args = [
|
// Invalid test cases grouped by expected error kind
|
||||||
|
let invalid_value = [
|
||||||
// No default defined for --format
|
// No default defined for --format
|
||||||
flat_map_collect(insert(
|
flat_map_collect(insert(
|
||||||
args.clone(),
|
args.clone(),
|
||||||
@@ -1055,6 +1432,9 @@ mod test {
|
|||||||
CliOption::new("image", ["--", "/dev/null"]),
|
CliOption::new("image", ["--", "/dev/null"]),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let invalid_conflict = [
|
||||||
// --print-json-schema conflicts with input
|
// --print-json-schema conflicts with input
|
||||||
flat_map_collect(insert(
|
flat_map_collect(insert(
|
||||||
args.clone(),
|
args.clone(),
|
||||||
@@ -1081,7 +1461,7 @@ mod test {
|
|||||||
)),
|
)),
|
||||||
// --print-json-schema conflicts with --show-secrets
|
// --print-json-schema conflicts with --show-secrets
|
||||||
flat_map_collect(insert(
|
flat_map_collect(insert(
|
||||||
args,
|
args.clone(),
|
||||||
vec![
|
vec![
|
||||||
CliOption::new("print-json-schema", ["--print-schema", "json"]),
|
CliOption::new("print-json-schema", ["--print-schema", "json"]),
|
||||||
CliOption::new("show-secrets", ["--show-secrets"]),
|
CliOption::new("show-secrets", ["--show-secrets"]),
|
||||||
@@ -1091,10 +1471,6 @@ mod test {
|
|||||||
|
|
||||||
let mut pvimg_valid_args = vec![];
|
let mut pvimg_valid_args = vec![];
|
||||||
|
|
||||||
// Test for invalid combinations
|
|
||||||
// Input is missing
|
|
||||||
let mut pvimg_invalid_args = vec![vec!["pvimg", "info"]];
|
|
||||||
|
|
||||||
for create_args in &valid_test_args {
|
for create_args in &valid_test_args {
|
||||||
pvimg_valid_args.push(
|
pvimg_valid_args.push(
|
||||||
[
|
[
|
||||||
@@ -1105,30 +1481,126 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for invalid_test_arg in &invalid_test_args {
|
let invalid_format = [
|
||||||
pvimg_invalid_args.push(
|
// Invalid format variant for text
|
||||||
[
|
flat_map_collect(insert(
|
||||||
["pvimg", "info"].to_vec(),
|
args.clone(),
|
||||||
Vec::from_iter(invalid_test_arg.iter().map(String::as_str)),
|
vec![
|
||||||
]
|
CliOption::new("format", ["--format", "text:minify"]),
|
||||||
.concat(),
|
CliOption::new("image", ["/dev/null"]),
|
||||||
);
|
],
|
||||||
}
|
)),
|
||||||
|
// Invalid format variant for json
|
||||||
|
flat_map_collect(insert(
|
||||||
|
args.clone(),
|
||||||
|
vec![
|
||||||
|
CliOption::new("format", ["--format", "json:full"]),
|
||||||
|
CliOption::new("image", ["/dev/null"]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
for arg in pvimg_valid_args {
|
let test_cases = [
|
||||||
let res = CliOptions::try_parse_from(&arg);
|
(
|
||||||
#[allow(clippy::use_debug, clippy::print_stdout)]
|
&invalid_value[..],
|
||||||
if let Err(e) = &res {
|
clap::error::ErrorKind::InvalidValue,
|
||||||
println!("arg: {arg:?}");
|
"InvalidValue",
|
||||||
println!("{e}");
|
),
|
||||||
}
|
(
|
||||||
assert!(res.is_ok());
|
&invalid_conflict[..],
|
||||||
}
|
clap::error::ErrorKind::ArgumentConflict,
|
||||||
|
"ArgumentConflict",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&invalid_format[..],
|
||||||
|
clap::error::ErrorKind::ValueValidation,
|
||||||
|
"ValueValidation",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
for arg in pvimg_invalid_args {
|
let parse_pvimg = |args: &[&str]| CliOptions::try_parse_from(args);
|
||||||
let res = CliOptions::try_parse_from(&arg);
|
test_valid_args(
|
||||||
assert!(res.is_err());
|
pvimg_valid_args,
|
||||||
}
|
&parse_pvimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||||
|
"pvimg info",
|
||||||
|
);
|
||||||
|
test_invalid_args(
|
||||||
|
&test_cases,
|
||||||
|
&["pvimg", "info"],
|
||||||
|
&parse_pvimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||||
|
"pvimg info",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hdr_version_conversions() {
|
||||||
|
assert_eq!(HkdVersion::from(HdrVersion::V1), HkdVersion::Classical);
|
||||||
|
assert_eq!(HkdVersion::from(HdrVersion::V2), HkdVersion::Hybrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_output_format_kind_display() {
|
||||||
|
assert_eq!(OutputFormatKind::Text.to_string(), "human-readable");
|
||||||
|
assert_eq!(OutputFormatKind::Json.to_string(), "JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_output_format_kind_from_str() {
|
||||||
|
assert_eq!(
|
||||||
|
"text".parse::<OutputFormatKind>().unwrap(),
|
||||||
|
OutputFormatKind::Text
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"json".parse::<OutputFormatKind>().unwrap(),
|
||||||
|
OutputFormatKind::Json
|
||||||
|
);
|
||||||
|
assert!("invalid".parse::<OutputFormatKind>().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_version_cmd_opts() {
|
||||||
|
let opts = CliOptions::new_version_cmd_opts();
|
||||||
|
assert!(opts.version);
|
||||||
|
assert!(matches!(opts.cmd, SubCommands::Version));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_genprotimg_to_cli_options_conversion() {
|
||||||
|
let genprotimg_opts = GenprotimgCliOptions {
|
||||||
|
args: Box::new(CreateBootImageArgs::default()),
|
||||||
|
verbose: DeprecatedVerbosityOptions::default(),
|
||||||
|
version: (),
|
||||||
|
help_all: (),
|
||||||
|
help_experimental: (),
|
||||||
|
};
|
||||||
|
let cli_opts: CliOptions = genprotimg_opts.into();
|
||||||
|
assert!(!cli_opts.version);
|
||||||
|
assert!(matches!(cli_opts.cmd, SubCommands::Create(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_se_hdr_flag_name_display() {
|
||||||
|
assert_eq!(
|
||||||
|
SeHdrFlagName::ConfidentialDump.to_string(),
|
||||||
|
"confidential-dump"
|
||||||
|
);
|
||||||
|
assert_eq!(SeHdrFlagName::PckmoDeaTdea.to_string(), "pckmo-dea-tdea");
|
||||||
|
assert_eq!(SeHdrFlagName::PckmoAes.to_string(), "pckmo-aes");
|
||||||
|
assert_eq!(SeHdrFlagName::PckmoEcc.to_string(), "pckmo-ecc");
|
||||||
|
assert_eq!(SeHdrFlagName::PckmoHmac.to_string(), "pckmo-hmac");
|
||||||
|
assert_eq!(
|
||||||
|
SeHdrFlagName::BackupTargetKeys.to_string(),
|
||||||
|
"backup-target-keys"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
SeHdrFlagName::CckExtensionSecretEnforcement.to_string(),
|
||||||
|
"cck-extension-secret-enforcement"
|
||||||
|
);
|
||||||
|
assert_eq!(SeHdrFlagName::CckUpdate.to_string(), "cck-update");
|
||||||
|
assert_eq!(
|
||||||
|
SeHdrFlagName::NoComponentEncryption.to_string(),
|
||||||
|
"no-component-encryption"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ mod info;
|
|||||||
mod test;
|
mod test;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
pub const CMD_FN: &[&str] = &["+create", "+test", "+info"];
|
pub const CMD_FN: &[&str] = &["+create", "+test", "+info", "+quantumsafe"];
|
||||||
|
|
||||||
pub use create::create;
|
pub use create::create;
|
||||||
pub use info::info;
|
pub use info::info;
|
||||||
|
|||||||
+397
-72
@@ -5,18 +5,21 @@
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use log::{debug, warn};
|
use log::{debug, info, warn};
|
||||||
use pv::misc::{open_file, try_parse_u64};
|
use pv::misc::{open_file, try_parse_u64};
|
||||||
|
use pv::request::HostKey;
|
||||||
use pvimg::error::OwnExitCode;
|
use pvimg::error::OwnExitCode;
|
||||||
use pvimg::secured_comp::ComponentTrait;
|
use pvimg::secured_comp::ComponentTrait;
|
||||||
use pvimg::uvdata::{
|
use pvimg::uvdata::{
|
||||||
ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1,
|
EffectiveControlFlags, FlagState, FlagsOverride, SeHdrControlFlags, SeHdrControlFlagsModel,
|
||||||
SeHdrDataV1, SecretControlFlagsV1,
|
SeHdrDataV1, SeHdrDataV2, SeHdrFlag, SeHdrVersion, SeTarget,
|
||||||
};
|
};
|
||||||
use utils::{AtomicFile, AtomicFileOperation};
|
use utils::{AtomicFile, AtomicFileOperation};
|
||||||
|
|
||||||
use crate::cli::{ComponentPaths, CreateBootImageArgs};
|
use crate::cli::{
|
||||||
|
ComponentPaths, CreateBootImageArgs, HdrVersion, HdrVersionSelection, SeHdrFlagName,
|
||||||
|
};
|
||||||
use crate::cmd::common::read_user_provided_keys;
|
use crate::cmd::common::read_user_provided_keys;
|
||||||
use crate::se_img::{SeHdrArgs, SeImgBuilder};
|
use crate::se_img::{SeHdrArgs, SeImgBuilder};
|
||||||
use crate::se_img_comps::cmdline::Cmdline;
|
use crate::se_img_comps::cmdline::Cmdline;
|
||||||
@@ -24,6 +27,21 @@ use crate::se_img_comps::kernel::S390Kernel;
|
|||||||
use crate::se_img_comps::ramdisk::Ramdisk;
|
use crate::se_img_comps::ramdisk::Ramdisk;
|
||||||
use crate::se_img_comps::{check_components, Component};
|
use crate::se_img_comps::{check_components, Component};
|
||||||
|
|
||||||
|
/// Convert CLI flag name to internal SeHdrFlag
|
||||||
|
fn convert_flag_name(name: SeHdrFlagName) -> SeHdrFlag {
|
||||||
|
match name {
|
||||||
|
SeHdrFlagName::ConfidentialDump => SeHdrFlag::ConfidentialDump,
|
||||||
|
SeHdrFlagName::PckmoDeaTdea => SeHdrFlag::PckmoDeaTdea,
|
||||||
|
SeHdrFlagName::PckmoAes => SeHdrFlag::PckmoAes,
|
||||||
|
SeHdrFlagName::PckmoEcc => SeHdrFlag::PckmoEcc,
|
||||||
|
SeHdrFlagName::PckmoHmac => SeHdrFlag::PckmoHmac,
|
||||||
|
SeHdrFlagName::BackupTargetKeys => SeHdrFlag::BackupTargetKeys,
|
||||||
|
SeHdrFlagName::CckExtensionSecretEnforcement => SeHdrFlag::CckExtensionSecretEnforcement,
|
||||||
|
SeHdrFlagName::CckUpdate => SeHdrFlag::CckUpdate,
|
||||||
|
SeHdrFlagName::NoComponentEncryption => SeHdrFlag::NoComponentEncryption,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The returned vector is sorted by the occurrence in the memory layout:
|
/// The returned vector is sorted by the occurrence in the memory layout:
|
||||||
/// First the kernel, then the ramdisk and then the kernel cmdline.
|
/// First the kernel, then the ramdisk and then the kernel cmdline.
|
||||||
///
|
///
|
||||||
@@ -43,91 +61,272 @@ fn components(component_args: &ComponentPaths) -> Result<Vec<Component>> {
|
|||||||
Ok(components)
|
Ok(components)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_flags(
|
/// Parse new-style flags (--flags and --disable-flags)
|
||||||
args: &CreateBootImageArgs,
|
fn parse_new_style_flags(
|
||||||
) -> Result<(PlaintextControlFlagsV1, SecretControlFlagsV1)> {
|
flags: &[SeHdrFlagName],
|
||||||
let lf = &args.legacy_flags;
|
disable_flags: &[SeHdrFlagName],
|
||||||
|
version: SeHdrVersion,
|
||||||
|
) -> Result<(FlagsOverride<SeHdrFlag>, FlagsOverride<SeHdrFlag>)> {
|
||||||
|
let target = SeTarget::from_se_hdr_version(version);
|
||||||
|
let mut pcf_overrides: FlagsOverride<SeHdrFlag> = FlagsOverride::new();
|
||||||
|
let mut scf_overrides: FlagsOverride<SeHdrFlag> = FlagsOverride::new();
|
||||||
|
|
||||||
|
if flags.is_empty() && disable_flags.is_empty() {
|
||||||
|
return Ok((pcf_overrides, scf_overrides));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pcf_model = SeHdrControlFlagsModel::pcf_for_target(target);
|
||||||
|
let scf_model = SeHdrControlFlagsModel::scf_for_target(target);
|
||||||
|
let pcf_supported = pcf_model.supported_flags();
|
||||||
|
let scf_supported = scf_model.supported_flags();
|
||||||
|
|
||||||
|
// Helper function to process flags
|
||||||
|
let mut process_flags =
|
||||||
|
|flag_list: &[SeHdrFlagName], enable: bool, flag_type: &str| -> Result<()> {
|
||||||
|
if flag_list.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let converted_flags: Vec<SeHdrFlag> =
|
||||||
|
flag_list.iter().map(|&f| convert_flag_name(f)).collect();
|
||||||
|
|
||||||
|
for flag in &converted_flags {
|
||||||
|
if pcf_supported.contains(flag) {
|
||||||
|
if enable {
|
||||||
|
pcf_overrides.enable(*flag);
|
||||||
|
} else {
|
||||||
|
pcf_overrides.disable(*flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scf_supported.contains(flag) {
|
||||||
|
if enable {
|
||||||
|
scf_overrides.enable(*flag);
|
||||||
|
} else {
|
||||||
|
scf_overrides.disable(*flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all flags were consumed (supported by either pcf or scf)
|
||||||
|
let unsupported_flags: Vec<&SeHdrFlag> = converted_flags
|
||||||
|
.iter()
|
||||||
|
.filter(|flag| !pcf_supported.contains(flag) && !scf_supported.contains(flag))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !unsupported_flags.is_empty() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"The following {} are not supported for SE header version {:?}: {:?}",
|
||||||
|
flag_type,
|
||||||
|
version,
|
||||||
|
unsupported_flags
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
process_flags(flags, true, "flags")?;
|
||||||
|
process_flags(disable_flags, false, "disable flags")?;
|
||||||
|
|
||||||
|
Ok((pcf_overrides, scf_overrides))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse legacy-style flags
|
||||||
|
fn parse_legacy_flags(
|
||||||
|
legacy_flags: &crate::cli::CreateBootImageLegacyFlags,
|
||||||
|
) -> (FlagsOverride<SeHdrFlag>, FlagsOverride<SeHdrFlag>) {
|
||||||
macro_rules! flag_disabled {
|
macro_rules! flag_disabled {
|
||||||
($cli_flag:expr, $control_flags:expr) => {
|
($cli_flag:expr, $control_flags:expr) => {
|
||||||
$cli_flag
|
$cli_flag.filter(|x| *x).map(|_| {
|
||||||
.filter(|x| *x)
|
let mut flags = FlagsOverride::new();
|
||||||
.and(Some(ControlFlagTrait::all_disabled($control_flags)))
|
flags.disable_all($control_flags);
|
||||||
|
flags
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
macro_rules! flag_enabled {
|
macro_rules! flag_enabled {
|
||||||
($cli_flag:expr, $control_flags:expr) => {
|
($cli_flag:expr, $control_flags:expr) => {
|
||||||
$cli_flag
|
$cli_flag.filter(|x| *x).map(|_| {
|
||||||
.filter(|x| *x)
|
let mut flags = FlagsOverride::new();
|
||||||
.and(Some(ControlFlagTrait::all_enabled($control_flags)))
|
flags.enable_all($control_flags);
|
||||||
|
flags
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let plaintext_flags: Vec<FlagData<PcfV1>> = [
|
let pcf_overrides = [
|
||||||
flag_disabled!(lf.disable_dump, [PcfV1::AllowDumping]),
|
flag_disabled!(legacy_flags.disable_dump, [SeHdrFlag::ConfidentialDump]),
|
||||||
flag_enabled!(lf.enable_dump, [PcfV1::AllowDumping]),
|
flag_enabled!(legacy_flags.enable_dump, [SeHdrFlag::ConfidentialDump]),
|
||||||
flag_disabled!(lf.disable_pckmo, PlaintextControlFlagsV1::PCKMO),
|
flag_disabled!(legacy_flags.disable_pckmo, SeHdrControlFlagsModel::PCKMO),
|
||||||
flag_enabled!(lf.enable_pckmo, PlaintextControlFlagsV1::PCKMO),
|
flag_enabled!(legacy_flags.enable_pckmo, SeHdrControlFlagsModel::PCKMO),
|
||||||
flag_disabled!(lf.disable_pckmo_hmac, [PcfV1::PckmoHmac]),
|
flag_disabled!(legacy_flags.disable_pckmo_hmac, [SeHdrFlag::PckmoHmac]),
|
||||||
flag_enabled!(lf.enable_pckmo_hmac, [PcfV1::PckmoHmac]),
|
flag_enabled!(legacy_flags.enable_pckmo_hmac, [SeHdrFlag::PckmoHmac]),
|
||||||
flag_disabled!(lf.disable_backup_keys, [PcfV1::BackupTargetKeys]),
|
|
||||||
flag_enabled!(lf.enable_backup_keys, [PcfV1::BackupTargetKeys]),
|
|
||||||
flag_enabled!(lf.disable_image_encryption, [PcfV1::NoComponentEncryption]),
|
|
||||||
flag_disabled!(lf.enable_image_encryption, [PcfV1::NoComponentEncryption]),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
// This is ensured by Clap's `conflicts_with`.
|
|
||||||
assert!(PlaintextControlFlagsV1::no_duplicates(&plaintext_flags));
|
|
||||||
|
|
||||||
let secret_flags: Vec<FlagData<ScfV1>> = [
|
|
||||||
flag_disabled!(
|
flag_disabled!(
|
||||||
lf.disable_cck_extension_secret,
|
legacy_flags.disable_backup_keys,
|
||||||
[ScfV1::CckExtensionSecretEnforcement]
|
[SeHdrFlag::BackupTargetKeys]
|
||||||
),
|
),
|
||||||
flag_enabled!(
|
flag_enabled!(
|
||||||
lf.enable_cck_extension_secret,
|
legacy_flags.enable_backup_keys,
|
||||||
[ScfV1::CckExtensionSecretEnforcement]
|
[SeHdrFlag::BackupTargetKeys]
|
||||||
|
),
|
||||||
|
flag_enabled!(
|
||||||
|
legacy_flags.disable_image_encryption,
|
||||||
|
[SeHdrFlag::NoComponentEncryption]
|
||||||
|
),
|
||||||
|
flag_disabled!(
|
||||||
|
legacy_flags.enable_image_encryption,
|
||||||
|
[SeHdrFlag::NoComponentEncryption]
|
||||||
),
|
),
|
||||||
flag_disabled!(lf.disable_cck_update, [ScfV1::CckUpdateAllowed]),
|
|
||||||
flag_enabled!(lf.enable_cck_update, [ScfV1::CckUpdateAllowed]),
|
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.fold(FlagsOverride::new(), |mut acc, override_set| {
|
||||||
|
for (flag, state) in override_set.iter() {
|
||||||
|
match state {
|
||||||
|
FlagState::Enabled => acc.enable(*flag),
|
||||||
|
FlagState::Disabled => acc.disable(*flag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
|
let scf_overrides = [
|
||||||
|
flag_disabled!(
|
||||||
|
legacy_flags.disable_cck_extension_secret,
|
||||||
|
[SeHdrFlag::CckExtensionSecretEnforcement]
|
||||||
|
),
|
||||||
|
flag_enabled!(
|
||||||
|
legacy_flags.enable_cck_extension_secret,
|
||||||
|
[SeHdrFlag::CckExtensionSecretEnforcement]
|
||||||
|
),
|
||||||
|
flag_disabled!(legacy_flags.disable_cck_update, [SeHdrFlag::CckUpdate]),
|
||||||
|
flag_enabled!(legacy_flags.enable_cck_update, [SeHdrFlag::CckUpdate]),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.fold(FlagsOverride::new(), |mut acc, override_set| {
|
||||||
// This is ensured by Clap's `conflicts_with`.
|
for (flag, state) in override_set.iter() {
|
||||||
assert!(SecretControlFlagsV1::no_duplicates(&secret_flags));
|
match state {
|
||||||
|
FlagState::Enabled => acc.enable(*flag),
|
||||||
|
FlagState::Disabled => acc.disable(*flag),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
|
||||||
let mut pcf: PlaintextControlFlagsV1 = match &args.experimental_args.x_pcf {
|
(pcf_overrides, scf_overrides)
|
||||||
Some(v) => try_parse_u64(v, "x-pcf")?.into(),
|
}
|
||||||
None => PlaintextControlFlagsV1::default(),
|
|
||||||
};
|
|
||||||
pcf.parse_flags(&plaintext_flags);
|
|
||||||
debug!("Using plaintext flags: {pcf:#x}");
|
|
||||||
|
|
||||||
let mut scf: SecretControlFlagsV1 = match &args.experimental_args.x_scf {
|
/// Apply experimental overrides to control flags
|
||||||
Some(v) => try_parse_u64(v, "x-scf")?.into(),
|
fn apply_experimental_overrides(
|
||||||
None => SecretControlFlagsV1::default(),
|
pcf_overrides: &FlagsOverride<SeHdrFlag>,
|
||||||
|
scf_overrides: &FlagsOverride<SeHdrFlag>,
|
||||||
|
x_pcf: &Option<String>,
|
||||||
|
x_scf: &Option<String>,
|
||||||
|
target: SeTarget,
|
||||||
|
) -> Result<(
|
||||||
|
EffectiveControlFlags<SeHdrFlag>,
|
||||||
|
EffectiveControlFlags<SeHdrFlag>,
|
||||||
|
)> {
|
||||||
|
let pcf = match x_pcf {
|
||||||
|
Some(v) => {
|
||||||
|
assert_eq!(pcf_overrides.len(), 0);
|
||||||
|
SeHdrControlFlags::from_u64(try_parse_u64(v, "x-pcf")?, target, true)
|
||||||
|
}
|
||||||
|
None => SeHdrControlFlagsModel::pcf_for_target(target).with_overrides(pcf_overrides)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scf = match x_scf {
|
||||||
|
Some(v) => {
|
||||||
|
assert_eq!(scf_overrides.len(), 0);
|
||||||
|
SeHdrControlFlags::from_u64(try_parse_u64(v, "x-scf")?, target, false)
|
||||||
|
}
|
||||||
|
None => SeHdrControlFlagsModel::scf_for_target(target).with_overrides(scf_overrides)?,
|
||||||
};
|
};
|
||||||
scf.parse_flags(&secret_flags);
|
|
||||||
debug!("Using secret flags: {scf:#x}");
|
|
||||||
|
|
||||||
Ok((pcf, scf))
|
Ok((pcf, scf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_flags(
|
||||||
|
args: &CreateBootImageArgs,
|
||||||
|
version: SeHdrVersion,
|
||||||
|
) -> Result<(
|
||||||
|
EffectiveControlFlags<SeHdrFlag>,
|
||||||
|
EffectiveControlFlags<SeHdrFlag>,
|
||||||
|
)> {
|
||||||
|
let target = SeTarget::from_se_hdr_version(version);
|
||||||
|
|
||||||
|
// Legacy flags and --(disable-)flags are mutually exclusive. Clap semantics
|
||||||
|
// is used for that.
|
||||||
|
let (pcf_overrides, scf_overrides) = if args.flags.is_empty() && args.disable_flags.is_empty() {
|
||||||
|
parse_legacy_flags(&args.legacy_flags)
|
||||||
|
} else {
|
||||||
|
parse_new_style_flags(&args.flags, &args.disable_flags, version)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let (pcf, scf) = apply_experimental_overrides(
|
||||||
|
&pcf_overrides,
|
||||||
|
&scf_overrides,
|
||||||
|
&args.experimental_args.x_pcf,
|
||||||
|
&args.experimental_args.x_scf,
|
||||||
|
target,
|
||||||
|
)?;
|
||||||
|
info!("Using plaintext flags:\n{pcf}");
|
||||||
|
info!("Using secret flags:\n{scf}");
|
||||||
|
|
||||||
|
Ok((pcf, scf))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auto-detect the SE header version based on the host keys.
|
||||||
|
///
|
||||||
|
/// Returns V2 if any host key is a hybrid key, otherwise returns V1.
|
||||||
|
fn auto_detect_version(host_keys: &[HostKey]) -> SeHdrVersion {
|
||||||
|
let use_hybrid_keys = host_keys.iter().any(|k: &HostKey| k.is_hybrid());
|
||||||
|
if use_hybrid_keys {
|
||||||
|
SeHdrVersion::V2
|
||||||
|
} else {
|
||||||
|
SeHdrVersion::V1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HdrVersion> for SeHdrVersion {
|
||||||
|
fn from(value: HdrVersion) -> Self {
|
||||||
|
match value {
|
||||||
|
HdrVersion::V1 => Self::V1,
|
||||||
|
HdrVersion::V2 => Self::V2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the SE header version to use.
|
||||||
|
///
|
||||||
|
/// If an explicit version is provided via CLI, use that.
|
||||||
|
/// Otherwise, auto-detect based on the host key types.
|
||||||
|
fn determine_version(cli_version: HdrVersionSelection, host_keys: &[HostKey]) -> SeHdrVersion {
|
||||||
|
match cli_version {
|
||||||
|
HdrVersionSelection::Auto => auto_detect_version(host_keys),
|
||||||
|
HdrVersionSelection::Explicit(hdr_version) => hdr_version.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a Secure Execution boot image
|
/// Create a Secure Execution boot image
|
||||||
pub fn create(opt: &CreateBootImageArgs) -> Result<OwnExitCode> {
|
pub fn create(opt: &CreateBootImageArgs) -> Result<OwnExitCode> {
|
||||||
// Verify host key documents first, because if they are not valid there is
|
// Verify host key documents first, because if they are not valid there is
|
||||||
// no reason to continue.
|
// no reason to continue.
|
||||||
let verified_host_keys = opt
|
let verified_host_keys = opt.certificate_args.get_verified_hkds(
|
||||||
.certificate_args
|
"Secure Execution image",
|
||||||
.get_verified_hkds("Secure Execution image")?;
|
HdrVersionSelection::Explicit(opt.hdr_version).map(|v| v.into()),
|
||||||
let user_provided_keys = read_user_provided_keys(&opt.keys)?;
|
)?;
|
||||||
let (plaintext_flags, secret_flags) = parse_flags(opt)?;
|
|
||||||
|
|
||||||
if plaintext_flags.is_set(PcfV1::NoComponentEncryption) {
|
let version = determine_version(
|
||||||
|
HdrVersionSelection::Explicit(opt.hdr_version),
|
||||||
|
&verified_host_keys,
|
||||||
|
);
|
||||||
|
|
||||||
|
let user_provided_keys = read_user_provided_keys(&opt.keys)?;
|
||||||
|
let (plaintext_flags, secret_flags) = parse_flags(opt, version)?;
|
||||||
|
|
||||||
|
if plaintext_flags.has(SeHdrFlag::NoComponentEncryption) {
|
||||||
warn!("The components encryption is disabled, make sure that the components do not contain any confidential content.");
|
warn!("The components encryption is disabled, make sure that the components do not contain any confidential content.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,11 +339,15 @@ pub fn create(opt: &CreateBootImageArgs) -> Result<OwnExitCode> {
|
|||||||
|
|
||||||
// FIXME get rid of the legacy mode. But that's only possible as soon as all
|
// FIXME get rid of the legacy mode. But that's only possible as soon as all
|
||||||
// available tools are updated.
|
// available tools are updated.
|
||||||
let expected_se_hdr_size = SeHdrDataV1::expected_size(verified_host_keys.len())?;
|
let expected_se_hdr_size = match version {
|
||||||
|
SeHdrVersion::V1 => SeHdrDataV1::expected_size(verified_host_keys.len())?,
|
||||||
|
SeHdrVersion::V2 => SeHdrDataV2::expected_size(verified_host_keys.len())?,
|
||||||
|
_ => return Err(anyhow!("Unsupported SE header version: {:?}", version)),
|
||||||
|
};
|
||||||
let mut writer = AtomicFile::with_extension(&opt.output, "part", &mut OpenOptions::new())?;
|
let mut writer = AtomicFile::with_extension(&opt.output, "part", &mut OpenOptions::new())?;
|
||||||
let mut seimg_ctx = SeImgBuilder::new_v1(
|
let mut seimg_ctx = SeImgBuilder::new(
|
||||||
&mut writer,
|
&mut writer,
|
||||||
plaintext_flags.is_unset(PcfV1::NoComponentEncryption),
|
!plaintext_flags.has(SeHdrFlag::NoComponentEncryption),
|
||||||
Some(expected_se_hdr_size),
|
Some(expected_se_hdr_size),
|
||||||
opt.experimental_args.x_bootloader_directory.as_ref(),
|
opt.experimental_args.x_bootloader_directory.as_ref(),
|
||||||
)?;
|
)?;
|
||||||
@@ -218,13 +421,135 @@ mod test {
|
|||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let parsed_flags = super::parse_flags(&args).expect("Failed to parse flags {args:?}");
|
let parsed_flags =
|
||||||
let mut exp_pcf = Vec::from(PlaintextControlFlagsV1::PCKMO);
|
super::parse_flags(&args, SeHdrVersion::V1).expect("Failed to parse flags {args:?}");
|
||||||
exp_pcf.push(PcfV1::AllowDumping);
|
|
||||||
let pcf = PlaintextControlFlagsV1::from_flags(PcfV1::all_enabled(exp_pcf));
|
// Build expected PCF
|
||||||
assert_eq!(parsed_flags.0, pcf);
|
let mut exp_pcf = Vec::from(SeHdrControlFlagsModel::PCKMO);
|
||||||
let exp_scf = vec![ScfV1::CckUpdateAllowed];
|
exp_pcf.push(SeHdrFlag::ConfidentialDump);
|
||||||
let scf = SecretControlFlagsV1::from_flags(ScfV1::all_enabled(exp_scf));
|
let mut pcf_overrides = FlagsOverride::new();
|
||||||
assert_eq!(parsed_flags.1, scf);
|
for flag in &exp_pcf {
|
||||||
|
pcf_overrides.enable(*flag);
|
||||||
|
}
|
||||||
|
let expected_pcf = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max)
|
||||||
|
.with_overrides(&pcf_overrides)
|
||||||
|
.expect("Failed to create expected PCF");
|
||||||
|
assert_eq!(parsed_flags.0, expected_pcf);
|
||||||
|
|
||||||
|
// Build expected SCF
|
||||||
|
let exp_scf = vec![SeHdrFlag::CckUpdate];
|
||||||
|
let mut scf_overrides = FlagsOverride::new();
|
||||||
|
for flag in &exp_scf {
|
||||||
|
scf_overrides.enable(*flag);
|
||||||
|
}
|
||||||
|
let expected_scf = SeHdrControlFlagsModel::scf_for_target(SeTarget::V1Max)
|
||||||
|
.with_overrides(&scf_overrides)
|
||||||
|
.expect("Failed to create expected SCF");
|
||||||
|
assert_eq!(parsed_flags.1, expected_scf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_auto_detect_version_v1() {
|
||||||
|
// Mock non-hybrid keys - should return V1
|
||||||
|
// Note: This is a simplified test. In real usage, you'd need actual HostKey instances
|
||||||
|
let keys: Vec<HostKey> = vec![];
|
||||||
|
let version = auto_detect_version(&keys);
|
||||||
|
assert_eq!(version, SeHdrVersion::V1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_determine_version_explicit_v1() {
|
||||||
|
let keys: Vec<HostKey> = vec![];
|
||||||
|
let version = determine_version(HdrVersionSelection::Explicit(HdrVersion::V1), &keys);
|
||||||
|
assert_eq!(version, SeHdrVersion::V1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_determine_version_explicit_v2() {
|
||||||
|
let keys: Vec<HostKey> = vec![];
|
||||||
|
let version = determine_version(HdrVersionSelection::Explicit(HdrVersion::V2), &keys);
|
||||||
|
assert_eq!(version, SeHdrVersion::V2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_determine_version_auto_detect() {
|
||||||
|
let keys: Vec<HostKey> = vec![];
|
||||||
|
let version = determine_version(HdrVersionSelection::Auto, &keys);
|
||||||
|
// With empty keys, should default to V1
|
||||||
|
assert_eq!(version, SeHdrVersion::V1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_flags_with_disable_flags_no_conflict() {
|
||||||
|
let args = CreateBootImageArgs {
|
||||||
|
flags: vec![SeHdrFlagName::ConfidentialDump],
|
||||||
|
disable_flags: vec![SeHdrFlagName::PckmoHmac],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = super::parse_flags(&args, SeHdrVersion::V1);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let (pcf, scf) = result.unwrap();
|
||||||
|
// ConfidentialDump should be enabled
|
||||||
|
assert!(pcf.has(SeHdrFlag::ConfidentialDump));
|
||||||
|
// PckmoHmac should be disabled
|
||||||
|
assert!(!pcf.has(SeHdrFlag::PckmoHmac));
|
||||||
|
assert_eq!(pcf.to_u64(), 0b100000000000000000000011100000_u64);
|
||||||
|
assert_eq!(scf.to_u64(), 0b0_u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_flags_with_multiple_disable_flags() {
|
||||||
|
let args = CreateBootImageArgs {
|
||||||
|
disable_flags: vec![SeHdrFlagName::PckmoHmac, SeHdrFlagName::BackupTargetKeys],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = super::parse_flags(&args, SeHdrVersion::V1);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let (pcf, _scf) = result.unwrap();
|
||||||
|
// Both flags should be disabled
|
||||||
|
assert!(!pcf.has(SeHdrFlag::PckmoHmac));
|
||||||
|
assert!(!pcf.has(SeHdrFlag::BackupTargetKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_flags_with_only_disable_flags() {
|
||||||
|
let args = CreateBootImageArgs {
|
||||||
|
disable_flags: vec![SeHdrFlagName::PckmoDeaTdea, SeHdrFlagName::PckmoAes],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = super::parse_flags(&args, SeHdrVersion::V1);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let (pcf, _scf) = result.unwrap();
|
||||||
|
// PCKMO DEA/TDEA and AES should be disabled
|
||||||
|
assert!(!pcf.has(SeHdrFlag::PckmoDeaTdea));
|
||||||
|
assert!(!pcf.has(SeHdrFlag::PckmoAes));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_flags_enable_and_disable_different_flags() {
|
||||||
|
let args = CreateBootImageArgs {
|
||||||
|
flags: vec![
|
||||||
|
SeHdrFlagName::ConfidentialDump,
|
||||||
|
SeHdrFlagName::BackupTargetKeys,
|
||||||
|
],
|
||||||
|
disable_flags: vec![
|
||||||
|
SeHdrFlagName::PckmoHmac,
|
||||||
|
SeHdrFlagName::NoComponentEncryption,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = super::parse_flags(&args, SeHdrVersion::V1);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let (pcf, _scf) = result.unwrap();
|
||||||
|
// Enabled flags should be set
|
||||||
|
assert!(pcf.has(SeHdrFlag::ConfidentialDump));
|
||||||
|
assert!(pcf.has(SeHdrFlag::BackupTargetKeys));
|
||||||
|
// Disabled flags should not be set
|
||||||
|
assert!(!pcf.has(SeHdrFlag::PckmoHmac));
|
||||||
|
assert!(!pcf.has(SeHdrFlag::NoComponentEncryption));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-43
@@ -6,10 +6,12 @@ use std::path::Path;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use pv::misc::{open_file, read_certs, read_file};
|
use pv::misc::open_file;
|
||||||
|
use pv::request::NoVerifyHkd;
|
||||||
use pv::{FileAccessErrorType, PvCoreError};
|
use pv::{FileAccessErrorType, PvCoreError};
|
||||||
use pvimg::error::{Error, OwnExitCode, PvError};
|
use pvimg::error::{Error, OwnExitCode};
|
||||||
use pvimg::uvdata::{KeyExchangeTrait, SeHdr, UvKeyHashesV1};
|
use pvimg::uvdata::{KeyExchangeTrait, SeHdr, UvKeyHashesV1};
|
||||||
|
use utils::hkd::{HkdLoader, HkdVersionSelection};
|
||||||
use utils::HexSlice;
|
use utils::HexSlice;
|
||||||
|
|
||||||
use crate::cli::TestArgs;
|
use crate::cli::TestArgs;
|
||||||
@@ -31,32 +33,25 @@ fn hdr_test_target_hashes(hdr: &SeHdr, key_hashes: &Path) -> Result<bool> {
|
|||||||
err => Error::PvCore(err),
|
err => Error::PvCore(err),
|
||||||
})?;
|
})?;
|
||||||
let hashes = UvKeyHashesV1::read_from_io(file)?;
|
let hashes = UvKeyHashesV1::read_from_io(file)?;
|
||||||
let mut contains = hdr.contains_hash(&hashes.pchkh);
|
let matches = hashes.matching_hashes(hdr);
|
||||||
if contains {
|
if matches.is_empty() {
|
||||||
log_println!(
|
|
||||||
" ✓ Host key hash {:#} is included",
|
|
||||||
HexSlice::from(&hashes.pchkh)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if hdr.contains_hash(&hashes.pbhkh) {
|
|
||||||
log_println!(
|
|
||||||
" ✓ Backup host key hash {:#} is included",
|
|
||||||
HexSlice::from(&hashes.pbhkh)
|
|
||||||
);
|
|
||||||
contains = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
for hash in hashes.res {
|
|
||||||
if hdr.contains_hash(&hash) {
|
|
||||||
log_println!(" ✓ Key hash {:#} is included", HexSlice::from(&hash));
|
|
||||||
contains = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !contains {
|
|
||||||
warn!(" ✘ None of the key hashes is included");
|
warn!(" ✘ None of the key hashes is included");
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
for m in matches {
|
||||||
|
match m.idx.kind() {
|
||||||
|
Some(kind) => {
|
||||||
|
log_println!(" ✓ {kind} {:#} is included", HexSlice::from(&m.hash))
|
||||||
|
}
|
||||||
|
None => log_println!(
|
||||||
|
" ✓ Key hash {:#} is included (zero-based index {})",
|
||||||
|
HexSlice::from(&m.hash),
|
||||||
|
m.idx.index()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
Ok(contains)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Ok(true)` if at least one of the given public key of the host key
|
/// Returns `Ok(true)` if at least one of the given public key of the host key
|
||||||
@@ -72,27 +67,21 @@ where
|
|||||||
|
|
||||||
let mut result = false;
|
let mut result = false;
|
||||||
for path in host_key_documents {
|
for path in host_key_documents {
|
||||||
let hkd_path = path.as_ref();
|
let hkd = HkdLoader::load_and_verify(
|
||||||
let hkd_data = read_file(hkd_path, "host key document")?;
|
path,
|
||||||
let certs = read_certs(&hkd_data)?;
|
&NoVerifyHkd,
|
||||||
if certs.is_empty() {
|
HkdVersionSelection::Explicit(hdr.common.version.into()),
|
||||||
return Err(PvError::NoHkdInFile(hkd_path.display().to_string()).into());
|
)?;
|
||||||
}
|
if hdr.contains(hkd)? {
|
||||||
|
|
||||||
if certs.len() != 1 {
|
|
||||||
warn!("The host key document in '{}' contains more than one certificate! Only the first certificate will be used.",
|
|
||||||
hkd_path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic: len is == 1 -> unwrap will succeed/not panic
|
|
||||||
let cert = certs.first().unwrap();
|
|
||||||
if hdr.contains(cert.public_key()?)? {
|
|
||||||
result = true;
|
result = true;
|
||||||
log_println!(" ✓ Host key document '{}' is included", hkd_path.display());
|
log_println!(
|
||||||
|
" ✓ Host key document '{}' is included",
|
||||||
|
path.as_ref().display()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log_println!(
|
log_println!(
|
||||||
" ✘ Host key document '{}' is not included",
|
" ✘ Host key document '{}' is not included",
|
||||||
hkd_path.display()
|
path.as_ref().display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,12 @@ pub mod misc {
|
|||||||
|
|
||||||
pub mod uvdata {
|
pub mod uvdata {
|
||||||
pub use crate::pv_utils::{
|
pub use crate::pv_utils::{
|
||||||
AeadPlainDataTrait, BuilderTrait, ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait,
|
AeadPlainDataTrait, BuilderTrait, ComponentMetadataV1, ControlFlagTrait,
|
||||||
EnvelopeSeHdrV1, FlagData, KeyExchangeTrait, PcfV1, PlaintextControlFlagsV1, ScfV1, SeH,
|
EffectiveControlFlags, EnvelopeSeHdrV1, FlagData, FlagState, FlagsOverride,
|
||||||
SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, SeHdrData, SeHdrDataV1, SeHdrPlain,
|
IntoEnumIterator, KeyExchangeTrait, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder,
|
||||||
SeHdrVersion, SeHdrVersioned, SecretControlFlagsV1, UvDataPlainTrait, UvDataTrait,
|
SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrFlag,
|
||||||
UvKeyHashesV1,
|
SeHdrPlain, SeHdrVersion, SeHdrVersioned, SeTarget, UnknownFlags, UvDataPlainTrait,
|
||||||
|
UvDataTrait, UvKeyHashesV1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ pub use layout::{Interval, Layout};
|
|||||||
pub use misc::{round_up, try_copy_slice_to_array};
|
pub use misc::{round_up, try_copy_slice_to_array};
|
||||||
pub use psw::{ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
|
pub use psw::{ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
|
||||||
pub use se_hdr::{
|
pub use se_hdr::{
|
||||||
ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait, EnvelopeSeHdrV1, FlagData, PcfV1,
|
ComponentMetadataV1, ControlFlagTrait, EffectiveControlFlags, EnvelopeSeHdrV1, FlagData,
|
||||||
PlaintextControlFlagsV1, ScfV1, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, SeHdrData,
|
FlagState, FlagsOverride, IntoEnumIterator, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder,
|
||||||
SeHdrDataV1, SeHdrPlain, SeHdrVersion, SeHdrVersioned, SecretControlFlagsV1,
|
SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrFlag,
|
||||||
|
SeHdrPlain, SeHdrVersion, SeHdrVersioned, SeTarget, UnknownFlags,
|
||||||
};
|
};
|
||||||
pub use secured_comp::{ComponentTrait, SecuredComponent, SecuredComponentBuilder};
|
pub use secured_comp::{ComponentTrait, SecuredComponent, SecuredComponentBuilder};
|
||||||
pub use serializing::{bytesize, serialize_to_bytes};
|
pub use serializing::{bytesize, serialize_to_bytes};
|
||||||
|
|||||||
@@ -126,6 +126,12 @@ pub enum Error {
|
|||||||
max_output_size: usize,
|
max_output_size: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("Operation {operation} not supported")]
|
||||||
|
UnsupportedOperation { operation: String },
|
||||||
|
|
||||||
|
#[error("Unsupported SE header version: {0:?}")]
|
||||||
|
UnsupportedSeHdrVersion(pv::request::SeHdrVersion),
|
||||||
|
|
||||||
// Errors from other crates
|
// Errors from other crates
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Deku(#[from] deku::DekuError),
|
Deku(#[from] deku::DekuError),
|
||||||
|
|||||||
@@ -5,16 +5,19 @@
|
|||||||
mod brb;
|
mod brb;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod flags;
|
mod flags;
|
||||||
|
mod generic_flags;
|
||||||
mod hdr_v1;
|
mod hdr_v1;
|
||||||
|
mod hdr_v2;
|
||||||
mod keys;
|
mod keys;
|
||||||
|
|
||||||
pub use brb::{
|
pub use brb::{
|
||||||
ComponentMetadata, ComponentMetadataV1, EnvelopeSeHdrV1, SeH, SeHdr, SeHdrBinV1, SeHdrData,
|
ComponentMetadata, ComponentMetadataV1, EnvelopeSeHdrV1, SeH, SeHdr, SeHdrBinV1, SeHdrBinV2,
|
||||||
SeHdrDataV1, SeHdrPlain, SeHdrVersion, SeHdrVersioned,
|
SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrPlain, SeHdrVersion, SeHdrVersioned,
|
||||||
};
|
};
|
||||||
pub use builder::SeHdrBuilder;
|
pub use builder::SeHdrBuilder;
|
||||||
pub use flags::{
|
pub use flags::{
|
||||||
ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1,
|
ControlFlagTrait, EffectiveControlFlags, FlagData, FlagState, FlagsOverride, SeHdrControlFlags,
|
||||||
SecretControlFlagsV1,
|
SeHdrControlFlagsModel, SeHdrFlag, SeTarget,
|
||||||
};
|
};
|
||||||
|
pub use generic_flags::{IntoEnumIterator, UnknownFlags};
|
||||||
pub use hdr_v1::SeHdrAadV1;
|
pub use hdr_v1::SeHdrAadV1;
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ use std::mem::size_of;
|
|||||||
use deku::ctx::Endian;
|
use deku::ctx::Endian;
|
||||||
use deku::prelude::*;
|
use deku::prelude::*;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use pv::request::openssl::pkey::{PKey, PKeyRef, Private, Public};
|
use pv::request::openssl::pkey::{PKey, Private, Public};
|
||||||
use pv::request::{seek_se_hdr_start, Aes256XtsKey, Confidential, SymKey, SymKeyType};
|
use pv::request::{seek_se_hdr_start, Aes256XtsKey, Confidential, SymKey, SymKeyType};
|
||||||
use pv::static_assert;
|
use pv::static_assert;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use utils::S390ToolsMetaData;
|
use utils::{HkdVersion, S390ToolsMetaData};
|
||||||
|
|
||||||
pub use super::hdr_v1::{SeHdrBinV1, SeHdrDataV1};
|
pub use super::hdr_v1::{SeHdrBinV1, SeHdrDataV1};
|
||||||
use super::{PlaintextControlFlagsV1, SecretControlFlagsV1};
|
pub use super::hdr_v2::{SeHdrBinV2, SeHdrDataV2};
|
||||||
|
use super::{EffectiveControlFlags, SeHdrFlag};
|
||||||
use crate::misc::PAGESIZE;
|
use crate::misc::PAGESIZE;
|
||||||
use crate::pv_utils::error::{Error, Result};
|
use crate::pv_utils::error::{Error, Result};
|
||||||
use crate::pv_utils::misc::display_indented;
|
use crate::pv_utils::misc::display_indented;
|
||||||
@@ -60,6 +61,17 @@ impl EnvelopeSeHdrV1 {
|
|||||||
pub enum SeHdrVersion {
|
pub enum SeHdrVersion {
|
||||||
/// Secure Execution header v1
|
/// Secure Execution header v1
|
||||||
V1 = 0x100,
|
V1 = 0x100,
|
||||||
|
/// Secure Execution header v2
|
||||||
|
V2 = 0x200,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SeHdrVersion> for HkdVersion {
|
||||||
|
fn from(val: SeHdrVersion) -> Self {
|
||||||
|
match val {
|
||||||
|
SeHdrVersion::V1 => Self::Classical,
|
||||||
|
SeHdrVersion::V2 => Self::Hybrid,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SeHdrVersion {
|
impl Display for SeHdrVersion {
|
||||||
@@ -69,6 +81,7 @@ impl Display for SeHdrVersion {
|
|||||||
"{}",
|
"{}",
|
||||||
match self {
|
match self {
|
||||||
SeHdrVersion::V1 => "1",
|
SeHdrVersion::V1 => "1",
|
||||||
|
SeHdrVersion::V2 => "2",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -204,13 +217,53 @@ impl Display for SeHdrPlain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch(AeadCipherTrait, AeadDataTrait, KeyExchangeTrait)]
|
#[non_exhaustive]
|
||||||
|
#[enum_dispatch(AeadCipherTrait, AeadDataTrait)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")]
|
#[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")]
|
||||||
pub enum SeHdrVersioned {
|
pub enum SeHdrVersioned {
|
||||||
#[deku(id = "SeHdrVersion::V1")]
|
#[deku(id = "SeHdrVersion::V1")]
|
||||||
SeHdrBinV1(SeHdrBinV1),
|
SeHdrBinV1(SeHdrBinV1),
|
||||||
|
#[deku(id = "SeHdrVersion::V2")]
|
||||||
|
SeHdrBinV2(SeHdrBinV2),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyExchangeTrait for SeHdrVersioned {
|
||||||
|
type PrivateKeyType = PKey<Private>;
|
||||||
|
type TargetKeyType = pv::request::HostKey;
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
match (self, key.as_ref()) {
|
||||||
|
(SeHdrVersioned::SeHdrBinV1(data), pv::request::HostKey::V1(key)) => data.contains(key),
|
||||||
|
(SeHdrVersioned::SeHdrBinV2(data), pv::request::HostKey::V2(key)) => data.contains(key),
|
||||||
|
(_, _) => Err(Error::InvalidSeHdr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
|
match self {
|
||||||
|
SeHdrVersioned::SeHdrBinV1(data) => data.contains_hash(hash),
|
||||||
|
SeHdrVersioned::SeHdrBinV2(data) => data.contains_hash(hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
|
match self {
|
||||||
|
SeHdrVersioned::SeHdrBinV1(data) => data.cust_pub_key(),
|
||||||
|
SeHdrVersioned::SeHdrBinV2(data) => data.cust_pub_key(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_type(&self) -> SymKeyType {
|
||||||
|
match self {
|
||||||
|
SeHdrVersioned::SeHdrBinV1(data) => data.key_type(),
|
||||||
|
SeHdrVersioned::SeHdrBinV2(data) => data.key_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SeHdrVersioned {
|
impl Display for SeHdrVersioned {
|
||||||
@@ -223,10 +276,18 @@ impl Display for SeHdrVersioned {
|
|||||||
write!(f, "{se_hdr_bin_v1}")
|
write!(f, "{se_hdr_bin_v1}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SeHdrVersioned::SeHdrBinV2(se_hdr_bin_v2) => {
|
||||||
|
if f.alternate() {
|
||||||
|
write!(f, "{se_hdr_bin_v2:#}")
|
||||||
|
} else {
|
||||||
|
write!(f, "{se_hdr_bin_v2}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
#[enum_dispatch(
|
#[enum_dispatch(
|
||||||
AeadCipherTrait,
|
AeadCipherTrait,
|
||||||
AeadPlainDataTrait,
|
AeadPlainDataTrait,
|
||||||
@@ -239,6 +300,8 @@ impl Display for SeHdrVersioned {
|
|||||||
pub enum SeHdrData {
|
pub enum SeHdrData {
|
||||||
#[deku(id = "SeHdrVersion::V1")]
|
#[deku(id = "SeHdrVersion::V1")]
|
||||||
SeHdrDataV1(SeHdrDataV1),
|
SeHdrDataV1(SeHdrDataV1),
|
||||||
|
#[deku(id = "SeHdrVersion::V2")]
|
||||||
|
SeHdrDataV2(SeHdrDataV2),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SeHdrData {
|
impl Display for SeHdrData {
|
||||||
@@ -251,6 +314,13 @@ impl Display for SeHdrData {
|
|||||||
write!(f, "{data_v1}")
|
write!(f, "{data_v1}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SeHdrData::SeHdrDataV2(data_v2) => {
|
||||||
|
if f.alternate() {
|
||||||
|
write!(f, "{data_v2:#}")
|
||||||
|
} else {
|
||||||
|
write!(f, "{data_v2}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +329,7 @@ impl AeadCipherBuilderTrait for SeHdrData {
|
|||||||
fn set_iv(&mut self, iv: &[u8]) -> Result<()> {
|
fn set_iv(&mut self, iv: &[u8]) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::SeHdrDataV1(data) => data.set_iv(iv),
|
Self::SeHdrDataV1(data) => data.set_iv(iv),
|
||||||
|
Self::SeHdrDataV2(data) => data.set_iv(iv),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +338,7 @@ impl AeadCipherBuilderTrait for SeHdrData {
|
|||||||
pub trait SeHdrPubBuilderTrait {
|
pub trait SeHdrPubBuilderTrait {
|
||||||
// Payload related methods
|
// Payload related methods
|
||||||
fn set_components(&mut self, meta: ComponentMetadata) -> Result<()>;
|
fn set_components(&mut self, meta: ComponentMetadata) -> Result<()>;
|
||||||
fn set_pcf(&mut self, pcf: &PlaintextControlFlagsV1) -> Result<()>;
|
fn set_pcf(&mut self, pcf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -276,7 +347,7 @@ pub trait SeHdrConfBuilderTrait {
|
|||||||
fn generate_cck(&self) -> Result<SymKey>;
|
fn generate_cck(&self) -> Result<SymKey>;
|
||||||
fn set_cck(&mut self, cck: Confidential<Vec<u8>>) -> Result<()>;
|
fn set_cck(&mut self, cck: Confidential<Vec<u8>>) -> Result<()>;
|
||||||
fn set_psw(&mut self, psw: &PSW);
|
fn set_psw(&mut self, psw: &PSW);
|
||||||
fn set_scf(&mut self, scf: &SecretControlFlagsV1) -> Result<()>;
|
fn set_scf(&mut self, scf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch(SeHdr)]
|
#[enum_dispatch(SeHdr)]
|
||||||
@@ -316,14 +387,13 @@ impl AeadDataTrait for SeHdr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeyExchangeTrait for SeHdr {
|
impl KeyExchangeTrait for SeHdr {
|
||||||
|
type PrivateKeyType = PKey<Private>;
|
||||||
|
type TargetKeyType = pv::request::HostKey;
|
||||||
|
|
||||||
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
self.data.contains_hash(hash)
|
self.data.contains_hash(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
|
||||||
self.data.contains(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
self.data.cust_pub_key()
|
self.data.cust_pub_key()
|
||||||
}
|
}
|
||||||
@@ -331,6 +401,13 @@ impl KeyExchangeTrait for SeHdr {
|
|||||||
fn key_type(&self) -> SymKeyType {
|
fn key_type(&self) -> SymKeyType {
|
||||||
self.aead_key_type()
|
self.aead_key_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
self.data.contains(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UvDataTrait for SeHdr {
|
impl UvDataTrait for SeHdr {
|
||||||
@@ -407,25 +484,46 @@ impl AeadCipherBuilderTrait for SeHdrPlain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeyExchangeBuilderTrait for SeHdrPlain {
|
impl KeyExchangeBuilderTrait for SeHdrPlain {
|
||||||
|
type AeadKeyType = SymKey;
|
||||||
|
type PrivateKeyType = PKey<Private>;
|
||||||
|
type TargetKeyType = pv::request::HostKey;
|
||||||
|
|
||||||
fn add_keyslot(
|
fn add_keyslot(
|
||||||
&mut self,
|
&mut self,
|
||||||
hostkey: &PKeyRef<Public>,
|
hostkey: &Self::TargetKeyType,
|
||||||
aead_key: &SymKey,
|
aead_key: &Self::AeadKeyType,
|
||||||
priv_key: &PKeyRef<Private>,
|
priv_key: &Self::PrivateKeyType,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.data.add_keyslot(hostkey, aead_key, priv_key)
|
match (&mut self.data, hostkey) {
|
||||||
|
(SeHdrData::SeHdrDataV1(data), pv::request::HostKey::V1(key)) => {
|
||||||
|
data.add_keyslot(key, aead_key, priv_key)
|
||||||
|
}
|
||||||
|
(SeHdrData::SeHdrDataV2(data), pv::request::HostKey::V2(key)) => {
|
||||||
|
data.add_keyslot(key, aead_key, priv_key)
|
||||||
|
}
|
||||||
|
(_, _) => Err(Error::InvalidSeHdr),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_keyslots(&mut self) -> Result<()> {
|
fn clear_keyslots(&mut self) -> Result<()> {
|
||||||
self.data.clear_keyslots()
|
match &mut self.data {
|
||||||
|
SeHdrData::SeHdrDataV1(data) => data.clear_keyslots(),
|
||||||
|
SeHdrData::SeHdrDataV2(data) => data.clear_keyslots(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_private_key(&self) -> Result<PKey<Private>> {
|
fn generate_private_key(&self) -> Result<PKey<Private>> {
|
||||||
self.data.generate_private_key()
|
match &self.data {
|
||||||
|
SeHdrData::SeHdrDataV1(data) => data.generate_private_key(),
|
||||||
|
SeHdrData::SeHdrDataV2(data) => data.generate_private_key(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cust_public_key(&mut self, key: &PKeyRef<Private>) -> Result<()> {
|
fn set_cust_public_key(&mut self, key: &Self::PrivateKeyType) -> Result<()> {
|
||||||
self.data.set_cust_public_key(key)
|
match &mut self.data {
|
||||||
|
SeHdrData::SeHdrDataV1(data) => data.set_cust_public_key(key),
|
||||||
|
SeHdrData::SeHdrDataV2(data) => data.set_cust_public_key(key),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,20 +549,39 @@ pub enum ComponentMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeyExchangeTrait for SeHdrPlain {
|
impl KeyExchangeTrait for SeHdrPlain {
|
||||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
type PrivateKeyType = PKey<Private>;
|
||||||
self.data.contains(key)
|
type TargetKeyType = pv::request::HostKey;
|
||||||
}
|
|
||||||
|
|
||||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
self.data.cust_pub_key()
|
match &mut self.data {
|
||||||
|
SeHdrData::SeHdrDataV1(data) => data.cust_pub_key(),
|
||||||
|
SeHdrData::SeHdrDataV2(data) => data.cust_pub_key(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_type(&self) -> SymKeyType {
|
fn key_type(&self) -> SymKeyType {
|
||||||
self.data.key_type()
|
match &self.data {
|
||||||
|
SeHdrData::SeHdrDataV1(data) => data.key_type(),
|
||||||
|
SeHdrData::SeHdrDataV2(data) => data.key_type(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
self.data.contains_hash(hash)
|
match &self.data {
|
||||||
|
SeHdrData::SeHdrDataV1(data) => data.contains_hash(hash),
|
||||||
|
SeHdrData::SeHdrDataV2(data) => data.contains_hash(hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
match (&self.data, key.as_ref()) {
|
||||||
|
(SeHdrData::SeHdrDataV1(data), pv::request::HostKey::V1(key)) => data.aad.contains(key),
|
||||||
|
(SeHdrData::SeHdrDataV2(data), pv::request::HostKey::V2(key)) => data.aad.contains(key),
|
||||||
|
(_, _) => Err(Error::InvalidSeHdr),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
use pv::request::Confidential;
|
use pv::request::Confidential;
|
||||||
|
|
||||||
use super::hdr_v1::SeHdrDataV1;
|
use super::hdr_v1::SeHdrDataV1;
|
||||||
use super::SeHdr;
|
use super::hdr_v2::SeHdrDataV2;
|
||||||
|
use super::{EffectiveControlFlags, SeHdr, SeHdrFlag};
|
||||||
use crate::pv_utils::error::{Error, Result};
|
use crate::pv_utils::error::{Error, Result};
|
||||||
use crate::pv_utils::se_hdr::brb::{
|
use crate::pv_utils::se_hdr::brb::{
|
||||||
SeHdrCommon, SeHdrConfBuilderTrait, SeHdrData, SeHdrPubBuilderTrait,
|
SeHdrCommon, SeHdrConfBuilderTrait, SeHdrData, SeHdrPubBuilderTrait,
|
||||||
@@ -15,10 +16,10 @@ use crate::pv_utils::uvdata::UvDataPlainTrait;
|
|||||||
use crate::pv_utils::uvdata_builder::{
|
use crate::pv_utils::uvdata_builder::{
|
||||||
AeadCipherBuilderTrait, BuilderTrait, KeyExchangeBuilderTrait, UvDataBuilder,
|
AeadCipherBuilderTrait, BuilderTrait, KeyExchangeBuilderTrait, UvDataBuilder,
|
||||||
};
|
};
|
||||||
use crate::pv_utils::{PlaintextControlFlagsV1, SecretControlFlagsV1, PSW};
|
use crate::pv_utils::PSW;
|
||||||
|
|
||||||
/// `SeHdrBuilder`
|
/// `SeHdrBuilder`
|
||||||
pub type SeHdrBuilder<'a> = UvDataBuilder<'a, SeHdrPlain>;
|
pub type SeHdrBuilder<'a> = UvDataBuilder<SeHdrPlain>;
|
||||||
|
|
||||||
impl SeHdrBuilder<'_> {
|
impl SeHdrBuilder<'_> {
|
||||||
pub fn new<M: Into<ComponentMetadata>>(
|
pub fn new<M: Into<ComponentMetadata>>(
|
||||||
@@ -40,6 +41,19 @@ impl SeHdrBuilder<'_> {
|
|||||||
data.set_cust_public_key(&priv_key)?;
|
data.set_cust_public_key(&priv_key)?;
|
||||||
(SeHdrData::SeHdrDataV1(data), aead_key, priv_key)
|
(SeHdrData::SeHdrDataV1(data), aead_key, priv_key)
|
||||||
}
|
}
|
||||||
|
SeHdrVersion::V2 => {
|
||||||
|
let mut data = SeHdrDataV2::new(
|
||||||
|
psw,
|
||||||
|
components_meta
|
||||||
|
.into()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::InvalidComponentMetadata)?,
|
||||||
|
)?;
|
||||||
|
let aead_key = data.generate_aead_key()?;
|
||||||
|
let priv_key = data.generate_private_key()?;
|
||||||
|
data.set_cust_public_key(&priv_key)?;
|
||||||
|
(SeHdrData::SeHdrDataV2(data), aead_key, priv_key)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let common = SeHdrCommon::new(version);
|
let common = SeHdrCommon::new(version);
|
||||||
let hdr = SeHdrPlain { common, data };
|
let hdr = SeHdrPlain { common, data };
|
||||||
@@ -67,12 +81,12 @@ impl SeHdrBuilder<'_> {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_pcf(&mut self, flags: &PlaintextControlFlagsV1) -> Result<&mut Self> {
|
pub fn with_pcf(&mut self, flags: &EffectiveControlFlags<SeHdrFlag>) -> Result<&mut Self> {
|
||||||
self.plain_data.data.set_pcf(flags)?;
|
self.plain_data.data.set_pcf(flags)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_scf(&mut self, flags: &SecretControlFlagsV1) -> Result<&mut Self> {
|
pub fn with_scf(&mut self, flags: &EffectiveControlFlags<SeHdrFlag>) -> Result<&mut Self> {
|
||||||
self.plain_data.data.set_scf(flags)?;
|
self.plain_data.data.set_scf(flags)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
@@ -95,8 +109,8 @@ impl BuilderTrait for SeHdrBuilder<'_> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use pv::request::{Confidential, SymKeyType, SHA_512_HASH_LEN};
|
use pv::request::{Confidential, HostKey, HybridPKey, SymKeyType, SHA_512_HASH_LEN};
|
||||||
use pv::test_utils::get_test_key_and_cert;
|
use pv::test_utils::{get_test_key_and_cert, get_test_key_and_cert_hybrid};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::pv_utils::se_hdr::ComponentMetadataV1;
|
use crate::pv_utils::se_hdr::ComponentMetadataV1;
|
||||||
@@ -108,7 +122,7 @@ mod tests {
|
|||||||
use pv::test_utils::get_test_key_and_cert;
|
use pv::test_utils::get_test_key_and_cert;
|
||||||
|
|
||||||
let (cust_key, host_key) = get_test_key_and_cert();
|
let (cust_key, host_key) = get_test_key_and_cert();
|
||||||
let host_keys = [host_key.public_key().unwrap()];
|
let host_keys = [HostKey::V1(host_key.public_key().unwrap())];
|
||||||
let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||||
let xts_key2 = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
let xts_key2 = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||||
let mut builder = SeHdrBuilder::new(
|
let mut builder = SeHdrBuilder::new(
|
||||||
@@ -236,10 +250,140 @@ mod tests {
|
|||||||
let _decrypted_hdrv1: SeHdrDataV1 = decrypted.data.try_into().expect("BUG");
|
let _decrypted_hdrv1: SeHdrDataV1 = decrypted.data.try_into().expect("BUG");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builder_test_v2() {
|
||||||
|
use pv::test_utils::get_test_key_and_cert_hybrid;
|
||||||
|
|
||||||
|
let (cust_key, host_key1, host_key2) = get_test_key_and_cert_hybrid();
|
||||||
|
let host_keys = [HostKey::V2(
|
||||||
|
HybridPKey::new(
|
||||||
|
host_key1.public_key().unwrap(),
|
||||||
|
host_key2.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)];
|
||||||
|
let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||||
|
let xts_key2 = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||||
|
let mut builder = SeHdrBuilder::new(
|
||||||
|
SeHdrVersion::V2,
|
||||||
|
PSW {
|
||||||
|
addr: 1234,
|
||||||
|
mask: 5678,
|
||||||
|
},
|
||||||
|
ComponentMetadata::ComponentMetadataV1(ComponentMetadataV1 {
|
||||||
|
ald: [0x1; SHA_512_HASH_LEN],
|
||||||
|
pld: [0x2; SHA_512_HASH_LEN],
|
||||||
|
tld: [0x3; SHA_512_HASH_LEN],
|
||||||
|
nep: 1,
|
||||||
|
key: xts_key,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("should not fail");
|
||||||
|
|
||||||
|
// builder.add_comp_data(addr, tweak, )?;
|
||||||
|
builder
|
||||||
|
.with_components(ComponentMetadataV1 {
|
||||||
|
ald: [0x1; SHA_512_HASH_LEN],
|
||||||
|
pld: [0x2; SHA_512_HASH_LEN],
|
||||||
|
tld: [0x3; SHA_512_HASH_LEN],
|
||||||
|
nep: 1,
|
||||||
|
key: xts_key2,
|
||||||
|
})
|
||||||
|
.expect("should not fail");
|
||||||
|
builder
|
||||||
|
.with_priv_key(&cust_key)
|
||||||
|
.expect_err("Error expected as expert mode is not enabled");
|
||||||
|
|
||||||
|
builder.expert_mode = true;
|
||||||
|
builder.with_priv_key(&cust_key).expect("should not fail");
|
||||||
|
|
||||||
|
// Set CCK
|
||||||
|
// Too large key
|
||||||
|
builder
|
||||||
|
.with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN - 1].to_vec().into())
|
||||||
|
.expect_err("should fail");
|
||||||
|
// Too small key
|
||||||
|
builder
|
||||||
|
.with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN + 1].to_vec().into())
|
||||||
|
.expect_err("should fail");
|
||||||
|
|
||||||
|
builder
|
||||||
|
.with_cck([49; SymKeyType::AES_256_GCM_KEY_LEN].to_vec().into())
|
||||||
|
.expect("should not fail");
|
||||||
|
|
||||||
|
// Set protection key
|
||||||
|
// Too large key
|
||||||
|
builder
|
||||||
|
.with_aead_key(Confidential::new([50; 33].into()))
|
||||||
|
.expect_err("should fail");
|
||||||
|
// Too small key
|
||||||
|
builder
|
||||||
|
.with_aead_key(Confidential::new([50; 31].into()))
|
||||||
|
.expect_err("should fail");
|
||||||
|
|
||||||
|
builder
|
||||||
|
.with_aead_key(Confidential::new([50; 32].into()))
|
||||||
|
.expect("should not fail");
|
||||||
|
|
||||||
|
// Set IV
|
||||||
|
// Too large IV
|
||||||
|
builder.with_iv(&[51; 13]).expect_err("should fail");
|
||||||
|
// Too small IV
|
||||||
|
builder.with_iv(&[51; 11]).expect_err("should fail");
|
||||||
|
|
||||||
|
builder.with_iv(&[51; 12]).expect("should not fail");
|
||||||
|
|
||||||
|
builder.add_hostkeys(&host_keys).expect("should not fail");
|
||||||
|
|
||||||
|
let prot_key = builder.prot_key().clone();
|
||||||
|
let bin = builder.build().expect("wuhu");
|
||||||
|
assert_eq!(bin.common.version, SeHdrVersion::V2);
|
||||||
|
assert_eq!(bin.as_bytes().expect("should not fail").len(), 2240);
|
||||||
|
assert_eq!(
|
||||||
|
bin.as_bytes().expect("should not fail")[..480],
|
||||||
|
[
|
||||||
|
73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 2, 0, 0, 0, 8, 192, 51, 51, 51, 51, 51, 51,
|
||||||
|
51, 51, 51, 51, 51, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
128, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 199, 93, 52, 249, 22, 82, 219, 69, 123, 11, 32, 156, 70, 164, 145,
|
||||||
|
164, 78, 226, 177, 110, 35, 194, 216, 218, 241, 22, 103, 138, 98, 242, 76, 227, 50,
|
||||||
|
197, 153, 95, 8, 69, 107, 102, 177, 109, 213, 90, 146, 197, 7, 241, 227, 26, 247,
|
||||||
|
140, 100, 168, 46, 122, 84, 27, 21, 19, 80, 21, 242, 2, 134, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 64, 128, 88, 167, 241, 165, 195, 80, 151, 83, 58, 2, 169,
|
||||||
|
56, 121, 231, 222, 103, 186, 40, 11, 206, 131, 101, 236, 148, 178, 185, 8, 245,
|
||||||
|
137, 195, 169, 152, 216, 190, 30, 99, 7, 215, 74, 224, 26, 220, 70, 130, 95, 246,
|
||||||
|
187, 111, 160, 92, 17, 71, 207, 226, 204, 244, 162, 79, 61, 131, 61, 218, 112, 2,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||||
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||||
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 255, 94, 191,
|
||||||
|
53, 220, 196, 47, 37, 93, 227, 234, 101, 1, 174, 171, 68, 42, 136, 92, 238, 72, 6,
|
||||||
|
17, 77, 231, 225, 174, 22, 222, 188, 212, 15, 248, 145, 72, 126, 139, 17, 233, 225,
|
||||||
|
156, 46, 233, 151, 54, 2, 175, 88, 215, 254, 243, 222, 37, 81, 50, 110, 18, 76,
|
||||||
|
252, 12, 210, 146, 66, 23
|
||||||
|
][..480]
|
||||||
|
);
|
||||||
|
|
||||||
|
let decrypted = bin.decrypt(&prot_key).expect("BUG");
|
||||||
|
assert_eq!(bin.common, decrypted.common);
|
||||||
|
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_hdrv2: SeHdrDataV2 = decrypted.data.try_into().expect("BUG");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chain_test() {
|
fn chain_test() {
|
||||||
let (_, host_key) = get_test_key_and_cert();
|
let (_, host_key) = get_test_key_and_cert();
|
||||||
let host_keys = [host_key.public_key().unwrap()];
|
let host_keys = [HostKey::V1(host_key.public_key().unwrap())];
|
||||||
let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||||
let meta = ComponentMetadataV1 {
|
let meta = ComponentMetadataV1 {
|
||||||
ald: [0x1; SHA_512_HASH_LEN],
|
ald: [0x1; SHA_512_HASH_LEN],
|
||||||
@@ -274,4 +418,49 @@ mod tests {
|
|||||||
assert_eq!(hdr_plain.common.version, hdr.common.version);
|
assert_eq!(hdr_plain.common.version, hdr.common.version);
|
||||||
let _hdr_data_v1: SeHdrDataV1 = hdr_plain.data.try_into().expect("should not fail");
|
let _hdr_data_v1: SeHdrDataV1 = hdr_plain.data.try_into().expect("should not fail");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chain_test_v2() {
|
||||||
|
let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid();
|
||||||
|
let host_keys = [HostKey::V2(
|
||||||
|
HybridPKey::new(
|
||||||
|
host_key1.public_key().unwrap(),
|
||||||
|
host_key2.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)];
|
||||||
|
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 cck = Confidential::new([0x42; 32].to_vec());
|
||||||
|
let mut builder = SeHdrBuilder::new(
|
||||||
|
SeHdrVersion::V2,
|
||||||
|
PSW {
|
||||||
|
addr: 1234,
|
||||||
|
mask: 5678,
|
||||||
|
},
|
||||||
|
meta,
|
||||||
|
)
|
||||||
|
.expect("should not fail");
|
||||||
|
|
||||||
|
let prot_key = builder.prot_key().to_owned();
|
||||||
|
builder
|
||||||
|
.add_hostkeys(&host_keys)
|
||||||
|
.expect("should not fail")
|
||||||
|
.with_cck(cck)
|
||||||
|
.expect("should not fail");
|
||||||
|
let bin = builder.build().expect("should not fail");
|
||||||
|
|
||||||
|
let reader = Cursor::new(bin.as_bytes().expect("should not fail"));
|
||||||
|
let hdr = SeHdr::try_from_io(reader).unwrap();
|
||||||
|
let hdr_plain = hdr.decrypt(&prot_key).unwrap();
|
||||||
|
assert_eq!(hdr_plain.common.version, SeHdrVersion::V2);
|
||||||
|
assert_eq!(hdr_plain.common.version, hdr.common.version);
|
||||||
|
let _hdr_data_v2: SeHdrDataV2 = hdr_plain.data.try_into().expect("should not fail");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,878 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp.
|
||||||
|
|
||||||
|
//! Generic control flags infrastructure for Secure Execution (SE) headers.
|
||||||
|
//!
|
||||||
|
//! This module provides the generic, reusable components for managing control flags
|
||||||
|
//! in IBM Secure Execution headers. It defines traits, generic types, and implementations
|
||||||
|
//! that can work with any flag enum type.
|
||||||
|
//!
|
||||||
|
//! # Core Components
|
||||||
|
//!
|
||||||
|
//! ## Traits
|
||||||
|
//!
|
||||||
|
//! - [`ControlFlagTrait`] - Core trait that flag enums must implement
|
||||||
|
//! - [`IntoEnumIterator`] - Enables iteration over flag enum variants
|
||||||
|
//! - [`IntoBitPosition`] - Converts types to bit positions
|
||||||
|
//!
|
||||||
|
//! ## Generic Types
|
||||||
|
//!
|
||||||
|
//! - [`ControlFlagsModel<T>`] - Version-specific configuration model
|
||||||
|
//! - [`EffectiveControlFlags<T>`] - Final flags after applying overrides
|
||||||
|
//! - [`FlagsOverride<T>`] - Container for user-specified overrides
|
||||||
|
//! - [`FlagData<T>`] - Pairs a flag with its state
|
||||||
|
//! - [`UnknownFlags`] - Tracks unknown/unsupported flag bits
|
||||||
|
//!
|
||||||
|
//! ## Supporting Types
|
||||||
|
//!
|
||||||
|
//! - [`FlagState`] - Enabled or Disabled state
|
||||||
|
//! - [`FlagValidationError<T>`] - Validation errors
|
||||||
|
//! - [`Msb0FlagsConversionError<T>`] - Conversion errors
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! This module is not typically used directly. Instead, use the concrete implementations
|
||||||
|
//! in the [`flags`](super::flags) module which provide `SeHdrFlag`, `SeHdrControlFlagsModel`,
|
||||||
|
//! and related types built on top of this generic infrastructure.
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use pv::misc::{Flags, Msb0Flags64};
|
||||||
|
|
||||||
|
use super::brb::SeHdrVersion;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FlagValidationError<T: ControlFlagTrait> {
|
||||||
|
UnknownFlag(),
|
||||||
|
NotSupported { model: &'static str, flag: T },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> Display for FlagValidationError<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
FlagValidationError::UnknownFlag() => write!(f, "Unknown flag"),
|
||||||
|
FlagValidationError::NotSupported { model, flag } => {
|
||||||
|
write!(f, "Flag '{}' is not supported in {}", flag, model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> std::error::Error for FlagValidationError<T> {}
|
||||||
|
|
||||||
|
/// Secure Execution target configuration.
|
||||||
|
///
|
||||||
|
/// Specifies the target environment for which a Secure Execution boot image is being created.
|
||||||
|
/// This enum allows selecting the appropriate control flags and configuration based on the
|
||||||
|
/// target machine generation or SE header version.
|
||||||
|
///
|
||||||
|
/// # Current Usage
|
||||||
|
///
|
||||||
|
/// Currently, this enum is primarily used for selecting control flags configurations.
|
||||||
|
/// The target determines which control flags are available and their default values.
|
||||||
|
///
|
||||||
|
/// # Purpose
|
||||||
|
///
|
||||||
|
/// The target determines:
|
||||||
|
/// - Which control flags are available and their default values
|
||||||
|
/// - The SE header format version to use
|
||||||
|
/// - Compatibility with specific machine generations (future use)
|
||||||
|
///
|
||||||
|
/// # Future Extensibility
|
||||||
|
///
|
||||||
|
/// This design allows for future expansion when multiple variants of V1 or V2 configurations
|
||||||
|
/// may exist.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use pvimg::uvdata::{SeHdrControlFlagsModel, SeTarget};
|
||||||
|
///
|
||||||
|
/// // Select latest V1 configuration for newest features
|
||||||
|
/// let target = SeTarget::V1Max;
|
||||||
|
///
|
||||||
|
/// // Use target to get appropriate control flags
|
||||||
|
/// let pcf = SeHdrControlFlagsModel::pcf_for_target(target);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub enum SeTarget {
|
||||||
|
/// Latest V1 configuration.
|
||||||
|
///
|
||||||
|
/// Targets the most recent V1 SE header format with all available V1 control flags.
|
||||||
|
/// Use this for maximum compatibility with older machine generations that support V1.
|
||||||
|
V1Max,
|
||||||
|
|
||||||
|
/// Latest V2 configuration.
|
||||||
|
///
|
||||||
|
/// Targets the most recent V2 SE header format with all available V2 control flags.
|
||||||
|
/// Use this for newest features and machine generations that support V2.
|
||||||
|
V2Max,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeTarget {
|
||||||
|
/// Converts the target to the corresponding SeHdrVersion.
|
||||||
|
pub fn to_se_hdr_version(self) -> SeHdrVersion {
|
||||||
|
match self {
|
||||||
|
SeTarget::V1Max => SeHdrVersion::V1,
|
||||||
|
SeTarget::V2Max => SeHdrVersion::V2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a target from a SeHdrVersion.
|
||||||
|
pub fn from_se_hdr_version(version: SeHdrVersion) -> Self {
|
||||||
|
match version {
|
||||||
|
SeHdrVersion::V1 => SeTarget::V1Max,
|
||||||
|
SeHdrVersion::V2 => SeTarget::V2Max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SeTarget {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SeTarget::V1Max => write!(f, "V1-max"),
|
||||||
|
SeTarget::V2Max => write!(f, "V2-max"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoEnumIterator {
|
||||||
|
/// Returns an iterator over all variants of this enum.
|
||||||
|
fn iter() -> impl Iterator<Item = Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for individual control flag types.
|
||||||
|
///
|
||||||
|
/// This trait defines the interface for control flag enums, providing methods
|
||||||
|
/// to get the flag's bit position and create enabled/disabled flag data.
|
||||||
|
pub trait ControlFlagTrait:
|
||||||
|
Debug + Hash + Copy + Eq + Ord + Display + IntoEnumIterator + AsRef<Self>
|
||||||
|
{
|
||||||
|
/// Returns the bit position of this flag.
|
||||||
|
///
|
||||||
|
/// The bit position determines where this flag is set in the control flags bitfield.
|
||||||
|
fn bit_position(self) -> u8;
|
||||||
|
|
||||||
|
/// Creates flag data with this flag in the enabled state.
|
||||||
|
fn enabled(self) -> FlagData<Self> {
|
||||||
|
FlagData::new(self, FlagState::Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates flag data with this flag in the disabled state.
|
||||||
|
fn disabled(self) -> FlagData<Self> {
|
||||||
|
FlagData::new(self, FlagState::Disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal state of a control flag (enabled or disabled).
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||||
|
pub enum FlagState {
|
||||||
|
/// Flag is enabled (bit set to 1)
|
||||||
|
Enabled,
|
||||||
|
/// Flag is disabled (bit set to 0)
|
||||||
|
Disabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a control flag with its associated state.
|
||||||
|
///
|
||||||
|
/// This structure pairs a flag with its enabled/disabled state, used when
|
||||||
|
/// constructing or modifying `ControlFlags` instances.
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||||
|
pub struct FlagData<T: ControlFlagTrait> {
|
||||||
|
value: T,
|
||||||
|
state: FlagState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> FlagData<T> {
|
||||||
|
const fn new(value: T, state: FlagState) -> Self {
|
||||||
|
Self { value, state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic flags configuration for a specific SE header flags target.
|
||||||
|
///
|
||||||
|
/// Contains the default and supported flags for a given target version.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ControlFlagsModel<T: ControlFlagTrait> {
|
||||||
|
target: SeTarget,
|
||||||
|
default: HashSet<T>,
|
||||||
|
supported: HashSet<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> ControlFlagsModel<T> {
|
||||||
|
/// Creates a new Flags configuration.
|
||||||
|
pub(super) fn new(target: SeTarget, default: HashSet<T>, supported: HashSet<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
target,
|
||||||
|
default,
|
||||||
|
supported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the SE header flags target.
|
||||||
|
pub fn target(&self) -> SeTarget {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the SE header version corresponding to this target.
|
||||||
|
pub fn version(&self) -> SeHdrVersion {
|
||||||
|
self.target.to_se_hdr_version()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the set of default flags for this version.
|
||||||
|
pub fn default_flags(&self) -> &HashSet<T> {
|
||||||
|
&self.default
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the set of supported flags for this version.
|
||||||
|
pub fn supported_flags(&self) -> &HashSet<T> {
|
||||||
|
&self.supported
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a flag is supported in this version.
|
||||||
|
pub fn supports<F: AsRef<T>>(&self, flag: F) -> bool {
|
||||||
|
self.supported.contains(flag.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a flag is a default flag in this version.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_default<F: AsRef<T>>(&self, flag: F) -> bool {
|
||||||
|
self.default.contains(flag.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that all flags in the overrides are supported by this model.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `overrides` - The overrides to validate
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `FlagValidationError::NotSupported` if any override flag is not supported by this
|
||||||
|
/// model
|
||||||
|
pub fn validate_overrides(
|
||||||
|
&self,
|
||||||
|
overrides: &FlagsOverride<T>,
|
||||||
|
) -> Result<(), FlagValidationError<T>> {
|
||||||
|
for (flag, _state) in overrides.iter() {
|
||||||
|
if !self.supports(flag) {
|
||||||
|
return Err(FlagValidationError::NotSupported {
|
||||||
|
model: std::any::type_name::<T>(),
|
||||||
|
flag: *flag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies overrides to the default flags and returns the effective control flags.
|
||||||
|
///
|
||||||
|
/// This method validates the overrides, applies them to the default flags, and returns
|
||||||
|
/// an `EffectiveControlFlags` instance containing the resulting configuration.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `overrides` - Overrides to apply to the default flags
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// An `EffectiveControlFlags<T>` instance with the effective flags after applying overrides
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `FlagValidationError::NotSupported` if an override flag is not supported by this
|
||||||
|
/// model
|
||||||
|
pub fn with_overrides(
|
||||||
|
&self,
|
||||||
|
overrides: &FlagsOverride<T>,
|
||||||
|
) -> Result<EffectiveControlFlags<T>, FlagValidationError<T>> {
|
||||||
|
// Validate overrides first
|
||||||
|
self.validate_overrides(overrides)?;
|
||||||
|
let mut effective_flags = self.default.clone();
|
||||||
|
|
||||||
|
// Apply overrides
|
||||||
|
for (flag, state) in overrides.iter() {
|
||||||
|
match state {
|
||||||
|
FlagState::Enabled => {
|
||||||
|
effective_flags.insert(*flag);
|
||||||
|
}
|
||||||
|
FlagState::Disabled => {
|
||||||
|
effective_flags.remove(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EffectiveControlFlags {
|
||||||
|
version: self.version(),
|
||||||
|
known_flags: effective_flags,
|
||||||
|
unknown_flags: UnknownFlags::empty(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct UnknownFlags(Msb0Flags64);
|
||||||
|
|
||||||
|
/// Trait for types that can be converted to a bit position.
|
||||||
|
pub trait IntoBitPosition {
|
||||||
|
fn into_bit_position(self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement IntoBitPosition for u8 directly
|
||||||
|
impl IntoBitPosition for u8 {
|
||||||
|
fn into_bit_position(self) -> u8 {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement IntoBitPosition for types that implement ControlFlagTrait
|
||||||
|
impl<T: ControlFlagTrait> IntoBitPosition for T {
|
||||||
|
fn into_bit_position(self) -> u8 {
|
||||||
|
self.bit_position()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnknownFlags {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self(Msb0Flags64::from(0u64))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bits(bits: Msb0Flags64) -> Self {
|
||||||
|
Self(bits)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bits(self) -> u64 {
|
||||||
|
self.0.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_empty(self) -> bool {
|
||||||
|
self.bits() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a specific flag or bit position is set in the unknown flags.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flag` - Either a flag that implements `ControlFlagTrait`, a reference to such a flag, or
|
||||||
|
/// a raw bit position (u8)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `true` if the bit position is set in the unknown flags, `false` otherwise
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use pv::misc::Msb0Flags64;
|
||||||
|
/// use pvimg::uvdata::UnknownFlags;
|
||||||
|
///
|
||||||
|
/// // Create UnknownFlags with bit 5 set
|
||||||
|
/// let flags = Msb0Flags64::from(1u64 << (63 - 5));
|
||||||
|
/// let unknown_flags = UnknownFlags::from_bits(flags);
|
||||||
|
///
|
||||||
|
/// // Using a raw bit position
|
||||||
|
/// assert!(unknown_flags.contains(5u8));
|
||||||
|
/// assert!(!unknown_flags.contains(10u8));
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains(&self, position: u8) -> bool {
|
||||||
|
self.0.is_set(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the effective control flags after applying overrides.
|
||||||
|
///
|
||||||
|
/// This structure contains the final set of flags that will be used, including both
|
||||||
|
/// known flags (from the model) and any unknown flags that were present in the input.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct EffectiveControlFlags<T: ControlFlagTrait> {
|
||||||
|
version: SeHdrVersion,
|
||||||
|
known_flags: HashSet<T>,
|
||||||
|
unknown_flags: UnknownFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> EffectiveControlFlags<T> {
|
||||||
|
/// Creates a new EffectiveControlFlags instance.
|
||||||
|
///
|
||||||
|
/// This is primarily used internally by the flags module.
|
||||||
|
pub(super) fn new(
|
||||||
|
version: SeHdrVersion,
|
||||||
|
known_flags: HashSet<T>,
|
||||||
|
unknown_flags: UnknownFlags,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
version,
|
||||||
|
known_flags,
|
||||||
|
unknown_flags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the SE header version.
|
||||||
|
pub fn version(&self) -> SeHdrVersion {
|
||||||
|
self.version
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the set of known flags.
|
||||||
|
pub fn known_flags(&self) -> &HashSet<T> {
|
||||||
|
&self.known_flags
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the unknown flags.
|
||||||
|
pub fn unknown_flags(&self) -> UnknownFlags {
|
||||||
|
self.unknown_flags
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a flag or bit position is enabled in the effective flags.
|
||||||
|
///
|
||||||
|
/// This method accepts either a flag type or a raw bit position (u8).
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `position` - Either a flag that implements `ControlFlagTrait`, a reference to such a flag,
|
||||||
|
/// or a raw bit position (u8)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `true` if the flag/bit is enabled, `false` otherwise
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use pvimg::uvdata::{SeHdrControlFlagsModel, SeHdrFlag, SeTarget};
|
||||||
|
///
|
||||||
|
/// // Get default flags for V1
|
||||||
|
/// let pcf_v1 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max);
|
||||||
|
/// let flags = pcf_v1.with_overrides(&Default::default()).unwrap();
|
||||||
|
///
|
||||||
|
/// // Using a flag
|
||||||
|
/// assert!(flags.has(SeHdrFlag::PckmoAes));
|
||||||
|
///
|
||||||
|
/// // Using a raw bit position (PckmoAes is at bit 57)
|
||||||
|
/// assert!(flags.has(57u8));
|
||||||
|
/// ```
|
||||||
|
pub fn has<P: IntoBitPosition>(&self, position: P) -> bool {
|
||||||
|
let bit_pos = position.into_bit_position();
|
||||||
|
|
||||||
|
// Check if any known flag has this bit position
|
||||||
|
for flag in &self.known_flags {
|
||||||
|
if flag.bit_position() == bit_pos {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check unknown flags
|
||||||
|
self.unknown_flags.contains(bit_pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the effective flags to a u64 value.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A u64 representation combining both known and unknown flags
|
||||||
|
pub fn to_u64(&self) -> u64 {
|
||||||
|
let flags: Msb0Flags64 = self.into();
|
||||||
|
flags.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts `EffectiveControlFlags` to `Msb0Flags64`.
|
||||||
|
///
|
||||||
|
/// This combines both known and unknown flags into a single bitfield.
|
||||||
|
impl<T: ControlFlagTrait> From<EffectiveControlFlags<T>> for Msb0Flags64 {
|
||||||
|
fn from(flags: EffectiveControlFlags<T>) -> Self {
|
||||||
|
let mut value = Msb0Flags64::from(flags.unknown_flags.bits());
|
||||||
|
for flag in &flags.known_flags {
|
||||||
|
value.set_bit(flag.bit_position());
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a reference to `EffectiveControlFlags` to `Msb0Flags64`.
|
||||||
|
impl<T: ControlFlagTrait> From<&EffectiveControlFlags<T>> for Msb0Flags64 {
|
||||||
|
fn from(flags: &EffectiveControlFlags<T>) -> Self {
|
||||||
|
let mut value = Msb0Flags64::from(flags.unknown_flags.bits());
|
||||||
|
for flag in &flags.known_flags {
|
||||||
|
value.set_bit(flag.bit_position());
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display implementation for `EffectiveControlFlags<T>`.
|
||||||
|
///
|
||||||
|
/// Provides two display formats:
|
||||||
|
/// - Normal format: Lists all enabled flags with " - " prefix, one per line
|
||||||
|
/// - Alternate format (`{:#}`): Shows the raw bitfield as 66-character binary string
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{FlagsOverride, SeHdrControlFlagsModel, SeHdrFlag, SeTarget};
|
||||||
|
///
|
||||||
|
/// let pcf_v1 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max);
|
||||||
|
/// let mut overrides = FlagsOverride::new();
|
||||||
|
/// overrides.enable(SeHdrFlag::ConfidentialDump);
|
||||||
|
/// let effective = pcf_v1.with_overrides(&overrides).unwrap();
|
||||||
|
///
|
||||||
|
/// // Normal format - lists enabled flags
|
||||||
|
/// println!("{}", effective);
|
||||||
|
///
|
||||||
|
/// // Alternate format - shows binary representation
|
||||||
|
/// println!("{:#}", effective);
|
||||||
|
/// ```
|
||||||
|
impl<T: ControlFlagTrait> Display for EffectiveControlFlags<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if f.alternate() {
|
||||||
|
// Alternate format: show as binary
|
||||||
|
let flags: Msb0Flags64 = self.into();
|
||||||
|
write!(f, "{:#066b}", <u64>::from(flags))
|
||||||
|
} else {
|
||||||
|
// Normal format: list enabled flags
|
||||||
|
let flags_s: Vec<String> = self
|
||||||
|
.known_flags
|
||||||
|
.iter()
|
||||||
|
.map(|flag| format!(" - {flag}"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if flags_s.is_empty() && self.unknown_flags.is_empty() {
|
||||||
|
write!(f, "(no flags enabled)")
|
||||||
|
} else {
|
||||||
|
let mut output = flags_s.join("\n");
|
||||||
|
if !self.unknown_flags.is_empty() {
|
||||||
|
if !output.is_empty() {
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
output.push_str(&format!(
|
||||||
|
" - unknown flags: {:#018x}",
|
||||||
|
self.unknown_flags.bits()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
write!(f, "{}", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a reference to `ControlFlagsModel` to `EffectiveControlFlags` using default flags.
|
||||||
|
///
|
||||||
|
/// This implementation allows conversion without consuming the model.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{EffectiveControlFlags, SeHdrControlFlagsModel, SeTarget};
|
||||||
|
///
|
||||||
|
/// let pcf_v1 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max);
|
||||||
|
/// let flags: EffectiveControlFlags<_> = (&pcf_v1).into();
|
||||||
|
/// // pcf_v1 is still usable here
|
||||||
|
/// ```
|
||||||
|
impl<T: ControlFlagTrait> From<&ControlFlagsModel<T>> for EffectiveControlFlags<T> {
|
||||||
|
fn from(model: &ControlFlagsModel<T>) -> Self {
|
||||||
|
EffectiveControlFlags {
|
||||||
|
version: model.version(),
|
||||||
|
known_flags: model.default.clone(),
|
||||||
|
unknown_flags: UnknownFlags::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts `ControlFlagsModel` to `Msb0Flags64` using default flags.
|
||||||
|
///
|
||||||
|
/// This consumes the model and converts it to a bitfield representation.
|
||||||
|
impl<T: ControlFlagTrait> From<ControlFlagsModel<T>> for Msb0Flags64 {
|
||||||
|
fn from(model: ControlFlagsModel<T>) -> Self {
|
||||||
|
let mut value = Msb0Flags64::default();
|
||||||
|
for flag in &model.default {
|
||||||
|
value.set_bit(flag.bit_position());
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a reference to `ControlFlagsModel` to `Msb0Flags64` using default flags.
|
||||||
|
///
|
||||||
|
/// This implementation allows conversion without consuming the model.
|
||||||
|
impl<T: ControlFlagTrait> From<&ControlFlagsModel<T>> for Msb0Flags64 {
|
||||||
|
fn from(model: &ControlFlagsModel<T>) -> Self {
|
||||||
|
let mut value = Msb0Flags64::default();
|
||||||
|
for flag in &model.default {
|
||||||
|
value.set_bit(flag.bit_position());
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Display implementation for `ControlFlagsModel<T>`.
|
||||||
|
///
|
||||||
|
/// Provides two display formats:
|
||||||
|
/// - Normal format: Lists all default (enabled) flags with " - " prefix, one per line
|
||||||
|
/// - Alternate format (`{:#}`): Shows the raw bitfield as 66-character binary string
|
||||||
|
///
|
||||||
|
/// Note: This displays only the flags in the model's `default` set. Unknown flags
|
||||||
|
/// are not tracked by `ControlFlagsModel` - they are returned separately by
|
||||||
|
/// `from_msb0_flags()` when parsing raw bitfields.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{SeHdrControlFlagsModel, SeTarget};
|
||||||
|
///
|
||||||
|
/// let pcf_v1 = SeHdrControlFlagsModel::pcf_for_target(SeTarget::V1Max);
|
||||||
|
///
|
||||||
|
/// // Normal format - lists default flags
|
||||||
|
/// println!("{}", pcf_v1);
|
||||||
|
///
|
||||||
|
/// // Alternate format - shows binary representation
|
||||||
|
/// println!("{:#}", pcf_v1);
|
||||||
|
/// ```
|
||||||
|
impl<T: ControlFlagTrait> Display for ControlFlagsModel<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if f.alternate() {
|
||||||
|
// Alternate format: show as binary
|
||||||
|
let flags: Msb0Flags64 = self.into();
|
||||||
|
write!(f, "{:#066b}", <u64>::from(flags))
|
||||||
|
} else {
|
||||||
|
// Normal format: list default (enabled) flags
|
||||||
|
let flags_s: Vec<String> = self
|
||||||
|
.default
|
||||||
|
.iter()
|
||||||
|
.map(|flag| format!(" - {flag}"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if flags_s.is_empty() {
|
||||||
|
write!(f, "(no flags enabled)")
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", flags_s.join("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error type for `TryFrom<Msb0Flags64>` conversion.
|
||||||
|
#[derive(Debug)]
|
||||||
|
// Will be used in an upcoming commit
|
||||||
|
#[expect(dead_code)]
|
||||||
|
pub enum Msb0FlagsConversionError<T: ControlFlagTrait> {
|
||||||
|
/// A flag bit is set that is not supported in any version
|
||||||
|
UnsupportedFlag { bit_position: u8 },
|
||||||
|
/// A flag bit is set that is not supported in the specified version
|
||||||
|
NotSupportedInVersion { flag: T, version: SeHdrVersion },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> Display for Msb0FlagsConversionError<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::UnsupportedFlag { bit_position } => {
|
||||||
|
write!(f, "Unsupported flag at bit position {}", bit_position)
|
||||||
|
}
|
||||||
|
Self::NotSupportedInVersion { flag, version } => {
|
||||||
|
write!(f, "Flag {} not supported in version {:?}", flag, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> std::error::Error for Msb0FlagsConversionError<T> {}
|
||||||
|
|
||||||
|
/// Override configuration for control flags.
|
||||||
|
///
|
||||||
|
/// Allows specifying individual flag states that override the default configuration.
|
||||||
|
/// This is useful for customizing flag settings on a per-flag basis.
|
||||||
|
///
|
||||||
|
/// # Type Parameters
|
||||||
|
///
|
||||||
|
/// * `T` - The control flag enum type (e.g., [`Flag`])
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{FlagsOverride, SeHdrFlag};
|
||||||
|
///
|
||||||
|
/// let mut overrides = FlagsOverride::new();
|
||||||
|
/// overrides.enable(SeHdrFlag::ConfidentialDump);
|
||||||
|
/// overrides.disable(SeHdrFlag::PckmoAes);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FlagsOverride<T: ControlFlagTrait> {
|
||||||
|
overrides: HashMap<T, FlagState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> FlagsOverride<T> {
|
||||||
|
/// Creates a new empty FlagsOverride.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
overrides: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets an override for a specific flag.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flag` - The flag to override
|
||||||
|
/// * `state` - The desired state (Enabled or Disabled)
|
||||||
|
pub(super) fn set(&mut self, flag: T, state: FlagState) {
|
||||||
|
self.overrides.insert(flag, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables a specific flag by setting its override state to Enabled.
|
||||||
|
///
|
||||||
|
/// This is a convenience method equivalent to `set(flag, FlagState::Enabled)`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flag` - The flag to enable
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{FlagsOverride, SeHdrFlag};
|
||||||
|
///
|
||||||
|
/// let mut overrides = FlagsOverride::new();
|
||||||
|
/// overrides.enable(SeHdrFlag::ConfidentialDump);
|
||||||
|
/// ```
|
||||||
|
pub fn enable(&mut self, flag: T) {
|
||||||
|
self.set(flag, FlagState::Enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables a specific flag by setting its override state to Disabled.
|
||||||
|
///
|
||||||
|
/// This is a convenience method equivalent to `set(flag, FlagState::Disabled)`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flag` - The flag to disable
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{FlagsOverride, SeHdrFlag};
|
||||||
|
///
|
||||||
|
/// let mut overrides = FlagsOverride::new();
|
||||||
|
/// overrides.disable(SeHdrFlag::PckmoAes);
|
||||||
|
/// ```
|
||||||
|
pub fn disable(&mut self, flag: T) {
|
||||||
|
self.set(flag, FlagState::Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables multiple flags at once from an iterator.
|
||||||
|
///
|
||||||
|
/// This is a convenience method for enabling multiple flags in a single call.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flags` - An iterator over flags to enable
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{FlagsOverride, SeHdrFlag};
|
||||||
|
///
|
||||||
|
/// let mut overrides = FlagsOverride::new();
|
||||||
|
/// overrides.enable_all([SeHdrFlag::ConfidentialDump, SeHdrFlag::PckmoAes]);
|
||||||
|
/// ```
|
||||||
|
pub fn enable_all<I>(&mut self, flags: I)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
{
|
||||||
|
for flag in flags {
|
||||||
|
self.enable(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables multiple flags at once from an iterator.
|
||||||
|
///
|
||||||
|
/// This is a convenience method for disabling multiple flags in a single call.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flags` - An iterator over flags to disable
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pvimg::uvdata::{FlagsOverride, SeHdrFlag};
|
||||||
|
///
|
||||||
|
/// let mut overrides = FlagsOverride::new();
|
||||||
|
/// overrides.disable_all([SeHdrFlag::PckmoAes, SeHdrFlag::PckmoEcc]);
|
||||||
|
/// ```
|
||||||
|
pub fn disable_all<I>(&mut self, flags: I)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
{
|
||||||
|
for flag in flags {
|
||||||
|
self.disable(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an override for a specific flag.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flag` - The flag to remove the override for
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The previous state if it existed, None otherwise
|
||||||
|
pub fn remove(&mut self, flag: T) -> Option<FlagState> {
|
||||||
|
self.overrides.remove(&flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the override state for a specific flag.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flag` - The flag to query
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The override state if it exists, None otherwise
|
||||||
|
pub fn get(&self, flag: T) -> Option<FlagState> {
|
||||||
|
self.overrides.get(&flag).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if an override exists for a specific flag.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `flag` - The flag to check
|
||||||
|
pub fn has_override(&self, flag: T) -> bool {
|
||||||
|
self.overrides.contains_key(&flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all overrides.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&T, &FlagState)> {
|
||||||
|
self.overrides.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all flags that have overrides.
|
||||||
|
pub fn flags(&self) -> impl Iterator<Item = &T> {
|
||||||
|
self.overrides.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of overrides.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.overrides.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if there are no overrides.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.overrides.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all overrides.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.overrides.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ControlFlagTrait> Default for FlagsOverride<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use utils::HexSlice;
|
use utils::HexSlice;
|
||||||
|
|
||||||
use super::keys::phkh_v1;
|
use super::keys::phkh_v1;
|
||||||
|
use super::{EffectiveControlFlags, SeHdrControlFlags};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::misc::PAGESIZE;
|
use crate::misc::PAGESIZE;
|
||||||
use crate::pv_utils::error::Result;
|
use crate::pv_utils::error::Result;
|
||||||
@@ -37,9 +38,7 @@ use crate::pv_utils::uvdata::{
|
|||||||
UvDataTrait,
|
UvDataTrait,
|
||||||
};
|
};
|
||||||
use crate::pv_utils::uvdata_builder::{AeadCipherBuilderTrait, KeyExchangeBuilderTrait};
|
use crate::pv_utils::uvdata_builder::{AeadCipherBuilderTrait, KeyExchangeBuilderTrait};
|
||||||
use crate::pv_utils::{
|
use crate::pv_utils::{try_copy_slice_to_array, SeHdrFlag, SeTarget, PSW};
|
||||||
try_copy_slice_to_array, PlaintextControlFlagsV1, SecretControlFlagsV1, PSW,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct HdrSizesV1 {
|
struct HdrSizesV1 {
|
||||||
@@ -100,17 +99,15 @@ impl Display for SeHdrAadV1 {
|
|||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"plaintext control flags:\n{}",
|
"plaintext control flags:\n{}",
|
||||||
PlaintextControlFlagsV1::from(self.pcf)
|
SeHdrControlFlags::from_u64(self.pcf, SeTarget::V1Max, true)
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyExchangeTrait for SeHdrAadV1 {
|
impl KeyExchangeTrait for SeHdrAadV1 {
|
||||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
type PrivateKeyType = PKey<Private>;
|
||||||
let phkh = phkh_v1(key)?;
|
type TargetKeyType = PKeyRef<Public>;
|
||||||
Ok(self.contains_hash(phkh))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
self.cust_pub_key.clone().try_into()
|
self.cust_pub_key.clone().try_into()
|
||||||
@@ -129,6 +126,14 @@ impl KeyExchangeTrait for SeHdrAadV1 {
|
|||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
let phkh = phkh_v1(key)?;
|
||||||
|
Ok(self.contains_hash(phkh))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
@@ -176,7 +181,7 @@ impl Display for SeHdrConfV1 {
|
|||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"secret control flags:\n{}",
|
"secret control flags:\n{}",
|
||||||
SecretControlFlagsV1::from(self.scf)
|
SeHdrControlFlags::from_u64(self.scf, SeTarget::V1Max, false)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Support verbose mode if the `alternate` (`{:#}`) flag is used.
|
// Support verbose mode if the `alternate` (`{:#}`) flag is used.
|
||||||
@@ -413,11 +418,15 @@ impl UvDataPlainTrait for SeHdrDataV1 {
|
|||||||
impl SeHdrPlainTrait for SeHdrDataV1 {}
|
impl SeHdrPlainTrait for SeHdrDataV1 {}
|
||||||
|
|
||||||
impl KeyExchangeBuilderTrait for SeHdrDataV1 {
|
impl KeyExchangeBuilderTrait for SeHdrDataV1 {
|
||||||
|
type AeadKeyType = SymKey;
|
||||||
|
type PrivateKeyType = PKeyRef<Private>;
|
||||||
|
type TargetKeyType = PKeyRef<Public>;
|
||||||
|
|
||||||
fn add_keyslot(
|
fn add_keyslot(
|
||||||
&mut self,
|
&mut self,
|
||||||
hostkey: &PKeyRef<Public>,
|
hostkey: &Self::TargetKeyType,
|
||||||
aead_key: &SymKey,
|
aead_key: &Self::AeadKeyType,
|
||||||
priv_key: &PKeyRef<Private>,
|
priv_key: &Self::PrivateKeyType,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let keyslot = Keyslot::new(HostKey::V1(hostkey.to_owned()));
|
let keyslot = Keyslot::new(HostKey::V1(hostkey.to_owned()));
|
||||||
let keyslot_bin = keyslot.encrypt(aead_key.value(), priv_key)?.try_into()?;
|
let keyslot_bin = keyslot.encrypt(aead_key.value(), priv_key)?.try_into()?;
|
||||||
@@ -461,9 +470,8 @@ impl KeyExchangeBuilderTrait for SeHdrDataV1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeyExchangeTrait for SeHdrDataV1 {
|
impl KeyExchangeTrait for SeHdrDataV1 {
|
||||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
type PrivateKeyType = PKey<Private>;
|
||||||
self.aad.contains(key)
|
type TargetKeyType = PKeyRef<Public>;
|
||||||
}
|
|
||||||
|
|
||||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
self.aad.cust_pub_key()
|
self.aad.cust_pub_key()
|
||||||
@@ -476,6 +484,13 @@ impl KeyExchangeTrait for SeHdrDataV1 {
|
|||||||
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
self.aad.contains_hash(hash)
|
self.aad.contains_hash(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
self.aad.contains(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SeHdrConfBuilderTrait for SeHdrDataV1 {
|
impl SeHdrConfBuilderTrait for SeHdrDataV1 {
|
||||||
@@ -483,8 +498,8 @@ impl SeHdrConfBuilderTrait for SeHdrDataV1 {
|
|||||||
self.data.value_mut().psw = psw.clone();
|
self.data.value_mut().psw = psw.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_scf(&mut self, scf: &SecretControlFlagsV1) -> Result<()> {
|
fn set_scf(&mut self, scf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()> {
|
||||||
self.data.value_mut().scf = scf.into();
|
self.data.value_mut().scf = scf.to_u64();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,8 +514,8 @@ impl SeHdrConfBuilderTrait for SeHdrDataV1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SeHdrPubBuilderTrait for SeHdrDataV1 {
|
impl SeHdrPubBuilderTrait for SeHdrDataV1 {
|
||||||
fn set_pcf(&mut self, pcf: &PlaintextControlFlagsV1) -> Result<()> {
|
fn set_pcf(&mut self, pcf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()> {
|
||||||
self.aad.pcf = pcf.into();
|
self.aad.pcf = pcf.to_u64();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,9 +606,8 @@ impl AeadCipherBuilderTrait for SeHdrDataV1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeyExchangeTrait for SeHdrBinV1 {
|
impl KeyExchangeTrait for SeHdrBinV1 {
|
||||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
type PrivateKeyType = PKey<Private>;
|
||||||
self.aad.contains(key)
|
type TargetKeyType = PKeyRef<Public>;
|
||||||
}
|
|
||||||
|
|
||||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
self.aad.cust_pub_key()
|
self.aad.cust_pub_key()
|
||||||
@@ -606,6 +620,13 @@ impl KeyExchangeTrait for SeHdrBinV1 {
|
|||||||
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
self.aad.contains_hash(hash)
|
self.aad.contains_hash(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
self.aad.contains(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AeadDataTrait for SeHdrBinV1 {
|
impl AeadDataTrait for SeHdrBinV1 {
|
||||||
@@ -655,6 +676,7 @@ mod tests {
|
|||||||
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use pv::request::HostKey;
|
||||||
use pv::test_utils::get_test_key_and_cert;
|
use pv::test_utils::get_test_key_and_cert;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -663,7 +685,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn iv_keys_auto_generation_test() {
|
fn iv_keys_auto_generation_test() {
|
||||||
let (_, host_key) = get_test_key_and_cert();
|
let (_, host_key) = get_test_key_and_cert();
|
||||||
let host_keys = [host_key.public_key().unwrap()];
|
let host_keys = [HostKey::V1(host_key.public_key().unwrap())];
|
||||||
let mut builder = SeHdrBuilder::new(
|
let mut builder = SeHdrBuilder::new(
|
||||||
SeHdrVersion::V1,
|
SeHdrVersion::V1,
|
||||||
PSW {
|
PSW {
|
||||||
@@ -685,7 +707,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn chain_test() {
|
fn chain_test() {
|
||||||
let (_, host_key) = get_test_key_and_cert();
|
let (_, host_key) = get_test_key_and_cert();
|
||||||
let host_keys = [host_key.public_key().unwrap()];
|
let host_keys = [HostKey::V1(host_key.public_key().unwrap())];
|
||||||
let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||||
let meta = ComponentMetadataV1 {
|
let meta = ComponentMetadataV1 {
|
||||||
ald: [0x1; SHA_512_HASH_LEN],
|
ald: [0x1; SHA_512_HASH_LEN],
|
||||||
@@ -731,7 +753,7 @@ mod tests {
|
|||||||
const MAX_HOST_KEYS: usize = 95;
|
const MAX_HOST_KEYS: usize = 95;
|
||||||
|
|
||||||
let (_, host_key) = get_test_key_and_cert();
|
let (_, host_key) = get_test_key_and_cert();
|
||||||
let pub_key = host_key.public_key().unwrap();
|
let pub_key = HostKey::V1(host_key.public_key().unwrap());
|
||||||
let host_keys_max: Vec<_> = (0..MAX_HOST_KEYS).map(|_| pub_key.clone()).collect();
|
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 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 xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||||
|
|||||||
@@ -0,0 +1,916 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp. 2026
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::mem::{size_of, size_of_val};
|
||||||
|
|
||||||
|
use base64::prelude::BASE64_STANDARD;
|
||||||
|
use base64::Engine;
|
||||||
|
use deku::ctx::Endian;
|
||||||
|
use deku::prelude::*;
|
||||||
|
use openssl::nid::Nid;
|
||||||
|
use openssl::pkey::{PKeyRef, Public};
|
||||||
|
use pv::request::openssl::pkey::{PKey, Private};
|
||||||
|
use pv::request::{
|
||||||
|
gen_ec_key, random_array, Aes256XtsKey, Confidential, EcPubKeyCoord, HybridPKey, KeyslotV2,
|
||||||
|
SymKey, SymKeyType, Zeroize, SHA_512_HASH_LEN,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utils::HexSlice;
|
||||||
|
|
||||||
|
use super::keys::phkh_v2;
|
||||||
|
use super::{EffectiveControlFlags, SeHdrControlFlags};
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::pv_utils::error::Result;
|
||||||
|
use crate::pv_utils::misc::display_indented;
|
||||||
|
use crate::pv_utils::se_hdr::brb::{
|
||||||
|
ComponentMetadata, ComponentMetadataV1, SeHdrCommon, SeHdrConfBuilderTrait, SeHdrPlainTrait,
|
||||||
|
SeHdrPubBuilderTrait, SeHdrTrait,
|
||||||
|
};
|
||||||
|
use crate::pv_utils::se_hdr::keys::{BinaryKeySlotV2, EcPubKeyCoordV1};
|
||||||
|
use crate::pv_utils::serializing::{
|
||||||
|
bytesize, bytesize_confidential, confidential_read_slice, confidential_write_slice,
|
||||||
|
serde_base64, serde_hex_array, serde_hex_confidential_array, serde_hex_left_padded_u64,
|
||||||
|
serialize_to_bytes,
|
||||||
|
};
|
||||||
|
use crate::pv_utils::uv_keys::UvKeyHashV1;
|
||||||
|
use crate::pv_utils::uvdata::{
|
||||||
|
AeadCipherTrait, AeadDataTrait, AeadPlainDataTrait, KeyExchangeTrait, UvDataPlainTrait,
|
||||||
|
UvDataTrait,
|
||||||
|
};
|
||||||
|
use crate::pv_utils::uvdata_builder::{AeadCipherBuilderTrait, KeyExchangeBuilderTrait};
|
||||||
|
use crate::pv_utils::{try_copy_slice_to_array, SeHdrFlag, SeTarget, PSW};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct HdrSizesV2 {
|
||||||
|
pub phs: u64,
|
||||||
|
pub sea: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct SeHdrAadV2 {
|
||||||
|
#[deku(assert = "*sehs <= SeHdrDataV2::MAX_SIZE.try_into().unwrap()")]
|
||||||
|
pub sehs: u32,
|
||||||
|
#[serde(with = "serde_hex_array", rename = "iv_hex")]
|
||||||
|
pub iv: [u8; SymKeyType::AES_256_GCM_IV_LEN],
|
||||||
|
#[serde(skip)]
|
||||||
|
res1: u32,
|
||||||
|
#[deku(assert = "*nks <= (*sehs).into()", update = "self.keyslots.len()")]
|
||||||
|
pub nks: u64,
|
||||||
|
#[deku(assert = "*sea <= (*sehs).into()")]
|
||||||
|
pub sea: u64,
|
||||||
|
pub nep: u64,
|
||||||
|
#[serde(with = "serde_hex_left_padded_u64", rename = "pcf_hex")]
|
||||||
|
pub pcf: u64,
|
||||||
|
pub cust_pub_key: EcPubKeyCoordV1,
|
||||||
|
#[serde(with = "serde_hex_array", rename = "pld_hex")]
|
||||||
|
pub pld: [u8; SHA_512_HASH_LEN],
|
||||||
|
#[serde(with = "serde_hex_array", rename = "ald_hex")]
|
||||||
|
pub ald: [u8; SHA_512_HASH_LEN],
|
||||||
|
#[serde(with = "serde_hex_array", rename = "tld_hex")]
|
||||||
|
pub tld: [u8; SHA_512_HASH_LEN],
|
||||||
|
#[deku(count = "nks")]
|
||||||
|
pub keyslots: Vec<BinaryKeySlotV2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeHdrAadV2 {
|
||||||
|
const KEY_TYPE: SymKeyType = SymKeyType::Aes256Gcm;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SeHdrAadV2 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// Support verbose mode if the `alternate` (`{:#}`) flag is used.
|
||||||
|
if f.alternate() {
|
||||||
|
writeln!(f, "size: {} bytes", self.sehs)?;
|
||||||
|
writeln!(f, "number of key slots: {}", self.nks)?;
|
||||||
|
}
|
||||||
|
writeln!(f, "key slots:")?;
|
||||||
|
for s in &self.keyslots {
|
||||||
|
writeln!(f, " - {s}")?;
|
||||||
|
}
|
||||||
|
if f.alternate() {
|
||||||
|
let value = display_indented(f, &self.cust_pub_key, 2);
|
||||||
|
writeln!(f, "customer public key:\n{value}",)?;
|
||||||
|
writeln!(f, "number of component pages: {}", self.nep)?;
|
||||||
|
writeln!(f, "components content hash: {:}", HexSlice::from(&self.pld))?;
|
||||||
|
writeln!(f, "components address hash: {:}", HexSlice::from(&self.ald))?;
|
||||||
|
writeln!(f, "components tweak hash: {:}", HexSlice::from(&self.tld))?;
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"plaintext control flags:\n{}",
|
||||||
|
SeHdrControlFlags::from_u64(self.pcf, SeTarget::V2Max, true)
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyExchangeTrait for SeHdrAadV2 {
|
||||||
|
type PrivateKeyType = PKey<Private>;
|
||||||
|
type TargetKeyType = HybridPKey;
|
||||||
|
|
||||||
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
|
self.cust_pub_key.clone().try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_type(&self) -> SymKeyType {
|
||||||
|
Self::KEY_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
/** contains_hash - test if the given hash is in this SE-header
|
||||||
|
*
|
||||||
|
* * `hash`: hash to compare, either 32 or 64 byte long
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* - false if the given hash is not 64 or 32 bytes long
|
||||||
|
* - the comparison result otherwise
|
||||||
|
*/
|
||||||
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
|
let hash = hash.as_ref();
|
||||||
|
let size = hash.len();
|
||||||
|
|
||||||
|
match size {
|
||||||
|
UvKeyHashV1::UV_KEY_HASH_SIZE | SHA_512_HASH_LEN => (),
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.keyslots
|
||||||
|
.iter()
|
||||||
|
.any(|ks| ks.phkh[..size] == hash[..size])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
let key = key.as_ref();
|
||||||
|
let phkh = phkh_v2(key)?;
|
||||||
|
Ok(self.contains_hash(phkh))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct SeHdrConfV2 {
|
||||||
|
#[serde(with = "serde_hex_confidential_array", rename = "cck_hex")]
|
||||||
|
#[deku(
|
||||||
|
reader = "confidential_read_slice(deku::reader, endian)",
|
||||||
|
writer = "confidential_write_slice(cck, deku::writer, endian)"
|
||||||
|
)]
|
||||||
|
cck: Confidential<[u8; 32]>,
|
||||||
|
#[serde(with = "serde_hex_confidential_array", rename = "xts_hex")]
|
||||||
|
#[deku(
|
||||||
|
reader = "confidential_read_slice(deku::reader, endian)",
|
||||||
|
writer = "confidential_write_slice(xts, deku::writer, endian)"
|
||||||
|
)]
|
||||||
|
xts: Aes256XtsKey,
|
||||||
|
psw: PSW,
|
||||||
|
#[serde(with = "serde_hex_left_padded_u64", rename = "scf_hex")]
|
||||||
|
pub scf: u64,
|
||||||
|
#[serde(skip)]
|
||||||
|
#[deku(assert_eq = "0")]
|
||||||
|
noi: u32,
|
||||||
|
#[serde(skip)]
|
||||||
|
res2: u32,
|
||||||
|
#[serde(skip)]
|
||||||
|
#[deku(count = "noi")]
|
||||||
|
opt_items: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zeroize for SeHdrConfV2 {
|
||||||
|
fn zeroize(&mut self) {
|
||||||
|
self.cck.zeroize();
|
||||||
|
self.xts.zeroize();
|
||||||
|
self.psw.zeroize();
|
||||||
|
self.scf.zeroize();
|
||||||
|
self.noi.zeroize();
|
||||||
|
self.res2.zeroize();
|
||||||
|
self.opt_items.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SeHdrConfV2 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"secret control flags:\n{}",
|
||||||
|
SeHdrControlFlags::from_u64(self.scf, SeTarget::V2Max, false)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Support verbose mode if the `alternate` (`{:#}`) flag is used.
|
||||||
|
if f.alternate() {
|
||||||
|
writeln!(f, "CCK: {:}", HexSlice::from(self.cck.value()))?;
|
||||||
|
writeln!(f, "XTS key: {:}", HexSlice::from(self.xts.value()))?;
|
||||||
|
let psw = display_indented(f, &self.psw, 2);
|
||||||
|
writeln!(f, "PSW:\n{psw}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
pub struct SeHdrTagV2 {
|
||||||
|
#[serde(with = "serde_hex_array", rename = "tag_hex")]
|
||||||
|
tag: [u8; SymKeyType::AES_256_GCM_TAG_LEN],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SeHdrTagV2 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:}", HexSlice::from(&self.tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod ser_confidential_confv2 {
|
||||||
|
use pv::request::Confidential;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
use super::SeHdrConfV2;
|
||||||
|
|
||||||
|
pub fn serialize<S: Serializer>(
|
||||||
|
encrypted: &Confidential<SeHdrConfV2>,
|
||||||
|
ser: S,
|
||||||
|
) -> Result<S::Ok, S::Error> {
|
||||||
|
encrypted.value().serialize(ser)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Confidential<SeHdrConfV2>, D::Error> {
|
||||||
|
let conf = SeHdrConfV2::deserialize(deserializer)?;
|
||||||
|
Ok(Confidential::new(conf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Secure Execution Header definition
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
|
#[deku(endian = "big")]
|
||||||
|
pub struct SeHdrDataV2 {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub aad: SeHdrAadV2,
|
||||||
|
#[serde(flatten, with = "ser_confidential_confv2")]
|
||||||
|
#[deku(
|
||||||
|
reader = "confidential_read_sehdrconf_v2(deku::reader)",
|
||||||
|
writer = "confidential_write_sehdrconf_v2(data, deku::writer)"
|
||||||
|
)]
|
||||||
|
pub data: Confidential<SeHdrConfV2>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
tag: SeHdrTagV2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SeHdrDataV2 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// Support verbose mode if the `alternate` (`{:#}`) flag is used.
|
||||||
|
if f.alternate() {
|
||||||
|
write!(f, "{:#}", self.aad)?;
|
||||||
|
write!(f, "{:#}", self.data.value())?;
|
||||||
|
writeln!(f, "GCM tag: {}", self.tag)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", self.aad)?;
|
||||||
|
write!(f, "{}", self.data.value())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads from a `reader` and creates a confidential `SeHdrConfV2`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if there was an I/O error or the
|
||||||
|
/// `SeHdrConfV2` could not be constructed.
|
||||||
|
fn confidential_read_sehdrconf_v2<R>(
|
||||||
|
reader: &mut Reader<R>,
|
||||||
|
) -> Result<Confidential<SeHdrConfV2>, DekuError>
|
||||||
|
where
|
||||||
|
R: std::io::Read + std::io::Seek,
|
||||||
|
{
|
||||||
|
Ok(Confidential::new(SeHdrConfV2::from_reader_with_ctx(
|
||||||
|
reader,
|
||||||
|
(),
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a `Confidential<SeHdrConf1>` into this `writer`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if there was an I/O error.
|
||||||
|
fn confidential_write_sehdrconf_v2<W>(
|
||||||
|
value: &Confidential<SeHdrConfV2>,
|
||||||
|
writer: &mut Writer<W>,
|
||||||
|
) -> Result<(), DekuError>
|
||||||
|
where
|
||||||
|
W: std::io::Write + std::io::Seek,
|
||||||
|
{
|
||||||
|
value.value().to_writer(writer, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeHdrDataV2 {
|
||||||
|
// For Linux kernel >= 7.0, this is 1 MiB
|
||||||
|
const MAX_SIZE: usize = 1024 * 1024;
|
||||||
|
const PCF_DEFAULT: u64 = 0x0;
|
||||||
|
const SCF_DEFAULT: u64 = 0x0;
|
||||||
|
|
||||||
|
/// Creates a new `SeHdrDataV2`. It initializes the CCK and IV with random
|
||||||
|
/// data.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if there was not enough entropy to
|
||||||
|
/// create the random data or another error has occurred.
|
||||||
|
pub fn new(psw: PSW, components: ComponentMetadataV1) -> Result<Self> {
|
||||||
|
// Safety: The CCK is also 32 bytes large.
|
||||||
|
let cck = SymKey::random(SymKeyType::Aes256Gcm)?.try_into().unwrap();
|
||||||
|
let mut ret = Self {
|
||||||
|
aad: SeHdrAadV2 {
|
||||||
|
sehs: 0,
|
||||||
|
pcf: Self::PCF_DEFAULT,
|
||||||
|
ald: components.ald,
|
||||||
|
pld: components.pld,
|
||||||
|
tld: components.tld,
|
||||||
|
nep: components.nep,
|
||||||
|
sea: 0,
|
||||||
|
iv: random_array()?,
|
||||||
|
res1: 0,
|
||||||
|
nks: 0,
|
||||||
|
cust_pub_key: EcPubKeyCoordV1 { coord: [0_u8; 160] },
|
||||||
|
keyslots: vec![],
|
||||||
|
},
|
||||||
|
data: SeHdrConfV2 {
|
||||||
|
cck,
|
||||||
|
scf: Self::SCF_DEFAULT,
|
||||||
|
psw,
|
||||||
|
xts: components.key,
|
||||||
|
noi: 0,
|
||||||
|
res2: 0,
|
||||||
|
opt_items: vec![],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
tag: SeHdrTagV2::default(),
|
||||||
|
};
|
||||||
|
let hdr_size = ret.size()?;
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Result<HdrSizesV2> {
|
||||||
|
let sea = bytesize_confidential(&self.data)?;
|
||||||
|
let mut phs = bytesize(&self.aad)?
|
||||||
|
.checked_add(size_of::<SeHdrCommon>())
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?;
|
||||||
|
phs = phs
|
||||||
|
.checked_add(bytesize(&self.tag)?)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?;
|
||||||
|
phs = phs.checked_add(sea).ok_or(Error::UnexpectedOverflow)?;
|
||||||
|
|
||||||
|
Ok(HdrSizesV2 {
|
||||||
|
sea: sea.try_into()?,
|
||||||
|
phs: phs.try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the expected size of an constructed `SeHdrDataV2` with `n` key
|
||||||
|
/// slots.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if there was an arithmetic overflow
|
||||||
|
/// or.
|
||||||
|
pub fn expected_size(nks: usize) -> Result<usize> {
|
||||||
|
let cck = [0x0; 32].into();
|
||||||
|
let hdr = Self {
|
||||||
|
aad: SeHdrAadV2 {
|
||||||
|
sehs: 0,
|
||||||
|
pcf: Self::PCF_DEFAULT,
|
||||||
|
ald: [0x0; SHA_512_HASH_LEN],
|
||||||
|
pld: [0x0; SHA_512_HASH_LEN],
|
||||||
|
tld: [0x0; SHA_512_HASH_LEN],
|
||||||
|
nep: 0,
|
||||||
|
sea: 0,
|
||||||
|
iv: [0x0_u8; SymKeyType::AES_256_GCM_IV_LEN],
|
||||||
|
res1: 0,
|
||||||
|
nks: 0,
|
||||||
|
cust_pub_key: EcPubKeyCoordV1 { coord: [0_u8; 160] },
|
||||||
|
keyslots: vec![],
|
||||||
|
},
|
||||||
|
data: SeHdrConfV2 {
|
||||||
|
cck,
|
||||||
|
scf: Self::SCF_DEFAULT,
|
||||||
|
psw: PSW { mask: 0, addr: 0 },
|
||||||
|
xts: [0x0; SymKeyType::AES_256_XTS_KEY_LEN].into(),
|
||||||
|
noi: 0,
|
||||||
|
res2: 0,
|
||||||
|
opt_items: vec![],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
tag: SeHdrTagV2::default(),
|
||||||
|
};
|
||||||
|
let hdr_size: usize = hdr.size()?.phs.try_into().unwrap();
|
||||||
|
|
||||||
|
hdr_size
|
||||||
|
.checked_add(
|
||||||
|
size_of::<BinaryKeySlotV2>()
|
||||||
|
.checked_mul(nks)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?,
|
||||||
|
)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UvDataPlainTrait for SeHdrDataV2 {
|
||||||
|
type C = SeHdrBinV2;
|
||||||
|
}
|
||||||
|
impl SeHdrPlainTrait for SeHdrDataV2 {}
|
||||||
|
|
||||||
|
impl KeyExchangeBuilderTrait for SeHdrDataV2 {
|
||||||
|
type AeadKeyType = SymKey;
|
||||||
|
type PrivateKeyType = PKeyRef<Private>;
|
||||||
|
type TargetKeyType = HybridPKey;
|
||||||
|
|
||||||
|
fn generate_private_key(&self) -> Result<PKey<Private>> {
|
||||||
|
Ok(gen_ec_key(Nid::SECP521R1)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cust_public_key(&mut self, key: &PKeyRef<Private>) -> Result<()> {
|
||||||
|
self.aad.cust_pub_key = TryInto::<EcPubKeyCoord>::try_into(key)?.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_keyslots(&mut self) -> Result<()> {
|
||||||
|
let old_nks: usize = self.aad.nks.try_into().unwrap();
|
||||||
|
let keyslot_bin_size = size_of::<BinaryKeySlotV2>();
|
||||||
|
self.aad.keyslots.clear();
|
||||||
|
self.aad.nks = 0;
|
||||||
|
self.aad.sehs -= u32::try_from(
|
||||||
|
old_nks
|
||||||
|
.checked_mul(keyslot_bin_size)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_keyslot(
|
||||||
|
&mut self,
|
||||||
|
hostkey: &Self::TargetKeyType,
|
||||||
|
aead_key: &Self::AeadKeyType,
|
||||||
|
priv_key: &Self::PrivateKeyType,
|
||||||
|
) -> Result<()> {
|
||||||
|
let keyslot = KeyslotV2::new(hostkey.clone());
|
||||||
|
let keyslot_bin = keyslot.encrypt(aead_key.value(), priv_key)?.try_into()?;
|
||||||
|
let keyslot_bin_size = u32::try_from(size_of_val(&keyslot_bin)).unwrap();
|
||||||
|
self.aad.keyslots.push(keyslot_bin);
|
||||||
|
self.aad.nks = self
|
||||||
|
.aad
|
||||||
|
.nks
|
||||||
|
.checked_add(1)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?;
|
||||||
|
self.aad.sehs = self
|
||||||
|
.aad
|
||||||
|
.sehs
|
||||||
|
.checked_add(keyslot_bin_size)
|
||||||
|
.ok_or(Error::UnexpectedOverflow)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyExchangeTrait for SeHdrDataV2 {
|
||||||
|
type PrivateKeyType = PKey<Private>;
|
||||||
|
type TargetKeyType = HybridPKey;
|
||||||
|
|
||||||
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
|
self.aad.cust_pub_key()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_type(&self) -> SymKeyType {
|
||||||
|
self.aad.key_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
|
self.aad.contains_hash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
self.aad.contains(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeHdrConfBuilderTrait for SeHdrDataV2 {
|
||||||
|
fn set_psw(&mut self, psw: &PSW) {
|
||||||
|
self.data.value_mut().psw = psw.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scf(&mut self, scf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()> {
|
||||||
|
self.data.value_mut().scf = scf.to_u64();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cck(&mut self, cck: Confidential<Vec<u8>>) -> Result<()> {
|
||||||
|
self.data.value_mut().cck = cck.try_into()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_cck(&self) -> Result<SymKey> {
|
||||||
|
Ok(SymKey::random(SymKeyType::Aes256Gcm)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeHdrPubBuilderTrait for SeHdrDataV2 {
|
||||||
|
fn set_pcf(&mut self, pcf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()> {
|
||||||
|
self.aad.pcf = pcf.to_u64();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_components(&mut self, meta: ComponentMetadata) -> Result<()> {
|
||||||
|
let ComponentMetadataV1 {
|
||||||
|
ald,
|
||||||
|
pld,
|
||||||
|
tld,
|
||||||
|
nep,
|
||||||
|
key,
|
||||||
|
}: ComponentMetadataV1 = meta
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::InvalidComponentMetadata)?;
|
||||||
|
self.data.value_mut().xts = key;
|
||||||
|
self.aad.ald = ald;
|
||||||
|
self.aad.pld = pld;
|
||||||
|
self.aad.tld = tld;
|
||||||
|
self.aad.nep = nep;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
|
#[deku(endian = "big")]
|
||||||
|
pub struct SeHdrBinV2 {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub aad: SeHdrAadV2,
|
||||||
|
#[serde(with = "serde_base64", rename = "cipher_data_b64")]
|
||||||
|
#[deku(bytes_read = "aad.sea")]
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub tag: SeHdrTagV2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SeHdrBinV2 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// Support verbose mode if the `alternate` (`{:#}`) flag is used.
|
||||||
|
if f.alternate() {
|
||||||
|
write!(f, "{:#}", self.aad)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"encrypted data: {:#}",
|
||||||
|
BASE64_STANDARD.encode(&self.data)
|
||||||
|
)?;
|
||||||
|
writeln!(f, "GCM tag: {:#}", self.tag)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", self.aad)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeHdrBinV2 {
|
||||||
|
pub fn new(d: &[u8]) -> Result<Self> {
|
||||||
|
Self::try_from_data(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn try_from_data(data: &[u8]) -> Result<Self> {
|
||||||
|
let (_rest, val) = Self::from_bytes((data, 0))?;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UvDataTrait for SeHdrBinV2 {
|
||||||
|
type P = SeHdrDataV2;
|
||||||
|
}
|
||||||
|
impl SeHdrTrait for SeHdrBinV2 {}
|
||||||
|
|
||||||
|
impl AeadCipherTrait for SeHdrBinV2 {
|
||||||
|
fn aead_key_type(&self) -> SymKeyType {
|
||||||
|
self.key_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iv(&self) -> &[u8] {
|
||||||
|
&self.aad.iv
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aead_tag_size(&self) -> usize {
|
||||||
|
SymKeyType::AES_256_GCM_TAG_LEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadCipherBuilderTrait for SeHdrDataV2 {
|
||||||
|
fn set_iv(&mut self, iv: &[u8]) -> Result<()> {
|
||||||
|
self.aad.iv = try_copy_slice_to_array(iv)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyExchangeTrait for SeHdrBinV2 {
|
||||||
|
type PrivateKeyType = PKey<Private>;
|
||||||
|
type TargetKeyType = HybridPKey;
|
||||||
|
|
||||||
|
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||||
|
self.aad.cust_pub_key()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_type(&self) -> SymKeyType {
|
||||||
|
self.aad.key_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool {
|
||||||
|
self.aad.contains_hash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>,
|
||||||
|
{
|
||||||
|
self.aad.contains(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadDataTrait for SeHdrBinV2 {
|
||||||
|
fn aad(&self) -> Result<Vec<u8>> {
|
||||||
|
serialize_to_bytes(&self.aad)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Vec<u8> {
|
||||||
|
self.data.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag(&self) -> Vec<u8> {
|
||||||
|
serialize_to_bytes(&self.tag).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadPlainDataTrait for SeHdrDataV2 {
|
||||||
|
fn aad(&self) -> Result<Vec<u8>> {
|
||||||
|
serialize_to_bytes(&self.aad)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Result<Confidential<Vec<u8>>> {
|
||||||
|
Ok(serialize_to_bytes(self.data.value())?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag(&self) -> Vec<u8> {
|
||||||
|
serialize_to_bytes(&self.tag).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadCipherTrait for SeHdrDataV2 {
|
||||||
|
fn aead_key_type(&self) -> SymKeyType {
|
||||||
|
self.aad.key_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iv(&self) -> &[u8] {
|
||||||
|
&self.aad.iv
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aead_tag_size(&self) -> usize {
|
||||||
|
SymKeyType::AES_256_GCM_TAG_LEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use pv::request::HostKey;
|
||||||
|
use pv::test_utils::get_test_key_and_cert_hybrid;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::pv_utils::{BuilderTrait, SeHdr, SeHdrBuilder, SeHdrVersion};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iv_keys_auto_generation_test() {
|
||||||
|
let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid();
|
||||||
|
let host_keys = [HostKey::V2(
|
||||||
|
HybridPKey::new(
|
||||||
|
host_key1.public_key().unwrap(),
|
||||||
|
host_key2.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)];
|
||||||
|
let mut builder = SeHdrBuilder::new(
|
||||||
|
SeHdrVersion::V2,
|
||||||
|
PSW {
|
||||||
|
addr: 1234,
|
||||||
|
mask: 5678,
|
||||||
|
},
|
||||||
|
ComponentMetadataV1 {
|
||||||
|
ald: [0x1; SHA_512_HASH_LEN],
|
||||||
|
pld: [0x2; SHA_512_HASH_LEN],
|
||||||
|
tld: [0x3; SHA_512_HASH_LEN],
|
||||||
|
nep: 1,
|
||||||
|
key: Confidential::new([0x0_u8; SymKeyType::AES_256_XTS_KEY_LEN]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("should not fail");
|
||||||
|
builder.add_hostkeys(&host_keys).expect("should not fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chain_test() {
|
||||||
|
let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid();
|
||||||
|
let host_keys = [HostKey::V2(
|
||||||
|
HybridPKey::new(
|
||||||
|
host_key1.public_key().unwrap(),
|
||||||
|
host_key2.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)];
|
||||||
|
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 cck: Confidential<Vec<u8>> = [0x42; 32].to_vec().into();
|
||||||
|
let psw = PSW {
|
||||||
|
addr: 1234,
|
||||||
|
mask: 5678,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut builder = SeHdrBuilder::new(SeHdrVersion::V2, psw.clone(), meta.clone())
|
||||||
|
.expect("should not fail");
|
||||||
|
|
||||||
|
builder
|
||||||
|
.add_hostkeys(&host_keys)
|
||||||
|
.expect("should not fail")
|
||||||
|
.with_components(meta.clone())
|
||||||
|
.expect("should not fail")
|
||||||
|
.with_cck(cck.clone())
|
||||||
|
.expect("should not fail");
|
||||||
|
let prot_key = builder.prot_key().to_owned();
|
||||||
|
let bin = builder.build().expect("should not fail");
|
||||||
|
|
||||||
|
let reader = Cursor::new(bin.as_bytes().expect("should not fail"));
|
||||||
|
let hdr = SeHdr::try_from_io(reader).unwrap();
|
||||||
|
|
||||||
|
let hdr_plain = hdr.decrypt(&prot_key).unwrap();
|
||||||
|
assert_eq!(hdr_plain.common.version, SeHdrVersion::V2);
|
||||||
|
let hdr_data_v2: SeHdrDataV2 = hdr_plain.data.try_into().expect("should not fail");
|
||||||
|
assert_eq!(meta.ald, hdr_data_v2.aad.ald);
|
||||||
|
assert_eq!(meta.pld, hdr_data_v2.aad.pld);
|
||||||
|
assert_eq!(meta.tld, hdr_data_v2.aad.tld);
|
||||||
|
assert_eq!(psw, hdr_data_v2.data.value().psw);
|
||||||
|
assert_eq!(cck.value(), hdr_data_v2.data.value().cck.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_size_sehdr_test() {
|
||||||
|
const MAX_HOST_KEYS: usize = 623; // since Linux kernel 7.0
|
||||||
|
|
||||||
|
let (_, host_key1, host_key2) = get_test_key_and_cert_hybrid();
|
||||||
|
let pub_key = HostKey::V2(
|
||||||
|
HybridPKey::new(
|
||||||
|
host_key1.public_key().unwrap(),
|
||||||
|
host_key2.public_key().unwrap(),
|
||||||
|
)
|
||||||
|
.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::V2, 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::V2);
|
||||||
|
let hdr_v2: SeHdrBinV2 = bin.data.try_into().expect("should not fail");
|
||||||
|
assert_eq!(hdr_v2.aad.sehs, 1047200); // since kernel 7.0
|
||||||
|
|
||||||
|
let mut builder = SeHdrBuilder::new(SeHdrVersion::V2, 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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn roundtrip_se_hdr_tag_v2_json() {
|
||||||
|
let tag = SeHdrTagV2 {
|
||||||
|
tag: [0x42; SymKeyType::AES_256_GCM_TAG_LEN],
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&tag).expect("should serialize");
|
||||||
|
assert_eq!(json, "{\"tag_hex\":\"42424242424242424242424242424242\"}");
|
||||||
|
let deserialized: SeHdrTagV2 = serde_json::from_str(&json).expect("should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(tag, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn roundtrip_se_hdr_conf_v2_json() {
|
||||||
|
let conf = SeHdrConfV2 {
|
||||||
|
cck: Confidential::new([0x11; 32]),
|
||||||
|
xts: Confidential::new([0x22; SymKeyType::AES_256_XTS_KEY_LEN]),
|
||||||
|
psw: PSW {
|
||||||
|
addr: 0x1000,
|
||||||
|
mask: 0x2000,
|
||||||
|
},
|
||||||
|
scf: 0x42,
|
||||||
|
noi: 0,
|
||||||
|
res2: 0,
|
||||||
|
opt_items: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&conf).expect("should serialize");
|
||||||
|
assert_eq!(json, "{\"cck_hex\":\"1111111111111111111111111111111111111111111111111111111111111111\",\"xts_hex\":\"22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222\",\"psw\":{\"mask_hex\":\"0000000000002000\",\"addr_hex\":\"0000000000001000\"},\"scf_hex\":\"0000000000000042\"}");
|
||||||
|
let deserialized: SeHdrConfV2 = serde_json::from_str(&json).expect("should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(conf, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn roundtrip_se_hdr_aad_v2_json() {
|
||||||
|
let aad = SeHdrAadV2 {
|
||||||
|
sehs: 1024,
|
||||||
|
iv: [0x33; SymKeyType::AES_256_GCM_IV_LEN],
|
||||||
|
res1: 0,
|
||||||
|
nks: 2,
|
||||||
|
sea: 512,
|
||||||
|
nep: 10,
|
||||||
|
pcf: 0x100,
|
||||||
|
cust_pub_key: EcPubKeyCoordV1 { coord: [0x44; 160] },
|
||||||
|
pld: [0x55; SHA_512_HASH_LEN],
|
||||||
|
ald: [0x66; SHA_512_HASH_LEN],
|
||||||
|
tld: [0x77; SHA_512_HASH_LEN],
|
||||||
|
keyslots: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&aad).expect("should serialize");
|
||||||
|
assert_eq!(json, "{\"sehs\":1024,\"iv_hex\":\"333333333333333333333333\",\"nks\":2,\"sea\":512,\"nep\":10,\"pcf_hex\":\"0000000000000100\",\"cust_pub_key\":{\"coord_hex\":\"44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444\"},\"pld_hex\":\"55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555\",\"ald_hex\":\"66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666\",\"tld_hex\":\"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777\",\"keyslots\":[]}");
|
||||||
|
let deserialized: SeHdrAadV2 = serde_json::from_str(&json).expect("should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(aad, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn roundtrip_se_hdr_bin_v2_json() {
|
||||||
|
let bin = SeHdrBinV2 {
|
||||||
|
aad: SeHdrAadV2 {
|
||||||
|
sehs: 1024,
|
||||||
|
iv: [0x33; SymKeyType::AES_256_GCM_IV_LEN],
|
||||||
|
res1: 0,
|
||||||
|
nks: 0,
|
||||||
|
sea: 64,
|
||||||
|
nep: 10,
|
||||||
|
pcf: 0x100,
|
||||||
|
cust_pub_key: EcPubKeyCoordV1 { coord: [0x44; 160] },
|
||||||
|
pld: [0x55; SHA_512_HASH_LEN],
|
||||||
|
ald: [0x66; SHA_512_HASH_LEN],
|
||||||
|
tld: [0x77; SHA_512_HASH_LEN],
|
||||||
|
keyslots: vec![],
|
||||||
|
},
|
||||||
|
data: vec![0x88; 64],
|
||||||
|
tag: SeHdrTagV2 {
|
||||||
|
tag: [0x99; SymKeyType::AES_256_GCM_TAG_LEN],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&bin).expect("should serialize");
|
||||||
|
assert_eq!(json, "{\"sehs\":1024,\"iv_hex\":\"333333333333333333333333\",\"nks\":0,\"sea\":64,\"nep\":10,\"pcf_hex\":\"0000000000000100\",\"cust_pub_key\":{\"coord_hex\":\"44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444\"},\"pld_hex\":\"55555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555\",\"ald_hex\":\"66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666\",\"tld_hex\":\"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777\",\"keyslots\":[],\"cipher_data_b64\":\"iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiA==\",\"tag_hex\":\"99999999999999999999999999999999\"}");
|
||||||
|
let deserialized: SeHdrBinV2 = serde_json::from_str(&json).expect("should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(bin, deserialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ use deku::ctx::Endian;
|
|||||||
use deku::{DekuRead, DekuWrite};
|
use deku::{DekuRead, DekuWrite};
|
||||||
use openssl::hash::{hash, MessageDigest};
|
use openssl::hash::{hash, MessageDigest};
|
||||||
use openssl::pkey::{PKey, PKeyRef, Public};
|
use openssl::pkey::{PKey, PKeyRef, Public};
|
||||||
use pv::request::EcPubKeyCoord;
|
use pv::request::{EcPubKeyCoord, HybridPKey};
|
||||||
use pv::static_assert;
|
use pv::static_assert;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use utils::HexSlice;
|
use utils::HexSlice;
|
||||||
@@ -30,6 +30,20 @@ pub fn phkh_v1<T: AsRef<PKeyRef<Public>>>(key: T) -> Result<[u8; 32]> {
|
|||||||
try_copy_slice_to_array(&binding)
|
try_copy_slice_to_array(&binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to hash the public hybrid EC + ML-KEM key.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if OpenSSL could not hash the key.
|
||||||
|
pub fn phkh_v2(key: &HybridPKey) -> Result<[u8; 64]> {
|
||||||
|
let mut buf: Vec<u8> = vec![];
|
||||||
|
let phk: EcPubKeyCoord = key.ec_key().try_into()?;
|
||||||
|
buf.extend_from_slice(phk.as_ref());
|
||||||
|
buf.extend_from_slice(&key.mlkem_key().raw_public_key()?);
|
||||||
|
let binding = hash(MessageDigest::sha512(), &buf)?;
|
||||||
|
try_copy_slice_to_array(&binding)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
pub struct EcPubKeyCoordV1 {
|
pub struct EcPubKeyCoordV1 {
|
||||||
@@ -112,6 +126,58 @@ impl TryFrom<Vec<u8>> for BinaryKeySlotV1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, DekuRead, DekuWrite, Serialize, Deserialize)]
|
||||||
|
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||||
|
/// Binary key slot v1
|
||||||
|
pub struct BinaryKeySlotV2 {
|
||||||
|
#[serde(with = "serde_hex_array", rename = "phkh_hex")]
|
||||||
|
/// Public host key hash
|
||||||
|
pub phkh: [u8; 64],
|
||||||
|
#[serde(with = "serde_hex_array", rename = "wrpk_hex")]
|
||||||
|
/// Wrapper key
|
||||||
|
pub wrpk: [u8; 32],
|
||||||
|
/// Tag
|
||||||
|
#[serde(with = "serde_hex_array", rename = "kst_hex")]
|
||||||
|
pub kst: [u8; 16],
|
||||||
|
#[serde(with = "serde_hex_array", rename = "kc_hex")]
|
||||||
|
/// Ciohertext
|
||||||
|
pub kc: [u8; 1568],
|
||||||
|
}
|
||||||
|
static_assert!(size_of::<BinaryKeySlotV2>() == 1680);
|
||||||
|
|
||||||
|
impl Display for BinaryKeySlotV2 {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "target key hash: {:}", HexSlice::from(&self.phkh))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BinaryKeySlotV2 {
|
||||||
|
fn default() -> Self {
|
||||||
|
BinaryKeySlotV2 {
|
||||||
|
phkh: [0_u8; 64],
|
||||||
|
wrpk: [0_u8; 32],
|
||||||
|
kst: [0_u8; 16],
|
||||||
|
kc: [0_u8; 1568],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for BinaryKeySlotV2 {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
let data: [u8; 1680] = try_copy_slice_to_array(&value)?;
|
||||||
|
let bin = Self {
|
||||||
|
phkh: data[..64].try_into().unwrap(),
|
||||||
|
wrpk: data[64..96].try_into().unwrap(),
|
||||||
|
kst: data[96..112].try_into().unwrap(),
|
||||||
|
kc: data[112..].try_into().unwrap(),
|
||||||
|
};
|
||||||
|
Ok(bin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod serde_tests {
|
mod serde_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::io::{BufRead, BufReader, Read};
|
|||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use pv::misc::decode_hex;
|
use pv::misc::decode_hex;
|
||||||
|
|
||||||
use super::try_copy_slice_to_array;
|
use super::{try_copy_slice_to_array, KeyExchangeTrait, SeHdr};
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
/// The `enum_dispatch` macros needs at least one local trait to be implemented.
|
/// The `enum_dispatch` macros needs at least one local trait to be implemented.
|
||||||
@@ -41,15 +41,116 @@ impl UvKeyHashV1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum UvKeyHashV1Kind {
|
||||||
|
PCHKH,
|
||||||
|
PBHKH,
|
||||||
|
PCHHKH,
|
||||||
|
PBHHKH,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UvKeyHashV1Kind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::PCHKH => write!(f, "Classical Host key hash"),
|
||||||
|
Self::PBHKH => write!(f, "Backup classical host key hash"),
|
||||||
|
Self::PCHHKH => write!(f, "Hybrid host key hash"),
|
||||||
|
Self::PBHHKH => write!(f, "Backup hybrid host key hash"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Index into the UV key hash array.
|
||||||
|
///
|
||||||
|
/// The indices follow the UV specification layout:
|
||||||
|
/// - 0: PCHKH (Classical host key hash)
|
||||||
|
/// - 1: PBHKH (Backup classical host key hash)
|
||||||
|
/// - 2-3: Reserved for future use
|
||||||
|
/// - 4: PCHHKH (Hybrid host key hash)
|
||||||
|
/// - 5: PBHHKH (Backup hybrid host key hash)
|
||||||
|
/// - 6-14: Reserved for future use
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct UvKeyHashIdx(u8);
|
||||||
|
|
||||||
|
impl UvKeyHashIdx {
|
||||||
|
pub const PCHKH: Self = Self(0);
|
||||||
|
pub const PBHKH: Self = Self(1);
|
||||||
|
pub const PCHHKH: Self = Self(4);
|
||||||
|
pub const PBHHKH: Self = Self(5);
|
||||||
|
|
||||||
|
pub fn index(self) -> usize {
|
||||||
|
self.0 as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(self) -> Option<UvKeyHashV1Kind> {
|
||||||
|
match self.0 {
|
||||||
|
0 => Some(UvKeyHashV1Kind::PCHKH),
|
||||||
|
1 => Some(UvKeyHashV1Kind::PBHKH),
|
||||||
|
4 => Some(UvKeyHashV1Kind::PCHHKH),
|
||||||
|
5 => Some(UvKeyHashV1Kind::PBHHKH),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<usize> for UvKeyHashIdx {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0..=14 => Ok(Self(value as u8)),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct UvKeyHashesV1 {
|
pub struct UvKeyHashesV1 {
|
||||||
pub pchkh: UvKeyHashV1,
|
pub hashes: [UvKeyHashV1; 15],
|
||||||
pub pbhkh: UvKeyHashV1,
|
}
|
||||||
pub res: [UvKeyHashV1; 13],
|
|
||||||
|
impl Index<UvKeyHashIdx> for UvKeyHashesV1 {
|
||||||
|
type Output = UvKeyHashV1;
|
||||||
|
|
||||||
|
fn index(&self, pos: UvKeyHashIdx) -> &Self::Output {
|
||||||
|
&self.hashes[pos.index()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<UvKeyHashIdx> for UvKeyHashesV1 {
|
||||||
|
fn index_mut(&mut self, pos: UvKeyHashIdx) -> &mut Self::Output {
|
||||||
|
&mut self.hashes[pos.index()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MatchingUvKeyHash<'a> {
|
||||||
|
pub idx: UvKeyHashIdx,
|
||||||
|
pub hash: &'a UvKeyHashV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UvKeyHashesV1 {
|
||||||
|
pub fn matching_hashes(&self, hdr: &SeHdr) -> Vec<MatchingUvKeyHash<'_>> {
|
||||||
|
self.hashes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, hash)| hdr.contains_hash(hash))
|
||||||
|
.filter_map(|(idx, hash)| {
|
||||||
|
Some(MatchingUvKeyHash {
|
||||||
|
idx: idx.try_into().ok()?,
|
||||||
|
hash,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UvKeyHashV1 {
|
impl UvKeyHashV1 {
|
||||||
pub const UV_KEY_HASH_NULL: Self = Self([0x0_u8; 32]);
|
pub const UV_KEY_HASH_SIZE: usize = 32;
|
||||||
|
pub const UV_KEY_HASH_NULL: Self = Self([0x0_u8; Self::UV_KEY_HASH_SIZE]);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<[u8]> for UvKeyHashV1 {
|
impl AsRef<[u8]> for UvKeyHashV1 {
|
||||||
@@ -107,9 +208,8 @@ impl UvKeyHashesV1 {
|
|||||||
return Err(Error::InvalidUvKeyHashes);
|
return Err(Error::InvalidUvKeyHashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
let [pchkh, pbhkh, res @ ..]: [UvKeyHashV1; 15] =
|
let hashes: [UvKeyHashV1; 15] = hashes.try_into().map_err(|_| Error::InvalidUvKeyHashes)?;
|
||||||
hashes.try_into().map_err(|_| Error::InvalidUvKeyHashes)?;
|
Ok(Self { hashes })
|
||||||
Ok(Self { pchkh, pbhkh, res })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,21 +241,18 @@ mod tests {
|
|||||||
0000000000000000000000000000000000000000000000000000000000000000
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
";
|
";
|
||||||
let result = UvKeyHashesV1::read_from_io(Cursor::new(data)).expect("should not fail");
|
let result = UvKeyHashesV1::read_from_io(Cursor::new(data)).expect("should not fail");
|
||||||
assert_eq!(
|
let mut exp_hashes = [UvKeyHashV1::UV_KEY_HASH_NULL; 15];
|
||||||
result,
|
exp_hashes[0] = UvKeyHashV1::new(
|
||||||
UvKeyHashesV1 {
|
decode_hex("0b729fd62241b339840d61b964a06bb6a1fd4976d9ebea2b4fb48d44de3a2461").unwrap(),
|
||||||
pchkh: UvKeyHashV1::new(
|
)
|
||||||
decode_hex("0b729fd62241b339840d61b964a06bb6a1fd4976d9ebea2b4fb48d44de3a2461")
|
.unwrap();
|
||||||
.unwrap()
|
|
||||||
)
|
exp_hashes[1] = UvKeyHashV1::new(
|
||||||
.unwrap(),
|
decode_hex("8ec6bc2f77d5d6474b1417cf0a8c914f576245a5b9bb0eefacc7b821483ece7d").unwrap(),
|
||||||
pbhkh: UvKeyHashV1::new(
|
)
|
||||||
decode_hex("8ec6bc2f77d5d6474b1417cf0a8c914f576245a5b9bb0eefacc7b821483ece7d")
|
.unwrap();
|
||||||
.unwrap()
|
|
||||||
)
|
let uv_hashes = UvKeyHashesV1 { hashes: exp_hashes };
|
||||||
.unwrap(),
|
assert_eq!(result, uv_hashes);
|
||||||
res: [UvKeyHashV1::UV_KEY_HASH_NULL; 13],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,10 @@
|
|||||||
// Copyright IBM Corp. 2024
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use pv::request::openssl::pkey::{PKey, PKeyRef, Private, Public};
|
use pv::request::openssl::pkey::{PKey, Public};
|
||||||
use pv::request::{
|
use pv::request::{decrypt_aead, encrypt_aead, Confidential, SymKey, SymKeyType};
|
||||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, Confidential, SymKey, SymKeyType,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::se_hdr::{SeHdrBinV1, SeHdrData, SeHdrVersioned};
|
use super::se_hdr::{SeHdrBinV1, SeHdrBinV2, SeHdrData, SeHdrVersioned};
|
||||||
use crate::pv_utils::error::{Error, Result};
|
use crate::pv_utils::error::{Error, Result};
|
||||||
use crate::pv_utils::serializing::deserialize_from_bytes;
|
use crate::pv_utils::serializing::deserialize_from_bytes;
|
||||||
|
|
||||||
@@ -54,15 +52,19 @@ pub trait AeadPlainDataTrait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Key exchange related methods
|
/// Key exchange related methods
|
||||||
#[enum_dispatch]
|
|
||||||
pub trait KeyExchangeTrait {
|
pub trait KeyExchangeTrait {
|
||||||
/// Checks if a public key was used.
|
type TargetKeyType;
|
||||||
|
type PrivateKeyType: ToOwned;
|
||||||
|
|
||||||
|
/// Checks if a public target key was used.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This function will return an error if the public key cannot be converted
|
/// This function will return an error if the public key cannot be converted
|
||||||
/// into a hash.
|
/// into a hash.
|
||||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool>;
|
fn contains<K>(&self, key: K) -> Result<bool>
|
||||||
|
where
|
||||||
|
K: AsRef<Self::TargetKeyType>;
|
||||||
|
|
||||||
/// Checks if the hash of a public key was used.
|
/// Checks if the hash of a public key was used.
|
||||||
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool;
|
fn contains_hash<H: AsRef<[u8]>>(&self, hash: H) -> bool;
|
||||||
@@ -79,22 +81,24 @@ pub trait KeyExchangeTrait {
|
|||||||
/// Returns the key type of the exchanged key.
|
/// Returns the key type of the exchanged key.
|
||||||
fn key_type(&self) -> SymKeyType;
|
fn key_type(&self) -> SymKeyType;
|
||||||
|
|
||||||
/// Derive the key.
|
// TODO Implement it
|
||||||
///
|
// /// Derive the key.
|
||||||
/// # Errors
|
// ///
|
||||||
///
|
// /// # Errors
|
||||||
/// This function will return an error if there is no customer public key is
|
// ///
|
||||||
/// available or the key derivations fails.
|
// /// This function will return an error if there is no customer public key is
|
||||||
fn derive_key<K: AsRef<PKeyRef<Private>>>(&mut self, other_priv_key: K) -> Result<SymKey> {
|
// /// available or the key derivations fails.
|
||||||
match self.key_type() {
|
// fn derive_key<K>(&mut self, other_priv_key: K) -> Result<SymKey> where
|
||||||
SymKeyType::Aes256Gcm => Ok(derive_aes256_gcm_key(
|
// K: AsRef<Self::PrivateKeyType>{
|
||||||
other_priv_key.as_ref(),
|
// match self.key_type() {
|
||||||
self.cust_pub_key()?.as_ref(),
|
// SymKeyType::Aes256Gcm =>
|
||||||
)?
|
// match Self::TargetKeyType {
|
||||||
.into()),
|
// PKeyRef::<Public> => {todo!()},
|
||||||
_ => unreachable!("BUG"),
|
// HybridPKey => {todo!()},
|
||||||
}
|
// }
|
||||||
}
|
// _ => unreachable!("BUG"),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait to be used for plain UV data.
|
/// Trait to be used for plain UV data.
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2024
|
// Copyright IBM Corp. 2024
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
use openssl::pkey::{PKey, Private};
|
||||||
use pv::request::{Confidential, SymKey};
|
use pv::request::{Confidential, SymKey};
|
||||||
|
|
||||||
use super::Error;
|
use super::Error;
|
||||||
use crate::pv_utils::error::Result;
|
use crate::pv_utils::error::Result;
|
||||||
use crate::pv_utils::se_hdr::SeHdrData;
|
|
||||||
use crate::pv_utils::uvdata::{AeadCipherTrait, UvDataPlainTrait};
|
use crate::pv_utils::uvdata::{AeadCipherTrait, UvDataPlainTrait};
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
@@ -20,33 +21,33 @@ pub trait AeadCipherBuilderTrait: AeadCipherTrait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Key exchange related methods
|
/// Key exchange related methods
|
||||||
#[enum_dispatch]
|
|
||||||
pub trait KeyExchangeBuilderTrait {
|
pub trait KeyExchangeBuilderTrait {
|
||||||
|
type TargetKeyType: ToOwned;
|
||||||
|
type AeadKeyType: Display + std::fmt::Debug;
|
||||||
|
type PrivateKeyType: ToOwned;
|
||||||
|
|
||||||
fn add_keyslot(
|
fn add_keyslot(
|
||||||
&mut self,
|
&mut self,
|
||||||
hostkey: &PKeyRef<Public>,
|
hostkey: &Self::TargetKeyType,
|
||||||
aead_key: &SymKey,
|
aead_key: &Self::AeadKeyType,
|
||||||
priv_key: &PKeyRef<Private>,
|
priv_key: &Self::PrivateKeyType,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
fn clear_keyslots(&mut self) -> Result<()>;
|
fn clear_keyslots(&mut self) -> Result<()>;
|
||||||
|
// TODO How to handle PKey vs &PKeyRef?
|
||||||
fn generate_private_key(&self) -> Result<PKey<Private>>;
|
fn generate_private_key(&self) -> Result<PKey<Private>>;
|
||||||
fn set_cust_public_key(&mut self, key: &PKeyRef<Private>) -> Result<()>;
|
fn set_cust_public_key(&mut self, key: &Self::PrivateKeyType) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UvDataBuilder<
|
pub struct UvDataBuilder<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> {
|
||||||
'a,
|
|
||||||
T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait,
|
|
||||||
K = PKeyRef<Public>,
|
|
||||||
P = PKey<Private>,
|
|
||||||
> {
|
|
||||||
pub(crate) expert_mode: bool,
|
pub(crate) expert_mode: bool,
|
||||||
pub(crate) prot_key: SymKey,
|
pub(crate) prot_key: T::AeadKeyType,
|
||||||
pub(crate) priv_key: P,
|
pub(crate) priv_key: T::PrivateKeyType,
|
||||||
pub(crate) target_keys: Vec<&'a K>,
|
pub(crate) target_keys: Vec<T::TargetKeyType>,
|
||||||
pub(crate) plain_data: T,
|
pub(crate) plain_data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, K, P> UvDataBuilder<'_, T, K, P> {
|
impl<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<T> {
|
||||||
/// Enable expert mode - this is required for specifying PSW, etc.
|
/// Enable expert mode - this is required for specifying PSW, etc.
|
||||||
pub fn i_know_what_i_am_doing(&mut self) {
|
pub fn i_know_what_i_am_doing(&mut self) {
|
||||||
self.expert_mode = true;
|
self.expert_mode = true;
|
||||||
@@ -54,7 +55,7 @@ impl<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, K, P> UvDataBuilder<'_
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: std::fmt::Debug + KeyExchangeBuilderTrait + AeadCipherBuilderTrait + UvDataPlainTrait>
|
impl<T: std::fmt::Debug + KeyExchangeBuilderTrait + AeadCipherBuilderTrait + UvDataPlainTrait>
|
||||||
std::fmt::Debug for UvDataBuilder<'_, T>
|
std::fmt::Debug for UvDataBuilder<T>
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("UvDataBuilder")
|
f.debug_struct("UvDataBuilder")
|
||||||
@@ -65,15 +66,18 @@ impl<T: std::fmt::Debug + KeyExchangeBuilderTrait + AeadCipherBuilderTrait + UvD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a, T> {
|
impl<T> UvDataBuilder<T>
|
||||||
pub fn add_hostkeys<P: AsRef<PKeyRef<Public>>>(
|
where
|
||||||
&mut self,
|
T: KeyExchangeBuilderTrait<AeadKeyType = SymKey> + AeadCipherBuilderTrait,
|
||||||
hostkeys: &'a [P],
|
{
|
||||||
) -> Result<&mut Self> {
|
pub fn add_hostkeys(&mut self, hostkeys: &[T::TargetKeyType]) -> Result<&mut Self>
|
||||||
|
where
|
||||||
|
T::TargetKeyType: Clone,
|
||||||
|
{
|
||||||
for hk in hostkeys {
|
for hk in hostkeys {
|
||||||
self.plain_data
|
self.plain_data
|
||||||
.add_keyslot(hk.as_ref(), &self.prot_key, &self.priv_key)?;
|
.add_keyslot(hk, &self.prot_key, &self.priv_key)?;
|
||||||
self.target_keys.push(hk.as_ref());
|
self.target_keys.push(hk.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
@@ -89,9 +93,9 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a,
|
|||||||
|
|
||||||
fn update_target_key_slots(&mut self) -> Result<()> {
|
fn update_target_key_slots(&mut self) -> Result<()> {
|
||||||
self.plain_data.clear_keyslots()?;
|
self.plain_data.clear_keyslots()?;
|
||||||
for hk in &self.target_keys {
|
for hostkey in &self.target_keys {
|
||||||
self.plain_data
|
self.plain_data
|
||||||
.add_keyslot(hk, &self.prot_key, &self.priv_key)?;
|
.add_keyslot(hostkey, &self.prot_key, &self.priv_key)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -100,6 +104,7 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a,
|
|||||||
if !self.expert_mode {
|
if !self.expert_mode {
|
||||||
return Err(Error::NonExpertMode);
|
return Err(Error::NonExpertMode);
|
||||||
}
|
}
|
||||||
|
// TODO Implement TryFrom<...> ?!
|
||||||
let key = SymKey::try_from_data(self.plain_data.aead_key_type(), data)?;
|
let key = SymKey::try_from_data(self.plain_data.aead_key_type(), data)?;
|
||||||
self.prot_key = key;
|
self.prot_key = key;
|
||||||
self.update_target_key_slots()?;
|
self.update_target_key_slots()?;
|
||||||
@@ -107,23 +112,26 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a,
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_priv_key(&mut self, priv_key: &PKeyRef<Private>) -> Result<&mut Self> {
|
pub fn with_priv_key(&mut self, priv_key: &T::PrivateKeyType) -> Result<&mut Self>
|
||||||
|
where
|
||||||
|
T::PrivateKeyType: Clone,
|
||||||
|
{
|
||||||
if !self.expert_mode {
|
if !self.expert_mode {
|
||||||
return Err(Error::NonExpertMode);
|
return Err(Error::NonExpertMode);
|
||||||
}
|
}
|
||||||
self.plain_data.set_cust_public_key(priv_key)?;
|
self.plain_data.set_cust_public_key(priv_key)?;
|
||||||
self.priv_key = priv_key.to_owned();
|
self.priv_key = priv_key.clone();
|
||||||
self.update_target_key_slots()?;
|
self.update_target_key_slots()?;
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn prot_key(&self) -> &SymKey {
|
pub const fn prot_key(&self) -> &<T as KeyExchangeBuilderTrait>::AeadKeyType {
|
||||||
&self.prot_key
|
&self.prot_key
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn priv_key(&self) -> &PKeyRef<Private> {
|
pub fn priv_key(&self) -> &<T as KeyExchangeBuilderTrait>::PrivateKeyType {
|
||||||
self.priv_key.as_ref()
|
&self.priv_key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-11
@@ -10,17 +10,14 @@ use std::rc::Rc;
|
|||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use deku::DekuContainerRead;
|
use deku::DekuContainerRead;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use openssl::pkey::{PKey, Public};
|
|
||||||
use pv::misc::read_file;
|
use pv::misc::read_file;
|
||||||
use pv::request::Confidential;
|
use pv::request::{Confidential, HostKey};
|
||||||
use pvimg::error::Error;
|
use pvimg::error::Error;
|
||||||
use pvimg::misc::{round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
|
use pvimg::misc::{round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
|
||||||
use pvimg::secured_comp::{
|
use pvimg::secured_comp::{
|
||||||
ComponentTrait, Interval, Layout, SecuredComponent, SecuredComponentBuilder,
|
ComponentTrait, Interval, Layout, SecuredComponent, SecuredComponentBuilder,
|
||||||
};
|
};
|
||||||
use pvimg::uvdata::{
|
use pvimg::uvdata::{BuilderTrait, EffectiveControlFlags, SeHdrBuilder, SeHdrFlag, SeHdrVersion};
|
||||||
BuilderTrait, PlaintextControlFlagsV1, SeHdrBuilder, SeHdrVersion, SecretControlFlagsV1,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::se_img_comps::ipib::Ipib;
|
use crate::se_img_comps::ipib::Ipib;
|
||||||
use crate::se_img_comps::kernel::S390Kernel;
|
use crate::se_img_comps::kernel::S390Kernel;
|
||||||
@@ -33,9 +30,9 @@ use crate::se_img_comps::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct SeHdrArgs<'a> {
|
pub struct SeHdrArgs<'a> {
|
||||||
pub keys: &'a [PKey<Public>],
|
pub keys: &'a [HostKey],
|
||||||
pub pcf: &'a PlaintextControlFlagsV1,
|
pub pcf: &'a EffectiveControlFlags<SeHdrFlag>,
|
||||||
pub scf: &'a SecretControlFlagsV1,
|
pub scf: &'a EffectiveControlFlags<SeHdrFlag>,
|
||||||
pub cck: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
pub cck: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||||
pub hdr_aead_key: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
pub hdr_aead_key: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||||
pub psw_addr: &'a Option<u64>,
|
pub psw_addr: &'a Option<u64>,
|
||||||
@@ -95,7 +92,7 @@ impl<W: Write + Seek> SeImgBuilder<W> {
|
|||||||
|
|
||||||
/// Create a Secure Execution boot image builder
|
/// Create a Secure Execution boot image builder
|
||||||
#[allow(clippy::similar_names)]
|
#[allow(clippy::similar_names)]
|
||||||
pub(crate) fn new_v1(
|
pub(crate) fn new(
|
||||||
mut writer: W,
|
mut writer: W,
|
||||||
encryption: bool,
|
encryption: bool,
|
||||||
legacy_expected_se_hdr_size: Option<usize>,
|
legacy_expected_se_hdr_size: Option<usize>,
|
||||||
@@ -322,8 +319,25 @@ impl<W: Write + Seek> SeImgBuilder<W> {
|
|||||||
fn add_sehdr(&mut self, stage3b_entry: u64, sehdr_args: SeHdrArgs) -> Result<Rc<ImgComponent>> {
|
fn add_sehdr(&mut self, stage3b_entry: u64, sehdr_args: SeHdrArgs) -> Result<Rc<ImgComponent>> {
|
||||||
let meta = self.builder.finish()?;
|
let meta = self.builder.finish()?;
|
||||||
|
|
||||||
|
// Determine version from first key (all keys should be same version)
|
||||||
|
let version = match sehdr_args.keys.first() {
|
||||||
|
Some(HostKey::V1(_)) => SeHdrVersion::V1,
|
||||||
|
Some(HostKey::V2(_)) => SeHdrVersion::V2,
|
||||||
|
Some(_) => unreachable!("Unknown HostKey version"),
|
||||||
|
None => return Err(Error::NoHostkey.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify all hostkeys have the same version -> test if all or none are hybrid
|
||||||
|
let hybrid = matches!(version, SeHdrVersion::V2);
|
||||||
|
sehdr_args
|
||||||
|
.keys
|
||||||
|
.iter()
|
||||||
|
.all(|k| k.is_hybrid() == hybrid)
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(pv::Error::MixedHostkeyVersions)?;
|
||||||
|
|
||||||
let mut se_hdr_builder = SeHdrBuilder::new(
|
let mut se_hdr_builder = SeHdrBuilder::new(
|
||||||
SeHdrVersion::V1,
|
version,
|
||||||
PSW {
|
PSW {
|
||||||
addr: sehdr_args.psw_addr.unwrap_or(stage3b_entry),
|
addr: sehdr_args.psw_addr.unwrap_or(stage3b_entry),
|
||||||
mask: Self::DEFAULT_INITIAL_PSW_MASK,
|
mask: Self::DEFAULT_INITIAL_PSW_MASK,
|
||||||
@@ -497,7 +511,7 @@ mod tests {
|
|||||||
|
|
||||||
let encryption = true;
|
let encryption = true;
|
||||||
let mut writer = Cursor::new(Vec::new());
|
let mut writer = Cursor::new(Vec::new());
|
||||||
let ctx_res = SeImgBuilder::new_v1(&mut writer, encryption, None, None);
|
let ctx_res = SeImgBuilder::new(&mut writer, encryption, None, None);
|
||||||
assert!(ctx_res.is_ok());
|
assert!(ctx_res.is_ok());
|
||||||
let ctx = ctx_res.unwrap();
|
let ctx = ctx_res.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ use std::fmt::Display;
|
|||||||
use clap::error::ErrorKind::ValueValidation;
|
use clap::error::ErrorKind::ValueValidation;
|
||||||
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
|
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
|
||||||
use utils::{
|
use utils::{
|
||||||
combined_path_opt, combined_path_req, CertificateOptions, DeprecatedVerbosityOptions, STDOUT,
|
combined_path_opt, combined_path_req, AutoOrExplicit, CertificateOptions,
|
||||||
|
DeprecatedVerbosityOptions, HkdVersion, ValueEnumDisplay, ValueEnumFromStr, STDOUT,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Manage secrets for IBM Secure Execution guests.
|
/// Manage secrets for IBM Secure Execution guests.
|
||||||
@@ -34,6 +35,28 @@ pub enum CreateSecretFlags {
|
|||||||
DisableDump,
|
DisableDump,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Secure Execution add secret version for CLI
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)]
|
||||||
|
pub enum SecretVersion {
|
||||||
|
#[value(name = "1")]
|
||||||
|
/// Version 1 - uses traditional cryptographic keys
|
||||||
|
V1,
|
||||||
|
#[value(name = "2")]
|
||||||
|
/// Version 2 - uses hybrid (post-quantum) cryptographic keys
|
||||||
|
V2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SecretVersionSelection = AutoOrExplicit<SecretVersion>;
|
||||||
|
|
||||||
|
impl From<SecretVersion> for HkdVersion {
|
||||||
|
fn from(val: SecretVersion) -> Self {
|
||||||
|
match val {
|
||||||
|
SecretVersion::V1 => Self::Classical,
|
||||||
|
SecretVersion::V2 => Self::Hybrid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
#[command(group(ArgGroup::new("as-ext").args(["cck", "extension_secret"])),)]
|
#[command(group(ArgGroup::new("as-ext").args(["cck", "extension_secret"])),)]
|
||||||
pub struct CreateSecretOpt {
|
pub struct CreateSecretOpt {
|
||||||
@@ -173,6 +196,10 @@ pub struct CreateSecretOpt {
|
|||||||
/// Ignored for meta-secrets.
|
/// Ignored for meta-secrets.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub use_name: bool,
|
pub use_name: bool,
|
||||||
|
|
||||||
|
/// Specify the Add-secret version to use.
|
||||||
|
#[arg(long = "secret-version", value_name = "VERSION", default_value_t = SecretVersion::V1)]
|
||||||
|
pub secret_version: SecretVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub use create::create;
|
|||||||
mod verify;
|
mod verify;
|
||||||
pub use verify::verify;
|
pub use verify::verify;
|
||||||
|
|
||||||
pub const CMD_FN: &[&str] = &["+create", "+verify"];
|
pub const CMD_FN: &[&str] = &["+create", "+verify", "+quantumsafe"];
|
||||||
|
|
||||||
#[cfg(target_arch = "s390x")]
|
#[cfg(target_arch = "s390x")]
|
||||||
mod add;
|
mod add;
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ use serde_yaml::Value;
|
|||||||
use utils::get_writer_from_cli_file_arg;
|
use utils::get_writer_from_cli_file_arg;
|
||||||
use zerocopy::IntoBytes;
|
use zerocopy::IntoBytes;
|
||||||
|
|
||||||
use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt, RetrieveableSecretInpKind};
|
use crate::cli::{
|
||||||
|
AddSecretType, CreateSecretFlags, CreateSecretOpt, RetrieveableSecretInpKind, SecretVersion,
|
||||||
|
SecretVersionSelection,
|
||||||
|
};
|
||||||
|
|
||||||
fn write_out<P, D>(path: &P, data: D, ctx: &str) -> pv::Result<()>
|
fn write_out<P, D>(path: &P, data: D, ctx: &str) -> pv::Result<()>
|
||||||
where
|
where
|
||||||
@@ -83,6 +86,41 @@ fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Re
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Auto-detect the Add-secret version based on the host keys.
|
||||||
|
///
|
||||||
|
/// Returns Two if any host key is a hybrid key, otherwise returns V1.
|
||||||
|
fn auto_detect_version(host_keys: &[HostKey]) -> AddSecretVersion {
|
||||||
|
let use_hybrid_keys = host_keys.iter().any(|k: &HostKey| k.is_hybrid());
|
||||||
|
if use_hybrid_keys {
|
||||||
|
AddSecretVersion::Two
|
||||||
|
} else {
|
||||||
|
AddSecretVersion::One
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SecretVersion> for AddSecretVersion {
|
||||||
|
fn from(value: SecretVersion) -> Self {
|
||||||
|
match value {
|
||||||
|
SecretVersion::V1 => Self::One,
|
||||||
|
SecretVersion::V2 => Self::Two,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the attestation version to use.
|
||||||
|
///
|
||||||
|
/// If an explicit version is provided via CLI, use that.
|
||||||
|
/// Otherwise, auto-detect based on the host key types.
|
||||||
|
fn determine_version(
|
||||||
|
cli_version: SecretVersionSelection,
|
||||||
|
host_keys: &[HostKey],
|
||||||
|
) -> AddSecretVersion {
|
||||||
|
match cli_version {
|
||||||
|
SecretVersionSelection::Auto => auto_detect_version(host_keys),
|
||||||
|
SecretVersionSelection::Explicit(att_version) => att_version.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Prepare an add-secret request
|
/// Prepare an add-secret request
|
||||||
pub fn create(opt: &CreateSecretOpt) -> Result<()> {
|
pub fn create(opt: &CreateSecretOpt) -> Result<()> {
|
||||||
if pv_guest_bit_set() {
|
if pv_guest_bit_set() {
|
||||||
@@ -94,17 +132,9 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut asrcb = build_asrcb(opt)?;
|
let asrcb = build_asrcb(opt)?;
|
||||||
debug!("Generated Add-secret request");
|
debug!("Generated Add-secret request");
|
||||||
|
|
||||||
// Add host-key documents
|
|
||||||
opt.certificate_args
|
|
||||||
.get_verified_hkds("secret")?
|
|
||||||
.into_iter()
|
|
||||||
.for_each(|k| asrcb.add_hostkey(HostKey::V1(k)));
|
|
||||||
|
|
||||||
debug!("Added all host-keys");
|
|
||||||
|
|
||||||
// build + encrypt the request
|
// build + encrypt the request
|
||||||
let rq =
|
let rq =
|
||||||
ReqEncrCtx::random(SymKeyType::Aes256Gcm).context("Failed to generate random input")?;
|
ReqEncrCtx::random(SymKeyType::Aes256Gcm).context("Failed to generate random input")?;
|
||||||
@@ -168,9 +198,23 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
|
|||||||
debug!("FLAGS: {flags:x?}");
|
debug!("FLAGS: {flags:x?}");
|
||||||
|
|
||||||
let mut se_hdr = open_file(&opt.hdr)?;
|
let mut se_hdr = open_file(&opt.hdr)?;
|
||||||
let (tags, _) = BootHdrTags::from_se_image(&mut se_hdr)
|
let (boot_tags, _) = BootHdrTags::from_se_image(&mut se_hdr)
|
||||||
.with_context(|| format!("Provided SE-header in '{}' is malformed", &opt.hdr))?;
|
.with_context(|| format!("Provided SE-header in '{}' is malformed", &opt.hdr))?;
|
||||||
let mut asrcb = AddSecretRequest::new(AddSecretVersion::One, secret, tags, flags);
|
|
||||||
|
let hkds = opt.certificate_args.get_verified_hkds(
|
||||||
|
"secret",
|
||||||
|
SecretVersionSelection::Explicit(opt.secret_version).map(|v| v.into()),
|
||||||
|
)?;
|
||||||
|
let secret_version =
|
||||||
|
determine_version(SecretVersionSelection::Explicit(opt.secret_version), &hkds);
|
||||||
|
|
||||||
|
let mut asrcb = AddSecretRequest::new(secret_version, secret, boot_tags, flags)?;
|
||||||
|
|
||||||
|
for k in hkds.into_iter() {
|
||||||
|
asrcb.add_hostkey(k)?
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Added all host-keys");
|
||||||
|
|
||||||
// Set CUID
|
// Set CUID
|
||||||
read_cuid(&mut asrcb, opt)?;
|
read_cuid(&mut asrcb, opt)?;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
|
|
||||||
use clap::{CommandFactory, ValueEnum};
|
use clap::CommandFactory;
|
||||||
use clap_complete::{generate_to, Shell};
|
use clap_complete::{generate_to, Shell};
|
||||||
|
|
||||||
include!("src/cli.rs");
|
include!("src/cli.rs");
|
||||||
|
|||||||
@@ -3,11 +3,36 @@
|
|||||||
// Copyright IBM Corp. 2025
|
// Copyright IBM Corp. 2025
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser, ValueEnum};
|
||||||
use utils::CertificateOptions;
|
use utils::{
|
||||||
|
AutoOrExplicit, AutoOrExplicitParser, CertificateOptions, HkdVersion, ValueEnumDisplay,
|
||||||
|
ValueEnumFromStr,
|
||||||
|
};
|
||||||
|
|
||||||
static VERSION: OnceLock<String> = OnceLock::new();
|
static VERSION: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Secure Execution HostKey version for CLI
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, ValueEnumDisplay, ValueEnumFromStr)]
|
||||||
|
pub enum HostKeyVersion {
|
||||||
|
#[value(name = "1")]
|
||||||
|
/// Version 1 - uses traditional cryptographic keys
|
||||||
|
V1,
|
||||||
|
#[value(name = "2")]
|
||||||
|
/// Version 2 - uses hybrid (post-quantum) cryptographic keys
|
||||||
|
V2,
|
||||||
|
}
|
||||||
|
pub type HostKeyVersionSelection = AutoOrExplicit<HostKeyVersion>;
|
||||||
|
pub type HostKeyVersionSelectionParser = AutoOrExplicitParser<HostKeyVersion>;
|
||||||
|
|
||||||
|
impl From<HostKeyVersion> for HkdVersion {
|
||||||
|
fn from(val: HostKeyVersion) -> Self {
|
||||||
|
match val {
|
||||||
|
HostKeyVersion::V1 => Self::Classical,
|
||||||
|
HostKeyVersion::V2 => Self::Hybrid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(long_version=ver(), disable_version_flag(true))]
|
#[command(long_version=ver(), disable_version_flag(true))]
|
||||||
/// Tool to verify host-keys
|
/// Tool to verify host-keys
|
||||||
@@ -23,6 +48,10 @@ pub struct CliOptions {
|
|||||||
#[arg(long, action=ArgAction::Version)]
|
#[arg(long, action=ArgAction::Version)]
|
||||||
/// Print version information and exit.
|
/// Print version information and exit.
|
||||||
version: (),
|
version: (),
|
||||||
|
|
||||||
|
/// Specify the Host-key version to use.
|
||||||
|
#[arg(long = "hkd-version", value_name = "VERSION", default_value_t = HostKeyVersionSelection::Auto, value_parser = HostKeyVersionSelectionParser::default())]
|
||||||
|
pub hkd_version: HostKeyVersionSelection,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ver() -> &'static str {
|
fn ver() -> &'static str {
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ static LOGGER: PvLogger = PvLogger;
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
LOGGER.start(LevelFilter::Trace)?;
|
LOGGER.start(LevelFilter::Trace)?;
|
||||||
cli::CliOptions::parse()
|
let opt = cli::CliOptions::parse();
|
||||||
.certificate_args
|
opt.certificate_args
|
||||||
.get_verified_hkds("info")?;
|
.get_verified_hkds("info", opt.hkd_version.map(|v| v.into()))?;
|
||||||
info!("Host-key documents verified.");
|
info!("Host-key documents verified.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ chrono = { version = "0.4.44", default-features = false, features = ["std"] }
|
|||||||
clap = { version ="4.6", features = ["derive", "wrap_help"] }
|
clap = { version ="4.6", features = ["derive", "wrap_help"] }
|
||||||
libc = "0.2.186"
|
libc = "0.2.186"
|
||||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||||
|
openssl = "0.10"
|
||||||
pv = { path = "../pv", package = "s390_pv" }
|
pv = { path = "../pv", package = "s390_pv" }
|
||||||
serde = { version = "1.0.228"}
|
serde = { version = "1.0.228"}
|
||||||
|
|
||||||
|
utils_macros = { path = "../utils_macros" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
|||||||
+136
-30
@@ -1,17 +1,136 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
//
|
//
|
||||||
// Copyright IBM Corp. 2023, 2024
|
// Copyright IBM Corp.
|
||||||
|
use std::fmt::Display;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use clap::{ArgAction, ArgGroup, Args, Command, ValueHint};
|
use clap::builder::{EnumValueParser, PossibleValue, TypedValueParser};
|
||||||
use log::{info, warn, LevelFilter};
|
use clap::{Arg, ArgAction, ArgGroup, Args, Command, ValueEnum, ValueHint};
|
||||||
use pv::misc::{create_file, open_file, read_certs, read_file};
|
use log::LevelFilter;
|
||||||
use pv::request::openssl::pkey::{PKey, Public};
|
use pv::misc::{create_file, open_file};
|
||||||
use pv::request::HkdVerifier;
|
use pv::request::{HkdVerifier, HostKey};
|
||||||
use pv::{Error, Result};
|
use pv::Result;
|
||||||
|
|
||||||
|
use crate::hkd::{HkdLoader, HkdVersionSelection};
|
||||||
|
|
||||||
|
/// Generic version selection for CLI
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AutoOrExplicit<T> {
|
||||||
|
Auto,
|
||||||
|
Explicit(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Display for AutoOrExplicit<T>
|
||||||
|
where
|
||||||
|
T: Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
AutoOrExplicit::Auto => write!(f, "auto"),
|
||||||
|
AutoOrExplicit::Explicit(version) => write!(f, "{version}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AutoOrExplicit<T> {
|
||||||
|
pub fn map<U, F>(self, f: F) -> AutoOrExplicit<U>
|
||||||
|
where
|
||||||
|
F: FnOnce(T) -> U,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
AutoOrExplicit::Explicit(v) => AutoOrExplicit::Explicit(f(v)),
|
||||||
|
AutoOrExplicit::Auto => AutoOrExplicit::Auto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FromStr for AutoOrExplicit<T>
|
||||||
|
where
|
||||||
|
T: FromStr<Err = String>,
|
||||||
|
{
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"auto" => Ok(Self::Auto),
|
||||||
|
_ => {
|
||||||
|
let v = T::from_str(s)?;
|
||||||
|
Ok(Self::Explicit(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AutoOrExplicitParser<T> {
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for AutoOrExplicitParser<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AutoOrExplicitParser<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TypedValueParser for AutoOrExplicitParser<T>
|
||||||
|
where
|
||||||
|
T: ValueEnum + FromStr + Clone + Send + Sync + Display + 'static,
|
||||||
|
T::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
type Value = AutoOrExplicit<T>;
|
||||||
|
|
||||||
|
fn parse_ref(
|
||||||
|
&self,
|
||||||
|
cmd: &Command,
|
||||||
|
arg: Option<&Arg>,
|
||||||
|
value: &std::ffi::OsStr,
|
||||||
|
) -> Result<Self::Value, clap::error::Error> {
|
||||||
|
let s = value
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8).with_cmd(cmd))?;
|
||||||
|
|
||||||
|
if s == format!("{}", Self::Value::Auto) {
|
||||||
|
Ok(Self::Value::Auto)
|
||||||
|
} else {
|
||||||
|
let parsed = s.parse::<T>().map_err(|_e| {
|
||||||
|
let mut err =
|
||||||
|
clap::error::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
||||||
|
if let Some(arg) = arg {
|
||||||
|
err.insert(
|
||||||
|
clap::error::ContextKind::InvalidArg,
|
||||||
|
clap::error::ContextValue::String(arg.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
err.insert(
|
||||||
|
clap::error::ContextKind::InvalidValue,
|
||||||
|
clap::error::ContextValue::String(s.to_string()),
|
||||||
|
);
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self::Value::Explicit(parsed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||||
|
let enum_parser = EnumValueParser::<T>::new();
|
||||||
|
let mut values = vec![PossibleValue::new("auto")];
|
||||||
|
values.extend(enum_parser.possible_values()?);
|
||||||
|
|
||||||
|
Some(Box::new(values.into_iter()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// CLI Argument collection for handling host-keys, IBM signing keys, and certificates.
|
/// CLI Argument collection for handling host-keys, IBM signing keys, and certificates.
|
||||||
#[derive(Args, Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Args, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
@@ -108,40 +227,27 @@ impl CertificateOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the host-keys specified and verifies them if required
|
/// Read the hybrid host-keys specified and verifies them if required
|
||||||
///
|
///
|
||||||
/// - `protectee`: what you want to create. e.g. add-secret request or SE-image
|
/// - `protectee`: what you want to create. e.g. add-secret request or SE-image
|
||||||
|
/// - `version`: requested host-key document version
|
||||||
///
|
///
|
||||||
/// # Error
|
/// # Error
|
||||||
/// Returns an error if something went wrong during parsing the HKDs, the verification chain
|
/// Returns an error if something went wrong during parsing the HKDs, the verification chain
|
||||||
/// could not built, or when the verification
|
/// could not built, or when the verification
|
||||||
/// failed.
|
/// failed.
|
||||||
pub fn get_verified_hkds(&self, protectee: &'static str) -> Result<Vec<PKey<Public>>> {
|
pub fn get_verified_hkds(
|
||||||
|
&self,
|
||||||
|
protectee: &'static str,
|
||||||
|
requested_version: HkdVersionSelection,
|
||||||
|
) -> Result<Vec<HostKey>> {
|
||||||
let hkds = &self.host_key_documents;
|
let hkds = &self.host_key_documents;
|
||||||
let verifier = self.verifier(protectee)?;
|
let verifier = self.verifier(protectee)?;
|
||||||
|
|
||||||
let mut res = Vec::with_capacity(hkds.len());
|
let mut res = Vec::with_capacity(hkds.len());
|
||||||
for hkd in hkds {
|
for hkd in hkds {
|
||||||
let hk = read_file(hkd, "host-key document")?;
|
let host_key = HkdLoader::load_and_verify(hkd, verifier.as_ref(), requested_version)?;
|
||||||
let certs = read_certs(&hk).map_err(|source| Error::HkdNotPemOrDer {
|
res.push(host_key);
|
||||||
hkd: hkd.display().to_string(),
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
if certs.is_empty() {
|
|
||||||
return Err(Error::NoHkdInFile(hkd.display().to_string()));
|
|
||||||
}
|
|
||||||
if certs.len() != 1 {
|
|
||||||
warn!(
|
|
||||||
"The host-key document in '{}' contains more than one certificate!",
|
|
||||||
hkd.display()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic: len is == 1 -> unwrap will succeed/not panic
|
|
||||||
let c = certs.first().unwrap();
|
|
||||||
verifier.verify(c)?;
|
|
||||||
res.push(c.public_key()?);
|
|
||||||
info!("Use host-key document at '{}'", hkd.display());
|
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp.
|
||||||
|
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use log::{error, info};
|
||||||
|
use openssl::nid::Nid;
|
||||||
|
use openssl::pkey::{Id, KeyType, PKeyRef, Public};
|
||||||
|
use openssl::x509::X509;
|
||||||
|
use pv::misc::{read_certs, read_file};
|
||||||
|
use pv::request::{HkdVerifier, HostKey, HybridPKey};
|
||||||
|
use pv::{Error, Result};
|
||||||
|
|
||||||
|
use crate::AutoOrExplicit;
|
||||||
|
|
||||||
|
/// Host key document version
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum HkdVersion {
|
||||||
|
/// Version 1 - uses traditional cryptographic keys (1 certificate)
|
||||||
|
Classical,
|
||||||
|
/// Version 2 - uses hybrid (post-quantum) cryptographic keys (2 certificates)
|
||||||
|
Hybrid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HkdVersion {
|
||||||
|
/// Get the required certificate count for this version
|
||||||
|
pub fn cert_count(self) -> usize {
|
||||||
|
match self {
|
||||||
|
HkdVersion::Classical => 1,
|
||||||
|
HkdVersion::Hybrid => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for HkdVersion {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
HkdVersion::Classical => write!(f, "classical"),
|
||||||
|
HkdVersion::Hybrid => write!(f, "hybrid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type HkdVersionSelection = AutoOrExplicit<HkdVersion>;
|
||||||
|
|
||||||
|
/// Helper struct for loading and verifying host-key documents
|
||||||
|
pub struct HkdLoader;
|
||||||
|
|
||||||
|
impl HkdLoader {
|
||||||
|
fn detect_version(path: &Path, certs: &Vec<X509>) -> Result<HkdVersion> {
|
||||||
|
info!("Auto-detecting version of the host-key document format");
|
||||||
|
Ok(match certs.len() {
|
||||||
|
1 => HkdVersion::Classical,
|
||||||
|
2 => HkdVersion::Hybrid,
|
||||||
|
_ => {
|
||||||
|
error!(
|
||||||
|
"Invalid host-key document '{}': it contains more than two certificates, which is not supported by any host-key document format.",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
return Err(Error::WrongNumberOfKeys(path.display().to_string()));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_version(path: &Path, certs: &Vec<X509>, version: HkdVersion) -> Result<HkdVersion> {
|
||||||
|
if certs.len() != version.cert_count() {
|
||||||
|
error!(
|
||||||
|
"Host-key document '{}' is not a {} host-key document.",
|
||||||
|
path.display(),
|
||||||
|
version,
|
||||||
|
);
|
||||||
|
return Err(Error::WrongNumberOfKeys(path.display().to_string()));
|
||||||
|
}
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ec_p521_key(key: &PKeyRef<Public>) -> bool {
|
||||||
|
if key.id() == Id::EC {
|
||||||
|
if let Ok(ec_key) = key.ec_key() {
|
||||||
|
let group = ec_key.group();
|
||||||
|
if let Some(curve_nid) = group.curve_name() {
|
||||||
|
return curve_nid == Nid::SECP521R1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_mlkem1024_key(key: &PKeyRef<Public>) -> bool {
|
||||||
|
key.is_a(KeyType::ML_KEM_1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load and verify a host-key document from a file
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if:
|
||||||
|
/// - The file cannot be read
|
||||||
|
/// - The content is not valid PEM or DER format
|
||||||
|
/// - The file contains no certificates or wrong number of certificates
|
||||||
|
/// - The public key cannot be extracted from the certificate(s)
|
||||||
|
/// - The verification fails
|
||||||
|
/// - Key types are invalid
|
||||||
|
pub fn load_and_verify<P: AsRef<Path>>(
|
||||||
|
path: P,
|
||||||
|
verifier: &dyn HkdVerifier,
|
||||||
|
requested_version: HkdVersionSelection,
|
||||||
|
) -> Result<HostKey> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let hk = read_file(path, "host-key document")?;
|
||||||
|
let certs = read_certs(&hk).map_err(|source| Error::HkdNotPemOrDer {
|
||||||
|
hkd: path.display().to_string(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if certs.is_empty() {
|
||||||
|
return Err(Error::NoHkdInFile(path.display().to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let version = match requested_version {
|
||||||
|
HkdVersionSelection::Auto => Self::detect_version(path, &certs)?,
|
||||||
|
HkdVersionSelection::Explicit(version) => {
|
||||||
|
Self::validate_version(path, &certs, version)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Using {version} host-key document format");
|
||||||
|
|
||||||
|
// SAFETY: certs is guaranteed to be non-empty due to the check
|
||||||
|
let c1 = certs
|
||||||
|
.first()
|
||||||
|
.expect("Certificate list validated as non-empty");
|
||||||
|
|
||||||
|
if !Self::is_ec_p521_key(c1.public_key()?.as_ref()) {
|
||||||
|
return Err(Error::InvalidHkd(
|
||||||
|
"First key must be a EC-p521 key".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
verifier.verify(c1)?;
|
||||||
|
|
||||||
|
match version {
|
||||||
|
HkdVersion::Classical => Ok(HostKey::V1(c1.public_key()?)),
|
||||||
|
HkdVersion::Hybrid => {
|
||||||
|
let c2 = &certs
|
||||||
|
.get(1)
|
||||||
|
.expect("Certificate list length was already checked");
|
||||||
|
if !Self::is_mlkem1024_key(c2.public_key()?.as_ref()) {
|
||||||
|
return Err(Error::InvalidHkd(
|
||||||
|
"Second key must be a ML-KEM 1024 key".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
verifier.verify(c2)?;
|
||||||
|
Ok(HostKey::V2(HybridPKey::new(
|
||||||
|
c1.public_key()?,
|
||||||
|
c2.public_key()?,
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,21 +7,26 @@ mod cli;
|
|||||||
mod exit_code;
|
mod exit_code;
|
||||||
mod file;
|
mod file;
|
||||||
mod hexslice;
|
mod hexslice;
|
||||||
|
pub mod hkd;
|
||||||
mod hostname;
|
mod hostname;
|
||||||
mod json;
|
mod json;
|
||||||
mod log;
|
mod log;
|
||||||
mod tmpfile;
|
mod tmpfile;
|
||||||
|
|
||||||
pub use ::log::LevelFilter;
|
pub use ::log::LevelFilter;
|
||||||
|
// Re-export procedural macros from utils_macros
|
||||||
|
pub use utils_macros::{ControlFlag, ValueEnumDisplay, ValueEnumFromStr};
|
||||||
|
|
||||||
pub use crate::cli::{
|
pub use crate::cli::{
|
||||||
combined_path_opt, combined_path_req, get_reader_from_cli_file_arg,
|
combined_path_opt, combined_path_req, get_reader_from_cli_file_arg,
|
||||||
get_writer_from_cli_file_arg, print_cli_error, print_error, CertificateOptions,
|
get_writer_from_cli_file_arg, print_cli_error, print_error, AutoOrExplicit,
|
||||||
DeprecatedVerbosityOptions, VerbosityOptions, STDIN, STDOUT,
|
AutoOrExplicitParser, CertificateOptions, DeprecatedVerbosityOptions, VerbosityOptions, STDIN,
|
||||||
|
STDOUT,
|
||||||
};
|
};
|
||||||
pub use crate::exit_code::{docstring, ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc};
|
pub use crate::exit_code::{docstring, ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc};
|
||||||
pub use crate::file::{AtomicFile, AtomicFileOperation};
|
pub use crate::file::{AtomicFile, AtomicFileOperation};
|
||||||
pub use crate::hexslice::HexSlice;
|
pub use crate::hexslice::HexSlice;
|
||||||
|
pub use crate::hkd::{HkdLoader, HkdVersion, HkdVersionSelection};
|
||||||
pub use crate::hostname::gethostname;
|
pub use crate::hostname::gethostname;
|
||||||
pub use crate::json::S390ToolsMetaData;
|
pub use crate::json::S390ToolsMetaData;
|
||||||
pub use crate::log::PvLogger;
|
pub use crate::log::PvLogger;
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp.
|
||||||
|
|
||||||
|
//! Integration tests for malformed host key document handling
|
||||||
|
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use pv::request::NoVerifyHkd;
|
||||||
|
use utils::{AutoOrExplicit, HkdLoader, HkdVersion, TemporaryDirectory};
|
||||||
|
|
||||||
|
/// Path to test certificate assets
|
||||||
|
fn cert_asset_path(name: &str) -> PathBuf {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("pv/tests/assets/cert")
|
||||||
|
.join(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_hkd_file() {
|
||||||
|
let temp_dir = TemporaryDirectory::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("empty.hkd");
|
||||||
|
File::create(&file_path).unwrap();
|
||||||
|
|
||||||
|
let result = HkdLoader::load_and_verify(&file_path, &NoVerifyHkd, AutoOrExplicit::Auto);
|
||||||
|
assert!(result.is_err(), "Empty HKD file should be rejected");
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(
|
||||||
|
matches!(err, pv::Error::NoHkdInFile(_)),
|
||||||
|
"Expected NoHkdInFile error, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_pem_format() {
|
||||||
|
// Create a file with invalid PEM content
|
||||||
|
let temp_dir = TemporaryDirectory::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("invalid.hkd");
|
||||||
|
let mut temp_file = File::create(&file_path).unwrap();
|
||||||
|
writeln!(temp_file, "-----BEGIN CERTIFICATE-----").unwrap();
|
||||||
|
writeln!(temp_file, "INVALID_BASE64_CONTENT!!!").unwrap();
|
||||||
|
writeln!(temp_file, "-----END CERTIFICATE-----").unwrap();
|
||||||
|
temp_file.flush().unwrap();
|
||||||
|
|
||||||
|
// Test that invalid PEM is properly rejected with HkdNotPemOrDer error
|
||||||
|
let result = HkdLoader::load_and_verify(&file_path, &NoVerifyHkd, AutoOrExplicit::Auto);
|
||||||
|
assert!(result.is_err(), "Invalid PEM format should be rejected");
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(
|
||||||
|
matches!(err, pv::Error::HkdNotPemOrDer { .. }),
|
||||||
|
"Expected HkdNotPemOrDer error, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_number_of_certificates_v1() {
|
||||||
|
// Create a file with three valid certificates (invalid count for v1, expects 1)
|
||||||
|
let temp_dir = TemporaryDirectory::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("wrong_count_v1.hkd");
|
||||||
|
let mut temp_file = File::create(&file_path).unwrap();
|
||||||
|
|
||||||
|
let cert1 = fs::read_to_string(cert_asset_path("host.crt")).unwrap();
|
||||||
|
let cert2 = fs::read_to_string(cert_asset_path("ibm.crt")).unwrap();
|
||||||
|
let cert3 = fs::read_to_string(cert_asset_path("root_ca.crt")).unwrap();
|
||||||
|
|
||||||
|
write!(temp_file, "{}{}{}", cert1, cert2, cert3).unwrap();
|
||||||
|
temp_file.flush().unwrap();
|
||||||
|
|
||||||
|
let result = HkdLoader::load_and_verify(
|
||||||
|
&file_path,
|
||||||
|
&NoVerifyHkd,
|
||||||
|
AutoOrExplicit::Explicit(HkdVersion::Classical),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Wrong number of certificates for v1 should be rejected"
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(
|
||||||
|
matches!(err, pv::Error::WrongNumberOfKeys(_)),
|
||||||
|
"Expected WrongNumberOfKeys error, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_number_of_certificates_v2() {
|
||||||
|
// Use existing single certificate file directly (invalid count for v2, expects 2)
|
||||||
|
let file_path = cert_asset_path("host.crt");
|
||||||
|
|
||||||
|
// Test that wrong number of certificates is properly rejected with WrongNumberOfKeys error
|
||||||
|
let result = HkdLoader::load_and_verify(
|
||||||
|
&file_path,
|
||||||
|
&NoVerifyHkd,
|
||||||
|
AutoOrExplicit::Explicit(HkdVersion::Hybrid),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Wrong number of certificates for v2 should be rejected"
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(
|
||||||
|
matches!(err, pv::Error::WrongNumberOfKeys(_)),
|
||||||
|
"Expected WrongNumberOfKeys error, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_corrupted_certificate() {
|
||||||
|
// Create a file with corrupted certificate data
|
||||||
|
let temp_dir = TemporaryDirectory::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("corrupted.hkd");
|
||||||
|
let mut temp_file = File::create(&file_path).unwrap();
|
||||||
|
writeln!(temp_file, "-----BEGIN CERTIFICATE-----").unwrap();
|
||||||
|
writeln!(
|
||||||
|
temp_file,
|
||||||
|
"MIICdTCCAd6gAwIBAgIBADANBgkqhkiG9w0BAQsFADBQMQswCQYDVQQGEwJVUzEL"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(temp_file, "CORRUPTED_DATA_HERE").unwrap();
|
||||||
|
writeln!(temp_file, "-----END CERTIFICATE-----").unwrap();
|
||||||
|
temp_file.flush().unwrap();
|
||||||
|
|
||||||
|
let result = HkdLoader::load_and_verify(&file_path, &NoVerifyHkd, AutoOrExplicit::Auto);
|
||||||
|
assert!(result.is_err(), "Corrupted certificate should be rejected");
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(
|
||||||
|
matches!(err, pv::Error::HkdNotPemOrDer { .. }),
|
||||||
|
"Expected HkdNotPemOrDer error, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_key_type_v1() {
|
||||||
|
// Use RSA certificate instead of EC-p521 for v1 (should fail)
|
||||||
|
let file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("pv/tests/assets/keys/rsa2048.crt");
|
||||||
|
|
||||||
|
let result = HkdLoader::load_and_verify(
|
||||||
|
&file_path,
|
||||||
|
&NoVerifyHkd,
|
||||||
|
AutoOrExplicit::Explicit(HkdVersion::Classical),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"RSA key should be rejected for v1 (expects EC-p521)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
if let pv::Error::InvalidHkd(msg) = err {
|
||||||
|
assert_eq!(
|
||||||
|
msg, "First key must be a EC-p521 key",
|
||||||
|
"Error message should indicate EC-p521 requirement"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Expected InvalidHkd error for wrong key type, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_key_type_v2() {
|
||||||
|
// Create a file with two RSA certificates instead of EC-p521 + ML-KEM for v2
|
||||||
|
let temp_dir = TemporaryDirectory::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("wrong_key_v2.hkd");
|
||||||
|
let mut temp_file = File::create(&file_path).unwrap();
|
||||||
|
|
||||||
|
// Read two RSA certificates and concatenate them
|
||||||
|
let rsa_cert_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("pv/tests/assets/keys/rsa2048.crt");
|
||||||
|
let rsa_cert = fs::read_to_string(&rsa_cert_path).unwrap();
|
||||||
|
|
||||||
|
write!(temp_file, "{}{}", rsa_cert, rsa_cert).unwrap();
|
||||||
|
temp_file.flush().unwrap();
|
||||||
|
|
||||||
|
// Test that wrong key types are properly rejected with InvalidHkd error
|
||||||
|
let result = HkdLoader::load_and_verify(
|
||||||
|
&file_path,
|
||||||
|
&NoVerifyHkd,
|
||||||
|
AutoOrExplicit::Explicit(HkdVersion::Hybrid),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"RSA keys should be rejected for v2 (expects EC-p521 + ML-KEM)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
if let pv::Error::InvalidHkd(msg) = err {
|
||||||
|
assert_eq!(
|
||||||
|
msg, "First key must be a EC-p521 key",
|
||||||
|
"Error message should indicate EC-p521 requirement"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Expected InvalidHkd error for wrong key types, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_second_key_type_v2() {
|
||||||
|
// Create a file with EC-p521 + EC (wrong) instead of EC-p521 + ML-KEM for v2
|
||||||
|
let temp_dir = TemporaryDirectory::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("wrong_second_key_v2.hkd");
|
||||||
|
let mut temp_file = File::create(&file_path).unwrap();
|
||||||
|
|
||||||
|
let ec_p521_cert = fs::read_to_string(cert_asset_path("host.crt")).unwrap();
|
||||||
|
let ec_cert_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("pv/tests/assets/keys/host.ec.crt");
|
||||||
|
let ec_cert = fs::read_to_string(&ec_cert_path).unwrap();
|
||||||
|
|
||||||
|
write!(temp_file, "{}{}", ec_p521_cert, ec_cert).unwrap();
|
||||||
|
temp_file.flush().unwrap();
|
||||||
|
|
||||||
|
let result = HkdLoader::load_and_verify(
|
||||||
|
&file_path,
|
||||||
|
&NoVerifyHkd,
|
||||||
|
AutoOrExplicit::Explicit(HkdVersion::Hybrid),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"EC key should be rejected as second key for v2 (expects ML-KEM-1024)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
if let pv::Error::InvalidHkd(msg) = err {
|
||||||
|
assert_eq!(
|
||||||
|
msg, "Second key must be a ML-KEM 1024 key",
|
||||||
|
"Error message should indicate ML-KEM-1024 requirement"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Expected InvalidHkd error for wrong second key type, got: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "utils_macros"
|
||||||
|
version = "0.12.0"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||||
|
quote = "1.0"
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
clap = { version = "4.6", features = ["derive"] }
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp.
|
||||||
|
|
||||||
|
//! Procedural macros for the utils crate.
|
||||||
|
//!
|
||||||
|
//! This crate provides derive macros to reduce boilerplate in enum definitions,
|
||||||
|
//! particularly for control flags.
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, Data, DeriveInput, Fields, Lit, Meta, MetaList};
|
||||||
|
|
||||||
|
/// Derive macro for control flag enums.
|
||||||
|
///
|
||||||
|
/// This macro generates implementations for `Display`, `IntoEnumIterator`, and `ControlFlagTrait`.
|
||||||
|
/// It supports the `#[flag(display = "...", value = N)]` attribute to specify custom display
|
||||||
|
/// strings and discriminant values.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use utils_macros::ControlFlag;
|
||||||
|
///
|
||||||
|
/// /// Trait for enums that can be iterated over.
|
||||||
|
/// pub trait IntoEnumIterator: Sized {
|
||||||
|
/// /// Returns an iterator over all variants of the enum.
|
||||||
|
/// fn iter() -> impl Iterator<Item = Self>;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// Trait for control flags that provide bit position information.
|
||||||
|
/// pub trait ControlFlagTrait {
|
||||||
|
/// /// Returns the bit position for this flag.
|
||||||
|
/// fn bit_position(self) -> u8;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(ControlFlag)]
|
||||||
|
/// pub enum PcfV1 {
|
||||||
|
/// #[flag(display = "Confidential dump support", value = 34)]
|
||||||
|
/// ConfidentialDump,
|
||||||
|
///
|
||||||
|
/// #[flag(display = "V1-specific flag", value = 35)]
|
||||||
|
/// V1OnlyFlag,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_derive(ControlFlag, attributes(flag))]
|
||||||
|
pub fn derive_control_flag(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = &input.ident;
|
||||||
|
|
||||||
|
let variants = match &input.data {
|
||||||
|
Data::Enum(data) => &data.variants,
|
||||||
|
_ => panic!("ControlFlag can only be derived for enums"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract variant information
|
||||||
|
let mut variant_names = Vec::new();
|
||||||
|
let mut variant_displays = Vec::new();
|
||||||
|
let mut variant_values = Vec::new();
|
||||||
|
|
||||||
|
for variant in variants {
|
||||||
|
if !matches!(variant.fields, Fields::Unit) {
|
||||||
|
panic!("ControlFlag only supports unit variants");
|
||||||
|
}
|
||||||
|
|
||||||
|
let variant_name = &variant.ident;
|
||||||
|
variant_names.push(variant_name);
|
||||||
|
|
||||||
|
// Parse the #[flag(...)] attribute
|
||||||
|
let mut display_str = variant_name.to_string();
|
||||||
|
let mut value: Option<u8> = None;
|
||||||
|
|
||||||
|
for attr in &variant.attrs {
|
||||||
|
if attr.path().is_ident("flag") {
|
||||||
|
// Try to parse as MetaList
|
||||||
|
if let Meta::List(MetaList { tokens, .. }) = &attr.meta {
|
||||||
|
// Parse the tokens inside the list
|
||||||
|
let parser = syn::meta::parser(|meta| {
|
||||||
|
if meta.path.is_ident("display") {
|
||||||
|
let val = meta.value()?;
|
||||||
|
let lit: Lit = val.parse()?;
|
||||||
|
if let Lit::Str(s) = lit {
|
||||||
|
display_str = s.value();
|
||||||
|
}
|
||||||
|
} else if meta.path.is_ident("value") {
|
||||||
|
let val = meta.value()?;
|
||||||
|
let lit: Lit = val.parse()?;
|
||||||
|
if let Lit::Int(i) = lit {
|
||||||
|
value = Some(i.base10_parse()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = syn::parse::Parser::parse2(parser, tokens.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.is_none() {
|
||||||
|
panic!(
|
||||||
|
"ControlFlag variant {} must have a value attribute",
|
||||||
|
variant_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
variant_displays.push(display_str);
|
||||||
|
variant_values.push(value.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl #name {
|
||||||
|
/// Returns the bit position value for this flag.
|
||||||
|
pub const fn flag_value(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
#(Self::#variant_names => #variant_values),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlFlagTrait for #name {
|
||||||
|
fn bit_position(self) -> u8 {
|
||||||
|
self.flag_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for #name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
#(Self::#variant_names => #variant_displays),*
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoEnumIterator for #name {
|
||||||
|
fn iter() -> impl Iterator<Item = Self> {
|
||||||
|
[
|
||||||
|
#(Self::#variant_names),*
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive `std::fmt::Display` for enums implementing `clap::ValueEnum`.
|
||||||
|
///
|
||||||
|
/// This macro generates a `Display` implementation that delegates to
|
||||||
|
/// `ValueEnum::to_possible_value()`, ensuring that the formatted output
|
||||||
|
/// matches the CLI representation used by clap (e.g. for help text,
|
||||||
|
/// completions, and parsing).
|
||||||
|
///
|
||||||
|
/// # Behavior
|
||||||
|
///
|
||||||
|
/// - Uses the canonical CLI name of each variant (as defined by `#[value(name = "...")]` or the
|
||||||
|
/// default casing).
|
||||||
|
/// - Fails at runtime if a variant is marked with `#[value(skip)]` and therefore has no CLI
|
||||||
|
/// representation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use clap::ValueEnum;
|
||||||
|
/// use utils_macros::ValueEnumDisplay;
|
||||||
|
///
|
||||||
|
/// #[derive(ValueEnum, ValueEnumDisplay, Clone)]
|
||||||
|
/// enum Mode {
|
||||||
|
/// #[value(name = "very-fast")]
|
||||||
|
/// Fast,
|
||||||
|
///
|
||||||
|
/// #[value(name = "slow")]
|
||||||
|
/// Slow,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(Mode::Fast.to_string(), "very-fast");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Rationale
|
||||||
|
///
|
||||||
|
/// clap requires `Display` for features like `default_value_t`. However,
|
||||||
|
/// `ValueEnum` already defines the canonical string representation via
|
||||||
|
/// `to_possible_value()`. This derive avoids duplicating those strings
|
||||||
|
/// and guarantees consistency between parsing, help output, and display.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if called on a variant with `#[value(skip)]`, as such variants
|
||||||
|
/// have no associated CLI representation.
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// - [`clap::ValueEnum`]
|
||||||
|
/// - [`clap::builder::PossibleValue`]
|
||||||
|
#[proc_macro_derive(ValueEnumDisplay)]
|
||||||
|
pub fn derive_value_enum_display(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = input.ident;
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl std::fmt::Display for #name {
|
||||||
|
fn fmt(
|
||||||
|
&self,
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let value = self
|
||||||
|
.to_possible_value()
|
||||||
|
.expect("skipped ValueEnum variant cannot be displayed");
|
||||||
|
|
||||||
|
write!(f, "{}", value.get_name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derives a `std::str::FromStr` implementation for enums implementing
|
||||||
|
/// [`clap::ValueEnum`].
|
||||||
|
///
|
||||||
|
/// This macro generates a `FromStr` implementation that delegates to
|
||||||
|
/// [`ValueEnum::from_str`], ensuring that parsing behavior is identical
|
||||||
|
/// to clap's CLI parsing.
|
||||||
|
///
|
||||||
|
/// # Behavior
|
||||||
|
///
|
||||||
|
/// - Parses input strings using the canonical CLI representation defined by `ValueEnum` (including
|
||||||
|
/// `#[value(name = "...")]` and aliases).
|
||||||
|
/// - Supports the same parsing semantics as clap (e.g. case sensitivity, if enabled).
|
||||||
|
/// - Returns a human-readable error if parsing fails.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use clap::ValueEnum;
|
||||||
|
/// use utils_macros::ValueEnumFromStr;
|
||||||
|
///
|
||||||
|
/// #[derive(ValueEnum, ValueEnumFromStr, Clone, Debug, PartialEq)]
|
||||||
|
/// enum Mode {
|
||||||
|
/// #[value(name = "fast")]
|
||||||
|
/// Fast,
|
||||||
|
///
|
||||||
|
/// #[value(name = "slow")]
|
||||||
|
/// Slow,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!("fast".parse::<Mode>().unwrap(), Mode::Fast);
|
||||||
|
/// assert!("invalid".parse::<Mode>().is_err());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Rationale
|
||||||
|
///
|
||||||
|
/// clap's [`ValueEnum`] trait already defines the canonical mapping
|
||||||
|
/// between strings and enum variants. This derive avoids duplicating
|
||||||
|
/// that logic in manual `FromStr` implementations and guarantees that
|
||||||
|
/// CLI parsing and programmatic parsing remain consistent.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the input does not match any of the allowed values
|
||||||
|
/// defined by `ValueEnum`.
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// - [`clap::ValueEnum`]
|
||||||
|
/// - [`std::str::FromStr`]
|
||||||
|
#[proc_macro_derive(ValueEnumFromStr)]
|
||||||
|
pub fn derive_value_enum_from_str(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = input.ident;
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl std::str::FromStr for #name {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
<Self as clap::ValueEnum>::from_str(s, false).map_err(|_| {
|
||||||
|
let possible = <Self as clap::ValueEnum>::value_variants()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.to_possible_value())
|
||||||
|
.map(|v| v.get_name().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
format!("invalid value '{}', expected one of: {}", s, possible)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp.
|
||||||
|
|
||||||
|
//! Integration tests for the ControlFlag derive macro.
|
||||||
|
|
||||||
|
mod test_helpers;
|
||||||
|
|
||||||
|
use test_helpers::{ControlFlagTrait, IntoEnumIterator};
|
||||||
|
use utils_macros::ControlFlag;
|
||||||
|
|
||||||
|
// Test enum for basic functionality
|
||||||
|
#[derive(ControlFlag, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum TestFlag {
|
||||||
|
#[flag(display = "first flag", value = 1)]
|
||||||
|
First,
|
||||||
|
#[flag(display = "second flag", value = 2)]
|
||||||
|
Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test enum with multiple variants
|
||||||
|
#[derive(ControlFlag, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum MultiFlag {
|
||||||
|
#[flag(display = "flag 1", value = 10)]
|
||||||
|
Flag1,
|
||||||
|
#[flag(display = "flag 2", value = 20)]
|
||||||
|
Flag2,
|
||||||
|
#[flag(display = "flag 3", value = 30)]
|
||||||
|
Flag3,
|
||||||
|
#[flag(display = "flag 4", value = 40)]
|
||||||
|
Flag4,
|
||||||
|
#[flag(display = "flag 5", value = 50)]
|
||||||
|
Flag5,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test enum with non-sequential values
|
||||||
|
#[derive(ControlFlag, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum SparseFlag {
|
||||||
|
#[flag(display = "low bit", value = 1)]
|
||||||
|
Low,
|
||||||
|
#[flag(display = "high bit", value = 63)]
|
||||||
|
High,
|
||||||
|
#[flag(display = "middle bit", value = 32)]
|
||||||
|
Middle,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test enum with edge case display strings
|
||||||
|
#[derive(ControlFlag, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum EdgeCaseFlag {
|
||||||
|
#[flag(display = "simple", value = 1)]
|
||||||
|
Simple,
|
||||||
|
#[flag(display = "with spaces and punctuation!", value = 2)]
|
||||||
|
WithSpaces,
|
||||||
|
#[flag(display = "UPPERCASE", value = 3)]
|
||||||
|
Uppercase,
|
||||||
|
#[flag(display = "with-dashes-and_underscores", value = 4)]
|
||||||
|
WithDashes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test enum for Copy/Clone compatibility
|
||||||
|
#[derive(ControlFlag, Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
enum CopyableFlag {
|
||||||
|
#[flag(display = "copyable", value = 1)]
|
||||||
|
Copyable,
|
||||||
|
#[flag(display = "another", value = 2)]
|
||||||
|
Another,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_derive() {
|
||||||
|
// Verify the macro successfully derives all required traits
|
||||||
|
let flag = TestFlag::First;
|
||||||
|
|
||||||
|
// Should compile and be accessible
|
||||||
|
let _ = flag.flag_value();
|
||||||
|
let _ = flag.bit_position();
|
||||||
|
let _ = format!("{}", flag);
|
||||||
|
let _ = TestFlag::iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display_trait() {
|
||||||
|
// Verify custom display strings are correctly used
|
||||||
|
assert_eq!(format!("{}", TestFlag::First), "first flag");
|
||||||
|
assert_eq!(format!("{}", TestFlag::Second), "second flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enum_iterator() {
|
||||||
|
// Verify iteration over all enum variants works correctly
|
||||||
|
let flags: Vec<TestFlag> = TestFlag::iter().collect();
|
||||||
|
assert_eq!(flags.len(), 2);
|
||||||
|
assert_eq!(flags[0], TestFlag::First);
|
||||||
|
assert_eq!(flags[1], TestFlag::Second);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_control_flag_trait() {
|
||||||
|
// Verify bit_position() returns correct values
|
||||||
|
assert_eq!(TestFlag::First.bit_position(), 1);
|
||||||
|
assert_eq!(TestFlag::Second.bit_position(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_flag_value_method() {
|
||||||
|
// Verify flag_value() returns correct bit positions
|
||||||
|
assert_eq!(TestFlag::First.flag_value(), 1);
|
||||||
|
assert_eq!(TestFlag::Second.flag_value(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_variants() {
|
||||||
|
// Verify the macro handles enums with many variants
|
||||||
|
|
||||||
|
// Test iteration
|
||||||
|
let flags: Vec<MultiFlag> = MultiFlag::iter().collect();
|
||||||
|
assert_eq!(flags.len(), 5);
|
||||||
|
assert_eq!(flags[0], MultiFlag::Flag1);
|
||||||
|
assert_eq!(flags[1], MultiFlag::Flag2);
|
||||||
|
assert_eq!(flags[2], MultiFlag::Flag3);
|
||||||
|
assert_eq!(flags[3], MultiFlag::Flag4);
|
||||||
|
assert_eq!(flags[4], MultiFlag::Flag5);
|
||||||
|
|
||||||
|
// Test bit positions
|
||||||
|
assert_eq!(MultiFlag::Flag1.bit_position(), 10);
|
||||||
|
assert_eq!(MultiFlag::Flag2.bit_position(), 20);
|
||||||
|
assert_eq!(MultiFlag::Flag3.bit_position(), 30);
|
||||||
|
assert_eq!(MultiFlag::Flag4.bit_position(), 40);
|
||||||
|
assert_eq!(MultiFlag::Flag5.bit_position(), 50);
|
||||||
|
|
||||||
|
// Test display strings
|
||||||
|
assert_eq!(format!("{}", MultiFlag::Flag1), "flag 1");
|
||||||
|
assert_eq!(format!("{}", MultiFlag::Flag2), "flag 2");
|
||||||
|
assert_eq!(format!("{}", MultiFlag::Flag3), "flag 3");
|
||||||
|
assert_eq!(format!("{}", MultiFlag::Flag4), "flag 4");
|
||||||
|
assert_eq!(format!("{}", MultiFlag::Flag5), "flag 5");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_non_sequential_values() {
|
||||||
|
// Verify the macro handles non-sequential bit position values
|
||||||
|
assert_eq!(SparseFlag::Low.bit_position(), 1);
|
||||||
|
assert_eq!(SparseFlag::High.bit_position(), 63);
|
||||||
|
assert_eq!(SparseFlag::Middle.bit_position(), 32);
|
||||||
|
|
||||||
|
// Verify iteration order matches declaration order
|
||||||
|
let flags: Vec<SparseFlag> = SparseFlag::iter().collect();
|
||||||
|
assert_eq!(flags.len(), 3);
|
||||||
|
assert_eq!(flags[0], SparseFlag::Low);
|
||||||
|
assert_eq!(flags[1], SparseFlag::High);
|
||||||
|
assert_eq!(flags[2], SparseFlag::Middle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display_string_edge_cases() {
|
||||||
|
// Verify various display string formats work correctly
|
||||||
|
assert_eq!(format!("{}", EdgeCaseFlag::Simple), "simple");
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", EdgeCaseFlag::WithSpaces),
|
||||||
|
"with spaces and punctuation!"
|
||||||
|
);
|
||||||
|
assert_eq!(format!("{}", EdgeCaseFlag::Uppercase), "UPPERCASE");
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", EdgeCaseFlag::WithDashes),
|
||||||
|
"with-dashes-and_underscores"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trait_bounds() {
|
||||||
|
// Verify generated implementations work with common trait bounds
|
||||||
|
|
||||||
|
// Function that requires Display
|
||||||
|
fn requires_display<T: std::fmt::Display>(flag: T) -> String {
|
||||||
|
format!("{}", flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function that requires IntoEnumIterator
|
||||||
|
fn requires_iterator<T: IntoEnumIterator>() -> Vec<T> {
|
||||||
|
T::iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with TestFlag
|
||||||
|
let result = requires_display(TestFlag::First);
|
||||||
|
assert_eq!(result, "first flag");
|
||||||
|
|
||||||
|
let flags: Vec<TestFlag> = requires_iterator();
|
||||||
|
assert_eq!(flags.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_copy_clone_compatibility() {
|
||||||
|
// Verify the macro works with Copy and Clone derives
|
||||||
|
let flag1 = CopyableFlag::Copyable;
|
||||||
|
let flag2 = flag1; // Copy
|
||||||
|
let flag3 = flag1; // Clone
|
||||||
|
|
||||||
|
assert_eq!(flag1, flag2);
|
||||||
|
assert_eq!(flag1, flag3);
|
||||||
|
|
||||||
|
// Verify all methods still work
|
||||||
|
assert_eq!(flag1.bit_position(), 1);
|
||||||
|
assert_eq!(flag2.flag_value(), 1);
|
||||||
|
assert_eq!(format!("{}", flag3), "copyable");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iterator_multiple_calls() {
|
||||||
|
// Verify iterator can be called multiple times
|
||||||
|
let iter1: Vec<TestFlag> = TestFlag::iter().collect();
|
||||||
|
let iter2: Vec<TestFlag> = TestFlag::iter().collect();
|
||||||
|
|
||||||
|
assert_eq!(iter1, iter2);
|
||||||
|
assert_eq!(iter1.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_flag_value_const() {
|
||||||
|
// Verify flag_value() can be used in const contexts
|
||||||
|
const FLAG_VALUE: u8 = TestFlag::First.flag_value();
|
||||||
|
assert_eq!(FLAG_VALUE, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_variants_unique_values() {
|
||||||
|
// Verify all variants have unique bit positions
|
||||||
|
let flags: Vec<TestFlag> = TestFlag::iter().collect();
|
||||||
|
let values: Vec<u8> = flags.iter().map(|f| f.bit_position()).collect();
|
||||||
|
|
||||||
|
// Check uniqueness
|
||||||
|
for i in 0..values.len() {
|
||||||
|
for j in (i + 1)..values.len() {
|
||||||
|
assert_ne!(values[i], values[j], "Duplicate bit position found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display_consistency() {
|
||||||
|
// Verify Display is consistent across multiple calls
|
||||||
|
let flag = TestFlag::First;
|
||||||
|
let display1 = format!("{}", flag);
|
||||||
|
let display2 = format!("{}", flag);
|
||||||
|
|
||||||
|
assert_eq!(display1, display2);
|
||||||
|
assert_eq!(display1, "first flag");
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Copyright IBM Corp.
|
||||||
|
|
||||||
|
//! Helper traits and utilities for testing the ControlFlag derive macro.
|
||||||
|
|
||||||
|
/// Trait for control flags that provide bit position information.
|
||||||
|
pub trait ControlFlagTrait {
|
||||||
|
/// Returns the bit position for this flag.
|
||||||
|
fn bit_position(self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for enums that can be iterated over.
|
||||||
|
pub trait IntoEnumIterator: Sized {
|
||||||
|
/// Returns an iterator over all variants of the enum.
|
||||||
|
fn iter() -> impl Iterator<Item = Self>;
|
||||||
|
}
|
||||||
+2
-1
@@ -515,6 +515,7 @@ CMDS="${CMDS}\
|
|||||||
:lspci -vvv\
|
:lspci -vvv\
|
||||||
:lstape\
|
:lstape\
|
||||||
:smc_dbg\
|
:smc_dbg\
|
||||||
|
:zpcimon --module-info --smart-log-raw\
|
||||||
"
|
"
|
||||||
|
|
||||||
# block device and other scsi device commands (in non alphabetical order)
|
# block device and other scsi device commands (in non alphabetical order)
|
||||||
@@ -629,7 +630,7 @@ NETWORK_CMDS="ip -br a\
|
|||||||
:nstat -az\
|
:nstat -az\
|
||||||
:openssl engine # deprecated in OpenSSL 3.n\
|
:openssl engine # deprecated in OpenSSL 3.n\
|
||||||
:openssl list --providers 2>/dev/null # redirect help on older systems\
|
:openssl list --providers 2>/dev/null # redirect help on older systems\
|
||||||
:opticsmon --module-info\
|
:opticsmon --module-info\ # Now called zpcimon but kept for compatibility
|
||||||
:route -n\
|
:route -n\
|
||||||
"
|
"
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -4,7 +4,7 @@ SYSTEM_UNITS = ttyrun-getty@.service iucvtty-login@.service \
|
|||||||
cpacfstatsd.service cpuplugd.service \
|
cpacfstatsd.service cpuplugd.service \
|
||||||
dumpconf.service cpi.service \
|
dumpconf.service cpi.service \
|
||||||
mon_fsstatd.service mon_procd.service \
|
mon_fsstatd.service mon_procd.service \
|
||||||
opticsmon.service
|
zpcimon.service
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
|
||||||
@@ -19,6 +19,9 @@ install: system_units
|
|||||||
$(INSTALL) -g $(GROUP) -o $(OWNER) \
|
$(INSTALL) -g $(GROUP) -o $(OWNER) \
|
||||||
-m 644 $$unit $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) ; \
|
-m 644 $$unit $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) ; \
|
||||||
done
|
done
|
||||||
|
# Make the legacy opticsmon.service a link to zpcimon.service
|
||||||
|
ln -sf zpcimon.service \
|
||||||
|
$(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)/opticsmon.service
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Systemd unit for mon_fsstatd (monitor z/VM guest file systems)
|
# Systemd unit for zpcimon PCI NIC optical module monitoring
|
||||||
#
|
#
|
||||||
# Copyright IBM Corp. 2024
|
# Copyright IBM Corp. 2024
|
||||||
#
|
#
|
||||||
@@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Monitor health of directly attached PCI NIC optical modules
|
Description=Monitor health of directly attached PCI NIC optical modules
|
||||||
# Documentation=man:opticsmon
|
# Documentation=man:zpcimon
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=exec
|
Type=exec
|
||||||
ExecStart=@usrsbin_path@/opticsmon --send-report --monitor --interval 86400
|
ExecStart=@usrsbin_path@/zpcimon --send-report --monitor --format jsonl --interval 86400
|
||||||
KillMode=control-group
|
KillMode=control-group
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
@@ -56,6 +56,11 @@ Sort and display the data according to given FIELD value. Valid values for
|
|||||||
FIELD are "nr" for partition number, "lpar" for partition name, and "sum" for
|
FIELD are "nr" for partition number, "lpar" for partition name, and "sum" for
|
||||||
total partition increments.
|
total partition increments.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR "\-p <SUBSTRING>" " or " "\-\-partition-filter=<SUBSTRING>"
|
||||||
|
Display only the partitions with names that contain SUBSTRING.
|
||||||
|
Matching is case-insensitive.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BR "\-i" " or " "\-\-ascii"
|
.BR "\-i" " or " "\-\-ascii"
|
||||||
Use only ASCII characters to render views.
|
Use only ASCII characters to render views.
|
||||||
|
|||||||
+55
-15
@@ -46,6 +46,7 @@ static struct zmemtopo_globals {
|
|||||||
unsigned int sort_field;
|
unsigned int sort_field;
|
||||||
unsigned int ascii;
|
unsigned int ascii;
|
||||||
unsigned int fmt_specified;
|
unsigned int fmt_specified;
|
||||||
|
char *partition_filter;
|
||||||
enum util_fmt_t format;
|
enum util_fmt_t format;
|
||||||
enum util_fmt_flags_t fmt_flags;
|
enum util_fmt_flags_t fmt_flags;
|
||||||
} g;
|
} g;
|
||||||
@@ -62,15 +63,11 @@ static void parse_nesting_level(char *arg)
|
|||||||
|
|
||||||
static void parse_sort_field(char *arg)
|
static void parse_sort_field(char *arg)
|
||||||
{
|
{
|
||||||
char *s;
|
if (strcasecmp(arg, "nr") == 0)
|
||||||
|
|
||||||
s = util_strdup(arg);
|
|
||||||
util_strstrip(s);
|
|
||||||
if (strcasecmp(s, "nr") == 0)
|
|
||||||
g.sort_field = SORT_NR;
|
g.sort_field = SORT_NR;
|
||||||
else if (strcasecmp(s, "lpar") == 0)
|
else if (strcasecmp(arg, "lpar") == 0)
|
||||||
g.sort_field = SORT_NAME;
|
g.sort_field = SORT_NAME;
|
||||||
else if (strcasecmp(s, "size") == 0)
|
else if (strcasecmp(arg, "size") == 0)
|
||||||
g.sort_field = SORT_SIZE;
|
g.sort_field = SORT_SIZE;
|
||||||
else
|
else
|
||||||
errx(EXIT_FAILURE, "%s is not a valid sort field option", arg);
|
errx(EXIT_FAILURE, "%s is not a valid sort field option", arg);
|
||||||
@@ -120,6 +117,9 @@ static void parse_args(int argc, char *argv[])
|
|||||||
case 's':
|
case 's':
|
||||||
parse_sort_field(optarg);
|
parse_sort_field(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'p':
|
||||||
|
g.partition_filter = optarg;
|
||||||
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
g.ascii = 1;
|
g.ascii = 1;
|
||||||
break;
|
break;
|
||||||
@@ -313,7 +313,6 @@ static void topology_entries_add_entry(struct topology_entry *entry,
|
|||||||
static void partition_set_name(struct partition *part, char *pname)
|
static void partition_set_name(struct partition *part, char *pname)
|
||||||
{
|
{
|
||||||
ebcdic_to_ascii(pname, part->part_name, LPAR_NAME_LEN);
|
ebcdic_to_ascii(pname, part->part_name, LPAR_NAME_LEN);
|
||||||
util_strstrip(part->part_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void partition_add_entry(struct partition *part,
|
static void partition_add_entry(struct partition *part,
|
||||||
@@ -488,6 +487,22 @@ static void partition_list_sort(struct partitions *parts)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int partition_filter_matches(struct partition *part)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
if (!part)
|
||||||
|
return 0;
|
||||||
|
if (!g.partition_filter)
|
||||||
|
return 1;
|
||||||
|
if (!strlen(g.partition_filter))
|
||||||
|
return 1;
|
||||||
|
result = 0;
|
||||||
|
if (strcasestr(part->part_name, g.partition_filter))
|
||||||
|
result = 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned int find_entry_cell_size(struct partitions *parts)
|
static unsigned int find_entry_cell_size(struct partitions *parts)
|
||||||
{
|
{
|
||||||
unsigned int max_digit, max_increment;
|
unsigned int max_digit, max_increment;
|
||||||
@@ -545,8 +560,8 @@ static void table_print_row(char **buf, struct partition *cur,
|
|||||||
unsigned int i, s_padding;
|
unsigned int i, s_padding;
|
||||||
|
|
||||||
s_padding = vdata->entry_len >= SUM_PAD ? vdata->entry_len : SUM_PAD;
|
s_padding = vdata->entry_len >= SUM_PAD ? vdata->entry_len : SUM_PAD;
|
||||||
concat_w_padding(buf, LPAR_NO_LEN, 0, "%2d", cur->part_nr);
|
concat_w_padding(buf, LPAR_NO_LEN, 1, "%2d", cur->part_nr);
|
||||||
concat_w_padding(buf, LPAR_NAME_LEN, 0, "%s", cur->part_name);
|
concat_w_padding(buf, LPAR_NAME_LEN, 1, "%s", cur->part_name);
|
||||||
concat_w_padding(buf, s_padding, 0, "%lu", cur->increment_total);
|
concat_w_padding(buf, s_padding, 0, "%lu", cur->increment_total);
|
||||||
entries = &cur->entries[g.nesting_level - 1];
|
entries = &cur->entries[g.nesting_level - 1];
|
||||||
for (i = 0; i < entries->count; i++) {
|
for (i = 0; i < entries->count; i++) {
|
||||||
@@ -584,8 +599,8 @@ static void table_print_header(char **buf, struct view_data *vdata)
|
|||||||
}
|
}
|
||||||
util_concatf(buf, "\n");
|
util_concatf(buf, "\n");
|
||||||
}
|
}
|
||||||
util_concatf(buf, "%*s", LPAR_NO_LEN, "NR");
|
util_concatf(buf, "%-*s", LPAR_NO_LEN, "NR");
|
||||||
util_concatf(buf, "%*s", LPAR_NAME_LEN, "LPAR");
|
util_concatf(buf, "%-*s", LPAR_NAME_LEN, "LPAR");
|
||||||
util_concatf(buf, "%*s\n", s_padding, "SUM");
|
util_concatf(buf, "%*s\n", s_padding, "SUM");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,8 +617,11 @@ static void table_print(struct partitions *parts)
|
|||||||
vdata->entry_len = find_entry_cell_size(parts);
|
vdata->entry_len = find_entry_cell_size(parts);
|
||||||
partition_list_calculate_level_lengths(parts, vdata);
|
partition_list_calculate_level_lengths(parts, vdata);
|
||||||
table_print_header(table, vdata);
|
table_print_header(table, vdata);
|
||||||
util_list_iterate(parts->list, cur)
|
util_list_iterate(parts->list, cur) {
|
||||||
|
if (!partition_filter_matches(cur))
|
||||||
|
continue;
|
||||||
table_print_row(table, cur, vdata);
|
table_print_row(table, cur, vdata);
|
||||||
|
}
|
||||||
printf("%s\n", *table);
|
printf("%s\n", *table);
|
||||||
printf("Increment size: %lu%s\n", unit.size / unit.scale, unit.suffix);
|
printf("Increment size: %lu%s\n", unit.size / unit.scale, unit.suffix);
|
||||||
free(vdata);
|
free(vdata);
|
||||||
@@ -721,6 +739,8 @@ static void tree_create(char **tree, struct partitions *parts,
|
|||||||
unit = vdata->unit;
|
unit = vdata->unit;
|
||||||
tree_create_header(tree, vdata);
|
tree_create_header(tree, vdata);
|
||||||
util_list_iterate(parts->list, cur) {
|
util_list_iterate(parts->list, cur) {
|
||||||
|
if (!partition_filter_matches(cur))
|
||||||
|
continue;
|
||||||
util_concatf(tree, "%-*s", vdata->cell_len, cur->part_name);
|
util_concatf(tree, "%-*s", vdata->cell_len, cur->part_name);
|
||||||
part_size = cur->increment_total * unit.size / unit.scale;
|
part_size = cur->increment_total * unit.size / unit.scale;
|
||||||
concat_w_padding(tree, vdata->entry_len, 0, "%u%s", part_size,
|
concat_w_padding(tree, vdata->entry_len, 0, "%u%s", part_size,
|
||||||
@@ -742,7 +762,9 @@ static unsigned int is_increment_at(struct partitions *parts,
|
|||||||
else
|
else
|
||||||
next = util_list_next(parts->list, cur);
|
next = util_list_next(parts->list, cur);
|
||||||
for (; next; next = util_list_next(parts->list, next)) {
|
for (; next; next = util_list_next(parts->list, next)) {
|
||||||
if (next->entries[level - 1].increments[idx])
|
if (!next->entries[level - 1].increments[idx])
|
||||||
|
continue;
|
||||||
|
if (partition_filter_matches(next))
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -777,6 +799,8 @@ static void rtree_print_parts(char **buf, struct partitions *parts,
|
|||||||
unit = vdata->unit;
|
unit = vdata->unit;
|
||||||
flag_idx = (g.max_level - level) + 1;
|
flag_idx = (g.max_level - level) + 1;
|
||||||
util_list_iterate(parts->list, cur) {
|
util_list_iterate(parts->list, cur) {
|
||||||
|
if (!partition_filter_matches(cur))
|
||||||
|
continue;
|
||||||
entries = &cur->entries[level - 1];
|
entries = &cur->entries[level - 1];
|
||||||
if (!entries->increments[idx])
|
if (!entries->increments[idx])
|
||||||
continue;
|
continue;
|
||||||
@@ -794,6 +818,21 @@ static void rtree_print_parts(char **buf, struct partitions *parts,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned int rtree_is_last_entry(struct partitions *parts, unsigned int idx,
|
||||||
|
unsigned int level, unsigned int end)
|
||||||
|
{
|
||||||
|
unsigned int tidx;
|
||||||
|
|
||||||
|
if (g.partition_filter && !g.tree_full) {
|
||||||
|
for (tidx = idx + 1; tidx < end; tidx++) {
|
||||||
|
if (is_increment_at(parts, NULL, level, tidx))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return (idx + 1) == end;
|
||||||
|
}
|
||||||
|
|
||||||
static void rtree_level_to_part(char **buf, struct partitions *parts,
|
static void rtree_level_to_part(char **buf, struct partitions *parts,
|
||||||
unsigned int step, unsigned int level,
|
unsigned int step, unsigned int level,
|
||||||
struct view_data *vdata)
|
struct view_data *vdata)
|
||||||
@@ -812,7 +851,8 @@ static void rtree_level_to_part(char **buf, struct partitions *parts,
|
|||||||
if (!is_increment_at(parts, NULL, level, idx) && !g.tree_full)
|
if (!is_increment_at(parts, NULL, level, idx) && !g.tree_full)
|
||||||
continue;
|
continue;
|
||||||
indent = 0;
|
indent = 0;
|
||||||
vdata->end_flag[flag_idx] = (idx + 1) == end;
|
vdata->end_flag[flag_idx] = rtree_is_last_entry(parts, idx,
|
||||||
|
level, end);
|
||||||
if (level != g.max_level)
|
if (level != g.max_level)
|
||||||
indent = tree_add_indent(buf, level, vdata->end_flag);
|
indent = tree_add_indent(buf, level, vdata->end_flag);
|
||||||
concat_w_padding(buf, vdata->cell_len - indent, 1, "LEVEL%u_%u",
|
concat_w_padding(buf, vdata->cell_len - indent, 1, "LEVEL%u_%u",
|
||||||
|
|||||||
+3
-3
@@ -68,9 +68,9 @@ struct diag310_t_hdr {
|
|||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
struct diag310_p_hdr {
|
struct diag310_p_hdr {
|
||||||
char pname[8]; /* partition name */
|
char pname[LPAR_NAME_LEN]; /* partition name */
|
||||||
uint8_t pn; /* partition number */
|
uint8_t pn; /* partition number */
|
||||||
uint8_t tie; /* count of entries following this header */
|
uint8_t tie; /* count of entries following this header */
|
||||||
uint16_t reserved1;
|
uint16_t reserved1;
|
||||||
uint32_t reserved2;
|
uint32_t reserved2;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ static struct util_opt opt_vec[] = {
|
|||||||
.option = { "sort", required_argument, NULL, 's' },
|
.option = { "sort", required_argument, NULL, 's' },
|
||||||
.argument = "FIELD",
|
.argument = "FIELD",
|
||||||
.desc = "Sort view by FIELD (nr, lpar, size)"
|
.desc = "Sort view by FIELD (nr, lpar, size)"
|
||||||
|
}, {
|
||||||
|
.option = { "partition-filter", required_argument, NULL, 'p' },
|
||||||
|
.argument = "SUBSTRING",
|
||||||
|
.desc = "Filter partitions by name substring (case-insensitive)"
|
||||||
}, {
|
}, {
|
||||||
.option = { "ascii", no_argument, NULL, 'i' },
|
.option = { "ascii", no_argument, NULL, 'i' },
|
||||||
.desc = "Use only ASCII characters",
|
.desc = "Use only ASCII characters",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user