mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Compare commits
228 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 | ||
|
|
2a6657dfd1 | ||
|
|
b3dd05e16d | ||
|
|
f216862052 | ||
|
|
e4aeeef8eb | ||
|
|
c911419121 | ||
|
|
e6de495daa | ||
|
|
419d93bef7 | ||
|
|
2714f1d90e | ||
|
|
10ec3f18fc | ||
|
|
0c771423b4 | ||
|
|
f67fa03581 | ||
|
|
29db9032b3 | ||
|
|
56a55901c1 | ||
|
|
43edafd088 | ||
|
|
b1e3ee255f | ||
|
|
a69a46eafd | ||
|
|
c85b4e54dd | ||
|
|
da4b881eac | ||
|
|
9f03ddf519 | ||
|
|
a05935cca9 | ||
|
|
6f9f846bc0 | ||
|
|
603a2b1762 | ||
|
|
dcece3f980 | ||
|
|
ff19180f03 | ||
|
|
42eba67268 | ||
|
|
3cf95a1d92 | ||
|
|
5e93b53f78 | ||
|
|
360e8d4156 | ||
|
|
a048670bec | ||
|
|
4ca93aa808 | ||
|
|
9a5886c0b3 | ||
|
|
78676df2f0 | ||
|
|
224e35b600 | ||
|
|
bd52e14740 | ||
|
|
dd380942ed | ||
|
|
9ce9ed72a9 | ||
|
|
a8a87779c0 | ||
|
|
cdabf280ac | ||
|
|
33c75584cd | ||
|
|
1f68c1aaf3 | ||
|
|
6c6938d1e2 | ||
|
|
90ad42d814 | ||
|
|
cf7c74b2dc | ||
|
|
4388a6d299 | ||
|
|
44821d0586 | ||
|
|
405b2da3f3 | ||
|
|
d3ac9f3365 | ||
|
|
7535682b1b | ||
|
|
72f1a2e0a9 | ||
|
|
220f51fb98 | ||
|
|
5ab3473442 | ||
|
|
cf51aa1421 | ||
|
|
b1d44876bd | ||
|
|
c006800bd5 | ||
|
|
634095aa24 | ||
|
|
b4f575abc4 | ||
|
|
6c729fba9e | ||
|
|
71503c0e7d | ||
|
|
5c8bd68f6f | ||
|
|
927f901969 | ||
|
|
35cc35894b | ||
|
|
dfaa1791b7 | ||
|
|
52158e73fa | ||
|
|
cc6f1a6d7b | ||
|
|
866324bcd2 | ||
|
|
7fd2421a2c | ||
|
|
66581d57f3 | ||
|
|
de688f350f | ||
|
|
958ccb46c5 | ||
|
|
3ea90a2258 | ||
|
|
7d56dde24a | ||
|
|
c7d0d3c1b9 | ||
|
|
d7648875cc | ||
|
|
5fdeaab3d0 | ||
|
|
f0bf1985c3 | ||
|
|
a3a2d70603 | ||
|
|
226728731c | ||
|
|
94292dac54 | ||
|
|
278f4f6fd5 | ||
|
|
17007ab121 | ||
|
|
4f9e5d7842 | ||
|
|
8a52693acc | ||
|
|
a93146d976 | ||
|
|
187437c6c8 | ||
|
|
cdc787d92d | ||
|
|
5feee12827 | ||
|
|
cf70a27d76 | ||
|
|
5552b476bd | ||
|
|
7510b96238 | ||
|
|
778d9e436a | ||
|
|
692af4058c | ||
|
|
2565a4ca99 | ||
|
|
6d5f4a99de | ||
|
|
a9ed19285a | ||
|
|
4eb18bd6b7 | ||
|
|
cf45a6e108 | ||
|
|
a879036e3f | ||
|
|
5340d11165 | ||
|
|
162a041077 | ||
|
|
b38d5833fa | ||
|
|
ee8897f9db | ||
|
|
4f622325ef | ||
|
|
f2902a1990 | ||
|
|
9882aabe44 | ||
|
|
d06d197522 | ||
|
|
3fd07bb7f2 | ||
|
|
58ede126cc | ||
|
|
e4a03c6f71 | ||
|
|
c66495888d | ||
|
|
f85c8ebba0 | ||
|
|
e4e455630b | ||
|
|
d82beef937 | ||
|
|
c3e61e5e0f | ||
|
|
f04e46baf0 | ||
|
|
e93bd7e61c | ||
|
|
edf40976f0 | ||
|
|
e557f41c6e | ||
|
|
68fe816da9 | ||
|
|
367ca6e16b | ||
|
|
6c67f05ca7 | ||
|
|
4887e80995 | ||
|
|
74a338edfc | ||
|
|
8878ba4fd2 | ||
|
|
e53f5ccfea | ||
|
|
6e7eb62ea1 | ||
|
|
2bb61f96c0 | ||
|
|
a902fd9afe | ||
|
|
dc42460d82 | ||
|
|
6689e25865 | ||
|
|
20f6791fb4 | ||
|
|
f0a148fd0b | ||
|
|
390e48869e | ||
|
|
3635a19462 | ||
|
|
584e785f5c | ||
|
|
7fffdcfe8c | ||
|
|
44d6020774 | ||
|
|
30ce11b8da | ||
|
|
c7ab70df2c | ||
|
|
c953486dec | ||
|
|
610cc581da | ||
|
|
604c34e66b | ||
|
|
e2cd2e78c5 | ||
|
|
d04d331673 | ||
|
|
27f20495a7 | ||
|
|
679ab77b96 | ||
|
|
8bb20be528 | ||
|
|
d04ea8603d | ||
|
|
51d2c92067 | ||
|
|
7e3b8b8c16 | ||
|
|
f8fb9ce32a | ||
|
|
3d5f75d6c3 | ||
|
|
ddc8248b21 | ||
|
|
77083b1cbb | ||
|
|
21c2a04347 | ||
|
|
8f2d77c9d3 | ||
|
|
b6a4d7a6c1 | ||
|
|
f223069f0b | ||
|
|
19a5af8da9 | ||
|
|
03b73ab3f9 | ||
|
|
080a6678fb | ||
|
|
1d6f7d0bec | ||
|
|
a50d0485c9 | ||
|
|
d2a6a771a5 | ||
|
|
7568a0790f | ||
|
|
ca0ee966b8 | ||
|
|
36a7b2e6eb | ||
|
|
851f63eb03 | ||
|
|
3d679f61fc | ||
|
|
9237c5b675 | ||
|
|
d1ab6be082 | ||
|
|
d6c2bac99f |
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
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
edition = "2021"
|
||||
newline_style = "Unix"
|
||||
|
||||
# Unstable options that help catching some mistakes in formatting and that we may want to enable
|
||||
# when they become stable.
|
||||
#
|
||||
# They are kept here since they are useful to run from time to time.
|
||||
#comment_width = 100
|
||||
#format_code_in_doc_comments = true
|
||||
#group_imports = "StdExternalCrate"
|
||||
#normalize_comments = true
|
||||
# Unstable options that help catching some mistakes in formatting
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
use_field_init_shorthand = true
|
||||
comment_width = 100
|
||||
format_code_in_doc_comments = true
|
||||
normalize_comments = true
|
||||
wrap_comments = true
|
||||
#reorder_impl_items = true
|
||||
#wrap_comments = true
|
||||
|
||||
@@ -77,7 +77,6 @@ List of all individuals having contributed content to s390-tools
|
||||
- Jean-Baptiste Joret
|
||||
- Jens Remus
|
||||
- Jochen Roehrig
|
||||
- Joern Siglen
|
||||
- Juergen Christ
|
||||
- Julian Wiedmann
|
||||
- Jörn Siglen
|
||||
@@ -135,6 +134,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Steffen Eiden
|
||||
- Steffen Maier
|
||||
- Steffen Thoss
|
||||
- Stephen Gallagher
|
||||
- Sumanth Korikkar
|
||||
- Susanne Wintenberger
|
||||
- Sven Schnelle
|
||||
@@ -148,6 +148,7 @@ List of all individuals having contributed content to s390-tools
|
||||
- Thomas Spatzier
|
||||
- Thomas Weber
|
||||
- Thorsten Winkler
|
||||
- Timo Keller
|
||||
- Tobias Huschle
|
||||
- Tuan Hoang
|
||||
- Ursula Braun
|
||||
|
||||
104
CHANGELOG.md
104
CHANGELOG.md
@@ -1,6 +1,110 @@
|
||||
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
|
||||
|
||||
Changes of existing tools:
|
||||
- dasdfmt: Check disk type before prompting for blocksize
|
||||
- iucvterm/ts-shell: Introduce config parameter to specify iucvconn binary
|
||||
- iucvterm/ts-shell: Introduce pager config to replace env variable
|
||||
- pv: Add root CA organization pinning to certificate verification
|
||||
- zdump/ngdump: Use OpenSSL's SHA256 to compute digest of dump image
|
||||
|
||||
Bug Fixes:
|
||||
- 95sel-ebc: Add udev-settle dependency
|
||||
- 95sel-ebc: Add umount to boot service
|
||||
- 95sel-ebc: Harden boot mount service
|
||||
- fdasd: Fix memory leak in yes_no() function
|
||||
- iucvterm/iucvtty: Ensure PTY and server fd's are closed at exec
|
||||
- iucvterm/iucvtty: Prevent connection stalls when receiving TERM env
|
||||
- iucvterm/iucvtty: Validate TERM environment name
|
||||
- libkmipclient: Protect from symlink-following attacks
|
||||
- libkmipclient: Various bugfixes
|
||||
- libseckey: Protect from symlink-following attacks
|
||||
- libseckey: Various bugfixes
|
||||
- mon_procd: Fix possible static buffer overflow
|
||||
- osasnmpd: Fix SNMP non-compliance
|
||||
- zdev: Harden against invalid udev, import, firmware, or hypervisor data
|
||||
- zipl-editenv: Check in-bootmap environment block validity
|
||||
- zipl/boot: Check in-bootmap environment block syntax
|
||||
- zipl: Check keyword duplications in BLS entries
|
||||
- zipl: Fix bugs in mirror support
|
||||
- zkey: Protect from symlink-following attacks
|
||||
|
||||
* __v2.43.0 (2026-06-25)__
|
||||
|
||||
For Linux kernel version: 7.1
|
||||
|
||||
s390-tools: Set Rust MSRV to 1.85.0
|
||||
|
||||
Changes of existing tools:
|
||||
- dbginfo.sh: Add IBM appliance specific files
|
||||
- lshwc: Show explicitly selected unnamed counters with --hide
|
||||
- pvattest: Add firmware check version 2
|
||||
- zipl: Introduce verbosity levels of zipl session (--debug)
|
||||
- zkey: Remove the use of AF_ALG for calculating key verification patterns
|
||||
|
||||
Bug Fixes:
|
||||
- ebc: implement --version option for pvics
|
||||
- pvebc: Log services to journal+console
|
||||
- pvics: Fix virt-resize permission error
|
||||
|
||||
* __v2.42.1 (2026-05-22)__
|
||||
|
||||
For Linux kernel version: 7.0
|
||||
|
||||
Changes of existing tools:
|
||||
- cpumf/pai: Improve -m XXX argument verification
|
||||
- pvattest: Add -i -o option variant for check
|
||||
- pvattest: Show perform -i & -o option in help
|
||||
- pvebc: Disable unit logging to /boot
|
||||
- pvsecret: Add -i -o option variants
|
||||
|
||||
Bug Fixes:
|
||||
- cpumf/pai: Remove unnecessary const parameter definition
|
||||
- pv: Fix error description
|
||||
- pvebc: Fix dependency for non EBC guests
|
||||
- pvebc: Fix kernel module dependencies
|
||||
- zipl: Don't modify job->data.dump and job->data.mvdump sequentially
|
||||
|
||||
* __v2.42.0 (2026-04-30)__
|
||||
|
||||
For Linux kernel version: 7.0
|
||||
|
||||
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)
|
||||
|
||||
@@ -713,7 +713,7 @@ static int readdir_entry(struct fst_entry *fst, off_t addr)
|
||||
*/
|
||||
static inline int strip_right(const char *str, int size)
|
||||
{
|
||||
while (str[size - 1] == 0x20)
|
||||
while (size > 0 && str[size - 1] == 0x20)
|
||||
size--;
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ endif
|
||||
# The variable "DISTRELEASE" should be overwritten in rpm spec files with:
|
||||
# "make DISTRELEASE=%{release}" and "make install DISTRELEASE=%{release}"
|
||||
VERSION := 2
|
||||
RELEASE := 42
|
||||
RELEASE := 44
|
||||
PATCHLEVEL := 0
|
||||
DISTRELEASE := build-$(shell date +%Y%m%d)
|
||||
S390_TOOLS_RELEASE := $(VERSION).$(RELEASE).$(PATCHLEVEL)-$(DISTRELEASE)
|
||||
|
||||
@@ -406,6 +406,38 @@ static void output_times(struct time_formats date)
|
||||
}
|
||||
}
|
||||
|
||||
struct ctr_filter_match {
|
||||
bool selected;
|
||||
bool selected_by_id;
|
||||
};
|
||||
|
||||
static struct ctr_filter_match ctr_match(const char *name, const char *list,
|
||||
size_t id)
|
||||
{
|
||||
struct ctr_filter_match match = { .selected = !list };
|
||||
char *copy, *token;
|
||||
char id_str[32];
|
||||
|
||||
if (!list)
|
||||
return match;
|
||||
|
||||
snprintf(id_str, sizeof(id_str), "%zu", id);
|
||||
copy = util_strdup(list);
|
||||
for (token = strtok(copy, ","); token; token = strtok(NULL, ",")) {
|
||||
if (name && strcmp(token, name) == 0) {
|
||||
match.selected = true;
|
||||
break;
|
||||
}
|
||||
if (strcmp(token, id_str) == 0) {
|
||||
match.selected = true;
|
||||
match.selected_by_id = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
return match;
|
||||
}
|
||||
|
||||
static void prepare_counter(size_t id, unsigned long value)
|
||||
{
|
||||
if (output_format == FMT_CSV) {
|
||||
@@ -438,9 +470,14 @@ static void output_per_cpu(struct time_formats date)
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
struct ctr_filter_match match;
|
||||
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
match = ctr_match(ctrname[i].name, ctrlist, i);
|
||||
if (!match.selected)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name && !match.selected_by_id)
|
||||
continue;
|
||||
prepare_counter(i, ctrname[i].ccv[h]);
|
||||
}
|
||||
@@ -462,9 +499,14 @@ static void output_total(struct time_formats date)
|
||||
util_fmt_obj_start(FMT_LIST, "counters");
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ctrname); ++i) {
|
||||
struct ctr_filter_match match;
|
||||
|
||||
if (!ctrname[i].hitcnt)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name)
|
||||
match = ctr_match(ctrname[i].name, ctrlist, i);
|
||||
if (!match.selected)
|
||||
continue;
|
||||
if (hideundef && !ctrname[i].name && !match.selected_by_id)
|
||||
continue;
|
||||
prepare_counter(i, ctrname[i].total);
|
||||
ctrname[i].total = 0;
|
||||
|
||||
24
cpumf/pai.c
24
cpumf/pai.c
@@ -894,7 +894,7 @@ static int parse_event_attr(char *cp)
|
||||
}
|
||||
|
||||
/* Parse CPU list and event specifications */
|
||||
static void parse_cpulist(int enr, const char *parm)
|
||||
static void parse_cpulist(int enr, char *parm)
|
||||
{
|
||||
unsigned int evt_attr = 0;
|
||||
cpu_set_t cmdlist, result;
|
||||
@@ -948,33 +948,27 @@ static const struct util_prg prg = {
|
||||
}
|
||||
};
|
||||
|
||||
static void record_cpus_crypto(const char *cp)
|
||||
static void record_cpus_crypto(char *cp)
|
||||
{
|
||||
if (!libcpumf_have_pai_crypto())
|
||||
errx(EXIT_FAILURE, "No support for PAI crypto counters");
|
||||
parse_cpulist(S390_EVT_PAI_CRYPTO, cp);
|
||||
}
|
||||
|
||||
static void record_cpus_nnpa(const char *cp)
|
||||
static void record_cpus_nnpa(char *cp)
|
||||
{
|
||||
if (!libcpumf_have_pai_nnpa())
|
||||
errx(EXIT_FAILURE, "No support for PAI nnpa counters");
|
||||
parse_cpulist(S390_EVT_PAI_NNPA, cp);
|
||||
}
|
||||
|
||||
/* Mapsize must be power of 2 and larger than 4. Count bits in n and
|
||||
* return 0 if input is invalid and has a bit count larger than one.
|
||||
/* Mapsize must be power of 2 and larger than 4. Return true in this case.
|
||||
*/
|
||||
static unsigned long check_mapsize(unsigned long n)
|
||||
static bool check_mapsize(unsigned long n)
|
||||
{
|
||||
int bit, cnt = 0;
|
||||
|
||||
if (n < 4)
|
||||
return 0;
|
||||
for (bit = 0; bit < __BITS_PER_LONG; ++bit)
|
||||
if (n & (1 << bit))
|
||||
++cnt;
|
||||
return cnt == 1 ? n : 0;
|
||||
return (n & (n - 1)) == 0;
|
||||
}
|
||||
|
||||
static void setprio(const char *prio)
|
||||
@@ -1040,11 +1034,11 @@ int main(int argc, char **argv)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
break;
|
||||
case 'm':
|
||||
errno = 0;
|
||||
mapsize = strtoul(optarg, &slash, 0);
|
||||
mapsize = check_mapsize(mapsize);
|
||||
if (errno || !mapsize || *slash)
|
||||
if (!mapsize || *slash)
|
||||
errx(EXIT_FAILURE, "Invalid argument for -%c", ch);
|
||||
if (!check_mapsize(mapsize))
|
||||
errx(EXIT_FAILURE, "No power of 2 number for -%c", ch);
|
||||
break;
|
||||
case 'n':
|
||||
record_cpus_nnpa(optarg);
|
||||
|
||||
@@ -1566,6 +1566,8 @@ int main(int argc, char *argv[])
|
||||
g.ese = dasd_sys_ese(g.dev_node);
|
||||
eval_format_mode();
|
||||
|
||||
check_disk();
|
||||
|
||||
/* Either let the user specify the blksize or get it from the kernel */
|
||||
if (!g.blksize_specified) {
|
||||
if (!(mode == FULL ||
|
||||
@@ -1587,8 +1589,6 @@ int main(int argc, char *argv[])
|
||||
error("VOLSER not found on device %s", g.dev_path);
|
||||
}
|
||||
|
||||
check_disk();
|
||||
|
||||
if (check_param(str, ERR_LENGTH, &format_params) < 0)
|
||||
error("%s", str);
|
||||
|
||||
|
||||
@@ -495,6 +495,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
|
||||
fprintf(stderr,
|
||||
"Error: Device name was truncated\n");
|
||||
free(path);
|
||||
free(readbuf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -504,6 +505,7 @@ static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode)
|
||||
|
||||
closedir(directory);
|
||||
free(path);
|
||||
free(readbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -387,20 +387,28 @@ static int yes_no(char *question_str)
|
||||
ssize_t bytes_read;
|
||||
char *answer;
|
||||
size_t size;
|
||||
int rc;
|
||||
|
||||
size = 0;
|
||||
answer = NULL;
|
||||
while (1) {
|
||||
printf("%s (y/n): ", question_str);
|
||||
bytes_read = getline(&answer, &size, stdin);
|
||||
if (bytes_read < 0)
|
||||
return -1;
|
||||
if (answer[0] == 'y')
|
||||
return 0;
|
||||
if (answer[0] == 'n')
|
||||
return 1;
|
||||
if (bytes_read < 0) {
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
if (answer[0] == 'y') {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
if (answer[0] == 'n') {
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(answer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static char *fdasd_partition_type(char *dsname)
|
||||
|
||||
@@ -514,7 +514,7 @@ static int hmcdrv_path_copy(struct hmcdrv_fuse_file *fp, char *dest)
|
||||
char *src = HMCDRV_FUSE_PATH(fp);
|
||||
int len = 0;
|
||||
|
||||
while ((len < (HMCDRV_FUSE_MAXPATH - 1)) &&
|
||||
while ((len < (HMCDRV_FUSE_MAXPATH - 2)) &&
|
||||
(*src != '\0')) {
|
||||
|
||||
*dest = *src;
|
||||
|
||||
@@ -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.
|
||||
@@ -102,6 +107,9 @@
|
||||
_x > _y ? _x : _y; \
|
||||
})
|
||||
|
||||
#ifdef __SIZEOF_INT128__
|
||||
typedef unsigned __int128 u128 __aligned(16);
|
||||
#endif
|
||||
typedef unsigned long long u64;
|
||||
typedef signed long long s64;
|
||||
typedef unsigned int u32;
|
||||
|
||||
@@ -14,14 +14,19 @@ use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use Getopt::Long qw(:config no_ignore_case);
|
||||
use Scalar::Util qw(tainted);
|
||||
use Term::ReadLine;
|
||||
use POSIX;
|
||||
|
||||
$ENV{'PERL5LIB'} = "";
|
||||
$ENV{'PERL5OPT'} = "";
|
||||
$ENV{'PERLLIB'} = "";
|
||||
$ENV{'PERL_USE_UNSAFE_INC'} = "";
|
||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||
|
||||
$ENV{'PERL_RL'} = " o=0"; # use best avail. readline
|
||||
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin";
|
||||
$ENV{'LESSSECURE'} = 1; # let less run in "secure" mode
|
||||
$ENV{'PAGER'} = $ENV{'PAGER'} || "/usr/bin/less";
|
||||
my $ts_shell = fileparse($0, qr/\.[^.]+/);
|
||||
$SIG{__WARN__} = sub { print STDERR "$ts_shell: $_[0]"; };
|
||||
$SIG{__DIE__} = sub { print STDERR "$ts_shell: $_[0]"; exit 255; };
|
||||
@@ -36,6 +41,7 @@ my %config = (
|
||||
'auditdir' => "@var_path@/log/ts-shell",
|
||||
'iucvconn' => "@iucvconn_path@/iucvconn",
|
||||
'prompt' => getpwuid($>) . '@'."$ts_shell> ",
|
||||
'pager' => "/usr/bin/less",
|
||||
# runtime options
|
||||
'rl' => undef, # terminal readline (rl)
|
||||
'user' => getpwuid($>), # user name
|
||||
@@ -65,6 +71,7 @@ sub cmd_list(\%);
|
||||
sub rl_cmd_completion($$$);
|
||||
sub list_regex_match($);
|
||||
sub updateConfiguration(\%$);
|
||||
sub checkConfiguration(\%);
|
||||
sub loadAuthorization(\%);
|
||||
sub readFile($$);
|
||||
sub log_debug($);
|
||||
@@ -103,6 +110,7 @@ sub main()
|
||||
"$config{conffile} failed: $!";
|
||||
exit 3;
|
||||
}
|
||||
checkConfiguration(%config);
|
||||
|
||||
# load list of systems the ts-shell is allowed to use; and
|
||||
# hash the systems names in uppercase
|
||||
@@ -185,11 +193,50 @@ sub updateConfiguration(\%$)
|
||||
{
|
||||
my ($cfg, $line) = @_;
|
||||
my ($option, $value) = split /\s*=\s*/, $line;
|
||||
|
||||
|
||||
$cfg->{sysfile} = $value if $option =~ /^ts-systems$/;
|
||||
$cfg->{authfile} = $value if $option =~ /^ts-authorization$/;
|
||||
$cfg->{auditdir} = $value if $option =~ /^transcript-directory$/;
|
||||
$cfg->{auditfile} = $value if $option =~ /^transcript-systems$/;
|
||||
$cfg->{pager} = $value if $option =~ /^pager$/;
|
||||
$cfg->{iucvconn} = $value if $option =~ /^iucvconn$/;
|
||||
}
|
||||
|
||||
# checkConfiguration() - Validate and untaint configuration values
|
||||
#
|
||||
# Called once after readFile() has finished populating %cfg from
|
||||
# ts-shell.conf. Validates each security-sensitive path and untaints
|
||||
# it via a regex capture so Perl taint mode permits later filesystem use.
|
||||
#
|
||||
# %cfg: Hash reference to terminal server configuration
|
||||
#
|
||||
sub checkConfiguration(\%)
|
||||
{
|
||||
my $cfg = shift;
|
||||
|
||||
# Validate pager to untaint for later use
|
||||
unless ($cfg->{pager} =~ m#^(/[\w./-]+)$#) {
|
||||
log_error "Invalid pager configuration";
|
||||
exit 6;
|
||||
}
|
||||
$cfg->{pager} = $1;
|
||||
|
||||
# Validate iucvconn path to untaint for later use
|
||||
unless ($cfg->{iucvconn} =~ m#^(/[\w./-]+)$#) {
|
||||
log_error "Invalid iucvconn configuration";
|
||||
exit 7;
|
||||
}
|
||||
$cfg->{iucvconn} = $1;
|
||||
|
||||
# Validate transcript-directory to untaint for later use
|
||||
unless ($cfg->{auditdir} =~ m#^(/[\w./-]+)$#) {
|
||||
log_error "Invalid transcript-directory configuration";
|
||||
exit 8;
|
||||
}
|
||||
$cfg->{auditdir} = $1;
|
||||
|
||||
# Untaint user name (tainted by getpwuid) for use in filesystem paths
|
||||
($cfg->{user}) = ($cfg->{user} =~ /^([\w.-]+)$/);
|
||||
}
|
||||
|
||||
# loadAuthorization() - Load system authorizations from file
|
||||
@@ -205,7 +252,7 @@ sub loadAuthorization(\%)
|
||||
{
|
||||
my $cfg = shift();
|
||||
|
||||
return 0 unless open(AUTH, "<$cfg->{authfile}");
|
||||
return 0 unless open(AUTH, "<", $cfg->{authfile});
|
||||
|
||||
AUTH_ENT: while (<AUTH>) {
|
||||
chomp;
|
||||
@@ -217,13 +264,13 @@ sub loadAuthorization(\%)
|
||||
my ($key, $val) = split /\s*=\s*/;
|
||||
|
||||
# read authorization configuration for user and its groups
|
||||
if ($key =~ /^$cfg->{user}$/) {
|
||||
if ($key =~ /^\Q$cfg->{user}\E$/) {
|
||||
$authorized = 1;
|
||||
log_debug "Found user: $key";
|
||||
|
||||
} elsif ($key =~ /^@(\S+)$/) {
|
||||
my $group = $1;
|
||||
$authorized = 1 if grep {/^${group}$/} @{$cfg->{groups}};
|
||||
$authorized = 1 if grep {/^\Q${group}\E$/} @{$cfg->{groups}};
|
||||
log_debug "Found group: $key" if $authorized;
|
||||
}
|
||||
|
||||
@@ -303,7 +350,7 @@ sub readFile($$)
|
||||
{
|
||||
my ($file, $sub) = @_;
|
||||
|
||||
return 0 unless open(CONF, "<$file");
|
||||
return 0 unless open(CONF, "<", "$file");
|
||||
while (<CONF>) {
|
||||
chomp;
|
||||
next if /^#/; # ignore comments
|
||||
@@ -392,7 +439,15 @@ sub get_auditlog_file($$$)
|
||||
}
|
||||
}
|
||||
|
||||
$$filepath = "$cfg->{auditdir}/$cfg->{user}/" . uc $guest . "_";
|
||||
# validate and sanitize guest directory
|
||||
$guest = uc $guest;
|
||||
unless ($guest =~ /^([A-Z0-9@#\$_-]{1,8})$/) {
|
||||
log_error "Creating session file path failed for guest: $guest";
|
||||
return 0; # return error
|
||||
}
|
||||
|
||||
# construct the file path
|
||||
$$filepath = "$cfg->{auditdir}/$cfg->{user}/" . $guest . "_";
|
||||
$$filepath .= strftime '%F-%H%M%S', localtime;
|
||||
|
||||
log_debug "Session transcript file: $$filepath";
|
||||
@@ -416,7 +471,7 @@ sub cmd_connect($\%)
|
||||
}
|
||||
|
||||
my ($guest, $srv) = split /\s+/, $params;
|
||||
$service = $srv if $srv && $srv =~ /\w{1,8}/;
|
||||
$service = $srv if $srv && $srv =~ /^\w{1,8}$/;
|
||||
|
||||
# check authorization:
|
||||
# The auth_func contract is to return a code, that must be one of:
|
||||
@@ -482,7 +537,7 @@ sub cmd_service($\%)
|
||||
return;
|
||||
}
|
||||
|
||||
if ($new =~ /\w{1,8}/) {
|
||||
if ($new =~ /^\w{1,8}$/) {
|
||||
$cfg->{service} = $new;
|
||||
# push new service for cmd completion
|
||||
unless (grep /$new/, @{$cfg->{services}}) {
|
||||
@@ -545,7 +600,7 @@ sub pager($)
|
||||
{
|
||||
my $eval = shift();
|
||||
|
||||
unless (open(PAGER, "|$ENV{'PAGER'}")) {
|
||||
unless (open(PAGER, '|-', "$config{'pager'}")) {
|
||||
eval &$eval;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.\" ts-shell.1
|
||||
.\"
|
||||
.\"
|
||||
.\" Copyright IBM Corp. 2008, 2017
|
||||
.\" s390-tools is free software; you can redistribute it and/or modify
|
||||
.\" it under the terms of the MIT license. See LICENSE for details.
|
||||
.\" Copyright IBM Corp.
|
||||
.\" ----------------------------------------------------------------------
|
||||
.TH "ts-shell" "1" "March 2009" "s390-tools" "Terminal Server over IUCV"
|
||||
.
|
||||
@@ -269,6 +268,10 @@ session transcripts are saved.
|
||||
|
||||
See section "Configure terminal session transcripts" for details.
|
||||
.
|
||||
.TP
|
||||
.BR pager " = " \fI/usr/bin/less\fP
|
||||
Specifies the path to a pager program to paginate output of ts-shell commands.
|
||||
.
|
||||
.RE
|
||||
.
|
||||
.
|
||||
@@ -542,14 +545,6 @@ Directory for saving terminal session transcripts.
|
||||
.
|
||||
.SH "ENVIRONMENT"
|
||||
.TP
|
||||
.B PAGER
|
||||
The \fBPAGER\fP environment variable designates a program used as pager for the
|
||||
\fBlist\fP command of the terminal server shell.
|
||||
If \fBPAGER\fP is not set or empty,
|
||||
.BR less (1)
|
||||
is used.
|
||||
.
|
||||
.TP
|
||||
.B LESSSECURE
|
||||
\fBts-shell\fP sets this variable to run
|
||||
.BR less (1)
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
#
|
||||
# See ts-shell(1) manual page for file format syntax.
|
||||
|
||||
# Generic settings
|
||||
# ----------------
|
||||
|
||||
# pager
|
||||
# binary to paginate output (default: less)
|
||||
#pager = /usr/bin/less
|
||||
|
||||
# System and authorization settings
|
||||
# ---------------------------------
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
/* Message buffer: message header + 4096 bytes of data */
|
||||
#define MSG_BUFFER_SIZE (MSG_DATA_OFFSET + (4096))
|
||||
|
||||
/* Message data sizes */
|
||||
#define MAX_TERM_SIZE 256
|
||||
|
||||
/* Error macros */
|
||||
#define print_error(s) program_error(PRG_COMPONENT, (s))
|
||||
#define iucvtty_error(m) \
|
||||
@@ -55,13 +58,14 @@ extern int iucvtty_read_data(int, struct iucvtty_msg *, size_t);
|
||||
|
||||
extern int iucvtty_read_msg(int, struct iucvtty_msg *, size_t, size_t *);
|
||||
extern int iucvtty_write_msg(int, struct iucvtty_msg *);
|
||||
extern void iucvtty_skip_msg_residual(int, size_t *);
|
||||
extern void iucvtty_skip_msg_chunk(int, size_t *);
|
||||
|
||||
extern ssize_t __write(int, const void*, size_t);
|
||||
|
||||
extern int strmatch(const char *, const char *);
|
||||
extern int is_regex_valid(const char *);
|
||||
extern int is_client_allowed(const char *, const struct iucvterm_cfg *);
|
||||
extern int is_term_valid(const char *term, size_t len);
|
||||
extern void userid_cpy(char [9], const char [8]);
|
||||
|
||||
extern void iucv_msg_error(const char *, uint32_t);
|
||||
|
||||
@@ -86,7 +86,7 @@ static inline struct iucvtty_msg *msg_alloc(uint8_t type, uint16_t size)
|
||||
{
|
||||
struct iucvtty_msg *m;
|
||||
|
||||
m = malloc(size + MSG_DATA_OFFSET);
|
||||
m = calloc(1, size + MSG_DATA_OFFSET);
|
||||
if (m != NULL) {
|
||||
m->version = MSG_VERSION;
|
||||
m->type = type;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* 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 <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <regex.h>
|
||||
#include <stdio.h>
|
||||
@@ -36,7 +38,7 @@
|
||||
* @len: Buffer length
|
||||
*
|
||||
* Write @len number of bytes from the buffer @buf to the file
|
||||
* descriptor @fd. The routines handles EINTR and partially writes.
|
||||
* descriptor @fd. The routine handles EINTR and partial writes.
|
||||
* Returns the error code from the underlying write(2) syscall.
|
||||
*/
|
||||
ssize_t __write(int fd, const void *buf, size_t len)
|
||||
@@ -55,6 +57,33 @@ ssize_t __write(int fd, const void *buf, size_t len)
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* __read() - Read data
|
||||
* @fd: File descriptor
|
||||
* @buf: Pointer to data buffer
|
||||
* @len: Buffer size
|
||||
*
|
||||
* Read up to @len number of bytes from file descriptor @fd and stores them
|
||||
* in the buffer to which @buf points.
|
||||
* The routine handles EINTR and partial reads and returns the error code
|
||||
* from the underlying read(2) syscall.
|
||||
*/
|
||||
ssize_t __read(int fd, void *buf, size_t len)
|
||||
{
|
||||
ssize_t rc;
|
||||
size_t count = 0;
|
||||
|
||||
while (count < len) {
|
||||
rc = read(fd, buf + count, len - count);
|
||||
if (rc == -1 && errno == EINTR)
|
||||
continue;
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
count += rc;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#ifdef __DEBUG__
|
||||
static void __dump_msg(int fd, const struct iucvtty_msg *m, char dir)
|
||||
{
|
||||
@@ -116,14 +145,16 @@ int iucvtty_tx_termenv(int dest, char *dflt)
|
||||
|
||||
len = 0;
|
||||
if (term != NULL)
|
||||
len = 1 + strlen(term);
|
||||
len = MIN(1 + strlen(term), (size_t)MAX_TERM_SIZE);
|
||||
|
||||
/* Note: The server console tool waits for terminal environment
|
||||
* information: the message is sent even if it is empty */
|
||||
msg = msg_alloc(MSG_TYPE_TERMENV, len);
|
||||
if (msg == NULL)
|
||||
return -1;
|
||||
msg_cpy_from(msg, term, len);
|
||||
msg->datalen = len;
|
||||
if (msg->datalen)
|
||||
snprintf((char *)msg->data, msg->datalen, "%s", term);
|
||||
rc = iucvtty_write_msg(dest, msg);
|
||||
msg_free(msg);
|
||||
|
||||
@@ -146,12 +177,14 @@ int iucvtty_rx_termenv(int fd, void *buf, size_t len)
|
||||
return -1;
|
||||
skip = 0;
|
||||
rc = iucvtty_read_msg(fd, msg, msg_size(msg), &skip);
|
||||
iucvtty_skip_msg_residual(fd, &skip);
|
||||
iucvtty_skip_msg_chunk(fd, &skip);
|
||||
if (!rc) {
|
||||
if (msg->datalen == 0)
|
||||
if (msg->datalen == 0) {
|
||||
memset(buf, 0, MIN(1u, len));
|
||||
else
|
||||
} else {
|
||||
msg_cpy_to(msg, buf, len);
|
||||
memset(buf + MIN(msg->datalen, len - 1), 0, 1);
|
||||
}
|
||||
}
|
||||
msg_free(msg);
|
||||
return rc;
|
||||
@@ -262,93 +295,175 @@ int iucvtty_copy_data(int dest, struct iucvtty_msg *msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* iucvtty_skip_msg_residual() - Skip (receive & forget) count number of bytes
|
||||
* iucvtty_skip_msg_chunk() - Skip (receive & forget) count number of bytes
|
||||
* @fd: File descriptor
|
||||
* @residual: Residual of an iucv tty message received by iucvtty_read_msg()
|
||||
* @chunk: Remaining chunk from a previous iucvtty_read_msg() call
|
||||
*
|
||||
* See iucvtty_read_msg() for an explanation when to use this routine.
|
||||
* Note: The @residual parameter shall not be NULL.
|
||||
* Note: The @chunk parameter must not be NULL.
|
||||
*/
|
||||
void iucvtty_skip_msg_residual(int fd, size_t *residual)
|
||||
void iucvtty_skip_msg_chunk(int fd, size_t *chunk)
|
||||
{
|
||||
char b;
|
||||
size_t i;
|
||||
int r;
|
||||
char buf[256];
|
||||
|
||||
if (*residual <= 0)
|
||||
if (*chunk <= 0)
|
||||
return;
|
||||
for (i = 0; i < *residual; i++)
|
||||
if (read(fd, &b, 1) <= 0)
|
||||
|
||||
while (*chunk) {
|
||||
r = __read(fd, buf, MIN(sizeof(buf), *chunk));
|
||||
if (r <= 0)
|
||||
break;
|
||||
*residual = 0;
|
||||
*chunk -= r;
|
||||
}
|
||||
|
||||
*chunk = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* iucvtty_read_msg_chunk() - Read IUCV message chunk
|
||||
* @fd: File descriptor to read from
|
||||
* @msg: Pointer to IUCV message buffer
|
||||
* @msglen: IUCV message buffer size (including message header)
|
||||
* @chunk: Size of chunk data to read
|
||||
*
|
||||
* Stores new message data and calculates next chunk size if not all data
|
||||
* could be read. Returns zero on success, non-zero otherwise.
|
||||
*/
|
||||
static int iucvtty_read_msg_chunk(int fd, struct iucvtty_msg *msg,
|
||||
size_t msglen, size_t *chunk)
|
||||
{
|
||||
ssize_t r;
|
||||
size_t datalen;
|
||||
|
||||
/* Calculate message data length to read */
|
||||
datalen = MIN(msglen - MSG_DATA_OFFSET, *chunk);
|
||||
|
||||
for (;;) {
|
||||
r = read(fd, msg->data, datalen);
|
||||
if (r == -1 && errno == EINTR)
|
||||
continue;
|
||||
if (r <= 0)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Update message and re-calculate next chunk */
|
||||
msg->datalen = r;
|
||||
*chunk -= r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_msg() - Perform sanity checks on a received message
|
||||
* @msg: IUCV message buffer
|
||||
*
|
||||
* Returns zero if the message is valid; otherwise non-zero
|
||||
*/
|
||||
static int validate_msg(struct iucvtty_msg *msg)
|
||||
{
|
||||
switch (msg->type) {
|
||||
case MSG_TYPE_DATA:
|
||||
/* The datalen ranges from 0 to its maximum of 0xffff
|
||||
* which is the maximum of the type definition of uint16_t.
|
||||
*
|
||||
* Consider the datalen value as valid.
|
||||
*/
|
||||
break;
|
||||
case MSG_TYPE_ERROR:
|
||||
if (msg->datalen != sizeof(uint32_t))
|
||||
return 1;
|
||||
break;
|
||||
case MSG_TYPE_TERMENV:
|
||||
if (msg->datalen > MAX_TERM_SIZE)
|
||||
return 1;
|
||||
break;
|
||||
case MSG_TYPE_TERMIOS: /* ignored */
|
||||
break;
|
||||
case MSG_TYPE_WINSIZE:
|
||||
if (msg->datalen != sizeof(struct winsize))
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
/* Invalid message type */
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* iucvtty_read_msg() - Read/Receive an IUCV message
|
||||
* @fd: File descriptor to read from
|
||||
* @msg: Pointer to IUCV message buffer
|
||||
* @len: IUCV message data len
|
||||
* @residual: Status to be used by next call
|
||||
* @msglen: IUCV message buffer size (MUST BE > MSG_DATA_OFFSET)
|
||||
* @chunk: Size of remaining data; must be passed on next call
|
||||
*
|
||||
* The function reads up to @len bytes from file descriptor @fd.
|
||||
* If the received message is larger than @len bytes, the @residual value
|
||||
* is set to the number of bytes remaining.
|
||||
* The function shall then be re-called to create a new message and receive
|
||||
* the next chunk of size @residual; or the remaining characters must be
|
||||
* skipped using the iucvtty_skip_msg() routine.
|
||||
* Note: The @len parameter shall be greater than MSG_DATA_OFFSET.
|
||||
* The @residual parameter shall not be NULL.
|
||||
* The function reads up to @len bytes from file descriptor @fd. If the
|
||||
* received message is larger than @len bytes, the @chunk value is set to
|
||||
* the number of remaining bytes. The function shall then be re-called to
|
||||
* create a new message and receive the outstanding data. Alternatively,
|
||||
* call iucvtty_skip_msg_chunk() to discard remaining data.
|
||||
*
|
||||
* NOTE: The @len parameter must be greater than MSG_DATA_OFFSET!
|
||||
* The @chunk parameter must not be NULL.
|
||||
*/
|
||||
int iucvtty_read_msg(int fd, struct iucvtty_msg *msg,
|
||||
size_t len, size_t *residual)
|
||||
size_t msglen, size_t *chunk)
|
||||
{
|
||||
int rc;
|
||||
ssize_t r; /* number of bytes read from fd */
|
||||
|
||||
if (*residual)
|
||||
len = MIN(len - MSG_DATA_OFFSET, *residual);
|
||||
/* Ensure message buffer size can hold data and can be processed */
|
||||
assert(msglen > MSG_DATA_OFFSET);
|
||||
assert(chunk != NULL);
|
||||
|
||||
while (1) {
|
||||
if (*residual) {
|
||||
r = read(fd, msg->data, len);
|
||||
if (r > 0)
|
||||
msg->datalen = r;
|
||||
} else
|
||||
r = read(fd, msg, len);
|
||||
/* Read pending message data */
|
||||
if (*chunk)
|
||||
return iucvtty_read_msg_chunk(fd, msg, msglen, chunk);
|
||||
|
||||
if (r == -1 && errno == EINTR)
|
||||
continue;
|
||||
if (r <= 0) {
|
||||
rc = -1;
|
||||
goto out_read_error;
|
||||
}
|
||||
|
||||
break; /* exit loop for a successful read */
|
||||
/* Read message header of new message */
|
||||
r = __read(fd, msg, MSG_DATA_OFFSET);
|
||||
if (r <= 0) {
|
||||
rc = -1;
|
||||
goto out_read_error;
|
||||
}
|
||||
|
||||
#ifdef __DEBUG__
|
||||
if (!*residual)
|
||||
__dump_msg(fd, msg, 'R');
|
||||
__dump_msg(fd, msg, 'R');
|
||||
#endif
|
||||
|
||||
/* (re)calculate next chunk */
|
||||
if (*residual)
|
||||
*residual -= msg->datalen;
|
||||
else
|
||||
if (msg->datalen > (r - MSG_DATA_OFFSET)) {
|
||||
/* calculate pending msg data and update datalen */
|
||||
*residual = msg->datalen - (r - MSG_DATA_OFFSET);
|
||||
msg->datalen = r - MSG_DATA_OFFSET;
|
||||
}
|
||||
|
||||
/* check for a sane message */
|
||||
/* Check message header */
|
||||
if (msg->version != MSG_VERSION) {
|
||||
fprintf(stderr, _("%s: %s\n"),
|
||||
PRG_COMPONENT, _("The version of the received data "
|
||||
"message is not supported\n"));
|
||||
"message is not supported"));
|
||||
fprintf(stderr, "MSG: msg->version=%u msg->type=%u msg->datalen=%u\n",
|
||||
msg->version, msg->type, msg->datalen);
|
||||
rc = -2;
|
||||
goto out_read_error;
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
/* Check for an empty message */
|
||||
if (!msg->datalen)
|
||||
return 0;
|
||||
|
||||
/* Check message type and data length */
|
||||
if (validate_msg(msg)) {
|
||||
fprintf(stderr, _("%s: %s\n"),
|
||||
PRG_COMPONENT, _("The received message is invalid"));
|
||||
fprintf(stderr, "MSG: msg->version=%u msg->type=%u msg->datalen=%u\n",
|
||||
msg->version, msg->type, msg->datalen);
|
||||
rc = -3;
|
||||
goto out_read_error;
|
||||
}
|
||||
|
||||
/* Process the new message as a one chunk */
|
||||
*chunk = msg->datalen;
|
||||
msg->datalen = 0;
|
||||
rc = iucvtty_read_msg_chunk(fd, msg, msglen, chunk);
|
||||
|
||||
out_read_error:
|
||||
return rc;
|
||||
}
|
||||
@@ -501,6 +616,31 @@ int is_client_allowed(const char *client, const struct iucvterm_cfg *cfg)
|
||||
return strmatch(client, cfg->client_re);
|
||||
}
|
||||
|
||||
/**
|
||||
* is_term_valid() - Validate TERM environment value
|
||||
* @term: Terminal environment name to validate
|
||||
* @len: Maximum number of characters to validate
|
||||
*/
|
||||
int is_term_valid(const char *term, size_t len)
|
||||
{
|
||||
const char *c;
|
||||
|
||||
if (term == NULL || *term == '\0')
|
||||
return 0;
|
||||
|
||||
for (c = term; *c != '\0'; c++) {
|
||||
if ((size_t)(c - term) >= len)
|
||||
return 0;
|
||||
if (!isalnum((unsigned char)*c) &&
|
||||
*c != '.' &&
|
||||
*c != '_' &&
|
||||
*c != '-')
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* userid_cpy() - Copy z/VM user ID and skip trailing spaces.
|
||||
* @dest: Destination buffer
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <syslog.h>
|
||||
@@ -32,7 +33,6 @@
|
||||
|
||||
#define SYSLOG_IDENT "iucvtty"
|
||||
#define PRG_COMPONENT SYSLOG_IDENT
|
||||
#define TERM_BUFSIZE 256
|
||||
#define TERM_DEFAULT "linux"
|
||||
|
||||
|
||||
@@ -63,12 +63,46 @@ static int exec_login_prog(char *cmd[])
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive_term_env() - receive terminal environment with timeout
|
||||
* @term: Terminal environment variable
|
||||
* @len: Maximum characters for the terminal environment variable
|
||||
*/
|
||||
static void receive_term_env(int client, char *term, size_t len)
|
||||
{
|
||||
struct timeval tv = { .tv_sec = 0, .tv_usec = 0};
|
||||
|
||||
/* Set a temporary timeout of 10s to time out a stalling
|
||||
* client connection. Use SO_RCVTIMEO to set the timeout.
|
||||
* Using those hard coded values directly is safe as
|
||||
* SO_RCVTIMEO is not being used in other parts of iucvtty.
|
||||
*/
|
||||
tv.tv_sec = 10;
|
||||
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
memset(term, 0, len);
|
||||
if (iucvtty_rx_termenv(client, term, len))
|
||||
snprintf(term, len, "%s", TERM_DEFAULT);
|
||||
|
||||
tv.tv_sec = 0;
|
||||
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
if (!is_term_valid(term, len)) {
|
||||
print_error("Ignoring received TERM env due to invalid character(s)");
|
||||
snprintf(term, len, "%s", TERM_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iucvtty_worker() - Handle an incoming client connection
|
||||
* @client: Client file descriptor
|
||||
* @master: PTY master file descriptor
|
||||
* @slave: PTY slave file descriptor
|
||||
* @cfg: IUCV TTY configuration structure.
|
||||
*
|
||||
* Unlike the HVC IUCV terminal device driver, iucvtty expects to
|
||||
* receive the TERM environment first. This is necessary to correctly
|
||||
* set up the login program.
|
||||
*/
|
||||
static int iucvtty_worker(int client, int master, int slave,
|
||||
const struct iucvterm_cfg *cfg)
|
||||
@@ -78,15 +112,14 @@ static int iucvtty_worker(int client, int master, int slave,
|
||||
pid_t child;
|
||||
fd_set set;
|
||||
size_t chunk;
|
||||
char term_env[TERM_BUFSIZE];
|
||||
char term_env[MAX_TERM_SIZE];
|
||||
|
||||
|
||||
/* flush pending terminal data */
|
||||
tcflush(master, TCIOFLUSH);
|
||||
|
||||
/* read terminal parameters from client */
|
||||
if (iucvtty_rx_termenv(client, term_env, TERM_BUFSIZE))
|
||||
sprintf(term_env, TERM_DEFAULT);
|
||||
/* read and validate terminal parameters from client */
|
||||
receive_term_env(client, term_env, sizeof(term_env));
|
||||
|
||||
/* start login program */
|
||||
child = fork();
|
||||
@@ -113,6 +146,9 @@ static int iucvtty_worker(int client, int master, int slave,
|
||||
exit(3); /* we only reach here if exec has failed */
|
||||
}
|
||||
|
||||
/* Close slave fd to properly handle SIGHUP for the child process */
|
||||
close(slave);
|
||||
|
||||
/* setup buffers */
|
||||
msg = malloc(MSG_BUFFER_SIZE);
|
||||
if (msg == NULL) {
|
||||
@@ -226,8 +262,13 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
/* set close-on-exec for file descriptors */
|
||||
fcntl(master, F_SETFD, FD_CLOEXEC);
|
||||
fcntl(server, F_SETFD, FD_CLOEXEC);
|
||||
if (fcntl(master, F_SETFD, FD_CLOEXEC) ||
|
||||
fcntl(server, F_SETFD, FD_CLOEXEC)) {
|
||||
print_error("Setting file controls failed");
|
||||
close(server);
|
||||
rc = 1;
|
||||
goto exit_on_error;
|
||||
}
|
||||
|
||||
/* syslog */
|
||||
openlog(SYSLOG_IDENT, LOG_PID, LOG_AUTHPRIV);
|
||||
@@ -240,6 +281,7 @@ int main(int argc, char *argv[])
|
||||
client = accept(server, (struct sockaddr *) &caddr, &len);
|
||||
if (client == -1) {
|
||||
print_error("An incoming connection could not be accepted");
|
||||
close(server);
|
||||
rc = 2;
|
||||
goto exit_on_error;
|
||||
}
|
||||
@@ -256,10 +298,14 @@ int main(int argc, char *argv[])
|
||||
} else { /* client is allowed to connect */
|
||||
syslog(LOG_INFO, "Accepted client connection from %s",
|
||||
client_host);
|
||||
/* set close-on-exec for client socket */
|
||||
fcntl(client, F_SETFD, FD_CLOEXEC);
|
||||
/* close server socket */
|
||||
close(server);
|
||||
/* set close-on-exec for client socket */
|
||||
if (fcntl(client, F_SETFD, FD_CLOEXEC)) {
|
||||
print_error("Setting file controls failed");
|
||||
rc = 4;
|
||||
goto exit_on_error;
|
||||
}
|
||||
|
||||
/* setup signal handler to notify shutdown signal */
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "iucvterm/functions.h"
|
||||
@@ -114,7 +113,7 @@ static int __testReadWriteMessage_nochunks(int sv[2])
|
||||
if (iucvtty_read_msg(sv[1], msg[1], 6 + MSG_DATA_OFFSET, &residual))
|
||||
return 2;
|
||||
assert(0 == memcmp(msg[1]->data, data, msg[1]->datalen));
|
||||
iucvtty_skip_msg_residual(sv[1], &residual);
|
||||
iucvtty_skip_msg_chunk(sv[1], &residual);
|
||||
assert(0 == residual);
|
||||
/*printf("datalen=%u data='%s'\n",
|
||||
msg[1]->datalen, msg[1]->data);*/
|
||||
@@ -144,7 +143,7 @@ static int __testReadWriteMessage(int sv[2])
|
||||
/* read msg */
|
||||
if (iucvtty_read_msg(sv[1], msg[1], BUF_SIZE, &chunk))
|
||||
return 2;
|
||||
iucvtty_skip_msg_residual(sv[1], &chunk);
|
||||
iucvtty_skip_msg_chunk(sv[1], &chunk);
|
||||
|
||||
/* compare msg */
|
||||
if (__msgcmp(msg[0], msg[1]))
|
||||
|
||||
@@ -43,7 +43,7 @@ detect-openssl-version.dep:
|
||||
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
|
||||
echo "#endif" >> $(TMPFILE)
|
||||
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
|
||||
echo "}" >> $(TMPFILE)
|
||||
mv $(TMPFILE) $@
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ static int _ekmf_extract_pubkey(const char *cert, const char *pub_key_pem,
|
||||
goto out;
|
||||
}
|
||||
|
||||
fp = fopen(pub_key_pem, "w");
|
||||
fp = fopen_nofollow(pub_key_pem, "w");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
pr_verbose(verbose, "File '%s': %s", pub_key_pem,
|
||||
@@ -458,7 +458,7 @@ retry:
|
||||
out);
|
||||
|
||||
if (server_cert_pem != NULL) {
|
||||
fp = fopen(server_cert_pem, "w");
|
||||
fp = fopen_nofollow(server_cert_pem, "w");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
pr_verbose(verbose, "File '%s': %s", server_cert_pem,
|
||||
@@ -495,7 +495,7 @@ retry:
|
||||
*/
|
||||
if (ci->num_of_certs > 1 && ca_bundle_pem != NULL &&
|
||||
do_verify == 0) {
|
||||
fp = fopen(ca_bundle_pem, "w");
|
||||
fp = fopen_nofollow(ca_bundle_pem, "w");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
pr_verbose(verbose, "File '%s': %s",
|
||||
@@ -1415,7 +1415,7 @@ int ekmf_login(const struct ekmf_config *config, CURL **curl_handle,
|
||||
while (*tok == ' ')
|
||||
tok++;
|
||||
|
||||
fp = fopen(config->login_token, "w");
|
||||
fp = fopen_nofollow(config->login_token, "w");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
pr_verbose(verbose, "Failed to open file %s: '%s'",
|
||||
@@ -1739,7 +1739,7 @@ int ekmf_get_settings(const struct ekmf_config *config, CURL **curl_handle,
|
||||
if (xts_key2_template != NULL) {
|
||||
*xts_key2_template = _ekmf_find_setting(response_obj,
|
||||
SETTING_ID_XTS_KEY2_TEMPLATE, verbose);
|
||||
if (*identity_template == NULL) {
|
||||
if (*xts_key2_template == NULL) {
|
||||
if (error_msg != NULL) {
|
||||
if (asprintf(error_msg, "The EKMF Web setting "
|
||||
"'XTS Key Template Name (Key 2)' "
|
||||
@@ -1882,8 +1882,9 @@ int ekmf_check_feature(const struct ekmf_config *config, CURL **curl_handle,
|
||||
pr_verbose(verbose, "Feature '%s' is not installed",
|
||||
FEATURE_ID_PERVASIVE_ENCRYPTION);
|
||||
rc = -ENOTSUP;
|
||||
if (asprintf(error_msg, "EKMF Web feature "
|
||||
"'Pervasive Encryption' is not installed.")) {
|
||||
if (error_msg != NULL &&
|
||||
asprintf(error_msg, "EKMF Web feature "
|
||||
"'Pervasive Encryption' is not installed.") < 0) {
|
||||
pr_verbose(verbose, "asprintf failed");
|
||||
rc = -ENOMEM;
|
||||
}
|
||||
@@ -1925,7 +1926,7 @@ static int _ekmf_build_party_info(const char *key_uuid, const char *timestamp,
|
||||
|
||||
if (*party_info_length < (size_t)EVP_MD_size(md)) {
|
||||
pr_verbose(verbose, "Party info buffer is too small");
|
||||
return -ERANGE;
|
||||
rc = -ERANGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -2219,6 +2220,11 @@ static int _ekmf_import_key(unsigned char *req_sess_key,
|
||||
unsigned char *party_info = NULL;
|
||||
int rc;
|
||||
|
||||
if (resp_party_info_length > SIZE_MAX - req_party_info_length) {
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
party_info_length = req_party_info_length + resp_party_info_length;
|
||||
party_info = malloc(party_info_length);
|
||||
if (party_info == NULL) {
|
||||
@@ -4547,8 +4553,9 @@ out:
|
||||
*/
|
||||
static char *_ekmf_base64_encode(const unsigned char *data, size_t data_size)
|
||||
{
|
||||
int outlen, len;
|
||||
size_t outlen;
|
||||
char *out;
|
||||
int len;
|
||||
|
||||
outlen = (data_size / 3) * 4;
|
||||
if (data_size % 3 > 0)
|
||||
@@ -4559,7 +4566,7 @@ static char *_ekmf_base64_encode(const unsigned char *data, size_t data_size)
|
||||
return NULL;
|
||||
|
||||
len = EVP_EncodeBlock((unsigned char *)out, data, data_size);
|
||||
if (len != outlen) {
|
||||
if (len < 0 || (size_t)len != outlen) {
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
@@ -4596,7 +4603,7 @@ static int _ekmf_build_key_material(const unsigned char *certificate,
|
||||
"JSON object", verbose, out);
|
||||
|
||||
payload = _ekmf_base64_encode(certificate, certificate_size);
|
||||
JSON_CHECK_ERROR(*keymat_obj == NULL, rc, -EIO,
|
||||
JSON_CHECK_ERROR(payload == NULL, rc, -EIO,
|
||||
"Failed to base64 encode the certificate",
|
||||
verbose, out);
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
@@ -104,7 +106,7 @@ int decode_base64url(unsigned char *output, size_t *outlen,
|
||||
|
||||
len = EVP_DecodeBlock((unsigned char *)padded_output,
|
||||
(unsigned char *)padded_input, padded_inlen);
|
||||
if (len != (int)padded_inlen * 3 / 4) {
|
||||
if ((size_t)len != padded_inlen * 3 / 4) {
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
@@ -229,8 +231,9 @@ int parse_json_web_token(const char *token, json_object **header_obj,
|
||||
size_t *signature_len)
|
||||
{
|
||||
json_object *hdr = NULL, *pld = NULL, *b64_obj = NULL;
|
||||
char *ch, *header, *payload, *json = NULL;
|
||||
size_t header_len, payload_len, json_len;
|
||||
const char *ch, *header, *payload;
|
||||
char *json = NULL;
|
||||
bool b64 = true;
|
||||
int rc = 0;
|
||||
|
||||
@@ -254,7 +257,7 @@ int parse_json_web_token(const char *token, json_object **header_obj,
|
||||
* and the following parts (if any) are used for JWS Signature, or JWE
|
||||
* Encryption (not considered here).
|
||||
*/
|
||||
header = (char *)token;
|
||||
header = token;
|
||||
|
||||
ch = strchr(token, '.');
|
||||
if (ch == NULL) {
|
||||
@@ -363,9 +366,9 @@ out:
|
||||
*payload_obj = pld;
|
||||
else
|
||||
json_object_put(pld);
|
||||
if (signature != NULL && rc != 0) {
|
||||
free(signature);
|
||||
signature = NULL;
|
||||
if (signature != NULL && *signature != NULL && rc != 0) {
|
||||
free(*signature);
|
||||
*signature = NULL;
|
||||
*signature_len = 0;
|
||||
}
|
||||
if (json != NULL)
|
||||
@@ -653,7 +656,7 @@ int verify_json_web_signature(const char *jws, const unsigned char *payload,
|
||||
BIGNUM *bn_s = NULL;
|
||||
const char *alg;
|
||||
size_t sig_len;
|
||||
char *ch;
|
||||
const char *ch;
|
||||
|
||||
if (jws == NULL || pkey == NULL)
|
||||
return -EINVAL;
|
||||
@@ -1034,7 +1037,7 @@ int clone_tag_def_list(const struct ekmf_tag_def_list *src,
|
||||
if (src->tag_defs[i].description != NULL) {
|
||||
dest->tag_defs[i].description =
|
||||
strdup(src->tag_defs[i].description);
|
||||
if (dest->tag_defs[i].description != NULL) {
|
||||
if (dest->tag_defs[i].description == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
@@ -1930,7 +1933,7 @@ int write_key_blob(const char *filename, unsigned char *key_blob,
|
||||
if (filename == NULL || key_blob == NULL || key_blob_len == 0)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(filename, "w");
|
||||
fp = fopen_nofollow(filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
@@ -2051,7 +2054,7 @@ int write_x509_certificate(const char *pem_filename, X509 *cert)
|
||||
if (pem_filename == NULL || cert == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(pem_filename, "w");
|
||||
fp = fopen_nofollow(pem_filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
@@ -2085,7 +2088,7 @@ int write_x509_request(const char *pem_filename, X509_REQ *req, bool new_hdr)
|
||||
if (pem_filename == NULL || req == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(pem_filename, "w");
|
||||
fp = fopen_nofollow(pem_filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
@@ -2153,7 +2156,7 @@ int write_public_key(const char *pem_filename, EVP_PKEY *pkey)
|
||||
if (pem_filename == NULL || pkey == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(pem_filename, "w");
|
||||
fp = fopen_nofollow(pem_filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
@@ -2605,3 +2608,33 @@ int json_object_object_add_ex(struct json_object *obj, const char *const key,
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
FILE *fopen_nofollow(const char *path, const char *mode)
|
||||
{
|
||||
int flags = O_NOFOLLOW;
|
||||
int fd;
|
||||
FILE *fp;
|
||||
|
||||
/* Determine flags based on mode */
|
||||
if (mode[0] == 'r')
|
||||
flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY;
|
||||
else if (mode[0] == 'w')
|
||||
flags |= O_CREAT | O_TRUNC |
|
||||
((mode[1] == '+') ? O_RDWR : O_WRONLY);
|
||||
else if (mode[0] == 'a')
|
||||
flags |= O_CREAT | O_APPEND |
|
||||
((mode[1] == '+') ? O_RDWR : O_WRONLY);
|
||||
else
|
||||
return NULL;
|
||||
|
||||
fd = open(path, flags, 0600);
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
fp = fdopen(fd, mode);
|
||||
if (fp == NULL) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
@@ -124,4 +124,6 @@ int json_object_object_add_ex(struct json_object *obj, const char *const key,
|
||||
const unsigned int opts);
|
||||
#endif
|
||||
|
||||
FILE *fopen_nofollow(const char *path, const char *mode);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -46,7 +46,7 @@ detect-openssl-version.dep:
|
||||
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
|
||||
echo "#endif" >> $(TMPFILE)
|
||||
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
|
||||
echo "}" >> $(TMPFILE)
|
||||
mv $(TMPFILE) $@
|
||||
|
||||
|
||||
@@ -735,7 +735,8 @@ int kmip_connection_https_perform(struct kmip_connection *conn,
|
||||
switch (conn->config.encoding) {
|
||||
case KMIP_ENCODING_TTLV:
|
||||
rc = kmip_decode_ttlv(write_cb.ttlv.resp_mem_bio, NULL,
|
||||
response, debug);
|
||||
response, KMIP_DECODE_MAX_NESTING_LEVEL,
|
||||
debug);
|
||||
if (rc != 0) {
|
||||
kmip_debug(debug, "kmip_decode_ttlv failed");
|
||||
goto out;
|
||||
@@ -750,7 +751,7 @@ int kmip_connection_https_perform(struct kmip_connection *conn,
|
||||
}
|
||||
|
||||
rc = kmip_decode_json(write_cb.json.resp_obj, NULL, response,
|
||||
debug);
|
||||
KMIP_DECODE_MAX_NESTING_LEVEL, debug);
|
||||
if (rc != 0) {
|
||||
kmip_debug(debug, "kmip_decode_json failed");
|
||||
goto out;
|
||||
@@ -768,7 +769,8 @@ int kmip_connection_https_perform(struct kmip_connection *conn,
|
||||
|
||||
rc = kmip_decode_xml(xmlDocGetRootElement(
|
||||
write_cb.xml.ctx->myDoc),
|
||||
NULL, response, debug);
|
||||
NULL, response,
|
||||
KMIP_DECODE_MAX_NESTING_LEVEL, debug);
|
||||
if (rc != 0) {
|
||||
kmip_debug(debug, "kmip_decode_xml failed");
|
||||
goto out;
|
||||
|
||||
@@ -27,12 +27,16 @@
|
||||
* @param parent the parent node or NULL if no parent exists.
|
||||
* @param node On return: the decoded node. The newly allocated
|
||||
* node has a reference count of 1.
|
||||
* @param max_nesting_level the maximum nesting levels of structures within the
|
||||
* KMIP node. If the nesting level is reached, E2BIG
|
||||
* is returned.
|
||||
* @param debug if true, debug messages are printed
|
||||
*
|
||||
* @returns 0 in case of success, or a negative errno value
|
||||
*/
|
||||
int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
|
||||
struct kmip_node **node, bool debug)
|
||||
struct kmip_node **node, size_t max_nesting_level,
|
||||
bool debug)
|
||||
{
|
||||
json_object *tag_obj, *type_obj, *value_obj, *name_obj;
|
||||
enum kmip_tag tag, v1_attr_tag = 0;
|
||||
@@ -42,6 +46,9 @@ int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
|
||||
int rc, num, i;
|
||||
int64_t int64;
|
||||
|
||||
if (max_nesting_level == 0)
|
||||
return -E2BIG;
|
||||
|
||||
if (obj == NULL || node == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -82,7 +89,7 @@ int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
|
||||
rc = -EBADMSG;
|
||||
goto out;
|
||||
}
|
||||
n->name = strdup(json_object_get_string(tag_obj));
|
||||
n->name = strdup(json_object_get_string(name_obj));
|
||||
}
|
||||
|
||||
type_obj = json_object_object_get(obj, KMIP_JSON_TYPE);
|
||||
@@ -138,7 +145,7 @@ int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
|
||||
for (i = 0; i < num; i++) {
|
||||
rc = kmip_decode_json(
|
||||
json_object_array_get_idx(value_obj, i),
|
||||
n, &e, debug);
|
||||
n, &e, max_nesting_level - 1, debug);
|
||||
if (rc != 0) {
|
||||
kmip_debug(debug, "Failed to parse "
|
||||
"array element %d", i);
|
||||
@@ -614,6 +621,10 @@ int kmip_encode_json(const struct kmip_node *node, json_object **obj,
|
||||
|
||||
case KMIP_TYPE_DATE_TIME:
|
||||
tm = gmtime((time_t *)&node->date_time_value);
|
||||
if (tm == NULL) {
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
strftime(outstr, sizeof(outstr), KMIP_ISO8601_TIMESTAMP_UTC,
|
||||
tm);
|
||||
memb_obj = json_object_new_string(outstr);
|
||||
|
||||
@@ -426,8 +426,14 @@ int kmip_get_key_value(const struct kmip_node *node,
|
||||
/* Must be a KMIP v1.x attribute then */
|
||||
kmip_node_free(attr);
|
||||
|
||||
if (num_attrs != NULL)
|
||||
*num_attrs = kmip_node_get_structure_element_count(node) - 1;
|
||||
if (num_attrs != NULL) {
|
||||
*num_attrs = kmip_node_get_structure_element_count(node);
|
||||
if (*num_attrs == 0) {
|
||||
rc = -EBADMSG;
|
||||
goto error;
|
||||
}
|
||||
(*num_attrs)--;
|
||||
}
|
||||
|
||||
if (v2_attr == NULL)
|
||||
return 0;
|
||||
|
||||
@@ -258,7 +258,7 @@ int kmip_node_add_structure_elements(struct kmip_node *node,
|
||||
*
|
||||
* @param node the KMIP node
|
||||
*
|
||||
* @returns the number of elements, or -1 if the node is not of type structure
|
||||
* @returns the number of elements, or 0 if the node is not of type structure
|
||||
*/
|
||||
unsigned int kmip_node_get_structure_element_count(const struct kmip_node *node)
|
||||
{
|
||||
@@ -266,10 +266,10 @@ unsigned int kmip_node_get_structure_element_count(const struct kmip_node *node)
|
||||
unsigned int i;
|
||||
|
||||
if (node == NULL)
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
if (node->type != KMIP_TYPE_STRUCTURE)
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
element = node->structure_value;
|
||||
for (i = 0; element != NULL; i++)
|
||||
@@ -319,7 +319,7 @@ struct kmip_node *kmip_node_get_structure_element_by_index(
|
||||
* @param node the KMIP node
|
||||
* @param tag the tag to find
|
||||
*
|
||||
* @returns the number of elements, or -1 if the node is not of type structure
|
||||
* @returns the number of elements, or 0 if the node is not of type structure
|
||||
*/
|
||||
unsigned int kmip_node_get_structure_element_by_tag_count(
|
||||
const struct kmip_node *node,
|
||||
@@ -329,10 +329,10 @@ unsigned int kmip_node_get_structure_element_by_tag_count(
|
||||
unsigned int i;
|
||||
|
||||
if (node == NULL)
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
if (node->type != KMIP_TYPE_STRUCTURE)
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
element = node->structure_value;
|
||||
for (i = 0; element != NULL; element = element->next) {
|
||||
@@ -897,7 +897,7 @@ struct kmip_node *kmip_node_clone(const struct kmip_node *node)
|
||||
case KMIP_TYPE_TEXT_STRING:
|
||||
if (node->text_value != NULL) {
|
||||
clone->text_value = strdup(node->text_value);
|
||||
if (node->text_value == NULL)
|
||||
if (clone->text_value == NULL)
|
||||
goto error;
|
||||
clone->length = strlen(clone->text_value);
|
||||
}
|
||||
@@ -1370,7 +1370,8 @@ int kmip_connection_get_server_cert(const char *server,
|
||||
struct kmip_conn_config config = { 0 };
|
||||
struct kmip_connection *conn = NULL;
|
||||
int rc, numcerts, i, port_found = 0;
|
||||
char *hostname = NULL, *tok, *tok2;
|
||||
const char *tok, *tok2;
|
||||
char *hostname = NULL;
|
||||
STACK_OF(X509) *chain;
|
||||
bool do_verify = true;
|
||||
FILE *fp = NULL;
|
||||
@@ -1488,7 +1489,7 @@ retry:
|
||||
}
|
||||
|
||||
if (i == 0 && server_cert_pem != NULL) {
|
||||
fp = fopen(server_cert_pem, "w");
|
||||
fp = fopen_nofollow(server_cert_pem, "w");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
kmip_debug(debug, "Failed to open %s for write",
|
||||
@@ -1506,7 +1507,7 @@ retry:
|
||||
fp = NULL;
|
||||
|
||||
if (server_pubkey_pem != NULL) {
|
||||
fp = fopen(server_pubkey_pem, "w");
|
||||
fp = fopen_nofollow(server_pubkey_pem, "w");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
kmip_debug(debug, "Failed to open %s "
|
||||
@@ -1532,7 +1533,7 @@ retry:
|
||||
|
||||
if (i > 0 && cert_chain_pem != NULL) {
|
||||
if (fp == NULL)
|
||||
fp = fopen(cert_chain_pem, "w");
|
||||
fp = fopen_nofollow(cert_chain_pem, "w");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
kmip_debug(debug, "Failed to open %s for write",
|
||||
|
||||
@@ -94,19 +94,23 @@ int kmip_connection_https_perform(struct kmip_connection *connection,
|
||||
bool debug);
|
||||
void kmip_connection_https_term(struct kmip_connection *connection);
|
||||
|
||||
/* KIMP decoding and encoding internal functions */
|
||||
/* KIMP decoding and encoding internal functions and definitions */
|
||||
#define KMIP_DECODE_MAX_NESTING_LEVEL 32
|
||||
|
||||
int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
||||
bool debug);
|
||||
size_t max_nesting_level, bool debug);
|
||||
int kmip_encode_ttlv(struct kmip_node *node, BIO *bio, size_t *size,
|
||||
bool debug);
|
||||
|
||||
int kmip_decode_json(const json_object *obj, struct kmip_node *parent,
|
||||
struct kmip_node **node, bool debug);
|
||||
struct kmip_node **node, size_t max_nesting_level,
|
||||
bool debug);
|
||||
int kmip_encode_json(const struct kmip_node *node, json_object **obj,
|
||||
bool debug);
|
||||
|
||||
int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
|
||||
struct kmip_node **node, bool debug);
|
||||
struct kmip_node **node, size_t max_nesting_level,
|
||||
bool debug);
|
||||
int kmip_encode_xml(const struct kmip_node *node, xmlNode **xml, bool debug);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -686,14 +686,20 @@ int kmip_get_get_attribute_list_response_payload(const struct kmip_node *node,
|
||||
|
||||
if (kmip_node_get_tag(node) != KMIP_TAG_RESPONSE_PAYLOAD)
|
||||
return -EBADMSG;
|
||||
if (kmip_node_get_type(node) != KMIP_TYPE_STRUCTURE)
|
||||
return -EBADMSG;
|
||||
|
||||
if (unique_id != NULL)
|
||||
*unique_id = kmip_node_get_structure_element_by_tag(node,
|
||||
KMIP_TAG_UNIQUE_IDENTIFIER, 0);
|
||||
|
||||
if (num_attr_refs != NULL)
|
||||
if (num_attr_refs != NULL) {
|
||||
*num_attr_refs =
|
||||
kmip_node_get_structure_element_count(node) - 1;
|
||||
kmip_node_get_structure_element_count(node);
|
||||
if (*num_attr_refs == 0)
|
||||
return -EBADMSG;
|
||||
(*num_attr_refs)--;
|
||||
}
|
||||
|
||||
if (attr_ref == NULL)
|
||||
return 0;
|
||||
@@ -766,6 +772,8 @@ int kmip_get_get_attributes_response_payload(const struct kmip_node *node,
|
||||
|
||||
if (kmip_node_get_tag(node) != KMIP_TAG_RESPONSE_PAYLOAD)
|
||||
return -EBADMSG;
|
||||
if (kmip_node_get_type(node) != KMIP_TYPE_STRUCTURE)
|
||||
return -EBADMSG;
|
||||
|
||||
if (unique_id != NULL)
|
||||
*unique_id = kmip_node_get_structure_element_by_tag(node,
|
||||
@@ -798,8 +806,12 @@ int kmip_get_get_attributes_response_payload(const struct kmip_node *node,
|
||||
/* Must be a KMIP v1.x attribute then */
|
||||
kmip_node_free(attr);
|
||||
|
||||
if (num_attrs != NULL)
|
||||
*num_attrs = kmip_node_get_structure_element_count(node) - 1;
|
||||
if (num_attrs != NULL) {
|
||||
*num_attrs = kmip_node_get_structure_element_count(node);
|
||||
if (*num_attrs == 0)
|
||||
return -EBADMSG;
|
||||
(*num_attrs)--;
|
||||
}
|
||||
|
||||
if (v2_attr == NULL)
|
||||
return 0;
|
||||
|
||||
@@ -493,7 +493,8 @@ int kmip_connection_tls_perform(struct kmip_connection *conn,
|
||||
kmip_debug(debug, "%lu bytes sent", size);
|
||||
|
||||
/* receive the response */
|
||||
rc = kmip_decode_ttlv(conn->plain_tls.bio, NULL, response, debug);
|
||||
rc = kmip_decode_ttlv(conn->plain_tls.bio, NULL, response,
|
||||
KMIP_DECODE_MAX_NESTING_LEVEL, debug);
|
||||
if (rc != 0 || *response == NULL) {
|
||||
kmip_debug(debug, "kmip_decode_ttlv failed");
|
||||
goto out;
|
||||
|
||||
@@ -28,12 +28,15 @@
|
||||
* as many bytes as needed.
|
||||
* @param node On return: the decoded node. The newly allocated
|
||||
* node has a reference count of 1.
|
||||
* @param max_nesting_level the maximum nesting levels of structures within the
|
||||
* KMIP node. If the nesting level is reached, E2BIG
|
||||
* is returned.
|
||||
* @param debug if true, debug messages are printed
|
||||
*
|
||||
* @returns 0 in case of success, or a negative errno value
|
||||
*/
|
||||
int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
||||
bool debug)
|
||||
size_t max_nesting_level, bool debug)
|
||||
{
|
||||
unsigned char padding[KMIP_TTLV_BLOCK_LENGTH];
|
||||
unsigned char ttlv[KMIP_TTLV_HEADER_LENGTH];
|
||||
@@ -44,6 +47,9 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
||||
uint64_t int64;
|
||||
int rc;
|
||||
|
||||
if (max_nesting_level == 0)
|
||||
return -E2BIG;
|
||||
|
||||
if (bio == NULL || node == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -73,18 +79,18 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
||||
n->ref_count = 1;
|
||||
|
||||
/* Tag: 3-byte binary unsigned integer, transmitted big endian */
|
||||
n->tag |= (uint32_t)(ttlv[0] << 16);
|
||||
n->tag |= (uint32_t)(ttlv[1] << 8);
|
||||
n->tag |= (uint32_t)(ttlv[2]);
|
||||
n->tag |= (uint32_t)ttlv[0] << 16;
|
||||
n->tag |= (uint32_t)ttlv[1] << 8;
|
||||
n->tag |= (uint32_t)ttlv[2];
|
||||
|
||||
/* Type: 1 byte containing a coded value that indicates the data type */
|
||||
n->type = ttlv[3];
|
||||
|
||||
/* Length: 32-bit binary integer, transmitted big-endian */
|
||||
n->length |= (uint32_t)(ttlv[4] << 24);
|
||||
n->length |= (uint32_t)(ttlv[5] << 16);
|
||||
n->length |= (uint32_t)(ttlv[6] << 8);
|
||||
n->length |= (uint32_t)(ttlv[7]);
|
||||
n->length |= (uint32_t)ttlv[4] << 24;
|
||||
n->length |= (uint32_t)ttlv[5] << 16;
|
||||
n->length |= (uint32_t)ttlv[6] << 8;
|
||||
n->length |= (uint32_t)ttlv[7];
|
||||
|
||||
kmip_debug(debug, "tag: 0x%x type: 0x%x, length: %u", n->tag, n->type,
|
||||
n->length);
|
||||
@@ -98,6 +104,11 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
||||
case KMIP_TYPE_TEXT_STRING:
|
||||
case KMIP_TYPE_BYTE_STRING:
|
||||
value_len = n->length;
|
||||
if (value_len > INT_MAX) {
|
||||
rc = -EMSGSIZE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
value = calloc(1, value_len + 1);
|
||||
if (value == NULL) {
|
||||
kmip_debug(debug, "calloc failed");
|
||||
@@ -160,14 +171,22 @@ int kmip_decode_ttlv(BIO *bio, size_t *size, struct kmip_node **node,
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
if (size != NULL)
|
||||
|
||||
if (size != NULL) {
|
||||
if (*size < pad_len) {
|
||||
rc = -EMSGSIZE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*size -= pad_len;
|
||||
}
|
||||
}
|
||||
|
||||
switch (n->type) {
|
||||
case KMIP_TYPE_STRUCTURE:
|
||||
while (value_len > 0) {
|
||||
rc = kmip_decode_ttlv(bio, &value_len, &e, debug);
|
||||
rc = kmip_decode_ttlv(bio, &value_len, &e,
|
||||
max_nesting_level - 1, debug);
|
||||
if (rc != 0) {
|
||||
kmip_debug(debug, "kmip_decode_ttlv failed: "
|
||||
"rc: %d", rc);
|
||||
@@ -263,7 +282,7 @@ out:
|
||||
static int kmip_node_get_length(struct kmip_node *node, size_t *length)
|
||||
{
|
||||
struct kmip_node *element;
|
||||
size_t len;
|
||||
size_t len, prev_len;
|
||||
int rc;
|
||||
|
||||
if (node == NULL || length == NULL)
|
||||
@@ -278,10 +297,13 @@ static int kmip_node_get_length(struct kmip_node *node, size_t *length)
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
prev_len = *length;
|
||||
*length += KMIP_TTLV_HEADER_LENGTH + len;
|
||||
if ((len % KMIP_TTLV_BLOCK_LENGTH) != 0)
|
||||
*length += KMIP_TTLV_BLOCK_LENGTH -
|
||||
(len % KMIP_TTLV_BLOCK_LENGTH);
|
||||
if (*length < prev_len)
|
||||
return -EOVERFLOW;
|
||||
|
||||
element = element->next;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "names.h"
|
||||
@@ -72,6 +74,8 @@ int kmip_parse_decimal_uint(const char *str, uint64_t *val)
|
||||
|
||||
if (str == NULL)
|
||||
return -EINVAL;
|
||||
if (str[0] == '-')
|
||||
return -EBADMSG;
|
||||
|
||||
errno = 0;
|
||||
v = strtoull(str, &endptr, 10);
|
||||
@@ -164,13 +168,16 @@ int kmip_parse_hex(const char *str, bool has_prefix, unsigned char **val,
|
||||
* Format a hex string from the byte array specified in val. The caller must
|
||||
* free the returned str.
|
||||
*/
|
||||
int kmip_format_hex(const unsigned char *val, uint32_t length, bool prefix,
|
||||
int kmip_format_hex(const unsigned char *val, size_t length, bool prefix,
|
||||
char **str)
|
||||
{
|
||||
uint32_t str_len, i;
|
||||
size_t str_len, i;
|
||||
char tmp[4];
|
||||
char *ret;
|
||||
|
||||
if (length > (SIZE_MAX - ((prefix ? 2 : 0) + 1)) / 2)
|
||||
return -EINVAL;
|
||||
|
||||
str_len = length * 2 + (prefix ? 2 : 0) + 1;
|
||||
ret = calloc(1, str_len);
|
||||
if (ret == NULL)
|
||||
@@ -220,14 +227,18 @@ int kmip_parse_bignum(const char *str, bool has_prefix, BIGNUM **bn)
|
||||
int kmip_format_bignum(const BIGNUM *bn, bool prefix, char **str)
|
||||
{
|
||||
unsigned char *buf;
|
||||
uint32_t len;
|
||||
uint32_t len, prev_len;
|
||||
int rc;
|
||||
|
||||
len = kmip_encode_bignum_length(bn);
|
||||
/* BIG INTEGERS must be a multiple of 8 bytes long */
|
||||
if ((len % KMIP_BIG_INTEGER_BLOCK_LENGTH) != 0)
|
||||
if ((len % KMIP_BIG_INTEGER_BLOCK_LENGTH) != 0) {
|
||||
prev_len = len;
|
||||
len += KMIP_BIG_INTEGER_BLOCK_LENGTH -
|
||||
(len % KMIP_BIG_INTEGER_BLOCK_LENGTH);
|
||||
if (len < prev_len)
|
||||
return -EOVERFLOW;
|
||||
}
|
||||
|
||||
buf = malloc(len);
|
||||
if (buf == NULL)
|
||||
@@ -402,10 +413,10 @@ int kmip_parse_mask(enum kmip_tag tag, const char *str, char separator,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int kmip_append_string(char **str, int *str_len, char separator,
|
||||
static int kmip_append_string(char **str, size_t *str_len, char separator,
|
||||
const char *append)
|
||||
{
|
||||
int new_len;
|
||||
size_t new_len;
|
||||
char *tmp;
|
||||
|
||||
if (str == NULL || str_len == NULL)
|
||||
@@ -422,6 +433,9 @@ static int kmip_append_string(char **str, int *str_len, char separator,
|
||||
if (append != NULL)
|
||||
new_len += strlen(append);
|
||||
|
||||
if (new_len < *str_len)
|
||||
return -EOVERFLOW;
|
||||
|
||||
tmp = realloc(*str, new_len);
|
||||
if (tmp == NULL)
|
||||
return -ENOMEM;
|
||||
@@ -446,8 +460,9 @@ int kmip_format_mask(enum kmip_tag tag, int32_t value, char separator,
|
||||
char **str)
|
||||
{
|
||||
const struct kmip_enum *info;
|
||||
int rc = 0, i, s_len = 0;
|
||||
char *s = NULL, *tmp;
|
||||
size_t s_len = 0;
|
||||
int rc = 0, i;
|
||||
|
||||
info = kmip_enum_info_by_tag(tag);
|
||||
if (info == NULL || value == 0)
|
||||
@@ -726,3 +741,32 @@ enum kmip_tag kmip_find_v1_attribute_name_tag(struct kmip_node *parent)
|
||||
return 0;
|
||||
}
|
||||
|
||||
FILE *fopen_nofollow(const char *path, const char *mode)
|
||||
{
|
||||
int flags = O_NOFOLLOW;
|
||||
int fd;
|
||||
FILE *fp;
|
||||
|
||||
/* Determine flags based on mode */
|
||||
if (mode[0] == 'r')
|
||||
flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY;
|
||||
else if (mode[0] == 'w')
|
||||
flags |= O_CREAT | O_TRUNC |
|
||||
((mode[1] == '+') ? O_RDWR : O_WRONLY);
|
||||
else if (mode[0] == 'a')
|
||||
flags |= O_CREAT | O_APPEND |
|
||||
((mode[1] == '+') ? O_RDWR : O_WRONLY);
|
||||
else
|
||||
return NULL;
|
||||
|
||||
fd = open(path, flags, 0600);
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
fp = fdopen(fd, mode);
|
||||
if (fp == NULL) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ int kmip_parse_decimal_uint(const char *str, uint64_t *val);
|
||||
int kmip_parse_hex_int(const char *str, int64_t *val);
|
||||
int kmip_parse_hex(const char *str, bool has_prefix, unsigned char **val,
|
||||
uint32_t *length);
|
||||
int kmip_format_hex(const unsigned char *val, uint32_t length, bool prefix,
|
||||
int kmip_format_hex(const unsigned char *val, size_t length, bool prefix,
|
||||
char **str);
|
||||
|
||||
int kmip_parse_bignum(const char *str, bool has_prefix, BIGNUM **bn);
|
||||
@@ -58,4 +58,6 @@ void kmip_node_dump(struct kmip_node *node, bool debug);
|
||||
|
||||
enum kmip_tag kmip_find_v1_attribute_name_tag(struct kmip_node *parent);
|
||||
|
||||
FILE *fopen_nofollow(const char *path, const char *mode);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,12 +28,16 @@
|
||||
* @param parent the parent node or NULL if no parent exists.
|
||||
* @param node On return: the decoded node.The newly allocated
|
||||
* node has a reference count of 1.
|
||||
* @param max_nesting_level the maximum nesting levels of structures within the
|
||||
* KMIP node. If the nesting level is reached, E2BIG
|
||||
* is returned.
|
||||
* @param debug if true, debug messages are printed
|
||||
*
|
||||
* @returns 0 in case of success, or a negative errno value
|
||||
*/
|
||||
int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
|
||||
struct kmip_node **node, bool debug)
|
||||
struct kmip_node **node, size_t max_nesting_level,
|
||||
bool debug)
|
||||
{
|
||||
char *tag_attr = NULL, *name_attr = NULL, *type_attr = NULL;
|
||||
enum kmip_tag tag, v1_attr_tag = 0;
|
||||
@@ -44,6 +48,9 @@ int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
|
||||
int64_t int64;
|
||||
int rc = 0, i;
|
||||
|
||||
if (max_nesting_level == 0)
|
||||
return -E2BIG;
|
||||
|
||||
if (xml == NULL || node == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -122,7 +129,8 @@ int kmip_decode_xml(const xmlNode *xml, struct kmip_node *parent,
|
||||
if (child->type != XML_ELEMENT_NODE)
|
||||
continue;
|
||||
|
||||
rc = kmip_decode_xml(child, n, &e, debug);
|
||||
rc = kmip_decode_xml(child, n, &e,
|
||||
max_nesting_level - 1, debug);
|
||||
if (rc != 0) {
|
||||
kmip_debug(debug, "Failed to parse child "
|
||||
"element %d", i);
|
||||
@@ -452,6 +460,10 @@ int kmip_encode_xml(const struct kmip_node *node, xmlNode **xml, bool debug)
|
||||
|
||||
case KMIP_TYPE_DATE_TIME:
|
||||
tm = gmtime((time_t *)&node->date_time_value);
|
||||
if (tm == NULL) {
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
strftime(tmp_str, sizeof(tmp_str), KMIP_ISO8601_TIMESTAMP_UTC,
|
||||
tm);
|
||||
attr = xmlSetProp(ret_xml, (xmlChar *)KMIP_XML_VALUE,
|
||||
|
||||
@@ -28,7 +28,7 @@ detect-openssl-version.dep:
|
||||
echo " #error openssl version 1.1 is required" >> $(TMPFILE)
|
||||
echo "#endif" >> $(TMPFILE)
|
||||
echo "static void __attribute__((unused)) test(void) {" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_meth_remove(NULL);" >> $(TMPFILE)
|
||||
echo " EVP_PKEY_id(NULL);" >> $(TMPFILE)
|
||||
echo "}" >> $(TMPFILE)
|
||||
mv $(TMPFILE) $@
|
||||
|
||||
|
||||
@@ -258,7 +258,9 @@ static int sk_cca_get_library_functions(const struct sk_ext_cca_lib *cca_lib,
|
||||
*
|
||||
* @param cca_lib the CCA library structure
|
||||
* @param curve_nid the nid specifying the curve.
|
||||
* @param key_token a buffer to store the generated key token
|
||||
* @param key_token a buffer to store the generated key token. If NULL,
|
||||
* the required buffer size is returned in
|
||||
* key_token_length (size query).
|
||||
* @param key_token_length On entry: the size of the buffer
|
||||
* On return: the size of the key token
|
||||
* @param debug if true, debug messages are printed
|
||||
@@ -286,7 +288,7 @@ int SK_CCA_generate_ec_key_pair(const struct sk_ext_cca_lib *cca_lib,
|
||||
long param1 = 0;
|
||||
int rc;
|
||||
|
||||
if (cca_lib == NULL || key_token == NULL || key_token_length == NULL)
|
||||
if (cca_lib == NULL || key_token_length == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (key_token == NULL) {
|
||||
@@ -384,7 +386,9 @@ int SK_CCA_generate_ec_key_pair(const struct sk_ext_cca_lib *cca_lib,
|
||||
* 3, 5, 17, 257, or 65537. Specify zero to choose the
|
||||
* exponent by random (only possible for modulus_bits
|
||||
* up to 2048).
|
||||
* @param key_token a buffer to store the generated key token
|
||||
* @param key_token a buffer to store the generated key token. If NULL,
|
||||
* the required buffer size is returned in
|
||||
* key_token_length (size query).
|
||||
* @param key_token_length On entry: the size of the buffer
|
||||
* On return: the size of the key token
|
||||
* @param debug if true, debug messages are printed
|
||||
@@ -412,7 +416,7 @@ int SK_CCA_generate_rsa_key_pair(const struct sk_ext_cca_lib *cca_lib,
|
||||
long param1 = 0;
|
||||
int rc;
|
||||
|
||||
if (cca_lib == NULL || key_token == NULL || key_token_length == NULL)
|
||||
if (cca_lib == NULL || key_token_length == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (key_token == NULL) {
|
||||
@@ -459,7 +463,7 @@ int SK_CCA_generate_rsa_key_pair(const struct sk_ext_cca_lib *cca_lib,
|
||||
case 257:
|
||||
key_value_structure.public_exp_length = 2;
|
||||
key_value_structure.public_exponent[0] = 0x01;
|
||||
key_value_structure.public_exponent[0] = 0x01;
|
||||
key_value_structure.public_exponent[1] = 0x01;
|
||||
break;
|
||||
case 65537:
|
||||
key_value_structure.public_exp_length = 3;
|
||||
@@ -560,6 +564,11 @@ static const void *sk_cca_get_pka_section(const unsigned char *key_token,
|
||||
section_hdr = (struct cca_section_header *)&key_token[ofs];
|
||||
|
||||
while (section_hdr->section_identifier != section_id) {
|
||||
if (section_hdr->section_length <
|
||||
sizeof(struct cca_section_header)) {
|
||||
sk_debug(debug, "ERROR: invalid section length");
|
||||
return NULL;
|
||||
}
|
||||
ofs += section_hdr->section_length;
|
||||
if (ofs >= token_hdr->token_length) {
|
||||
sk_debug(debug, "ERROR: section %u not found",
|
||||
@@ -1385,7 +1394,7 @@ static int sk_cca_get_public_from_rsa_key(const unsigned char *key_token,
|
||||
return -EINVAL;
|
||||
}
|
||||
if (rsa_pub_section->section_header.section_length <
|
||||
sizeof(struct cca_ec_pub_key_section)) {
|
||||
sizeof(struct cca_rsa_pub_key_section)) {
|
||||
sk_debug(debug, "ERROR: invalid RSA public key section length");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ static int sk_ep11_generate_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
|
||||
CK_RV rv;
|
||||
int rc;
|
||||
|
||||
if (ep11_lib == NULL || key_token == NULL || key_token_length == NULL)
|
||||
if (ep11_lib == NULL || key_token_length == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (key_token == NULL) {
|
||||
@@ -262,7 +262,9 @@ static int sk_ep11_generate_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
|
||||
*
|
||||
* @param ep11_lib the Ep11 library structure
|
||||
* @param curve_nid the nid specifying the curve.
|
||||
* @param key_token a buffer to store the generated key token
|
||||
* @param key_token a buffer to store the generated key token. If NULL,
|
||||
* the required buffer size is returned in
|
||||
* key_token_length (size query).
|
||||
* @param key_token_length On entry: the size of the buffer
|
||||
* On return: the size of the key token
|
||||
* @param debug if true, debug messages are printed
|
||||
@@ -292,7 +294,7 @@ int SK_EP11_generate_ec_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
|
||||
const struct sk_ec_curve_info *curve;
|
||||
int rc;
|
||||
|
||||
if (ep11_lib == NULL || key_token == NULL || key_token_length == NULL)
|
||||
if (ep11_lib == NULL || key_token_length == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
sk_debug(debug, "curve_nid: %d", curve_nid);
|
||||
@@ -331,7 +333,9 @@ int SK_EP11_generate_ec_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
|
||||
* 3, 5, 17, 257, or 65537. Specify zero to choose the
|
||||
* exponent by random.
|
||||
* @param x9_31 if true, generate a X9.31 RSA key
|
||||
* @param key_token a buffer to store the generated key token
|
||||
* @param key_token a buffer to store the generated key token. If NULL,
|
||||
* the required buffer size is returned in
|
||||
* key_token_length (size query).
|
||||
* @param key_token_length On entry: the size of the buffer
|
||||
* On return: the size of the key token
|
||||
* @param debug if true, debug messages are printed
|
||||
@@ -365,7 +369,7 @@ int SK_EP11_generate_rsa_key_pair(const struct sk_ext_ep11_lib *ep11_lib,
|
||||
CK_ULONG priv_tmpl_num = sizeof(priv_tmpl) / sizeof(CK_ATTRIBUTE);
|
||||
int rc;
|
||||
|
||||
if (ep11_lib == NULL || key_token == NULL || key_token_length == NULL)
|
||||
if (ep11_lib == NULL || key_token_length == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
sk_debug(debug, "modulus_bits: %lu pub_exp: %u x9_31: %d",
|
||||
@@ -413,6 +417,8 @@ static unsigned char sk_ep11_parse_der_tag(const unsigned char *data,
|
||||
if (num > sizeof(size_t))
|
||||
return 0;
|
||||
*value_len = data[2];
|
||||
if (data_len < 2 + num)
|
||||
return 0;
|
||||
for (i = 1; i < num; i++) {
|
||||
*value_len <<= 8;
|
||||
*value_len |= data[2 + i];
|
||||
@@ -498,6 +504,8 @@ static int sk_ep11_parse_spki(const unsigned char *spki, size_t spki_len,
|
||||
pub_key, pub_key_len);
|
||||
if (tag != 0x03) /* BITSTRING */
|
||||
return -EINVAL;
|
||||
if (*pub_key_len == 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* skip unsused-bits byte */
|
||||
(*pub_key)++;
|
||||
@@ -555,7 +563,7 @@ int SK_EP11_get_key_type(const unsigned char *key_token,
|
||||
return -EINVAL;
|
||||
|
||||
spki_size = key_token_length - hdr->len;
|
||||
if (spki_size <= 0)
|
||||
if (spki_size == 0)
|
||||
return -EINVAL;
|
||||
|
||||
rc = sk_ep11_parse_spki(key_token + hdr->len, spki_size,
|
||||
@@ -702,7 +710,7 @@ static int sk_ep11_rsa_sign(const unsigned char *key_token,
|
||||
case RSA_X931_PADDING:
|
||||
mech.mechanism = CKM_RSA_X9_31;
|
||||
|
||||
if ((hdr->flags && PKEY_TYPE_EP11_FLAG_X9_31) == 0) {
|
||||
if ((hdr->flags & PKEY_TYPE_EP11_FLAG_X9_31) == 0) {
|
||||
sk_debug(debug, "ERROR: no RSA X9.31 key");
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -1375,7 +1383,7 @@ int SK_EP11_get_public_from_secure_key(const unsigned char *key_token,
|
||||
return -EINVAL;
|
||||
|
||||
spki_size = key_token_length - hdr->len;
|
||||
if (spki_size <= 0)
|
||||
if (spki_size == 0)
|
||||
return -EINVAL;
|
||||
|
||||
rc = sk_ep11_parse_spki(key_token + hdr->len, spki_size,
|
||||
|
||||
@@ -698,6 +698,7 @@ static int sk_pkey_meth_setup_pkey(EVP_PKEY *pkey,
|
||||
struct sk_pkey_data *data;
|
||||
EC_KEY *ec;
|
||||
RSA *rsa;
|
||||
int rc;
|
||||
|
||||
if (pkey == NULL || secure_key == NULL || secure_key_size == 0 ||
|
||||
funcs == NULL)
|
||||
@@ -733,12 +734,14 @@ static int sk_pkey_meth_setup_pkey(EVP_PKEY *pkey,
|
||||
ec = EVP_PKEY_get0_EC_KEY(pkey);
|
||||
if (ec == NULL) {
|
||||
sk_debug(debug, "EVP_PKEY_get0_EC_KEY failed");
|
||||
return -EIO;
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!EC_KEY_set_ex_data(ec, sk_pkey_data_ec_index, data)) {
|
||||
sk_debug(debug, "EC_KEY_set_ex_data failed");
|
||||
return -EIO;
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case EVP_PKEY_RSA:
|
||||
@@ -746,17 +749,29 @@ static int sk_pkey_meth_setup_pkey(EVP_PKEY *pkey,
|
||||
rsa = EVP_PKEY_get0_RSA(pkey);
|
||||
if (rsa == NULL) {
|
||||
sk_debug(debug, "EVP_PKEY_get0_RSA failed");
|
||||
return -EIO;
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!RSA_set_ex_data(rsa, sk_pkey_data_rsa_index, data)) {
|
||||
sk_debug(debug, "RSA_set_ex_data failed");
|
||||
return -EIO;
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (data != NULL) {
|
||||
OPENSSL_free(data->key_blob);
|
||||
OPENSSL_free(data);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -796,13 +811,13 @@ int SK_OPENSSL_init(bool debug)
|
||||
return -EIO;
|
||||
}
|
||||
sk_pkey_meth_default_method_rsa = EVP_PKEY_meth_find(EVP_PKEY_RSA);
|
||||
if (sk_pkey_meth_default_method_ec == NULL) {
|
||||
if (sk_pkey_meth_default_method_rsa == NULL) {
|
||||
sk_debug(debug, "ERROR: EVP_PKEY_meth_find(RSA) failed");
|
||||
return -EIO;
|
||||
}
|
||||
sk_pkey_meth_default_method_rsa_pss =
|
||||
EVP_PKEY_meth_find(EVP_PKEY_RSA_PSS);
|
||||
if (sk_pkey_meth_default_method_ec == NULL) {
|
||||
if (sk_pkey_meth_default_method_rsa_pss == NULL) {
|
||||
sk_debug(debug, "ERROR: EVP_PKEY_meth_find(RSA-PSS) failed");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
#include <string.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/sha.h>
|
||||
@@ -811,6 +813,35 @@ out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static inline FILE *fopen_nofollow(const char *path, const char *mode)
|
||||
{
|
||||
int flags = O_NOFOLLOW;
|
||||
int fd;
|
||||
FILE *fp;
|
||||
|
||||
/* Determine flags based on mode */
|
||||
if (mode[0] == 'r')
|
||||
flags |= (mode[1] == '+') ? O_RDWR : O_RDONLY;
|
||||
else if (mode[0] == 'w')
|
||||
flags |= O_CREAT | O_TRUNC |
|
||||
((mode[1] == '+') ? O_RDWR : O_WRONLY);
|
||||
else if (mode[0] == 'a')
|
||||
flags |= O_CREAT | O_APPEND |
|
||||
((mode[1] == '+') ? O_RDWR : O_WRONLY);
|
||||
else
|
||||
return NULL;
|
||||
|
||||
fd = open(path, flags, 0600);
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
fp = fdopen(fd, mode);
|
||||
if (fp == NULL) {
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a X.509 certificate from the specified PEM file.
|
||||
@@ -863,7 +894,7 @@ int SK_UTIL_write_x509_certificate(const char *pem_filename, X509 *cert)
|
||||
if (pem_filename == NULL || cert == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(pem_filename, "w");
|
||||
fp = fopen_nofollow(pem_filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
@@ -898,7 +929,7 @@ int SK_UTIL_write_x509_request(const char *pem_filename, X509_REQ *req,
|
||||
if (pem_filename == NULL || req == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(pem_filename, "w");
|
||||
fp = fopen_nofollow(pem_filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
@@ -982,7 +1013,7 @@ int SK_UTIL_write_key_blob(const char *filename, unsigned char *key_blob,
|
||||
if (filename == NULL || key_blob == NULL || key_blob_len == 0)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(filename, "w");
|
||||
fp = fopen_nofollow(filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
@@ -1047,7 +1078,7 @@ int SK_UTIL_write_public_key(const char *pem_filename, EVP_PKEY *pkey)
|
||||
if (pem_filename == NULL || pkey == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
fp = fopen(pem_filename, "w");
|
||||
fp = fopen_nofollow(pem_filename, "w");
|
||||
if (fp == NULL)
|
||||
return -errno;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -742,6 +742,8 @@ static int read_cmdline(struct task_t *task)
|
||||
buf[i] = ' ';
|
||||
}
|
||||
name_lens.cmdline_len = num;
|
||||
if (name_lens.cmdline_len > MAX_CMD_LEN)
|
||||
name_lens.cmdline_len = MAX_CMD_LEN;
|
||||
cmdlnlenp = mon_record + sizeof(struct monwrite_hdr);
|
||||
cmdlnlenp += sizeof(struct procd_hdr);
|
||||
cmdlnlenp += sizeof(struct task_t);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -44,7 +44,20 @@
|
||||
#define MAX_GET_DATA 4094 /* maximum GET response data length */
|
||||
#define GET_AREA_LEN MAX_GET_DATA + 512 /* size for GET command area length */
|
||||
#define TIME_BUF_SIZE 128 /* buffer size for date and time string */
|
||||
#define MAX_OID_STR_LEN MAX_OID_LEN * 5 /* max OID string size */
|
||||
|
||||
/*
|
||||
* Define maximum SNMP OID string size.
|
||||
* An OID can have up to 128 sub-ids and each sub-id can have up to 10
|
||||
* digits. When converted to string, this means 1 character for the
|
||||
* period separator and 10 characters for the digits themselves. At
|
||||
* the end of OID string, we need one more character for the null
|
||||
* terminator. The unsigned long data type, which can have up to 20
|
||||
* digits is used to represent a sub-id. This means each sub-id
|
||||
* string now needs 20+1 characters instead of 10+1 in-order to avoid
|
||||
* compiler warnings.
|
||||
*/
|
||||
#define MAX_OID_STR_LEN (MAX_OID_LEN * 21) + 1
|
||||
|
||||
/* definitions for 2.6 qeth */
|
||||
#define QETH_SYSFILE "/sys/bus/ccwgroup/drivers/qeth/notifier_register"
|
||||
#define SIOC_QETH_ADP_SET_SNMP_CONTROL (SIOCDEVPRIVATE + 5)
|
||||
|
||||
@@ -110,7 +110,7 @@ int str_to_oid_conv ( char* uc_oid, oid* ul_oid )
|
||||
*********************************************************************/
|
||||
int oid_to_str_conv (oid* ul_oid, size_t length, char* uc_oid )
|
||||
{
|
||||
#define MAX_CHARS 50 /* size of buffer */
|
||||
#define MAX_CHARS 22 /* size of buffer */
|
||||
int i;
|
||||
short valid = TRUE;
|
||||
char buffer[MAX_CHARS]; /* buffer used for conversion */
|
||||
@@ -125,9 +125,9 @@ int oid_to_str_conv (oid* ul_oid, size_t length, char* uc_oid )
|
||||
{
|
||||
/* convert and append OID digit to return string */
|
||||
if (i == 0)
|
||||
sprintf( buffer, "%lu", ul_oid[i] );
|
||||
snprintf( buffer, sizeof(buffer), "%lu", ul_oid[i] );
|
||||
else
|
||||
sprintf( buffer, ".%lu", ul_oid[i] );
|
||||
snprintf( buffer, sizeof(buffer), ".%lu", ul_oid[i] );
|
||||
|
||||
strcat( uc_oid, buffer );
|
||||
} /* end for */
|
||||
|
||||
599
rust/Cargo.lock
generated
599
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,18 +11,18 @@ members = [
|
||||
"pvsecret",
|
||||
"pvverify",
|
||||
"utils",
|
||||
"utils_macros",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.75.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[workspace.lints.rust]
|
||||
missing_docs = { level = "deny", priority = 1 }
|
||||
missing_debug_implementations = "warn"
|
||||
non_ascii_idents = "warn"
|
||||
nonstandard-style = "warn"
|
||||
trivial_numeric_casts = "warn"
|
||||
unstable_features = "warn"
|
||||
|
||||
@@ -114,8 +114,10 @@ ifneq ($(HAVE_DRACUT),0)
|
||||
$(INSTALL) -m 755 pvebc/$(SEL_EBC_MODDIR)/module-setup.sh \
|
||||
pvebc/$(SEL_EBC_MODDIR)/override-crypttab.sh \
|
||||
pvebc/$(SEL_EBC_MODDIR)/pvebc-wrapper.sh \
|
||||
pvebc/$(SEL_EBC_MODDIR)/boot-mount.sh \
|
||||
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-modules.conf \
|
||||
$(DESTDIR)$(DRACUTMODDIR)/$(SEL_EBC_MODDIR)
|
||||
$(INSTALL) -m 644 pvebc/$(SEL_EBC_MODDIR)/boot.mount \
|
||||
$(INSTALL) -m 644 pvebc/$(SEL_EBC_MODDIR)/sel-ebc-boot-mount.service \
|
||||
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-override-crypttab.service \
|
||||
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-paes-enforce.service \
|
||||
pvebc/$(SEL_EBC_MODDIR)/sel-ebc-pvebc.service \
|
||||
|
||||
@@ -6,15 +6,15 @@ license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.95"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
anyhow = "1.0.102"
|
||||
clap = { version = "4.6", features = ["derive"] }
|
||||
libc = "0.2"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
utils = { path = "../utils" }
|
||||
zerocopy = { version="0.8", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.2"
|
||||
clap = { version ="4.5", features = ["derive"]}
|
||||
clap = { version ="4.6", features = ["derive"]}
|
||||
lazy_static = "1.5"
|
||||
|
||||
@@ -7,11 +7,11 @@ mod msa;
|
||||
mod query;
|
||||
mod stfle;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Result as anyhowRes;
|
||||
use clap::Parser;
|
||||
use std::io::ErrorKind;
|
||||
use std::result::Result::Ok;
|
||||
|
||||
use anyhow::{bail, Result as anyhowRes};
|
||||
use clap::Parser;
|
||||
use utils::print_version;
|
||||
|
||||
use crate::cli::{Cli, Format};
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::cli::Cli;
|
||||
use core::fmt::{Display, Formatter, Result};
|
||||
|
||||
use serde::{Serialize, Serializer};
|
||||
use utils::HexSlice;
|
||||
|
||||
use crate::cli::Cli;
|
||||
|
||||
/// Number of total function codes (0 to 127)
|
||||
pub const NUMBER_FUNC_CODES: usize = 128;
|
||||
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Error;
|
||||
use std::io::Read;
|
||||
use std::io::{Error, Read};
|
||||
use std::ops::Index;
|
||||
use std::result::Result;
|
||||
|
||||
use zerocopy::FromBytes;
|
||||
|
||||
use crate::msa::InstructionKind;
|
||||
use crate::msa::QueryAuthInfo;
|
||||
use crate::msa::{InstructionKind, QueryAuthInfo};
|
||||
|
||||
/// Path to sysfs in which the query and qai informations are fetched from
|
||||
const SYSFS_PATH: &str = "/sys/devices/system/cpu/cpacf/";
|
||||
|
||||
@@ -15,17 +15,18 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.5"
|
||||
curl = "0.4.47"
|
||||
curl = "0.4.49"
|
||||
enum_dispatch = "0.3.13"
|
||||
foreign-types = "0.3.2"
|
||||
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.70"
|
||||
openssl-sys = "0.9.105"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
thiserror = "2.0.11"
|
||||
log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
openssl = "0.10.80"
|
||||
openssl-sys = "0.9.116"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
thiserror = "2.0.18"
|
||||
zerocopy = { version="0.8", features = ["derive"] }
|
||||
|
||||
pv_core = { path = "../pv_core", package = "s390_pv_core", version = "0.12.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0.177"
|
||||
testing_logger = "0.1.1"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use pv_core::misc::encode_hex;
|
||||
use s390_pv as pv;
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use pv::misc::{read_certs, read_file};
|
||||
use pv::request::EcPubKeyCoord;
|
||||
use pv::{Error, Result};
|
||||
use pv_core::misc::encode_hex;
|
||||
use s390_pv as pv;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let hkd = args().nth(1).expect("Expect one Host-key document");
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023, 2024
|
||||
|
||||
use std::{
|
||||
io::{Read, Seek, SeekFrom::Current},
|
||||
mem::size_of,
|
||||
};
|
||||
use std::io::SeekFrom::Current;
|
||||
use std::io::{Read, Seek};
|
||||
use std::mem::size_of;
|
||||
|
||||
use log::{debug, warn};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64};
|
||||
@@ -13,6 +12,15 @@ use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32, U64
|
||||
// (SE) boot request control block aka SE header
|
||||
use crate::{assert_size, request::MagicValue, static_assert, Error, Result, PAGESIZE};
|
||||
|
||||
/// Version of the Secure Execution header
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SeHdrVersion {
|
||||
/// Secure Execution header v1
|
||||
One = 0x100,
|
||||
/// Secure Execution header v2
|
||||
Two = 0x200,
|
||||
}
|
||||
|
||||
/// Struct containing all SE-header tags.
|
||||
///
|
||||
/// Contains:
|
||||
@@ -206,7 +214,7 @@ impl BootHdrTags {
|
||||
///
|
||||
/// This function will return an error if the header could not be found in
|
||||
/// `img` or is invalid.
|
||||
pub fn from_se_image<R>(img: &mut R) -> Result<Self>
|
||||
pub fn from_se_image<R>(img: &mut R) -> Result<(Self, SeHdrVersion)>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
@@ -233,10 +241,14 @@ impl BootHdrTags {
|
||||
};
|
||||
|
||||
// Some sanity checks
|
||||
if hdr_head.version.get() != 0x100 {
|
||||
debug!("Unsupported hdr-version: {:0>4x}", hdr_head.version.get());
|
||||
return Err(Error::InvBootHdr);
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// go to the Boot header tag
|
||||
img.seek(Current(
|
||||
@@ -249,12 +261,15 @@ impl BootHdrTags {
|
||||
let mut tag = [0u8; BootHdrHead::TAG_SIZE];
|
||||
img.read_exact(tag.as_mut_slice())?;
|
||||
|
||||
Ok(Self {
|
||||
pld: hdr_head.pld,
|
||||
ald: hdr_head.ald,
|
||||
tld: hdr_head.tld,
|
||||
tag,
|
||||
})
|
||||
Ok((
|
||||
Self {
|
||||
pld: hdr_head.pld,
|
||||
ald: hdr_head.ald,
|
||||
tld: hdr_head.tld,
|
||||
tag,
|
||||
},
|
||||
hdr_version,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +335,7 @@ mod tests {
|
||||
fn from_se_image_hdr() {
|
||||
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
|
||||
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap();
|
||||
assert_eq!(hdr_tags, EXP_HDR);
|
||||
assert_eq!(hdr_tags, (EXP_HDR, SeHdrVersion::One));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -356,18 +371,19 @@ mod tests {
|
||||
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
|
||||
img[0x12000..0x12280].copy_from_slice(bin_hdr);
|
||||
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(img)).unwrap();
|
||||
assert_eq!(hdr_tags, EXP_HDR);
|
||||
assert_eq!(hdr_tags, (EXP_HDR, SeHdrVersion::One));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tags_convert_u8() {
|
||||
let bin_hdr = get_test_asset!("exp/secure_guest.hdr");
|
||||
let hdr_tags = BootHdrTags::from_se_image(&mut Cursor::new(*bin_hdr)).unwrap();
|
||||
let ser: &[u8] = hdr_tags.as_ref();
|
||||
let ser: &[u8] = hdr_tags.0.as_ref();
|
||||
let mut ser = ser.to_vec();
|
||||
|
||||
let der: BootHdrTags = ser.clone().try_into().unwrap();
|
||||
assert_eq!(hdr_tags, der);
|
||||
assert_eq!(hdr_tags.0, der);
|
||||
assert_eq!(hdr_tags.1, SeHdrVersion::One);
|
||||
|
||||
ser.pop();
|
||||
let der: Result<BootHdrTags> = ser.clone().try_into();
|
||||
@@ -380,7 +396,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn se_img_metadata() {
|
||||
fn se_img_metadata_v1() {
|
||||
let metadata = SeImgMetaData::new_v1(0x14000, 0x16000);
|
||||
let data = [
|
||||
83, 101, 73, 109, 103, 76, 110, 120, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 1, 0, 0, 0, 0,
|
||||
|
||||
@@ -2,25 +2,73 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023, 2024
|
||||
|
||||
use std::{convert::TryInto, fmt::Display, ops::Range};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Display;
|
||||
use std::ops::Range;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use openssl::{
|
||||
derive::Deriver,
|
||||
ec::{EcGroup, EcKey},
|
||||
hash::{DigestBytes, MessageDigest},
|
||||
md::MdRef,
|
||||
nid::Nid,
|
||||
pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public},
|
||||
pkey_ctx::{HkdfMode, PkeyCtx},
|
||||
rand::rand_bytes,
|
||||
rsa::Padding,
|
||||
sign::{Signer, Verifier},
|
||||
symm::{decrypt_aead as openssl_decrypt_aead, encrypt_aead as openssl_encrypt_aead, Cipher},
|
||||
use openssl::bn::BigNumContext;
|
||||
use openssl::derive::Deriver;
|
||||
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, KeyType, PKey, PKeyRef, Private, Public};
|
||||
use openssl::pkey_ctx::{HkdfMode, PkeyCtx};
|
||||
use openssl::rand::rand_bytes;
|
||||
use openssl::rsa::Padding;
|
||||
use openssl::sign::{Signer, Verifier};
|
||||
use openssl::symm::{
|
||||
decrypt_aead as openssl_decrypt_aead, encrypt_aead as openssl_encrypt_aead, Cipher,
|
||||
};
|
||||
use pv_core::request::Confidential;
|
||||
|
||||
use crate::{error::Result, Error};
|
||||
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]>;
|
||||
@@ -71,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()
|
||||
}
|
||||
@@ -159,6 +208,12 @@ impl SymKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SymKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SymKey({:?})", self.key_type())
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an hkdf according to RFC 5869.
|
||||
/// See [`OpenSSL HKDF`]()
|
||||
///
|
||||
@@ -205,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
|
||||
@@ -539,8 +808,166 @@ pub(crate) fn verify_signature<T: HasPublic>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use super::*;
|
||||
use crate::{get_test_asset, test_utils::*, PvCoreError};
|
||||
use crate::test_utils::*;
|
||||
use crate::{get_test_asset, PvCoreError};
|
||||
|
||||
/// Test that deterministic RNG contexts are thread-local and don't interfere.
|
||||
///
|
||||
/// Per OpenSSL documentation (RAND_get0_primary(3)):
|
||||
/// "The public and private DRBG are thread-local instances, which are used by
|
||||
/// RAND_bytes() and RAND_priv_bytes(), respectively."
|
||||
///
|
||||
/// Reference: <https://docs.openssl.org/3.1/man3/RAND_get0_primary/>
|
||||
///
|
||||
/// Note: RAND_set0_public() and RAND_set0_private() require OpenSSL >= 3.1.
|
||||
#[test]
|
||||
fn test_deterministic_rng_thread_isolation() {
|
||||
use std::sync::Barrier;
|
||||
|
||||
use openssl::rand::rand_bytes;
|
||||
|
||||
// Barriers to synchronize: thread1 installs → thread2 installs → both generate → both
|
||||
// complete
|
||||
let barrier_after_t1_install = Arc::new(Barrier::new(2));
|
||||
let barrier_after_t2_install = Arc::new(Barrier::new(2));
|
||||
let barrier_after_rand_bytes = Arc::new(Barrier::new(2));
|
||||
|
||||
let barrier1_clone = Arc::clone(&barrier_after_t1_install);
|
||||
let barrier2_clone = Arc::clone(&barrier_after_t2_install);
|
||||
let barrier3_clone = Arc::clone(&barrier_after_rand_bytes);
|
||||
|
||||
// Thread 1: Install deterministic RNG with specific entropy
|
||||
let thread1 = thread::spawn(move || {
|
||||
let entropy = [0x42u8; 4096];
|
||||
let nonce = [0x24u8; 48];
|
||||
|
||||
// Install thread-local deterministic RNG
|
||||
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
|
||||
|
||||
// Signal thread2 that we've installed our RNG
|
||||
barrier1_clone.wait();
|
||||
|
||||
// Wait for thread2 to install its RNG
|
||||
barrier2_clone.wait();
|
||||
|
||||
// Now generate bytes while thread2 also has its RNG installed
|
||||
let mut buf = [0u8; 32];
|
||||
rand_bytes(&mut buf).unwrap();
|
||||
|
||||
// Wait for thread2 to also complete rand_bytes
|
||||
barrier3_clone.wait();
|
||||
|
||||
buf
|
||||
});
|
||||
|
||||
// Thread 2: Install different deterministic RNG after thread1
|
||||
let thread2 = thread::spawn(move || {
|
||||
// Wait for thread1 to install its RNG first
|
||||
barrier_after_t1_install.wait();
|
||||
|
||||
// Now install our own thread-local deterministic RNG with different entropy
|
||||
let entropy = [0xAAu8; 4096];
|
||||
let nonce = [0x55u8; 48];
|
||||
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
|
||||
|
||||
// Signal thread1 that we've installed our RNG
|
||||
barrier_after_t2_install.wait();
|
||||
|
||||
// Generate bytes with our different entropy (concurrently with thread1)
|
||||
let mut buf = [0u8; 32];
|
||||
rand_bytes(&mut buf).unwrap();
|
||||
|
||||
// Wait for thread1 to also complete rand_bytes
|
||||
barrier_after_rand_bytes.wait();
|
||||
|
||||
buf
|
||||
});
|
||||
|
||||
let t1_buf = thread1.join().unwrap();
|
||||
let t2_buf = thread2.join().unwrap();
|
||||
|
||||
// Expected deterministic values for thread 1 (entropy=0x42, nonce=0x24)
|
||||
let expected_t1: [u8; 32] = [
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
];
|
||||
|
||||
// Expected deterministic values for thread 2 (entropy=0xAA, nonce=0x55)
|
||||
let expected_t2: [u8; 32] = [
|
||||
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
|
||||
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
|
||||
];
|
||||
|
||||
// Verify each thread produced its expected deterministic output
|
||||
assert_eq!(
|
||||
t1_buf, expected_t1,
|
||||
"Thread 1 should produce deterministic output"
|
||||
);
|
||||
assert_eq!(
|
||||
t2_buf, expected_t2,
|
||||
"Thread 2 should produce deterministic output"
|
||||
);
|
||||
|
||||
// Also verify they are different (proves thread-local isolation)
|
||||
assert_ne!(
|
||||
t1_buf, t2_buf,
|
||||
"Different thread-local entropy should produce different output"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that the original RNG is properly restored after DeterministicTestRandGuard is dropped
|
||||
#[test]
|
||||
fn test_deterministic_rng_restoration() {
|
||||
use openssl::rand::rand_bytes;
|
||||
|
||||
// Generate random bytes with system RNG before installing deterministic RNG
|
||||
let mut before_buf = [0u8; 32];
|
||||
rand_bytes(&mut before_buf).unwrap();
|
||||
|
||||
let deterministic_buf = {
|
||||
let entropy = [0x42u8; 4096];
|
||||
let nonce = [0x24u8; 48];
|
||||
|
||||
// Install deterministic RNG
|
||||
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
|
||||
|
||||
// Generate deterministic bytes
|
||||
let mut buf = [0u8; 32];
|
||||
rand_bytes(&mut buf).unwrap();
|
||||
|
||||
// Expected deterministic output
|
||||
let expected: [u8; 32] = [
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
];
|
||||
assert_eq!(buf, expected, "Should produce deterministic output");
|
||||
|
||||
buf
|
||||
// _rng is dropped here, should restore original RNG
|
||||
};
|
||||
|
||||
// Generate random bytes again with restored system RNG
|
||||
let mut after_buf = [0u8; 32];
|
||||
rand_bytes(&mut after_buf).unwrap();
|
||||
|
||||
// The system RNG should produce different random values each time
|
||||
// (extremely unlikely to match the deterministic output)
|
||||
assert_ne!(
|
||||
after_buf, deterministic_buf,
|
||||
"After restoration, system RNG should produce different random values"
|
||||
);
|
||||
|
||||
// Also verify that before and after are different (system RNG produces random values)
|
||||
// Note: This could theoretically fail with probability 1/2^256, but that's negligible
|
||||
assert_ne!(
|
||||
before_buf, after_buf,
|
||||
"System RNG should produce different random values on each call"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_ec() {
|
||||
@@ -593,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;
|
||||
@@ -766,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,17 +87,20 @@ 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),
|
||||
|
||||
#[error("Input does not contain an attestation request")]
|
||||
NoArcb,
|
||||
|
||||
#[error("The attestation request has an unknown version (.0)")]
|
||||
#[error("The attestation request has an unknown version {0}")]
|
||||
BinArcbInvVersion(u32),
|
||||
|
||||
#[error(
|
||||
"The attestation request encrypted sice is to0 small (.0). Request probably tampered with."
|
||||
"The attestation request encrypted sice is too small {0}. Request probably tampered with."
|
||||
)]
|
||||
BinArcbSeaSmall(u32),
|
||||
|
||||
@@ -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
|
||||
@@ -173,8 +185,16 @@ pub enum HkdVerifyErrorType {
|
||||
IssuerMismatch,
|
||||
#[error("No CRL distribution points found")]
|
||||
NoCrlDP,
|
||||
#[error("CRL distribution point uses unsupported protocol (only HTTP/HTTPS allowed)")]
|
||||
InvalidCrlProtocol,
|
||||
#[error("The IBM Z signing key could not be verified. Error occurred at level {1}")]
|
||||
IbmSignInvalid(#[source] openssl::x509::X509VerifyResult, u32),
|
||||
#[error("Too many redirections during CRL download")]
|
||||
TooManyRedirectionsCrlDownload,
|
||||
#[error("CRL download exceeds maximum file size of {} MiB", .0 / (1024 * 1024))]
|
||||
CrlDownloadTooLarge(u64),
|
||||
#[error("CRL download failed")]
|
||||
CrlDownloadFailed,
|
||||
}
|
||||
|
||||
macro_rules! bail_hkd_verify {
|
||||
|
||||
@@ -63,13 +63,11 @@ pub mod uv {
|
||||
pub mod attest {
|
||||
pub use pv_core::attest::*;
|
||||
|
||||
pub use crate::uvattest::{
|
||||
additional::AdditionalData,
|
||||
arcb::{
|
||||
AttestationAuthenticated, AttestationFlags, AttestationRequest, AttestationVersion,
|
||||
},
|
||||
attest::{AttestationItems, AttestationMeasurement},
|
||||
pub use crate::uvattest::additional::AdditionalData;
|
||||
pub use crate::uvattest::arcb::{
|
||||
AttestationAuthenticated, AttestationFlags, AttestationRequest, AttestationVersion,
|
||||
};
|
||||
pub use crate::uvattest::attest::{AttestationItems, AttestationMeasurement};
|
||||
}
|
||||
|
||||
/// Definitions and functions to write objects in PEM format
|
||||
@@ -91,27 +89,25 @@ pub use crate::error::HkdVerifyErrorType;
|
||||
|
||||
/// Functionalities to build UV requests
|
||||
pub mod request {
|
||||
pub use crate::{
|
||||
brcb::{seek_se_hdr_start, BootHdrTags, SeImgMetaData},
|
||||
crypto::{
|
||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, random_array,
|
||||
AeadDecryptionResult, AeadEncryptionResult, Aes256GcmKey, Aes256XtsKey, SymKey,
|
||||
SymKeyType, SHA_512_HASH_LEN,
|
||||
},
|
||||
req::{EcPubKeyCoord, Encrypt, Keyslot, ReqEncrCtx, Request},
|
||||
verify::{CertVerifier, HkdVerifier, NoVerifyHkd},
|
||||
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, HybridPKey, Keyslot, KeyslotV1, KeyslotV2, ReqEncrCtx,
|
||||
Request,
|
||||
};
|
||||
pub use crate::verify::{CertVerifier, HkdVerifier, NoVerifyHkd};
|
||||
|
||||
/// Reexports some useful OpenSSL symbols
|
||||
pub mod openssl {
|
||||
pub use openssl::{
|
||||
error::ErrorStack,
|
||||
hash::DigestBytes,
|
||||
nid::Nid,
|
||||
pkey,
|
||||
sha::{Sha256, Sha512},
|
||||
x509,
|
||||
};
|
||||
pub use openssl::error::ErrorStack;
|
||||
pub use openssl::hash::DigestBytes;
|
||||
pub use openssl::nid::Nid;
|
||||
pub use openssl::sha::{Sha256, Sha512};
|
||||
pub use openssl::{pkey, x509};
|
||||
// rust-OpenSSL does not define these NIDs
|
||||
#[allow(missing_docs)]
|
||||
pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519);
|
||||
@@ -120,7 +116,6 @@ pub mod request {
|
||||
}
|
||||
|
||||
pub use pv_core::request::*;
|
||||
|
||||
pub use pv_core::PolicyReference;
|
||||
}
|
||||
|
||||
@@ -128,11 +123,9 @@ pub mod request {
|
||||
pub mod secret {
|
||||
pub use pv_core::secret::*;
|
||||
|
||||
pub use crate::uvsecret::{
|
||||
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
|
||||
ext_secret::ExtSecret,
|
||||
guest_secret::GuestSecret,
|
||||
retr_secret::{IbmProtectedKey, RetrievedSecret},
|
||||
user_data::verify_asrcb_and_get_user_data,
|
||||
};
|
||||
pub use crate::uvsecret::asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion};
|
||||
pub use crate::uvsecret::ext_secret::ExtSecret;
|
||||
pub use crate::uvsecret::guest_secret::GuestSecret;
|
||||
pub use crate::uvsecret::retr_secret::{IbmProtectedKey, RetrievedSecret};
|
||||
pub use crate::uvsecret::user_data::verify_asrcb_and_get_user_data;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use std::ffi::c_int;
|
||||
use std::fmt;
|
||||
|
||||
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
|
||||
use openssl::x509::{X509CrlRef, X509Ref};
|
||||
use std::ffi::c_int;
|
||||
|
||||
mod ffi {
|
||||
extern "C" {
|
||||
@@ -99,9 +99,8 @@ impl AkidExtension for X509CrlRef {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::test_utils::load_gen_cert;
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::load_gen_cert;
|
||||
|
||||
#[test]
|
||||
fn akid() {
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use core::slice;
|
||||
use std::ffi::c_int;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr;
|
||||
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl_sys::BIO_new_mem_buf;
|
||||
use std::ffi::c_int;
|
||||
use std::{marker::PhantomData, ptr};
|
||||
|
||||
pub struct BioMem(*mut openssl_sys::BIO);
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
pub use crate::openssl_extensions::stackable_crl::*;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::{Stack, StackRef},
|
||||
x509::{
|
||||
store::{X509StoreBuilderRef, X509StoreRef},
|
||||
X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509,
|
||||
},
|
||||
};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::stack::{Stack, StackRef};
|
||||
use openssl::x509::store::{X509StoreBuilderRef, X509StoreRef};
|
||||
use openssl::x509::{X509CrlRef, X509NameRef, X509Ref, X509StoreContextRef, X509};
|
||||
|
||||
pub use crate::openssl_extensions::stackable_crl::*;
|
||||
|
||||
pub fn opt_to_ptr<T: ForeignTypeRef>(o: Option<&T>) -> *mut T::CType {
|
||||
match o {
|
||||
|
||||
595
rust/pv/src/openssl_extensions/ml_kem.rs
Normal file
595
rust/pv/src/openssl_extensions/ml_kem.rs
Normal file
@@ -0,0 +1,595 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
#![cfg_attr(not(test), expect(unused))]
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use foreign_types::ForeignType;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::pkey::{KeyType, PKey, Private};
|
||||
use openssl::pkey_ctx::PkeyCtx;
|
||||
|
||||
// automatically generated by rust-bindgen 0.69.5
|
||||
|
||||
mod ffi {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct evp_pkey_ctx_st {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
pub type EvpPkeyCtx = evp_pkey_ctx_st;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ossl_param_st {
|
||||
pub key: *const ::std::os::raw::c_char,
|
||||
pub data_type: ::std::os::raw::c_uint,
|
||||
pub data: *mut ::std::os::raw::c_void,
|
||||
pub data_size: usize,
|
||||
pub return_size: usize,
|
||||
}
|
||||
pub type OsslParam = ossl_param_st;
|
||||
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_CTX_new_from_name(
|
||||
libctx: *mut ::std::os::raw::c_void,
|
||||
name: *const ::std::os::raw::c_char,
|
||||
propquery: *const ::std::os::raw::c_char,
|
||||
) -> *mut EvpPkeyCtx;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_keygen_init(ctx: *mut EvpPkeyCtx) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_generate(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
ppkey: *mut *mut ::std::os::raw::c_void,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_CTX_free(ctx: *mut EvpPkeyCtx);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_encapsulate_init(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
params: *const OsslParam,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_encapsulate(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
wrappedkey: *mut ::std::os::raw::c_uchar,
|
||||
wrappedkeylen: *mut usize,
|
||||
genkey: *mut ::std::os::raw::c_uchar,
|
||||
genkeylen: *mut usize,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_decapsulate_init(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
params: *const OsslParam,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn EVP_PKEY_decapsulate(
|
||||
ctx: *mut EvpPkeyCtx,
|
||||
unwrapped: *mut ::std::os::raw::c_uchar,
|
||||
unwrappedlen: *mut usize,
|
||||
wrapped: *const ::std::os::raw::c_uchar,
|
||||
wrappedlen: usize,
|
||||
) -> ::std::os::raw::c_int;
|
||||
}
|
||||
}
|
||||
|
||||
const ML_KEM_512_NAME: &CStr = c"ML-KEM-512";
|
||||
const ML_KEM_768_NAME: &CStr = c"ML-KEM-768";
|
||||
const ML_KEM_1024_NAME: &CStr = c"ML-KEM-1024";
|
||||
|
||||
fn ml_kem_name(key_type: KeyType) -> Result<&'static CStr, ErrorStack> {
|
||||
if key_type == KeyType::ML_KEM_512 {
|
||||
Ok(ML_KEM_512_NAME)
|
||||
} else if key_type == KeyType::ML_KEM_768 {
|
||||
Ok(ML_KEM_768_NAME)
|
||||
} else if key_type == KeyType::ML_KEM_1024 {
|
||||
Ok(ML_KEM_1024_NAME)
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulate a key
|
||||
pub trait PkeyEncapsulateContext {
|
||||
/// Initialize the encapsulation operation.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn encapsulate_init(&mut self) -> Result<(), ErrorStack>;
|
||||
|
||||
/// Perform the encapsulation operation.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrappedkey`: Optional buffer to receive the wrapped key.
|
||||
/// - `genkey`: Optional buffer to receive the generated key.
|
||||
///
|
||||
/// # Returns
|
||||
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn encapsulate(
|
||||
&mut self,
|
||||
wrappedkey: Option<&mut [u8]>,
|
||||
genkey: Option<&mut [u8]>,
|
||||
) -> Result<(usize, usize), ErrorStack>;
|
||||
|
||||
/// Convenience method to encapsulate into vectors.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrappedkey`: Buffer to receive the wrapped key (ciphertext).
|
||||
/// - `genkey`: Buffer to receive the generated key (shared secret).
|
||||
///
|
||||
/// # Returns
|
||||
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn encapsulate_to_vec(
|
||||
&mut self,
|
||||
wrappedkey: &mut Vec<u8>,
|
||||
genkey: &mut Vec<u8>,
|
||||
) -> Result<(usize, usize), ErrorStack> {
|
||||
let wrappedkey_base = wrappedkey.len();
|
||||
let genkey_base = genkey.len();
|
||||
|
||||
// Query the required output buffer sizes.
|
||||
let (wrappedkey_len, genkey_len) = self.encapsulate(None, None)?;
|
||||
|
||||
wrappedkey.resize(wrappedkey_base + wrappedkey_len, 0);
|
||||
genkey.resize(genkey_base + genkey_len, 0);
|
||||
|
||||
let (wrappedkey_len, genkey_len) = self.encapsulate(
|
||||
Some(&mut wrappedkey[wrappedkey_base..]),
|
||||
Some(&mut genkey[genkey_base..]),
|
||||
)?;
|
||||
|
||||
wrappedkey.truncate(wrappedkey_base + wrappedkey_len);
|
||||
genkey.truncate(genkey_base + genkey_len);
|
||||
|
||||
Ok((wrappedkey_len, genkey_len))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PkeyEncapsulateContext for PkeyCtx<T> {
|
||||
#[inline]
|
||||
fn encapsulate_init(&mut self) -> Result<(), ErrorStack> {
|
||||
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
|
||||
// configuration is intentionally unsupported here, so a null params pointer is passed.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_encapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn encapsulate(
|
||||
&mut self,
|
||||
wrappedkey: Option<&mut [u8]>,
|
||||
genkey: Option<&mut [u8]>,
|
||||
) -> Result<(usize, usize), ErrorStack> {
|
||||
let mut wrappedkey_len = wrappedkey.as_ref().map_or(0, |buf| buf.len());
|
||||
let mut genkey_len = genkey.as_ref().map_or(0, |buf| buf.len());
|
||||
|
||||
let wrappedkey_ptr = wrappedkey
|
||||
.map(|buf| buf.as_mut_ptr())
|
||||
.unwrap_or(std::ptr::null_mut());
|
||||
let genkey_ptr = genkey
|
||||
.map(|buf| buf.as_mut_ptr())
|
||||
.unwrap_or(std::ptr::null_mut());
|
||||
|
||||
// SAFETY: All pointers are either valid mutable buffers or null.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_encapsulate(
|
||||
self.as_ptr() as *mut ffi::EvpPkeyCtx,
|
||||
wrappedkey_ptr,
|
||||
&mut wrappedkey_len,
|
||||
genkey_ptr,
|
||||
&mut genkey_len,
|
||||
)
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok((wrappedkey_len, genkey_len))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decapsulate a key
|
||||
pub trait PkeyDecapsulateContext {
|
||||
/// Initialize the decapsulation operation.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn decapsulate_init(&mut self) -> Result<(), ErrorStack>;
|
||||
|
||||
/// Perform the decapsulation operation.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
|
||||
/// - `unwrapped`: Optional buffer to receive the unwrapped shared secret.
|
||||
///
|
||||
/// # Returns
|
||||
/// The length of the unwrapped shared secret on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn decapsulate(
|
||||
&mut self,
|
||||
wrapped: &[u8],
|
||||
unwrapped: Option<&mut [u8]>,
|
||||
) -> Result<usize, ErrorStack>;
|
||||
|
||||
/// Convenience method to decapsulate into a vector.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
|
||||
/// - `unwrapped`: Buffer to receive the unwrapped shared secret.
|
||||
///
|
||||
/// # Returns
|
||||
/// The length of the unwrapped shared secret on success.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the OpenSSL operation fails.
|
||||
fn decapsulate_to_vec(
|
||||
&mut self,
|
||||
wrapped: &[u8],
|
||||
unwrapped: &mut Vec<u8>,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
let unwrapped_base = unwrapped.len();
|
||||
|
||||
// Query the required output buffer size.
|
||||
let unwrapped_len = self.decapsulate(wrapped, None)?;
|
||||
|
||||
unwrapped.resize(unwrapped_base + unwrapped_len, 0);
|
||||
|
||||
let unwrapped_len = self.decapsulate(wrapped, Some(&mut unwrapped[unwrapped_base..]))?;
|
||||
|
||||
unwrapped.truncate(unwrapped_base + unwrapped_len);
|
||||
|
||||
Ok(unwrapped_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PkeyDecapsulateContext for PkeyCtx<T> {
|
||||
#[inline]
|
||||
fn decapsulate_init(&mut self) -> Result<(), ErrorStack> {
|
||||
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
|
||||
// configuration is intentionally unsupported here, so a null params pointer is passed.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_decapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn decapsulate(
|
||||
&mut self,
|
||||
wrapped: &[u8],
|
||||
unwrapped: Option<&mut [u8]>,
|
||||
) -> Result<usize, ErrorStack> {
|
||||
let mut unwrapped_len = unwrapped.as_ref().map_or(0, |buf| buf.len());
|
||||
|
||||
let unwrapped_ptr = unwrapped
|
||||
.map(|buf| buf.as_mut_ptr())
|
||||
.unwrap_or(std::ptr::null_mut());
|
||||
|
||||
// SAFETY: All pointers are either valid mutable buffers or null, and wrapped is a valid
|
||||
// slice.
|
||||
let ret = unsafe {
|
||||
ffi::EVP_PKEY_decapsulate(
|
||||
self.as_ptr() as *mut ffi::EvpPkeyCtx,
|
||||
unwrapped_ptr,
|
||||
&mut unwrapped_len,
|
||||
wrapped.as_ptr(),
|
||||
wrapped.len(),
|
||||
)
|
||||
};
|
||||
|
||||
if ret == 1 {
|
||||
Ok(unwrapped_len)
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ML-KEM key generation context.
|
||||
///
|
||||
/// The inner pointer is guaranteed to be non-null for the lifetime of the context.
|
||||
pub struct MlKemKeyCtx {
|
||||
ctx: NonNull<ffi::EvpPkeyCtx>,
|
||||
}
|
||||
|
||||
impl MlKemKeyCtx {
|
||||
/// Create a new ML-KEM key generation context.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
|
||||
/// `KeyType::ML_KEM_1024`).
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the context cannot be created or the key type is unsupported.
|
||||
pub fn new(key_type: KeyType) -> Result<Self, ErrorStack> {
|
||||
let name = ml_kem_name(key_type)?;
|
||||
|
||||
// SAFETY: name is a valid null-terminated C string.
|
||||
let ctx = unsafe {
|
||||
ffi::EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
|
||||
};
|
||||
let Some(ctx) = NonNull::new(ctx) else {
|
||||
return Err(ErrorStack::get());
|
||||
};
|
||||
|
||||
// SAFETY: ctx is a valid non-null pointer.
|
||||
let ret = unsafe { ffi::EVP_PKEY_keygen_init(ctx.as_ptr()) };
|
||||
if ret != 1 {
|
||||
// SAFETY: ctx is a valid non-null pointer.
|
||||
unsafe { ffi::EVP_PKEY_CTX_free(ctx.as_ptr()) };
|
||||
return Err(ErrorStack::get());
|
||||
}
|
||||
|
||||
Ok(Self { ctx })
|
||||
}
|
||||
|
||||
/// Generate an ML-KEM keypair.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `PKey` containing both the private and public key.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if key generation fails.
|
||||
pub fn generate(&mut self) -> Result<PKey<Private>, ErrorStack> {
|
||||
let mut pkey: *mut std::os::raw::c_void = std::ptr::null_mut();
|
||||
|
||||
// SAFETY: self.ctx is valid, and pkey is a valid mutable pointer.
|
||||
let ret = unsafe { ffi::EVP_PKEY_generate(self.ctx.as_ptr(), &mut pkey) };
|
||||
|
||||
if ret != 1 || pkey.is_null() {
|
||||
return Err(ErrorStack::get());
|
||||
}
|
||||
|
||||
// SAFETY: pkey is a valid EVP_PKEY pointer created by OpenSSL.
|
||||
unsafe { Ok(PKey::from_ptr(pkey as *mut openssl_sys::EVP_PKEY)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MlKemKeyCtx {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: self.ctx is guaranteed to be non-null and owned by this context.
|
||||
unsafe {
|
||||
ffi::EVP_PKEY_CTX_free(self.ctx.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate an ML-KEM keypair.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
|
||||
/// `KeyType::ML_KEM_1024`).
|
||||
///
|
||||
/// # Returns
|
||||
/// A `PKey` containing both the private and public key.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if key generation fails.
|
||||
pub fn generate_ml_kem(key_type: KeyType) -> Result<PKey<Private>, ErrorStack> {
|
||||
let mut ctx = MlKemKeyCtx::new(key_type)?;
|
||||
ctx.generate()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn roundtrip_ml_kem(key_type: KeyType, expected_ciphertext_len: usize) {
|
||||
let keypair = generate_ml_kem(key_type).expect("Failed to generate ML-KEM keypair");
|
||||
|
||||
// Extract public key for encapsulation
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Failed to export public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
||||
|
||||
// Encapsulate: generate ciphertext and shared secret using public key
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
ctx_enc
|
||||
.encapsulate_init()
|
||||
.expect("Failed to initialize encapsulation");
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
let mut shared_secret_enc = Vec::new();
|
||||
ctx_enc
|
||||
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret_enc)
|
||||
.expect("Failed to encapsulate");
|
||||
|
||||
assert!(!ciphertext.is_empty(), "Ciphertext should not be empty");
|
||||
assert!(
|
||||
!shared_secret_enc.is_empty(),
|
||||
"Shared secret should not be empty"
|
||||
);
|
||||
|
||||
// Decapsulate: recover shared secret from ciphertext using private key
|
||||
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
||||
ctx_dec
|
||||
.decapsulate_init()
|
||||
.expect("Failed to initialize decapsulation");
|
||||
|
||||
let mut shared_secret_dec = Vec::new();
|
||||
ctx_dec
|
||||
.decapsulate_to_vec(&ciphertext, &mut shared_secret_dec)
|
||||
.expect("Failed to decapsulate");
|
||||
|
||||
// Verify that the shared secrets match
|
||||
assert_eq!(
|
||||
shared_secret_enc, shared_secret_dec,
|
||||
"Shared secrets from encapsulation and decapsulation should match"
|
||||
);
|
||||
assert_eq!(ciphertext.len(), expected_ciphertext_len);
|
||||
assert_eq!(shared_secret_enc.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_decapsulate_roundtrip_ml_kem_512() {
|
||||
roundtrip_ml_kem(KeyType::ML_KEM_512, 768);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_decapsulate_roundtrip_ml_kem_768() {
|
||||
roundtrip_ml_kem(KeyType::ML_KEM_768, 1088);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_decapsulate_roundtrip_ml_kem_1024() {
|
||||
roundtrip_ml_kem(KeyType::ML_KEM_1024, 1568);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ml_kem_key_ctx_new_accepts_supported_key_types() {
|
||||
MlKemKeyCtx::new(KeyType::ML_KEM_512).expect("ML-KEM-512 context creation must succeed");
|
||||
MlKemKeyCtx::new(KeyType::ML_KEM_768).expect("ML-KEM-768 context creation must succeed");
|
||||
MlKemKeyCtx::new(KeyType::ML_KEM_1024).expect("ML-KEM-1024 context creation must succeed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ml_kem_key_ctx_new_rejects_unsupported_key_type() {
|
||||
assert!(
|
||||
MlKemKeyCtx::new(KeyType::RSA).is_err(),
|
||||
"Unsupported key type must fail"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ml_kem_key_ctx_generate_produces_ml_kem_keypair() {
|
||||
let mut ctx = MlKemKeyCtx::new(KeyType::ML_KEM_768)
|
||||
.expect("ML-KEM-768 context creation must succeed");
|
||||
let keypair = ctx
|
||||
.generate()
|
||||
.expect("ML-KEM-768 key generation must succeed");
|
||||
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Generated keypair must export a public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Generated public key must reimport");
|
||||
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
ctx_enc
|
||||
.encapsulate_init()
|
||||
.expect("Failed to initialize encapsulation");
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
let mut shared_secret = Vec::new();
|
||||
ctx_enc
|
||||
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
|
||||
.expect("Generated keypair must support encapsulation");
|
||||
|
||||
assert_eq!(ciphertext.len(), 1088);
|
||||
assert_eq!(shared_secret.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_ml_kem_rejects_unsupported_key_type() {
|
||||
generate_ml_kem(KeyType::RSA).expect_err("Unsupported key type must fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encapsulate_requires_init() {
|
||||
let keypair =
|
||||
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Failed to export public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
||||
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
let err = ctx_enc
|
||||
.encapsulate_to_vec(&mut Vec::new(), &mut Vec::new())
|
||||
.expect_err("Encapsulation without init must fail");
|
||||
|
||||
assert!(
|
||||
!err.errors().is_empty(),
|
||||
"OpenSSL should report an error when encapsulate_init was not called"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decapsulate_requires_init() {
|
||||
let keypair =
|
||||
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
|
||||
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
||||
let err = ctx_dec
|
||||
.decapsulate_to_vec(&[0_u8; 768], &mut Vec::new())
|
||||
.expect_err("Decapsulation without init must fail");
|
||||
|
||||
assert!(
|
||||
!err.errors().is_empty(),
|
||||
"OpenSSL should report an error when decapsulate_init was not called"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decapsulate_rejects_undersized_output_buffer() {
|
||||
let keypair =
|
||||
generate_ml_kem(KeyType::ML_KEM_768).expect("Failed to generate ML-KEM-768 keypair");
|
||||
|
||||
let public_key_der = keypair
|
||||
.public_key_to_der()
|
||||
.expect("Failed to export public key");
|
||||
let public_key =
|
||||
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
||||
|
||||
let mut ctx_enc =
|
||||
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
||||
ctx_enc
|
||||
.encapsulate_init()
|
||||
.expect("Failed to initialize encapsulation");
|
||||
|
||||
let mut ciphertext = Vec::new();
|
||||
let mut shared_secret = Vec::new();
|
||||
ctx_enc
|
||||
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
|
||||
.expect("Failed to encapsulate");
|
||||
|
||||
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
||||
ctx_dec
|
||||
.decapsulate_init()
|
||||
.expect("Failed to initialize decapsulation");
|
||||
|
||||
let mut undersized = [0_u8; 31];
|
||||
let err = ctx_dec
|
||||
.decapsulate(&ciphertext, Some(&mut undersized))
|
||||
.expect_err("Undersized output buffer must fail");
|
||||
|
||||
assert!(
|
||||
!err.errors().is_empty(),
|
||||
"OpenSSL should report an error for an undersized output buffer"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@
|
||||
mod akid;
|
||||
mod bio;
|
||||
mod crl;
|
||||
mod ml_kem;
|
||||
mod stackable_crl;
|
||||
|
||||
pub use akid::*;
|
||||
pub use bio::*;
|
||||
pub use crl::*;
|
||||
pub use ml_kem::*;
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::openssl_extensions::bio::BioMemSlice;
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
stack::Stackable,
|
||||
x509::{X509Crl, X509CrlRef},
|
||||
};
|
||||
use std::ptr;
|
||||
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::stack::Stackable;
|
||||
use openssl::x509::{X509Crl, X509CrlRef};
|
||||
|
||||
use crate::openssl_extensions::bio::BioMemSlice;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
|
||||
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::Result;
|
||||
use crate::{openssl_extensions::BioMem, Error};
|
||||
use std::ffi::{c_char, CString};
|
||||
use std::fmt::Display;
|
||||
|
||||
use openssl::error::ErrorStack;
|
||||
use pv_core::request::Confidential;
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
use crate::openssl_extensions::BioMem;
|
||||
use crate::{Error, Result};
|
||||
|
||||
mod ffi {
|
||||
use openssl_sys::BIO;
|
||||
use std::ffi::{c_char, c_int, c_long, c_uchar};
|
||||
|
||||
use openssl_sys::BIO;
|
||||
extern "C" {
|
||||
pub fn PEM_write_bio(
|
||||
bio: *mut BIO,
|
||||
@@ -141,8 +142,8 @@ impl Pem {
|
||||
|
||||
let inner_pem = InnerPem::new(name, header, data.as_ref())?;
|
||||
|
||||
// Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or OpenSSL Errors
|
||||
// Both error should be very unlikely
|
||||
// Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or
|
||||
// OpenSSL Errors Both error should be very unlikely
|
||||
// OpenSSL should be able to create PEM if there is enough memory and produce a non-null
|
||||
// terminated ASCII-string
|
||||
// Unwrap succeeds it's all ASCII
|
||||
|
||||
@@ -1,692 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
use std::mem::size_of;
|
||||
|
||||
use openssl::{
|
||||
bn::{BigNum, BigNumContext},
|
||||
ec::{EcGroup, EcGroupRef, EcKey, EcPointRef},
|
||||
error::ErrorStack,
|
||||
hash::{DigestBytes, MessageDigest},
|
||||
nid::Nid,
|
||||
pkey::{PKey, PKeyRef, Private, Public},
|
||||
};
|
||||
use pv_core::request::{RequestMagic, RequestVersion};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
crypto::{
|
||||
decrypt_aead, derive_aes256_gcm_key, encrypt_aead, gen_ec_key, hash, random_array,
|
||||
AeadEncryptionResult, SymKey, SymKeyType,
|
||||
},
|
||||
misc::to_u32,
|
||||
request::Confidential,
|
||||
Error, Result,
|
||||
};
|
||||
/// Encrypt a _secret_ using self and a given private key.
|
||||
pub trait Encrypt {
|
||||
/// 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.
|
||||
fn encrypt(&self, secret: &[u8], priv_key: &PKeyRef<Private>) -> Result<Vec<u8>> {
|
||||
let mut res = Vec::with_capacity(80);
|
||||
self.encrypt_to(secret, priv_key, &mut res)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Encrypts `secret` using `self` and `priv_key` the encryption.
|
||||
/// Appends the encrypted data to `to`
|
||||
///
|
||||
/// # Returns
|
||||
/// The encrypted data.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not encrypt the secret.
|
||||
fn encrypt_to(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
priv_key: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Types of Authenticated Data
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub enum Aad<'a> {
|
||||
/// Authenticated Keyslot
|
||||
Ks(&'a Keyslot),
|
||||
/// Unchanged authenticated data
|
||||
Plain(&'a [u8]),
|
||||
/// Authenticated data that has to be encrypted in beforehand
|
||||
Encr(&'a dyn Encrypt),
|
||||
}
|
||||
|
||||
/// IBM Z Host key-slot
|
||||
///
|
||||
/// Layout in binary format:
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | Public Host Key Hash (32) |
|
||||
/// | Wrapped(=Encrypted) Request Protection Key(32) |
|
||||
/// | Key Slot Tag (16) |
|
||||
/// |_____________________________________________________________|
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Keyslot(PKey<Public>);
|
||||
|
||||
impl Keyslot {
|
||||
/// Size of a host-key hash
|
||||
pub const PHKH_SIZE: u32 = 0x20;
|
||||
|
||||
/// Creates a new Keyslot from the provided public key
|
||||
pub fn new(hostkey: PKey<Public>) -> Self {
|
||||
Self(hostkey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypt for Keyslot {
|
||||
/// 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 Keyslot.
|
||||
///
|
||||
/// # 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 = derive_aes256_gcm_key(priv_key, &self.0)?;
|
||||
let mut wrpk_and_kst =
|
||||
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
|
||||
let phk: EcPubKeyCoord = self.0.as_ref().try_into()?;
|
||||
|
||||
to.reserve(80);
|
||||
to.extend_from_slice(&hash(MessageDigest::sha256(), phk.as_ref())?);
|
||||
to.append(&mut wrpk_and_kst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Context used to manage the encryption of requests.
|
||||
/// Intended to be used by [`Request`] implementations
|
||||
#[derive(Debug)]
|
||||
pub struct ReqEncrCtx {
|
||||
iv: [u8; 12],
|
||||
priv_key: PKey<Private>,
|
||||
prot_key: SymKey,
|
||||
}
|
||||
impl ReqEncrCtx {
|
||||
/// Create a new encryption context that uses AES256.
|
||||
///
|
||||
/// * `iv` - Initialization vector for the request encryption
|
||||
/// * `priv_key` - Private key to wrap [`Keyslot`]
|
||||
/// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`]
|
||||
///
|
||||
/// If an argument is set to `None` a ranom is generated
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not generate a random value.
|
||||
pub fn new_aes_256<I, P, S>(iv: I, priv_key: P, prot_key: S) -> Result<Self>
|
||||
where
|
||||
I: Into<Option<[u8; 12]>>,
|
||||
P: Into<Option<PKey<Private>>>,
|
||||
S: Into<Option<SymKey>>,
|
||||
{
|
||||
let iv = iv.into().unwrap_or(random_array()?);
|
||||
let priv_key = priv_key.into().unwrap_or(gen_ec_key(Nid::SECP521R1)?);
|
||||
let prot_key = prot_key
|
||||
.into()
|
||||
.unwrap_or(SymKey::random(SymKeyType::Aes256Gcm)?);
|
||||
Ok(Self {
|
||||
iv,
|
||||
priv_key,
|
||||
prot_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new encryption context with random input values.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not generate a random value.
|
||||
pub fn random(ket_tp: SymKeyType) -> Result<Self> {
|
||||
match ket_tp {
|
||||
SymKeyType::Aes256Gcm => Self::new_aes_256(None, None, None),
|
||||
SymKeyType::Aes256Xts => Err(Error::NoAeadKey),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the authenticated data for a request.
|
||||
/// # Returns
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | MAGIC (8) Version Number (4) Size (4)|
|
||||
/// | IV (12) Reserved (4)|
|
||||
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
|
||||
/// | --------------------------------------------------- |
|
||||
/// | Request type dependent AAD data |
|
||||
/// |-------------------------------------------------------------|
|
||||
/// ```
|
||||
pub fn build_aad<O>(
|
||||
&self,
|
||||
version: RequestVersion,
|
||||
aad: &Vec<Aad>,
|
||||
encr_size: usize,
|
||||
magic: O,
|
||||
) -> Result<Vec<u8>>
|
||||
where
|
||||
O: Into<Option<RequestMagic>>,
|
||||
{
|
||||
self.build_aad_impl(version, aad, encr_size, magic.into())
|
||||
}
|
||||
|
||||
/// Concrete implementation for [`ReqEncrCtx::build_aad`].
|
||||
fn build_aad_impl(
|
||||
&self,
|
||||
version: RequestVersion,
|
||||
aad: &Vec<Aad>,
|
||||
encr_size: usize,
|
||||
magic: Option<RequestMagic>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let nks = aad.iter().filter(|a| matches!(a, Aad::Ks(_))).count();
|
||||
let nks: u8 = match nks {
|
||||
0 => Err(Error::NoHostkey),
|
||||
n if n > u8::MAX as usize => Err(Error::ManyHostkeys),
|
||||
n => Ok(n as u8),
|
||||
}?;
|
||||
let mut auth_data: Vec<u8> = Vec::with_capacity(2048);
|
||||
|
||||
// reserve space for the request header
|
||||
auth_data.resize(size_of::<RequestHdr>(), 0);
|
||||
|
||||
for a in aad {
|
||||
match a {
|
||||
Aad::Plain(p) => auth_data.extend_from_slice(p),
|
||||
Aad::Ks(ks) => {
|
||||
ks.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
|
||||
}
|
||||
Aad::Encr(e) => {
|
||||
e.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| {
|
||||
pv_core::Error::Specification("Configured request size to large".to_string())
|
||||
})?;
|
||||
let sea = to_u32(encr_size)
|
||||
.ok_or_else(|| pv_core::Error::Specification("Encrypted size to large".to_string()))?;
|
||||
|
||||
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
|
||||
// copy request header to the start of the request
|
||||
auth_data[..size_of::<RequestHdr>()].copy_from_slice(req_hdr.as_bytes());
|
||||
Ok(auth_data)
|
||||
}
|
||||
|
||||
/// Get the public coordinates from the private key (Customer private key)
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the public key could not be extracted by OpenSSL.
|
||||
/// Very unlikely.
|
||||
pub fn key_coords(&self) -> Result<EcPubKeyCoord> {
|
||||
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
|
||||
}
|
||||
|
||||
/// Encrypt confidential Data with this encryption context and provide a GCM tag.
|
||||
///
|
||||
/// * `aad` - additional authentic data
|
||||
/// * `conf` - data to be encrypted
|
||||
///
|
||||
/// # Returns
|
||||
/// [`Vec<u8>`] with the following content:
|
||||
/// 1. `aad`
|
||||
/// 2. `encr(conf)`
|
||||
/// 3. `aes gcm tag`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
||||
pub(crate) fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AeadEncryptionResult> {
|
||||
encrypt_aead(&self.prot_key, &self.iv, aad, conf)
|
||||
}
|
||||
|
||||
/// Returns a reference to the request protection key of this [`ReqEncrCtx`].
|
||||
pub fn prot_key(&self) -> &SymKey {
|
||||
&self.prot_key
|
||||
}
|
||||
}
|
||||
|
||||
/// Public key components of an [`openssl::ec::EcKey`] key.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EcPubKeyCoord([u8; 160]);
|
||||
impl AsRef<[u8]> for EcPubKeyCoord {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
const ECDH_PUB_KEY_COORD_POINT_SIZE: usize = 0x50;
|
||||
|
||||
impl EcPubKeyCoord {
|
||||
/// Returns the SHA256 hash of the [`EcPubKeyCoord`].
|
||||
///
|
||||
/// If [`EcPubKeyCoord`] was built from a host-key, this value is the public host-key hash.
|
||||
pub fn sha256(&self) -> Result<DigestBytes> {
|
||||
hash(MessageDigest::sha256(), self.as_ref())
|
||||
}
|
||||
|
||||
/// Construct a [`EcPubKeyCoord`]
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is marked unsafe, because data not representing two EC points violates the
|
||||
/// invariant of this struct.
|
||||
pub unsafe fn from_data(data: [u8; 160]) -> Self {
|
||||
EcPubKeyCoord(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let mut x = BigNum::new()?;
|
||||
let mut y = BigNum::new()?;
|
||||
let mut bn_ctx = BigNumContext::new()?;
|
||||
pkey.affine_coordinates(grp, &mut x, &mut y, &mut bn_ctx)?;
|
||||
let mut coord: Vec<u8> = x.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?;
|
||||
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?);
|
||||
Ok(coord.try_into().unwrap())
|
||||
}
|
||||
|
||||
impl TryFrom<EcPubKeyCoord> for PKey<Public> {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(value: EcPubKeyCoord) -> Result<Self, Self::Error> {
|
||||
let ecdh = value.as_ref();
|
||||
let grp = EcGroup::from_curve_name(Nid::SECP521R1)?;
|
||||
let x = BigNum::from_slice(&ecdh[..ECDH_PUB_KEY_COORD_POINT_SIZE])?;
|
||||
let y = BigNum::from_slice(&ecdh[ECDH_PUB_KEY_COORD_POINT_SIZE..])?;
|
||||
let ec_key = EcKey::from_public_key_affine_coordinates(&grp, &x, &y)?;
|
||||
Self::from_ec_key(ec_key)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ecdh_from {
|
||||
($type: ty) => {
|
||||
impl TryFrom<&PKeyRef<$type>> for EcPubKeyCoord {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(key: &PKeyRef<$type>) -> Result<Self, Self::Error> {
|
||||
let k = key.ec_key()?;
|
||||
k.check_key()?;
|
||||
let grp = k.group();
|
||||
let pub_key = k.public_key();
|
||||
let coord = get_pub_ecdh_points(pub_key, grp)?;
|
||||
Ok(Self(coord))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PKey<$type>> for EcPubKeyCoord {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(key: PKey<$type>) -> Result<Self, Self::Error> {
|
||||
let key_ref = key.as_ref();
|
||||
key_ref.try_into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ecdh_from!(Private);
|
||||
ecdh_from!(Public);
|
||||
|
||||
/// Representation of the shared parts of the request header.
|
||||
/// Used by [`ReqEncrCtx`]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
|
||||
struct RequestHdr {
|
||||
magic: [u8; 8],
|
||||
rqvn: U32<BigEndian>,
|
||||
rql: U32<BigEndian>,
|
||||
iv: [u8; 12],
|
||||
reserved1c: [u8; 4],
|
||||
reserved20: [u8; 7],
|
||||
nks: u8,
|
||||
reserved28: u32,
|
||||
sea: U32<BigEndian>,
|
||||
}
|
||||
assert_size!(RequestHdr, 48);
|
||||
|
||||
impl RequestHdr {
|
||||
fn new(rqvn: u32, rql: u32, iv: [u8; 12], nks: u8, sea: u32, magic: Option<[u8; 8]>) -> Self {
|
||||
Self {
|
||||
magic: magic.unwrap_or_default(),
|
||||
rqvn: rqvn.into(),
|
||||
rql: rql.into(),
|
||||
iv,
|
||||
reserved1c: [0; 4],
|
||||
reserved20: [0; 7],
|
||||
nks,
|
||||
reserved28: 0,
|
||||
sea: sea.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait representing a request for the Ultravisor.
|
||||
///
|
||||
/// All requests share a few things:
|
||||
/// * All requests need to be encrypted on a trusted machine
|
||||
/// * All requests have at least one Hostkeyslot
|
||||
///
|
||||
/// The encryption setup is handled by [`ReqEncrCtx`]. Implementers need to pass the data to the
|
||||
/// `ReqEncrCtx` when implementing `encrypt`. A hostkey should be represented by [`Keyslot`] during
|
||||
/// encryption.
|
||||
///
|
||||
/// An UV request consists of an authenticated area (AAD), an encrypted area (Encr) and a 16 byte
|
||||
/// tag. The AAD contains a general header and Request type defined data (including Keyslots). It
|
||||
/// is encrypted with an Request protection key (symmetric). This key is encrypted with a
|
||||
/// (generated) private key and the public key of the host system (Host key)
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | MAGIC (8) Version Number (4) Size (4)|
|
||||
/// | IV (12) Reserved (4)|
|
||||
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
|
||||
/// | --------------------------------------------------- |
|
||||
/// | Request type dependent AAD data |
|
||||
/// | ---------------------------------------------------- |
|
||||
/// | Encrypted (request type dependent) data |
|
||||
/// | ---------------------------------------------------- |
|
||||
/// | AES GCM Tag (16) |
|
||||
/// |_____________________________________________________________|
|
||||
/// ```
|
||||
pub trait Request {
|
||||
/// Encrypt the request into its binary format
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the encryption fails, the request does not have at
|
||||
/// least a hostkey, or other implementation dependent contracts are not met.
|
||||
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>>;
|
||||
/// Add a host-key to this request
|
||||
///
|
||||
/// Must be called at least once, otherwise {`Request::encrypt`} will fail
|
||||
fn add_hostkey(&mut self, hostkey: PKey<Public>);
|
||||
}
|
||||
|
||||
/// A struct to represent some parts of a binary/encrypted request.
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub(crate) struct BinReqValues<'a> {
|
||||
iv: &'a [u8],
|
||||
aad: &'a [u8],
|
||||
req_dep_aad: &'a [u8],
|
||||
encr: &'a [u8],
|
||||
tag: &'a [u8],
|
||||
version: u32,
|
||||
len: usize,
|
||||
}
|
||||
impl<'a> BinReqValues<'a> {
|
||||
pub(crate) const TAG_LEN: usize = SymKeyType::AES_256_GCM_TAG_LEN;
|
||||
|
||||
/// Get the locations from this request.
|
||||
///
|
||||
/// Does minimal sanity test, just tests to prevent panics.
|
||||
/// `req` may be larger than the actual request.
|
||||
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
|
||||
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
|
||||
let rql = hdr.rql.get() as usize;
|
||||
let sea = hdr.sea.get() as usize;
|
||||
|
||||
if rql < req.len() || sea + Self::TAG_LEN > rql {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
let aad_size = rql - sea - Self::TAG_LEN;
|
||||
if aad_size < size_of::<RequestHdr>() {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
|
||||
let iv = &req[0x10..0x1c];
|
||||
let aad = &req[..aad_size];
|
||||
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
|
||||
let encr = &req[aad_size..(aad_size + sea)];
|
||||
let tag = &req[rql - Self::TAG_LEN..];
|
||||
|
||||
Ok(Self {
|
||||
iv,
|
||||
aad,
|
||||
req_dep_aad,
|
||||
encr,
|
||||
tag,
|
||||
version: hdr.rqvn.get(),
|
||||
len: rql,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the version of this [`BinReqValues`].
|
||||
pub(crate) fn version(&self) -> u32 {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Returns the length of this [`BinReqValues`].
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns the size of the encrypted area
|
||||
pub(crate) fn sea(&self) -> u32 {
|
||||
self.encr.len() as u32
|
||||
}
|
||||
|
||||
/// Decrypts the encrypted area with the provided key
|
||||
pub(crate) fn decrypt(&self, key: &SymKey) -> Result<Confidential<Vec<u8>>> {
|
||||
let result = decrypt_aead(key, self.iv, self.aad, self.encr, self.tag)?;
|
||||
Ok(result.into_plain())
|
||||
}
|
||||
|
||||
/// Returns a reference to the request dependent authenticated area of this [`BinReqValues`]
|
||||
/// already interpreted.
|
||||
///
|
||||
/// If target struct is larger than the request depended-AAD None is returned. See
|
||||
/// [`FromBytes::ref_from_prefix`]
|
||||
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: FromBytes + Sized + Immutable + KnownLayout,
|
||||
{
|
||||
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
|
||||
}
|
||||
|
||||
/// Returns a reference to the tag of this [`BinReqValues`].
|
||||
pub(crate) fn tag(&self) -> &[u8] {
|
||||
self.tag
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{get_test_asset, request::SymKey, test_utils::*};
|
||||
|
||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let ks = Keyslot::new(host_key);
|
||||
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 mut aad_exp = vec![
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 168, // 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
|
||||
];
|
||||
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
|
||||
assert_eq!(&aad, &aad_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad_nks_no() {
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let aad = Vec::<Aad>::new();
|
||||
|
||||
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
||||
assert!(matches!(aad, Err(Error::NoHostkey)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad_nks_many() {
|
||||
let (_, host_key) = get_test_keys();
|
||||
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();
|
||||
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);
|
||||
let hdr_bin = hdr.as_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0u8, 0, 0, 0, 0, 0, 0, 0, // magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 22, // 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
|
||||
15, // nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn req_hdr2() {
|
||||
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
|
||||
let hdr_bin = hdr.as_mut_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0x12, 0x34, // 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
|
||||
15, // nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyslot() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec();
|
||||
|
||||
let keyslot = Keyslot::new(host_key);
|
||||
let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
|
||||
|
||||
assert_eq!(exp_keyslot, encr_ks);
|
||||
|
||||
let encr_ks = keyslot.encrypt(&[0x16u8; 32], &cust_key).unwrap();
|
||||
assert_ne!(exp_keyslot, encr_ks);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ec_pub_ec_coord_from() {
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let pub_key = get_test_asset!("keys/public_cust.bin");
|
||||
assert_eq!(pub_key.len(), 160);
|
||||
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
assert_eq!(ec_coord.as_ref(), pub_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ec_pub_ec_coord_hash() {
|
||||
let exp = [
|
||||
0x5e, 0xe9, 0x05, 0xa9, 0xbe, 0x70, 0x36, 0x68, 0x15, 0xa4, 0x56, 0x41, 0xaf, 0xae,
|
||||
0x00, 0x97, 0x3b, 0x1f, 0x45, 0x29, 0x2f, 0x43, 0xbc, 0xd7, 0x63, 0x8e, 0xe2, 0xa7,
|
||||
0x3f, 0xd7, 0xc4, 0x5e,
|
||||
];
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
let hash = ec_coord.sha256().unwrap();
|
||||
|
||||
assert_eq!(hash.as_ref(), &exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_ecdh_and_vice_versa() {
|
||||
let (_, cust_pub) = get_test_keys();
|
||||
let phk: EcPubKeyCoord = cust_pub.clone().try_into().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
phk.as_ref(),
|
||||
&[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 118, 136, 28, 216, 75, 139, 109, 231,
|
||||
18, 60, 126, 144, 14, 223, 120, 231, 247, 182, 132, 153, 145, 70, 177, 38, 59, 168,
|
||||
184, 108, 132, 71, 240, 138, 182, 212, 105, 194, 177, 40, 237, 158, 28, 53, 1, 88,
|
||||
5, 172, 211, 211, 2, 51, 211, 145, 34, 247, 226, 248, 170, 28, 43, 20, 123, 120,
|
||||
131, 180, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 69, 86, 194, 92,
|
||||
249, 47, 41, 206, 102, 189, 68, 17, 77, 107, 123, 60, 120, 225, 58, 63, 144, 189,
|
||||
185, 0, 64, 246, 135, 110, 82, 98, 247, 120, 166, 26, 147, 125, 27, 52, 128, 46,
|
||||
178, 87, 227, 78, 6, 114, 221, 95, 42, 52, 122, 221, 170, 40, 32, 53, 9, 42, 112,
|
||||
195, 92, 46, 121, 115
|
||||
]
|
||||
);
|
||||
let cust_pub_back: PKey<Public> = phk.try_into().unwrap();
|
||||
assert!(cust_pub.public_eq(&cust_pub_back));
|
||||
}
|
||||
}
|
||||
257
rust/pv/src/req/context.rs
Normal file
257
rust/pv/src/req/context.rs
Normal file
@@ -0,0 +1,257 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Request encryption context for managing UV request encryption.
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use pv_core::request::{RequestMagic, RequestVersion};
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::{Aad, EcPubKeyCoord, Encrypt, RequestHdr};
|
||||
use crate::crypto::{
|
||||
encrypt_aead, gen_ec_key, random_array, AeadEncryptionResult, SymKey, SymKeyType,
|
||||
};
|
||||
use crate::misc::to_u32;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Context used to manage the encryption of requests.
|
||||
/// Intended to be used by [`Request`](super::Request) implementations
|
||||
#[derive(Debug)]
|
||||
pub struct ReqEncrCtx {
|
||||
iv: [u8; 12],
|
||||
priv_key: PKey<Private>,
|
||||
prot_key: SymKey,
|
||||
}
|
||||
|
||||
impl ReqEncrCtx {
|
||||
/// Create a new encryption context that uses AES256.
|
||||
///
|
||||
/// * `iv` - Initialization vector for the request encryption
|
||||
/// * `priv_key` - Private key to wrap [`Keyslot`](super::Keyslot)
|
||||
/// * `prot_key` - Symmetric key for request encryption. Part of [`Keyslot`](super::Keyslot)
|
||||
///
|
||||
/// If an argument is set to `None` a random is generated
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not generate a random value.
|
||||
pub fn new_aes_256<I, P, S>(iv: I, priv_key: P, prot_key: S) -> Result<Self>
|
||||
where
|
||||
I: Into<Option<[u8; 12]>>,
|
||||
P: Into<Option<PKey<Private>>>,
|
||||
S: Into<Option<SymKey>>,
|
||||
{
|
||||
let iv = iv.into().unwrap_or(random_array()?);
|
||||
let priv_key = priv_key.into().unwrap_or(gen_ec_key(Nid::SECP521R1)?);
|
||||
let prot_key = prot_key
|
||||
.into()
|
||||
.unwrap_or(SymKey::random(SymKeyType::Aes256Gcm)?);
|
||||
Ok(Self {
|
||||
iv,
|
||||
priv_key,
|
||||
prot_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new encryption context with random input values.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not generate a random value.
|
||||
pub fn random(key_tp: SymKeyType) -> Result<Self> {
|
||||
match key_tp {
|
||||
SymKeyType::Aes256Gcm => Self::new_aes_256(None, None, None),
|
||||
SymKeyType::Aes256Xts => Err(Error::NoAeadKey),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the authenticated data for a request.
|
||||
/// # Returns
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | MAGIC (8) Version Number (4) Size (4)|
|
||||
/// | IV (12) Reserved (4)|
|
||||
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
|
||||
/// | --------------------------------------------------- |
|
||||
/// | Request type dependent AAD data |
|
||||
/// |-------------------------------------------------------------|
|
||||
/// ```
|
||||
pub fn build_aad<O>(
|
||||
&self,
|
||||
version: RequestVersion,
|
||||
aad: &Vec<Aad>,
|
||||
encr_size: usize,
|
||||
magic: O,
|
||||
) -> Result<Vec<u8>>
|
||||
where
|
||||
O: Into<Option<RequestMagic>>,
|
||||
{
|
||||
self.build_aad_impl(version, aad, encr_size, magic.into())
|
||||
}
|
||||
|
||||
/// Concrete implementation for [`ReqEncrCtx::build_aad`].
|
||||
fn build_aad_impl(
|
||||
&self,
|
||||
version: RequestVersion,
|
||||
aad: &Vec<Aad>,
|
||||
encr_size: usize,
|
||||
magic: Option<RequestMagic>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let nks = aad.iter().filter(|a| matches!(a, Aad::Ks(_))).count();
|
||||
let nks: u8 = match nks {
|
||||
0 => Err(Error::NoHostkey),
|
||||
n if n > u8::MAX as usize => Err(Error::ManyHostkeys),
|
||||
n => Ok(n as u8),
|
||||
}?;
|
||||
let mut auth_data: Vec<u8> = Vec::with_capacity(2048);
|
||||
|
||||
// reserve space for the request header
|
||||
auth_data.resize(size_of::<RequestHdr>(), 0);
|
||||
|
||||
for a in aad {
|
||||
match a {
|
||||
Aad::Plain(p) => auth_data.extend_from_slice(p),
|
||||
Aad::Ks(ks) => {
|
||||
ks.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
|
||||
}
|
||||
Aad::Encr(e) => {
|
||||
e.encrypt_to(self.prot_key.value(), &self.priv_key, &mut auth_data)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rql = to_u32(auth_data.len() + encr_size + 16).ok_or_else(|| {
|
||||
pv_core::Error::Specification("Configured request size to large".to_string())
|
||||
})?;
|
||||
let sea = to_u32(encr_size)
|
||||
.ok_or_else(|| pv_core::Error::Specification("Encrypted size to large".to_string()))?;
|
||||
|
||||
let req_hdr = RequestHdr::new(version, rql, self.iv, nks, sea, magic);
|
||||
// copy request header to the start of the request
|
||||
auth_data[..size_of::<RequestHdr>()].copy_from_slice(req_hdr.as_bytes());
|
||||
Ok(auth_data)
|
||||
}
|
||||
|
||||
/// Get the public coordinates from the private key (Customer private key)
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the public key could not be extracted by OpenSSL.
|
||||
/// Very unlikely.
|
||||
pub fn key_coords(&self) -> Result<EcPubKeyCoord> {
|
||||
self.priv_key.as_ref().try_into().map_err(Error::Crypto)
|
||||
}
|
||||
|
||||
/// Encrypt confidential Data with this encryption context and provide a GCM tag.
|
||||
///
|
||||
/// * `aad` - additional authentic data
|
||||
/// * `conf` - data to be encrypted
|
||||
///
|
||||
/// # Returns
|
||||
/// [`Vec<u8>`] with the following content:
|
||||
/// 1. `aad`
|
||||
/// 2. `encr(conf)`
|
||||
/// 3. `aes gcm tag`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the data could not be encrypted by OpenSSL.
|
||||
pub(crate) fn encrypt_aead(&self, aad: &[u8], conf: &[u8]) -> Result<AeadEncryptionResult> {
|
||||
encrypt_aead(&self.prot_key, &self.iv, aad, conf)
|
||||
}
|
||||
|
||||
/// Returns a reference to the request protection key of this [`ReqEncrCtx`].
|
||||
pub fn prot_key(&self) -> &SymKey {
|
||||
&self.prot_key
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::req::hostkey::HostKey;
|
||||
use crate::req::keyslot::Keyslot;
|
||||
use crate::request::SymKey;
|
||||
use crate::test_utils::*;
|
||||
|
||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let ks = Keyslot::new(HostKey::V1(host_key));
|
||||
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 mut aad_exp = vec![
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 168, // 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
|
||||
];
|
||||
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
|
||||
assert_eq!(&aad, &aad_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad_nks_no() {
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let aad = Vec::<Aad>::new();
|
||||
|
||||
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
||||
assert!(matches!(aad, Err(Error::NoHostkey)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad_nks_many() {
|
||||
let (_, host_key) = get_test_keys();
|
||||
let ctx = ReqEncrCtx::new_aes_256(Some([0x11; 12]), None, None).unwrap();
|
||||
|
||||
let ks: Vec<Keyslot> = (0..257)
|
||||
.map(|_| Keyslot::new(HostKey::V1(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();
|
||||
let host_key = HostKey::V1(host_key);
|
||||
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);
|
||||
}
|
||||
}
|
||||
157
rust/pv/src/req/ec_coord.rs
Normal file
157
rust/pv/src/req/ec_coord.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! EC public key coordinate utilities and conversions.
|
||||
|
||||
use openssl::bn::{BigNum, BigNumContext};
|
||||
use openssl::ec::{EcGroup, EcGroupRef, EcKey, EcPointRef};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::{DigestBytes, MessageDigest};
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
|
||||
use crate::crypto::hash;
|
||||
use crate::Result;
|
||||
|
||||
/// Public key components of an [`openssl::ec::EcKey`] key.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EcPubKeyCoord([u8; 160]);
|
||||
|
||||
impl AsRef<[u8]> for EcPubKeyCoord {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
const ECDH_PUB_KEY_COORD_POINT_SIZE: usize = 0x50;
|
||||
|
||||
impl EcPubKeyCoord {
|
||||
/// Returns the SHA256 hash of the [`EcPubKeyCoord`].
|
||||
///
|
||||
/// If [`EcPubKeyCoord`] was built from a host-key, this value is the public host-key hash.
|
||||
pub fn sha256(&self) -> Result<DigestBytes> {
|
||||
hash(MessageDigest::sha256(), self.as_ref())
|
||||
}
|
||||
|
||||
/// Construct a [`EcPubKeyCoord`]
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is marked unsafe, because data not representing two EC points violates the
|
||||
/// invariant of this struct.
|
||||
pub unsafe fn from_data(data: [u8; 160]) -> Self {
|
||||
EcPubKeyCoord(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
|
||||
/// The two coordinates are padded to 80 bytes each.
|
||||
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()?;
|
||||
pkey.affine_coordinates(grp, &mut x, &mut y, &mut bn_ctx)?;
|
||||
let mut coord: Vec<u8> = x.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?;
|
||||
coord.append(&mut y.to_vec_padded(ECDH_PUB_KEY_COORD_POINT_SIZE as i32)?);
|
||||
Ok(coord.try_into().unwrap())
|
||||
}
|
||||
|
||||
impl TryFrom<EcPubKeyCoord> for PKey<Public> {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(value: EcPubKeyCoord) -> Result<Self, Self::Error> {
|
||||
let ecdh = value.as_ref();
|
||||
let grp = EcGroup::from_curve_name(Nid::SECP521R1)?;
|
||||
let x = BigNum::from_slice(&ecdh[..ECDH_PUB_KEY_COORD_POINT_SIZE])?;
|
||||
let y = BigNum::from_slice(&ecdh[ECDH_PUB_KEY_COORD_POINT_SIZE..])?;
|
||||
let ec_key = EcKey::from_public_key_affine_coordinates(&grp, &x, &y)?;
|
||||
Self::from_ec_key(ec_key)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ecdh_from {
|
||||
($type: ty) => {
|
||||
impl TryFrom<&PKeyRef<$type>> for EcPubKeyCoord {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(key: &PKeyRef<$type>) -> Result<Self, Self::Error> {
|
||||
let k = key.ec_key()?;
|
||||
k.check_key()?;
|
||||
let grp = k.group();
|
||||
let pub_key = k.public_key();
|
||||
let coord = get_pub_ecdh_points(pub_key, grp)?;
|
||||
Ok(Self(coord))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PKey<$type>> for EcPubKeyCoord {
|
||||
type Error = ErrorStack;
|
||||
|
||||
fn try_from(key: PKey<$type>) -> Result<Self, Self::Error> {
|
||||
let key_ref = key.as_ref();
|
||||
key_ref.try_into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ecdh_from!(Private);
|
||||
ecdh_from!(Public);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::get_test_asset;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn ec_pub_ec_coord_from() {
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let pub_key = get_test_asset!("keys/public_cust.bin");
|
||||
assert_eq!(pub_key.len(), 160);
|
||||
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
assert_eq!(ec_coord.as_ref(), pub_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ec_pub_ec_coord_hash() {
|
||||
let exp = [
|
||||
0x5e, 0xe9, 0x05, 0xa9, 0xbe, 0x70, 0x36, 0x68, 0x15, 0xa4, 0x56, 0x41, 0xaf, 0xae,
|
||||
0x00, 0x97, 0x3b, 0x1f, 0x45, 0x29, 0x2f, 0x43, 0xbc, 0xd7, 0x63, 0x8e, 0xe2, 0xa7,
|
||||
0x3f, 0xd7, 0xc4, 0x5e,
|
||||
];
|
||||
let (cust_key, _) = get_test_keys();
|
||||
let ec_coord: EcPubKeyCoord = cust_key.as_ref().try_into().unwrap();
|
||||
let hash = ec_coord.sha256().unwrap();
|
||||
|
||||
assert_eq!(hash.as_ref(), &exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion_ecdh_and_vice_versa() {
|
||||
let (_, cust_pub) = get_test_keys();
|
||||
let phk: EcPubKeyCoord = cust_pub.clone().try_into().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
phk.as_ref(),
|
||||
&[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 118, 136, 28, 216, 75, 139, 109, 231,
|
||||
18, 60, 126, 144, 14, 223, 120, 231, 247, 182, 132, 153, 145, 70, 177, 38, 59, 168,
|
||||
184, 108, 132, 71, 240, 138, 182, 212, 105, 194, 177, 40, 237, 158, 28, 53, 1, 88,
|
||||
5, 172, 211, 211, 2, 51, 211, 145, 34, 247, 226, 248, 170, 28, 43, 20, 123, 120,
|
||||
131, 180, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 69, 86, 194, 92,
|
||||
249, 47, 41, 206, 102, 189, 68, 17, 77, 107, 123, 60, 120, 225, 58, 63, 144, 189,
|
||||
185, 0, 64, 246, 135, 110, 82, 98, 247, 120, 166, 26, 147, 125, 27, 52, 128, 46,
|
||||
178, 87, 227, 78, 6, 114, 221, 95, 42, 52, 122, 221, 170, 40, 32, 53, 9, 42, 112,
|
||||
195, 92, 46, 121, 115
|
||||
]
|
||||
);
|
||||
let cust_pub_back: PKey<Public> = phk.try_into().unwrap();
|
||||
assert!(cust_pub.public_eq(&cust_pub_back));
|
||||
}
|
||||
}
|
||||
54
rust/pv/src/req/encrypt.rs
Normal file
54
rust/pv/src/req/encrypt.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Encryption trait and authenticated data types for UV requests.
|
||||
|
||||
use openssl::pkey::{PKeyRef, Private};
|
||||
|
||||
use crate::req::Keyslot;
|
||||
use crate::Result;
|
||||
|
||||
/// Encrypt a _secret_ using self and a given private key.
|
||||
pub trait Encrypt {
|
||||
/// 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.
|
||||
fn encrypt(&self, secret: &[u8], priv_key: &PKeyRef<Private>) -> Result<Vec<u8>> {
|
||||
let mut res = Vec::with_capacity(80);
|
||||
self.encrypt_to(secret, priv_key, &mut res)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Encrypts `secret` using `self` and `priv_key` the encryption.
|
||||
/// Appends the encrypted data to `to`
|
||||
///
|
||||
/// # Returns
|
||||
/// The encrypted data.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if OpenSSL could not encrypt the secret.
|
||||
fn encrypt_to(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
priv_key: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Types of Authenticated Data
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub enum Aad<'a> {
|
||||
/// Authenticated Keyslot
|
||||
Ks(&'a Keyslot),
|
||||
/// Unchanged authenticated data
|
||||
Plain(&'a [u8]),
|
||||
/// Authenticated data that has to be encrypted in beforehand
|
||||
Encr(&'a dyn Encrypt),
|
||||
}
|
||||
93
rust/pv/src/req/header.rs
Normal file
93
rust/pv/src/req/header.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// 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;
|
||||
|
||||
/// Representation of the shared parts of the request header.
|
||||
/// Used by [`ReqEncrCtx`](super::ReqEncrCtx)
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, IntoBytes, FromBytes, Immutable)]
|
||||
pub struct RequestHdr {
|
||||
magic: [u8; 8],
|
||||
pub(crate) rqvn: U32<BigEndian>,
|
||||
pub(crate) rql: U32<BigEndian>,
|
||||
iv: [u8; 12],
|
||||
reserved1c: [u8; 4],
|
||||
reserved20: [u8; 7],
|
||||
nks: u8,
|
||||
reserved28: u32,
|
||||
pub(crate) sea: U32<BigEndian>,
|
||||
}
|
||||
assert_size!(RequestHdr, 48);
|
||||
|
||||
impl RequestHdr {
|
||||
pub(super) fn new(
|
||||
rqvn: RequestVersion,
|
||||
rql: u32,
|
||||
iv: [u8; 12],
|
||||
nks: u8,
|
||||
sea: u32,
|
||||
magic: Option<RequestMagic>,
|
||||
) -> Self {
|
||||
Self {
|
||||
magic: magic.unwrap_or_default(),
|
||||
rqvn: rqvn.into(),
|
||||
rql: rql.into(),
|
||||
iv,
|
||||
reserved1c: [0; 4],
|
||||
reserved20: [0; 7],
|
||||
nks,
|
||||
reserved28: 0,
|
||||
sea: sea.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::*;
|
||||
|
||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||
|
||||
#[test]
|
||||
fn req_hdr() {
|
||||
let hdr = RequestHdr::new(0x200, 22, [0x11; 12], 15, 44, None);
|
||||
let hdr_bin = hdr.as_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0u8, 0, 0, 0, 0, 0, 0, 0, // magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 22, // 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
|
||||
15, // nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn req_hdr2() {
|
||||
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
|
||||
let hdr_bin = hdr.as_mut_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0x12, 0x34, // 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
|
||||
15, // nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
}
|
||||
342
rust/pv/src/req/hostkey.rs
Normal file
342
rust/pv/src/req/hostkey.rs
Normal file
@@ -0,0 +1,342 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Host key types for UV requests
|
||||
|
||||
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]
|
||||
#[derive(Clone, Debug)]
|
||||
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) -> 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 {
|
||||
fn as_ref(&self) -> &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());
|
||||
}
|
||||
}
|
||||
467
rust/pv/src/req/keyslot.rs
Normal file
467
rust/pv/src/req/keyslot.rs
Normal file
@@ -0,0 +1,467 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! IBM Z Host key-slot implementations.
|
||||
|
||||
use openssl::hash::{DigestBytes, MessageDigest};
|
||||
use openssl::pkey::{PKey, PKeyRef, Private, Public};
|
||||
|
||||
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
|
||||
///
|
||||
/// Layout in binary format:
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | Public Host Key Hash (32) |
|
||||
/// | Wrapped(=Encrypted) Request Protection Key(32) |
|
||||
/// | Key Slot Tag (16) |
|
||||
/// |_____________________________________________________________|
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyslotV1(PKey<Public>);
|
||||
|
||||
impl KeyslotV1 {
|
||||
/// Size of a host-key hash
|
||||
pub const PHKH_SIZE: u32 = 0x20;
|
||||
/// Size of complete V1 keyslot in bytes
|
||||
pub const SIZE: usize = 80;
|
||||
|
||||
/// Creates a new Keyslot from the provided public key
|
||||
pub fn new(hostkey: PKey<Public>) -> Self {
|
||||
Self(hostkey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypt for KeyslotV1 {
|
||||
/// 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 Keyslot.
|
||||
///
|
||||
/// # 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 = derive_aes256_gcm_key(priv_key, &self.0)?;
|
||||
let mut wrpk_and_kst =
|
||||
encrypt_aead(&derived_key.into(), &[0; 12], &[], prot_key)?.into_buf();
|
||||
assert_eq!(wrpk_and_kst.len(), 48);
|
||||
let phk: EcPubKeyCoord = self.0.as_ref().try_into()?;
|
||||
|
||||
to.reserve(80);
|
||||
let hash = hash(MessageDigest::sha256(), phk.as_ref())?;
|
||||
assert_eq!(hash.len(), 32);
|
||||
to.extend_from_slice(&hash);
|
||||
to.append(&mut wrpk_and_kst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Return a keyslot with the same key-type as the given host-key
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypt for Keyslot {
|
||||
fn encrypt_to(
|
||||
&self,
|
||||
secret: &[u8],
|
||||
priv_key: &PKeyRef<Private>,
|
||||
to: &mut Vec<u8>,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
Keyslot::V1(ks) => ks.encrypt_to(secret, priv_key, to),
|
||||
Keyslot::V2(ks) => ks.encrypt_to(secret, priv_key, to),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PKey<Public>> for Keyslot {
|
||||
fn from(key: PKey<Public>) -> Self {
|
||||
Keyslot::V1(KeyslotV1::new(key))
|
||||
}
|
||||
}
|
||||
|
||||
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::{DeterministicTestRandGuard, *};
|
||||
|
||||
#[test]
|
||||
fn keyslot() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let exp_keyslot = get_test_asset!("exp/keyslot.bin").to_vec();
|
||||
|
||||
let keyslot = KeyslotV1(host_key);
|
||||
let encr_ks = keyslot.encrypt(&[0x17u8; 32], &cust_key).unwrap();
|
||||
|
||||
assert_eq!(exp_keyslot, encr_ks);
|
||||
|
||||
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)"
|
||||
);
|
||||
}
|
||||
}
|
||||
26
rust/pv/src/req/mod.rs
Normal file
26
rust/pv/src/req/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
//! Request encryption and key management for IBM Z Ultravisor.
|
||||
//!
|
||||
//! This module provides functionality for creating and encrypting requests to the
|
||||
//! IBM Z Ultravisor, including host key management, keyslot encryption, and request
|
||||
//! context handling.
|
||||
|
||||
mod context;
|
||||
mod ec_coord;
|
||||
mod encrypt;
|
||||
mod header;
|
||||
mod hostkey;
|
||||
mod keyslot;
|
||||
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, HybridPKey};
|
||||
pub use keyslot::{Keyslot, KeyslotV1, KeyslotV2};
|
||||
pub use request::{BinReqValues, Request};
|
||||
343
rust/pv/src/req/request.rs
Normal file
343
rust/pv/src/req/request.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
use std::mem::size_of;
|
||||
|
||||
use zerocopy::{FromBytes, Immutable, KnownLayout};
|
||||
|
||||
use super::HostKey;
|
||||
use crate::crypto::{decrypt_aead, SymKey, SymKeyType};
|
||||
use crate::req::context::ReqEncrCtx;
|
||||
use crate::req::header::RequestHdr;
|
||||
use crate::request::Confidential;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// A trait representing a request for the Ultravisor.
|
||||
///
|
||||
/// All requests share a few things:
|
||||
/// * All requests need to be encrypted on a trusted machine
|
||||
/// * All requests have at least one Hostkeyslot
|
||||
///
|
||||
/// The encryption setup is handled by [`ReqEncrCtx`]. Implementers need to pass the data to the
|
||||
/// `ReqEncrCtx` when implementing `encrypt`. A hostkey should be represented by
|
||||
/// [`Keyslot`](super::Keyslot) during encryption.
|
||||
///
|
||||
/// An UV request consists of an authenticated area (AAD), an encrypted area (Encr) and a 16 byte
|
||||
/// tag. The AAD contains a general header and Request type defined data (including Keyslots). It
|
||||
/// is encrypted with an Request protection key (symmetric). This key is encrypted with a
|
||||
/// (generated) private key and the public key of the host system (Host key)
|
||||
/// ```none
|
||||
/// _______________________________________________________________
|
||||
/// | MAGIC (8) Version Number (4) Size (4)|
|
||||
/// | IV (12) Reserved (4)|
|
||||
/// | Reserved (7) Num keyslots (1) Reserved(4) Encr Size (4)|
|
||||
/// | --------------------------------------------------- |
|
||||
/// | Request type dependent AAD data |
|
||||
/// | ---------------------------------------------------- |
|
||||
/// | Encrypted (request type dependent) data |
|
||||
/// | ---------------------------------------------------- |
|
||||
/// | AES GCM Tag (16) |
|
||||
/// |_____________________________________________________________|
|
||||
/// ```
|
||||
pub trait Request {
|
||||
/// Encrypt the request into its binary format
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the encryption fails, the request does not have at
|
||||
/// least a hostkey, or other implementation dependent contracts are not met.
|
||||
fn encrypt(&self, ctx: &ReqEncrCtx) -> Result<Vec<u8>>;
|
||||
|
||||
/// 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) -> Result<()>;
|
||||
}
|
||||
|
||||
/// A struct to represent some parts of a binary/encrypted request.
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub struct BinReqValues<'a> {
|
||||
iv: &'a [u8],
|
||||
aad: &'a [u8],
|
||||
req_dep_aad: &'a [u8],
|
||||
encr: &'a [u8],
|
||||
tag: &'a [u8],
|
||||
version: u32,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<'a> BinReqValues<'a> {
|
||||
pub(crate) const TAG_LEN: usize = SymKeyType::AES_256_GCM_TAG_LEN;
|
||||
|
||||
/// Get the locations from this request.
|
||||
///
|
||||
/// Does minimal sanity test, just tests to prevent panics.
|
||||
/// `req` may be larger than the actual request.
|
||||
pub(crate) fn get(req: &'a [u8]) -> Result<Self> {
|
||||
let (hdr, _) = RequestHdr::read_from_prefix(req).map_err(|_| Error::BinRequestSmall)?;
|
||||
let rql = hdr.rql.get() as usize;
|
||||
let sea = hdr.sea.get() as usize;
|
||||
|
||||
if req.len() < rql || sea + Self::TAG_LEN > rql {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
let aad_size = rql - sea - Self::TAG_LEN;
|
||||
if aad_size < size_of::<RequestHdr>() {
|
||||
return Err(Error::BinRequestSmall);
|
||||
}
|
||||
|
||||
let iv = &req[0x10..0x1c];
|
||||
let aad = &req[..aad_size];
|
||||
let req_dep_aad = &req[size_of::<RequestHdr>()..aad_size];
|
||||
let encr = &req[aad_size..(aad_size + sea)];
|
||||
let tag = &req[rql - Self::TAG_LEN..];
|
||||
|
||||
Ok(Self {
|
||||
iv,
|
||||
aad,
|
||||
req_dep_aad,
|
||||
encr,
|
||||
tag,
|
||||
version: hdr.rqvn.get(),
|
||||
len: rql,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the version of this [`BinReqValues`].
|
||||
pub(crate) fn version(&self) -> u32 {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Returns the length of this [`BinReqValues`].
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns the size of the encrypted area
|
||||
pub(crate) fn sea(&self) -> u32 {
|
||||
self.encr.len() as u32
|
||||
}
|
||||
|
||||
/// Decrypts the encrypted area with the provided key
|
||||
pub(crate) fn decrypt(&self, key: &SymKey) -> Result<Confidential<Vec<u8>>> {
|
||||
let result = decrypt_aead(key, self.iv, self.aad, self.encr, self.tag)?;
|
||||
Ok(result.into_plain())
|
||||
}
|
||||
|
||||
/// Returns a reference to the request dependent authenticated area of this [`BinReqValues`]
|
||||
/// already interpreted.
|
||||
///
|
||||
/// If target struct is larger than the request depended-AAD None is returned. See
|
||||
/// [`FromBytes::ref_from_prefix`]
|
||||
pub(crate) fn req_dep_aad<T>(&self) -> Option<&T>
|
||||
where
|
||||
T: FromBytes + Sized + Immutable + KnownLayout,
|
||||
{
|
||||
T::ref_from_prefix(self.req_dep_aad).map(|s| s.0).ok()
|
||||
}
|
||||
|
||||
/// Returns a reference to the tag of this [`BinReqValues`].
|
||||
pub(crate) fn tag(&self) -> &[u8] {
|
||||
self.tag
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zerocopy::IntoBytes;
|
||||
|
||||
use super::*;
|
||||
use crate::crypto::SymKey;
|
||||
use crate::get_test_asset;
|
||||
use crate::req::{Aad, HybridPKey, Keyslot};
|
||||
use crate::test_utils::*;
|
||||
|
||||
static TEST_MAGIC: [u8; 8] = 0x12345689abcdef00u64.to_be_bytes();
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad() {
|
||||
let (cust_key, host_key) = get_test_keys();
|
||||
let ks = Keyslot::new(HostKey::V1(host_key));
|
||||
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 mut aad_exp = vec![
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // progr
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 168, // 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
|
||||
];
|
||||
aad_exp.extend_from_slice(get_test_asset!("exp/keyslot.bin"));
|
||||
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();
|
||||
|
||||
let aad = Vec::<Aad>::new();
|
||||
|
||||
let aad = ctx.build_aad(0x200, &aad, 16, Some(TEST_MAGIC));
|
||||
assert!(matches!(aad, Err(Error::NoHostkey)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encr_build_aad_nks_many() {
|
||||
let (_, host_key) = get_test_keys();
|
||||
let host_key = HostKey::V1(host_key);
|
||||
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_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();
|
||||
let host_key = HostKey::V1(host_key);
|
||||
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 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);
|
||||
let hdr_bin = hdr.as_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0u8, 0, 0, 0, 0, 0, 0, 0, // magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0, 22, // 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
|
||||
15, // nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn req_hdr2() {
|
||||
let mut hdr = RequestHdr::new(0x200, 0x1234, [0x11; 12], 15, 44, Some(TEST_MAGIC));
|
||||
let hdr_bin = hdr.as_mut_bytes();
|
||||
let hdr_bin_exp = [
|
||||
0x12, 0x34, 0x56, 0x89, 0xab, 0xcd, 0xef, 0, // magic
|
||||
0, 0, 2, 0, // vers
|
||||
0, 0, 0x12, 0x34, // 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
|
||||
15, // nks
|
||||
0, 0, 0, 0, // res
|
||||
0, 0, 0, 44, // sea
|
||||
];
|
||||
assert_eq!(hdr_bin, &hdr_bin_exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bin_req_values_buffer_too_small() {
|
||||
// Create a valid header with request length set to 200 bytes
|
||||
let hdr = RequestHdr::new(0x200, 200, [0x11; 12], 1, 32, Some(TEST_MAGIC));
|
||||
let hdr_bin = hdr.as_bytes();
|
||||
|
||||
// Create a buffer that's smaller than the declared request length (48
|
||||
// bytes)
|
||||
let small_buffer = Vec::from(hdr_bin);
|
||||
|
||||
let result = BinReqValues::get(&small_buffer);
|
||||
assert!(matches!(result, Err(Error::BinRequestSmall)));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
// Copyright IBM Corp.
|
||||
|
||||
// DO NOT USE ANY OF THESE ITEMS IN PRODUCTION CODE
|
||||
// USED FOR INTERNAL UNIT AND FVT TESTING ONLY!!!
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::ffi::c_void;
|
||||
use std::fs;
|
||||
use std::mem::{size_of, ManuallyDrop};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use openssl::{
|
||||
bn::BigNum,
|
||||
ec::{EcGroup, EcKey},
|
||||
error::ErrorStack,
|
||||
nid::Nid,
|
||||
pkey::{PKey, Private, Public},
|
||||
x509::{X509Crl, X509},
|
||||
};
|
||||
use openssl::bn::BigNum;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{PKey, Private, Public};
|
||||
use openssl::x509::{X509Crl, X509};
|
||||
|
||||
/// TEST ONLY! Loads the specified asset into the binary at compile time.
|
||||
///
|
||||
@@ -90,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
|
||||
@@ -99,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])?;
|
||||
@@ -122,3 +151,379 @@ fn get_keypair(pub_coords: &[u8], priv_num: &[u8]) -> Result<PKey<Private>, Erro
|
||||
key.check_key()?;
|
||||
PKey::from_ec_key(key)
|
||||
}
|
||||
|
||||
// To regenerate these bindings, run:
|
||||
// bindgen bindgen_wrapper.h -o bindgen_output.rs \
|
||||
// --allowlist-function "RAND_get0_public" \
|
||||
// --allowlist-function "RAND_get0_private" --allowlist-function "EVP_RAND_fetch" \
|
||||
// --allowlist-function "EVP_RAND_free" --allowlist-function "EVP_RAND_CTX_new" \
|
||||
// --allowlist-function "EVP_RAND_CTX_free" --allowlist-function "EVP_RAND_CTX_up_ref" \
|
||||
// --allowlist-function "EVP_RAND_instantiate" --allowlist-function "RAND_set0_public" \
|
||||
// --allowlist-function "RAND_set0_private" --allowlist-type "OSSL_PARAM"
|
||||
// where bindgen_wrapper.h contains:
|
||||
// #include <openssl/provider.h>
|
||||
// #include <openssl/rand.h>
|
||||
// #include <openssl/evp.h>
|
||||
// #include <openssl/params.h>
|
||||
mod ffi {
|
||||
use std::ffi::{c_char, c_int, c_uchar, c_uint, c_void};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct OsslParam {
|
||||
pub key: *const c_char,
|
||||
pub data_type: c_uint,
|
||||
pub data: *mut c_void,
|
||||
pub data_size: usize,
|
||||
pub return_size: usize,
|
||||
}
|
||||
|
||||
pub enum OsslLibCtx {}
|
||||
pub enum OsslProvider {}
|
||||
pub enum EvpRand {}
|
||||
pub enum EvpRandCtx {}
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn RAND_get0_public(ctx: *mut OsslLibCtx) -> *mut EvpRandCtx;
|
||||
pub fn RAND_get0_private(ctx: *mut OsslLibCtx) -> *mut EvpRandCtx;
|
||||
pub fn EVP_RAND_fetch(
|
||||
libctx: *mut OsslLibCtx,
|
||||
algorithm: *const c_char,
|
||||
properties: *const c_char,
|
||||
) -> *mut EvpRand;
|
||||
pub fn EVP_RAND_free(rand: *mut EvpRand);
|
||||
pub fn EVP_RAND_CTX_new(rand: *mut EvpRand, parent: *mut EvpRandCtx) -> *mut EvpRandCtx;
|
||||
pub fn EVP_RAND_CTX_free(ctx: *mut EvpRandCtx);
|
||||
pub fn EVP_RAND_CTX_up_ref(ctx: *mut EvpRandCtx) -> c_int;
|
||||
pub fn EVP_RAND_instantiate(
|
||||
ctx: *mut EvpRandCtx,
|
||||
strength: c_uint,
|
||||
prediction_resistance: c_int,
|
||||
pstr: *const c_uchar,
|
||||
pstr_len: usize,
|
||||
params: *const OsslParam,
|
||||
) -> c_int;
|
||||
pub fn RAND_set0_public(ctx: *mut OsslLibCtx, rand: *mut EvpRandCtx) -> c_int;
|
||||
pub fn RAND_set0_private(ctx: *mut OsslLibCtx, rand: *mut EvpRandCtx) -> c_int;
|
||||
}
|
||||
}
|
||||
|
||||
// Constants for OSSL_PARAM construction
|
||||
const OSSL_PARAM_OCTET_STRING: u32 = 5;
|
||||
const OSSL_PARAM_UNSIGNED_INTEGER: u32 = 2;
|
||||
const OSSL_PARAM_END: u32 = 0;
|
||||
|
||||
fn ossl_param_end() -> ffi::OsslParam {
|
||||
ffi::OsslParam {
|
||||
key: std::ptr::null(),
|
||||
data_type: OSSL_PARAM_END,
|
||||
data: std::ptr::null_mut(),
|
||||
data_size: 0,
|
||||
return_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn ossl_param_octet_string(name: &'static [u8], data: &mut [u8]) -> ffi::OsslParam {
|
||||
// SAFETY: Constructing OSSL_PARAM for octet string.
|
||||
// - name is a static null-terminated C string, valid for 'static
|
||||
// - data is a valid mutable slice, pointer remains valid during param usage
|
||||
// - Pointer casts are safe as they preserve alignment and validity
|
||||
ffi::OsslParam {
|
||||
key: name.as_ptr().cast(),
|
||||
data_type: OSSL_PARAM_OCTET_STRING,
|
||||
data: data.as_mut_ptr().cast(),
|
||||
data_size: data.len(),
|
||||
return_size: data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ossl_param_uint(name: &'static [u8], value: &mut u32) -> ffi::OsslParam {
|
||||
// SAFETY: Constructing OSSL_PARAM for unsigned integer.
|
||||
// - name is a static null-terminated C string, valid for 'static
|
||||
// - value is a valid mutable reference, pointer remains valid during param usage
|
||||
// - Pointer cast to c_void is safe as it preserves alignment and validity
|
||||
ffi::OsslParam {
|
||||
key: name.as_ptr().cast(),
|
||||
data_type: OSSL_PARAM_UNSIGNED_INTEGER,
|
||||
data: (value as *mut u32).cast::<c_void>(),
|
||||
data_size: size_of::<u32>(),
|
||||
return_size: size_of::<u32>(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FetchedRand(NonNull<ffi::EvpRand>);
|
||||
|
||||
impl FetchedRand {
|
||||
const TEST_RAND_NAME: &'static [u8] = b"TEST-RAND\0";
|
||||
|
||||
fn fetch_test_rand() -> Result<Self, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API with valid parameters.
|
||||
// - null_mut() is valid for optional OSSL_LIB_CTX parameter
|
||||
// - Self::TEST_RAND_NAME is a valid null-terminated C string
|
||||
// - null() is valid for optional properties parameter
|
||||
// - Returns null on error, which we handle via NonNull::new
|
||||
let rand = unsafe {
|
||||
ffi::EVP_RAND_fetch(
|
||||
std::ptr::null_mut(),
|
||||
Self::TEST_RAND_NAME.as_ptr().cast(),
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
NonNull::new(rand).map(Self).ok_or_else(ErrorStack::get)
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut ffi::EvpRand {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FetchedRand {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: self.0 is a valid non-null EVP_RAND pointer that we own.
|
||||
// This is the only place we call free, preventing double-free.
|
||||
unsafe {
|
||||
ffi::EVP_RAND_free(self.0.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RandCtx(NonNull<ffi::EvpRandCtx>);
|
||||
|
||||
impl RandCtx {
|
||||
fn new(rand: &FetchedRand) -> Result<Self, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API with valid parameters.
|
||||
// - rand.as_ptr() is a valid non-null EVP_RAND pointer
|
||||
// - null_mut() is valid for optional parent parameter
|
||||
// - Returns null on error, which we handle via NonNull::new
|
||||
let ctx = unsafe { ffi::EVP_RAND_CTX_new(rand.as_ptr(), std::ptr::null_mut()) };
|
||||
NonNull::new(ctx).map(Self).ok_or_else(ErrorStack::get)
|
||||
}
|
||||
|
||||
fn up_ref(ptr: *mut ffi::EvpRandCtx) -> Result<Self, ErrorStack> {
|
||||
let ptr = NonNull::new(ptr).ok_or_else(ErrorStack::get)?;
|
||||
// SAFETY: ptr is a valid non-null EVP_RAND_CTX pointer.
|
||||
// EVP_RAND_CTX_up_ref increments the reference count.
|
||||
// Returns 1 on success, 0 on failure.
|
||||
let rc = unsafe { ffi::EVP_RAND_CTX_up_ref(ptr.as_ptr()) };
|
||||
if rc == 1 {
|
||||
Ok(Self(ptr))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn current_public() -> Result<Option<Self>, ErrorStack> {
|
||||
// SAFETY: RAND_get0_public returns a borrowed pointer (no ownership transfer).
|
||||
// Returns null if no public RNG is set, which we handle.
|
||||
let ptr = unsafe { ffi::RAND_get0_public(std::ptr::null_mut()) };
|
||||
if ptr.is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Self::up_ref(ptr).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
fn current_private() -> Result<Option<Self>, ErrorStack> {
|
||||
// SAFETY: RAND_get0_private returns a borrowed pointer (no ownership transfer).
|
||||
// Returns null if no private RNG is set, which we handle.
|
||||
let ptr = unsafe { ffi::RAND_get0_private(std::ptr::null_mut()) };
|
||||
if ptr.is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Self::up_ref(ptr).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut ffi::EvpRandCtx {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
|
||||
fn instantiate_test_rand(&self, entropy: &[u8], nonce: &[u8]) -> Result<(), ErrorStack> {
|
||||
// See https://docs.openssl.org/3.1/man7/EVP_RAND-TEST-RAND/#description
|
||||
// for the available parameters.
|
||||
const TEST_ENTROPY_PARAM: &[u8] = b"test_entropy\0";
|
||||
const TEST_NONCE_PARAM: &[u8] = b"test_nonce\0";
|
||||
const STRENGTH_PARAM: &[u8] = b"strength\0";
|
||||
|
||||
let mut entropy = entropy.to_vec();
|
||||
let mut nonce = nonce.to_vec();
|
||||
let mut strength = 256u32;
|
||||
let params = [
|
||||
ossl_param_uint(STRENGTH_PARAM, &mut strength),
|
||||
ossl_param_octet_string(TEST_ENTROPY_PARAM, &mut entropy),
|
||||
ossl_param_octet_string(TEST_NONCE_PARAM, &mut nonce),
|
||||
ossl_param_end(),
|
||||
];
|
||||
|
||||
// SAFETY: Calling OpenSSL C API with valid parameters.
|
||||
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - strength is a valid u32 value
|
||||
// - prediction_resistance=0 is valid
|
||||
// - pstr=null and pstr_len=0 indicate no personalization string
|
||||
// - params points to a valid array of OSSL_PARAM with proper terminator
|
||||
// - All mutable references in params remain valid for the call duration
|
||||
let rc = unsafe {
|
||||
ffi::EVP_RAND_instantiate(
|
||||
self.as_ptr(),
|
||||
strength,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
params.as_ptr(),
|
||||
)
|
||||
};
|
||||
if rc == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn install_as_public(self) -> Result<InstalledRandCtx, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API to transfer ownership.
|
||||
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_public takes ownership of the context on success (rc==1)
|
||||
// - We wrap in ManuallyDrop to prevent double-free since OpenSSL now owns it
|
||||
let rc = unsafe { ffi::RAND_set0_public(std::ptr::null_mut(), self.as_ptr()) };
|
||||
if rc == 1 {
|
||||
Ok(InstalledRandCtx(ManuallyDrop::new(self)))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn install_as_private(self) -> Result<InstalledRandCtx, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API to transfer ownership.
|
||||
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_private takes ownership of the context on success (rc==1)
|
||||
// - We wrap in ManuallyDrop to prevent double-free since OpenSSL now owns it
|
||||
let rc = unsafe { ffi::RAND_set0_private(std::ptr::null_mut(), self.as_ptr()) };
|
||||
if rc == 1 {
|
||||
Ok(InstalledRandCtx(ManuallyDrop::new(self)))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RandCtx {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: self.0 is a valid non-null EVP_RAND_CTX pointer that we own.
|
||||
// This is only called when ownership was NOT transferred to OpenSSL.
|
||||
// InstalledRandCtx uses ManuallyDrop to prevent this from running after transfer.
|
||||
unsafe {
|
||||
ffi::EVP_RAND_CTX_free(self.0.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InstalledRandCtx(ManuallyDrop<RandCtx>);
|
||||
|
||||
impl Drop for InstalledRandCtx {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: Ownership of the EVP_RAND_CTX was transferred to OpenSSL
|
||||
// via RAND_set0_public/private, so we must not call EVP_RAND_CTX_free.
|
||||
// ManuallyDrop prevents RandCtx::drop from running automatically.
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PreviousRandCtx(Option<RandCtx>);
|
||||
|
||||
impl PreviousRandCtx {
|
||||
fn capture_public() -> Result<Self, ErrorStack> {
|
||||
RandCtx::current_public().map(Self)
|
||||
}
|
||||
|
||||
fn capture_private() -> Result<Self, ErrorStack> {
|
||||
RandCtx::current_private().map(Self)
|
||||
}
|
||||
|
||||
fn restore_public(&mut self) {
|
||||
let Some(ctx) = self.0.take() else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: Restoring previously captured RNG context.
|
||||
// - ctx.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_public takes ownership of the context
|
||||
// - We forget ctx to prevent double-free since OpenSSL now owns it
|
||||
// - Ignoring return value as restoration is best-effort during cleanup
|
||||
let _ = unsafe { ffi::RAND_set0_public(std::ptr::null_mut(), ctx.as_ptr()) };
|
||||
std::mem::forget(ctx);
|
||||
}
|
||||
|
||||
fn restore_private(&mut self) {
|
||||
let Some(ctx) = self.0.take() else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: Restoring previously captured RNG context.
|
||||
// - ctx.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_private takes ownership of the context
|
||||
|
||||
// - We forget ctx to prevent double-free since OpenSSL now owns it
|
||||
// - Ignoring return value as restoration is best-effort during cleanup
|
||||
let _ = unsafe { ffi::RAND_set0_private(std::ptr::null_mut(), ctx.as_ptr()) };
|
||||
std::mem::forget(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeterministicTestRandGuard {
|
||||
previous_public: PreviousRandCtx,
|
||||
previous_private: PreviousRandCtx,
|
||||
_public: InstalledRandCtx,
|
||||
_private: InstalledRandCtx,
|
||||
}
|
||||
|
||||
impl DeterministicTestRandGuard {
|
||||
/// Install OpenSSL >= 3 TEST-RAND as the thread-local public/private RNG for deterministic
|
||||
/// tests.
|
||||
///
|
||||
/// The supplied entropy is consumed across generate calls. The nonce is replayed for each
|
||||
/// nonce request. Per OpenSSL documentation, the public and private DRBG instances are
|
||||
/// thread-local, so each thread can safely install its own deterministic RNG without
|
||||
/// affecting other threads.
|
||||
///
|
||||
/// # Thread Safety
|
||||
///
|
||||
/// From OpenSSL documentation (RAND_get0_primary(3)):
|
||||
/// > "The public and private DRBG are thread-local instances, which are used by
|
||||
/// > RAND_bytes() and RAND_priv_bytes(), respectively."
|
||||
///
|
||||
/// Reference: <https://docs.openssl.org/3.1/man3/RAND_get0_primary/>
|
||||
///
|
||||
/// **Note:** RAND_set0_public() and RAND_set0_private() require OpenSSL >= 3.1.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an OpenSSL error if the TEST-RAND provider cannot be configured.
|
||||
pub fn install(entropy: &[u8], nonce: &[u8]) -> Result<Self, ErrorStack> {
|
||||
let previous_public = PreviousRandCtx::capture_public()?;
|
||||
let previous_private = PreviousRandCtx::capture_private()?;
|
||||
let rand = FetchedRand::fetch_test_rand()?;
|
||||
|
||||
let public = RandCtx::new(&rand)?;
|
||||
public.instantiate_test_rand(entropy, nonce)?;
|
||||
let public = public.install_as_public()?;
|
||||
|
||||
let private = RandCtx::new(&rand)?;
|
||||
private.instantiate_test_rand(entropy, nonce)?;
|
||||
let private = private.install_as_private()?;
|
||||
|
||||
Ok(Self {
|
||||
previous_public,
|
||||
previous_private,
|
||||
_public: public,
|
||||
_private: private,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DeterministicTestRandGuard {
|
||||
fn drop(&mut self) {
|
||||
self.previous_private.restore_private();
|
||||
self.previous_public.restore_public();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::x509::{X509Crl, X509};
|
||||
|
||||
use crate::{Error, Result};
|
||||
use openssl::{
|
||||
error::ErrorStack,
|
||||
x509::{X509Crl, X509},
|
||||
};
|
||||
|
||||
/// Read all CRLs from the buffer and parse them into a vector.
|
||||
///
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::req::Keyslot;
|
||||
use crate::static_assert;
|
||||
use crate::{Error, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::arcb::AttestationFlags;
|
||||
use crate::req::KeyslotV1;
|
||||
use crate::{static_assert, Error, Result};
|
||||
|
||||
/// Hash for additional-data stuff used for parsing [`AdditionalData`]
|
||||
pub(super) const PHKH_SIZE: u32 = 0x20;
|
||||
static_assert!(Keyslot::PHKH_SIZE == PHKH_SIZE);
|
||||
static_assert!(KeyslotV1::PHKH_SIZE == PHKH_SIZE);
|
||||
pub(super) const SECRET_STORE_HASH_SIZE: u32 = 0x40;
|
||||
pub(super) const FW_STATE_SIZE: u32 = 0x140;
|
||||
|
||||
|
||||
@@ -2,27 +2,20 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use openssl::pkey::{PKey, Public};
|
||||
use std::mem::size_of;
|
||||
|
||||
use pv_core::static_assert;
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U32};
|
||||
|
||||
use crate::{
|
||||
assert_size,
|
||||
attest::{AttestationMagic, AttestationMeasAlg},
|
||||
crypto::random_array,
|
||||
misc::Flags,
|
||||
req::{Aad, BinReqValues, Keyslot, ReqEncrCtx},
|
||||
request::{Confidential, MagicValue, Request, RequestVersion, SymKey, Zeroize},
|
||||
static_assert,
|
||||
uv::UvFlags,
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
use super::{
|
||||
additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE},
|
||||
AttNonce,
|
||||
};
|
||||
|
||||
use super::additional::{FW_STATE_SIZE, PHKH_SIZE, SECRET_STORE_HASH_SIZE};
|
||||
use super::AttNonce;
|
||||
use crate::attest::{AttestationMagic, AttestationMeasAlg};
|
||||
use crate::crypto::random_array;
|
||||
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, Error, Result};
|
||||
#[cfg(doc)]
|
||||
use crate::{
|
||||
request::SymKeyType,
|
||||
@@ -59,7 +52,7 @@ use crate::{
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use s390_pv::attest::{AttestationFlags, AttestationMeasAlg, AttestationRequest, AttestationVersion};
|
||||
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx};
|
||||
/// # use s390_pv::request::{SymKeyType, Request, ReqEncrCtx, HostKey};
|
||||
/// # fn main() -> s390_pv::Result<()> {
|
||||
/// let att_version = AttestationVersion::One;
|
||||
/// let meas_alg = AttestationMeasAlg::HmacSha512;
|
||||
@@ -68,11 +61,11 @@ 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(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::Aes256)?;
|
||||
/// let ctx = ReqEncrCtx::random(SymKeyType::Aes256Gcm)?;
|
||||
/// let arcb = arcb.encrypt(&ctx)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
@@ -114,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`].
|
||||
@@ -150,6 +149,7 @@ impl AttestationRequest {
|
||||
let values = BinReqValues::get(arcb)?;
|
||||
match values.version().try_into()? {
|
||||
AttestationVersion::One => (),
|
||||
AttestationVersion::Two => (),
|
||||
};
|
||||
|
||||
Ok(values)
|
||||
@@ -243,8 +243,19 @@ impl Request for AttestationRequest {
|
||||
ctx.encrypt_aead(&aad, conf).map(|res| res.into_buf())
|
||||
}
|
||||
|
||||
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,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 {
|
||||
@@ -262,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))
|
||||
}
|
||||
@@ -419,8 +434,9 @@ impl Zeroize for ReqConfData {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use crate::{get_test_asset, request::SymKey, test_utils::get_test_keys};
|
||||
use crate::get_test_asset;
|
||||
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];
|
||||
@@ -428,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),
|
||||
@@ -450,7 +468,37 @@ mod test {
|
||||
arcb.conf.value_mut().nonce = NONCE;
|
||||
arcb.conf.value_mut().meas_key = MEAS;
|
||||
|
||||
arcb.add_hostkey(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()
|
||||
}
|
||||
|
||||
@@ -462,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();
|
||||
@@ -471,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();
|
||||
@@ -480,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());
|
||||
@@ -491,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());
|
||||
@@ -506,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());
|
||||
@@ -516,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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use super::AttNonce;
|
||||
use crate::{
|
||||
attest::AttestationMeasAlg, brcb::BootHdrTags, crypto::calculate_hmac, request::Confidential,
|
||||
uv::ConfigUid, Result,
|
||||
};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
pkey::{PKeyRef, Private},
|
||||
};
|
||||
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;
|
||||
use crate::attest::AttestationMeasAlg;
|
||||
#[cfg(doc)]
|
||||
use crate::attest::AttestationRequest;
|
||||
use crate::brcb::BootHdrTags;
|
||||
use crate::crypto::calculate_hmac;
|
||||
use crate::request::Confidential;
|
||||
use crate::uv::ConfigUid;
|
||||
use crate::Result;
|
||||
|
||||
/// Holds the data to be measured.
|
||||
///
|
||||
@@ -100,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())
|
||||
}
|
||||
}
|
||||
@@ -151,9 +154,10 @@ impl From<Vec<u8>> for AttestationMeasurement {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use openssl::pkey::PKey;
|
||||
|
||||
use super::*;
|
||||
|
||||
const M_KEY: [u8; 64] = [0x41; 64];
|
||||
const BOOT_HDR_TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
|
||||
const CUID: [u8; 16] = [5; 16];
|
||||
|
||||
@@ -2,36 +2,36 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use super::{guest_secret::ListableSecretHdr, user_data::UserData};
|
||||
use crate::{
|
||||
assert_size,
|
||||
crypto::{hkdf_rfc_5869, AeadEncryptionResult},
|
||||
misc::Flags,
|
||||
req::{Aad, BinReqValues, Keyslot, ReqEncrCtx},
|
||||
request::{BootHdrTags, Confidential, Request},
|
||||
secret::{ExtSecret, GuestSecret},
|
||||
uv::{ConfigUid, UvFlags},
|
||||
Result,
|
||||
};
|
||||
use openssl::{
|
||||
md::Md,
|
||||
pkey::{PKey, Private, Public},
|
||||
};
|
||||
use pv_core::{request::RequestVersion, secret::AddSecretMagic, uv::SecretId};
|
||||
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, 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, RequestHdr};
|
||||
use crate::request::{BootHdrTags, Confidential, Request};
|
||||
use crate::secret::{ExtSecret, GuestSecret};
|
||||
use crate::uv::{ConfigUid, UvFlags};
|
||||
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(),
|
||||
@@ -42,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,
|
||||
@@ -93,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))]
|
||||
@@ -100,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)
|
||||
@@ -134,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.
|
||||
///
|
||||
@@ -153,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`].
|
||||
@@ -183,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(),
|
||||
@@ -222,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 {
|
||||
@@ -233,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())
|
||||
}
|
||||
@@ -274,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
|
||||
@@ -310,7 +511,101 @@ impl Request for AddSecretRequest {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_hostkey(&mut self, hostkey: PKey<Public>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,31 +2,26 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#[allow(unused_imports)] // used for more convenient docstring
|
||||
use super::asrcb::AddSecretRequest;
|
||||
use crate::{
|
||||
assert_size,
|
||||
crypto::{hash, random_array, SymKeyType},
|
||||
request::{
|
||||
openssl::{NID_ED25519, NID_ED448},
|
||||
Confidential,
|
||||
},
|
||||
uv::{
|
||||
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
||||
RetrieveCmd, SecretId,
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
nid::Nid,
|
||||
pkey::{Id, PKey, PKeyRef, Private},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::{Id, PKey, PKeyRef, Private};
|
||||
use pv_core::static_assert;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use zerocopy::{BigEndian, KnownLayout};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, U16, U32};
|
||||
use zerocopy::{BigEndian, FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32};
|
||||
|
||||
#[allow(unused_imports)] // used for more convenient docstring
|
||||
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,
|
||||
};
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
const ASSOC_SECRET_SIZE: usize = 32;
|
||||
const CCK_SIZE: usize = 32;
|
||||
@@ -394,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(),
|
||||
}
|
||||
@@ -408,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,
|
||||
@@ -427,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 {
|
||||
@@ -476,13 +500,13 @@ impl UpdateCckHdr {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::HmacShaSizes as HmacSizes;
|
||||
use super::RetrievableSecret::*;
|
||||
use super::*;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use pv_core::uv::AesSizes;
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
use super::RetrievableSecret::*;
|
||||
use super::{HmacShaSizes as HmacSizes, *};
|
||||
|
||||
#[test]
|
||||
fn association() {
|
||||
let secret_value = [0x11; 32];
|
||||
@@ -707,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())
|
||||
}
|
||||
|
||||
@@ -722,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());
|
||||
}
|
||||
|
||||
@@ -735,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]);
|
||||
|
||||
@@ -752,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::{crypto::SymKeyType, pem::Pem, uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD, Result};
|
||||
|
||||
use log::warn;
|
||||
use pv_core::{
|
||||
request::Confidential,
|
||||
uv::{ListableSecretType, RetrievableSecret, RetrieveCmd},
|
||||
};
|
||||
use zerocopy::BigEndian;
|
||||
use zerocopy::{FromBytes, U16};
|
||||
use pv_core::request::Confidential;
|
||||
use pv_core::uv::{ListableSecretType, RetrievableSecret, RetrieveCmd};
|
||||
use zerocopy::{BigEndian, FromBytes, U16};
|
||||
|
||||
use crate::crypto::SymKeyType;
|
||||
use crate::pem::Pem;
|
||||
use crate::uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD;
|
||||
use crate::Result;
|
||||
|
||||
/// An IBM Protected Key
|
||||
///
|
||||
@@ -72,8 +72,8 @@ impl From<RetrieveCmd> for RetrievedSecret {
|
||||
|
||||
match kind {
|
||||
ListableSecretType::Retrievable(RetrievableSecret::PlainText) => {
|
||||
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is the
|
||||
// minimum size
|
||||
// Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is
|
||||
// the minimum size
|
||||
let len = U16::<BigEndian>::read_from_prefix(key.value())
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
@@ -81,8 +81,8 @@ impl From<RetrieveCmd> for RetrievedSecret {
|
||||
|
||||
// Test if the plain text secret has a size:
|
||||
// 1. len <= 8190
|
||||
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the
|
||||
// block length
|
||||
// 2. first two bytes are max 15 less than buffer-size+2 i.e. smaller than the block
|
||||
// length
|
||||
// 3. bytes after len + 2 are zero
|
||||
match len <= MAX_SIZE_PLAIN_PAYLOAD
|
||||
&& key.value().len() - (len + 2) < SymKeyType::AES_256_GCM_BLOCK_LEN
|
||||
@@ -133,6 +133,7 @@ impl RetrievedSecret {
|
||||
RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the data in PEM format.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -148,9 +149,10 @@ impl RetrievedSecret {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pv_core::uv::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn mk_retr(secret: &[u8]) -> RetrievedSecret {
|
||||
let entry = SecretEntry::new(
|
||||
0,
|
||||
|
||||
@@ -2,21 +2,17 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use crate::assert_size;
|
||||
use crate::{
|
||||
crypto::{sign_msg, verify_signature},
|
||||
req::BinReqValues,
|
||||
request::{
|
||||
openssl::pkey::{HasParams, HasPublic, Id, PKey, PKeyRef, Private, Public},
|
||||
RequestMagic,
|
||||
},
|
||||
secret::{AddSecretMagic, AddSecretRequest, AddSecretVersion, UserDataType},
|
||||
Error, Result,
|
||||
};
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::nid::Nid;
|
||||
use zerocopy::{BigEndian, FromBytes, IntoBytes, KnownLayout, U16};
|
||||
|
||||
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, UserDataType};
|
||||
use crate::{assert_size, Error, Result};
|
||||
|
||||
/// User data.
|
||||
///
|
||||
/// User defined data can be:
|
||||
@@ -265,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:
|
||||
@@ -380,7 +374,8 @@ impl From<VerifiedUserData> for Vec<u8> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{get_test_asset, test_utils::get_test_keys};
|
||||
use crate::get_test_asset;
|
||||
use crate::test_utils::get_test_keys;
|
||||
|
||||
#[test]
|
||||
fn sign_null() {
|
||||
|
||||
@@ -2,22 +2,18 @@
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
|
||||
use core::slice;
|
||||
use std::path::Path;
|
||||
|
||||
use helper::{download_first_crl_from_x509, StoreSetupMode};
|
||||
use log::{debug, trace};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::stack::Stack;
|
||||
use openssl::x509::store::X509Store;
|
||||
use openssl::x509::{CrlStatus, X509NameRef, X509Ref, X509StoreContext, X509StoreContextRef, X509};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use helper::download_first_crl_from_x509;
|
||||
#[cfg(test)]
|
||||
use test::download_first_crl_from_x509;
|
||||
|
||||
use crate::error::bail_hkd_verify;
|
||||
use crate::misc::{read_certs, read_file};
|
||||
use crate::openssl_extensions::{StackableX509Crl, X509StoreContextExtension, X509StoreExtension};
|
||||
use crate::Result;
|
||||
|
||||
mod helper;
|
||||
@@ -167,7 +163,8 @@ impl CertVerifier {
|
||||
/// * `cert_paths` - Paths to certificates for the chain of trust
|
||||
/// * `crl_paths` - Paths to certificate revocation lists for the chain of trust
|
||||
/// * `root_ca_path` - Path to the root of trust
|
||||
/// * `offline` - if set to true the verification process will not try to download CRLs from the internet.
|
||||
/// * `offline` - if set to true the verification process will not try to download CRLs from the
|
||||
/// internet.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -183,26 +180,51 @@ impl CertVerifier {
|
||||
Q: AsRef<Path>,
|
||||
R: AsRef<Path>,
|
||||
{
|
||||
let mut store = helper::store_setup(root_ca_path, crl_paths, cert_paths)?;
|
||||
let mut untr_certs = Vec::with_capacity(cert_paths.len());
|
||||
for path in cert_paths {
|
||||
let mut crt = read_certs(&read_file(path, "certificate")?)?;
|
||||
if !offline {
|
||||
for c in &crt {
|
||||
if let Some(crl) = download_first_crl_from_x509(c)? {
|
||||
crl.iter().try_for_each(|c| store.add_crl(c))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
untr_certs.append(&mut crt);
|
||||
}
|
||||
let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs.clone())?;
|
||||
let root_ca_verification = match root_ca_path {
|
||||
Some(_) => helper::RootCaVerification::SkipPinning,
|
||||
None => helper::RootCaVerification::RootCaOrganizationPinning("DigiCert"),
|
||||
};
|
||||
|
||||
// remove the IBM signing certificate from chain.
|
||||
// We have to verify them separately as they are not marked as intermediate certs
|
||||
let (ibm_z_sign_key, chain) = helper::extract_ibm_sign_key(untr_certs)?;
|
||||
// Two-round verification:
|
||||
//
|
||||
// Round 1: Verify chain without CRL checks before downloading files
|
||||
// from URLs from (yet) untrusted certificates.
|
||||
let store_builder = helper::store_setup(
|
||||
root_ca_path.as_ref(),
|
||||
crl_paths,
|
||||
cert_paths,
|
||||
StoreSetupMode::WithoutCrlCheck,
|
||||
)?;
|
||||
helper::verify_chain(
|
||||
&store_builder.build(),
|
||||
&chain,
|
||||
&[&ibm_z_sign_key],
|
||||
&root_ca_verification,
|
||||
)?;
|
||||
|
||||
let store = store.build();
|
||||
helper::verify_chain(&store, &chain, slice::from_ref(&ibm_z_sign_key))?;
|
||||
// Round 2: Download CRLs and verify again, but this time with CRL checks
|
||||
let mut store_builder = helper::store_setup(
|
||||
root_ca_path,
|
||||
crl_paths,
|
||||
cert_paths,
|
||||
StoreSetupMode::WithCrlCheck,
|
||||
)?;
|
||||
if !offline {
|
||||
for cert in &untr_certs {
|
||||
if let Some(crls) = download_first_crl_from_x509(cert)? {
|
||||
crls.iter().try_for_each(|c| store_builder.add_crl(c))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let store = store_builder.build();
|
||||
helper::verify_chain(&store, &chain, &[&ibm_z_sign_key], &root_ca_verification)?;
|
||||
|
||||
Ok(Self {
|
||||
store,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,32 +4,14 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::{helper, helper::*, *};
|
||||
use crate::{utils::read_crls, Error, HkdVerifyErrorType::*};
|
||||
use openssl::{stack::Stack, x509::X509Crl};
|
||||
use std::path::Path;
|
||||
use openssl::stack::Stack;
|
||||
|
||||
use super::helper::*;
|
||||
use super::{helper, *};
|
||||
use crate::test_utils::*;
|
||||
|
||||
// Mock function
|
||||
pub fn download_first_crl_from_x509(cert: &X509Ref) -> Result<Option<Vec<X509Crl>>> {
|
||||
fn mock_download<P: AsRef<Path>>(path: P) -> Result<Vec<X509Crl>> {
|
||||
read_crls(std::fs::read(path)?)
|
||||
}
|
||||
|
||||
for dist_point in x509_dist_points(cert) {
|
||||
{
|
||||
let path = get_cert_asset_path(&dist_point);
|
||||
let crls = if let Ok(buf) = mock_download(&path) {
|
||||
buf
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
return Ok(Some(crls));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
use crate::verify::helper::StoreSetupMode;
|
||||
use crate::Error;
|
||||
use crate::HkdVerifyErrorType::*;
|
||||
|
||||
#[test]
|
||||
fn store_setup() {
|
||||
@@ -37,7 +19,12 @@ fn store_setup() {
|
||||
let inter_path = get_cert_asset_path("inter.crt");
|
||||
let crls: [String; 0] = [];
|
||||
|
||||
let store = helper::store_setup(None::<String>, &crls, &[&ibm_path, &inter_path]);
|
||||
let store = helper::store_setup(
|
||||
None::<String>,
|
||||
&crls,
|
||||
&[&ibm_path, &inter_path],
|
||||
StoreSetupMode::WithCrlCheck,
|
||||
);
|
||||
assert!(store.is_ok());
|
||||
}
|
||||
|
||||
@@ -60,20 +47,51 @@ fn verify_chain_offline() {
|
||||
let root_crt = get_cert_asset_path("root_ca.chained.crt");
|
||||
let certs: [String; 0] = [];
|
||||
|
||||
let store = helper::store_setup(Some(&root_crt), &[&inter_crl], &certs)
|
||||
.unwrap()
|
||||
.build();
|
||||
let store = helper::store_setup(
|
||||
Some(&root_crt),
|
||||
&[&inter_crl],
|
||||
&certs,
|
||||
StoreSetupMode::WithCrlCheck,
|
||||
)
|
||||
.unwrap()
|
||||
.build();
|
||||
|
||||
let mut sk = Stack::<X509>::new().unwrap();
|
||||
sk.push(inter_crt).unwrap();
|
||||
assert!(verify_chain(&store, &sk, &[ibm_crt]).is_ok());
|
||||
assert!(verify_chain(
|
||||
&store,
|
||||
&sk,
|
||||
&[&ibm_crt],
|
||||
&RootCaVerification::RootCaOrganizationPinning(
|
||||
"International Business Machines Corporationn"
|
||||
)
|
||||
)
|
||||
.is_err());
|
||||
assert!(verify_chain(
|
||||
&store,
|
||||
&sk,
|
||||
&[&ibm_crt],
|
||||
&RootCaVerification::RootCaOrganizationPinning("International")
|
||||
)
|
||||
.is_err());
|
||||
assert!(verify_chain(
|
||||
&store,
|
||||
&sk,
|
||||
&[&ibm_crt],
|
||||
&RootCaVerification::RootCaOrganizationPinning(
|
||||
"International Business Machines Corporation"
|
||||
)
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
assert!(verify_chain(&store, &sk, &[&ibm_crt], &RootCaVerification::SkipPinning).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dist_points() {
|
||||
let crt = load_gen_cert("ibm.crt");
|
||||
let res = x509_dist_points(&crt);
|
||||
let exp = vec!["inter_ca.crl"];
|
||||
let exp = vec!["http://inter_ca.crl"];
|
||||
assert_eq!(res, exp);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,25 +3,18 @@
|
||||
// Copyright IBM Corp. 2023
|
||||
|
||||
#![allow(missing_docs)]
|
||||
use openssl::{
|
||||
ec::{EcGroup, EcKey},
|
||||
nid::Nid,
|
||||
pkey::Private,
|
||||
};
|
||||
use s390_pv::{
|
||||
get_test_asset,
|
||||
request::{
|
||||
openssl::pkey::{PKey, Public},
|
||||
BootHdrTags, ReqEncrCtx, Request, SymKey,
|
||||
},
|
||||
secret::{
|
||||
verify_asrcb_and_get_user_data, AddSecretFlags, AddSecretRequest, AddSecretVersion,
|
||||
ExtSecret, GuestSecret,
|
||||
},
|
||||
test_utils::get_test_keys,
|
||||
uv::ConfigUid,
|
||||
Result,
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkey::Private;
|
||||
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, get_test_keys_hybrid, DeterministicTestRandGuard};
|
||||
use s390_pv::uv::ConfigUid;
|
||||
use s390_pv::{get_test_asset, Result};
|
||||
|
||||
const TAGS: BootHdrTags = BootHdrTags::new([1; 64], [2; 64], [3; 64], [4; 16]);
|
||||
const CUID: ConfigUid = [0x42u8; 16];
|
||||
@@ -33,11 +26,16 @@ fn create_asrcb(
|
||||
ext_secret: Option<ExtSecret>,
|
||||
flags: AddSecretFlags,
|
||||
cuid: Option<ConfigUid>,
|
||||
hkd: PKey<Public>,
|
||||
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)?
|
||||
};
|
||||
@@ -45,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),
|
||||
@@ -77,6 +88,24 @@ where
|
||||
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 {
|
||||
GuestSecret::association(ASSOC_ID, ASSOC_SECRET).unwrap()
|
||||
}
|
||||
@@ -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(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(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(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);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE/TCCAuWgAwIBAgIUWT/F3gP9fOTTq3yOMVskcXM8vOAwDQYJKoZIhvcNAQEN
|
||||
MIIFBDCCAuygAwIBAgIUD2bTT6yk21U+ojAQnf3iw5w2EEowDQYJKoZIhvcNAQEN
|
||||
BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
|
||||
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
|
||||
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
|
||||
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl
|
||||
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjQwMzIxMTQ1MjM5WhgPMjM4ODEyMjMxNDUy
|
||||
MzlaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
|
||||
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjYwNzAyMDg0NzQ4WhgPMjM5MTA0MDUwODQ3
|
||||
NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
|
||||
bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h
|
||||
bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv
|
||||
cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw
|
||||
EAYHKoZIzj0CAQYFK4EEACMDgYYABAAT1zpBNemPCT0tEx2S9qnlPxyBgxKl6BSX
|
||||
ghqf3MfOqQKIUdKEbxP0nr2QtIR/MwvBp4YhjxZ9AZtzVtXbQrULTAGlKK4qcf1I
|
||||
W1rYZ5S0b4dmvh2HbIv9wZLWKaZ/ovnvAJk949WDCbBzC3Dy1E3zCaYPefLHHNve
|
||||
dWX8RtFUhnTqm6NxMG8wHQYDVR0fBBYwFDASoBCgDoYMZmFrZV9pYm0uY3J0MA4G
|
||||
A1UdDwEB/wQEAwIDCDAfBgNVHSMEGDAWgBSQCOxEmGwiupA2ER7srMBydGO1aTAd
|
||||
BgNVHQ4EFgQUUl5SPIEseFNeneyuOidsw06L7gQwDQYJKoZIhvcNAQENBQADggIB
|
||||
AFAg/hFlaBT+WNlUmVJlAd6FYr6vvRjJiKxcNBZ5wElzxA5OjuGX6pLYiNkzXSy2
|
||||
N/4nQ3a8fr04IB9Uhx5ncMzSKVkG+4mbN3xmR7f6zZuFWV/T9Aom3LIbQ5KYR2wW
|
||||
EvX8b2xbvd74rKAgavq/iuFRn8skQJGgQk9J2YEApOW9wkoFQRgziuu55Cw5GT+f
|
||||
w9rKgAFTN33ZwfWs86ELJlDOY0aX5373WGccuEKm8y+l2UVLlly125eezz84RSh7
|
||||
5j0VUTjK4ZqpSD8yiPxN+vocV+nY52cgWxrVf7g7wlPunxXWX4rxR0z/mEVDDBAC
|
||||
y7cAxsa4HtmJexuZbjEh8TEgDZXDA24BuFXqtkwqnUpv8KghyKb0KgTQecP9rRhL
|
||||
b2iHdQrALnddMzJzp7Tn+jsR2A2G0lLLsIuIRFR/1eAxecgTMoxXV8N0SJHn6emH
|
||||
Uwif1Qr5JVw4UOFSLW3MT3f70hY5hMzrxowOapcYhyR0vbhGFrF1YI8YmAuk2m4P
|
||||
Dfd5Za5cnfVJiAdNBnNPtV/5wiViX1VJGCrIxgDN6B2VE0VQLp2gKwpsSmhqN4CA
|
||||
qHzAi3Yj3F8vQ2mWQeuABWLAJrIoxXpVYQvTf4uij0ARZRbajQXx+LZnAUsCi5eZ
|
||||
LaYrTVn8NzHlGQocUWljG06GaLY8GNqKPQrZD/+6hIYK
|
||||
EAYHKoZIzj0CAQYFK4EEACMDgYYABAGXMdZTHQ6O1boDC7ncHiILfFqHBRkOaEPs
|
||||
NTntrxSdyWj7CCAa/VkUKVxq3CnyXqbUv8V/fNncU/ibw0o1m/T3SABExiXKfuKl
|
||||
1rUm99FfFeZO2uuIcg0pzbGf+opPttIyR+zp1LLbF3eDo0PEovBLT1J1s6iUIbx8
|
||||
hyfMwUG5JmxdiKN4MHYwJAYDVR0fBB0wGzAZoBegFYYTaHR0cDovL2Zha2VfaWJt
|
||||
LmNydDAOBgNVHQ8BAf8EBAMCAwgwHwYDVR0jBBgwFoAUVpyIVzghY6i5zPVTHvRO
|
||||
xxIW8SswHQYDVR0OBBYEFGWsrMjwzRKXSrSBTPdYPTSU6aHnMA0GCSqGSIb3DQEB
|
||||
DQUAA4ICAQAZ9mWLrrIFk65OJIk6UuX3wwRo824+rsU0TSATzTqRNVb4Ie0KUhMK
|
||||
V3JbLG16PKk/1k/U6t9h+hZp9O8e3netj5rpxUxQVdMXbUdF4FSxNr1MrY0tWynf
|
||||
U121bdsDLAgaZlBtl+DRgltH/ApDNnyVetAT7LpOwdmssnPhFytD5//kE4+s54Fy
|
||||
GsB+aKgvZDHjFgIUkcTYvuB3eTK3J3jrEv8QJYB/Pp3233yMnsSbrE3XLRafJtqQ
|
||||
77Vi7U8/i9aC9XpDPjTC76sH4nPpby0M85+RnDe+wuV98c7oAd+HcBvxlb80dIbu
|
||||
jhzXXC6i9teR9PqjJo32T+Pn+gmZlQf1ddXzrYxFa/4mVnVw6IjQfs8NsZRVios7
|
||||
7vxxm4sLWIJ5s+2gp+jEsV0I+KY/54KLBpJ4XkPRgVhkZRgKieeaFhdknyKOPIPP
|
||||
dlCbt4e4ivGnbtwO5BnLxq/ek+DuHAsSp+dE4z7MtOKsL5HYgKEqTOV0H1UdjXH9
|
||||
ezAUwrrAaMc5cSk5ksnA/LOmGd4fZNhD9r6m/Dwtk3PY6DrESEIh/IXYEcurdNtu
|
||||
vj/Lhscv3AT5DZ7IRbBpHKtNPGezFvcLM18rK+pqHJyoFg77PhKJEUDqRbpbq+VM
|
||||
vFWV/aTqd6rNPpkVwlI1p3I3BculIlmKPaUc9qhsAlQXHWnBAzvSSg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAXR6FCIgd+fjhO/WJ
|
||||
KwLTP01mBDtYkWbVE71jshjLLpZHunRpsYKbQKCwaDmMYLRrQnTxPgZH1PVKzguI
|
||||
7M7n9zyhgYkDgYYABAAT1zpBNemPCT0tEx2S9qnlPxyBgxKl6BSXghqf3MfOqQKI
|
||||
UdKEbxP0nr2QtIR/MwvBp4YhjxZ9AZtzVtXbQrULTAGlKK4qcf1IW1rYZ5S0b4dm
|
||||
vh2HbIv9wZLWKaZ/ovnvAJk949WDCbBzC3Dy1E3zCaYPefLHHNvedWX8RtFUhnTq
|
||||
mw==
|
||||
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBoV15DZCYEBy2dx5Q
|
||||
7PjcNBni/J3FXBka2MZdy402KKrrW6mZI554n4IlpNeyL9Tb4bMp4glYdDsoHLp/
|
||||
jbZ4K4ChgYkDgYYABAGXMdZTHQ6O1boDC7ncHiILfFqHBRkOaEPsNTntrxSdyWj7
|
||||
CCAa/VkUKVxq3CnyXqbUv8V/fNncU/ibw0o1m/T3SABExiXKfuKl1rUm99FfFeZO
|
||||
2uuIcg0pzbGf+opPttIyR+zp1LLbF3eDo0PEovBLT1J1s6iUIbx8hyfMwUG5Jmxd
|
||||
iA==
|
||||
-----END PRIVATE KEY-----
|
||||
|
||||
@@ -3,18 +3,18 @@ MIIDVTCCAT0CAQEwDQYJKoZIhvcNAQENBQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYD
|
||||
VQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9u
|
||||
MTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBv
|
||||
cmF0aW9uMREwDwYDVQQIDAhOZXcgWW9yazEVMBMGA1UEBwwMUG91Z2hrZWVwc2ll
|
||||
MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UXDTI0MDMx
|
||||
MTE1NTIzOVoYDzIzODgxMjIzMTU1MjM5WjAVMBMCAgIrFw0yNDAzMjAxNTUyMzla
|
||||
oCMwITAfBgNVHSMEGDAWgBSQCOxEmGwiupA2ER7srMBydGO1aTANBgkqhkiG9w0B
|
||||
AQ0FAAOCAgEAsRE3oW/VAx2JPESuWDZVbKIX9n26BZUZ2mdmUocRmn7KQ6CAi0Ac
|
||||
L5YRUvYt7kmGo6BSA6rUa0TMoMBtFzfIJ6HQZzEoA/LOkIKMfNHsFFzpIJxghPYJ
|
||||
PfPkXoLpAOBcLDrYWr1bJ3mkvrd6Tuyx02wJEhVmTcF8W/18AFRPuEVg/u3vJmeQ
|
||||
yMdwQZG42kEslvhCTO688vozYeX+dXO1/AXamzYQZyEWk2cBQ28DIc5eg39Tq77N
|
||||
89xqWNK/FWKkF4USn9psiBJQpKEjq+P1jTgdpuN3IGv618hlxS455eGyJrbiTxjD
|
||||
osWGShI+ZVznnqw98aX558hnNWHic+5JVvdJAwNaMMuNNkApFdz328z9dPtcUAZT
|
||||
7mNcKAuY1NcqdxKTPwkGWfmZm+WndGwKLwpshBhF/ImiAb/UiDrLh0jGbQ/FDE3j
|
||||
DtWs4k8eXIibtzGhgjfewdXO66jw5Z55FajcOPgja3uw2g2KEhJ+/VP1YtCvCpkm
|
||||
NX0liKgKLzUGDCbzyrH7QD16T633ebLbak3CtNMMlxZRQO8DQDbDsEKybCG62f/w
|
||||
OmM+QmL7lRFcXDbtVNxBSQKaDBDlNaEGEjM8phz166g+UMD7M6xJjmwJx/G/QvUd
|
||||
98YQfMEIt8IlrIbIfHXnAlj4SJwWNfOw+SN0dCqD5CDEYzYVtZgrNXY=
|
||||
MScwJQYDVQQLDB5JQk0gWiBIb3N0IEtleSBTaWduaW5nIFNlcnZpY2UXDTI2MDYy
|
||||
MjEwNDc0OFoYDzIzOTEwNDA1MTA0NzQ4WjAVMBMCAgIrFw0yNjA3MDExMDQ3NDha
|
||||
oCMwITAfBgNVHSMEGDAWgBRWnIhXOCFjqLnM9VMe9E7HEhbxKzANBgkqhkiG9w0B
|
||||
AQ0FAAOCAgEAQIp0vuCpPifq82vs30SfcTn0ppIeM2s7TSpGL8AVC0+56x8RXl+O
|
||||
1QUw4gOWtlO4m7legzGbCu1Wh3gXFb+6nUhWhT0egtAneoNDXRSHGXdo0nWAoH3J
|
||||
XUN/PaipGvDIEZF/UnehYoRrLG307CgJBQkCErFVvI6XJxqGtc6wgsQM2a5A72zV
|
||||
jlPEAbCsj5k+y7XzJ+vFVA531IHjdtYrEVMh2T0dN821n/yCwwecuMP4semlqYx0
|
||||
xJNm2y4whvyQnDpg4YIfLw5LUc9otvztLYW8h3Si34TO0ApJ6TpEdBdFx23jm/jJ
|
||||
VbfM+g2wFoqsW621BiHn9S1KYD0ENSsbaG6BVRm/CTbPNYw6AdgZS1ggZ+a30NCn
|
||||
ktg6wxVmXK0I3fzgTcOnaspr/rxkBjDi16sfob9yUuaBuTBOgMO0suq7BEFIl97Q
|
||||
tpkVVz9mb53Ixt/ZuF6QpB4DqOyksS8YehBJc9nOGMknOxcS1zYrWJzVJyyrKrlB
|
||||
3VHQ4aYE0WsmP+dpX/QE+gj9/Mp6cvTywVSjW/d9zGOyrSvS/ViU1qsMWrr2iy23
|
||||
tIOaWRVJBi0TX8S8om4ACgi/qwYGTAcH32Hde0nKy07Bm7JJjGUtJbfLzkM/2rjW
|
||||
qvmPlmmPWJxMyVYoY9+mamuDd2jRtJyoULrE5hCIupFKVV7Di8RQJJc=
|
||||
-----END X509 CRL-----
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user