mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b592b0cc06 | ||
|
|
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 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -102,9 +102,11 @@ lsstp/_lsstp
|
||||
lsstp/lsstp.bash
|
||||
mon_tools/mon_fsstatd
|
||||
mon_tools/mon_procd
|
||||
opticsmon/opticsmon
|
||||
opticsmon/_opticsmon
|
||||
opticsmon/opticsmon.bash
|
||||
zpcimon/zpcimon
|
||||
zpcimon/zpcimon.bash
|
||||
zpcimon/_zpcimon
|
||||
zpcimon/opticsmon.bash
|
||||
zpcimon/_opticsmon
|
||||
osasnmpd/osasnmpd
|
||||
qetharp/qetharp
|
||||
qethqoat/qethqoat
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,6 +1,42 @@
|
||||
Release history for s390-tools (MIT version)
|
||||
--------------------------------------------
|
||||
|
||||
* __v2.xx.x (2023-xx-xx)__
|
||||
|
||||
For Linux kernel version: 6.x
|
||||
|
||||
Add new tools / libraries:
|
||||
|
||||
Changes of existing tools:
|
||||
|
||||
Bug Fixes:
|
||||
|
||||
* __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)__
|
||||
|
||||
For Linux kernel version: 7.1
|
||||
|
||||
2
Makefile
2
Makefile
@@ -15,7 +15,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
|
||||
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
|
||||
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
|
||||
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
|
||||
|
||||
else
|
||||
|
||||
17
README.md
17
README.md
@@ -316,7 +316,7 @@ build options:
|
||||
| glibc-static | `HAVE_LIBC_STATIC` | zfcpdump |
|
||||
| openssl | `HAVE_OPENSSL` | zkey, libekmfweb, libkmipclient, |
|
||||
| | | zgetdump, rust/pvattest, rust/pvimg, |
|
||||
| | | zgetdump/pvsecret, opticsmon |
|
||||
| | | zgetdump/pvsecret, zpcimon |
|
||||
| cryptsetup | `HAVE_CRYPTSETUP2` | zkey-cryptsetup |
|
||||
| json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb, |
|
||||
| | | libkmipclient |
|
||||
@@ -325,8 +325,9 @@ build options:
|
||||
| | | rust/pvattest, rust/pvsecret, |
|
||||
| libxml2 | `HAVE_LIBXML2` | libkmipclient |
|
||||
| systemd | `HAVE_SYSTEMD` | hsavmcore |
|
||||
| libudev | `HAVE_LIBUDEV` | cpacfstatsd |
|
||||
| libnl3 | `HAVE_LIBNL3` | opticsmon |
|
||||
| libudev | `HAVE_LIBUDEV` | cpacfstatsd, zpcimon |
|
||||
| libnl3 | `HAVE_LIBNL3` | zpcimon |
|
||||
| libnvme | `HAVE_LIBNVME` | zpcimon |
|
||||
|
||||
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.
|
||||
|
||||
* opticsmon:
|
||||
For building opticsmon OpenSSL and the Netlink Library Suite (libnl3) are
|
||||
required.
|
||||
Tip: you may skip the opticsmon build by adding
|
||||
`HAVE_OPENSSL=0` or `HAVE_LIBNL3=0`
|
||||
* zpcimon:
|
||||
For building zpcimon OpenSSL, the Netlink Library Suite (libnl3),
|
||||
and libnvme are required.
|
||||
Tip: you may skip the zpcimon build by adding
|
||||
`HAVE_OPENSSL=0` or `HAVE_LIBNL3=0` or `HAVE_LIBNVME=0`.
|
||||
|
||||
* osasnmpd:
|
||||
You need at least the NET-SNMP 5.1.x package (net-snmp-devel.rpm)
|
||||
|
||||
@@ -32,8 +32,8 @@ endif
|
||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||
VERSION := 2
|
||||
RELEASE := 43
|
||||
PATCHLEVEL := 1
|
||||
RELEASE := 44
|
||||
PATCHLEVEL := 0
|
||||
DISTRELEASE := build-$(shell date +%Y%m%d)
|
||||
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||
export S390_TOOLS_RELEASE
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
#include "util_list.h"
|
||||
|
||||
#define PCI_BDF_LEN 13 /* DDDD:BB:dd.f\0 */
|
||||
#define PCI_VENDOR_ID_IBM 0x1014
|
||||
|
||||
enum zpci_pft {
|
||||
ZPCI_PFT_UNCLASSIFIED = 0x00,
|
||||
|
||||
@@ -65,6 +68,9 @@ struct zpci_dev {
|
||||
bool uid_is_unique;
|
||||
/* Configuration state 0 - Standby, 1 Configured */
|
||||
bool conf;
|
||||
/* Common PCI attributes */
|
||||
uint16_t subsystem_vendor;
|
||||
uint16_t subsystem_device;
|
||||
|
||||
/* Associated netdevs if any */
|
||||
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_netdev **netdev);
|
||||
|
||||
char *zpci_get_nvme_device_node(const char *pci_addr);
|
||||
|
||||
#endif /* LIB_ZPCI_PCI_LIST_H */
|
||||
|
||||
@@ -21,9 +21,11 @@
|
||||
#define SCLP_ERRNOTIFY_AQ_DECONF 1
|
||||
#define SCLP_ERRNOTIFY_AQ_REPORT_ERR 2
|
||||
#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_OPTICSMON 0x4714
|
||||
#define SCLP_ERRNOTIFY_ID_NVMEMON 0x4715
|
||||
|
||||
#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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @base : Base indentation level to apply to all output lines (default 0)
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#define MSEC_PER_SEC 1000L
|
||||
|
||||
#define SEC_PER_DAY (24 * 60 * 60)
|
||||
|
||||
/**
|
||||
* Convert nanoseconds to microseconds
|
||||
*
|
||||
|
||||
@@ -72,6 +72,11 @@
|
||||
#define __section(x) __attribute__((__section__(#x)))
|
||||
#define __noinline __attribute__((__noinline__))
|
||||
#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
|
||||
* __always_inline. Therefore undefine it first to allow the headers
|
||||
* to be included first.
|
||||
|
||||
@@ -89,11 +89,26 @@ static const struct {
|
||||
const char *name;
|
||||
enum util_fmt_t fmt;
|
||||
} formats[] = {
|
||||
{ "json", FMT_JSON },
|
||||
{ "json-seq", FMT_JSONSEQ },
|
||||
{ "jsonl", FMT_JSONL },
|
||||
{ "pairs", FMT_PAIRS },
|
||||
{ "csv", FMT_CSV },
|
||||
[FMT_JSON] = {
|
||||
.name = "json",
|
||||
.fmt = FMT_JSON
|
||||
},
|
||||
[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. */
|
||||
@@ -112,6 +127,11 @@ bool util_fmt_name_to_type(const char *name, enum util_fmt_t *type)
|
||||
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)
|
||||
{
|
||||
switch (type) {
|
||||
|
||||
@@ -12,13 +12,17 @@ static void zpci_print(struct zpci_dev *zdev)
|
||||
int i;
|
||||
|
||||
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 {
|
||||
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->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) {
|
||||
printf("netdevs: ");
|
||||
printf("\tnetdevs: ");
|
||||
for (i = 0; i < zdev->num_netdevs; i++) {
|
||||
printf("%s (%s)", zdev->netdevs[i].name,
|
||||
zpci_operstate_str(zdev->netdevs[i].operstate));
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "lib/util_list.h"
|
||||
#include "lib/util_path.h"
|
||||
#include "lib/util_scandir.h"
|
||||
#include "lib/util_sys.h"
|
||||
|
||||
/**
|
||||
* 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,
|
||||
const char *slot_name)
|
||||
{
|
||||
char buf_addr[11]; /* "dddd:bb:dd\0" */
|
||||
char buf_addr[PCI_BDF_LEN];
|
||||
uint8_t bus, df;
|
||||
uint32_t domain;
|
||||
int val, rc;
|
||||
@@ -261,6 +262,16 @@ static int zpci_populate_from_dev_dir(struct zpci_dev *zdev)
|
||||
goto out_path;
|
||||
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)) {
|
||||
rc = zpci_populate_netdevices(zdev, path);
|
||||
if (rc)
|
||||
@@ -387,3 +398,46 @@ struct zpci_dev *zpci_find_by_netdev(struct util_list *zpci_list, char *netdev_n
|
||||
}
|
||||
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;
|
||||
}
|
||||
12
rust/Cargo.lock
generated
12
rust/Cargo.lock
generated
@@ -1222,9 +1222,21 @@ dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"s390_pv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utils_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utils_macros"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -11,6 +11,7 @@ members = [
|
||||
"pvsecret",
|
||||
"pvverify",
|
||||
"utils",
|
||||
"utils_macros",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGE
|
||||
pub enum SeHdrVersion {
|
||||
/// Secure Execution header v1
|
||||
One = 0x100,
|
||||
/// Secure Execution header v2
|
||||
Two = 0x200,
|
||||
}
|
||||
|
||||
/// Struct containing all SE-header tags.
|
||||
@@ -241,6 +243,7 @@ impl BootHdrTags {
|
||||
// Some sanity checks
|
||||
let hdr_version = match hdr_head.version.get() {
|
||||
0x100 => SeHdrVersion::One,
|
||||
0x200 => SeHdrVersion::Two,
|
||||
_ => {
|
||||
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
|
||||
return Err(Error::InvBootHdr);
|
||||
|
||||
@@ -7,12 +7,14 @@ use std::fmt::Display;
|
||||
use std::ops::Range;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use openssl::bn::BigNumContext;
|
||||
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::md::MdRef;
|
||||
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::rand::rand_bytes;
|
||||
use openssl::rsa::Padding;
|
||||
@@ -23,8 +25,51 @@ use openssl::symm::{
|
||||
use pv_core::request::Confidential;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::openssl_extensions::PkeyEncapsulateContext;
|
||||
use crate::req::get_pub_ecdh_points;
|
||||
use crate::request::EcPubKeyCoord;
|
||||
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
|
||||
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
|
||||
@@ -74,6 +119,7 @@ impl SymKeyType {
|
||||
}
|
||||
|
||||
/// Returns true if the [`SymKeyType`] is an AEAD key
|
||||
#[must_use]
|
||||
pub const fn is_aead(&self) -> bool {
|
||||
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.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -760,6 +1020,112 @@ mod tests {
|
||||
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]
|
||||
fn hkdf_rfc_5869() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,11 @@ pub enum Error {
|
||||
#[error("No user-key for verification provided and user-data is signed")]
|
||||
BinAsrcbNoUserDataSgnKey,
|
||||
|
||||
#[error("Input does not contain an add-secret request version 1")]
|
||||
BinAsrcbInvVersion,
|
||||
#[error("Input contains an unknown add-secret request version {0}")]
|
||||
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})")]
|
||||
AsrcbUserDataKeyMismatch { key: String, kind: UserDataType },
|
||||
@@ -84,6 +87,9 @@ pub enum Error {
|
||||
#[error("The provided host key document in {0} contains no certificate!")]
|
||||
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")]
|
||||
InvBootHdrSize(usize),
|
||||
|
||||
@@ -135,6 +141,12 @@ pub enum Error {
|
||||
|
||||
#[error("Unsupported cipher: {:?}", .0.as_raw())]
|
||||
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
|
||||
|
||||
@@ -89,13 +89,16 @@ pub use crate::error::HkdVerifyErrorType;
|
||||
|
||||
/// Functionalities to build UV requests
|
||||
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::{
|
||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
|
||||
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey, SymKeyType,
|
||||
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};
|
||||
|
||||
/// Reexports some useful OpenSSL symbols
|
||||
|
||||
@@ -14,5 +14,4 @@ mod stackable_crl;
|
||||
pub use akid::*;
|
||||
pub use bio::*;
|
||||
pub use crl::*;
|
||||
#[expect(unused_imports)]
|
||||
pub use ml_kem::*;
|
||||
|
||||
@@ -47,7 +47,10 @@ impl EcPubKeyCoord {
|
||||
|
||||
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
|
||||
/// 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 y = BigNum::new()?;
|
||||
let mut bn_ctx = BigNumContext::new()?;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Request header structure for UV requests.
|
||||
|
||||
use pv_core::request::{RequestMagic, RequestVersion};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, U32};
|
||||
|
||||
use crate::assert_size;
|
||||
@@ -25,13 +27,13 @@ pub struct RequestHdr {
|
||||
assert_size!(RequestHdr, 48);
|
||||
|
||||
impl RequestHdr {
|
||||
pub(crate) fn new(
|
||||
rqvn: u32,
|
||||
pub(super) fn new(
|
||||
rqvn: RequestVersion,
|
||||
rql: u32,
|
||||
iv: [u8; 12],
|
||||
nks: u8,
|
||||
sea: u32,
|
||||
magic: Option<[u8; 8]>,
|
||||
magic: Option<RequestMagic>,
|
||||
) -> Self {
|
||||
Self {
|
||||
magic: magic.unwrap_or_default(),
|
||||
|
||||
@@ -4,7 +4,56 @@
|
||||
|
||||
//! 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
|
||||
#[non_exhaustive]
|
||||
@@ -12,15 +61,33 @@ use openssl::pkey::{PKey, Public};
|
||||
pub enum HostKey {
|
||||
/// ECDH public key
|
||||
V1(PKey<Public>),
|
||||
|
||||
/// Hybrid public key (ECDH and ML-KEM)
|
||||
V2(HybridPKey),
|
||||
}
|
||||
|
||||
impl HostKey {
|
||||
/// Return the ECDH public key
|
||||
pub fn ec_key(&self) -> &PKey<Public> {
|
||||
match self {
|
||||
pub fn ec_key(&self) -> Option<&PKeyRef<Public>> {
|
||||
Some(match self {
|
||||
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 {
|
||||
@@ -28,3 +95,248 @@ impl AsRef<HostKey> for HostKey {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
|
||||
//! IBM Z Host key-slot implementations.
|
||||
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::hash::{DigestBytes, MessageDigest};
|
||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
|
||||
use super::ec_coord::EcPubKeyCoord;
|
||||
use super::encrypt::Encrypt;
|
||||
use crate::crypto::{derive_aes256_gcm_key, encrypt_aead, hash};
|
||||
use crate::request::HostKey;
|
||||
use crate::crypto::{derive_aes256_gcm_key, derive_aes256_gcm_key_hybrid, encrypt_aead, hash};
|
||||
use crate::req::{EcPubKeyCoord, Encrypt, HostKey, HybridPKey};
|
||||
use crate::Result;
|
||||
|
||||
/// 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
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Keyslot {
|
||||
/// V1 key-slots with ECDH keys
|
||||
V1(KeyslotV1),
|
||||
/// V2 key-slots with hybrid ECDH/ML-KEM keys
|
||||
V2(KeyslotV2),
|
||||
}
|
||||
|
||||
impl Keyslot {
|
||||
@@ -84,27 +177,34 @@ impl Keyslot {
|
||||
pub fn new(hostkey: HostKey) -> Self {
|
||||
match hostkey {
|
||||
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
|
||||
#[must_use]
|
||||
pub fn phkh_size(&self) -> u32 {
|
||||
match self {
|
||||
Keyslot::V1(_) => KeyslotV1::PHKH_SIZE,
|
||||
Keyslot::V2(_) => KeyslotV2::PHKH_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the size of the key-slot in bytes
|
||||
#[must_use]
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Keyslot::V1(_) => KeyslotV1::SIZE,
|
||||
Keyslot::V2(_) => KeyslotV2::SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return whether the key-slot uses hybrid keys
|
||||
#[must_use]
|
||||
pub fn is_hybrid(&self) -> bool {
|
||||
match self {
|
||||
Keyslot::V1(_) => false,
|
||||
Keyslot::V2(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +218,7 @@ impl Encrypt for Keyslot {
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::test_utils::*;
|
||||
use crate::test_utils::{DeterministicTestRandGuard, *};
|
||||
|
||||
#[test]
|
||||
fn keyslot() {
|
||||
@@ -147,4 +254,214 @@ mod tests {
|
||||
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
|
||||
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
|
||||
pub use context::ReqEncrCtx;
|
||||
pub(crate) use ec_coord::get_pub_ecdh_points;
|
||||
pub use ec_coord::EcPubKeyCoord;
|
||||
pub use encrypt::{Aad, Encrypt};
|
||||
pub use header::RequestHdr;
|
||||
pub use hostkey::HostKey;
|
||||
pub use keyslot::{Keyslot, KeyslotV1};
|
||||
pub use hostkey::{HostKey, HybridPKey};
|
||||
pub use keyslot::{Keyslot, KeyslotV1, KeyslotV2};
|
||||
pub use request::{BinReqValues, Request};
|
||||
|
||||
@@ -51,7 +51,7 @@ pub trait Request {
|
||||
/// Add a host-key to this request
|
||||
///
|
||||
/// 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.
|
||||
@@ -148,10 +148,9 @@ mod tests {
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::*;
|
||||
use crate::crypto::SymKey;
|
||||
use crate::get_test_asset;
|
||||
use crate::req::header::RequestHdr;
|
||||
use crate::req::{Aad, HostKey, Keyslot, ReqEncrCtx};
|
||||
use crate::request::SymKey;
|
||||
use crate::req::{Aad, HybridPKey, Keyslot};
|
||||
use crate::test_utils::*;
|
||||
|
||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||
@@ -187,6 +186,37 @@ mod tests {
|
||||
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]
|
||||
fn encr_build_aad_nks_no() {
|
||||
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));
|
||||
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]
|
||||
fn encr_build_aad_nks() {
|
||||
let (_, host_key) = get_test_keys();
|
||||
@@ -229,6 +274,25 @@ mod tests {
|
||||
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]
|
||||
fn req_hdr() {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
///
|
||||
/// 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())
|
||||
}
|
||||
|
||||
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> {
|
||||
assert!(coords.len() == 160);
|
||||
let x = BigNum::from_slice(&coords[..80])?;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use pv_core::static_assert;
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||
|
||||
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::request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize};
|
||||
use crate::uv::UvFlags;
|
||||
use crate::{assert_size, static_assert, Error, Result};
|
||||
use crate::{assert_size, Error, Result};
|
||||
#[cfg(doc)]
|
||||
use crate::{
|
||||
request::SymKeyType,
|
||||
@@ -60,9 +61,9 @@ use crate::{
|
||||
/// let hkd = s390_pv::misc::read_certs(&std::fs::read("host-key-document.crt")?)?;
|
||||
/// // IBM issued HKD certificates typically have one X509
|
||||
/// let hkd = hkd.first().unwrap().public_key()?;
|
||||
/// arcb.add_hostkey(HostKey::V1(hkd));
|
||||
/// arcb.add_hostkey(HostKey::V1(hkd))?;
|
||||
/// // you can add multiple hostkeys
|
||||
/// // arcb.add_hostkey(another_hkd);
|
||||
/// // arcb.add_hostkey(HostKey::V1(another_hkd))?;
|
||||
/// // encrypt it
|
||||
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
|
||||
/// let arcb = arcb.encrypt(&ctx)?;
|
||||
@@ -106,7 +107,13 @@ impl AttestationRequest {
|
||||
|
||||
/// Returns a reference to the flags of this [`AttestationRequest`].
|
||||
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`].
|
||||
@@ -142,6 +149,7 @@ impl AttestationRequest {
|
||||
let values = BinReqValues::get(arcb)?;
|
||||
match values.version().try_into()? {
|
||||
AttestationVersion::One => (),
|
||||
AttestationVersion::Two => (),
|
||||
};
|
||||
|
||||
Ok(values)
|
||||
@@ -235,8 +243,19 @@ impl Request for AttestationRequest {
|
||||
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
|
||||
}
|
||||
|
||||
fn add_hostkey(&mut self, hostkey: HostKey) {
|
||||
self.keyslots.push(Keyslot::new(hostkey))
|
||||
fn add_hostkey(&mut self, hostkey: HostKey) -> Result<()> {
|
||||
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 {
|
||||
/// Version 1 (= 0x0100)
|
||||
One = 0x0100,
|
||||
/// Version 2 (= 0x0200)
|
||||
Two = 0x0200,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for AttestationVersion {
|
||||
@@ -254,6 +275,8 @@ impl TryFrom<u32> for AttestationVersion {
|
||||
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::BinArcbInvVersion(value))
|
||||
}
|
||||
@@ -412,8 +435,8 @@ impl Zeroize for ReqConfData {
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::request::SymKey;
|
||||
use crate::test_utils::get_test_keys;
|
||||
use crate::request::{HybridPKey, SymKey};
|
||||
use crate::test_utils::{get_test_keys, get_test_keys_hybrid};
|
||||
|
||||
const ARPK: [u8; 32] = [0x17; 32];
|
||||
const NONCE: [u8; 16] = [0xab; 16];
|
||||
@@ -421,6 +444,8 @@ mod test {
|
||||
|
||||
fn mk_arcb() -> Vec<u8> {
|
||||
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),
|
||||
@@ -443,7 +468,37 @@ mod test {
|
||||
arcb.conf.value_mut().nonce = NONCE;
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -455,6 +510,32 @@ mod test {
|
||||
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]
|
||||
fn auth_bin() {
|
||||
let request = mk_arcb();
|
||||
@@ -464,6 +545,15 @@ mod test {
|
||||
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]
|
||||
fn decrypt_bin() {
|
||||
let request = mk_arcb();
|
||||
@@ -473,6 +563,100 @@ mod test {
|
||||
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]
|
||||
fn decrypt_bin_fail_magic() {
|
||||
let arpk = SymKey::Aes256(ARPK.into());
|
||||
@@ -484,6 +668,17 @@ mod test {
|
||||
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]
|
||||
fn decrypt_bin_fail_mai() {
|
||||
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]
|
||||
fn decrypt_bin_fail_aad() {
|
||||
let arpk = SymKey::Aes256(ARPK.into());
|
||||
@@ -509,4 +719,15 @@ mod test {
|
||||
let ret = AttestationRequest::decrypt_bin(&tamp_arcb, &arpk);
|
||||
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::pkey::{PKeyRef, Private};
|
||||
use pv_core::misc::write_file;
|
||||
use zerocopy::{BigEndian, IntoBytes, U16, U32};
|
||||
|
||||
use super::AttNonce;
|
||||
@@ -101,6 +102,7 @@ impl AttestationItems {
|
||||
}
|
||||
items.extend_from_slice(additional);
|
||||
assert!(items.len() == size);
|
||||
write_file("additional_data_parsed", &items, "blah").unwrap();
|
||||
Self(items.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,31 +6,32 @@ use openssl::md::Md;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use pv_core::request::RequestVersion;
|
||||
use pv_core::secret::AddSecretMagic;
|
||||
use pv_core::static_assert;
|
||||
use pv_core::uv::SecretId;
|
||||
use zerocopy::{Immutable, IntoBytes};
|
||||
|
||||
use super::guest_secret::ListableSecretHdr;
|
||||
use super::guest_secret::{ListableSecretHdr, SecretAuth};
|
||||
use super::user_data::UserData;
|
||||
use crate::crypto::{hkdf_rfc_5869, AeadEncryptionResult};
|
||||
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::secret::{ExtSecret, GuestSecret};
|
||||
use crate::uv::{ConfigUid, UvFlags};
|
||||
use crate::{assert_size, Result};
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
/// Authenticated data w/o user data
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, IntoBytes, Immutable)]
|
||||
struct ReqAuthData {
|
||||
struct ReqAuthDataV1 {
|
||||
flags: UvFlags,
|
||||
boot_tags: BootHdrTags,
|
||||
cuid: ConfigUid,
|
||||
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 {
|
||||
Self {
|
||||
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)]
|
||||
struct ReqConfData {
|
||||
secret: GuestSecret,
|
||||
@@ -92,6 +117,8 @@ impl From<AddSecretFlags> for UvFlags {
|
||||
pub enum AddSecretVersion {
|
||||
/// Version 1 (= 0x0100)
|
||||
One = 0x0100,
|
||||
/// Version 2 (= 0x0200)
|
||||
Two = 0x0200,
|
||||
|
||||
#[cfg(not(doc))]
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
@@ -99,17 +126,182 @@ pub enum AddSecretVersion {
|
||||
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 {
|
||||
fn from(val: AddSecretVersion) -> 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
|
||||
///
|
||||
/// 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
|
||||
/// _______________________________________________________________
|
||||
/// | generic header (48)
|
||||
@@ -133,15 +325,19 @@ impl From<AddSecretVersion> for RequestVersion {
|
||||
#[derive(Debug)]
|
||||
pub struct AddSecretRequest {
|
||||
version: AddSecretVersion,
|
||||
aad: ReqAuthData,
|
||||
aad: ReqAuthDataVersion,
|
||||
keyslots: Vec<Keyslot>,
|
||||
conf: ReqConfData,
|
||||
user_data: UserData,
|
||||
}
|
||||
static_assert!(AddSecretRequest::USER_DATA_OFFS == 0x218);
|
||||
static_assert!(
|
||||
AddSecretRequest::USER_DATA_OFFS == size_of::<RequestHdr>() + size_of::<ReqAuthDataV2>()
|
||||
);
|
||||
|
||||
impl AddSecretRequest {
|
||||
/// 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.
|
||||
///
|
||||
@@ -152,22 +348,23 @@ impl AddSecretRequest {
|
||||
secret: GuestSecret,
|
||||
boot_tags: BootHdrTags,
|
||||
flags: AddSecretFlags,
|
||||
) -> Self {
|
||||
Self {
|
||||
conf: ReqConfData {
|
||||
extension_secret: Confidential::new([0; 32]),
|
||||
secret,
|
||||
},
|
||||
aad: ReqAuthData::new(boot_tags, flags),
|
||||
) -> Result<Self> {
|
||||
let conf = ReqConfData {
|
||||
extension_secret: Confidential::new([0; 32]),
|
||||
secret,
|
||||
};
|
||||
Ok(Self {
|
||||
aad: ReqAuthDataVersion::new(version, boot_tags, flags, &conf.secret.auth())?,
|
||||
conf,
|
||||
keyslots: vec![],
|
||||
version,
|
||||
user_data: UserData::Null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the Configuration Unique Id of this [`AddSecretRequest`].
|
||||
pub fn set_cuid(&mut self, cuid: ConfigUid) {
|
||||
self.aad.cuid = cuid;
|
||||
self.aad.set_cuid(cuid);
|
||||
}
|
||||
|
||||
/// Sets the extension secret of this [`AddSecretRequest`].
|
||||
@@ -182,7 +379,7 @@ impl AddSecretRequest {
|
||||
ExtSecret::Derived(cck) => hkdf_rfc_5869(
|
||||
Md::sha512(),
|
||||
cck.value(),
|
||||
self.aad.boot_tags.tag(),
|
||||
self.aad.boot_tags().tag(),
|
||||
DER_EXT_SECRET_INFO,
|
||||
)?
|
||||
.into(),
|
||||
@@ -221,7 +418,6 @@ impl AddSecretRequest {
|
||||
let cust_pub_key = ctx.key_coords()?;
|
||||
let secr_auth = self.conf.secret.auth();
|
||||
let user_data = self.user_data.data();
|
||||
|
||||
let mut aad: Vec<Aad> = Vec::with_capacity(5 + self.keyslots.len());
|
||||
aad.push(Aad::Plain(self.aad.as_bytes()));
|
||||
if let Some(data) = user_data.0 {
|
||||
@@ -232,7 +428,13 @@ impl AddSecretRequest {
|
||||
}
|
||||
aad.push(Aad::Plain(cust_pub_key.as_ref()));
|
||||
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())
|
||||
}
|
||||
@@ -273,7 +475,7 @@ impl AddSecretRequest {
|
||||
// add signature to authenticated data starting with USER_DATA_OFFS
|
||||
self.user_data.sign(
|
||||
&mut buf[aad_range.start..encr_range.end],
|
||||
Self::V1_USER_DATA_OFFS,
|
||||
Self::USER_DATA_OFFS,
|
||||
)?;
|
||||
|
||||
// encrypt again with signed data
|
||||
@@ -309,7 +511,101 @@ impl Request for AddSecretRequest {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_hostkey(&mut self, hostkey: HostKey) {
|
||||
self.keyslots.push(Keyslot::new(hostkey))
|
||||
fn add_hostkey(&mut self, hostkey: HostKey) -> Result<()> {
|
||||
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::request::openssl::{NID_ED25519, NID_ED448};
|
||||
use crate::request::Confidential;
|
||||
use crate::secret::AddSecretVersion;
|
||||
use crate::uv::{
|
||||
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
||||
RetrieveCmd, SecretId,
|
||||
@@ -388,12 +389,18 @@ pub(crate) enum 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();
|
||||
|
||||
pub fn get(&self) -> &[u8] {
|
||||
pub fn get(&self, version: AddSecretVersion) -> &[u8] {
|
||||
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::UpdateCck => Self::UPDATE_CCK_HDR.as_bytes(),
|
||||
}
|
||||
@@ -402,15 +409,15 @@ impl SecretAuth {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
||||
struct NullSecretHdr {
|
||||
struct NullSecretHdrV1 {
|
||||
res0: u16,
|
||||
kind: U16<BigEndian>,
|
||||
secret_len: U32<BigEndian>,
|
||||
res8: u64,
|
||||
}
|
||||
assert_size!(NullSecretHdr, 0x10);
|
||||
assert_size!(NullSecretHdrV1, 0x10);
|
||||
|
||||
impl NullSecretHdr {
|
||||
impl NullSecretHdrV1 {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
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)]
|
||||
#[derive(Debug, IntoBytes, FromBytes, Immutable, KnownLayout)]
|
||||
pub(crate) struct ListableSecretHdr {
|
||||
@@ -701,7 +731,7 @@ mod test {
|
||||
let gs_bytes = gs.auth();
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -729,7 +759,7 @@ mod test {
|
||||
secret: vec![2; 32].into(),
|
||||
};
|
||||
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];
|
||||
exp.extend([1; 32]);
|
||||
|
||||
@@ -746,7 +776,7 @@ mod test {
|
||||
let mut exp = vec![0u8, 0, 0, 0x16, 0, 0, 0, 0x20];
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ impl RetrievedSecret {
|
||||
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the data in PEM format.
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::crypto::{sign_msg, verify_signature};
|
||||
use crate::req::BinReqValues;
|
||||
use crate::request::openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public};
|
||||
use crate::request::RequestMagic;
|
||||
use crate::secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType};
|
||||
use crate::secret::{AddSecretMagic, AddSecretRequest, UserDataType};
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
/// User data.
|
||||
@@ -261,21 +261,19 @@ pub fn verify_asrcb_and_get_user_data(
|
||||
// check that the provided buffer contains an Add Secret request
|
||||
let magic = AddSecretMagic::try_from_bytes(&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
|
||||
let req_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());
|
||||
}
|
||||
|
||||
// forget the tag (and all additional data that might be behind the tag)
|
||||
asrcb.truncate(req_len - BinReqValues::TAG_LEN);
|
||||
// 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];
|
||||
|
||||
// depending on the user_data_type do:
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::Private;
|
||||
use s390_pv::request::openssl::pkey::{PKey, Public};
|
||||
use s390_pv::request::{BootHdrTags, HostKey, ReqEncrCtx, Request, SymKey};
|
||||
use s390_pv::request::openssl::pkey::PKey;
|
||||
use s390_pv::request::{BootHdrTags, HostKey, HybridPKey, ReqEncrCtx, Request, SymKey};
|
||||
use s390_pv::secret::{
|
||||
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret,
|
||||
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::{get_test_asset, Result};
|
||||
|
||||
@@ -29,8 +29,13 @@ fn create_asrcb(
|
||||
hkd: HostKey,
|
||||
ctx: &ReqEncrCtx,
|
||||
) -> 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 {
|
||||
asrcb.set_ext_secret(s)?
|
||||
};
|
||||
@@ -38,12 +43,25 @@ fn create_asrcb(
|
||||
asrcb.set_cuid(c);
|
||||
};
|
||||
|
||||
asrcb.add_hostkey(hkd);
|
||||
asrcb.add_hostkey(hkd)?;
|
||||
asrcb.encrypt(ctx)
|
||||
}
|
||||
|
||||
fn get_crypto() -> (PKey<Public>, ReqEncrCtx) {
|
||||
fn get_crypto() -> (HostKey, ReqEncrCtx) {
|
||||
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(
|
||||
Some([0x55; 12]),
|
||||
Some(cust_key),
|
||||
@@ -67,14 +85,25 @@ where
|
||||
true => Some(CUID),
|
||||
false => None,
|
||||
};
|
||||
create_asrcb(
|
||||
guest_secret,
|
||||
ext_secret.into(),
|
||||
flags,
|
||||
cuid,
|
||||
HostKey::V1(host_key),
|
||||
&ctx,
|
||||
)
|
||||
create_asrcb(guest_secret, ext_secret.into(), flags, cuid, host_key, &ctx)
|
||||
}
|
||||
|
||||
fn gen_asrcb_v2<E>(
|
||||
guest_secret: GuestSecret,
|
||||
ext_secret: E,
|
||||
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 {
|
||||
@@ -96,9 +125,21 @@ fn no_flag() -> AddSecretFlags {
|
||||
fn create_signed_asrcb(skey: PKey<Private>, user_data: Vec<u8>) -> Vec<u8> {
|
||||
let (host_key, ctx) = get_crypto();
|
||||
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.encrypt(&ctx).unwrap()
|
||||
}
|
||||
@@ -108,9 +149,10 @@ fn null_none_default_ncuid_one_user_unsgn() {
|
||||
let user_data_orig = vec![0x56; 0x183];
|
||||
let (host_key, ctx) = get_crypto();
|
||||
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();
|
||||
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()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_none_default_ncuid_one_user_ec() {
|
||||
let (usr_sgn_key, _) = get_test_keys();
|
||||
@@ -242,8 +285,11 @@ fn null_none_default_ncuid_one() {
|
||||
fn null_none_default_cuid_seven() {
|
||||
let (hkd, ctx) = get_crypto();
|
||||
let mut asrcb =
|
||||
AddSecretRequest::new(AddSecretVersion::One, GuestSecret::Null, TAGS, no_flag());
|
||||
(0..7).for_each(|_| asrcb.add_hostkey(HostKey::V1(hkd.clone())));
|
||||
AddSecretRequest::new(AddSecretVersion::One, 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();
|
||||
|
||||
@@ -259,3 +305,164 @@ fn verify_no_user_data() {
|
||||
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);
|
||||
}
|
||||
|
||||
BIN
rust/pv/tests/assets/exp/asrcb/assoc_derived_default_cuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/assoc_derived_default_cuid_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/assoc_none_default_cuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/assoc_none_default_cuid_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/assoc_simple_default_cuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/assoc_simple_default_cuid_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/null_derived_default_cuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/null_derived_default_cuid_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_seven_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_seven_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/null_none_default_cuid_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/null_none_default_ncuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/null_none_default_ncuid_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/null_none_dump_cuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/null_none_dump_cuid_two
Normal file
Binary file not shown.
BIN
rust/pv/tests/assets/exp/asrcb/null_simple_default_cuid_two
Normal file
BIN
rust/pv/tests/assets/exp/asrcb/null_simple_default_cuid_two
Normal file
Binary file not shown.
76
rust/pv/tests/assets/keys/create_hybrid_hkd.sh
Executable file
76
rust/pv/tests/assets/keys/create_hybrid_hkd.sh
Executable file
@@ -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"
|
||||
15
rust/pv/tests/assets/keys/host.ec.crt
Normal file
15
rust/pv/tests/assets/keys/host.ec.crt
Normal file
@@ -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-----
|
||||
8
rust/pv/tests/assets/keys/host.ec.key
Normal file
8
rust/pv/tests/assets/keys/host.ec.key
Normal file
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAKog0FWiwlXuAS1jK
|
||||
dNsahw9KZ+r4zvZrp1LmDSOQNOfkauXHH1EmBEnUkYccsi7PibYkz80sxbtk+NVW
|
||||
Kk6pGk2hgYkDgYYABAHwdB2ksf7RAzpwNLb/7UhXNr9bdGGqwvC/UHFdG1PqOBpO
|
||||
jga4kItBm1J2uAp3QD1q6wWLwv2VXdpyqmYceVhijQAKvzTm0nbx+rjX42WNu6Uu
|
||||
enwdmm8WuVM5R/ypGAFPiZtbBY2YTqg9Qb2/5os4c9VqnR7nBkkGDueVgy/ih9Oh
|
||||
qA==
|
||||
-----END PRIVATE KEY-----
|
||||
84
rust/pv/tests/assets/keys/host.hybrid.crt
Normal file
84
rust/pv/tests/assets/keys/host.hybrid.crt
Normal file
@@ -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-----
|
||||
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
|
||||
jq+SvDvKMqeV
|
||||
-----END CERTIFICATE-----
|
||||
54
rust/pv/tests/assets/keys/host.mlkem.crt
Normal file
54
rust/pv/tests/assets/keys/host.mlkem.crt
Normal file
@@ -0,0 +1,54 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
jq+SvDvKMqeV
|
||||
-----END CERTIFICATE-----
|
||||
71
rust/pv/tests/assets/keys/host.mlkem.key
Normal file
71
rust/pv/tests/assets/keys/host.mlkem.key
Normal file
@@ -0,0 +1,71 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
+JA=
|
||||
-----END PRIVATE KEY-----
|
||||
36
rust/pv/tests/assets/keys/host.mlkem.pub.pem
Normal file
36
rust/pv/tests/assets/keys/host.mlkem.pub.pem
Normal file
@@ -0,0 +1,36 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
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
|
||||
pJafxe8R
|
||||
-----END PUBLIC KEY-----
|
||||
31
rust/pv/tests/assets/keys/issuer.crt
Normal file
31
rust/pv/tests/assets/keys/issuer.crt
Normal file
@@ -0,0 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
52
rust/pv/tests/assets/keys/issuer.key
Normal file
52
rust/pv/tests/assets/keys/issuer.key
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
YFF7USKdWC1isfkk3OmsHvIwXzfRUA==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -5,7 +5,10 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
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
|
||||
///
|
||||
@@ -67,6 +70,28 @@ pub enum Command {
|
||||
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)]
|
||||
pub struct CreateAttOpt {
|
||||
#[command(flatten)]
|
||||
@@ -94,6 +119,10 @@ pub struct CreateAttOpt {
|
||||
value_delimiter = ','
|
||||
)]
|
||||
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)]
|
||||
|
||||
@@ -12,7 +12,7 @@ pub use check::check;
|
||||
pub use create::create;
|
||||
pub use verify::verify;
|
||||
|
||||
pub const CMD_FN: &[&str] = &["+create", "+verify"];
|
||||
pub const CMD_FN: &[&str] = &["+create", "+verify", "+quantumsafe"];
|
||||
// s390 branch
|
||||
#[cfg(target_arch = "s390x")]
|
||||
mod uv_cmd {
|
||||
|
||||
@@ -6,10 +6,9 @@ use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{debug, info};
|
||||
use log::{debug, info, warn};
|
||||
use pv::misc::{read_certs, read_file};
|
||||
use pv::request::openssl::DigestBytes;
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use pv::request::{EcPubKeyCoord, HybridPKey, KeyslotV2};
|
||||
use serde::Serialize;
|
||||
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());
|
||||
for hkd in hkds {
|
||||
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(),
|
||||
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)
|
||||
}
|
||||
|
||||
fn contains_phkh<'a>(
|
||||
hkd_hashes: &[(&'a Path, DigestBytes)],
|
||||
hkd_hashes: &[(&'a Path, Vec<u8>)],
|
||||
phkh: &HexSlice<'_>,
|
||||
mode: HkCheck,
|
||||
check_enforced: bool,
|
||||
) -> CheckState<HostKeyCheck<'a>> {
|
||||
let hk: Vec<_> = hkd_hashes
|
||||
.iter()
|
||||
.filter_map(|(path, hash)| match hash.as_ref() == phkh.as_ref() {
|
||||
.filter_map(|(path, hash)| match hash == phkh.as_ref() {
|
||||
true => Some(*path),
|
||||
false => None,
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ use pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, Attes
|
||||
use pv::misc::{create_file, write_file};
|
||||
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};
|
||||
|
||||
fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
|
||||
@@ -26,18 +26,57 @@ fn flags(cli_flags: &[AttAddFlags]) -> AttestationFlags {
|
||||
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> {
|
||||
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 mut arcb = AttestationRequest::new(att_version, meas_alg, flags(&opt.add_data))?;
|
||||
debug!("Generated Attestation request");
|
||||
|
||||
// Add host-key documents
|
||||
opt.certificate_args
|
||||
.get_verified_hkds("attestation request")?
|
||||
.into_iter()
|
||||
.for_each(|k| arcb.add_hostkey(HostKey::V1(k)));
|
||||
for k in hkds.into_iter() {
|
||||
arcb.add_hostkey(k)?
|
||||
}
|
||||
debug!("Added all host-keys");
|
||||
|
||||
let encr_ctx =
|
||||
|
||||
@@ -11,6 +11,8 @@ use pv::request::MagicValue;
|
||||
use pv::uv::{AttestationCmd, ConfigUid};
|
||||
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:";
|
||||
|
||||
#[repr(C)]
|
||||
@@ -99,10 +101,18 @@ impl ExchangeFormatV1Hdr {
|
||||
let measurement_entry = Entry::from_exp(Some(measurement));
|
||||
let exp_add = match additional {
|
||||
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); //, AttestationCmd::ADDITIONAL_MAX_SIZE, &mut offset);
|
||||
let additional_entry = Entry::from_exp(exp_add);
|
||||
let user_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.",
|
||||
))?;
|
||||
|
||||
// TODO remove unwrap
|
||||
let measurement = hdr.measurement.read(reader)?.data().ok_or(anyhow!(
|
||||
"{INV_EXCHANGE_FMT_ERROR_TEXT} Contains no attestation response (Measurement missing).",
|
||||
))?;
|
||||
@@ -614,9 +623,12 @@ mod test {
|
||||
measurement: usize,
|
||||
additional: usize,
|
||||
) {
|
||||
// TODO as 32 checks
|
||||
let ctx_write = ExchangeFormatRequest::new(arcb, measurement as u32, additional as u32)
|
||||
.expect("exchange fmt creation");
|
||||
let ctx_write = ExchangeFormatRequest::new(
|
||||
arcb,
|
||||
measurement.try_into().unwrap(),
|
||||
additional.try_into().unwrap(),
|
||||
)
|
||||
.expect("exchange fmt creation");
|
||||
|
||||
// let mut out = create_file(path).unwrap();
|
||||
let mut out = vec![];
|
||||
@@ -693,6 +705,50 @@ mod test {
|
||||
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]
|
||||
fn min_req() {
|
||||
test_read_write_request(
|
||||
|
||||
@@ -10,15 +10,18 @@ use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use clap::{Parser, ValueHint};
|
||||
use log::{info, warn};
|
||||
use pv::misc::{decode_hex, open_file, read_certs, read_file, try_parse_u64};
|
||||
use pv::request::SymKeyType;
|
||||
use pv::{Error as PvError, Result};
|
||||
use clap::{Parser, ValueEnum, ValueHint};
|
||||
use log::info;
|
||||
use pv::misc::{decode_hex, open_file, read_file, try_parse_u64};
|
||||
use pv::request::{HostKey, NoVerifyHkd, SymKeyType};
|
||||
use pv::Result;
|
||||
use pvimg::misc::PSW;
|
||||
use pvimg::secured_comp::{ComponentTrait, Layout, SecuredComponentBuilder};
|
||||
use pvimg::uvdata::{BuilderTrait, SeHdrBuilder, SeHdrVersion};
|
||||
use utils::{AtomicFile, AtomicFileOperation, HexSlice, PvLogger, VerbosityOptions};
|
||||
use pvimg::uvdata::{BuilderTrait, SeHdrBuilder, SeHdrControlFlags, SeHdrVersion, SeTarget};
|
||||
use utils::{
|
||||
AtomicFile, AtomicFileOperation, HexSlice, HkdLoader, HkdVersionSelection, PvLogger,
|
||||
VerbosityOptions,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Args {
|
||||
@@ -129,6 +164,10 @@ pub struct Args {
|
||||
#[arg(short, long)]
|
||||
pub output: PathBuf,
|
||||
|
||||
/// SE-header version to build
|
||||
#[arg(long, value_enum)]
|
||||
version: Option<SeHdrVersionArg>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub verbosity: VerbosityOptions,
|
||||
}
|
||||
@@ -203,36 +242,26 @@ fn main() -> anyhow::Result<()> {
|
||||
info!("\n# Creating Secure Execution Header");
|
||||
let addr = args.psw_addr;
|
||||
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![];
|
||||
for hkd_path in args.host_key_documents {
|
||||
info!(
|
||||
"Use the file '{}' as a host key document",
|
||||
hkd_path.display()
|
||||
);
|
||||
let hkd_data = read_file(&hkd_path, "host key document")?;
|
||||
let certs = read_certs(&hkd_data)?;
|
||||
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 cert = HkdLoader::load_and_verify(&hkd_path, &NoVerifyHkd, HkdVersionSelection::Auto)?;
|
||||
target_pub_keys.push(cert);
|
||||
}
|
||||
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)?;
|
||||
|
||||
let pcf = try_parse_u64(&args.pcf, "pcf")?.into();
|
||||
let scf = try_parse_u64(&args.scf, "scf")?.into();
|
||||
info!(
|
||||
"PSW addr ............: {addr:#018x}\n\
|
||||
PSW mask ............: {mask:#018x}\n\
|
||||
|
||||
@@ -13,7 +13,56 @@ use std::string::ToString;
|
||||
use clap::builder::{PossibleValue, TypedValueParser};
|
||||
use clap::{Arg, ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, ValueHint};
|
||||
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.
|
||||
///
|
||||
@@ -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.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -71,6 +142,92 @@ pub fn validate_cli(opts: &CliOptions) -> Result<(), clap::error::Error> {
|
||||
{
|
||||
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(()),
|
||||
@@ -100,6 +257,8 @@ pub struct ComponentPaths {
|
||||
pub parmfile: Option<PathBuf>,
|
||||
}
|
||||
|
||||
const LONG_FLAG_CCK: &str = "cck";
|
||||
|
||||
/// CLI Argument collection for handling user-provided keys.
|
||||
#[derive(Args, Debug)]
|
||||
#[cfg_attr(test, derive(Default))]
|
||||
@@ -109,7 +268,7 @@ pub struct UserKeys {
|
||||
/// The file must contain exactly 32 bytes of data. In previous versions,
|
||||
/// this option was called '--comm-key'.
|
||||
#[arg(
|
||||
long,
|
||||
long = LONG_FLAG_CCK,
|
||||
value_name = "FILE",
|
||||
group = "cck-available",
|
||||
visible_alias = "comm-key"
|
||||
@@ -184,11 +343,13 @@ pub struct CreateBootImageLegacyFlags {
|
||||
#[arg(long, action = clap::ArgAction::SetTrue, conflicts_with="enable_pckmo", group="header-flags")]
|
||||
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")]
|
||||
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")]
|
||||
pub disable_pckmo_hmac: Option<bool>,
|
||||
|
||||
@@ -519,7 +680,6 @@ impl GenprotimgCliOptions {
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[cfg_attr(test, derive(Default))]
|
||||
pub struct CreateBootImageArgs {
|
||||
#[clap(flatten)]
|
||||
pub component_paths: ComponentPaths,
|
||||
@@ -542,16 +702,62 @@ pub struct CreateBootImageArgs {
|
||||
#[arg(long)]
|
||||
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)]
|
||||
pub keys: UserKeys,
|
||||
|
||||
#[clap(flatten)]
|
||||
// TODO Declare as deprecated
|
||||
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)]
|
||||
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
|
||||
#[derive(Args, Debug)]
|
||||
#[cfg_attr(test, derive(Default))]
|
||||
@@ -666,6 +872,62 @@ mod test {
|
||||
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]
|
||||
#[rustfmt::skip]
|
||||
fn genprotimg_and_pvimg_create_args() {
|
||||
@@ -734,16 +996,67 @@ mod test {
|
||||
// --comm-key with --enable-cck-update (alias test)
|
||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("comm-key", ["--comm-key", "/dev/null"]),
|
||||
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(), "image")),
|
||||
flat_map_collect(remove(mvcanv.clone(), "hkd")),
|
||||
flat_map_collect(remove(mvcanv, "output")),
|
||||
|
||||
// missing both `--cck' and `--enable-cck-update'
|
||||
// missing both `--cck' and `--enable-cck-update' (required by --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
|
||||
flat_map_collect(insert(mvca.clone(), vec![
|
||||
CliOption::new("verbose", ["-v"]),
|
||||
@@ -761,7 +1074,6 @@ mod test {
|
||||
CliOption::new("disable-pckmo", ["--disable-pckmo"])])),
|
||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-image-encryption", ["--enable-image-encryption"]),
|
||||
CliOption::new("disable-image-encryption", ["--disable-image-encryption"])])),
|
||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-header-key", ["--hdr-key"]),])),
|
||||
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("extension", ["--enable-cck-extension-secret"]),
|
||||
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"]),
|
||||
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)
|
||||
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("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![
|
||||
@@ -794,53 +1148,31 @@ mod test {
|
||||
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.
|
||||
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());
|
||||
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 {
|
||||
genprotimg_invalid_args.push([["genprotimg"].to_vec(), Vec::from_iter(invalid_create_args.iter().map(String::as_str))].concat());
|
||||
pvimg_invalid_args.push([["pvimg", "create"].to_vec(), Vec::from_iter(invalid_create_args.iter().map(String::as_str))].concat());
|
||||
}
|
||||
// Test invalid args with expected error kinds
|
||||
let test_cases = [
|
||||
(&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 {
|
||||
let res = CliOptions::try_parse_from(&arg);
|
||||
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||
if let Err(e) = &res {
|
||||
println!("arg: {arg:?}");
|
||||
println!("{e}");
|
||||
}
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
// Parse and convert functions for each CLI variant
|
||||
let parse_pvimg = |args: &[&str]| CliOptions::try_parse_from(args);
|
||||
// The into converts Result<GenprotimgCliOptions, Error> into Result<CliOptions, Error>
|
||||
let parse_genprotimg = |args: &[&str]| GenprotimgCliOptions::try_parse_from(args).map(Into::into);
|
||||
|
||||
for arg in pvimg_invalid_args {
|
||||
let res = CliOptions::try_parse_from(&arg);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
for arg in genprotimg_valid_args {
|
||||
let res = GenprotimgCliOptions::try_parse_from(&arg);
|
||||
#[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());
|
||||
// Test both CLI variants
|
||||
for (name, valid_args, cmd_prefix, parse_fn) in [
|
||||
("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>),
|
||||
] {
|
||||
test_valid_args(valid_args, parse_fn, name);
|
||||
test_invalid_args(&test_cases, cmd_prefix, parse_fn, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,13 +1220,37 @@ mod test {
|
||||
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(
|
||||
args.clone(),
|
||||
vec![CliOption::new("image", ["/dev/null"])],
|
||||
)),
|
||||
];
|
||||
|
||||
let invalid_conflict = [
|
||||
// the argument '--key-hashes[=<FILE>]' cannot be used with '--host-key-document
|
||||
// <FILE>'
|
||||
flat_map_collect(insert(
|
||||
@@ -905,20 +1261,17 @@ mod test {
|
||||
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
|
||||
// Input is missing
|
||||
let mut pvimg_invalid_args = vec![vec!["pvimg", "test"]];
|
||||
let mut pvimg_valid_args = vec![];
|
||||
|
||||
for create_args in &valid_test_args {
|
||||
pvimg_valid_args.push(
|
||||
@@ -930,30 +1283,36 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
for invalid_test_arg in &invalid_test_args {
|
||||
pvimg_invalid_args.push(
|
||||
[
|
||||
["pvimg", "test"].to_vec(),
|
||||
Vec::from_iter(invalid_test_arg.iter().map(String::as_str)),
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
}
|
||||
let test_cases = [
|
||||
(
|
||||
&invalid_missing_required[..],
|
||||
clap::error::ErrorKind::MissingRequiredArgument,
|
||||
"MissingRequiredArgument",
|
||||
),
|
||||
(
|
||||
&invalid_conflict[..],
|
||||
clap::error::ErrorKind::ArgumentConflict,
|
||||
"ArgumentConflict",
|
||||
),
|
||||
(
|
||||
&invalid_unknown_arg[..],
|
||||
clap::error::ErrorKind::UnknownArgument,
|
||||
"UnknownArgument",
|
||||
),
|
||||
];
|
||||
|
||||
for arg in pvimg_valid_args {
|
||||
let res = CliOptions::try_parse_from(&arg);
|
||||
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||
if let Err(e) = &res {
|
||||
println!("arg: {arg:?}");
|
||||
println!("{e}");
|
||||
}
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
for arg in pvimg_invalid_args {
|
||||
let res = CliOptions::try_parse_from(&arg);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
let parse_pvimg = |args: &[&str]| CliOptions::try_parse_from(args);
|
||||
test_valid_args(
|
||||
pvimg_valid_args,
|
||||
&parse_pvimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||
"pvimg test",
|
||||
);
|
||||
test_invalid_args(
|
||||
&test_cases,
|
||||
&["pvimg", "test"],
|
||||
&parse_pvimg as &dyn Fn(&[&str]) -> Result<CliOptions, clap::Error>,
|
||||
"pvimg test",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1044,9 +1403,27 @@ mod test {
|
||||
["--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
|
||||
flat_map_collect(insert(
|
||||
args.clone(),
|
||||
@@ -1055,6 +1432,9 @@ mod test {
|
||||
CliOption::new("image", ["--", "/dev/null"]),
|
||||
],
|
||||
)),
|
||||
];
|
||||
|
||||
let invalid_conflict = [
|
||||
// --print-json-schema conflicts with input
|
||||
flat_map_collect(insert(
|
||||
args.clone(),
|
||||
@@ -1081,7 +1461,7 @@ mod test {
|
||||
)),
|
||||
// --print-json-schema conflicts with --show-secrets
|
||||
flat_map_collect(insert(
|
||||
args,
|
||||
args.clone(),
|
||||
vec![
|
||||
CliOption::new("print-json-schema", ["--print-schema", "json"]),
|
||||
CliOption::new("show-secrets", ["--show-secrets"]),
|
||||
@@ -1091,10 +1471,6 @@ mod test {
|
||||
|
||||
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 {
|
||||
pvimg_valid_args.push(
|
||||
[
|
||||
@@ -1105,30 +1481,126 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
for invalid_test_arg in &invalid_test_args {
|
||||
pvimg_invalid_args.push(
|
||||
[
|
||||
["pvimg", "info"].to_vec(),
|
||||
Vec::from_iter(invalid_test_arg.iter().map(String::as_str)),
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
}
|
||||
let invalid_format = [
|
||||
// Invalid format variant for text
|
||||
flat_map_collect(insert(
|
||||
args.clone(),
|
||||
vec![
|
||||
CliOption::new("format", ["--format", "text:minify"]),
|
||||
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 res = CliOptions::try_parse_from(&arg);
|
||||
#[allow(clippy::use_debug, clippy::print_stdout)]
|
||||
if let Err(e) = &res {
|
||||
println!("arg: {arg:?}");
|
||||
println!("{e}");
|
||||
}
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
let test_cases = [
|
||||
(
|
||||
&invalid_value[..],
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"InvalidValue",
|
||||
),
|
||||
(
|
||||
&invalid_conflict[..],
|
||||
clap::error::ErrorKind::ArgumentConflict,
|
||||
"ArgumentConflict",
|
||||
),
|
||||
(
|
||||
&invalid_format[..],
|
||||
clap::error::ErrorKind::ValueValidation,
|
||||
"ValueValidation",
|
||||
),
|
||||
];
|
||||
|
||||
for arg in pvimg_invalid_args {
|
||||
let res = CliOptions::try_parse_from(&arg);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
let parse_pvimg = |args: &[&str]| CliOptions::try_parse_from(args);
|
||||
test_valid_args(
|
||||
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]
|
||||
|
||||
@@ -8,7 +8,7 @@ mod info;
|
||||
mod test;
|
||||
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 info::info;
|
||||
|
||||
@@ -5,18 +5,21 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufReader;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use log::{debug, warn};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use log::{debug, info, warn};
|
||||
use pv::misc::{open_file, try_parse_u64};
|
||||
use pv::request::HostKey;
|
||||
use pvimg::error::OwnExitCode;
|
||||
use pvimg::secured_comp::ComponentTrait;
|
||||
use pvimg::uvdata::{
|
||||
ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1,
|
||||
SeHdrDataV1, SecretControlFlagsV1,
|
||||
EffectiveControlFlags, FlagState, FlagsOverride, SeHdrControlFlags, SeHdrControlFlagsModel,
|
||||
SeHdrDataV1, SeHdrDataV2, SeHdrFlag, SeHdrVersion, SeTarget,
|
||||
};
|
||||
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::se_img::{SeHdrArgs, SeImgBuilder};
|
||||
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::{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:
|
||||
/// 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)
|
||||
}
|
||||
|
||||
fn parse_flags(
|
||||
args: &CreateBootImageArgs,
|
||||
) -> Result<(PlaintextControlFlagsV1, SecretControlFlagsV1)> {
|
||||
let lf = &args.legacy_flags;
|
||||
/// Parse new-style flags (--flags and --disable-flags)
|
||||
fn parse_new_style_flags(
|
||||
flags: &[SeHdrFlagName],
|
||||
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 {
|
||||
($cli_flag:expr, $control_flags:expr) => {
|
||||
$cli_flag
|
||||
.filter(|x| *x)
|
||||
.and(Some(ControlFlagTrait::all_disabled($control_flags)))
|
||||
$cli_flag.filter(|x| *x).map(|_| {
|
||||
let mut flags = FlagsOverride::new();
|
||||
flags.disable_all($control_flags);
|
||||
flags
|
||||
})
|
||||
};
|
||||
}
|
||||
macro_rules! flag_enabled {
|
||||
($cli_flag:expr, $control_flags:expr) => {
|
||||
$cli_flag
|
||||
.filter(|x| *x)
|
||||
.and(Some(ControlFlagTrait::all_enabled($control_flags)))
|
||||
$cli_flag.filter(|x| *x).map(|_| {
|
||||
let mut flags = FlagsOverride::new();
|
||||
flags.enable_all($control_flags);
|
||||
flags
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
let plaintext_flags: Vec<FlagData<PcfV1>> = [
|
||||
flag_disabled!(lf.disable_dump, [PcfV1::AllowDumping]),
|
||||
flag_enabled!(lf.enable_dump, [PcfV1::AllowDumping]),
|
||||
flag_disabled!(lf.disable_pckmo, PlaintextControlFlagsV1::PCKMO),
|
||||
flag_enabled!(lf.enable_pckmo, PlaintextControlFlagsV1::PCKMO),
|
||||
flag_disabled!(lf.disable_pckmo_hmac, [PcfV1::PckmoHmac]),
|
||||
flag_enabled!(lf.enable_pckmo_hmac, [PcfV1::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>> = [
|
||||
let pcf_overrides = [
|
||||
flag_disabled!(legacy_flags.disable_dump, [SeHdrFlag::ConfidentialDump]),
|
||||
flag_enabled!(legacy_flags.enable_dump, [SeHdrFlag::ConfidentialDump]),
|
||||
flag_disabled!(legacy_flags.disable_pckmo, SeHdrControlFlagsModel::PCKMO),
|
||||
flag_enabled!(legacy_flags.enable_pckmo, SeHdrControlFlagsModel::PCKMO),
|
||||
flag_disabled!(legacy_flags.disable_pckmo_hmac, [SeHdrFlag::PckmoHmac]),
|
||||
flag_enabled!(legacy_flags.enable_pckmo_hmac, [SeHdrFlag::PckmoHmac]),
|
||||
flag_disabled!(
|
||||
lf.disable_cck_extension_secret,
|
||||
[ScfV1::CckExtensionSecretEnforcement]
|
||||
legacy_flags.disable_backup_keys,
|
||||
[SeHdrFlag::BackupTargetKeys]
|
||||
),
|
||||
flag_enabled!(
|
||||
lf.enable_cck_extension_secret,
|
||||
[ScfV1::CckExtensionSecretEnforcement]
|
||||
legacy_flags.enable_backup_keys,
|
||||
[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()
|
||||
.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()
|
||||
.collect();
|
||||
// This is ensured by Clap's `conflicts_with`.
|
||||
assert!(SecretControlFlagsV1::no_duplicates(&secret_flags));
|
||||
.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 mut pcf: PlaintextControlFlagsV1 = match &args.experimental_args.x_pcf {
|
||||
Some(v) => try_parse_u64(v, "x-pcf")?.into(),
|
||||
None => PlaintextControlFlagsV1::default(),
|
||||
};
|
||||
pcf.parse_flags(&plaintext_flags);
|
||||
debug!("Using plaintext flags: {pcf:#x}");
|
||||
(pcf_overrides, scf_overrides)
|
||||
}
|
||||
|
||||
let mut scf: SecretControlFlagsV1 = match &args.experimental_args.x_scf {
|
||||
Some(v) => try_parse_u64(v, "x-scf")?.into(),
|
||||
None => SecretControlFlagsV1::default(),
|
||||
/// Apply experimental overrides to control flags
|
||||
fn apply_experimental_overrides(
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
pub fn create(opt: &CreateBootImageArgs) -> Result<OwnExitCode> {
|
||||
// Verify host key documents first, because if they are not valid there is
|
||||
// no reason to continue.
|
||||
let verified_host_keys = opt
|
||||
.certificate_args
|
||||
.get_verified_hkds("Secure Execution image")?;
|
||||
let user_provided_keys = read_user_provided_keys(&opt.keys)?;
|
||||
let (plaintext_flags, secret_flags) = parse_flags(opt)?;
|
||||
let verified_host_keys = opt.certificate_args.get_verified_hkds(
|
||||
"Secure Execution image",
|
||||
HdrVersionSelection::Explicit(opt.hdr_version).map(|v| v.into()),
|
||||
)?;
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// 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 seimg_ctx = SeImgBuilder::new_v1(
|
||||
let mut seimg_ctx = SeImgBuilder::new(
|
||||
&mut writer,
|
||||
plaintext_flags.is_unset(PcfV1::NoComponentEncryption),
|
||||
!plaintext_flags.has(SeHdrFlag::NoComponentEncryption),
|
||||
Some(expected_se_hdr_size),
|
||||
opt.experimental_args.x_bootloader_directory.as_ref(),
|
||||
)?;
|
||||
@@ -218,13 +421,135 @@ mod test {
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let parsed_flags = super::parse_flags(&args).expect("Failed to parse flags {args:?}");
|
||||
let mut exp_pcf = Vec::from(PlaintextControlFlagsV1::PCKMO);
|
||||
exp_pcf.push(PcfV1::AllowDumping);
|
||||
let pcf = PlaintextControlFlagsV1::from_flags(PcfV1::all_enabled(exp_pcf));
|
||||
assert_eq!(parsed_flags.0, pcf);
|
||||
let exp_scf = vec![ScfV1::CckUpdateAllowed];
|
||||
let scf = SecretControlFlagsV1::from_flags(ScfV1::all_enabled(exp_scf));
|
||||
assert_eq!(parsed_flags.1, scf);
|
||||
let parsed_flags =
|
||||
super::parse_flags(&args, SeHdrVersion::V1).expect("Failed to parse flags {args:?}");
|
||||
|
||||
// Build expected PCF
|
||||
let mut exp_pcf = Vec::from(SeHdrControlFlagsModel::PCKMO);
|
||||
exp_pcf.push(SeHdrFlag::ConfidentialDump);
|
||||
let mut pcf_overrides = FlagsOverride::new();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
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 pvimg::error::{Error, OwnExitCode, PvError};
|
||||
use pvimg::error::{Error, OwnExitCode};
|
||||
use pvimg::uvdata::{KeyExchangeTrait, SeHdr, UvKeyHashesV1};
|
||||
use utils::hkd::{HkdLoader, HkdVersionSelection};
|
||||
use utils::HexSlice;
|
||||
|
||||
use crate::cli::TestArgs;
|
||||
@@ -31,32 +33,25 @@ fn hdr_test_target_hashes(hdr: &SeHdr, key_hashes: &Path) -> Result<bool> {
|
||||
err => Error::PvCore(err),
|
||||
})?;
|
||||
let hashes = UvKeyHashesV1::read_from_io(file)?;
|
||||
let mut contains = hdr.contains_hash(&hashes.pchkh);
|
||||
if contains {
|
||||
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 {
|
||||
let matches = hashes.matching_hashes(hdr);
|
||||
if matches.is_empty() {
|
||||
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
|
||||
@@ -72,27 +67,21 @@ where
|
||||
|
||||
let mut result = false;
|
||||
for path in host_key_documents {
|
||||
let hkd_path = path.as_ref();
|
||||
let hkd_data = read_file(hkd_path, "host key document")?;
|
||||
let certs = read_certs(&hkd_data)?;
|
||||
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! 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()?)? {
|
||||
let hkd = HkdLoader::load_and_verify(
|
||||
path,
|
||||
&NoVerifyHkd,
|
||||
HkdVersionSelection::Explicit(hdr.common.version.into()),
|
||||
)?;
|
||||
if hdr.contains(hkd)? {
|
||||
result = true;
|
||||
log_println!(" ✓ Host key document '{}' is included", hkd_path.display());
|
||||
log_println!(
|
||||
" ✓ Host key document '{}' is included",
|
||||
path.as_ref().display()
|
||||
);
|
||||
} else {
|
||||
log_println!(
|
||||
" ✘ Host key document '{}' is not included",
|
||||
hkd_path.display()
|
||||
path.as_ref().display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,12 @@ pub mod misc {
|
||||
|
||||
pub mod uvdata {
|
||||
pub use crate::pv_utils::{
|
||||
AeadPlainDataTrait, BuilderTrait, ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait,
|
||||
EnvelopeSeHdrV1, FlagData, KeyExchangeTrait, PcfV1, PlaintextControlFlagsV1, ScfV1, SeH,
|
||||
SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, SeHdrData, SeHdrDataV1, SeHdrPlain,
|
||||
SeHdrVersion, SeHdrVersioned, SecretControlFlagsV1, UvDataPlainTrait, UvDataTrait,
|
||||
UvKeyHashesV1,
|
||||
AeadPlainDataTrait, BuilderTrait, ComponentMetadataV1, ControlFlagTrait,
|
||||
EffectiveControlFlags, EnvelopeSeHdrV1, FlagData, FlagState, FlagsOverride,
|
||||
IntoEnumIterator, KeyExchangeTrait, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder,
|
||||
SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrFlag,
|
||||
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 psw::{ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
|
||||
pub use se_hdr::{
|
||||
ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait, EnvelopeSeHdrV1, FlagData, PcfV1,
|
||||
PlaintextControlFlagsV1, ScfV1, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, SeHdrData,
|
||||
SeHdrDataV1, SeHdrPlain, SeHdrVersion, SeHdrVersioned, SecretControlFlagsV1,
|
||||
ComponentMetadataV1, ControlFlagTrait, EffectiveControlFlags, EnvelopeSeHdrV1, FlagData,
|
||||
FlagState, FlagsOverride, IntoEnumIterator, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder,
|
||||
SeHdrControlFlags, SeHdrControlFlagsModel, SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrFlag,
|
||||
SeHdrPlain, SeHdrVersion, SeHdrVersioned, SeTarget, UnknownFlags,
|
||||
};
|
||||
pub use secured_comp::{ComponentTrait, SecuredComponent, SecuredComponentBuilder};
|
||||
pub use serializing::{bytesize, serialize_to_bytes};
|
||||
|
||||
@@ -126,6 +126,12 @@ pub enum Error {
|
||||
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
|
||||
#[error(transparent)]
|
||||
Deku(#[from] deku::DekuError),
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
mod brb;
|
||||
mod builder;
|
||||
mod flags;
|
||||
mod generic_flags;
|
||||
mod hdr_v1;
|
||||
mod hdr_v2;
|
||||
mod keys;
|
||||
|
||||
pub use brb::{
|
||||
ComponentMetadata, ComponentMetadataV1, EnvelopeSeHdrV1, SeH, SeHdr, SeHdrBinV1, SeHdrData,
|
||||
SeHdrDataV1, SeHdrPlain, SeHdrVersion, SeHdrVersioned,
|
||||
ComponentMetadata, ComponentMetadataV1, EnvelopeSeHdrV1, SeH, SeHdr, SeHdrBinV1, SeHdrBinV2,
|
||||
SeHdrData, SeHdrDataV1, SeHdrDataV2, SeHdrPlain, SeHdrVersion, SeHdrVersioned,
|
||||
};
|
||||
pub use builder::SeHdrBuilder;
|
||||
pub use flags::{
|
||||
ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1,
|
||||
SecretControlFlagsV1,
|
||||
ControlFlagTrait, EffectiveControlFlags, FlagData, FlagState, FlagsOverride, SeHdrControlFlags,
|
||||
SeHdrControlFlagsModel, SeHdrFlag, SeTarget,
|
||||
};
|
||||
pub use generic_flags::{IntoEnumIterator, UnknownFlags};
|
||||
pub use hdr_v1::SeHdrAadV1;
|
||||
|
||||
@@ -8,14 +8,15 @@ use std::mem::size_of;
|
||||
use deku::ctx::Endian;
|
||||
use deku::prelude::*;
|
||||
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::static_assert;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utils::S390ToolsMetaData;
|
||||
use utils::{HkdVersion, S390ToolsMetaData};
|
||||
|
||||
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::pv_utils::error::{Error, Result};
|
||||
use crate::pv_utils::misc::display_indented;
|
||||
@@ -60,6 +61,17 @@ impl EnvelopeSeHdrV1 {
|
||||
pub enum SeHdrVersion {
|
||||
/// Secure Execution header v1
|
||||
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 {
|
||||
@@ -69,6 +81,7 @@ impl Display for SeHdrVersion {
|
||||
"{}",
|
||||
match self {
|
||||
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)]
|
||||
#[serde(untagged)]
|
||||
#[deku(ctx = "_endian: Endian, version: SeHdrVersion", id = "version")]
|
||||
pub enum SeHdrVersioned {
|
||||
#[deku(id = "SeHdrVersion::V1")]
|
||||
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 {
|
||||
@@ -223,10 +276,18 @@ impl Display for SeHdrVersioned {
|
||||
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(
|
||||
AeadCipherTrait,
|
||||
AeadPlainDataTrait,
|
||||
@@ -239,6 +300,8 @@ impl Display for SeHdrVersioned {
|
||||
pub enum SeHdrData {
|
||||
#[deku(id = "SeHdrVersion::V1")]
|
||||
SeHdrDataV1(SeHdrDataV1),
|
||||
#[deku(id = "SeHdrVersion::V2")]
|
||||
SeHdrDataV2(SeHdrDataV2),
|
||||
}
|
||||
|
||||
impl Display for SeHdrData {
|
||||
@@ -251,6 +314,13 @@ impl Display for SeHdrData {
|
||||
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<()> {
|
||||
match self {
|
||||
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 {
|
||||
// Payload related methods
|
||||
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)]
|
||||
@@ -276,7 +347,7 @@ pub trait SeHdrConfBuilderTrait {
|
||||
fn generate_cck(&self) -> Result<SymKey>;
|
||||
fn set_cck(&mut self, cck: Confidential<Vec<u8>>) -> Result<()>;
|
||||
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)]
|
||||
@@ -316,14 +387,13 @@ impl AeadDataTrait 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 {
|
||||
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>> {
|
||||
self.data.cust_pub_key()
|
||||
}
|
||||
@@ -331,6 +401,13 @@ impl KeyExchangeTrait for SeHdr {
|
||||
fn key_type(&self) -> SymKeyType {
|
||||
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 {
|
||||
@@ -407,25 +484,46 @@ impl AeadCipherBuilderTrait for SeHdrPlain {
|
||||
}
|
||||
|
||||
impl KeyExchangeBuilderTrait for SeHdrPlain {
|
||||
type AeadKeyType = SymKey;
|
||||
type PrivateKeyType = PKey<Private>;
|
||||
type TargetKeyType = pv::request::HostKey;
|
||||
|
||||
fn add_keyslot(
|
||||
&mut self,
|
||||
hostkey: &PKeyRef<Public>,
|
||||
aead_key: &SymKey,
|
||||
priv_key: &PKeyRef<Private>,
|
||||
hostkey: &Self::TargetKeyType,
|
||||
aead_key: &Self::AeadKeyType,
|
||||
priv_key: &Self::PrivateKeyType,
|
||||
) -> 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<()> {
|
||||
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>> {
|
||||
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<()> {
|
||||
self.data.set_cust_public_key(key)
|
||||
fn set_cust_public_key(&mut self, key: &Self::PrivateKeyType) -> Result<()> {
|
||||
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 {
|
||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
||||
self.data.contains(key)
|
||||
}
|
||||
type PrivateKeyType = PKey<Private>;
|
||||
type TargetKeyType = pv::request::HostKey;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 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::se_hdr::brb::{
|
||||
SeHdrCommon, SeHdrConfBuilderTrait, SeHdrData, SeHdrPubBuilderTrait,
|
||||
@@ -15,10 +16,10 @@ use crate::pv_utils::uvdata::UvDataPlainTrait;
|
||||
use crate::pv_utils::uvdata_builder::{
|
||||
AeadCipherBuilderTrait, BuilderTrait, KeyExchangeBuilderTrait, UvDataBuilder,
|
||||
};
|
||||
use crate::pv_utils::{PlaintextControlFlagsV1, SecretControlFlagsV1, PSW};
|
||||
use crate::pv_utils::PSW;
|
||||
|
||||
/// `SeHdrBuilder`
|
||||
pub type SeHdrBuilder<'a> = UvDataBuilder<'a, SeHdrPlain>;
|
||||
pub type SeHdrBuilder<'a> = UvDataBuilder<SeHdrPlain>;
|
||||
|
||||
impl SeHdrBuilder<'_> {
|
||||
pub fn new<M: Into<ComponentMetadata>>(
|
||||
@@ -40,6 +41,19 @@ impl SeHdrBuilder<'_> {
|
||||
data.set_cust_public_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 hdr = SeHdrPlain { common, data };
|
||||
@@ -67,12 +81,12 @@ impl SeHdrBuilder<'_> {
|
||||
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)?;
|
||||
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)?;
|
||||
Ok(self)
|
||||
}
|
||||
@@ -95,8 +109,8 @@ impl BuilderTrait for SeHdrBuilder<'_> {
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
|
||||
use pv::request::{Confidential, SymKeyType, SHA_512_HASH_LEN};
|
||||
use pv::test_utils::get_test_key_and_cert;
|
||||
use pv::request::{Confidential, HostKey, HybridPKey, SymKeyType, SHA_512_HASH_LEN};
|
||||
use pv::test_utils::{get_test_key_and_cert, get_test_key_and_cert_hybrid};
|
||||
|
||||
use super::*;
|
||||
use crate::pv_utils::se_hdr::ComponentMetadataV1;
|
||||
@@ -108,7 +122,7 @@ mod tests {
|
||||
use pv::test_utils::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_key2 = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
|
||||
let mut builder = SeHdrBuilder::new(
|
||||
@@ -236,10 +250,140 @@ mod tests {
|
||||
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]
|
||||
fn chain_test() {
|
||||
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 meta = ComponentMetadataV1 {
|
||||
ald: [0x1; SHA_512_HASH_LEN],
|
||||
@@ -274,4 +418,49 @@ mod tests {
|
||||
assert_eq!(hdr_plain.common.version, hdr.common.version);
|
||||
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
878
rust/pvimg/src/pv_utils/se_hdr/generic_flags.rs
Normal file
878
rust/pvimg/src/pv_utils/se_hdr/generic_flags.rs
Normal file
@@ -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 super::keys::phkh_v1;
|
||||
use super::{EffectiveControlFlags, SeHdrControlFlags};
|
||||
use crate::error::Error;
|
||||
use crate::misc::PAGESIZE;
|
||||
use crate::pv_utils::error::Result;
|
||||
@@ -37,9 +38,7 @@ use crate::pv_utils::uvdata::{
|
||||
UvDataTrait,
|
||||
};
|
||||
use crate::pv_utils::uvdata_builder::{AeadCipherBuilderTrait, KeyExchangeBuilderTrait};
|
||||
use crate::pv_utils::{
|
||||
try_copy_slice_to_array, PlaintextControlFlagsV1, SecretControlFlagsV1, PSW,
|
||||
};
|
||||
use crate::pv_utils::{try_copy_slice_to_array, SeHdrFlag, SeTarget, PSW};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HdrSizesV1 {
|
||||
@@ -100,17 +99,15 @@ impl Display for SeHdrAadV1 {
|
||||
writeln!(
|
||||
f,
|
||||
"plaintext control flags:\n{}",
|
||||
PlaintextControlFlagsV1::from(self.pcf)
|
||||
SeHdrControlFlags::from_u64(self.pcf, SeTarget::V1Max, true)
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyExchangeTrait for SeHdrAadV1 {
|
||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
||||
let phkh = phkh_v1(key)?;
|
||||
Ok(self.contains_hash(phkh))
|
||||
}
|
||||
type PrivateKeyType = PKey<Private>;
|
||||
type TargetKeyType = PKeyRef<Public>;
|
||||
|
||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||
self.cust_pub_key.clone().try_into()
|
||||
@@ -129,6 +126,14 @@ impl KeyExchangeTrait for SeHdrAadV1 {
|
||||
}
|
||||
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)]
|
||||
@@ -176,7 +181,7 @@ impl Display for SeHdrConfV1 {
|
||||
writeln!(
|
||||
f,
|
||||
"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.
|
||||
@@ -413,11 +418,15 @@ impl UvDataPlainTrait for SeHdrDataV1 {
|
||||
impl SeHdrPlainTrait for SeHdrDataV1 {}
|
||||
|
||||
impl KeyExchangeBuilderTrait for SeHdrDataV1 {
|
||||
type AeadKeyType = SymKey;
|
||||
type PrivateKeyType = PKeyRef<Private>;
|
||||
type TargetKeyType = PKeyRef<Public>;
|
||||
|
||||
fn add_keyslot(
|
||||
&mut self,
|
||||
hostkey: &PKeyRef<Public>,
|
||||
aead_key: &SymKey,
|
||||
priv_key: &PKeyRef<Private>,
|
||||
hostkey: &Self::TargetKeyType,
|
||||
aead_key: &Self::AeadKeyType,
|
||||
priv_key: &Self::PrivateKeyType,
|
||||
) -> Result<()> {
|
||||
let keyslot = Keyslot::new(HostKey::V1(hostkey.to_owned()));
|
||||
let keyslot_bin = keyslot.encrypt(aead_key.value(), priv_key)?.try_into()?;
|
||||
@@ -461,9 +470,8 @@ impl KeyExchangeBuilderTrait for SeHdrDataV1 {
|
||||
}
|
||||
|
||||
impl KeyExchangeTrait for SeHdrDataV1 {
|
||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
||||
self.aad.contains(key)
|
||||
}
|
||||
type PrivateKeyType = PKey<Private>;
|
||||
type TargetKeyType = PKeyRef<Public>;
|
||||
|
||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||
self.aad.cust_pub_key()
|
||||
@@ -476,6 +484,13 @@ impl KeyExchangeTrait for SeHdrDataV1 {
|
||||
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 SeHdrDataV1 {
|
||||
@@ -483,8 +498,8 @@ impl SeHdrConfBuilderTrait for SeHdrDataV1 {
|
||||
self.data.value_mut().psw = psw.clone();
|
||||
}
|
||||
|
||||
fn set_scf(&mut self, scf: &SecretControlFlagsV1) -> Result<()> {
|
||||
self.data.value_mut().scf = scf.into();
|
||||
fn set_scf(&mut self, scf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()> {
|
||||
self.data.value_mut().scf = scf.to_u64();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -499,8 +514,8 @@ impl SeHdrConfBuilderTrait for SeHdrDataV1 {
|
||||
}
|
||||
|
||||
impl SeHdrPubBuilderTrait for SeHdrDataV1 {
|
||||
fn set_pcf(&mut self, pcf: &PlaintextControlFlagsV1) -> Result<()> {
|
||||
self.aad.pcf = pcf.into();
|
||||
fn set_pcf(&mut self, pcf: &EffectiveControlFlags<SeHdrFlag>) -> Result<()> {
|
||||
self.aad.pcf = pcf.to_u64();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -591,9 +606,8 @@ impl AeadCipherBuilderTrait for SeHdrDataV1 {
|
||||
}
|
||||
|
||||
impl KeyExchangeTrait for SeHdrBinV1 {
|
||||
fn contains<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
|
||||
self.aad.contains(key)
|
||||
}
|
||||
type PrivateKeyType = PKey<Private>;
|
||||
type TargetKeyType = PKeyRef<Public>;
|
||||
|
||||
fn cust_pub_key(&mut self) -> Result<PKey<Public>> {
|
||||
self.aad.cust_pub_key()
|
||||
@@ -606,6 +620,13 @@ impl KeyExchangeTrait for SeHdrBinV1 {
|
||||
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 SeHdrBinV1 {
|
||||
@@ -655,6 +676,7 @@ mod tests {
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use pv::request::HostKey;
|
||||
use pv::test_utils::get_test_key_and_cert;
|
||||
|
||||
use super::*;
|
||||
@@ -663,7 +685,7 @@ mod tests {
|
||||
#[test]
|
||||
fn iv_keys_auto_generation_test() {
|
||||
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(
|
||||
SeHdrVersion::V1,
|
||||
PSW {
|
||||
@@ -685,7 +707,7 @@ mod tests {
|
||||
#[test]
|
||||
fn chain_test() {
|
||||
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 meta = ComponentMetadataV1 {
|
||||
ald: [0x1; SHA_512_HASH_LEN],
|
||||
@@ -731,7 +753,7 @@ mod tests {
|
||||
const MAX_HOST_KEYS: usize = 95;
|
||||
|
||||
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 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]);
|
||||
|
||||
916
rust/pvimg/src/pv_utils/se_hdr/hdr_v2.rs
Normal file
916
rust/pvimg/src/pv_utils/se_hdr/hdr_v2.rs
Normal file
@@ -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 openssl::hash::{hash, MessageDigest};
|
||||
use openssl::pkey::{PKey, PKeyRef, Public};
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use pv::request::{EcPubKeyCoord, HybridPKey};
|
||||
use pv::static_assert;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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 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)]
|
||||
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
|
||||
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)]
|
||||
mod serde_tests {
|
||||
use super::*;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::io::{BufRead, BufReader, Read};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
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};
|
||||
|
||||
/// 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)]
|
||||
pub struct UvKeyHashesV1 {
|
||||
pub pchkh: UvKeyHashV1,
|
||||
pub pbhkh: UvKeyHashV1,
|
||||
pub res: [UvKeyHashV1; 13],
|
||||
pub hashes: [UvKeyHashV1; 15],
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
@@ -107,9 +208,8 @@ impl UvKeyHashesV1 {
|
||||
return Err(Error::InvalidUvKeyHashes);
|
||||
}
|
||||
|
||||
let [pchkh, pbhkh, res @ ..]: [UvKeyHashV1; 15] =
|
||||
hashes.try_into().map_err(|_| Error::InvalidUvKeyHashes)?;
|
||||
Ok(Self { pchkh, pbhkh, res })
|
||||
let hashes: [UvKeyHashV1; 15] = hashes.try_into().map_err(|_| Error::InvalidUvKeyHashes)?;
|
||||
Ok(Self { hashes })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,21 +241,18 @@ mod tests {
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
";
|
||||
let result = UvKeyHashesV1::read_from_io(Cursor::new(data)).expect("should not fail");
|
||||
assert_eq!(
|
||||
result,
|
||||
UvKeyHashesV1 {
|
||||
pchkh: UvKeyHashV1::new(
|
||||
decode_hex("0b729fd62241b339840d61b964a06bb6a1fd4976d9ebea2b4fb48d44de3a2461")
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap(),
|
||||
pbhkh: UvKeyHashV1::new(
|
||||
decode_hex("8ec6bc2f77d5d6474b1417cf0a8c914f576245a5b9bb0eefacc7b821483ece7d")
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap(),
|
||||
res: [UvKeyHashV1::UV_KEY_HASH_NULL; 13],
|
||||
}
|
||||
);
|
||||
let mut exp_hashes = [UvKeyHashV1::UV_KEY_HASH_NULL; 15];
|
||||
exp_hashes[0] = UvKeyHashV1::new(
|
||||
decode_hex("0b729fd62241b339840d61b964a06bb6a1fd4976d9ebea2b4fb48d44de3a2461").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
exp_hashes[1] = UvKeyHashV1::new(
|
||||
decode_hex("8ec6bc2f77d5d6474b1417cf0a8c914f576245a5b9bb0eefacc7b821483ece7d").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let uv_hashes = UvKeyHashesV1 { hashes: exp_hashes };
|
||||
assert_eq!(result, uv_hashes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use pv::request::openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
use pv::request::{
|
||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, Confidential, SymKey, SymKeyType,
|
||||
};
|
||||
use pv::request::openssl::pkey::{PKey, Public};
|
||||
use pv::request::{decrypt_aead, 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::serializing::deserialize_from_bytes;
|
||||
|
||||
@@ -54,15 +52,19 @@ pub trait AeadPlainDataTrait {
|
||||
}
|
||||
|
||||
/// Key exchange related methods
|
||||
#[enum_dispatch]
|
||||
pub trait KeyExchangeTrait {
|
||||
/// Checks if a public key was used.
|
||||
type TargetKeyType;
|
||||
type PrivateKeyType: ToOwned;
|
||||
|
||||
/// Checks if a public target key was used.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the public key cannot be converted
|
||||
/// 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.
|
||||
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.
|
||||
fn key_type(&self) -> SymKeyType;
|
||||
|
||||
/// Derive the key.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if there is no customer public key is
|
||||
/// available or the key derivations fails.
|
||||
fn derive_key<K: AsRef<PKeyRef<Private>>>(&mut self, other_priv_key: K) -> Result<SymKey> {
|
||||
match self.key_type() {
|
||||
SymKeyType::Aes256Gcm => Ok(derive_aes256_gcm_key(
|
||||
other_priv_key.as_ref(),
|
||||
self.cust_pub_key()?.as_ref(),
|
||||
)?
|
||||
.into()),
|
||||
_ => unreachable!("BUG"),
|
||||
}
|
||||
}
|
||||
// TODO Implement it
|
||||
// /// Derive the key.
|
||||
// ///
|
||||
// /// # Errors
|
||||
// ///
|
||||
// /// This function will return an error if there is no customer public key is
|
||||
// /// available or the key derivations fails.
|
||||
// fn derive_key<K>(&mut self, other_priv_key: K) -> Result<SymKey> where
|
||||
// K: AsRef<Self::PrivateKeyType>{
|
||||
// match self.key_type() {
|
||||
// SymKeyType::Aes256Gcm =>
|
||||
// match Self::TargetKeyType {
|
||||
// PKeyRef::<Public> => {todo!()},
|
||||
// HybridPKey => {todo!()},
|
||||
// }
|
||||
// _ => unreachable!("BUG"),
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/// Trait to be used for plain UV data.
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use pv::request::{Confidential, SymKey};
|
||||
|
||||
use super::Error;
|
||||
use crate::pv_utils::error::Result;
|
||||
use crate::pv_utils::se_hdr::SeHdrData;
|
||||
use crate::pv_utils::uvdata::{AeadCipherTrait, UvDataPlainTrait};
|
||||
|
||||
#[enum_dispatch]
|
||||
@@ -20,33 +21,33 @@ pub trait AeadCipherBuilderTrait: AeadCipherTrait {
|
||||
}
|
||||
|
||||
/// Key exchange related methods
|
||||
#[enum_dispatch]
|
||||
pub trait KeyExchangeBuilderTrait {
|
||||
type TargetKeyType: ToOwned;
|
||||
type AeadKeyType: Display + std::fmt::Debug;
|
||||
type PrivateKeyType: ToOwned;
|
||||
|
||||
fn add_keyslot(
|
||||
&mut self,
|
||||
hostkey: &PKeyRef<Public>,
|
||||
aead_key: &SymKey,
|
||||
priv_key: &PKeyRef<Private>,
|
||||
hostkey: &Self::TargetKeyType,
|
||||
aead_key: &Self::AeadKeyType,
|
||||
priv_key: &Self::PrivateKeyType,
|
||||
) -> Result<()>;
|
||||
|
||||
fn clear_keyslots(&mut self) -> Result<()>;
|
||||
// TODO How to handle PKey vs &PKeyRef?
|
||||
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<
|
||||
'a,
|
||||
T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait,
|
||||
K = PKeyRef<Public>,
|
||||
P = PKey<Private>,
|
||||
> {
|
||||
pub struct UvDataBuilder<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> {
|
||||
pub(crate) expert_mode: bool,
|
||||
pub(crate) prot_key: SymKey,
|
||||
pub(crate) priv_key: P,
|
||||
pub(crate) target_keys: Vec<&'a K>,
|
||||
pub(crate) prot_key: T::AeadKeyType,
|
||||
pub(crate) priv_key: T::PrivateKeyType,
|
||||
pub(crate) target_keys: Vec<T::TargetKeyType>,
|
||||
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.
|
||||
pub fn i_know_what_i_am_doing(&mut self) {
|
||||
self.expert_mode = true;
|
||||
@@ -54,7 +55,7 @@ impl<T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait, K, P> UvDataBuilder<'_
|
||||
}
|
||||
|
||||
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 {
|
||||
f.debug_struct("UvDataBuilder")
|
||||
@@ -65,15 +66,18 @@ impl<T: std::fmt::Debug + KeyExchangeBuilderTrait + AeadCipherBuilderTrait + UvD
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a, T> {
|
||||
pub fn add_hostkeys<P: AsRef<PKeyRef<Public>>>(
|
||||
&mut self,
|
||||
hostkeys: &'a [P],
|
||||
) -> Result<&mut Self> {
|
||||
impl<T> UvDataBuilder<T>
|
||||
where
|
||||
T: KeyExchangeBuilderTrait<AeadKeyType = SymKey> + AeadCipherBuilderTrait,
|
||||
{
|
||||
pub fn add_hostkeys(&mut self, hostkeys: &[T::TargetKeyType]) -> Result<&mut Self>
|
||||
where
|
||||
T::TargetKeyType: Clone,
|
||||
{
|
||||
for hk in hostkeys {
|
||||
self.plain_data
|
||||
.add_keyslot(hk.as_ref(), &self.prot_key, &self.priv_key)?;
|
||||
self.target_keys.push(hk.as_ref());
|
||||
.add_keyslot(hk, &self.prot_key, &self.priv_key)?;
|
||||
self.target_keys.push(hk.clone());
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
@@ -89,9 +93,9 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a,
|
||||
|
||||
fn update_target_key_slots(&mut self) -> Result<()> {
|
||||
self.plain_data.clear_keyslots()?;
|
||||
for hk in &self.target_keys {
|
||||
for hostkey in &self.target_keys {
|
||||
self.plain_data
|
||||
.add_keyslot(hk, &self.prot_key, &self.priv_key)?;
|
||||
.add_keyslot(hostkey, &self.prot_key, &self.priv_key)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -100,6 +104,7 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a,
|
||||
if !self.expert_mode {
|
||||
return Err(Error::NonExpertMode);
|
||||
}
|
||||
// TODO Implement TryFrom<...> ?!
|
||||
let key = SymKey::try_from_data(self.plain_data.aead_key_type(), data)?;
|
||||
self.prot_key = key;
|
||||
self.update_target_key_slots()?;
|
||||
@@ -107,23 +112,26 @@ impl<'a, T: KeyExchangeBuilderTrait + AeadCipherBuilderTrait> UvDataBuilder<'a,
|
||||
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 {
|
||||
return Err(Error::NonExpertMode);
|
||||
}
|
||||
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()?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub const fn prot_key(&self) -> &SymKey {
|
||||
pub const fn prot_key(&self) -> &<T as KeyExchangeBuilderTrait>::AeadKeyType {
|
||||
&self.prot_key
|
||||
}
|
||||
|
||||
pub fn priv_key(&self) -> &PKeyRef<Private> {
|
||||
self.priv_key.as_ref()
|
||||
pub fn priv_key(&self) -> &<T as KeyExchangeBuilderTrait>::PrivateKeyType {
|
||||
&self.priv_key
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,17 +10,14 @@ use std::rc::Rc;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use deku::DekuContainerRead;
|
||||
use log::debug;
|
||||
use openssl::pkey::{PKey, Public};
|
||||
use pv::misc::read_file;
|
||||
use pv::request::Confidential;
|
||||
use pv::request::{Confidential, HostKey};
|
||||
use pvimg::error::Error;
|
||||
use pvimg::misc::{round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
|
||||
use pvimg::secured_comp::{
|
||||
ComponentTrait, Interval, Layout, SecuredComponent, SecuredComponentBuilder,
|
||||
};
|
||||
use pvimg::uvdata::{
|
||||
BuilderTrait, PlaintextControlFlagsV1, SeHdrBuilder, SeHdrVersion, SecretControlFlagsV1,
|
||||
};
|
||||
use pvimg::uvdata::{BuilderTrait, EffectiveControlFlags, SeHdrBuilder, SeHdrFlag, SeHdrVersion};
|
||||
|
||||
use crate::se_img_comps::ipib::Ipib;
|
||||
use crate::se_img_comps::kernel::S390Kernel;
|
||||
@@ -33,9 +30,9 @@ use crate::se_img_comps::{
|
||||
};
|
||||
|
||||
pub struct SeHdrArgs<'a> {
|
||||
pub keys: &'a [PKey<Public>],
|
||||
pub pcf: &'a PlaintextControlFlagsV1,
|
||||
pub scf: &'a SecretControlFlagsV1,
|
||||
pub keys: &'a [HostKey],
|
||||
pub pcf: &'a EffectiveControlFlags<SeHdrFlag>,
|
||||
pub scf: &'a EffectiveControlFlags<SeHdrFlag>,
|
||||
pub cck: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||
pub hdr_aead_key: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||
pub psw_addr: &'a Option<u64>,
|
||||
@@ -95,7 +92,7 @@ impl<W: Write + Seek> SeImgBuilder<W> {
|
||||
|
||||
/// Create a Secure Execution boot image builder
|
||||
#[allow(clippy::similar_names)]
|
||||
pub(crate) fn new_v1(
|
||||
pub(crate) fn new(
|
||||
mut writer: W,
|
||||
encryption: bool,
|
||||
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>> {
|
||||
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(
|
||||
SeHdrVersion::V1,
|
||||
version,
|
||||
PSW {
|
||||
addr: sehdr_args.psw_addr.unwrap_or(stage3b_entry),
|
||||
mask: Self::DEFAULT_INITIAL_PSW_MASK,
|
||||
@@ -497,7 +511,7 @@ mod tests {
|
||||
|
||||
let encryption = true;
|
||||
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());
|
||||
let ctx = ctx_res.unwrap();
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ use std::fmt::Display;
|
||||
use clap::error::ErrorKind::ValueValidation;
|
||||
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
|
||||
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.
|
||||
@@ -34,6 +35,28 @@ pub enum CreateSecretFlags {
|
||||
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)]
|
||||
#[command(group(ArgGroup::new("as-ext").args(["cck", "extension_secret"])),)]
|
||||
pub struct CreateSecretOpt {
|
||||
@@ -173,6 +196,10 @@ pub struct CreateSecretOpt {
|
||||
/// Ignored for meta-secrets.
|
||||
#[arg(long)]
|
||||
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)]
|
||||
|
||||
@@ -8,7 +8,7 @@ pub use create::create;
|
||||
mod verify;
|
||||
pub use verify::verify;
|
||||
|
||||
pub const CMD_FN: &[&str] = &["+create", "+verify"];
|
||||
pub const CMD_FN: &[&str] = &["+create", "+verify", "+quantumsafe"];
|
||||
|
||||
#[cfg(target_arch = "s390x")]
|
||||
mod add;
|
||||
|
||||
@@ -22,7 +22,10 @@ use serde_yaml::Value;
|
||||
use utils::get_writer_from_cli_file_arg;
|
||||
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<()>
|
||||
where
|
||||
@@ -83,6 +86,41 @@ fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Re
|
||||
.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
|
||||
pub fn create(opt: &CreateSecretOpt) -> Result<()> {
|
||||
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");
|
||||
|
||||
// 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
|
||||
let rq =
|
||||
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?}");
|
||||
|
||||
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))?;
|
||||
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
|
||||
read_cuid(&mut asrcb, opt)?;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use std::env;
|
||||
use std::io::Error;
|
||||
|
||||
use clap::{CommandFactory, ValueEnum};
|
||||
use clap::CommandFactory;
|
||||
use clap_complete::{generate_to, Shell};
|
||||
|
||||
include!("src/cli.rs");
|
||||
|
||||
@@ -3,11 +3,36 @@
|
||||
// Copyright IBM Corp. 2025
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use clap::{ArgAction, Parser};
|
||||
use utils::CertificateOptions;
|
||||
use clap::{ArgAction, Parser, ValueEnum};
|
||||
use utils::{
|
||||
AutoOrExplicit, AutoOrExplicitParser, CertificateOptions, HkdVersion, ValueEnumDisplay,
|
||||
ValueEnumFromStr,
|
||||
};
|
||||
|
||||
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)]
|
||||
#[command(long_version=ver(), disable_version_flag(true))]
|
||||
/// Tool to verify host-keys
|
||||
@@ -23,6 +48,10 @@ pub struct CliOptions {
|
||||
#[arg(long, action=ArgAction::Version)]
|
||||
/// Print version information and exit.
|
||||
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 {
|
||||
|
||||
@@ -14,9 +14,9 @@ static LOGGER: PvLogger = PvLogger;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
LOGGER.start(LevelFilter::Trace)?;
|
||||
cli::CliOptions::parse()
|
||||
.certificate_args
|
||||
.get_verified_hkds("info")?;
|
||||
let opt = cli::CliOptions::parse();
|
||||
opt.certificate_args
|
||||
.get_verified_hkds("info", opt.hkd_version.map(|v| v.into()))?;
|
||||
info!("Host-key documents verified.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,8 +9,11 @@ chrono = { version = "0.4.44", default-features = false, features = ["std"] }
|
||||
clap = { version ="4.6", features = ["derive", "wrap_help"] }
|
||||
libc = "0.2.186"
|
||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10"
|
||||
pv = { path = "../pv", package = "s390_pv" }
|
||||
serde = { version = "1.0.228"}
|
||||
|
||||
utils_macros = { path = "../utils_macros" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.149"
|
||||
|
||||
@@ -1,17 +1,136 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023, 2024
|
||||
|
||||
// Copyright IBM Corp.
|
||||
use std::fmt::Display;
|
||||
use std::io::{Read, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::{ArgAction, ArgGroup, Args, Command, ValueHint};
|
||||
use log::{info, warn, LevelFilter};
|
||||
use pv::misc::{create_file, open_file, read_certs, read_file};
|
||||
use pv::request::openssl::pkey::{PKey, Public};
|
||||
use pv::request::HkdVerifier;
|
||||
use pv::{Error, Result};
|
||||
use clap::builder::{EnumValueParser, PossibleValue, TypedValueParser};
|
||||
use clap::{Arg, ArgAction, ArgGroup, Args, Command, ValueEnum, ValueHint};
|
||||
use log::LevelFilter;
|
||||
use pv::misc::{create_file, open_file};
|
||||
use pv::request::{HkdVerifier, HostKey};
|
||||
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.
|
||||
#[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
|
||||
/// - `version`: requested host-key document version
|
||||
///
|
||||
/// # Error
|
||||
/// Returns an error if something went wrong during parsing the HKDs, the verification chain
|
||||
/// could not built, or when the verification
|
||||
/// 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 verifier = self.verifier(protectee)?;
|
||||
|
||||
let mut res = Vec::with_capacity(hkds.len());
|
||||
for hkd in hkds {
|
||||
let hk = read_file(hkd, "host-key document")?;
|
||||
let certs = read_certs(&hk).map_err(|source| Error::HkdNotPemOrDer {
|
||||
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());
|
||||
let host_key = HkdLoader::load_and_verify(hkd, verifier.as_ref(), requested_version)?;
|
||||
res.push(host_key);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
162
rust/utils/src/hkd.rs
Normal file
162
rust/utils/src/hkd.rs
Normal file
@@ -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 file;
|
||||
mod hexslice;
|
||||
pub mod hkd;
|
||||
mod hostname;
|
||||
mod json;
|
||||
mod log;
|
||||
mod tmpfile;
|
||||
|
||||
pub use ::log::LevelFilter;
|
||||
// Re-export procedural macros from utils_macros
|
||||
pub use utils_macros::{ControlFlag, ValueEnumDisplay, ValueEnumFromStr};
|
||||
|
||||
pub use crate::cli::{
|
||||
combined_path_opt, combined_path_req, get_reader_from_cli_file_arg,
|
||||
get_writer_from_cli_file_arg, print_cli_error, print_error, CertificateOptions,
|
||||
DeprecatedVerbosityOptions, VerbosityOptions, STDIN, STDOUT,
|
||||
get_writer_from_cli_file_arg, print_cli_error, print_error, AutoOrExplicit,
|
||||
AutoOrExplicitParser, CertificateOptions, DeprecatedVerbosityOptions, VerbosityOptions, STDIN,
|
||||
STDOUT,
|
||||
};
|
||||
pub use crate::exit_code::{docstring, ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc};
|
||||
pub use crate::file::{AtomicFile, AtomicFileOperation};
|
||||
pub use crate::hexslice::HexSlice;
|
||||
pub use crate::hkd::{HkdLoader, HkdVersion, HkdVersionSelection};
|
||||
pub use crate::hostname::gethostname;
|
||||
pub use crate::json::S390ToolsMetaData;
|
||||
pub use crate::log::PvLogger;
|
||||
|
||||
259
rust/utils/tests/malformed_hkd.rs
Normal file
259
rust/utils/tests/malformed_hkd.rs
Normal file
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
17
rust/utils_macros/Cargo.toml
Normal file
17
rust/utils_macros/Cargo.toml
Normal file
@@ -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"] }
|
||||
297
rust/utils_macros/src/lib.rs
Normal file
297
rust/utils_macros/src/lib.rs
Normal file
@@ -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()
|
||||
}
|
||||
247
rust/utils_macros/tests/integration_tests.rs
Normal file
247
rust/utils_macros/tests/integration_tests.rs
Normal file
@@ -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");
|
||||
}
|
||||
17
rust/utils_macros/tests/test_helpers.rs
Normal file
17
rust/utils_macros/tests/test_helpers.rs
Normal file
@@ -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>;
|
||||
}
|
||||
@@ -515,6 +515,7 @@ CMDS="${CMDS}\
|
||||
:lspci -vvv\
|
||||
:lstape\
|
||||
:smc_dbg\
|
||||
:zpcimon --module-info --smart-log-raw\
|
||||
"
|
||||
|
||||
# block device and other scsi device commands (in non alphabetical order)
|
||||
@@ -629,7 +630,7 @@ NETWORK_CMDS="ip -br a\
|
||||
:nstat -az\
|
||||
:openssl engine # deprecated in OpenSSL 3.n\
|
||||
: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\
|
||||
"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ SYSTEM_UNITS = ttyrun-getty@.service iucvtty-login@.service \
|
||||
cpacfstatsd.service cpuplugd.service \
|
||||
dumpconf.service cpi.service \
|
||||
mon_fsstatd.service mon_procd.service \
|
||||
opticsmon.service
|
||||
zpcimon.service
|
||||
|
||||
all:
|
||||
|
||||
@@ -19,6 +19,9 @@ install: system_units
|
||||
$(INSTALL) -g $(GROUP) -o $(OWNER) \
|
||||
-m 644 $$unit $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) ; \
|
||||
done
|
||||
# Make the legacy opticsmon.service a link to zpcimon.service
|
||||
ln -sf zpcimon.service \
|
||||
$(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)/opticsmon.service
|
||||
|
||||
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
|
||||
#
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
[Unit]
|
||||
Description=Monitor health of directly attached PCI NIC optical modules
|
||||
# Documentation=man:opticsmon
|
||||
# Documentation=man:zpcimon
|
||||
|
||||
[Service]
|
||||
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
|
||||
|
||||
[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
|
||||
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
|
||||
.BR "\-i" " or " "\-\-ascii"
|
||||
Use only ASCII characters to render views.
|
||||
|
||||
@@ -46,6 +46,7 @@ static struct zmemtopo_globals {
|
||||
unsigned int sort_field;
|
||||
unsigned int ascii;
|
||||
unsigned int fmt_specified;
|
||||
char *partition_filter;
|
||||
enum util_fmt_t format;
|
||||
enum util_fmt_flags_t fmt_flags;
|
||||
} g;
|
||||
@@ -62,15 +63,11 @@ static void parse_nesting_level(char *arg)
|
||||
|
||||
static void parse_sort_field(char *arg)
|
||||
{
|
||||
char *s;
|
||||
|
||||
s = util_strdup(arg);
|
||||
util_strstrip(s);
|
||||
if (strcasecmp(s, "nr") == 0)
|
||||
if (strcasecmp(arg, "nr") == 0)
|
||||
g.sort_field = SORT_NR;
|
||||
else if (strcasecmp(s, "lpar") == 0)
|
||||
else if (strcasecmp(arg, "lpar") == 0)
|
||||
g.sort_field = SORT_NAME;
|
||||
else if (strcasecmp(s, "size") == 0)
|
||||
else if (strcasecmp(arg, "size") == 0)
|
||||
g.sort_field = SORT_SIZE;
|
||||
else
|
||||
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':
|
||||
parse_sort_field(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
g.partition_filter = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
g.ascii = 1;
|
||||
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)
|
||||
{
|
||||
ebcdic_to_ascii(pname, part->part_name, LPAR_NAME_LEN);
|
||||
util_strstrip(part->part_name);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
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_NAME_LEN, 0, "%s", cur->part_name);
|
||||
concat_w_padding(buf, LPAR_NO_LEN, 1, "%2d", cur->part_nr);
|
||||
concat_w_padding(buf, LPAR_NAME_LEN, 1, "%s", cur->part_name);
|
||||
concat_w_padding(buf, s_padding, 0, "%lu", cur->increment_total);
|
||||
entries = &cur->entries[g.nesting_level - 1];
|
||||
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, "%*s", LPAR_NO_LEN, "NR");
|
||||
util_concatf(buf, "%*s", LPAR_NAME_LEN, "LPAR");
|
||||
util_concatf(buf, "%-*s", LPAR_NO_LEN, "NR");
|
||||
util_concatf(buf, "%-*s", LPAR_NAME_LEN, "LPAR");
|
||||
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);
|
||||
partition_list_calculate_level_lengths(parts, 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);
|
||||
}
|
||||
printf("%s\n", *table);
|
||||
printf("Increment size: %lu%s\n", unit.size / unit.scale, unit.suffix);
|
||||
free(vdata);
|
||||
@@ -721,6 +739,8 @@ static void tree_create(char **tree, struct partitions *parts,
|
||||
unit = vdata->unit;
|
||||
tree_create_header(tree, vdata);
|
||||
util_list_iterate(parts->list, cur) {
|
||||
if (!partition_filter_matches(cur))
|
||||
continue;
|
||||
util_concatf(tree, "%-*s", vdata->cell_len, cur->part_name);
|
||||
part_size = cur->increment_total * unit.size / unit.scale;
|
||||
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
|
||||
next = util_list_next(parts->list, cur);
|
||||
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 0;
|
||||
@@ -777,6 +799,8 @@ static void rtree_print_parts(char **buf, struct partitions *parts,
|
||||
unit = vdata->unit;
|
||||
flag_idx = (g.max_level - level) + 1;
|
||||
util_list_iterate(parts->list, cur) {
|
||||
if (!partition_filter_matches(cur))
|
||||
continue;
|
||||
entries = &cur->entries[level - 1];
|
||||
if (!entries->increments[idx])
|
||||
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,
|
||||
unsigned int step, unsigned int level,
|
||||
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)
|
||||
continue;
|
||||
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)
|
||||
indent = tree_add_indent(buf, level, vdata->end_flag);
|
||||
concat_w_padding(buf, vdata->cell_len - indent, 1, "LEVEL%u_%u",
|
||||
|
||||
@@ -68,9 +68,9 @@ struct diag310_t_hdr {
|
||||
} __packed;
|
||||
|
||||
struct diag310_p_hdr {
|
||||
char pname[8]; /* partition name */
|
||||
uint8_t pn; /* partition number */
|
||||
uint8_t tie; /* count of entries following this header */
|
||||
char pname[LPAR_NAME_LEN]; /* partition name */
|
||||
uint8_t pn; /* partition number */
|
||||
uint8_t tie; /* count of entries following this header */
|
||||
uint16_t reserved1;
|
||||
uint32_t reserved2;
|
||||
} __packed;
|
||||
|
||||
@@ -36,6 +36,10 @@ static struct util_opt opt_vec[] = {
|
||||
.option = { "sort", required_argument, NULL, 's' },
|
||||
.argument = "FIELD",
|
||||
.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' },
|
||||
.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